Browse Source

rpc indicator series

main
strange 10 months ago
parent
commit
47fadb6f9f
  1. 31
      api/trading.proto
  2. 2
      cmd/trading/main.go
  3. 122
      internal/trading/indicator_context.go
  4. 38
      internal/trading/indicator_series.go
  5. 46
      internal/trading/kline_series.go
  6. 21
      internal/trading/kline_store.go
  7. 61
      internal/trading/strategy_context.go
  8. 22
      internal/trading/trading_grpc_server.go
  9. 124
      internal/trading/trading_service.go
  10. 7
      pkg/grpc/interceptor/recover_interceptor.go
  11. 10
      pkg/indicator/base.go
  12. 4
      pkg/indicator/rsi.go
  13. 4
      pkg/strategy/sig_strategy.go

31
api/trading.proto

@ -6,6 +6,8 @@ option go_package = "./pb";
service TradingService {
rpc SubIndicator(IndicatorSubReq) returns (stream Indicator); //
rpc IndicatorSeries(ReqIndicatorSeries) returns (RspIndicatorSeries); //
rpc StrategySeries(ReqStrategySeries) returns (RspStrategySeries); //
}
message IndicatorSubReq {
@ -22,3 +24,32 @@ message Indicator {
int64 Ts = 5;
bytes payload = 8;
}
message ReqIndicatorSeries {
string indicator = 1;
int32 window = 2; //
ExchangeType exchange = 3;
string instId = 4;
string interval = 5;
int64 before = 6; // 0
int64 after = 7; // 0
int32 count = 8; // k线条数,before或after其中一个为0时有效
}
message RspIndicatorSeries{
repeated double matrix = 1;
repeated int64 times = 2;
}
message ReqStrategySeries {
string strategy = 1;
ExchangeType exchange = 3;
string instId = 4;
string interval = 5;
int64 before = 6; // 0
int64 after = 7; // 0
int32 count = 8; // k线条数,before或after其中一个为0时有效
}
message RspStrategySeries {
repeated int32 signal = 1; // 0.sell,1.buy
repeated int64 times = 2;
}

2
cmd/trading/main.go

@ -69,7 +69,7 @@ func main() {
}
// grpc server
tradingGrpcServer := trading.NewTradingGrpcServer()
tradingGrpcServer := trading.NewTradingGrpcServer(tradingService)
if err := tradingGrpcServer.Init(); err != nil {
panic(err)
}

122
internal/trading/indicator_context.go

@ -0,0 +1,122 @@
package trading
import (
"context"
"fmt"
"io"
"sig-pub/api/pb"
"sig-pub/pkg/indicator"
"sig-pub/pkg/types"
"sig-pub/pkg/types/series"
"sig-pub/pkg/zlog"
"google.golang.org/grpc"
)
type IOffsetIndicatorContext interface {
indicator.IIndicatorContext
SetOffset(offset int16)
}
// IndicatorContext 指标上下文, 提供k线序列给指标计算使用
type IndicatorContext struct {
IOffsetIndicatorContext
kSeries *KlineSeries
offset int16
}
func NewIndicatorContext(kSeries *KlineSeries) *IndicatorContext {
return &IndicatorContext{
kSeries: kSeries,
}
}
func (c *IndicatorContext) SetOffset(offset int16) {
c.offset = offset
}
func (c *IndicatorContext) Get(offset int16) (kline types.Kline) {
offset += c.offset
k, ok := c.kSeries.Get(offset)
if !ok {
lastTs := c.kSeries.LastTs()
zlog.Warningf("get kline series offset out of range: offset=%d, lastTs=%d", offset, lastTs)
panic(fmt.Errorf("get kline series offset out of range: offset=%d", offset))
}
return k
}
func (c *IndicatorContext) Series(offset, count int16) (klines series.Klines) {
offset += c.offset
ks, ok := c.kSeries.Series(offset, count)
if !ok {
zlog.Warningf("get kline series offset out of range: offset=%d, count=%d, lastTs=%d", offset, count, c.kSeries.LastTs())
panic(fmt.Errorf("get kline series offset out of range: offset=%d, count=%d", offset, count))
}
return ks
}
type HistoryIndicatorContext struct {
IOffsetIndicatorContext
exchangeClient pb.ExchangeServiceClient
context *IndicatorContext
}
func NewHistoryIndicatorContext(exchangeClient pb.ExchangeServiceClient) *HistoryIndicatorContext {
return &HistoryIndicatorContext{
exchangeClient: exchangeClient,
}
}
func (c *HistoryIndicatorContext) Init(exchange pb.ExchangeType, instId string, interval types.Interval, before, after int64) (totalK int, err error) {
// fetch history series
req := &pb.ReqHistoryKlineStream{
Exchange: exchange,
InstId: instId,
Interval: string(interval),
Count: 0,
Before: before,
After: after,
}
stream, err := c.exchangeClient.HistoryKlineStream(context.Background(), req, grpc.UseCompressor("snappy"))
if err != nil {
zlog.Errorf("fetch history kline stream error: instId=%s(%s), interval=%s, %#v, err=%v", instId, exchange, interval, req, err)
return
}
klineSeries := NewKlineSeries(exchange, instId, interval)
for {
msg, err0 := stream.Recv()
if err0 == io.EOF {
break
}
if err0 != nil {
err = err0
zlog.Error("fetch kline stream recv error: ", err0)
return
}
// zlog.Debugf("recv: %s(%s), %s, branch=%d, ts=%d~%d", instId, exchange, interval, len(msg.Klines), msg.Klines[0].Ts, msg.Klines[len(msg.Klines)-1].Ts)
totalK += len(msg.Klines)
for _, k := range msg.Klines {
kline := new(types.Kline)
kline.ParsePBKline(exchange, k)
if lastTs, ok := klineSeries.Update(kline); !ok {
err = fmt.Errorf("history stream kline not series: last=%d", lastTs)
return
}
}
}
c.context = NewIndicatorContext(klineSeries)
return
}
func (c *HistoryIndicatorContext) SetOffset(offset int16) {
c.context.SetOffset(offset)
}
func (c *HistoryIndicatorContext) Get(offset int16) (kline types.Kline) {
return c.context.Get(offset)
}
func (c *HistoryIndicatorContext) Series(offset, count int16) (klines series.Klines) {
return c.context.Series(offset, count)
}

38
internal/trading/indicator_series.go

@ -0,0 +1,38 @@
package trading
import (
"sig-pub/pkg/indicator"
"sig-pub/pkg/types/series"
)
// WindowIndicatorSeries 封装
type WindowIndicatorSeries struct {
indicator.IIndicatorSeries
window int16
indicator indicator.IWindowIndicator
indicatorContext IOffsetIndicatorContext
}
func NewWindowIndicatorSeries(window int16, indicator indicator.IWindowIndicator, indicatorContext IOffsetIndicatorContext) *WindowIndicatorSeries {
return &WindowIndicatorSeries{
window: window,
indicator: indicator,
indicatorContext: indicatorContext,
}
}
func (s *WindowIndicatorSeries) Get(offset int16) (vector float64) {
s.indicatorContext.SetOffset(offset)
vector = s.indicator.Calculate(s.indicatorContext, s.window)
return
}
func (s *WindowIndicatorSeries) Series(offset, count int16) (matrix series.Floats) {
for i := range count {
offset += i
s.indicatorContext.SetOffset(offset)
vector := s.indicator.Calculate(s.indicatorContext, s.window)
matrix.Push(vector)
}
return
}

46
internal/trading/kline_series.go

@ -58,35 +58,45 @@ func NewKlineSeries(exchange pb.ExchangeType, instId string, interval types.Inte
}
// Get [0]当前k线
func (s *KlineSeries) Get(start int16) types.Kline {
index := len(s.klines) - 1 - int(start)
if index >= 0 && index < len(s.klines)-1 {
return *(s.klines[index])
func (s *KlineSeries) Get(offset int16) (k types.Kline, ok bool) {
if ok = offset >= 0 && offset < MaxSeriesKlines; !ok {
return
}
s.RLock()
defer s.RUnlock()
// todo query store
ts := s.Interval.MustAddMul(s.lastTs, int64(-start))
for _, k := range s.klines {
if k.Ts == ts {
return *k
length := len(s.klines)
index := (length - 1) - int(offset)
if ok = index > 0 && index < length; !ok {
return
}
return *(s.klines[index]), true
}
panic("kline not exists")
// Series 闭区间升序[count...offset]
func (s *KlineSeries) Series(offset, count int16) (klines series.Klines, ok bool) {
if ok = offset >= 0 && offset < MaxSeriesKlines; !ok {
return
}
if ok = count >= 0 && offset+count < MaxSeriesKlines; !ok {
return
}
// Series [start...end]
func (s *KlineSeries) Series(start, end int16) (klines series.Klines) {
s.RLock()
defer s.RUnlock()
endTs := s.Interval.MustAddMul(s.lastTs, int64(-start))
startTs := s.Interval.MustAddMul(s.lastTs, int64(-end))
_ = endTs
_ = startTs
// return a.klineStore.GetRange(startTs, endTs)
// todo
length := len(s.klines)
indexEnd := (length - 1) - int(offset)
indexStart := (length - 1) - int(offset) - int(count)
if ok = indexEnd >= 0 && indexEnd < length && indexStart >= 0 && indexStart < length; !ok {
return
}
klines = make(series.Klines, 0, indexEnd-indexStart+1)
for i := indexStart; i <= indexEnd; i++ {
klines = append(klines, *(s.klines[i]))
}
return klines, true
}
func (s *KlineSeries) LastTs() int64 {
return s.lastTs

21
internal/trading/kline_store.go

@ -2,6 +2,7 @@ package trading
import (
"context"
"fmt"
"io"
"math"
"sig-pub/api/pb"
@ -301,3 +302,23 @@ func (s *KlineStore) Update(exchange pb.ExchangeType, instId string, kline *type
// pubKey := fmt.Sprintf("/kline/%s/%s/%s/%d", exchangeType, tradeInst.InstId, kline.Interval, confirm)
// interval/okx/BTC_USDT/1m,3m,5m
}
// GetKlineSeires 获取k线序列
func (s *KlineStore) GetKlineSeires(exchange pb.ExchangeType, instId string, interval types.Interval) (klineSeries *KlineSeries, err error) {
if _, ok := types.SupportedIntervals[interval]; !ok {
err = fmt.Errorf("unsupport interval: %s", interval)
return
}
if !s.store.IsSupport(exchange) {
zlog.Warningf("unsupport exchange: %v", exchange)
return
}
instsSeries := s.store.Get(exchange)
instSeries, ok := instsSeries.Load(instId)
if !ok {
err = fmt.Errorf("trade instance %s not support", instId)
return
}
klineSeries = instSeries.IntervalKlines.Get(interval)
return
}

61
internal/trading/strategy_context.go

@ -0,0 +1,61 @@
package trading
import (
"fmt"
"sig-pub/pkg/indicator"
"sig-pub/pkg/strategy"
"sig-pub/pkg/types"
"sig-pub/pkg/types/series"
"sig-pub/pkg/utils/collect"
"sig-pub/pkg/zlog"
)
type IOffsetStrategyContext interface {
strategy.ISigStrategyContext
SetOffset(offset int16)
}
type StrategyContext struct {
IOffsetStrategyContext
indicatorContext *IndicatorContext
indicatorsW *collect.SyncMap[string, indicator.IWindowIndicator]
}
func NewStrategyContext(klineSeries *KlineSeries, indicatorsW *collect.SyncMap[string, indicator.IWindowIndicator]) *StrategyContext {
return &StrategyContext{
indicatorContext: NewIndicatorContext(klineSeries),
indicatorsW: indicatorsW,
}
}
func (c *StrategyContext) SetOffset(offset int16) {
c.indicatorContext.SetOffset(offset)
}
func (c *StrategyContext) Get(offset int16) (kline types.Kline) {
return c.indicatorContext.Get(offset)
}
func (c *StrategyContext) Series(offset, count int16) (klines series.Klines) {
return c.indicatorContext.Series(offset, count)
}
// Buy 发出多信号
func (c *StrategyContext) Buy() {
zlog.Infof("signal buy: %d", c.Get(0).Ts)
}
// Sell 发出空信号
func (c *StrategyContext) Sell() {
zlog.Infof("signal sell: %d", c.Get(0).Ts)
}
// 获取窗口类型指标
func (c *StrategyContext) IndicatorW(name string, window int16) (s indicator.IIndicatorSeries) {
indicator, ok := c.indicatorsW.Load(name)
if !ok {
panic(fmt.Errorf("indicatorW %s not exists", name))
}
return NewWindowIndicatorSeries(window, indicator, c.indicatorContext)
}

22
internal/trading/trading_grpc_server.go

@ -1,17 +1,33 @@
package trading
import (
"context"
"sig-pub/api/pb"
)
type TradingGrpcServer struct {
pb.UnimplementedTradingServiceServer
tradingService *TradingService
}
func NewTradingGrpcServer() *TradingGrpcServer {
return &TradingGrpcServer{}
func NewTradingGrpcServer(tradingService *TradingService) *TradingGrpcServer {
return &TradingGrpcServer{
tradingService: tradingService,
}
}
func (svr *TradingGrpcServer) Init() (err error) {
return
}
func (svr *TradingGrpcServer) IndicatorSeries(ctx context.Context, req *pb.ReqIndicatorSeries) (rsp *pb.RspIndicatorSeries, err error) {
rsp = &pb.RspIndicatorSeries{}
err = svr.tradingService.IndicatorSeries(req, rsp)
return
}
func (svr TradingGrpcServer) Init() (err error) {
func (svr *TradingGrpcServer) StrategySeries(ctx context.Context, req *pb.ReqStrategySeries) (rsp *pb.RspStrategySeries, err error) {
rsp = &pb.RspStrategySeries{}
err = svr.tradingService.StrategySeries(req, rsp)
return
}

124
internal/trading/trading_service.go

@ -7,6 +7,7 @@ import (
"sig-pub/pkg/indicator"
"sig-pub/pkg/publish"
"sig-pub/pkg/strategy"
"sig-pub/pkg/types"
"sig-pub/pkg/utils/collect"
)
@ -16,7 +17,7 @@ type TradingService struct {
klineStore *KlineStore
indicatorsW *collect.SyncMap[string, indicator.IWindowIndicator] // 注册窗口指标
strategies *collect.SyncMap[string, strategy.ISigStrategy] // 注册信号策略
sigStrategies *collect.SyncMap[string, strategy.ISigStrategy] // 注册信号策略
publisher publish.Publisher[int64, *TradingPlan]
tradingPlan chan *TradingPlan
}
@ -30,22 +31,22 @@ func NewTradingService(
exchangeClient: exchangeClient,
klineStore: NewKlineSeriesStore(exchangeClient),
indicatorsW: collect.NewSyncMap[string, indicator.IWindowIndicator](),
strategies: collect.NewSyncMap[string, strategy.ISigStrategy](),
sigStrategies: collect.NewSyncMap[string, strategy.ISigStrategy](),
}
}
// 初始化历史k线, 订阅实时k线
func (svr *TradingService) Init() (err error) {
if err = svr.klineStore.Init(); err != nil {
func (svc *TradingService) Init() (err error) {
if err = svc.klineStore.Init(); err != nil {
return
}
// indicator registry
{
svr.MustRegisterWindowIndicator(&indicator.RSI{})
svc.MustRegisterWindowIndicator(&indicator.RSI{})
}
// strategy registry
{
svr.MustRegisterStrategy(&strategy.GoldX{})
svc.MustRegisterStrategy(&strategy.GoldX{})
}
// strategy initial
// 初始化策略执行器 64
@ -54,9 +55,9 @@ func (svr *TradingService) Init() (err error) {
}
// RegisterWindowIndicator
func (svr *TradingService) RegisterWindowIndicator(ind indicator.IWindowIndicator) (err error) {
func (svc *TradingService) RegisterWindowIndicator(ind indicator.IWindowIndicator) (err error) {
indName := ind.Name()
_, loaded := svr.indicatorsW.LoadOrStore(indName, ind)
_, loaded := svc.indicatorsW.LoadOrStore(indName, ind)
if loaded {
err = fmt.Errorf("window indicator name %s already duplicated", indName)
return
@ -64,16 +65,16 @@ func (svr *TradingService) RegisterWindowIndicator(ind indicator.IWindowIndicato
return
}
func (svr *TradingService) MustRegisterWindowIndicator(ind indicator.IWindowIndicator) {
if err := svr.RegisterWindowIndicator(ind); err != nil {
func (svc *TradingService) MustRegisterWindowIndicator(ind indicator.IWindowIndicator) {
if err := svc.RegisterWindowIndicator(ind); err != nil {
panic(err)
}
}
// RegisterStrategy
func (svr *TradingService) RegisterStrategy(strategy strategy.ISigStrategy) (err error) {
func (svc *TradingService) RegisterStrategy(strategy strategy.ISigStrategy) (err error) {
strategyName := strategy.Meta().Name
_, loaded := svr.strategies.LoadOrStore(strategyName, strategy)
_, loaded := svc.sigStrategies.LoadOrStore(strategyName, strategy)
if loaded {
err = fmt.Errorf("strategy name %s already duplicated", strategyName)
return
@ -81,16 +82,16 @@ func (svr *TradingService) RegisterStrategy(strategy strategy.ISigStrategy) (err
return
}
func (svr *TradingService) MustRegisterStrategy(strategy strategy.ISigStrategy) {
if err := svr.RegisterStrategy(strategy); err != nil {
func (svc *TradingService) MustRegisterStrategy(strategy strategy.ISigStrategy) {
if err := svc.RegisterStrategy(strategy); err != nil {
panic(err)
}
}
// RunStrategy 运行策略
// todo 止盈止损...
func (svr *TradingService) RunQuantPlan(plan TradingPlan) (err error) {
strategy, ok := svr.strategies.Load(plan.StrategyName)
func (svc *TradingService) RunQuantPlan(plan TradingPlan) (err error) {
strategy, ok := svc.sigStrategies.Load(plan.StrategyName)
if !ok {
err = fmt.Errorf("strategy %s not exists", plan.StrategyName)
return
@ -100,3 +101,94 @@ func (svr *TradingService) RunQuantPlan(plan TradingPlan) (err error) {
runner.Update(nil)
return
}
// IndicatorSeries 获取指标实时或历史序列数据, 闭区间
func (svc *TradingService) IndicatorSeries(req *pb.ReqIndicatorSeries, rsp *pb.RspIndicatorSeries) (err error) {
// indicatorName string, exchange pb.ExchangeType, instId string, interval types.Interval, window int
indicator, ok := svc.indicatorsW.Load(req.Indicator)
if !ok {
err = fmt.Errorf("indicator %s not exists", req.Indicator)
return
}
interval := types.Interval(req.Interval)
intervalAdd, ok := types.SupportedIntervals[interval]
if !ok {
err = fmt.Errorf("unsupport interval %s", interval)
return
}
if req.Count <= 0 {
req.Count = 100
}
if req.Count > 0 {
// ...
}
// todo trade instance status check
before, after, count := req.Before, req.After, req.Count
var indCtx IOffsetIndicatorContext
// 查询实时指标数据
if before == 0 && after == 0 {
klineSeries, err1 := svc.klineStore.GetKlineSeires(req.Exchange, req.InstId, interval)
if err1 != nil {
err = err1
return
}
// recover todo out of range
indCtx = NewIndicatorContext(klineSeries)
} else {
// 查询历史指标数据
// todo calc before after...
ctx := NewHistoryIndicatorContext(svc.exchangeClient)
if count := int32((after-before)/intervalAdd(0, 1) + 1); count > 100 {
}
before = intervalAdd(before, int64(-req.Window-1)) // 多拉取窗口大小的k线数据
totalK := 0
if totalK, err = ctx.Init(req.Exchange, req.InstId, interval, before, after); err != nil {
return
}
count = int32(totalK) - req.Window
indCtx = ctx
}
rsp.Matrix = make([]float64, 0, req.Count)
rsp.Times = make([]int64, 0, req.Count)
for i := range count {
indCtx.SetOffset(int16(i))
vector := indicator.Calculate(indCtx, int16(req.Window))
rsp.Matrix = append(rsp.Matrix, vector)
rsp.Times = append(rsp.Times, indCtx.Get(0).Ts)
}
return
}
// StrategySeries 简单策略信号测试
func (svc *TradingService) StrategySeries(req *pb.ReqStrategySeries, rsp *pb.RspStrategySeries) (err error) {
strategy, ok := svc.sigStrategies.Load(req.Strategy)
if !ok {
err = fmt.Errorf("strategy %s not exists", req.Strategy)
return
}
interval := types.Interval(req.Interval)
intervalAdd, ok := types.SupportedIntervals[interval]
if !ok {
err = fmt.Errorf("unsupport interval %s", interval)
return
}
_ = intervalAdd
klineSeries, err1 := svc.klineStore.GetKlineSeires(req.Exchange, req.InstId, interval)
if err1 != nil {
err = err1
return
}
// recover todo out of range
strategyCtx := NewStrategyContext(klineSeries, svc.indicatorsW)
for i := range req.Count {
strategyCtx.SetOffset(int16(i))
strategy.Update(strategyCtx)
}
return
}

7
pkg/grpc/interceptor/recover_interceptor.go

@ -14,11 +14,11 @@ import (
func RecoverInterceptor(ctx context.Context, req any, server *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
defer func() {
if r := recover(); r != nil {
switch r.(type) {
switch r := r.(type) {
case error:
err = r.(error)
err = r
case string:
err = errors.New(r.(string))
err = errors.New(r)
default:
err = errors.New("grpc server recover error")
}
@ -27,6 +27,7 @@ func RecoverInterceptor(ctx context.Context, req any, server *grpc.UnaryServerIn
}
}()
// zlog.Debugf("call method: %s", server.FullMethod) // -> /TradingService/IndicatorSeries
resp, err = handler(ctx, req)
if err == nil {
if empty, ok := resp.(*emptypb.Empty); ok && empty == nil {

10
pkg/indicator/base.go

@ -8,22 +8,22 @@ import (
// IIndicator 指标基础计算接口
type IIndicator interface {
Name() string
Calculate(kSeries IKlineSeries) (vector float64)
Calculate(kSeries IIndicatorContext) (vector float64)
}
// IIndicator 窗口指标基础计算接口
type IWindowIndicator interface {
Name() string
Calculate(kSeries IKlineSeries, window int16) (vector float64)
Calculate(ctx IIndicatorContext, window int16) (vector float64)
}
// IKlineSeries k线序列, strategy服务提供
type IKlineSeries interface {
// IIndicatorContext k线序列, trading服务提供
type IIndicatorContext interface {
Get(offset int16) (kline types.Kline)
Series(offset, count int16) (klines series.Klines)
}
// IIndicatorSeries 指标序列, 供策略读取, strategy服务提供
// IIndicatorSeries 指标序列, 供策略读取, trading服务提供
type IIndicatorSeries interface {
Get(offset int16) (vector float64)
Series(offset, count int16) (matrix series.Floats)

4
pkg/indicator/rsi.go

@ -17,9 +17,9 @@ func (c *RSI) Name() string {
}
// Calculate 计算单根k线rsi指标
func (c *RSI) Calculate(kSeries IKlineSeries, window int16) (vector float64) {
func (c *RSI) Calculate(ctx IIndicatorContext, window int16) (vector float64) {
// 读k线, 计算
klineSeries := kSeries.Series(0, int16(window)) // 7根
klineSeries := ctx.Series(0, int16(window)) // 7根
closeSeries := klineSeries.Close()
closeDiff := closeSeries.Diff()

4
pkg/strategy/sig_strategy.go

@ -18,6 +18,8 @@ type ISigStrategy interface {
}
// todo Meta 策略调参, 回测引擎自动调参回测(最佳参数) argGenerator.next() (arg, ok)
// argA range -> [1,...,5], argB:=[0.1,...,0.7], argC:=[true,false]
// 可变参数组合 argValidate(argA, ArgB, argC...) bool(true则使用该组合进行回测,记录组合参数回测结果)
type ISigStrategyAdjustable interface {
ISigStrategy
NextParams() map[string]any // 根据当前策略参数, 返回下一批策略参数(并行回测 stateless)
@ -41,7 +43,7 @@ type ISigStrategyContext interface {
// Series [offset...end]
Series(offset, count int16) (klines series.Klines)
// 获取窗口类型指标
IndicatorW(name string, window int) indicator.IIndicatorSeries
IndicatorW(name string, window int16) indicator.IIndicatorSeries
}
// DriverIntervalKey 生成周期驱动事件key

Loading…
Cancel
Save