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. 81
      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";
service TradingService {
// request-response
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) {}
rpc SubIndicator(IndicatorSubReq) returns (stream Indicator); //
}
message SubmitOrder {
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 IndicatorSubReq {
string topic = 1;
string instId = 2;
int32 window = 3;
}
message SubmitOrderReq {
string session = 1;
repeated SubmitOrder submit_orders = 2;
}
message SubmitOrderRsp {
string session = 1;
repeated Order orders = 2;
Error error = 3;
message Indicator {
ExchangeType exhcange = 1;
string instId = 2;
string indicator = 3;
string sub = 4; // , MA5, MA10, MA20
int64 Ts = 5;
bytes payload = 8;
}

4
config/exchange.toml

@ -18,8 +18,8 @@ receiveBuffer = 4096
marketSubscribeLimit = 16
consumeBatch = 1024
consumeLater = 2000 # 时间到达later或者数据累计到batch触发consume
# httpProxy = "http://192.168.1.6: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

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/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/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
// run cmd: go generate generate.go

1
go.mod

@ -29,6 +29,7 @@ require (
golang.org/x/net v0.38.0
golang.org/x/sync v0.12.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/protobuf v1.36.6
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-20191204190536-9bdfabe68543/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/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
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 {
Inst *types.TradeInstance
Status atomic.Int32 // 交易产品状态, 0.初始化中 1.正常
Status atomic.Int32 // 交易产品状态, 0.初始化中 1.正常, todo status change event publish
LiveKline *types.IntervalState[types.Kline] // 实时k线数据
LiveKStartTs *types.IntervalState[int64] // ws开始订阅k线标记时间戳
HistoryMarkTs *types.IntervalState[int64] // 拉取历史k线标记时间戳

39
internal/exchange/exchange_service.go

@ -10,6 +10,7 @@ import (
"sig-pub/pkg/data"
"sig-pub/pkg/types"
"sig-pub/pkg/utils/collect"
"sig-pub/pkg/utils/conver"
"sig-pub/pkg/utils/times"
"sig-pub/pkg/zlog"
"sync"
@ -176,9 +177,11 @@ func (svc *ExchangeService) consumerKline(exchange *Exchange, c <-chan *types.Ch
msg.Klines = append(msg.Klines, pbk)
}
if len(confirmKlines) > 0 {
// 存储到 tsdb
if _, ok := types.SupportedIntervals[firstKline.Interval]; ok && len(confirmKlines) > 0 {
// tsdb storage todo 异步处理
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 {
zlog.Errorf("kline save to tsdb error: %v, %#v", err, confirmKlines)
} else {
@ -261,8 +264,9 @@ func (svc *ExchangeService) initialTradeInstanceKlines(exchange *Exchange, trade
retryTaskCh := make(chan fetchKlineTask, concurrent)
// 发布任务数, 成功任务数, 失败任务次数
var pubTasks, subTasks, failTasks atomic.Int32
var pubTasks, subTasks, failTimes atomic.Int32
var pubTaskDone atomic.Bool // 所有任务已发布
startTs := time.Now().UnixMilli()
ctx, cancel := context.WithCancel(context.Background())
defer func() {
@ -278,16 +282,32 @@ func (svc *ExchangeService) initialTradeInstanceKlines(exchange *Exchange, trade
}
})
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() {
defer func() {
pubTaskDone.Store(true)
// 无任务处理
if subTasks.Load() == 0 {
if pubTasks.Load() == 0 {
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 {
@ -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())
}
// fetch history kline
if lastKlineTs, ex := svc.fetchTaskKlinesToTSDB(exchange, task); ex != nil {
failTasks.Add(1)
failTimes.Add(1)
if task.times >= SingleKlineFetchTaskMaxFailTimes {
err = fmt.Errorf("task failed to many times %d, key: %s, err: %v", task.times, task.logKey(), ex)
cancel()
@ -382,9 +403,8 @@ func (svc *ExchangeService) initialTradeInstanceKlines(exchange *Exchange, trade
// 任务都已执行成功结束
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() {
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()
return
}
@ -462,7 +482,7 @@ func (svc *ExchangeService) ExchangeInstanceState(allExchange bool, exchangeType
if !ok {
continue
}
inst, ok := exchange.ExchangeInsts.Load(exchangeInstId)
exchangeInst, ok := exchange.ExchangeInsts.Load(exchangeInstId)
if !ok {
continue
}
@ -470,7 +490,8 @@ func (svc *ExchangeService) ExchangeInstanceState(allExchange bool, exchangeType
state := &pb.TradeInstanceState{
Exchange: exchange.ExchangeType,
InstId: instId,
Last: inst.Last.String(),
Status: exchangeInst.Status.Load(),
Last: exchangeInst.Last.String(),
}
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
}
tradeInsts, err := svr.tradeInstanceService.ListInstance(instIds...)
tradeInsts, err := svr.tradeInstanceService.ListInstanceById(instIds...)
for _, inst := range tradeInsts {
if pbInst, ok := exchangeInstMap[inst.InstId]; ok {
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
}
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)
if err != nil {
return
@ -148,7 +148,7 @@ func (s *TradeInstanceService) UpdateInstanceStatus(inst *args.UpdateTradeInstan
// ListExchangeTradeInstance 获取指定交易所的正常状态的交易产品
func (s *TradeInstanceService) ListExchangeTradeInstance(exchange pb.ExchangeType) (exchangesInsts []*entity.TradeInstanceExchange, err error) {
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})
if err != nil {
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

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

@ -1,7 +1,7 @@
package indicator
package trading
import (
vmts "sig-pub/pkg/storage/tsdb/victoria_metrics"
"sig-pub/pkg/indicator"
"sync"
)
@ -11,15 +11,20 @@ import (
// 历史指标 实时计算
// 自定义插件化指标
type IndicatorService struct {
vmdb vmts.VictoriaMetricsTSDB
indicators map[string]indicator.RSI
}
func NewIndicatorService(vmdb vmts.VictoriaMetricsTSDB) *IndicatorService {
func NewIndicatorService() *IndicatorService {
return &IndicatorService{
vmdb: vmdb,
indicators: make(map[string]indicator.RSI),
}
}
func (svc *IndicatorService) GetKlines(start, end int64) {
// Regist(RSI)
// if Indicators[RSI]
}
// 加载热指标
// 订阅k线数据 更新指标
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 -> all indicators ->
type Ind struct {
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)
}

81
pkg/indicator/rsi.go

@ -2,60 +2,65 @@ package indicator
import (
"fmt"
"sig-pub/pkg/trader"
"sig-pub/api/pb"
"sig-pub/pkg/types"
"sig-pub/pkg/types/series"
"github.com/spf13/cast"
)
// RSI: 相对强弱指数 (RSI)
// rsi define: https://www.investopedia.com/terms/r/rsi.asp
// RSI stateless indicator
type RSI struct {
trader.Indicator
series.Series
values series.Floats
prices series.Floats
argBaseDay int32
// types.IntervalWindow
window int // 窗口大小
kliner IKlineSeries
}
func NewRSI() *RSI {
return &RSI{}
// New indicator interface
// 依赖注入
// 指标参数注入
func (c RSI) New(klineAccesser IKlineSeries) *RSI {
return &RSI{
kliner: klineAccesser,
}
}
func (ind RSI) Meta() trader.IndicatorMeta {
return trader.IndicatorMeta{
Name: "RSI",
Desc: "",
Args: []trader.Arg{
{Name: "基准天数", Desc: "", ArgType: trader.ArgTypeUInt},
},
}
// indicator interface
func (c *RSI) IntervalWindow(window int) {
c.window = window
}
func (ind *RSI) 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
// Calculate 计算单根k线rsi指标
func (c *RSI) Calculate() (vector float64) {
// kline := c.kliner.Get(0)
// if kline.Close == kline.High {
// // emit buy/sell
// }
// 读k线, 计算
klineSeries := c.kliner.Series(0, int16(c.window)) // 7根
closeSeries := klineSeries.Close()
closeDiff := closeSeries.Diff()
cast.ToIntE("1")
return
avgGain := closeDiff.PositiveValuesOrZero().Abs().Sum() / float64(c.window)
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) {
for _, kline := range klines {
c, ok := kline.Close.Float64()
func (c *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("kline close to float64 error: %s", kline.Close.String())
err = fmt.Errorf("unsupport interval %s", interval)
return
}
ind.prices.Push(c)
}
minutes := intervalAdder(0, int64(rsi)) / 1000 / 60
diff := ind.prices.Diff()
_ = diff
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
}

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
import (
"fmt"
"sig-pub/pkg/zlog"
"sort"
"time"
@ -20,6 +21,14 @@ func (i Interval) AddMul(ts, mul int64) (int64, bool) {
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 (
Interval1s = Interval("1s")
Interval1m = Interval("1m")

59
pkg/types/series/floats.go

@ -1,9 +1,15 @@
package series
import (
"math"
"gonum.org/v1/gonum/floats"
)
type Floats []float64
func NewFloats(length int) Floats {
return make(Floats, 0, length+1)
func NewFloats(vs ...float64) Floats {
return Floats(vs)
}
func (s *Floats) Push(v float64) {
@ -24,3 +30,52 @@ func (s Floats) Diff() (values Floats) {
}
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 (
"fmt"
"strings"
"time"
"github.com/dsnet/golib/unitconv"
@ -42,3 +43,35 @@ func MustParseDuration(s string) time.Duration {
}
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