From 26f532b0a54da6651366ac33373682821cbd15f4 Mon Sep 17 00:00:00 2001 From: strange Date: Mon, 26 Jan 2026 19:17:24 +0800 Subject: [PATCH] indicator meta --- api/pub.proto | 31 +++++++-- api/trading.proto | 19 ++++++ config/exchange.toml | 4 +- internal/trading/trading_grpc_server.go | 68 +++++++++++++++++++ internal/trading/trading_service.go | 55 ++++++++++++--- pkg/indicator/boll.go | 89 +++++++------------------ pkg/indicator/indicator_plot.go | 3 + pkg/indicator/indicator_registry.go | 8 ++- pkg/indicator/macd.go | 6 +- pkg/types/input.go | 10 +-- 10 files changed, 200 insertions(+), 93 deletions(-) diff --git a/api/pub.proto b/api/pub.proto index 7a93640..4b39b2a 100644 --- a/api/pub.proto +++ b/api/pub.proto @@ -172,22 +172,45 @@ message Paging { bool asc = 3; // 升序排序 } +// 指标元数据 +message IndicatorMeta { + string name = 1; // 指标名称 + string desc = 2; // 指标描述 + repeated InputArg input = 3; // 输入参数 + repeated string state = 4; // 向外暴露状态 + repeated IndicatorPlotSeries plots = 5; // 指标绘图属性 +} + // 指标绘图 message IndicatorPlot { string indicator = 1; repeated IndicatorPlotSeries plots = 9; } message IndicatorPlotSeries { - string state = 1; - int32 type = 2; - google.protobuf.Struct props = 3; - repeated IndicatorPlotExp exps = 4; + string name = 1; + string state = 2; + int32 type = 3; + google.protobuf.Struct props = 4; + repeated IndicatorPlotExp exps = 5; } message IndicatorPlotExp { string exp = 1; google.protobuf.Struct props = 3; } +// 输入参数定义 +message InputArg { + string name = 1; + string desc = 2; + int32 type = 3; // 参数类型 + repeated InputOption options = 4 ; // 单选/多选选项列表 + string default = 5; // 默认值 +} +message InputOption { + string name = 1; + string desc = 2; +} + // 输入参数范围 message InputRange { string name = 1; // 参数名 names diff --git a/api/trading.proto b/api/trading.proto index 8c643b5..7e310b7 100644 --- a/api/trading.proto +++ b/api/trading.proto @@ -7,6 +7,12 @@ option go_package = "./pb"; // 交易系统服务 service TradingService { + // 获取指标列表 + rpc Indicators(ReqIndicators) returns (RspIndicators); + + // 获取指标元数据 + rpc IndicatorMetas(ReqIndicatorMetas) returns (RspIndicatorMetas); + // 获取指标绘图属性 rpc IndicatorPlots(ReqIndicatorPlots) returns (RspIndicatorPlots); @@ -26,6 +32,19 @@ service TradingService { rpc BacktestLog(ReqBacktestLog) returns (RspBacktestLog); } +message ReqIndicators { +} +message RspIndicators { + repeated string indicators = 1; +} + +message ReqIndicatorMetas { + repeated string indicators = 1; +} +message RspIndicatorMetas { + repeated IndicatorMeta metas = 1; +} + message ReqIndicatorPlots { repeated string indicators = 1; } diff --git a/config/exchange.toml b/config/exchange.toml index 19bb205..caa8ef0 100644 --- a/config/exchange.toml +++ b/config/exchange.toml @@ -19,8 +19,8 @@ marketSubscribeLimit = 16 consumeBatch = 1024 consumeLater = 2000 # 时间到达later或者数据累计到batch触发consume # httpProxy = "" -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/internal/trading/trading_grpc_server.go b/internal/trading/trading_grpc_server.go index 652bc85..ff349b0 100644 --- a/internal/trading/trading_grpc_server.go +++ b/internal/trading/trading_grpc_server.go @@ -2,8 +2,10 @@ package trading import ( "context" + "fmt" "sig-pub/api/pb" "sig-pub/pkg/types" + "sig-pub/pkg/utils/collect" "sig-pub/pkg/utils/lang" "sig-pub/pkg/utils/times" "sig-pub/pkg/zlog" @@ -26,6 +28,71 @@ func (svr *TradingGrpcServer) Init() (err error) { return } +func (svr *TradingGrpcServer) Indicators(ctx context.Context, req *pb.ReqIndicators) (rsp *pb.RspIndicators, err error) { + rsp = new(pb.RspIndicators) + rsp.Indicators = svr.tradingService.Indicators() + return +} + +func (svr *TradingGrpcServer) IndicatorMetas(ctx context.Context, req *pb.ReqIndicatorMetas) (rsp *pb.RspIndicatorMetas, err error) { + indMetas, err := svr.tradingService.IndicatorMeta(req.Indicators...) + if err != nil { + return + } + indPlots, err := svr.tradingService.IndicatorPlots(req.Indicators...) + if err != nil { + return + } + rsp = new(pb.RspIndicatorMetas) + for _, indName := range req.Indicators { + // 指标元数据 + meta := indMetas[indName] + p := &pb.IndicatorMeta{ + Name: indName, + Desc: meta.Desc, + State: meta.State, + } + // 指标输入参数 + for _, input := range meta.Input { + p.Input = append(p.Input, &pb.InputArg{ + Name: input.Name, + Desc: input.Desc, + Type: int32(input.Type), + Default: lang.Ternary(input.Default == nil, "", fmt.Sprintf("%v", input.Default)), + Options: collect.Mapping(input.Options, func(opt types.InputOption) *pb.InputOption { + return &pb.InputOption{ + Name: opt.Name, + Desc: opt.Desc, + } + }), + }) + } + // 指标绘图属性 + for _, plot := range indPlots[indName] { + ps := &pb.IndicatorPlotSeries{ + Name: plot.Name, + State: plot.State, + Type: int32(plot.Type), + } + if ps.Props, err = structpb.NewStruct(plot.Props); err != nil { + return + } + for _, exp := range plot.Exps { + pe := &pb.IndicatorPlotExp{ + Exp: exp.Exp, + } + if pe.Props, err = structpb.NewStruct(exp.Props); err != nil { + return + } + ps.Exps = append(ps.Exps, pe) + } + p.Plots = append(p.Plots, ps) + } + rsp.Metas = append(rsp.Metas, p) + } + return +} + func (svr *TradingGrpcServer) IndicatorPlots(ctx context.Context, req *pb.ReqIndicatorPlots) (rsp *pb.RspIndicatorPlots, err error) { indPlots, err := svr.tradingService.IndicatorPlots(req.Indicators...) if err != nil { @@ -37,6 +104,7 @@ func (svr *TradingGrpcServer) IndicatorPlots(ctx context.Context, req *pb.ReqInd p := &pb.IndicatorPlot{Indicator: indName} for _, plot := range plots { ps := &pb.IndicatorPlotSeries{ + Name: plot.Name, State: plot.State, Type: int32(plot.Type), } diff --git a/internal/trading/trading_service.go b/internal/trading/trading_service.go index 01d70ea..4f563e3 100644 --- a/internal/trading/trading_service.go +++ b/internal/trading/trading_service.go @@ -18,6 +18,7 @@ import ( "sig-pub/pkg/utils/lang" "sig-pub/pkg/utils/times" "sig-pub/pkg/zlog" + "sort" "sig-pub/internal/trading/backtest" "sig-pub/internal/trading/sig" @@ -199,28 +200,60 @@ func (svc *TradingService) fetchHistoryKlineSeries(ctx context.Context, sr *pb.S return } -// IndicatorPlots -func (svc *TradingService) IndicatorPlots(indicatorNames ...string) (indPlots map[string][]indicator.Plot, err error) { - indPlots = make(map[string][]indicator.Plot, len(indicatorNames)) +// Indicators +func (svc *TradingService) Indicators() (indicatorNames []string) { + svc.indicatorReg.RangeIndicators(func(k string, v indicator.IIndicator) bool { + indicatorNames = append(indicatorNames, k) + return true + }) + sort.Strings(indicatorNames) + return +} + +// IndicatorMeta +func (svc *TradingService) IndicatorMeta(indicatorNames ...string) (indMetas map[string]indicator.IndicatorMeta, err error) { + indMetas = make(map[string]indicator.IndicatorMeta, len(indicatorNames)) for _, indicatorName := range indicatorNames { ind, ok := svc.indicatorReg.Indicator(indicatorName) if !ok { err = fmt.Errorf("indicator %s not exists", indicatorName) - return + return nil, err } - plots := ind.Meta().Plots - if len(plots) == 0 { - plots = append(plots, indicator.Plot{ - State: "vector", - Type: indicator.PlotLine, - Props: indicator.PlotProps{"color": indicator.ColorBlue}, - }) + indMetas[indicatorName] = ind.Meta() + } + return +} + +// IndicatorPlots +func (svc *TradingService) IndicatorPlots(indicatorNames ...string) (indPlots map[string][]indicator.Plot, err error) { + indPlots = make(map[string][]indicator.Plot, len(indicatorNames)) + for _, indicatorName := range indicatorNames { + plots, e := svc.indicatorPlots(indicatorName) + if e != nil { + return nil, e } indPlots[indicatorName] = plots } return } +func (svc *TradingService) indicatorPlots(indicatorName string) (plots []indicator.Plot, err error) { + ind, ok := svc.indicatorReg.Indicator(indicatorName) + if !ok { + err = fmt.Errorf("indicator %s not exists", indicatorName) + return + } + plots = ind.Meta().Plots + if len(plots) == 0 { + plots = append(plots, indicator.Plot{ + State: "vector", + Type: indicator.PlotLine, + Props: indicator.PlotProps{"color": indicator.ColorBlue}, + }) + } + return +} + // IndicatorSeries 获取指标实时或历史序列数据, 闭区间 func (svc *TradingService) IndicatorSeries(ctx context.Context, indicatorName string, digit int32, input types.Input, sr *pb.SeriesRange) (matrix []float64, times []int64, states map[string][]float64, err error) { appros := indicator.ApproCandles diff --git a/pkg/indicator/boll.go b/pkg/indicator/boll.go index ae13207..c2493a2 100644 --- a/pkg/indicator/boll.go +++ b/pkg/indicator/boll.go @@ -5,92 +5,51 @@ import ( "sig-pub/pkg/types" ) -// BollMB 布林带中轨 -type BollMB struct { +// Boll 布林带 +type Boll struct { } -func (c *BollMB) Meta() IndicatorMeta { +func (c *Boll) Meta() IndicatorMeta { return IndicatorMeta{ - Name: "BollMB", + Name: "Boll", Input: []types.InputArg{ {Name: "window", Type: types.InputTypeUInt, Desc: "窗口大小"}, + {Name: "pt", Type: types.InputTypeKPriceType, Desc: "k线序列类型"}, }, - } -} - -func (c *BollMB) CandlePeriods(ctx IIndicatorContext) int16 { - return ctx.Input().Int16("window") -} - -func (c *BollMB) Calculate(ctx IIndicatorContext) (vector float64) { - window := ctx.Input().Int16("window") - closeSeries := ctx.Series(0, int16(window)).Close() - vector = closeSeries.Avg() - return -} - -// BollUB 布林带上轨 -type BollUB struct { -} - -func (c *BollUB) Meta() IndicatorMeta { - return IndicatorMeta{ - Name: "BollUB", - Input: []types.InputArg{ - {Name: "window", Type: types.InputTypeUInt, Desc: "窗口大小"}, + State: []string{"ub", "lb"}, + Plots: []Plot{ + {Name: "中轨", State: "vector", Type: PlotHistogram, Props: PlotProps{"color": ColorOrange}}, + {Name: "上轨", State: "ub", Type: PlotLine, Props: PlotProps{"color": ColorRed2}}, + {Name: "下轨", State: "lb", Type: PlotLine, Props: PlotProps{"color": ColorRed2}}, + {Name: "布林带阴影", State: "ub,lb", Type: PlotShadow, Props: PlotProps{"color": "rgba(247, 169, 167, 0.3)"}}, }, } } -func (c *BollUB) CandlePeriods(ctx IIndicatorContext) int16 { +func (c *Boll) CandlePeriods(ctx IIndicatorContext) int16 { return ctx.Input().Int16("window") } -func (c *BollUB) Calculate(ctx IIndicatorContext) (vector float64) { +func (c *Boll) Calculate(ctx IIndicatorContext) (vector float64) { window := ctx.Input().Int16("window") - closeSeries := ctx.Series(0, int16(window)).Close() - mb := closeSeries.Avg() + pt := ctx.Input().PriceType() + priceSeries := ctx.Series(0, int16(window)).Price(pt) + mb := priceSeries.Avg() // 中轨 + vector = mb // 标准差σ_t = sqrt(∑(P-MB)^2 / (n-1)) sst := float64(0) - for _, p := range closeSeries { + for _, p := range priceSeries { sst += math.Pow(p-mb, 2) } sigma := math.Sqrt(sst / float64(window-1)) - vector = mb + 2*sigma - return -} - -// BollLB 布林带下轨 -type BollLB struct { -} - -func (c *BollLB) Meta() IndicatorMeta { - return IndicatorMeta{ - Name: "BollLB", - Input: []types.InputArg{ - {Name: "window", Type: types.InputTypeUInt, Desc: "窗口大小"}, - }, - } -} - -func (c *BollLB) CandlePeriods(ctx IIndicatorContext) int16 { - return ctx.Input().Int16("window") -} - -func (c *BollLB) Calculate(ctx IIndicatorContext) (vector float64) { - window := ctx.Input().Int16("window") - closeSeries := ctx.Series(0, int16(window)).Close() - mb := closeSeries.Avg() - - // 标准差σ_t = sqrt(∑(P-MB)^2 / (n-1)) - sst := float64(0) - for _, p := range closeSeries { - sst += math.Pow(p-mb, 2) - } - sigma := math.Sqrt(sst / float64(window-1)) + // BollUB 布林带上轨 + ub := mb + 2*sigma + ctx.State().Set("ub", ub) - vector = mb - 2*sigma + // BollLB 布林带下轨 + lb := mb - 2*sigma + ctx.State().Set("lb", lb) return } diff --git a/pkg/indicator/indicator_plot.go b/pkg/indicator/indicator_plot.go index c72b243..a1a6c1b 100644 --- a/pkg/indicator/indicator_plot.go +++ b/pkg/indicator/indicator_plot.go @@ -1,6 +1,7 @@ package indicator type Plot struct { + Name string `json:"name"` // 绘图名称 State string `json:"state"` // vector, stateName Type PlotType `json:"series"` // 绘图类型 线/柱 Props PlotProps `json:"props"` // 绘图属性 @@ -23,6 +24,7 @@ const ( PlotLine PlotHistogram PlotArea + PlotShadow ) // Series 绘图颜色 @@ -34,4 +36,5 @@ const ( ColorYellow string = "#cdcf36" ColorBlue string = "#556cd6" ColorPurple string = "#e48dce" + ColorOrange string = "#dd5e0aff" ) diff --git a/pkg/indicator/indicator_registry.go b/pkg/indicator/indicator_registry.go index e564f5a..3487e76 100644 --- a/pkg/indicator/indicator_registry.go +++ b/pkg/indicator/indicator_registry.go @@ -25,9 +25,7 @@ func (r *IndicatorRegistry) Init() (err error) { r.MustRegistIndicator(&MACD{}) r.MustRegistIndicator(&OBV{}) r.MustRegistIndicator(&WOBV{}) - r.MustRegistIndicator(&BollMB{}) - r.MustRegistIndicator(&BollUB{}) - r.MustRegistIndicator(&BollLB{}) + r.MustRegistIndicator(&Boll{}) r.MustRegistIndicator(&SuperTrend{}) r.MustRegistIndicator(&ADX{}) r.MustRegistIndicator(&KDJ{}) @@ -55,3 +53,7 @@ func (r *IndicatorRegistry) MustRegistIndicator(ind IIndicator) { func (r *IndicatorRegistry) Indicator(name string) (indW IIndicator, ok bool) { return r.indicators.Load(name) } + +func (r *IndicatorRegistry) RangeIndicators(fn func(k string, v IIndicator) bool) { + r.indicators.Range(fn) +} diff --git a/pkg/indicator/macd.go b/pkg/indicator/macd.go index 6dcd743..c60180e 100644 --- a/pkg/indicator/macd.go +++ b/pkg/indicator/macd.go @@ -18,12 +18,12 @@ func (c *MACD) Meta() IndicatorMeta { }, State: []string{"dif", "dea"}, Plots: []Plot{ - {State: "vector", Type: PlotHistogram, Props: PlotProps{"color": ColorGreen2}, Exps: []PlotExp{ + {Name: "MACD", State: "vector", Type: PlotHistogram, Props: PlotProps{"color": ColorGreen2}, Exps: []PlotExp{ {Exp: "vector < 0", Props: PlotProps{"color": ColorRed2}}, {Exp: "vector >= 0", Props: PlotProps{"color": ColorGreen2}}, }}, - {State: "dif", Type: PlotLine, Props: PlotProps{"color": ColorYellow}}, - {State: "dea", Type: PlotLine, Props: PlotProps{"color": ColorRed}}, + {Name: "DIF线", State: "dif", Type: PlotLine, Props: PlotProps{"color": ColorYellow}}, + {Name: "DEA线", State: "dea", Type: PlotLine, Props: PlotProps{"color": ColorRed}}, }, } } diff --git a/pkg/types/input.go b/pkg/types/input.go index c52c34a..974d132 100644 --- a/pkg/types/input.go +++ b/pkg/types/input.go @@ -158,11 +158,11 @@ const ( InputTypeInt InputTypeUInt InputTypeTime - InputTypeKPriceType - InputTypeUFloats // float数组 - InputTypeUFloats2D // float二维数组 - InputTypeSelect // 单选 - InputTypeCheckBox // 多选 + InputTypeKPriceType // K线价格类型 + InputTypeUFloats // float数组 + InputTypeUFloats2D // float二维数组 + InputTypeSelect // 单选 + InputTypeCheckBox // 多选 ) type InputArg struct {