fix bug
This commit is contained in:
@@ -3,9 +3,11 @@ package restful
|
|||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"git.apinb.com/bsm-sdk/core/utils"
|
||||||
"git.apinb.com/quant/gostock/internal/impl"
|
"git.apinb.com/quant/gostock/internal/impl"
|
||||||
"git.apinb.com/quant/gostock/internal/logic/mock"
|
"git.apinb.com/quant/gostock/internal/logic/mock"
|
||||||
"git.apinb.com/quant/gostock/internal/logic/strategy"
|
"git.apinb.com/quant/gostock/internal/logic/strategy"
|
||||||
|
"git.apinb.com/quant/gostock/internal/logic/strategy/indicator"
|
||||||
"git.apinb.com/quant/gostock/internal/logic/strategy/rule"
|
"git.apinb.com/quant/gostock/internal/logic/strategy/rule"
|
||||||
"git.apinb.com/quant/gostock/internal/models"
|
"git.apinb.com/quant/gostock/internal/models"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -13,41 +15,67 @@ import (
|
|||||||
|
|
||||||
func Starter(ctx *gin.Context) {
|
func Starter(ctx *gin.Context) {
|
||||||
log.Println("Strategy START.")
|
log.Println("Strategy START.")
|
||||||
ymd := models.GetYmd()
|
ymdQuery := ctx.DefaultQuery("ymd", "")
|
||||||
|
var ymd int
|
||||||
|
if ymdQuery != "" {
|
||||||
|
ymd = utils.String2Int(ymdQuery)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ymd == 0 {
|
||||||
|
ymd = models.GetYmd()
|
||||||
|
}
|
||||||
|
|
||||||
for _, code := range strategy.GetStocks() {
|
for _, code := range strategy.GetStocks() {
|
||||||
strategy.InitCacheByCode(code)
|
basic := strategy.GetBasic(code)
|
||||||
model := models.NewStratModel("selector", code, ymd)
|
model := models.NewStratModel("selector", code, ymd)
|
||||||
stratRule := rule.NewRule(model)
|
stratRule := rule.NewRule(model)
|
||||||
{
|
{
|
||||||
// 规则:上市时间
|
// 规则:上市时间
|
||||||
stratRule.RunUpDate(strategy.Cache[code].Basic.ListDate)
|
stratRule.RunUpDate(basic.ListDate)
|
||||||
// 规则:是否是ST
|
// 规则:是否是ST
|
||||||
stratRule.RunST(strategy.Cache[code].Basic.Name)
|
stratRule.RunST(basic.Name)
|
||||||
// 规则:行业,剔除夕阳和中性行业
|
// 规则:行业,剔除夕阳和中性行业
|
||||||
stratRule.RunIndustry(strategy.Cache[code].Basic.Industry)
|
stratRule.RunIndustry(basic.Industry)
|
||||||
// 规则:最近20天每天最低价高于5元
|
// 规则:最近20天每天最低价高于5元
|
||||||
stratRule.RunPrice(code)
|
stratRule.RunPrice(code)
|
||||||
// 规则:每天交易额超过10亿
|
// 规则:每天交易额超过10亿
|
||||||
stratRule.RunAmount(code)
|
stratRule.RunAmount(code)
|
||||||
// 规则:ROE 市盈率必须为正
|
// 规则:ROE 市盈率必须为正
|
||||||
stratRule.RunRoe(code)
|
stratRule.RunRoe(code)
|
||||||
// 规则:RSI指标贴近下轨并成上涨趋势
|
|
||||||
stratRule.RunRsi(code)
|
|
||||||
|
|
||||||
// 满足以上规则在让Deepseek分析
|
// 满足以上规则在让Deepseek分析
|
||||||
if model.UpDateDay > 360 && model.StScore > 0 && model.IndustryScore > 1 && model.GtPrice > 0 && model.GtAmount > 0 && model.GtRoe > 0 && model.ScoreRsi > 0 {
|
if model.UpDateDay > 360 && model.StScore > 0 && model.IndustryScore > 1 && model.GtPrice > 0 && model.GtAmount > 0 && model.RoeScore > 0 {
|
||||||
model.AiScore = 0 // 待分析
|
model.AiScore = 0 // 待分析
|
||||||
model.TotalScore = float64(model.IndustryScore + model.StScore + model.GtPrice + model.GtAmount + model.GtRoe + model.ScoreRsi)
|
model.Status = 1
|
||||||
|
model.TotalScore = float64(model.IndustryScore + model.StScore + model.GtPrice + model.GtAmount + model.RoeScore)
|
||||||
model.RecommendDesc = "Rule规则"
|
model.RecommendDesc = "Rule规则"
|
||||||
} else {
|
} else {
|
||||||
|
model.Status = -1
|
||||||
model.AiScore = -2
|
model.AiScore = -2
|
||||||
model.AddDesc("无需AI分析")
|
model.AddDesc("Rule规则不满足,不加入标的")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model.Save()
|
model.Save()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var allowStocks []*models.StratModel
|
||||||
|
impl.DBService.Model(&models.StratModel{}).Where("status=1 and ymd=?", ymd).Find(&allowStocks)
|
||||||
|
for _, m := range allowStocks {
|
||||||
|
// CTA:RSI指标贴近下轨并成上涨趋势
|
||||||
|
indicator.New(m).RunRsi()
|
||||||
|
|
||||||
|
// 满足以上规则在让Deepseek分析
|
||||||
|
if m.ScoreRsi < 0 {
|
||||||
|
impl.DBService.Model(m).Where("id=?", m.ID).Updates(map[string]any{"ai_score": -2, "recommend_desc": m.RecommendDesc + "||" + "无需AI分析,RsiScore:" + utils.Int2String(m.ScoreRsi)})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CTA:MACD指标红绿柱及价量关系
|
||||||
|
score, desc := indicator.New(m).RunMacd()
|
||||||
|
impl.DBService.Model(m).Where("id=?", m.ID).Updates(map[string]any{"macd_score": score, "recommend_desc": m.RecommendDesc + "||" + desc})
|
||||||
|
}
|
||||||
|
|
||||||
// 加入资金流向特大的标的
|
// 加入资金流向特大的标的
|
||||||
var codes []string
|
var codes []string
|
||||||
impl.DBService.Model(&models.MoneyTotal{}).Where("is_greater_pervious = ? and last3_day_mf_amount>?", true, 100000).Pluck("code", &codes)
|
impl.DBService.Model(&models.MoneyTotal{}).Where("is_greater_pervious = ? and last3_day_mf_amount>?", true, 100000).Pluck("code", &codes)
|
||||||
|
|||||||
332
internal/logic/strategy/indicator/macd.go
Normal file
332
internal/logic/strategy/indicator/macd.go
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
package indicator
|
||||||
|
|
||||||
|
/*
|
||||||
|
以MACD指标红绿柱(能量柱)为核心决策依据的A股交易规则系统。旨在通过捕捉柱体的“由绿变红”(空转多)和“由红变绿”(多转空)的临界点,结合柱体的伸缩长度及与股价的背离关系,来量化买卖信号,减少主观情绪干扰。
|
||||||
|
系统需能识别并提示以下几类基于红绿柱的买入场景:
|
||||||
|
参数:
|
||||||
|
日线
|
||||||
|
参数(12,26,9)
|
||||||
|
|
||||||
|
3.1 基础买入信号(柱体反转)
|
||||||
|
需求描述:捕捉下跌动能衰竭、上涨动能启动的瞬间。
|
||||||
|
|
||||||
|
判定标准:
|
||||||
|
|
||||||
|
在零轴下方或零轴附近。
|
||||||
|
MACD绿色柱体开始缩短的最后一根(或第一根红色柱体出现)。
|
||||||
|
量化确认:当MACD柱状线由绿变红(负值变正值),视为买入信号。根据上海证券报的历史回测,在周线级别出现红柱且数值超过5后的第二周开盘买入,长期收益显著。
|
||||||
|
|
||||||
|
3.2 趋势跟随买入(红柱伸长)
|
||||||
|
需求描述:在多头趋势中,捕捉主升浪阶段。
|
||||||
|
判定标准:
|
||||||
|
DIF与DEA在零轴上方运行(水上)。
|
||||||
|
红柱连续增长(每一根比前一根高),表明上涨动能持续增强。
|
||||||
|
持有规则:只要红柱持续伸长,继续持有;若虽为红柱但开始缩短,则应考虑部分止盈。
|
||||||
|
|
||||||
|
|
||||||
|
3.4 底背离买入(左侧交易)
|
||||||
|
需求描述:捕捉价格新低但动能不再的潜在反转点。
|
||||||
|
判定标准:
|
||||||
|
股价创出新低(或收盘价新低)。
|
||||||
|
MACD绿柱的底部(绝对值低点)较前一波绿柱底部抬高(即绿柱长度变短,下跌缩量)。
|
||||||
|
随后出现红柱确认时买入。
|
||||||
|
|
||||||
|
5. 特殊形态与过滤条件
|
||||||
|
为提高胜率,系统需识别以下复合形态:
|
||||||
|
零轴水下金叉后的拒绝死叉:
|
||||||
|
MACD在0轴下方金叉后,股价小幅调整导致快慢线黏合,但并未形成死叉(或形成失败的死叉),随后红柱再次加长。这是洗盘结束、主升浪启动的信号。
|
||||||
|
成交量验证:
|
||||||
|
红柱放大时,需要成交量同步放大的确认。若红柱放大但缩量,信号的可靠性降低。
|
||||||
|
双线联合形态:
|
||||||
|
零轴上,DIF与DEA线粘合后再次分离向上发散,同时红柱伸长,是加速上涨信号
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"git.apinb.com/bsm-sdk/core/utils"
|
||||||
|
"git.apinb.com/quant/gostock/internal/impl"
|
||||||
|
"git.apinb.com/quant/gostock/internal/models"
|
||||||
|
talib "github.com/markcheno/go-talib"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultMacdFast = 12
|
||||||
|
defaultMacdSlow = 26
|
||||||
|
defaultMacdSignal = 9
|
||||||
|
)
|
||||||
|
|
||||||
|
// RunMacd 根据MACD红绿柱及价量关系,对当前标的进行打分与描述
|
||||||
|
func (r *IndicatorFactory) RunMacd() (int, string) {
|
||||||
|
args, _, err := GetArgConfig(r.Model.Code)
|
||||||
|
if err != nil {
|
||||||
|
return -1, "MACD参数错误!"
|
||||||
|
}
|
||||||
|
|
||||||
|
fast := args.EmaFast
|
||||||
|
slow := args.EmaSlow
|
||||||
|
signal := defaultMacdSignal
|
||||||
|
|
||||||
|
if fast <= 0 {
|
||||||
|
fast = defaultMacdFast
|
||||||
|
}
|
||||||
|
if slow <= 0 {
|
||||||
|
slow = defaultMacdSlow
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为了有足够的数据观察柱体形态,这里多取一些K线
|
||||||
|
limit := slow * 6
|
||||||
|
if limit < 200 {
|
||||||
|
limit = 200
|
||||||
|
}
|
||||||
|
|
||||||
|
var closes []float64
|
||||||
|
var vols []float64
|
||||||
|
|
||||||
|
impl.DBService.Model(models.StockDaily{}).
|
||||||
|
Where("ts_code = ?", r.Model.Code).
|
||||||
|
Order("trade_date desc").
|
||||||
|
Limit(limit).
|
||||||
|
Pluck("close", &closes)
|
||||||
|
|
||||||
|
impl.DBService.Model(models.StockDaily{}).
|
||||||
|
Where("ts_code = ?", r.Model.Code).
|
||||||
|
Order("trade_date desc").
|
||||||
|
Limit(limit).
|
||||||
|
Pluck("vol", &vols)
|
||||||
|
|
||||||
|
if len(closes) < slow+signal+5 {
|
||||||
|
return -1, "MACD 数据不足"
|
||||||
|
}
|
||||||
|
|
||||||
|
closes = ReverseSlice(closes)
|
||||||
|
if len(vols) >= len(closes) {
|
||||||
|
vols = vols[:len(closes)]
|
||||||
|
vols = ReverseSlice(vols)
|
||||||
|
} else {
|
||||||
|
// 成交量不足时不做成交量验证
|
||||||
|
vols = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
macdLine, signalLine, hist := talib.Macd(closes, fast, slow, signal)
|
||||||
|
n := len(hist)
|
||||||
|
if n < 5 {
|
||||||
|
return -1, "MACD 计算结果不足"
|
||||||
|
}
|
||||||
|
|
||||||
|
lastIdx := n - 1
|
||||||
|
prevIdx := n - 2
|
||||||
|
|
||||||
|
lastHist := hist[lastIdx]
|
||||||
|
prevHist := hist[prevIdx]
|
||||||
|
lastMacd := macdLine[lastIdx]
|
||||||
|
lastSignal := signalLine[lastIdx]
|
||||||
|
|
||||||
|
r.Model.MacdVal = utils.FloatRound(lastHist, 4)
|
||||||
|
|
||||||
|
score := -1
|
||||||
|
|
||||||
|
// 3.1 基础买入信号:绿柱转红柱,零轴下方或附近
|
||||||
|
if prevHist < 0 && lastHist > 0 && lastMacd <= 0 {
|
||||||
|
score = maxInt(score, 1)
|
||||||
|
return score, fmt.Sprintf("MACD红绿柱反转:由绿转红,零轴下方/附近,hist=%.4f", lastHist)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3.2 趋势跟随买入:红柱连续伸长,DIF/DEA在零轴上方
|
||||||
|
if lastMacd > 0 && lastSignal > 0 && isGrowingPositive(hist, 3) {
|
||||||
|
score = maxInt(score, 2)
|
||||||
|
return score, "MACD红柱连续伸长,DIF/DEA零轴上方,趋势跟随买入信号"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3.4 底背离买入:价格新低但绿柱底部抬高,随后红柱确认
|
||||||
|
if lastHist > 0 && hasBottomDivergence(closes, hist) {
|
||||||
|
score = maxInt(score, 2)
|
||||||
|
return score, "MACD底背离:价格创新低但绿柱底部抬高,红柱确认"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5.1 零轴下金叉后的拒绝死叉:金叉发生在零轴下方,之后未形成有效死叉,红柱再次放大
|
||||||
|
if lastHist > 0 && isGrowingPositive(hist, 2) && hasGoldenCrossRejection(macdLine, signalLine, hist) {
|
||||||
|
score = maxInt(score, 3)
|
||||||
|
return score, "MACD零轴下金叉后拒绝死叉,红柱再次放大,疑似洗盘结束主升浪启动"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 成交量验证:红柱放大但缩量,降低一次评分
|
||||||
|
if score > 0 && vols != nil && lastHist > 0 && isGrowingPositive(hist, 2) {
|
||||||
|
if !isVolumeConfirmed(vols) {
|
||||||
|
score--
|
||||||
|
if score <= 0 {
|
||||||
|
score = -1
|
||||||
|
}
|
||||||
|
return score, "MACD红柱放大但成交量未同步放大,信号可靠性降低"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if score == -1 {
|
||||||
|
if lastHist <= 0 {
|
||||||
|
return -1, fmt.Sprintf("MACD未出现明确多头信号,hist=%.4f", lastHist)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return score, "无信号"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最近k根红柱是否连续伸长(严格递增且均为正)
|
||||||
|
func isGrowingPositive(hist []float64, k int) bool {
|
||||||
|
n := len(hist)
|
||||||
|
if k <= 1 || n < k {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
start := n - k
|
||||||
|
prev := hist[start]
|
||||||
|
if prev <= 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := start + 1; i < n; i++ {
|
||||||
|
if hist[i] <= 0 || hist[i] <= prev {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
prev = hist[i]
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 底背离:最近两段绿柱,后一段对应的价格更低但绿柱绝对值更小
|
||||||
|
func hasBottomDivergence(closes, hist []float64) bool {
|
||||||
|
n := len(hist)
|
||||||
|
if n < 20 || len(closes) != n {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type valley struct {
|
||||||
|
idx int
|
||||||
|
hist float64
|
||||||
|
px float64
|
||||||
|
}
|
||||||
|
|
||||||
|
var valleys []valley
|
||||||
|
inNeg := false
|
||||||
|
curMinIdx := -1
|
||||||
|
|
||||||
|
for i := 1; i < n-1; i++ {
|
||||||
|
if hist[i] < 0 {
|
||||||
|
if !inNeg {
|
||||||
|
inNeg = true
|
||||||
|
curMinIdx = i
|
||||||
|
}
|
||||||
|
if curMinIdx == -1 || hist[i] < hist[curMinIdx] {
|
||||||
|
curMinIdx = i
|
||||||
|
}
|
||||||
|
} else if inNeg {
|
||||||
|
// 负柱结束,记录一段绿柱的底部
|
||||||
|
valleys = append(valleys, valley{
|
||||||
|
idx: curMinIdx,
|
||||||
|
hist: hist[curMinIdx],
|
||||||
|
px: closes[curMinIdx],
|
||||||
|
})
|
||||||
|
inNeg = false
|
||||||
|
curMinIdx = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if inNeg && curMinIdx != -1 {
|
||||||
|
valleys = append(valleys, valley{
|
||||||
|
idx: curMinIdx,
|
||||||
|
hist: hist[curMinIdx],
|
||||||
|
px: closes[curMinIdx],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(valleys) < 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
v1 := valleys[len(valleys)-2]
|
||||||
|
v2 := valleys[len(valleys)-1]
|
||||||
|
|
||||||
|
// 价格创新低,但绿柱绝对值变小(底部抬高)
|
||||||
|
if v2.px >= v1.px {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if math.Abs(v2.hist) >= math.Abs(v1.hist) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 零轴下方金叉后,未形成死叉且红柱再次放大
|
||||||
|
func hasGoldenCrossRejection(macdLine, signalLine, hist []float64) bool {
|
||||||
|
n := len(macdLine)
|
||||||
|
if n < 10 || len(signalLine) != n || len(hist) != n {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
lastIdx := n - 1
|
||||||
|
goldenIdx := -1
|
||||||
|
|
||||||
|
// 在最近一段时间内(最多往前看60根),寻找最近一次位于零轴下方的金叉
|
||||||
|
for i := lastIdx; i >= 1 && i >= n-60; i-- {
|
||||||
|
if macdLine[i-1] < signalLine[i-1] &&
|
||||||
|
macdLine[i] > signalLine[i] &&
|
||||||
|
macdLine[i] < 0 &&
|
||||||
|
signalLine[i] < 0 {
|
||||||
|
goldenIdx = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if goldenIdx == -1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 金叉之后是否出现了真正的死叉?若出现则不算“拒绝死叉”
|
||||||
|
for i := goldenIdx + 1; i <= lastIdx; i++ {
|
||||||
|
if macdLine[i-1] > signalLine[i-1] && macdLine[i] < signalLine[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 红柱再次放大
|
||||||
|
if hist[lastIdx] <= 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if hist[lastIdx] <= hist[goldenIdx] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 成交量确认:最近3根K线成交量均值应不低于之前3根
|
||||||
|
func isVolumeConfirmed(vols []float64) bool {
|
||||||
|
n := len(vols)
|
||||||
|
if n < 6 {
|
||||||
|
// 数据不足时不强制要求放量
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastSum, prevSum float64
|
||||||
|
for i := n - 3; i < n; i++ {
|
||||||
|
lastSum += vols[i]
|
||||||
|
}
|
||||||
|
for i := n - 6; i < n-3; i++ {
|
||||||
|
prevSum += vols[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
lastAvg := lastSum / 3
|
||||||
|
prevAvg := prevSum / 3
|
||||||
|
|
||||||
|
return lastAvg >= prevAvg
|
||||||
|
}
|
||||||
|
|
||||||
|
func maxInt(a, b int) int {
|
||||||
|
if a > b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
13
internal/logic/strategy/indicator/new.go
Normal file
13
internal/logic/strategy/indicator/new.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package indicator
|
||||||
|
|
||||||
|
import "git.apinb.com/quant/gostock/internal/models"
|
||||||
|
|
||||||
|
type IndicatorFactory struct {
|
||||||
|
Model *models.StratModel
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(m *models.StratModel) *IndicatorFactory {
|
||||||
|
return &IndicatorFactory{
|
||||||
|
Model: m,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package rule
|
package indicator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -38,8 +38,8 @@ func GetArgConfig(code string) (*models.StockArgs, *StockArgConf, error) {
|
|||||||
return &args, &conf, nil
|
return &args, &conf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuleFactory) RunRsi(code string) {
|
func (r *IndicatorFactory) RunRsi() {
|
||||||
args, conf, err := GetArgConfig(code)
|
args, conf, err := GetArgConfig(r.Model.Code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.Model.ScoreRsi = -1
|
r.Model.ScoreRsi = -1
|
||||||
r.Model.AddDesc("RSI参数错误!")
|
r.Model.AddDesc("RSI参数错误!")
|
||||||
@@ -59,14 +59,14 @@ func (r *RuleFactory) RunRsi(code string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var close []float64
|
var close []float64
|
||||||
impl.DBService.Model(models.StockDaily{}).Where("ts_code = ?", code).Order("trade_date desc").Limit(args.RsiPeriod*4).Pluck("close", &close)
|
impl.DBService.Model(models.StockDaily{}).Where("ts_code = ?", r.Model.Code).Order("trade_date desc").Limit(args.RsiPeriod*4).Pluck("close", &close)
|
||||||
if len(close) < args.RsiPeriod {
|
if len(close) < args.RsiPeriod {
|
||||||
r.Model.ScoreRsi = -1
|
r.Model.ScoreRsi = -1
|
||||||
r.Model.AddDesc("数据不足")
|
r.Model.AddDesc("数据不足")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newCloses := reverseSlice(close)
|
newCloses := ReverseSlice(close)
|
||||||
args.RsiOversold = args.RsiOversold + offset
|
args.RsiOversold = args.RsiOversold + offset
|
||||||
|
|
||||||
rsiResult := talib.Rsi(newCloses, args.RsiPeriod)
|
rsiResult := talib.Rsi(newCloses, args.RsiPeriod)
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
package rule
|
package indicator
|
||||||
|
|
||||||
// 如果数据是降序(最新在前),需要反转
|
// 如果数据是降序(最新在前),需要反转
|
||||||
func reverseSlice(s []float64) []float64 {
|
func ReverseSlice(s []float64) []float64 {
|
||||||
result := make([]float64, len(s))
|
result := make([]float64, len(s))
|
||||||
for i, j := 0, len(s)-1; i < len(s); i, j = i+1, j-1 {
|
for i, j := 0, len(s)-1; i < len(s); i, j = i+1, j-1 {
|
||||||
result[i] = s[j]
|
result[i] = s[j]
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查值是否为数组最后N个元素中的最小值
|
// 检查值是否为数组最后N个元素中的最小值
|
||||||
@@ -19,8 +19,7 @@ func (r *RuleFactory) RunRoe(code string) {
|
|||||||
var data models.StockFinaIndicator
|
var data models.StockFinaIndicator
|
||||||
err := impl.DBService.Where("ts_code = ?", code).Order("period desc").Limit(1).First(&data).Error
|
err := impl.DBService.Where("ts_code = ?", code).Order("period desc").Limit(1).First(&data).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.Model.GtRoe = -1
|
r.Model.RoeScore = -1
|
||||||
r.Model.ValRoe = -1
|
|
||||||
r.Model.AddDesc("最近无财报,无ROE值!")
|
r.Model.AddDesc("最近无财报,无ROE值!")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -29,12 +28,13 @@ func (r *RuleFactory) RunRoe(code string) {
|
|||||||
r.Model.ValRoe = data.Roe
|
r.Model.ValRoe = data.Roe
|
||||||
|
|
||||||
if data.Roe < MinRoe {
|
if data.Roe < MinRoe {
|
||||||
r.Model.GtRoe = -1
|
r.Model.RoeScore = -1
|
||||||
r.Model.AddDesc(fmt.Sprintf("ROE=%.2f 低于%.2f", data.Roe, MinRoe))
|
r.Model.AddDesc(fmt.Sprintf("ROE=%.2f 低于%.2f", data.Roe, MinRoe))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Model.GtRoe = 1
|
r.Model.RoeScore = 1
|
||||||
|
r.Model.ValRoe = data.Roe
|
||||||
r.Model.AddDesc(fmt.Sprintf("ROE=%.2f 高于%.2f", data.Roe, MinRoe))
|
r.Model.AddDesc(fmt.Sprintf("ROE=%.2f 高于%.2f", data.Roe, MinRoe))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.apinb.com/bsm-sdk/core/database"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StratDesc struct {
|
|
||||||
ID uint `gorm:"primarykey"`
|
|
||||||
CreatedAt time.Time
|
|
||||||
StratModelID uint `gorm:"column:strat_model_id"`
|
|
||||||
Hash string `gorm:"uniqueIndex"`
|
|
||||||
Desc string
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
database.AppendMigrate(&StratDesc{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TableName 设置表名
|
|
||||||
func (StratDesc) TableName() string {
|
|
||||||
return "strat_desc"
|
|
||||||
}
|
|
||||||
@@ -16,21 +16,25 @@ type StratModel struct {
|
|||||||
StratKey string
|
StratKey string
|
||||||
Ymd int
|
Ymd int
|
||||||
Code string
|
Code string
|
||||||
UpDateDay int //上市时间,天数
|
UpDateDay int //上市时间,天数
|
||||||
IndustryScore int // 行业分组
|
IndustryScore int // 行业分组
|
||||||
StScore int // 是否是ST
|
StScore int // 是否是ST
|
||||||
GtAmount int // 每日交易额大于设定值
|
GtAmount int // 每日交易额大于设定值
|
||||||
GtPrice int // 最近20日交易日价格大于设定值
|
GtPrice int // 最近20日交易日价格大于设定值
|
||||||
GtRoe int // ROE 是否大于设定值
|
RoeScore int // ROE 是否大于设定值
|
||||||
|
ValRoe float64 // ROE 值
|
||||||
ScoreRsi int
|
ScoreRsi int
|
||||||
RecommendDesc string // 推荐描述
|
RecommendDesc string // 推荐描述
|
||||||
|
Status int // 状态 -1:不推荐,0:正常 1:推荐
|
||||||
|
|
||||||
|
// macd
|
||||||
|
MacdScore int
|
||||||
|
MacdVal float64
|
||||||
|
|
||||||
// 值
|
// 值
|
||||||
ValRoe float64
|
|
||||||
ValRsiOversold int
|
ValRsiOversold int
|
||||||
ValRsiPrve float64
|
ValRsiPrve float64
|
||||||
ValRsiLast float64
|
ValRsiLast float64
|
||||||
Desc []StratDesc `gorm:"foreignKey:StratModelID"`
|
|
||||||
|
|
||||||
//AI
|
//AI
|
||||||
AiSummary string
|
AiSummary string
|
||||||
|
|||||||
Reference in New Issue
Block a user