114 lines
3.1 KiB
Go
114 lines
3.1 KiB
Go
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)
|
||
}
|