deving
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
|
||||
// 加入资金流向特大的标的
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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:推荐
|
||||
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user