From 13cb1ed4797bedaaaab652b2c35b6739217a11ca Mon Sep 17 00:00:00 2001 From: yanweidonog Date: Wed, 25 Feb 2026 11:04:03 +0800 Subject: [PATCH] deving --- cmd/pnl/main.go | 35 +++++++++- internal/logic/mock/order.go | 2 +- internal/logic/restful/starter.go | 18 ++---- internal/logic/strategy/boot.go | 2 +- internal/logic/strategy/indicator/macd.go | 46 ++++++++----- internal/logic/strategy/indicator/new.go | 13 ---- internal/logic/strategy/indicator/rsi.go | 78 +++++++++++------------ internal/models/strat_model.go | 16 ++--- 8 files changed, 118 insertions(+), 92 deletions(-) delete mode 100644 internal/logic/strategy/indicator/new.go diff --git a/cmd/pnl/main.go b/cmd/pnl/main.go index 1aab5f6..de795c0 100644 --- a/cmd/pnl/main.go +++ b/cmd/pnl/main.go @@ -19,13 +19,44 @@ func main() { impl.NewImpl() fmt.Println("") - run() + ClosedTable() + UnclosedTable() fmt.Println("") } -func run() { +func ClosedTable() { tw := table.NewWriter() tw.SetStyle(table.StyleLight) + tw.SetTitle("已清仓列表") + tw.AppendHeader(table.Row{"ID", "Code", "Name", "OpenDate", "OpenPrice", "CloseDate", "ClosePrice", "PNL/Per", "PNLRate(%)"}) + var data []models.MockPosition + impl.DBService.Where("status=?", 1).Find(&data) + var tPNL, tPNLR, cost, sell float64 + for _, item := range data { + var stock models.StockBasic + impl.DBService.Where("ts_code=?", item.Code).First(&stock) + + pnl := utils.FloatRound(item.ClosePrice-item.OpenPrice, 2) + pnlRate := utils.FloatRound(pnl/item.OpenPrice*100, 2) + + tPNL = tPNL + pnl + cost = cost + item.OpenPrice + sell = sell + item.ClosePrice + + tw.AppendRow(table.Row{item.ID, item.Code, stock.Name, item.CreatedAt.Format("2006-01-02"), item.OpenPrice, item.UpdatedAt.Format("2006-01-02"), item.ClosePrice, pnl, pnlRate}) + } + + tPNLR = utils.FloatRound(((sell-cost)/cost)*100, 2) + + tw.AppendFooter(table.Row{"", "", "", "TOTAL", utils.FloatRound(cost, 2), utils.FloatRound(sell, 2), utils.FloatRound(tPNL, 2), tPNLR}) + + fmt.Println(tw.Render()) +} + +func UnclosedTable() { + tw := table.NewWriter() + tw.SetStyle(table.StyleLight) + tw.SetTitle("未平仓列表") tw.AppendHeader(table.Row{"ID", "Code", "Name", "OpenDate", "OpenPrice", "TodayPrice", "PNL/Per", "PNLRate(%)"}) var data []models.MockPosition impl.DBService.Where("status=?", 0).Find(&data) diff --git a/internal/logic/mock/order.go b/internal/logic/mock/order.go index 3aeeebe..1e6ea46 100644 --- a/internal/logic/mock/order.go +++ b/internal/logic/mock/order.go @@ -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 diff --git a/internal/logic/restful/starter.go b/internal/logic/restful/starter.go index 8850653..7d02c45 100644 --- a/internal/logic/restful/starter.go +++ b/internal/logic/restful/starter.go @@ -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 { // 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)}) - } + 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}) // 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}) + 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}) } // 加入资金流向特大的标的 diff --git a/internal/logic/strategy/boot.go b/internal/logic/strategy/boot.go index c231a6a..51bf5ba 100644 --- a/internal/logic/strategy/boot.go +++ b/internal/logic/strategy/boot.go @@ -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) diff --git a/internal/logic/strategy/indicator/macd.go b/internal/logic/strategy/indicator/macd.go index a6ad826..0d5a72b 100644 --- a/internal/logic/strategy/indicator/macd.go +++ b/internal/logic/strategy/indicator/macd.go @@ -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 } - diff --git a/internal/logic/strategy/indicator/new.go b/internal/logic/strategy/indicator/new.go deleted file mode 100644 index a3b9cad..0000000 --- a/internal/logic/strategy/indicator/new.go +++ /dev/null @@ -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, - } -} diff --git a/internal/logic/strategy/indicator/rsi.go b/internal/logic/strategy/indicator/rsi.go index 935d28d..e851c69 100644 --- a/internal/logic/strategy/indicator/rsi.go +++ b/internal/logic/strategy/indicator/rsi.go @@ -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 } diff --git a/internal/models/strat_model.go b/internal/models/strat_model.go index 1879674..896583b 100644 --- a/internal/models/strat_model.go +++ b/internal/models/strat_model.go @@ -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