Browse Source

rpc strategy series

main
strange 10 months ago
parent
commit
44a6367518
  1. 4
      api/pub.proto
  2. 4
      api/trading.proto
  3. 4
      config/exchange.toml
  4. 1
      go.mod
  5. 2
      go.sum
  6. 2
      internal/trading/backtrace/backtrace.go
  7. 5
      internal/trading/indicator_context.go
  8. 16
      internal/trading/indicator_series.go
  9. 6
      internal/trading/kline_series.go
  10. 25
      internal/trading/strategy_context.go
  11. 9
      internal/trading/trading_service.go
  12. 24
      pkg/indicator/sam.go
  13. 10
      pkg/strategy/gold_x.go
  14. 2
      pkg/strategy/sig_strategy.go
  15. 11
      pkg/strategy/sig_strategy_exchanges.go
  16. 30
      pkg/strategy/sig_strategy_intervals.go
  17. 18
      pkg/strategy/strategy_multi_interval.go
  18. 12
      pkg/types/series/floats.go

4
api/pub.proto

@ -40,8 +40,8 @@ enum Channel {
} }
enum Side { enum Side {
BUY = 0; SELL = 0;
SELL = 1; BUY = 1;
} }
enum OrderType { enum OrderType {

4
api/trading.proto

@ -50,6 +50,8 @@ message ReqStrategySeries {
int32 count = 8; // k线条数,before或after其中一个为0时有效 int32 count = 8; // k线条数,before或after其中一个为0时有效
} }
message RspStrategySeries { message RspStrategySeries {
repeated int32 signal = 1; // 0.sell,1.buy repeated Side signal = 1; // 0.sell,1.buy
repeated int64 times = 2; repeated int64 times = 2;
repeated bool wins = 3; // k线价格方向是否正确
double winRate = 4;
} }

4
config/exchange.toml

@ -18,8 +18,8 @@ receiveBuffer = 4096
marketSubscribeLimit = 16 marketSubscribeLimit = 16
consumeBatch = 1024 consumeBatch = 1024
consumeLater = 2000 # 时间到达later或者数据累计到batch触发consume consumeLater = 2000 # 时间到达later或者数据累计到batch触发consume
# httpProxy = "http://192.168.1.5:7890" httpProxy = "http://192.168.1.5:7890"
httpProxy = "http://10.255.183.209:7890" # httpProxy = "http://10.255.183.209:7890"
# 模拟盘API交易地址如下: # 模拟盘API交易地址如下:
# REST:https://www.okx.com # REST:https://www.okx.com

1
go.mod

@ -81,6 +81,7 @@ require (
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/leodido/go-urn v1.4.0 // 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-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect

2
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/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 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 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.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.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=

2
pkg/backtrace/backtrace.go → internal/trading/backtrace/backtrace.go

@ -9,5 +9,5 @@ type BacktraceEngine struct {
// 多周期策略回测引擎 // 多周期策略回测引擎
type MultiIntervalBacktraceEngine struct { type MultiIntervalBacktraceEngine struct {
strategy strategy.MultiIntervalStrategy strategy strategy.IIntervalsSigStrategy
} }

5
internal/trading/indicator_context.go

@ -16,6 +16,7 @@ import (
type IOffsetIndicatorContext interface { type IOffsetIndicatorContext interface {
indicator.IIndicatorContext indicator.IIndicatorContext
SetOffset(offset int16) SetOffset(offset int16)
AddOffset(offset int16)
} }
// IndicatorContext 指标上下文, 提供k线序列给指标计算使用 // IndicatorContext 指标上下文, 提供k线序列给指标计算使用
@ -35,6 +36,10 @@ func (c *IndicatorContext) SetOffset(offset int16) {
c.offset = offset c.offset = offset
} }
func (c *IndicatorContext) AddOffset(offset int16) {
c.offset += offset
}
func (c *IndicatorContext) Get(offset int16) (kline types.Kline) { func (c *IndicatorContext) Get(offset int16) (kline types.Kline) {
offset += c.offset offset += c.offset
k, ok := c.kSeries.Get(offset) k, ok := c.kSeries.Get(offset)

16
internal/trading/indicator_series.go

@ -22,17 +22,25 @@ func NewWindowIndicatorSeries(window int16, indicator indicator.IWindowIndicator
} }
func (s *WindowIndicatorSeries) Get(offset int16) (vector float64) { 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) vector = s.indicator.Calculate(s.indicatorContext, s.window)
// 计算结束后还原
s.indicatorContext.AddOffset(-offset)
return return
} }
func (s *WindowIndicatorSeries) Series(offset, count int16) (matrix series.Floats) { func (s *WindowIndicatorSeries) Series(offset, count int16) (matrix series.Floats) {
for i := range count { // 设置当前相对offset
offset += i s.indicatorContext.AddOffset(offset)
s.indicatorContext.SetOffset(offset) for range count {
vector := s.indicator.Calculate(s.indicatorContext, s.window) vector := s.indicator.Calculate(s.indicatorContext, s.window)
matrix.Push(vector) matrix.Push(vector)
offset++
s.indicatorContext.AddOffset(1)
} }
// 计算结束后还原
s.indicatorContext.AddOffset(-offset)
return return
} }

6
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 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) { func (s *KlineSeries) Series(offset, count int16) (klines series.Klines, ok bool) {
if ok = offset >= 0 && offset < MaxSeriesKlines; !ok { if ok = offset >= 0 && offset < MaxSeriesKlines; !ok {
return return
@ -87,7 +89,7 @@ func (s *KlineSeries) Series(offset, count int16) (klines series.Klines, ok bool
length := len(s.klines) length := len(s.klines)
indexEnd := (length - 1) - int(offset) 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 { if ok = indexEnd >= 0 && indexEnd < length && indexStart >= 0 && indexStart < length; !ok {
return return
} }

25
internal/trading/strategy_context.go

@ -2,6 +2,7 @@ package trading
import ( import (
"fmt" "fmt"
"sig-pub/api/pb"
"sig-pub/pkg/indicator" "sig-pub/pkg/indicator"
"sig-pub/pkg/strategy" "sig-pub/pkg/strategy"
"sig-pub/pkg/types" "sig-pub/pkg/types"
@ -20,6 +21,9 @@ type StrategyContext struct {
indicatorContext *IndicatorContext indicatorContext *IndicatorContext
indicatorsW *collect.SyncMap[string, indicator.IWindowIndicator] 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 { 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 发出多信号 // Buy 发出多信号
func (c *StrategyContext) Buy() { func (c *StrategyContext) Buy() {
zlog.Infof("signal buy: %d", c.Get(0).Ts) 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 发出空信号 // Sell 发出空信号
func (c *StrategyContext) Sell() { func (c *StrategyContext) Sell() {
zlog.Infof("signal sell: %d", c.Get(0).Ts) 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)
} }
// 获取窗口类型指标 // 获取窗口类型指标

9
internal/trading/trading_service.go

@ -43,6 +43,7 @@ func (svc *TradingService) Init() (err error) {
// indicator registry // indicator registry
{ {
svc.MustRegisterWindowIndicator(&indicator.RSI{}) svc.MustRegisterWindowIndicator(&indicator.RSI{})
svc.MustRegisterWindowIndicator(&indicator.SMA{})
} }
// strategy registry // 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 totalK := 0
if totalK, err = ctx.Init(req.Exchange, req.InstId, interval, before, after); err != nil { if totalK, err = ctx.Init(req.Exchange, req.InstId, interval, before, after); err != nil {
return return
@ -190,5 +191,11 @@ func (svc *TradingService) StrategySeries(req *pb.ReqStrategySeries, rsp *pb.Rsp
strategyCtx.SetOffset(int16(i)) strategyCtx.SetOffset(int16(i))
strategy.Update(strategyCtx) 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 return
} }

24
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
}

10
pkg/strategy/gold_x.go

@ -2,6 +2,7 @@ package strategy
// GoldX 金叉策略 // GoldX 金叉策略
type GoldX struct { type GoldX struct {
ISigStrategy
} }
func (s *GoldX) New() 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) { func (s *GoldX) Update(ctx ISigStrategyContext) {
sma14 := ctx.IndicatorW("sma", 14) sma14 := ctx.IndicatorW("sma", 14)
sma28 := ctx.IndicatorW("sma", 28) sma28 := ctx.IndicatorW("sma", 28)
// 包装方法 // 包装方法 crossover/crossunder
s14 := sma14.Series(0, 2) s14 := sma14.Series(0, 2)
s28 := sma28.Series(0, 2) s28 := sma28.Series(0, 2)
crossover := s14[0] > s28[0] && s14[1] < s28[1] // 上穿 crossover := s14[0] > s28[0] && s14[1] < s28[1] // 上穿

2
pkg/strategy/sig_strategy.go

@ -10,7 +10,7 @@ import (
"strings" "strings"
) )
// ISigStrategy 交易信号策略接口 // ISigStrategy 交易信号策略接口(单周期单交易所)
type ISigStrategy interface { type ISigStrategy interface {
New() ISigStrategy New() ISigStrategy
Meta() StrategyMeta Meta() StrategyMeta

11
pkg/strategy/sig_strategy_exchanges.go

@ -0,0 +1,11 @@
package strategy
// IExchangesSigStrategy 多交易所策略
type IExchangesSigStrategy interface {
ISigStrategy
}
// IExchangesIntervalsStrategy 多交易所多周期策略
type IExchangesIntervalsStrategy interface {
ISigStrategy
}

30
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
}

18
pkg/strategy/strategy_multi_interval.go

@ -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
}

12
pkg/types/series/floats.go

@ -20,6 +20,10 @@ func (s *Floats) Append(vs ...float64) {
*s = append(*s, vs...) *s = append(*s, vs...)
} }
func (s Floats) Length() int {
return len(s)
}
func (s Floats) Diff() (values Floats) { func (s Floats) Diff() (values Floats) {
for i, v := range s { for i, v := range s {
if i == 0 { if i == 0 {
@ -79,3 +83,11 @@ func (s Floats) Sum() (sum float64) {
} }
return sum return sum
} }
func (s Floats) Avg() (avg float64) {
length := s.Length()
if length == 0 {
return
}
return s.Sum() / float64(length)
}

Loading…
Cancel
Save