From 44a6367518ced219d9093c07823d061da010b7d7 Mon Sep 17 00:00:00 2001 From: strange Date: Thu, 23 Oct 2025 00:57:17 +0800 Subject: [PATCH] rpc strategy series --- api/pub.proto | 4 +-- api/trading.proto | 4 ++- config/exchange.toml | 4 +-- go.mod | 1 + go.sum | 2 ++ .../trading}/backtrace/backtrace.go | 2 +- internal/trading/indicator_context.go | 5 ++++ internal/trading/indicator_series.go | 16 +++++++--- internal/trading/kline_series.go | 6 ++-- internal/trading/strategy_context.go | 25 ++++++++++++++++ internal/trading/trading_service.go | 9 +++++- pkg/indicator/sam.go | 24 +++++++++++++++ pkg/strategy/gold_x.go | 10 ++++++- pkg/strategy/sig_strategy.go | 2 +- pkg/strategy/sig_strategy_exchanges.go | 11 +++++++ pkg/strategy/sig_strategy_intervals.go | 30 +++++++++++++++++++ pkg/strategy/strategy_multi_interval.go | 18 ----------- pkg/types/series/floats.go | 12 ++++++++ 18 files changed, 152 insertions(+), 33 deletions(-) rename {pkg => internal/trading}/backtrace/backtrace.go (83%) create mode 100644 pkg/indicator/sam.go create mode 100644 pkg/strategy/sig_strategy_exchanges.go create mode 100644 pkg/strategy/sig_strategy_intervals.go delete mode 100644 pkg/strategy/strategy_multi_interval.go diff --git a/api/pub.proto b/api/pub.proto index edeabac..8c46000 100644 --- a/api/pub.proto +++ b/api/pub.proto @@ -40,8 +40,8 @@ enum Channel { } enum Side { - BUY = 0; - SELL = 1; + SELL = 0; + BUY = 1; } enum OrderType { diff --git a/api/trading.proto b/api/trading.proto index 12e1c5c..72b174c 100644 --- a/api/trading.proto +++ b/api/trading.proto @@ -50,6 +50,8 @@ message ReqStrategySeries { int32 count = 8; // k线条数,before或after其中一个为0时有效 } message RspStrategySeries { - repeated int32 signal = 1; // 0.sell,1.buy + repeated Side signal = 1; // 0.sell,1.buy repeated int64 times = 2; + repeated bool wins = 3; // 下一根k线价格方向是否正确 + double winRate = 4; } diff --git a/config/exchange.toml b/config/exchange.toml index 4d567a5..c0a42b7 100644 --- a/config/exchange.toml +++ b/config/exchange.toml @@ -18,8 +18,8 @@ receiveBuffer = 4096 marketSubscribeLimit = 16 consumeBatch = 1024 consumeLater = 2000 # 时间到达later或者数据累计到batch触发consume -# httpProxy = "http://192.168.1.5:7890" -httpProxy = "http://10.255.183.209:7890" +httpProxy = "http://192.168.1.5:7890" +# httpProxy = "http://10.255.183.209:7890" # 模拟盘API交易地址如下: # REST:https://www.okx.com diff --git a/go.mod b/go.mod index 50cac87..fa1d500 100644 --- a/go.mod +++ b/go.mod @@ -81,6 +81,7 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/markcheno/go-talib v0.0.0-20250114000313-ec55a20c902f // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect diff --git a/go.sum b/go.sum index d5f24a8..eca0de2 100644 --- a/go.sum +++ b/go.sum @@ -211,6 +211,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/markcheno/go-talib v0.0.0-20250114000313-ec55a20c902f h1:iKq//xEUUaeRoXNcAshpK4W8eSm7HtgI0aNznWtX7lk= +github.com/markcheno/go-talib v0.0.0-20250114000313-ec55a20c902f/go.mod h1:3YUtoVrKWu2ql+iAeRyepSz3fy6a+19hJzGS88+u4u0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= diff --git a/pkg/backtrace/backtrace.go b/internal/trading/backtrace/backtrace.go similarity index 83% rename from pkg/backtrace/backtrace.go rename to internal/trading/backtrace/backtrace.go index 92154fc..8478223 100644 --- a/pkg/backtrace/backtrace.go +++ b/internal/trading/backtrace/backtrace.go @@ -9,5 +9,5 @@ type BacktraceEngine struct { // 多周期策略回测引擎 type MultiIntervalBacktraceEngine struct { - strategy strategy.MultiIntervalStrategy + strategy strategy.IIntervalsSigStrategy } diff --git a/internal/trading/indicator_context.go b/internal/trading/indicator_context.go index f74b8c7..55816a7 100644 --- a/internal/trading/indicator_context.go +++ b/internal/trading/indicator_context.go @@ -16,6 +16,7 @@ import ( type IOffsetIndicatorContext interface { indicator.IIndicatorContext SetOffset(offset int16) + AddOffset(offset int16) } // IndicatorContext 指标上下文, 提供k线序列给指标计算使用 @@ -35,6 +36,10 @@ func (c *IndicatorContext) SetOffset(offset int16) { c.offset = offset } +func (c *IndicatorContext) AddOffset(offset int16) { + c.offset += offset +} + func (c *IndicatorContext) Get(offset int16) (kline types.Kline) { offset += c.offset k, ok := c.kSeries.Get(offset) diff --git a/internal/trading/indicator_series.go b/internal/trading/indicator_series.go index 1254ec3..fc03e22 100644 --- a/internal/trading/indicator_series.go +++ b/internal/trading/indicator_series.go @@ -22,17 +22,25 @@ func NewWindowIndicatorSeries(window int16, indicator indicator.IWindowIndicator } func (s *WindowIndicatorSeries) Get(offset int16) (vector float64) { - s.indicatorContext.SetOffset(offset) + // 根据当前相对offset + s.indicatorContext.AddOffset(offset) vector = s.indicator.Calculate(s.indicatorContext, s.window) + // 计算结束后还原 + s.indicatorContext.AddOffset(-offset) return } func (s *WindowIndicatorSeries) Series(offset, count int16) (matrix series.Floats) { - for i := range count { - offset += i - s.indicatorContext.SetOffset(offset) + // 设置当前相对offset + s.indicatorContext.AddOffset(offset) + for range count { vector := s.indicator.Calculate(s.indicatorContext, s.window) matrix.Push(vector) + + offset++ + s.indicatorContext.AddOffset(1) } + // 计算结束后还原 + s.indicatorContext.AddOffset(-offset) return } diff --git a/internal/trading/kline_series.go b/internal/trading/kline_series.go index 46d5247..e42fcfa 100644 --- a/internal/trading/kline_series.go +++ b/internal/trading/kline_series.go @@ -73,7 +73,9 @@ func (s *KlineSeries) Get(offset int16) (k types.Kline, ok bool) { return *(s.klines[index]), true } -// Series 闭区间升序[count...offset] +// Series 时间升序序列[count...offset] +// offset: 从序列尾部开始偏移量 +// count: 从offset位置开始向序列头部k线条数 func (s *KlineSeries) Series(offset, count int16) (klines series.Klines, ok bool) { if ok = offset >= 0 && offset < MaxSeriesKlines; !ok { return @@ -87,7 +89,7 @@ func (s *KlineSeries) Series(offset, count int16) (klines series.Klines, ok bool length := len(s.klines) indexEnd := (length - 1) - int(offset) - indexStart := (length - 1) - int(offset) - int(count) + indexStart := (length - 1) - int(offset) - int(count) + 1 if ok = indexEnd >= 0 && indexEnd < length && indexStart >= 0 && indexStart < length; !ok { return } diff --git a/internal/trading/strategy_context.go b/internal/trading/strategy_context.go index 9875128..f07496a 100644 --- a/internal/trading/strategy_context.go +++ b/internal/trading/strategy_context.go @@ -2,6 +2,7 @@ package trading import ( "fmt" + "sig-pub/api/pb" "sig-pub/pkg/indicator" "sig-pub/pkg/strategy" "sig-pub/pkg/types" @@ -20,6 +21,9 @@ type StrategyContext struct { indicatorContext *IndicatorContext indicatorsW *collect.SyncMap[string, indicator.IWindowIndicator] + signal []pb.Side // 0.sell,1.buy + signalTimes []int64 + wins []bool } func NewStrategyContext(klineSeries *KlineSeries, indicatorsW *collect.SyncMap[string, indicator.IWindowIndicator]) *StrategyContext { @@ -44,11 +48,32 @@ func (c *StrategyContext) Series(offset, count int16) (klines series.Klines) { // Buy 发出多信号 func (c *StrategyContext) Buy() { zlog.Infof("signal buy: %d", c.Get(0).Ts) + + c.signal = append(c.signal, pb.Side_BUY) + c.signalTimes = append(c.signalTimes, c.Get(0).Ts) + win := false + signalPrice := c.Get(0).Close + if c.indicatorContext.offset > 0 { + c.indicatorContext.AddOffset(-1) + win = c.Get(0).Close.Cmp(signalPrice) > 0 + c.indicatorContext.AddOffset(1) + } + c.wins = append(c.wins, win) } // Sell 发出空信号 func (c *StrategyContext) Sell() { zlog.Infof("signal sell: %d", c.Get(0).Ts) + c.signal = append(c.signal, pb.Side_SELL) + c.signalTimes = append(c.signalTimes, c.Get(0).Ts) + win := false + signalPrice := c.Get(0).Close + if c.indicatorContext.offset > 0 { + c.indicatorContext.AddOffset(-1) + win = c.Get(0).Close.Cmp(signalPrice) < 0 + c.indicatorContext.AddOffset(1) + } + c.wins = append(c.wins, win) } // 获取窗口类型指标 diff --git a/internal/trading/trading_service.go b/internal/trading/trading_service.go index e732b2f..a48814e 100644 --- a/internal/trading/trading_service.go +++ b/internal/trading/trading_service.go @@ -43,6 +43,7 @@ func (svc *TradingService) Init() (err error) { // indicator registry { svc.MustRegisterWindowIndicator(&indicator.RSI{}) + svc.MustRegisterWindowIndicator(&indicator.SMA{}) } // strategy registry { @@ -144,7 +145,7 @@ func (svc *TradingService) IndicatorSeries(req *pb.ReqIndicatorSeries, rsp *pb.R } - before = intervalAdd(before, int64(-req.Window-1)) // 多拉取窗口大小的k线数据 + before = intervalAdd(before, int64(-req.Window)) // 多拉取窗口大小的k线数据 totalK := 0 if totalK, err = ctx.Init(req.Exchange, req.InstId, interval, before, after); err != nil { return @@ -190,5 +191,11 @@ func (svc *TradingService) StrategySeries(req *pb.ReqStrategySeries, rsp *pb.Rsp strategyCtx.SetOffset(int16(i)) strategy.Update(strategyCtx) } + rsp.Signal = strategyCtx.signal + rsp.Times = strategyCtx.signalTimes + rsp.Wins = strategyCtx.wins + // 信号点胜率判断 + wins := collect.Filter(rsp.Wins, func(_ int, win bool) bool { return win }) + rsp.WinRate = float64(len(wins)) / float64(len(rsp.Wins)) return } diff --git a/pkg/indicator/sam.go b/pkg/indicator/sam.go new file mode 100644 index 0000000..800e8f6 --- /dev/null +++ b/pkg/indicator/sam.go @@ -0,0 +1,24 @@ +package indicator + +import ( + "github.com/markcheno/go-talib" +) + +// RSI stateless indicator +// 相对强弱指数 (RSI) rsi define: https://www.investopedia.com/terms/r/rsi.asp +type SMA struct { +} + +// indicator interface +func (c *SMA) Name() string { + return "sma" +} + +// Calculate 计算单根k线sma指标 +func (c *SMA) Calculate(ctx IIndicatorContext, window int16) (vector float64) { + closeSeries := ctx.Series(0, window).Close() + sma := talib.Sma(closeSeries, int(window)) + _ = sma[len(sma)-1] + vector = closeSeries.Avg() + return +} diff --git a/pkg/strategy/gold_x.go b/pkg/strategy/gold_x.go index 2f2e7b0..9482b5a 100644 --- a/pkg/strategy/gold_x.go +++ b/pkg/strategy/gold_x.go @@ -2,6 +2,7 @@ package strategy // GoldX 金叉策略 type GoldX struct { + ISigStrategy } func (s *GoldX) New() ISigStrategy { @@ -15,10 +16,17 @@ func (s *GoldX) Meta() StrategyMeta { } } +func (s *GoldX) Arg() map[string]any { + return map[string]any{ + "short": 14, + "long": 28, + } +} + func (s *GoldX) Update(ctx ISigStrategyContext) { sma14 := ctx.IndicatorW("sma", 14) sma28 := ctx.IndicatorW("sma", 28) - // 包装方法 + // 包装方法 crossover/crossunder s14 := sma14.Series(0, 2) s28 := sma28.Series(0, 2) crossover := s14[0] > s28[0] && s14[1] < s28[1] // 上穿 diff --git a/pkg/strategy/sig_strategy.go b/pkg/strategy/sig_strategy.go index e550f4b..6760145 100644 --- a/pkg/strategy/sig_strategy.go +++ b/pkg/strategy/sig_strategy.go @@ -10,7 +10,7 @@ import ( "strings" ) -// ISigStrategy 交易信号策略接口 +// ISigStrategy 交易信号策略接口(单周期单交易所) type ISigStrategy interface { New() ISigStrategy Meta() StrategyMeta diff --git a/pkg/strategy/sig_strategy_exchanges.go b/pkg/strategy/sig_strategy_exchanges.go new file mode 100644 index 0000000..ab8a6e1 --- /dev/null +++ b/pkg/strategy/sig_strategy_exchanges.go @@ -0,0 +1,11 @@ +package strategy + +// IExchangesSigStrategy 多交易所策略 +type IExchangesSigStrategy interface { + ISigStrategy +} + +// IExchangesIntervalsStrategy 多交易所多周期策略 +type IExchangesIntervalsStrategy interface { + ISigStrategy +} diff --git a/pkg/strategy/sig_strategy_intervals.go b/pkg/strategy/sig_strategy_intervals.go new file mode 100644 index 0000000..ffae5f9 --- /dev/null +++ b/pkg/strategy/sig_strategy_intervals.go @@ -0,0 +1,30 @@ +package strategy + +import ( + "sig-pub/pkg/indicator" + "sig-pub/pkg/types" + "sig-pub/pkg/types/series" +) + +// 多周期k线策略 +type IIntervalsSigStrategy interface { + New() IIntervalsSigStrategy + Meta() StrategyMeta + Update(ctx IIntervalsSigStrategyContext) + DriverIntervals() []types.Interval // 驱动k线周期, 当驱动周期k线更新时则判断调用Update方法 + SubscribeIntervals() []types.Interval // 订阅k线周期, 当同一时间的订阅周期都更新时调用Update方法 +} + +// ISigStrategyContext 策略外部访问能力 +// klineSeries, Indicator +type IIntervalsSigStrategyContext interface { + Buy() // 发出多信号 + Sell() // 发出空信号 + + // Get [0]当前k线 + Get(interval types.Interval, offset int16) types.Kline + // Series [offset...end] + Series(interval types.Interval, offset, count int16) (klines series.Klines) + // 获取窗口类型指标 + IndicatorW(interval types.Interval, name string, window int16) indicator.IIndicatorSeries +} diff --git a/pkg/strategy/strategy_multi_interval.go b/pkg/strategy/strategy_multi_interval.go deleted file mode 100644 index b330951..0000000 --- a/pkg/strategy/strategy_multi_interval.go +++ /dev/null @@ -1,18 +0,0 @@ -package strategy - -import "sig-pub/pkg/types" - -// 多k线周期策略 -type MultiIntervalStrategy interface { - ISigStrategy - DriverInterval() types.Interval // 驱动k线周期, 当驱动周期k线更新时则判断调用Update方法 - SubscribeIntervals() []types.Interval // 订阅k线周期, 当同一时间的订阅周期都更新时调用Update方法 -} - -type MultiExchangeStrategy interface { - ISigStrategy -} - -type MultiIntervalExchangeStrategy interface { - ISigStrategy -} diff --git a/pkg/types/series/floats.go b/pkg/types/series/floats.go index 8c4f325..d7cf0a1 100644 --- a/pkg/types/series/floats.go +++ b/pkg/types/series/floats.go @@ -20,6 +20,10 @@ func (s *Floats) Append(vs ...float64) { *s = append(*s, vs...) } +func (s Floats) Length() int { + return len(s) +} + func (s Floats) Diff() (values Floats) { for i, v := range s { if i == 0 { @@ -79,3 +83,11 @@ func (s Floats) Sum() (sum float64) { } return sum } + +func (s Floats) Avg() (avg float64) { + length := s.Length() + if length == 0 { + return + } + return s.Sum() / float64(length) +}