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

@@ -19,13 +19,44 @@ func main() {
impl.NewImpl() impl.NewImpl()
fmt.Println("") fmt.Println("")
run() ClosedTable()
UnclosedTable()
fmt.Println("") fmt.Println("")
} }
func run() { func ClosedTable() {
tw := table.NewWriter() tw := table.NewWriter()
tw.SetStyle(table.StyleLight) 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(%)"}) tw.AppendHeader(table.Row{"ID", "Code", "Name", "OpenDate", "OpenPrice", "TodayPrice", "PNL/Per", "PNLRate(%)"})
var data []models.MockPosition var data []models.MockPosition
impl.DBService.Where("status=?", 0).Find(&data) impl.DBService.Where("status=?", 0).Find(&data)

View File

@@ -12,7 +12,7 @@ import (
func Run(key string, ymd int) { func Run(key string, ymd int) {
log.Println("Run Mock Order.") log.Println("Run Mock Order.")
var stocks []*models.StratModel 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 { if len(stocks) == 0 {
log.Println("No data to process.") log.Println("No data to process.")
return return

View File

@@ -45,13 +45,11 @@ func Starter(ctx *gin.Context) {
// 满足以上规则在让Deepseek分析 // 满足以上规则在让Deepseek分析
if model.UpDateDay > 360 && model.StScore > 0 && model.IndustryScore > 1 && model.GtPrice > 0 && model.GtAmount > 0 && model.RoeScore > 0 { 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 // 待Indicator分析
model.Status = 1
model.TotalScore = float64(model.IndustryScore + model.StScore + model.GtPrice + model.GtAmount + model.RoeScore) model.TotalScore = float64(model.IndustryScore + model.StScore + model.GtPrice + model.GtAmount + model.RoeScore)
model.RecommendDesc = "Rule规则" model.AddDesc("Rule规则满足加入标的")
} else { } else {
model.Status = -1 model.Status = -1
model.AiScore = -2
model.AddDesc("Rule规则不满足不加入标的") 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) impl.DBService.Model(&models.StratModel{}).Where("status=1 and ymd=?", ymd).Find(&allowStocks)
for _, m := range allowStocks { for _, m := range allowStocks {
// CTARSI指标贴近下轨并成上涨趋势 // CTARSI指标贴近下轨并成上涨趋势
indicator.New(m).RunRsi() 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})
// 满足以上规则在让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)})
}
// CTAMACD指标红绿柱及价量关系 // CTAMACD指标红绿柱及价量关系
score, desc := indicator.New(m).RunMacd() macdResult := indicator.RunMacd(m.Code)
impl.DBService.Model(m).Where("id=?", m.ID).Updates(map[string]any{"macd_score": score, "recommend_desc": m.RecommendDesc + "||" + desc}) 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 分析任务 // 启动 AI 分析任务
func BootAiStart(key string, ymd int) error { func BootAiStart(key string, ymd int) error {
var datas []models.StratModel 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 { if err != nil {
log.Printf("Failed to query data: %v", err) log.Printf("Failed to query data: %v", err)
return fmt.Errorf("query failed: %w", err) return fmt.Errorf("query failed: %w", err)

View File

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

View File

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