Browse Source

indicator

main
strange 11 months ago
parent
commit
586e08a6ae
  1. 25
      api/indicator.proto
  2. 38
      api/order.proto
  3. 38
      api/trading.proto
  4. 4
      config/exchange.toml
  5. 2
      generate.go
  6. 1
      go.mod
  7. 2
      go.sum
  8. 2
      internal/exchange/exchange.go
  9. 39
      internal/exchange/exchange_service.go
  10. 35
      internal/indicator/rsi.go
  11. 2
      internal/market/market_grpc_server.go
  12. 4
      internal/market/trade_instance_service.go
  13. 0
      internal/order/okx/trading.go
  14. 2
      internal/order/service.go
  15. 16
      internal/trading/indicator.go
  16. 56
      internal/trading/kline_series.go
  17. 27
      internal/trading/kline_store.go
  18. 23
      pkg/indicator/base.go
  19. 85
      pkg/indicator/rsi.go
  20. 61
      pkg/indicator/rsi0.go
  21. 15
      pkg/types/decimals/decimal.go
  22. 9
      pkg/types/interval.go
  23. 59
      pkg/types/series/floats.go
  24. 23
      pkg/types/series/funcs.go
  25. 37
      pkg/types/series/klines.go
  26. 33
      pkg/utils/conver/unit_conver.go

25
api/indicator.proto

@ -1,25 +0,0 @@
syntax = "proto3";
import "api/pub.proto";
option go_package = "./pb";
service IndicatorService {
rpc Subscribe(IndicatorSubReq) returns (stream Indicator); //
rpc Plot(Indicator) returns (Indicator); //
}
message IndicatorSubReq {
string topic = 1;
string instId = 2;
int32 window = 3;
}
message Indicator {
ExchangeType exhcange = 1;
string instId = 2;
string indicator = 3;
string sub = 4; // , MA5, MA10, MA20
int64 Ts = 5;
bytes payload = 8;
}

38
api/order.proto

@ -0,0 +1,38 @@
syntax = "proto3";
import "api/pub.proto";
option go_package = "./pb";
service OrderService {
// request-response
rpc SubmitOrder(OrderSubmitReq) returns (OrderSubmitRsp) {}
// rpc CancelOrder(CancelOrderRequest) returns (CancelOrderResponse) {}
// rpc QueryOrder(QueryOrderRequest) returns (QueryOrderResponse) {}
// rpc QueryOrders(QueryOrdersRequest) returns (QueryOrdersResponse) {}
// rpc QueryTrades(QueryTradesRequest) returns (QueryTradesResponse) {}
}
message OrderSubmit {
string session = 1;
string exchange = 2;
string symbol = 3;
Side side = 4;
string price = 6;
string quantity = 5;
string stop_price = 7;
OrderType order_type = 8;
string client_order_id = 9;
int64 group_id = 10;
}
message OrderSubmitReq {
string session = 1;
repeated OrderSubmit submit_orders = 2;
}
message OrderSubmitRsp {
string session = 1;
repeated Order orders = 2;
Error error = 3;
}

38
api/trading.proto

@ -5,34 +5,20 @@ import "api/pub.proto";
option go_package = "./pb"; option go_package = "./pb";
service TradingService { service TradingService {
// request-response rpc SubIndicator(IndicatorSubReq) returns (stream Indicator); //
rpc SubmitOrder(SubmitOrderReq) returns (SubmitOrderRsp) {}
// rpc CancelOrder(CancelOrderRequest) returns (CancelOrderResponse) {}
// rpc QueryOrder(QueryOrderRequest) returns (QueryOrderResponse) {}
// rpc QueryOrders(QueryOrdersRequest) returns (QueryOrdersResponse) {}
// rpc QueryTrades(QueryTradesRequest) returns (QueryTradesResponse) {}
} }
message SubmitOrder { message IndicatorSubReq {
string session = 1; string topic = 1;
string exchange = 2; string instId = 2;
string symbol = 3; int32 window = 3;
Side side = 4;
string price = 6;
string quantity = 5;
string stop_price = 7;
OrderType order_type = 8;
string client_order_id = 9;
int64 group_id = 10;
} }
message SubmitOrderReq { message Indicator {
string session = 1; ExchangeType exhcange = 1;
repeated SubmitOrder submit_orders = 2; string instId = 2;
} string indicator = 3;
string sub = 4; // , MA5, MA10, MA20
message SubmitOrderRsp { int64 Ts = 5;
string session = 1; bytes payload = 8;
repeated Order orders = 2;
Error error = 3;
} }

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.6: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

2
generate.go

@ -6,7 +6,7 @@ import "fmt"
//go:generate protoc --go_out=./api/ --go-grpc_out=./api/ ./api/pub.proto //go:generate protoc --go_out=./api/ --go-grpc_out=./api/ ./api/pub.proto
//go:generate protoc --go_out=./api/ --go-grpc_out=./api/ ./api/market.proto //go:generate protoc --go_out=./api/ --go-grpc_out=./api/ ./api/market.proto
//go:generate protoc --go_out=./api/ --go-grpc_out=./api/ ./api/exchange.proto //go:generate protoc --go_out=./api/ --go-grpc_out=./api/ ./api/exchange.proto
//go:generate protoc --go_out=./api/ --go-grpc_out=./api/ ./api/indicator.proto //go:generate protoc --go_out=./api/ --go-grpc_out=./api/ ./api/order.proto
//go:generate protoc --go_out=./api/ --go-grpc_out=./api/ ./api/trading.proto //go:generate protoc --go_out=./api/ --go-grpc_out=./api/ ./api/trading.proto
// run cmd: go generate generate.go // run cmd: go generate generate.go

1
go.mod

@ -29,6 +29,7 @@ require (
golang.org/x/net v0.38.0 golang.org/x/net v0.38.0
golang.org/x/sync v0.12.0 golang.org/x/sync v0.12.0
golang.org/x/time v0.8.0 golang.org/x/time v0.8.0
gonum.org/v1/gonum v0.16.0
google.golang.org/grpc v1.71.1 google.golang.org/grpc v1.71.1
google.golang.org/protobuf v1.36.6 google.golang.org/protobuf v1.36.6
gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1

2
go.sum

@ -424,6 +424,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4= google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=

2
internal/exchange/exchange.go

@ -46,7 +46,7 @@ type Exchange struct {
// 交易所交易产品 // 交易所交易产品
type ExchangeTradeInstance struct { type ExchangeTradeInstance struct {
Inst *types.TradeInstance Inst *types.TradeInstance
Status atomic.Int32 // 交易产品状态, 0.初始化中 1.正常 Status atomic.Int32 // 交易产品状态, 0.初始化中 1.正常, todo status change event publish
LiveKline *types.IntervalState[types.Kline] // 实时k线数据 LiveKline *types.IntervalState[types.Kline] // 实时k线数据
LiveKStartTs *types.IntervalState[int64] // ws开始订阅k线标记时间戳 LiveKStartTs *types.IntervalState[int64] // ws开始订阅k线标记时间戳
HistoryMarkTs *types.IntervalState[int64] // 拉取历史k线标记时间戳 HistoryMarkTs *types.IntervalState[int64] // 拉取历史k线标记时间戳

39
internal/exchange/exchange_service.go

@ -10,6 +10,7 @@ import (
"sig-pub/pkg/data" "sig-pub/pkg/data"
"sig-pub/pkg/types" "sig-pub/pkg/types"
"sig-pub/pkg/utils/collect" "sig-pub/pkg/utils/collect"
"sig-pub/pkg/utils/conver"
"sig-pub/pkg/utils/times" "sig-pub/pkg/utils/times"
"sig-pub/pkg/zlog" "sig-pub/pkg/zlog"
"sync" "sync"
@ -176,9 +177,11 @@ func (svc *ExchangeService) consumerKline(exchange *Exchange, c <-chan *types.Ch
msg.Klines = append(msg.Klines, pbk) msg.Klines = append(msg.Klines, pbk)
} }
if len(confirmKlines) > 0 { // 存储到 tsdb
if _, ok := types.SupportedIntervals[firstKline.Interval]; ok && len(confirmKlines) > 0 {
// tsdb storage todo 异步处理 // tsdb storage todo 异步处理
err := svc.exchangeDataPersist.SaveKline(*tradeInst, confirmKlines) err := svc.exchangeDataPersist.SaveKline(*tradeInst, confirmKlines)
// zlog.Infof("save confirm klines: instId=%s(%s), interval=%s, ts=%d", tradeInst.InstId, tradeInst.Exchange, firstKline.Interval, firstKline.Ts)
if err != nil { if err != nil {
zlog.Errorf("kline save to tsdb error: %v, %#v", err, confirmKlines) zlog.Errorf("kline save to tsdb error: %v, %#v", err, confirmKlines)
} else { } else {
@ -261,8 +264,9 @@ func (svc *ExchangeService) initialTradeInstanceKlines(exchange *Exchange, trade
retryTaskCh := make(chan fetchKlineTask, concurrent) retryTaskCh := make(chan fetchKlineTask, concurrent)
// 发布任务数, 成功任务数, 失败任务次数 // 发布任务数, 成功任务数, 失败任务次数
var pubTasks, subTasks, failTasks atomic.Int32 var pubTasks, subTasks, failTimes atomic.Int32
var pubTaskDone atomic.Bool // 所有任务已发布 var pubTaskDone atomic.Bool // 所有任务已发布
startTs := time.Now().UnixMilli()
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer func() { defer func() {
@ -278,16 +282,32 @@ func (svc *ExchangeService) initialTradeInstanceKlines(exchange *Exchange, trade
} }
}) })
exchangeInst.Status.Store(int32(data.StatusOk)) exchangeInst.Status.Store(int32(data.StatusOk))
zlog.Infof("initial history kline finish: instId=%s(%s), pub=%d, sub=%d, fail=%d, use %s", tradeInst.InstId, tradeInst.Exchange, pubTasks.Load(), subTasks.Load(), failTimes.Load(), conver.TimeMilliFormat(time.Now().UnixMilli()-startTs, "/"))
}() }()
// progress monitor
go func() {
ticker := time.NewTicker(time.Second)
for {
select {
case <-ctx.Done():
ticker.Stop()
return
case <-ticker.C:
zlog.Debugf("processing initial history kline tasks: %s(%s), pub %d, sub %d, fail %d", tradeInst.InstId, tradeInst.Exchange, pubTasks.Load(), subTasks.Load(), failTimes.Load())
}
}
}()
// fetch kline history task publish
go func() { go func() {
defer func() { defer func() {
pubTaskDone.Store(true) pubTaskDone.Store(true)
// 无任务处理 // 无任务处理
if subTasks.Load() == 0 { if pubTasks.Load() == 0 {
cancel() cancel()
} }
zlog.Infof("trade instance initial kline %s(%s), pub %d fetch tasks", tradeInst.InstId, tradeInst.Exchange, pubTasks.Load()) // zlog.Infof("trade instance initial kline %s(%s), pub %d fetch tasks", tradeInst.InstId, tradeInst.Exchange, pubTasks.Load())
}() }()
for interval, intervalAdder := range types.SupportedIntervals { for interval, intervalAdder := range types.SupportedIntervals {
@ -358,8 +378,9 @@ func (svc *ExchangeService) initialTradeInstanceKlines(exchange *Exchange, trade
zlog.Infof("retry fetch history kline task %d times: task -> %s", task.times, task.logKey()) zlog.Infof("retry fetch history kline task %d times: task -> %s", task.times, task.logKey())
} }
// fetch history kline
if lastKlineTs, ex := svc.fetchTaskKlinesToTSDB(exchange, task); ex != nil { if lastKlineTs, ex := svc.fetchTaskKlinesToTSDB(exchange, task); ex != nil {
failTasks.Add(1) failTimes.Add(1)
if task.times >= SingleKlineFetchTaskMaxFailTimes { if task.times >= SingleKlineFetchTaskMaxFailTimes {
err = fmt.Errorf("task failed to many times %d, key: %s, err: %v", task.times, task.logKey(), ex) err = fmt.Errorf("task failed to many times %d, key: %s, err: %v", task.times, task.logKey(), ex)
cancel() cancel()
@ -382,9 +403,8 @@ func (svc *ExchangeService) initialTradeInstanceKlines(exchange *Exchange, trade
// 任务都已执行成功结束 // 任务都已执行成功结束
subs := subTasks.Add(1) subs := subTasks.Add(1)
zlog.Debugf("trade instance initial kline tasks processing: %s(%s), pub %d, sub %d, fail %d", tradeInst.InstId, tradeInst.Exchange, pubTasks.Load(), subTasks.Load(), failTasks.Load())
if pubTaskDone.Load() && subs >= pubTasks.Load() { if pubTaskDone.Load() && subs >= pubTasks.Load() {
zlog.Infof("trade instance initial kline tasks success finished, %s(%s), pub %d, sub %d, fail %d", tradeInst.InstId, tradeInst.Exchange, pubTasks.Load(), subTasks.Load(), failTasks.Load()) // zlog.Infof("initial history kline tasks finished success, %s(%s), pub %d, sub %d, fail %d", tradeInst.InstId, tradeInst.Exchange, pubTasks.Load(), subTasks.Load(), failTimes.Load())
cancel() cancel()
return return
} }
@ -462,7 +482,7 @@ func (svc *ExchangeService) ExchangeInstanceState(allExchange bool, exchangeType
if !ok { if !ok {
continue continue
} }
inst, ok := exchange.ExchangeInsts.Load(exchangeInstId) exchangeInst, ok := exchange.ExchangeInsts.Load(exchangeInstId)
if !ok { if !ok {
continue continue
} }
@ -470,7 +490,8 @@ func (svc *ExchangeService) ExchangeInstanceState(allExchange bool, exchangeType
state := &pb.TradeInstanceState{ state := &pb.TradeInstanceState{
Exchange: exchange.ExchangeType, Exchange: exchange.ExchangeType,
InstId: instId, InstId: instId,
Last: inst.Last.String(), Status: exchangeInst.Status.Load(),
Last: exchangeInst.Last.String(),
} }
states = append(states, state) states = append(states, state)
} }

35
internal/indicator/rsi.go

@ -1,35 +0,0 @@
package indicator
import (
"fmt"
"sig-pub/api/pb"
"sig-pub/pkg/types"
)
type RSI struct {
// types.IntervalWindow
window int // 窗口大小
}
func (ind *RSI) IntervalWindow() {
// 指标参数注入
}
func (ind *RSI) QueryRange(exchange pb.ExchangeType, instId string, interval types.Interval, rsi int) (query string, err error) {
var r types.MeticMatrix
_ = r
intervalAdder, ok := types.SupportedIntervals[interval]
if !ok {
err = fmt.Errorf("unsupport interval %s", interval)
return
}
minutes := intervalAdder(0, int64(rsi)) / 1000 / 60
query = fmt.Sprintf(`
100 - 100 / (1 + (
avg_over_time(clamp_min(delta(%s{kind="close", interval="%s", exchange="%s"}), 0)[%dm]) /
avg_over_time(abs(clamp_max(delta(%s{kind="close", interval="%s", exchange="%s"}), 0))[%dm])
))
`, instId, interval, exchange, minutes, instId, interval, exchange, minutes)
return
}

2
internal/market/market_grpc_server.go

@ -76,7 +76,7 @@ func (svr *MarketGrpcServer) ListMarketTradeInstance(ctx context.Context, req *p
exchangeInstMap[exchangeInst.InstId] = pbInst exchangeInstMap[exchangeInst.InstId] = pbInst
} }
tradeInsts, err := svr.tradeInstanceService.ListInstance(instIds...) tradeInsts, err := svr.tradeInstanceService.ListInstanceById(instIds...)
for _, inst := range tradeInsts { for _, inst := range tradeInsts {
if pbInst, ok := exchangeInstMap[inst.InstId]; ok { if pbInst, ok := exchangeInstMap[inst.InstId]; ok {
pbInst.InstType = pb.TradeInstanceType(inst.InstType) pbInst.InstType = pb.TradeInstanceType(inst.InstType)

4
internal/market/trade_instance_service.go

@ -40,7 +40,7 @@ func (s *TradeInstanceService) GetInstance(instId string) (inst *entity.TradeIns
return return
} }
func (s *TradeInstanceService) ListInstance(instIds ...string) (insts []*entity.TradeInstance, err error) { func (s *TradeInstanceService) ListInstanceById(instIds ...string) (insts []*entity.TradeInstance, err error) {
err = s.db.Select(&insts, `select * from t_trade_instance where inst_id in ? and status != ?`, instIds, data.StatusDeleted) err = s.db.Select(&insts, `select * from t_trade_instance where inst_id in ? and status != ?`, instIds, data.StatusDeleted)
if err != nil { if err != nil {
return return
@ -148,7 +148,7 @@ func (s *TradeInstanceService) UpdateInstanceStatus(inst *args.UpdateTradeInstan
// ListExchangeTradeInstance 获取指定交易所的正常状态的交易产品 // ListExchangeTradeInstance 获取指定交易所的正常状态的交易产品
func (s *TradeInstanceService) ListExchangeTradeInstance(exchange pb.ExchangeType) (exchangesInsts []*entity.TradeInstanceExchange, err error) { func (s *TradeInstanceService) ListExchangeTradeInstance(exchange pb.ExchangeType) (exchangesInsts []*entity.TradeInstanceExchange, err error) {
err = s.db.Select(&exchangesInsts, ` err = s.db.Select(&exchangesInsts, `
select * from t_trade_instance_exchange where exchange = ? and status in ? order by inst_id, exchange select * from t_trade_instance_exchange where exchange = ? and status in ? order by inst_id, exchange order by update_time asc
`, exchange, []data.Status{data.StatusOk, data.StatusProcessing}) `, exchange, []data.Status{data.StatusOk, data.StatusProcessing})
if err != nil { if err != nil {
return return

0
internal/trading/okx/trading.go → internal/order/okx/trading.go

2
internal/trading/service.go → internal/order/service.go

@ -1,4 +1,4 @@
package trading package order
// 交易服务 // 交易服务
// load strategy plugins // load strategy plugins

16
internal/indicator/indicator.go → internal/trading/indicator.go

@ -1,7 +1,7 @@
package indicator package trading
import ( import (
vmts "sig-pub/pkg/storage/tsdb/victoria_metrics" "sig-pub/pkg/indicator"
"sync" "sync"
) )
@ -11,15 +11,20 @@ import (
// 历史指标 实时计算 // 历史指标 实时计算
// 自定义插件化指标 // 自定义插件化指标
type IndicatorService struct { type IndicatorService struct {
vmdb vmts.VictoriaMetricsTSDB indicators map[string]indicator.RSI
} }
func NewIndicatorService(vmdb vmts.VictoriaMetricsTSDB) *IndicatorService { func NewIndicatorService() *IndicatorService {
return &IndicatorService{ return &IndicatorService{
vmdb: vmdb, indicators: make(map[string]indicator.RSI),
} }
} }
func (svc *IndicatorService) GetKlines(start, end int64) {
// Regist(RSI)
// if Indicators[RSI]
}
// 加载热指标 // 加载热指标
// 订阅k线数据 更新指标 // 订阅k线数据 更新指标
func (svc *IndicatorService) Init() { func (svc *IndicatorService) Init() {
@ -43,6 +48,7 @@ func (svc *IndicatorService) Indicator(indicatorName string, args ...any) {
} }
// kline -> indicator root -> /internal/force_flush -> all indicators (wg concurrent) -> all strategy (concurrent) // kline -> indicator root -> /internal/force_flush -> all indicators (wg concurrent) -> all strategy (concurrent)
// kline -> indicator root -> all indicators ->
type Ind struct { type Ind struct {
LiveMu sync.RWMutex // 实时k线锁, k线更新后指标更新时写锁 LiveMu sync.RWMutex // 实时k线锁, k线更新后指标更新时写锁
} }

56
internal/trading/kline_series.go

@ -0,0 +1,56 @@
package trading
import (
"sig-pub/api/pb"
"sig-pub/pkg/types"
"sig-pub/pkg/types/series"
)
type KlineSeries struct {
Exchange pb.ExchangeType
InstId string
Interval types.Interval
Ts int64
klines []*types.Kline
klineStore KlineStore
}
func NewKlineSeries(ts int64, interval types.Interval, klineStore KlineStore) KlineSeries {
return KlineSeries{
Ts: ts,
Interval: interval,
klineStore: klineStore,
}
}
// Get
// [0]当前k线
func (a KlineSeries) Get(start int16) (kline types.Kline) {
ts := a.Interval.MustAddMul(a.Ts, int64(-start))
for _, k := range a.klines {
if k.Ts == ts {
return *k
}
}
// todo query tsdb
return
}
// Series [start...end]
func (a KlineSeries) Series(start, end int16) (klines series.Klines) {
endTs := a.Interval.MustAddMul(a.Ts, int64(-start))
startTs := a.Interval.MustAddMul(a.Ts, int64(-end))
_ = endTs
_ = startTs
// return a.klineStore.GetRange(startTs, endTs)
// todo
return
}
func (s *KlineSeries) Update(kline *types.Kline) []types.Kline {
s.klines = append(s.klines, kline)
// todo copy(s.klines, s.klines[0:1]) set index=20, ts=kline.ts
s.Ts = kline.Ts
return nil
}

27
internal/trading/kline_store.go

@ -0,0 +1,27 @@
package trading
import (
"sig-pub/api/pb"
vmts "sig-pub/pkg/storage/tsdb/victoria_metrics"
"sig-pub/pkg/types"
"sig-pub/pkg/utils/collect"
)
type KlineStore struct {
vmdb vmts.VictoriaMetricsTSDB
store [3]*collect.ConcurrentMap[string, collect.ConcurrentMap[types.Interval, *KlineSeries]] // K线列表: []exchange<instId, interval, klines>
}
func NewKlineSeriesStore(vmdb vmts.VictoriaMetricsTSDB) (kss *KlineStore) {
kss = &KlineStore{
vmdb: vmdb,
}
kss.store[pb.ExchangeType_OKX] = collect.NewConcurrentMap[string, collect.ConcurrentMap[types.Interval, *KlineSeries]](64, func(s string) string { return s })
// kss.klines[pb.ExchangeType_BINANCE] =
return
}
func (s *KlineStore) Update(exchange pb.ExchangeType, kline *types.Kline) (k types.Kline) {
return
}

23
pkg/indicator/base.go

@ -0,0 +1,23 @@
package indicator
import (
"sig-pub/pkg/types"
"sig-pub/pkg/types/series"
)
// IIndicator 指标基础计算接口
type IIndicator interface {
Calculate() (vector float64)
}
// IKlineSeries k线序列, strategy服务提供
type IKlineSeries interface {
Get(start int16) (kline types.Kline)
Series(start, end int16) (klines series.Klines)
}
// IIndicatorSeries 指标序列, 供策略读取, strategy服务提供
type IIndicatorSeries interface {
Get(start int16) (vector float64)
Series(start, end int16) (matrix series.Floats)
}

85
pkg/indicator/rsi.go

@ -2,60 +2,65 @@ package indicator
import ( import (
"fmt" "fmt"
"sig-pub/pkg/trader" "sig-pub/api/pb"
"sig-pub/pkg/types" "sig-pub/pkg/types"
"sig-pub/pkg/types/series"
"github.com/spf13/cast"
) )
// RSI: 相对强弱指数 (RSI) // RSI stateless indicator
// rsi define: https://www.investopedia.com/terms/r/rsi.asp
type RSI struct { type RSI struct {
trader.Indicator // types.IntervalWindow
series.Series window int // 窗口大小
values series.Floats kliner IKlineSeries
prices series.Floats
argBaseDay int32
} }
func NewRSI() *RSI { // New indicator interface
return &RSI{} // 依赖注入
// 指标参数注入
func (c RSI) New(klineAccesser IKlineSeries) *RSI {
return &RSI{
kliner: klineAccesser,
}
} }
func (ind RSI) Meta() trader.IndicatorMeta { // indicator interface
return trader.IndicatorMeta{ func (c *RSI) IntervalWindow(window int) {
Name: "RSI", c.window = window
Desc: "",
Args: []trader.Arg{
{Name: "基准天数", Desc: "", ArgType: trader.ArgTypeUInt},
},
}
} }
func (ind *RSI) Init(indId int64, exchange any, args []string) (code trader.ErrorCode, err error) { // Calculate 计算单根k线rsi指标
arg0, err := cast.ToInt32E(args[0]) func (c *RSI) Calculate() (vector float64) {
if err != nil { // kline := c.kliner.Get(0)
return // if kline.Close == kline.High {
} // // emit buy/sell
ind.argBaseDay = arg0 // }
// 读k线, 计算
klineSeries := c.kliner.Series(0, int16(c.window)) // 7根
closeSeries := klineSeries.Close()
closeDiff := closeSeries.Diff()
cast.ToIntE("1") avgGain := closeDiff.PositiveValuesOrZero().Abs().Sum() / float64(c.window)
return avgLoss := closeDiff.NegativeValuesOrZero().Abs().Sum() / float64(c.window)
rs := avgGain / avgLoss
rsi := 100 - (100 / (1 + rs))
return rsi
} }
func (ind *RSI) Update(klines []types.Kline) (err error) { func (c *RSI) QueryRange(exchange pb.ExchangeType, instId string, interval types.Interval, rsi int) (query string, err error) {
for _, kline := range klines { var r types.MeticMatrix
c, ok := kline.Close.Float64() _ = r
if !ok { intervalAdder, ok := types.SupportedIntervals[interval]
err = fmt.Errorf("kline close to float64 error: %s", kline.Close.String()) if !ok {
return err = fmt.Errorf("unsupport interval %s", interval)
} return
ind.prices.Push(c)
} }
minutes := intervalAdder(0, int64(rsi)) / 1000 / 60
diff := ind.prices.Diff() query = fmt.Sprintf(`
_ = diff 100 - 100 / (1 + (
avg_over_time(clamp_min(delta(%s{kind="close", interval="%s", exchange="%s"}), 0)[%dm]) /
avg_over_time(abs(clamp_max(delta(%s{kind="close", interval="%s", exchange="%s"}), 0))[%dm])
))
`, instId, interval, exchange, minutes, instId, interval, exchange, minutes)
return return
} }

61
pkg/indicator/rsi0.go

@ -0,0 +1,61 @@
package indicator
import (
"fmt"
"sig-pub/pkg/trader"
"sig-pub/pkg/types"
"sig-pub/pkg/types/series"
"github.com/spf13/cast"
)
// RSI0: 相对强弱指数 (RSI0)
// rsi define: https://www.investopedia.com/terms/r/rsi.asp
type RSI0 struct {
trader.Indicator
series.Series
values series.Floats
prices series.Floats
argBaseDay int32
}
func NewRSI() *RSI0 {
return &RSI0{}
}
func (ind RSI0) Meta() trader.IndicatorMeta {
return trader.IndicatorMeta{
Name: "RSI",
Desc: "",
Args: []trader.Arg{
{Name: "基准天数", Desc: "", ArgType: trader.ArgTypeUInt},
},
}
}
func (ind *RSI0) Init(indId int64, exchange any, args []string) (code trader.ErrorCode, err error) {
arg0, err := cast.ToInt32E(args[0])
if err != nil {
return
}
ind.argBaseDay = arg0
cast.ToIntE("1")
return
}
func (ind *RSI0) Update(klines []types.Kline) (err error) {
for _, kline := range klines {
c, ok := kline.Close.Float64()
if !ok {
err = fmt.Errorf("kline close to float64 error: %s", kline.Close.String())
return
}
ind.prices.Push(c)
}
diff := ind.prices.Diff()
_ = diff
return
}

15
pkg/types/decimals/decimal.go

@ -0,0 +1,15 @@
package decimals
import (
"fmt"
"github.com/govalues/decimal"
)
func MustToFloat64(v decimal.Decimal) float64 {
f, ok := v.Float64()
if !ok {
panic(fmt.Errorf("decimal to float64 error: %v", v))
}
return f
}

9
pkg/types/interval.go

@ -1,6 +1,7 @@
package types package types
import ( import (
"fmt"
"sig-pub/pkg/zlog" "sig-pub/pkg/zlog"
"sort" "sort"
"time" "time"
@ -20,6 +21,14 @@ func (i Interval) AddMul(ts, mul int64) (int64, bool) {
return c(ts, mul), true return c(ts, mul), true
} }
func (i Interval) MustAddMul(ts, mul int64) int64 {
ts, ok := i.AddMul(ts, mul)
if !ok {
panic(fmt.Errorf("unsupport interval AddMul: interval=%s, ts=%d, mul=%d", i, ts, mul))
}
return ts
}
const ( const (
Interval1s = Interval("1s") Interval1s = Interval("1s")
Interval1m = Interval("1m") Interval1m = Interval("1m")

59
pkg/types/series/floats.go

@ -1,9 +1,15 @@
package series package series
import (
"math"
"gonum.org/v1/gonum/floats"
)
type Floats []float64 type Floats []float64
func NewFloats(length int) Floats { func NewFloats(vs ...float64) Floats {
return make(Floats, 0, length+1) return Floats(vs)
} }
func (s *Floats) Push(v float64) { func (s *Floats) Push(v float64) {
@ -24,3 +30,52 @@ func (s Floats) Diff() (values Floats) {
} }
return values return values
} }
func (s Floats) Sub(b Floats) (c Floats) {
if len(s) != len(b) {
return c
}
c = make(Floats, len(s))
for i := range s {
c[i] = s[i] - b[i]
}
return c
}
func (s Floats) Max() float64 {
return floats.Max(s)
}
func (s Floats) Min() float64 {
return floats.Min(s)
}
func (s Floats) PositiveValuesOrZero() (values Floats) {
for _, v := range s {
values.Push(math.Max(v, 0))
}
return values
}
func (s Floats) NegativeValuesOrZero() (values Floats) {
for _, v := range s {
values.Push(math.Min(v, 0))
}
return values
}
func (s Floats) Abs() (values Floats) {
values = make(Floats, 0, len(s))
for _, v := range s {
values.Push(math.Abs(v))
}
return values
}
func (s Floats) Sum() (sum float64) {
for _, v := range s {
sum += v
}
return sum
}

23
pkg/types/series/funcs.go

@ -1,23 +0,0 @@
package series
import (
"sig-pub/pkg/types"
"github.com/govalues/decimal"
)
func Klines2Decimals(klines []types.Kline, mapping func(kline types.Kline) decimal.Decimal) (r Decimals) {
r = NewDecimals(len(klines))
for _, k := range klines {
r.Push(mapping(k))
}
return
}
func Klines2Floats(klines []types.Kline, mapping func(kline types.Kline) float64) (r Floats) {
r = NewFloats(len(klines))
for _, k := range klines {
r.Push(mapping(k))
}
return
}

37
pkg/types/series/klines.go

@ -0,0 +1,37 @@
package series
import (
"sig-pub/pkg/types"
"sig-pub/pkg/types/decimals"
"sig-pub/pkg/utils/collect"
)
type Klines []types.Kline
func (s Klines) Times() []int64 {
return collect.Mapping(s, func(_ int, k types.Kline) int64 { return k.Ts })
}
func (s Klines) Open() Floats {
return collect.Mapping(s, func(_ int, k types.Kline) float64 { return decimals.MustToFloat64(k.Open) })
}
func (s Klines) Close() Floats {
return collect.Mapping(s, func(_ int, k types.Kline) float64 { return decimals.MustToFloat64(k.Close) })
}
func (s Klines) High() Floats {
return collect.Mapping(s, func(_ int, k types.Kline) float64 { return decimals.MustToFloat64(k.High) })
}
func (s Klines) Low() Floats {
return collect.Mapping(s, func(_ int, k types.Kline) float64 { return decimals.MustToFloat64(k.Low) })
}
func (s Klines) Vol() Floats {
return collect.Mapping(s, func(_ int, k types.Kline) float64 { return decimals.MustToFloat64(k.Vol) })
}
func (s Klines) VolQuote() Floats {
return collect.Mapping(s, func(_ int, k types.Kline) float64 { return decimals.MustToFloat64(k.VolQuote) })
}

33
pkg/utils/conver/unit_conver.go

@ -2,6 +2,7 @@ package conver
import ( import (
"fmt" "fmt"
"strings"
"time" "time"
"github.com/dsnet/golib/unitconv" "github.com/dsnet/golib/unitconv"
@ -42,3 +43,35 @@ func MustParseDuration(s string) time.Duration {
} }
return d return d
} }
// TimeMilliFormat 时间戳差转换成可视化时间单位 2100 -> 1h-2s-100ms
func TimeMilliFormat(milli int64, sep string) (r string) {
day := milli / (1000 * 60 * 60 * 24)
milli -= day * (1000 * 60 * 60 * 24)
hour := milli / (1000 * 60 * 60)
milli -= hour * (1000 * 60 * 60)
minite := milli / (1000 * 60)
milli -= minite * (1000 * 60)
second := milli / (1000)
milli -= second * (1000)
vs := []int64{day, hour, minite, second, milli}
units := []string{"d", "h", "m", "s", "ms"}
index := -1
for i, v := range vs {
if v > 0 {
index = i
break
}
}
if index == -1 {
r = "0ms"
return
}
var rs []string
for ; index < len(vs); index++ {
rs = append(rs, fmt.Sprintf("%d%s", vs[index], units[index]))
}
r = strings.Join(rs, sep)
return
}

Loading…
Cancel
Save