Files
gostock/internal/logic/strategy/rule/rsi.go
yanweidong fd511e1695 deving
2026-01-29 03:16:11 +08:00

114 lines
3.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package rule
import (
"encoding/json"
"fmt"
"git.apinb.com/quant/gostock/internal/impl"
"git.apinb.com/quant/gostock/internal/logic/types"
"git.apinb.com/quant/gostock/internal/models"
talib "github.com/markcheno/go-talib"
)
type Rsi struct {
Key string
Name string
Conf *StockArgConf
Args *models.StockArgs
}
type StockArgConf struct {
BestByDrawdown string `json:"best_by_drawdown"`
BestByProfit string `json:"best_by_profit"`
BestByReturn string `json:"best_by_return"`
BestBySharpe string `json:"best_by_sharpe"`
BestByWinRate string `json:"best_by_win_rate"`
}
func NewRsi(args *models.StockArgs) *Rsi {
rsi := &Rsi{
Key: "Rsi",
Name: "RSI指标",
Conf: nil,
Args: nil,
}
if args == nil {
return rsi
}
var conf StockArgConf
err := json.Unmarshal([]byte(args.Config), &conf)
if err != nil {
return rsi
}
rsi.Conf = &conf
rsi.Args = args
return rsi
}
func (r *Rsi) Run(code string) *types.RuleResult {
if r.Conf == nil {
return &types.RuleResult{Key: r.Key, Name: r.Name, Score: -1, Desc: "参数错误!"}
}
if r.Conf.BestByProfit != "rsi" {
return &types.RuleResult{Key: r.Key, Name: r.Name, Score: -1, Desc: "BestByProfit!=RSI,BestByProfit=" + r.Conf.BestByProfit}
}
var close []float64
impl.DBService.Model(models.StockDaily{}).Where("ts_code = ?", code).Order("trade_date desc").Limit(r.Args.RsiPeriod*4).Pluck("close", &close)
if len(close) < r.Args.RsiPeriod {
return &types.RuleResult{Key: r.Key, Name: r.Name, Score: -1, Desc: "数据不足"}
}
rsiResult := talib.Rsi(close, r.Args.RsiPeriod)
prveRsi := rsiResult[len(rsiResult)-2]
lastRsi := rsiResult[len(rsiResult)-1]
prveRsiInt := int(prveRsi)
lastRsiInt := int(lastRsi)
// 跌破RSI下轨
if lastRsiInt > r.Args.RsiOversold {
if CheckLowest(close, lastRsiInt, 14) {
return &types.RuleResult{Key: r.Key, Name: r.Name, Score: 1, Desc: fmt.Sprintf("RSI=%d 跌破下轨,14日最低", lastRsiInt)}
}
if CheckLowest(close, lastRsiInt, 20) {
return &types.RuleResult{Key: r.Key, Name: r.Name, Score: 1, Desc: fmt.Sprintf("RSI=%d 跌破下轨,20日最低", lastRsiInt)}
}
return &types.RuleResult{Key: r.Key, Name: r.Name, Score: -1, Desc: fmt.Sprintf("RSI=%d 高于%d", lastRsiInt, r.Args.RsiOversold)}
}
// RSI跌破下轨后呈上涨趋势
if lastRsiInt < prveRsiInt {
return &types.RuleResult{Key: r.Key, Name: r.Name, Score: -1, Desc: fmt.Sprintf("Rsi=%d prveRsi=%d,跌破下轨,持续下跌", lastRsiInt, prveRsiInt)}
}
return &types.RuleResult{Key: r.Key, Name: r.Name, Score: 1, Rsi: lastRsi, Desc: fmt.Sprintf("RSI=%d prveRsi=%d,跌破下轨后呈上涨趋势", lastRsiInt, prveRsiInt)}
}
// 检查值是否为数组最后N个元素中的最小值
func CheckLowest(prices []float64, target int, n int) bool {
if len(prices) == 0 || n <= 0 {
return false
}
// 如果n大于数组长度使用整个数组
if n > len(prices) {
n = len(prices)
}
// 获取最后n个元素
slice := prices[len(prices)-n:]
// 查找最小值
minVal := slice[0]
for _, val := range slice[1:] {
if val < minVal {
minVal = val
}
}
return target == int(minVal)
}