This commit is contained in:
2026-02-25 11:04:03 +08:00
parent 24fe2a97cf
commit 13cb1ed479
8 changed files with 118 additions and 92 deletions

View File

@@ -12,7 +12,7 @@ import (
func Run(key string, ymd int) {
log.Println("Run Mock Order.")
var stocks []*models.StratModel
impl.DBService.Where("strat_key=? and ymd=? and ai_score>=?", key, ymd, 5).Find(&stocks)
impl.DBService.Where("strat_key=? and ymd=? and rsi_score>0 and macd_score>0 ai_score>=?", key, ymd, 5).Find(&stocks)
if len(stocks) == 0 {
log.Println("No data to process.")
return

View File

@@ -45,13 +45,11 @@ func Starter(ctx *gin.Context) {
// 满足以上规则在让Deepseek分析
if model.UpDateDay > 360 && model.StScore > 0 && model.IndustryScore > 1 && model.GtPrice > 0 && model.GtAmount > 0 && model.RoeScore > 0 {
model.AiScore = 0 // 待分析
model.Status = 1
model.Status = 1 // 待Indicator分析
model.TotalScore = float64(model.IndustryScore + model.StScore + model.GtPrice + model.GtAmount + model.RoeScore)
model.RecommendDesc = "Rule规则"
model.AddDesc("Rule规则满足加入标的")
} else {
model.Status = -1
model.AiScore = -2
model.AddDesc("Rule规则不满足不加入标的")
}
@@ -64,16 +62,12 @@ func Starter(ctx *gin.Context) {
impl.DBService.Model(&models.StratModel{}).Where("status=1 and ymd=?", ymd).Find(&allowStocks)
for _, m := range allowStocks {
// CTARSI指标贴近下轨并成上涨趋势
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)})
}
rsiResult := indicator.RunRsi(m.Code)
impl.DBService.Model(m).Where("id=?", m.ID).Updates(map[string]any{"rsi_score": rsiResult.Score, "rsi_val_oversold": rsiResult.Oversold, "rsi_val_prve": rsiResult.Prve, "rsi_val_last": rsiResult.Last, "recommend_desc": m.RecommendDesc + "||Rsi:" + rsiResult.Desc})
// CTAMACD指标红绿柱及价量关系
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})
macdResult := indicator.RunMacd(m.Code)
impl.DBService.Model(m).Where("id=?", m.ID).Updates(map[string]any{"macd_score": macdResult.Score, "macd_val": macdResult.Val, "recommend_desc": m.RecommendDesc + "||Macd:" + macdResult.Desc})
}
// 加入资金流向特大的标的

View File

@@ -16,7 +16,7 @@ func Boot() {
// 启动 AI 分析任务
func BootAiStart(key string, ymd int) error {
var datas []models.StratModel
err := impl.DBService.Where("strat_key=? and ymd=? and ai_score=0", key, ymd).Find(&datas).Error
err := impl.DBService.Where("strat_key=? and ymd=? and (rsi_score>0 or macd_score>0)", key, ymd).Find(&datas).Error
if err != nil {
log.Printf("Failed to query data: %v", err)
return fmt.Errorf("query failed: %w", err)

View File

@@ -44,6 +44,7 @@ MACD在0轴下方金叉后股价小幅调整导致快慢线黏合但并未
import (
"fmt"
"math"
"strings"
"git.apinb.com/bsm-sdk/core/utils"
"git.apinb.com/quant/gostock/internal/impl"
@@ -57,11 +58,21 @@ const (
defaultMacdSignal = 9
)
var (
desc []string
)
type MacdResult struct {
Score int
Val float64
Desc string
}
// RunMacd 根据MACD红绿柱及价量关系对当前标的进行打分与描述
func (r *IndicatorFactory) RunMacd() (int, string) {
args, _, err := GetArgConfig(r.Model.Code)
func RunMacd(code string) *MacdResult {
args, _, err := GetArgConfig(code)
if err != nil {
return -1, "MACD参数错误"
return &MacdResult{Score: -1, Desc: "MACD参数错误"}
}
fast := args.EmaFast
@@ -85,19 +96,19 @@ func (r *IndicatorFactory) RunMacd() (int, string) {
var vols []float64
impl.DBService.Model(models.StockDaily{}).
Where("ts_code = ?", r.Model.Code).
Where("ts_code = ?", code).
Order("trade_date desc").
Limit(limit).
Pluck("close", &closes)
impl.DBService.Model(models.StockDaily{}).
Where("ts_code = ?", r.Model.Code).
Where("ts_code = ?", code).
Order("trade_date desc").
Limit(limit).
Pluck("vol", &vols)
if len(closes) < slow+signal+5 {
return -1, "MACD 数据不足"
return &MacdResult{Score: -1, Desc: "MACD 数据不足"}
}
closes = ReverseSlice(closes)
@@ -112,7 +123,7 @@ func (r *IndicatorFactory) RunMacd() (int, string) {
macdLine, signalLine, hist := talib.Macd(closes, fast, slow, signal)
n := len(hist)
if n < 5 {
return -1, "MACD 计算结果不足"
return &MacdResult{Score: -1, Desc: "MACD 计算结果不足"}
}
lastIdx := n - 1
@@ -123,33 +134,33 @@ func (r *IndicatorFactory) RunMacd() (int, string) {
lastMacd := macdLine[lastIdx]
lastSignal := signalLine[lastIdx]
r.Model.MacdVal = utils.FloatRound(lastHist, 4)
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)
desc = append(desc, "MACD红绿柱反转由绿转红零轴下方/附近")
}
// 3.2 趋势跟随买入红柱连续伸长DIF/DEA在零轴上方
if lastMacd > 0 && lastSignal > 0 && isGrowingPositive(hist, 3) {
score = maxInt(score, 2)
return score, "MACD红柱连续伸长DIF/DEA零轴上方趋势跟随买入信号"
desc = append(desc, "MACD红柱连续伸长DIF/DEA零轴上方趋势跟随买入信号")
}
// 3.4 底背离买入:价格新低但绿柱底部抬高,随后红柱确认
if lastHist > 0 && hasBottomDivergence(closes, hist) {
score = maxInt(score, 2)
return score, "MACD底背离:价格新低但绿柱底部抬高,红柱确认"
desc = append(desc, "MACD价格新低但绿柱底部抬高后续红柱确认")
}
// 5.1 零轴下金叉后的拒绝死叉:金叉发生在零轴下方,之后未形成有效死叉,红柱再次放大
if lastHist > 0 && isGrowingPositive(hist, 2) && hasGoldenCrossRejection(macdLine, signalLine, hist) {
score = maxInt(score, 3)
return score, "MACD零轴下金叉后拒绝死叉红柱再次放大,疑似洗盘结束主升浪启动"
desc = append(desc, "MACD零轴下金叉后拒绝死叉红柱再次放大")
}
// 成交量验证:红柱放大但缩量,降低一次评分
@@ -159,17 +170,21 @@ func (r *IndicatorFactory) RunMacd() (int, string) {
if score <= 0 {
score = -1
}
return score, "MACD红柱放大但成交量未同步放大信号可靠性降低"
return &MacdResult{Score: score, Val: macdVal, Desc: "MACD红柱放大但成交量未同步放大信号可靠性降低"}
}
}
if score == -1 {
if lastHist <= 0 {
return -1, fmt.Sprintf("MACD未出现明确多头信号hist=%.4f", lastHist)
return &MacdResult{Score: -1, Val: macdVal, Desc: fmt.Sprintf("MACD未出现明确多头信号hist=%.4f", lastHist)}
}
}
return score, "无信号"
if score > 0 {
return &MacdResult{Score: score, Val: macdVal, Desc: strings.Join(desc, "||")}
}
return &MacdResult{Score: -1, Val: macdVal, Desc: "无信号"}
}
// 最近k根红柱是否连续伸长严格递增且均为正
@@ -329,4 +344,3 @@ func maxInt(a, b int) int {
}
return b
}

View File

@@ -1,13 +0,0 @@
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,
}
}

View File

@@ -22,6 +22,14 @@ type StockArgConf struct {
BestByWinRate string `json:"best_by_win_rate"`
}
type RsiResult struct {
Score int
Oversold int
Prve float64
Last float64
Desc string
}
func GetArgConfig(code string) (*models.StockArgs, *StockArgConf, error) {
var args models.StockArgs
err := impl.DBService.Where("ts_code = ?", code).First(&args).Error
@@ -38,32 +46,24 @@ func GetArgConfig(code string) (*models.StockArgs, *StockArgConf, error) {
return &args, &conf, nil
}
func (r *IndicatorFactory) RunRsi() {
args, conf, err := GetArgConfig(r.Model.Code)
func RunRsi(code string) *RsiResult {
args, conf, err := GetArgConfig(code)
if err != nil {
r.Model.ScoreRsi = -1
r.Model.AddDesc("RSI参数错误")
return
return &RsiResult{Score: -1, Desc: "RSI参数错误"}
}
if args.RsiOversold == 0 {
r.Model.ScoreRsi = -1
r.Model.AddDesc("RSI RsiOversold=0,参数错误!")
return
return &RsiResult{Score: -1, Desc: "RSI Oversold=0,参数错误!"}
}
if conf.BestByProfit != "rsi" {
r.Model.ScoreRsi = -1
r.Model.AddDesc("BestByProfit不是RSI")
return
return &RsiResult{Score: -1, Desc: "BestByProfit不是RSI"}
}
var close []float64
impl.DBService.Model(models.StockDaily{}).Where("ts_code = ?", r.Model.Code).Order("trade_date desc").Limit(args.RsiPeriod*4).Pluck("close", &close)
impl.DBService.Model(models.StockDaily{}).Where("ts_code = ?", code).Order("trade_date desc").Limit(args.RsiPeriod*4).Pluck("close", &close)
if len(close) < args.RsiPeriod {
r.Model.ScoreRsi = -1
r.Model.AddDesc("数据不足")
return
return &RsiResult{Score: -1, Desc: "数据不足"}
}
newCloses := ReverseSlice(close)
@@ -72,46 +72,46 @@ func (r *IndicatorFactory) RunRsi() {
rsiResult := talib.Rsi(newCloses, args.RsiPeriod)
prveRsi := utils.FloatRound(rsiResult[len(rsiResult)-2], 2)
lastRsi := utils.FloatRound(rsiResult[len(rsiResult)-1], 2)
r.Model.ValRsiLast = lastRsi
r.Model.ValRsiPrve = prveRsi
r.Model.ValRsiOversold = args.RsiOversold
r := &RsiResult{Oversold: args.RsiOversold, Prve: prveRsi, Last: lastRsi}
prveRsiInt := int(prveRsi)
lastRsiInt := int(lastRsi)
if lastRsiInt == 0 {
r.Model.ScoreRsi = -1
r.Model.AddDesc("RSI lastRsiInt=0,计算错误!")
return
r.Score = -1
r.Desc = "RSI lastRsiInt=0,计算错误!"
return r
}
// 跌破RSI下轨
if lastRsiInt > args.RsiOversold {
if CheckLowest(close, lastRsiInt, 14) {
r.Model.ScoreRsi = 1
r.Model.AddDesc(fmt.Sprintf("RSI=%d 跌破下轨,14日最低", lastRsiInt))
return
r.Score = 1
r.Desc = fmt.Sprintf("RSI=%d 跌破下轨,14日最低", lastRsiInt)
return r
}
if CheckLowest(close, lastRsiInt, 20) {
r.Model.ScoreRsi = 1
r.Model.AddDesc(fmt.Sprintf("RSI=%d 跌破下轨,20日最低", lastRsiInt))
return
r.Score = 1
r.Desc = fmt.Sprintf("RSI=%d 跌破下轨,20日最低", lastRsiInt)
return r
}
r.Model.ScoreRsi = -1
r.Model.AddDesc(fmt.Sprintf("RSI=%d 高于Oversold%d", lastRsiInt, args.RsiOversold))
return
r.Score = -1
r.Desc = fmt.Sprintf("RSI=%d 高于Oversold%d", lastRsiInt, args.RsiOversold)
return r
}
// RSI跌破下轨后呈上涨趋势
if lastRsiInt < prveRsiInt {
r.Model.ScoreRsi = -1
r.Model.AddDesc(fmt.Sprintf("Rsi=%d prveRsi=%d,突破下轨,持续下跌", lastRsiInt, prveRsiInt))
return
r.Score = -1
r.Desc = fmt.Sprintf("Rsi=%d prveRsi=%d,突破下轨,持续下跌", lastRsiInt, prveRsiInt)
return r
} else if lastRsiInt == prveRsiInt {
r.Model.ScoreRsi = 1
r.Model.AddDesc(fmt.Sprintf("Rsi=%d prveRsi=%d,突破下轨,与前一交易日无太大波动", lastRsiInt, prveRsiInt))
return
r.Score = 1
r.Desc = fmt.Sprintf("Rsi=%d prveRsi=%d,突破下轨,与前一交易日无太大波动", lastRsiInt, prveRsiInt)
return r
}
r.Model.ScoreRsi = 2
r.Model.AddDesc(fmt.Sprintf("Rsi=%d prveRsi=%d,突破下轨后呈上涨趋势", lastRsiInt, prveRsiInt))
return
r.Score = 1
r.Desc = fmt.Sprintf("Rsi=%d prveRsi=%d,突破下轨后呈上涨趋势", lastRsiInt, prveRsiInt)
return r
}

View File

@@ -23,18 +23,18 @@ type StratModel struct {
GtPrice int // 最近20日交易日价格大于设定值
RoeScore int // ROE 是否大于设定值
ValRoe float64 // ROE 值
ScoreRsi int
RecommendDesc string // 推荐描述
Status int // 状态 -1:不推荐0:正常 1:推荐
RecommendDesc string // 推荐描述
Status int // 状态 -1:不推荐0:正常 1:推荐
// macd
MacdScore int
MacdScore int `gorm:"default:0"`
MacdVal float64
// 值
ValRsiOversold int
ValRsiPrve float64
ValRsiLast float64
// Rsi
RsiScore int `gorm:"default:0"`
RsiValOversold int
RsiValPrve float64
RsiValLast float64
//AI
AiSummary string