Browse Source

stateful indicator ema,macd

main
strange 10 months ago
parent
commit
554c65a3bb
  1. 22
      api/trading.proto
  2. 100
      internal/trading/sig/indicator_context.go
  3. 14
      internal/trading/sig/indicator_series.go
  4. 47
      internal/trading/sig/indicator_state.go
  5. 45
      internal/trading/sig/strategy_context.go
  6. 2
      internal/trading/trading_grpc_server.go
  7. 22
      internal/trading/trading_service.go
  8. 16
      pkg/indicator/atr.go
  9. 37
      pkg/indicator/ema.go
  10. 42
      pkg/indicator/indicator.go
  11. 22
      pkg/indicator/indicator_registry.go
  12. 108
      pkg/indicator/macd.go
  13. 41
      pkg/indicator/obv.go
  14. 17
      pkg/indicator/rsi.go
  15. 16
      pkg/indicator/sam.go
  16. 37
      pkg/indicator/wobv.go
  17. 6
      pkg/strategy/cross_star.go
  18. 10
      pkg/strategy/gold_x.go
  19. 8
      pkg/strategy/sig_strategy.go
  20. 60
      pkg/strategy/sig_strategy_input_adjust.go
  21. 12
      pkg/strategy/super_trend.go
  22. 53
      pkg/types/input.go
  23. 69
      pkg/types/ring_series.go
  24. 30
      pkg/types/ring_series_test.go

22
api/trading.proto

@ -6,34 +6,18 @@ import "api/pub.proto";
option go_package = "./pb"; option go_package = "./pb";
service TradingService { service TradingService {
rpc SubIndicator(IndicatorSubReq) returns (stream Indicator); //
rpc IndicatorSeries(ReqIndicatorSeries) returns (RspIndicatorSeries); // rpc IndicatorSeries(ReqIndicatorSeries) returns (RspIndicatorSeries); //
rpc StrategySeries(ReqStrategySeries) returns (RspStrategySeries); // rpc StrategySeries(ReqStrategySeries) returns (RspStrategySeries); //
rpc Backtest(ReqBacktest) returns (RspBacktest); // rpc Backtest(ReqBacktest) returns (RspBacktest); //
} }
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;
}
message ReqIndicatorSeries { message ReqIndicatorSeries {
string indicator = 1; string indicator = 1;
uint32 window = 2; // int32 digit = 2; //
SeriesRange series = 9; SeriesRange series = 9;
google.protobuf.Struct input = 10; // google.protobuf.Struct input = 10; //
} }
message RspIndicatorSeries{ message RspIndicatorSeries {
repeated double matrix = 1; repeated double matrix = 1;
repeated int64 times = 2; repeated int64 times = 2;
} }
@ -53,6 +37,6 @@ message ReqBacktest {
string stime = 2; string stime = 2;
string etime = 3; string etime = 3;
} }
message RspBacktest{ message RspBacktest {
} }

100
internal/trading/sig/indicator_context.go

@ -1,12 +1,24 @@
package sig package sig
import ( import (
"fmt"
"sig-pub/pkg/indicator" "sig-pub/pkg/indicator"
"sig-pub/pkg/types" "sig-pub/pkg/types"
"sig-pub/pkg/types/series" "sig-pub/pkg/types/series"
"sig-pub/pkg/utils/collect"
"sig-pub/pkg/zlog" "sig-pub/pkg/zlog"
"slices"
"strings"
"github.com/spf13/cast"
) )
type IndicatorStates map[string]*IndicatorState
func NewIndicatorStates() IndicatorStates {
return make(IndicatorStates)
}
type IOffsetIndicatorContext interface { type IOffsetIndicatorContext interface {
indicator.IIndicatorContext indicator.IIndicatorContext
SetOffset(offset int16) SetOffset(offset int16)
@ -17,15 +29,23 @@ type IOffsetIndicatorContext interface {
// IndicatorContext 指标上下文, 提供k线序列给指标计算使用 // IndicatorContext 指标上下文, 提供k线序列给指标计算使用
type IndicatorContext struct { type IndicatorContext struct {
IOffsetIndicatorContext IOffsetIndicatorContext
indicator indicator.IIndicator
indicatorsReg *indicator.IndicatorRegistry
input types.Input input types.Input
indicatorStates map[string]*IndicatorState // {macd{window:0,fast:9,slow:21,single:10}: state} 初始化时与KlineSeries周期同步
kSeries *KlineSeries kSeries *KlineSeries
offset int16 offset int16
indicatorTrace []string // 指标调用链避免指标循环引用
} }
func NewIndicatorContext(input types.Input, kSeries *KlineSeries) *IndicatorContext { func NewIndicatorContext(indicator indicator.IIndicator, input types.Input, indicatorStates IndicatorStates, kSeries *KlineSeries, indicatorsReg *indicator.IndicatorRegistry) *IndicatorContext {
return &IndicatorContext{ return &IndicatorContext{
indicator: indicator,
input: input, input: input,
indicatorStates: indicatorStates,
kSeries: kSeries, kSeries: kSeries,
indicatorsReg: indicatorsReg,
indicatorTrace: []string{indicator.Meta().Name},
} }
} }
@ -65,3 +85,81 @@ func (c *IndicatorContext) Series(offset, count int16) (klines series.Klines) {
} }
return ks return ks
} }
// func (c *IndicatorContext) State(effects ...any)
// 窗口/参数
// tradingPlan -> interval -> context -> {macd{window:0,fast:9,slow:21,single:10}: state, ema21:state} -> state[[ema]{1.1, 1.2}, [ema]{1.3, 1.4}]
func (c *IndicatorContext) State() indicator.IIndicatorState {
inputs := collect.Mapping(c.indicator.Meta().Input, func(in types.InputArg) string {
return c.Input().String(in.Name)
})
stateKey := fmt.Sprintf("%s{%s}", c.indicator.Meta().Name, strings.Join(inputs, ","))
state, ok := c.indicatorStates[stateKey]
if !ok {
state = NewIndicatorState(c.kSeries)
c.indicatorStates[stateKey] = state
// 从头KlineSeries跑一遍,针对ema,macd等回溯迭代指标
c.backtrackIndicatorState(c.indicator)
}
return state
}
// 当首次初始化某个state dea后, 把indicator在klineSeries从头跑一遍
func (c *IndicatorContext) backtrackIndicatorState(indicator indicator.IIndicator) {
_offset := c.offset
defer c.SetOffset(_offset)
previousCandles := indicator.CandlePeriods(c)
length := c.kSeries.Length()
for i := range length {
if i < int(previousCandles) {
continue
}
offset := int16(length - 1 - i)
c.SetOffset(offset)
indicator.Calculate(c)
}
}
// 获取窗口类型指标
func (c *IndicatorContext) Indicator(name string, args ...any) (series indicator.IIndicatorSeries) {
indicator, ok := c.indicatorsReg.Indicator(name)
if !ok {
panic(fmt.Errorf("indicator %s not exists", name))
}
// 避免循环依赖
if slices.Contains(c.indicatorTrace, name) {
panic(fmt.Errorf("indicator %s recursive call", name))
}
input := matchIndicatorArgs(args...)
indicatorContext := NewIndicatorContext(indicator, input, c.indicatorStates, c.kSeries, c.indicatorsReg)
indicatorContext.indicatorTrace = append(c.indicatorTrace, name)
return NewWindowIndicatorSeries(indicator, indicatorContext)
}
func matchIndicatorArgs(args ...any) (input types.Input) {
inputLoop:
for _, arg := range args {
switch v := arg.(type) {
case types.Input:
input = v
break inputLoop
}
}
windowLoop:
for _, arg := range args {
switch v := arg.(type) {
case int16, int, int32, int64:
window := cast.ToInt16(v)
if input == nil {
input = make(types.Input)
}
input["window"] = window
break windowLoop
}
}
return
}

14
internal/trading/sig/indicator_series.go

@ -8,23 +8,25 @@ import (
// WindowIndicatorSeries 封装 // WindowIndicatorSeries 封装
type WindowIndicatorSeries struct { type WindowIndicatorSeries struct {
indicator.IIndicatorSeries indicator.IIndicatorSeries
window int16 indicator indicator.IIndicator
indicator indicator.IWindowIndicator
indicatorContext IOffsetIndicatorContext indicatorContext IOffsetIndicatorContext
} }
func NewWindowIndicatorSeries(window int16, indicator indicator.IWindowIndicator, indicatorContext IOffsetIndicatorContext) *WindowIndicatorSeries { func NewWindowIndicatorSeries(indicator indicator.IIndicator, indicatorContext IOffsetIndicatorContext) *WindowIndicatorSeries {
return &WindowIndicatorSeries{ return &WindowIndicatorSeries{
window: window,
indicator: indicator, indicator: indicator,
indicatorContext: indicatorContext, indicatorContext: indicatorContext,
} }
} }
func (s *WindowIndicatorSeries) CandlePeriods() int16 {
return s.indicator.CandlePeriods(s.indicatorContext)
}
func (s *WindowIndicatorSeries) Get(offset int16) (vector float64) { func (s *WindowIndicatorSeries) Get(offset int16) (vector float64) {
// 根据当前相对offset // 根据当前相对offset
s.indicatorContext.AddOffset(offset) s.indicatorContext.AddOffset(offset)
vector = s.indicator.Calculate(s.indicatorContext, s.window) vector = s.indicator.Calculate(s.indicatorContext)
// 计算结束后还原 // 计算结束后还原
s.indicatorContext.AddOffset(-offset) s.indicatorContext.AddOffset(-offset)
return return
@ -35,7 +37,7 @@ func (s *WindowIndicatorSeries) Series(offset, count int16) (matrix series.Float
// 设置当前相对offset // 设置当前相对offset
s.indicatorContext.AddOffset(offset) s.indicatorContext.AddOffset(offset)
for range count { for range count {
vector := s.indicator.Calculate(s.indicatorContext, s.window) vector := s.indicator.Calculate(s.indicatorContext)
matrix.Push(vector) matrix.Push(vector)
offset++ offset++

47
internal/trading/sig/indicator_state.go

@ -0,0 +1,47 @@
package sig
import (
"sig-pub/pkg/indicator"
"sig-pub/pkg/types"
"sig-pub/pkg/types/series"
)
// IndicatorState
// ema, obv 指标递归计算时的状态存储
type IndicatorState struct {
indicator.IIndicatorState
kSeries *KlineSeries
state map[string]*types.RingSeries[float64]
}
func NewIndicatorState(kSeries *KlineSeries) *IndicatorState {
return &IndicatorState{
kSeries: kSeries,
state: make(map[string]*types.RingSeries[float64]),
}
}
func (s *IndicatorState) ring(k string) *types.RingSeries[float64] {
ring, ok := s.state[k]
if !ok {
ring = types.NewRingSeries[float64](indicator.MaxWindow, 8)
s.state[k] = ring
// 从 kSeries0 开始 calc ind 初始化
// 递归初始值
}
return ring
}
func (s *IndicatorState) Set(k string, v float64) {
s.ring(k).Push(v)
}
func (s *IndicatorState) Get(k string, offset int16) (v float64, ok bool) {
offset -= 1
return s.ring(k).Get(int(offset))
}
func (s *IndicatorState) Series(k string, offset, count int16) (v series.Floats, ok bool) {
offset -= 1
return s.ring(k).Series(int(offset), int(count))
}

45
internal/trading/sig/strategy_context.go

@ -14,6 +14,7 @@ type StrategyContext struct {
input types.Input input types.Input
kSeries *KlineSeries kSeries *KlineSeries
indicatorsReg *indicator.IndicatorRegistry indicatorsReg *indicator.IndicatorRegistry
indicatorContextStates IndicatorStates
} }
func NewStrategyContext(input types.Input, kSeries *KlineSeries, indicatorsReg *indicator.IndicatorRegistry) *StrategyContext { func NewStrategyContext(input types.Input, kSeries *KlineSeries, indicatorsReg *indicator.IndicatorRegistry) *StrategyContext {
@ -21,6 +22,7 @@ func NewStrategyContext(input types.Input, kSeries *KlineSeries, indicatorsReg *
input: input, input: input,
kSeries: kSeries, kSeries: kSeries,
indicatorsReg: indicatorsReg, indicatorsReg: indicatorsReg,
indicatorContextStates: NewIndicatorStates(),
} }
} }
@ -38,19 +40,14 @@ func (c *StrategyContext) Series(offset, count int16) (klines series.Klines) {
} }
// 获取窗口类型指标 // 获取窗口类型指标
func (c *StrategyContext) IndicatorW(name string, window int16, args ...any) (s indicator.IIndicatorSeries) { func (c *StrategyContext) Indicator(name string, args ...any) (s indicator.IIndicatorSeries) {
indicator, ok := c.indicatorsReg.IndicatorW(name) indicator, ok := c.indicatorsReg.Indicator(name)
if !ok { if !ok {
panic(fmt.Errorf("indicatorW %s not exists", name)) panic(fmt.Errorf("indicator %s not exists", name))
} }
var input types.Input input := matchIndicatorArgs(args...)
if len(args) > 0 { indicatorContext := NewIndicatorContext(indicator, input, c.indicatorContextStates, c.kSeries, c.indicatorsReg)
if in, ok := args[0].(types.Input); ok { return NewWindowIndicatorSeries(indicator, indicatorContext)
input = in
}
}
indicatorContext := NewIndicatorContext(input, c.kSeries)
return NewWindowIndicatorSeries(window, indicator, indicatorContext)
} }
// IntervalStrategyContext 周期策略上下文 // IntervalStrategyContext 周期策略上下文
@ -60,6 +57,7 @@ type IntervalStrategyContext struct {
input types.Input input types.Input
intervalKlineSeries *types.IntervalState[*KlineSeries] intervalKlineSeries *types.IntervalState[*KlineSeries]
indicatorsReg *indicator.IndicatorRegistry indicatorsReg *indicator.IndicatorRegistry
intervalIndicatorContextStates map[types.Interval]IndicatorStates
} }
func NewIntervalStrategyContext(input types.Input, intervalKlineSeries *types.IntervalState[*KlineSeries], indicatorsReg *indicator.IndicatorRegistry) *IntervalStrategyContext { func NewIntervalStrategyContext(input types.Input, intervalKlineSeries *types.IntervalState[*KlineSeries], indicatorsReg *indicator.IndicatorRegistry) *IntervalStrategyContext {
@ -67,6 +65,7 @@ func NewIntervalStrategyContext(input types.Input, intervalKlineSeries *types.In
input: input, input: input,
intervalKlineSeries: intervalKlineSeries, intervalKlineSeries: intervalKlineSeries,
indicatorsReg: indicatorsReg, indicatorsReg: indicatorsReg,
intervalIndicatorContextStates: make(map[types.Interval]IndicatorStates),
} }
} }
@ -96,20 +95,20 @@ func (c *IntervalStrategyContext) Series(interval types.Interval, offset, count
} }
// 获取窗口类型指标 // 获取窗口类型指标
func (c *IntervalStrategyContext) IndicatorW(interval types.Interval, name string, window int16, args ...any) (series indicator.IIndicatorSeries) { func (c *IntervalStrategyContext) Indicator(interval types.Interval, name string, args ...any) (series indicator.IIndicatorSeries) {
indicator, ok := c.indicatorsReg.IndicatorW(name) indicator, ok := c.indicatorsReg.Indicator(name)
if !ok { if !ok {
panic(fmt.Errorf("indicatorW %s not exists", name)) panic(fmt.Errorf("indicator %s not exists", name))
} }
var input types.Input input := matchIndicatorArgs(args...)
if len(args) > 0 { kSeries := c.getCandleSeries(interval)
if in, ok := args[0].(types.Input); ok { // 状态传递
input = in state, ok := c.intervalIndicatorContextStates[interval]
} if !ok {
state = NewIndicatorStates()
c.intervalIndicatorContextStates[interval] = state
} }
cs := c.getCandleSeries(interval) indicatorContext := NewIndicatorContext(indicator, input, state, kSeries, c.indicatorsReg)
indicatorContext := NewIndicatorContext(input, cs) return NewWindowIndicatorSeries(indicator, indicatorContext)
return NewWindowIndicatorSeries(window, indicator, indicatorContext)
} }

2
internal/trading/trading_grpc_server.go

@ -25,7 +25,7 @@ func (svr *TradingGrpcServer) Init() (err error) {
func (svr *TradingGrpcServer) IndicatorSeries(ctx context.Context, req *pb.ReqIndicatorSeries) (rsp *pb.RspIndicatorSeries, err error) { func (svr *TradingGrpcServer) IndicatorSeries(ctx context.Context, req *pb.ReqIndicatorSeries) (rsp *pb.RspIndicatorSeries, err error) {
// s, err := structpb.NewStruct(map[string]any{}) // s, err := structpb.NewStruct(map[string]any{})
input := req.Input.AsMap() input := req.Input.AsMap()
matrix, times, err := svr.tradingService.IndicatorSeries(ctx, req.Indicator, req.Window, types.Input(input), req.Series) matrix, times, err := svr.tradingService.IndicatorSeries(ctx, req.Indicator, req.Digit, types.Input(input), req.Series)
if err != nil { if err != nil {
return return
} }

22
internal/trading/trading_service.go

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"math"
"sig-pub/api/pb" "sig-pub/api/pb"
"sig-pub/pkg/client" "sig-pub/pkg/client"
"sig-pub/pkg/data" "sig-pub/pkg/data"
@ -198,8 +199,8 @@ func (svc *TradingService) fetchHistoryKlineSeries(ctx context.Context, sr *pb.S
} }
// IndicatorSeries 获取指标实时或历史序列数据, 闭区间 // IndicatorSeries 获取指标实时或历史序列数据, 闭区间
func (svc *TradingService) IndicatorSeries(ctx context.Context, indicatorName string, window uint32, input types.Input, sr *pb.SeriesRange) (matrix []float64, times []int64, err error) { func (svc *TradingService) IndicatorSeries(ctx context.Context, indicatorName string, digit int32, input types.Input, sr *pb.SeriesRange) (matrix []float64, times []int64, err error) {
indicator, ok := svc.indicatorReg.IndicatorW(indicatorName) indicator, ok := svc.indicatorReg.Indicator(indicatorName)
if !ok { if !ok {
err = fmt.Errorf("indicator %s not exists", indicatorName) err = fmt.Errorf("indicator %s not exists", indicatorName)
return return
@ -212,11 +213,16 @@ func (svc *TradingService) IndicatorSeries(ctx context.Context, indicatorName st
} }
// 查询历史指标数据 // 查询历史指标数据
requiredSeries := int(indicator.RequiredSeries(int16(window), input))
sr.WindowExtra = uint32(max(0, requiredSeries-1))
kSeries := sig.NewKlineSeries(sr.Exchange, sr.InstId, types.Interval(sr.Interval)) kSeries := sig.NewKlineSeries(sr.Exchange, sr.InstId, types.Interval(sr.Interval))
indicatorContext := sig.NewIndicatorContext(input, kSeries) indicatorContext := sig.NewIndicatorContext(indicator, input, sig.NewIndicatorStates(), kSeries, svc.indicatorReg)
candlePeriods := int(indicator.CandlePeriods(indicatorContext))
sr.Desc = false
sr.WindowExtra = uint32(max(0, candlePeriods-1))
// 保留小数位数
digit = lang.Ternary(digit > 0 && digit <= 10, digit, 6)
pow := math.Pow(10, float64(digit))
matrix = make([]float64, 0, 200) matrix = make([]float64, 0, 200)
times = make([]int64, 0, 200) times = make([]int64, 0, 200)
err = svc.fetchHistoryKlineSeries(ctx, sr, func(k *types.Kline) (err error) { err = svc.fetchHistoryKlineSeries(ctx, sr, func(k *types.Kline) (err error) {
@ -224,11 +230,11 @@ func (svc *TradingService) IndicatorSeries(ctx context.Context, indicatorName st
err = fmt.Errorf("kline not series: %s(%s), interval=%s, lastTs=%d", sr.InstId, sr.Exchange, interval, lastTs) err = fmt.Errorf("kline not series: %s(%s), interval=%s, lastTs=%d", sr.InstId, sr.Exchange, interval, lastTs)
return return
} }
if kSeries.Length() < requiredSeries { if kSeries.Length() < candlePeriods {
return return
} }
vector := indicator.Calculate(indicatorContext, int16(window)) vector := indicator.Calculate(indicatorContext)
matrix = append(matrix, vector) matrix = append(matrix, math.Round(vector*pow)/pow)
times = append(times, indicatorContext.Get(0).Ts) times = append(times, indicatorContext.Get(0).Ts)
return return
}) })

16
pkg/indicator/atr.go

@ -11,16 +11,22 @@ type ATR struct {
} }
// indicator interface // indicator interface
func (c *ATR) Name() string { func (c *ATR) Meta() IndicatorMeta {
return "atr" return IndicatorMeta{
Name: "atr",
Input: []types.InputArg{
{Name: "window", Type: types.InputTypeUInt, Desc: "窗口大小"},
},
}
} }
func (c *ATR) RequiredSeries(window int16, in types.Input) int16 { func (c *ATR) CandlePeriods(ctx IIndicatorContext) int16 {
return window + 1 return ctx.Input().Int16("window") + 1
} }
// Calculate 计算单根k线rsi指标 // Calculate 计算单根k线rsi指标
func (c *ATR) Calculate(ctx IIndicatorContext, window int16) (vector float64) { func (c *ATR) Calculate(ctx IIndicatorContext) (vector float64) {
window := ctx.Input().Int16("window")
klineSeries := ctx.Series(0, int16(window)+1) klineSeries := ctx.Series(0, int16(window)+1)
highs := klineSeries.High() highs := klineSeries.High()
lows := klineSeries.Low() lows := klineSeries.Low()

37
pkg/indicator/ema.go

@ -8,28 +8,33 @@ import (
type EMA struct { type EMA struct {
} }
func (c *EMA) Name() string { func (c *EMA) Meta() IndicatorMeta {
return "ema" return IndicatorMeta{
Name: "ema",
Input: []types.InputArg{
{Name: "window", Type: types.InputTypeUInt, Desc: "窗口大小"},
},
}
} }
func (c *EMA) RequiredSeries(window int16, in types.Input) int16 { func (c *EMA) CandlePeriods(ctx IIndicatorContext) int16 {
return window + 1 return ctx.Input().Int16("window") + 1
} }
// Calculate 计算单根k线sma指标 // Calculate 计算单根k线sma指标
func (c *EMA) Calculate(ctx IIndicatorContext, window int16) (vector float64) { func (c *EMA) Calculate(ctx IIndicatorContext) (vector float64) {
alpha := 2.0 / float64(window+1) window := ctx.Input().Int16("window")
prevEma, ok := ctx.State().Get("ema", 1)
if !ok {
// 初始值用 sma 替代
prevEma = ctx.Series(1, window).Close().Avg()
}
multiplier := 2.0 / float64(window+1)
close := ctx.Get(0).CloseF64() close := ctx.Get(0).CloseF64()
prevSMA := ctx.Series(1, window).Close().Avg() vector = multiplier*close + (1-multiplier)*prevEma
// same: vector = ((close - prevEma) * multiplier) + prevEma
vector = ((close - prevSMA) * alpha) + prevSMA
// ctx.GetSelf(0) // 自己计算的上一个值
// 计算eam ctx.State().Set("ema", vector)
// closeSeries := ctx.Series(0, window).Close().Reverse()
// ema := talib.Ema(closeSeries, int(window))
// vector = ema[len(ema)-1]
return return
} }

42
pkg/indicator/indicator.go

@ -6,35 +6,49 @@ import (
) )
const ( const (
MaxWindow = 128 MaxWindow = 256
) )
// IIndicator 指标基础计算接口 type IndicatorMeta struct {
type IIndicator interface { Name string `json:"name"` // 指标名称
Name() string Desc string `json:"desc"` // 指标描述
Calculate(kSeries IIndicatorContext) (vector float64) Input []types.InputArg `json:"input"` // 输入参数
} }
// IIndicator 窗口指标基础计算接口 // IIndicator 指标基础计算接口
type IWindowIndicator interface { type IIndicator interface {
// Name 指标名称 // Meta 指标元信息
Name() string Meta() IndicatorMeta
// RequiredSeries 计算窗口大小的指标值需要的K线数量 // CandlePeriods 计算窗口大小的指标值需要的K线数量
RequiredSeries(window int16, in types.Input) int16 CandlePeriods(ctx IIndicatorContext) int16
// Calculate 计算窗口大小的指标值 // Calculate 计算窗口大小的指标值
Calculate(ctx IIndicatorContext, window int16) (vector float64) Calculate(ctx IIndicatorContext) (vector float64)
} }
// IIndicatorContext k线序列, trading服务提供 // IIndicatorContext k线序列, trading服务提供
type IIndicatorContext interface { type IIndicatorContext interface {
// Input 获取输入参数
Input() types.Input
Get(offset int16) (kline types.Kline) Get(offset int16) (kline types.Kline)
Series(offset, count int16) (klines series.Klines) Series(offset, count int16) (klines series.Klines)
// Input 获取输入参数
Input() types.Input
// State 存储指标运行中状态
State() IIndicatorState
// IndicatorW 获取其他指标
Indicator(name string, args ...any) (series IIndicatorSeries)
} }
// IIndicatorSeries 指标序列, 供策略读取, trading服务提供 // IIndicatorSeries 指标序列, 供策略读取, trading服务提供
type IIndicatorSeries interface { type IIndicatorSeries interface {
CandlePeriods() int16
Get(offset int16) (vector float64) Get(offset int16) (vector float64)
Series(offset, count int16) (matrix series.Floats) Series(offset, count int16) (matrix series.Floats)
} }
type IIndicatorState interface {
// Set 存储指标当前状态
Set(k string, v float64)
// Get 获取指标之前存储的状态 offset >= 1
Get(k string, offset int16) (v float64, ok bool)
// Series 获取指标之前存储的状态序列 offset >= 1, count >= 1
Series(k string, offset, count int16) (v series.Floats, ok bool)
}

22
pkg/indicator/indicator_registry.go

@ -7,12 +7,12 @@ import (
// 指标注册器 // 指标注册器
type IndicatorRegistry struct { type IndicatorRegistry struct {
indicatorsW *collect.SyncMap[string, IWindowIndicator] // 注册窗口指标 indicators *collect.SyncMap[string, IIndicator] // 注册窗口指标
} }
func NewIndicatorRegistry() *IndicatorRegistry { func NewIndicatorRegistry() *IndicatorRegistry {
return &IndicatorRegistry{ return &IndicatorRegistry{
indicatorsW: collect.NewSyncMap[string, IWindowIndicator](), indicators: collect.NewSyncMap[string, IIndicator](),
} }
} }
@ -23,13 +23,17 @@ func (r *IndicatorRegistry) Init() (err error) {
r.MustRegistIndicatorW(&ATR{}) r.MustRegistIndicatorW(&ATR{})
r.MustRegistIndicatorW(&EMA{}) r.MustRegistIndicatorW(&EMA{})
r.MustRegistIndicatorW(&MACD{}) r.MustRegistIndicatorW(&MACD{})
r.MustRegistIndicatorW(&MacdDEA{})
r.MustRegistIndicatorW(&MacdHist{})
r.MustRegistIndicatorW(&OBV{})
r.MustRegistIndicatorW(&WOBV{})
return return
} }
// RegistIndicatorW // RegistIndicatorW
func (r *IndicatorRegistry) RegistIndicatorW(ind IWindowIndicator) (err error) { func (r *IndicatorRegistry) RegistIndicatorW(ind IIndicator) (err error) {
indName := ind.Name() indName := ind.Meta().Name
_, loaded := r.indicatorsW.LoadOrStore(indName, ind) _, loaded := r.indicators.LoadOrStore(indName, ind)
if loaded { if loaded {
err = fmt.Errorf("window indicator name %s already duplicated", indName) err = fmt.Errorf("window indicator name %s already duplicated", indName)
return return
@ -37,13 +41,13 @@ func (r *IndicatorRegistry) RegistIndicatorW(ind IWindowIndicator) (err error) {
return return
} }
func (r *IndicatorRegistry) MustRegistIndicatorW(ind IWindowIndicator) { func (r *IndicatorRegistry) MustRegistIndicatorW(ind IIndicator) {
if err := r.RegistIndicatorW(ind); err != nil { if err := r.RegistIndicatorW(ind); err != nil {
panic(err) panic(err)
} }
} }
// IndicatorW // Indicator
func (r *IndicatorRegistry) IndicatorW(name string) (indW IWindowIndicator, ok bool) { func (r *IndicatorRegistry) Indicator(name string) (indW IIndicator, ok bool) {
return r.indicatorsW.Load(name) return r.indicators.Load(name)
} }

108
pkg/indicator/macd.go

@ -2,33 +2,111 @@ package indicator
import ( import (
"sig-pub/pkg/types" "sig-pub/pkg/types"
"github.com/markcheno/go-talib"
) )
// todo macdSignal(信号线) macdHist(柱状图) // MACD 拆分成: MACD线, MacdDEA(信号线), MacdHist(柱状图)
// 计算 MACD 线 (DIF): 反映短期趋势与长期趋势的“收敛/散度”
// MACD: https://www.investopedia.com/terms/m/macd.asp
type MACD struct { type MACD struct {
} }
func (c *MACD) Name() string { // indicator interface
return "macd" func (c *MACD) Meta() IndicatorMeta {
return IndicatorMeta{
Name: "macd",
Input: []types.InputArg{
{Name: "fast", Type: types.InputTypeUInt, Desc: "快线周期"},
{Name: "slow", Type: types.InputTypeUInt, Desc: "慢线周期"},
},
}
} }
func (c *MACD) RequiredSeries(window int16, in types.Input) int16 { func (c *MACD) CandlePeriods(ctx IIndicatorContext) int16 {
return window return max(
ctx.Indicator("ema", ctx.Input().Int16("fast")).CandlePeriods(),
ctx.Indicator("ema", ctx.Input().Int16("slow")).CandlePeriods(),
)
} }
// Calculate 计算单根k线sma指标 // Calculate 计算单根k线sma指标
func (c *MACD) Calculate(ctx IIndicatorContext, window int16) (vector float64) { func (c *MACD) Calculate(ctx IIndicatorContext) (vector float64) {
fast := ctx.Input().Int("fast") fast := ctx.Input().Int16("fast") // 12
slow := ctx.Input().Int("slow") slow := ctx.Input().Int16("slow") // 26
// macd计算从第max(fast, slow)期开始稳定
fastEma := ctx.Indicator("ema", fast).Get(0)
slowEma := ctx.Indicator("ema", slow).Get(0)
macd := fastEma - slowEma
vector = macd
return
}
// MacdDEA macd信号线计算
type MacdDEA struct {
}
func (c *MacdDEA) Meta() IndicatorMeta {
return IndicatorMeta{
Name: "macd_dea",
Input: []types.InputArg{
{Name: "fast", Type: types.InputTypeUInt, Desc: "快线周期"},
{Name: "slow", Type: types.InputTypeUInt, Desc: "慢线周期"},
{Name: "singal", Type: types.InputTypeUInt, Desc: "信号线周期"},
},
}
}
func (c *MacdDEA) CandlePeriods(ctx IIndicatorContext) int16 {
return max(
ctx.Input().Int16("singal")+1,
ctx.Indicator("macd", ctx.Input()).CandlePeriods(),
)
}
// 计算eam func (c *MacdDEA) Calculate(ctx IIndicatorContext) (vector float64) {
closeSeries := ctx.Series(0, window).Close().Reverse() singal := ctx.Input().Int16("singal") // 9
aa, bb, cc := talib.Macd(closeSeries, fast, slow, int(window)) deaPrev, ok := ctx.State().Get("macd_dea", 1)
_, _, _ = aa, bb, cc if !ok {
// 初始值前9期的 MACD SMA
macdPrevs := ctx.Indicator("macd", ctx.Input()).Series(1, singal)
deaPrev = macdPrevs.Avg()
}
macd := ctx.Indicator("macd", ctx.Input()).Get(0)
// 计算DEA
beta := 2 / float64(singal+1)
dea := beta*macd + (1-beta)*deaPrev
ctx.State().Set("macd_dea", dea)
vector = dea
return
}
// MacdSingal macd柱状图计算
type MacdHist struct {
}
func (c *MacdHist) Meta() IndicatorMeta {
return IndicatorMeta{
Name: "macd_hist",
Input: []types.InputArg{
{Name: "fast", Type: types.InputTypeUInt, Desc: "快线周期"},
{Name: "slow", Type: types.InputTypeUInt, Desc: "慢线周期"},
{Name: "singal", Type: types.InputTypeUInt, Desc: "信号线周期"},
},
}
}
func (c *MacdHist) CandlePeriods(ctx IIndicatorContext) int16 {
return max(
ctx.Indicator("macd", ctx.Input()).CandlePeriods(),
ctx.Indicator("macd_dea", ctx.Input()).CandlePeriods(),
)
}
vector = 0 func (c *MacdHist) Calculate(ctx IIndicatorContext) (vector float64) {
macd := ctx.Indicator("macd", ctx.Input()).Get(0)
macd_dea := ctx.Indicator("macd_dea", ctx.Input()).Get(0)
vector = macd - macd_dea
return return
} }

41
pkg/indicator/obv.go

@ -0,0 +1,41 @@
package indicator
import "sig-pub/pkg/types"
// OBV 成交量平衡指标
// 1. 初始状态:OBV_0 = 0。
// 2. 若 Close_t > Close_{t-1}:OBV_t = OBV_{t-1} + Volume_t。
// 3. 若 Close_t < Close_{t-1}:OBV_t = OBV_{t-1} - Volume_t。
// 4. 平盘:OBV_t = OBV_{t-1}。
// 状态:前一 OBV 值。用于判断资金流入/流出
type OBV struct {
}
func (c *OBV) Meta() IndicatorMeta {
return IndicatorMeta{
Name: "obv",
Input: []types.InputArg{}, // todo 无参指标tsdb存储
}
}
func (c *OBV) CandlePeriods(ctx IIndicatorContext) int16 {
return 2
}
func (c *OBV) Calculate(ctx IIndicatorContext) (vector float64) {
obvPrev, ok := ctx.State().Get("obv", 1)
if !ok {
obvPrev = 0
}
k := ctx.Get(0)
cmp := k.Close.Cmp(ctx.Get(1).Close)
if cmp > 0 {
vector = obvPrev + k.VolF64()
} else if cmp < 0 {
vector = obvPrev - k.VolF64()
} else {
vector = obvPrev
}
ctx.State().Set("obv", vector)
return
}

17
pkg/indicator/rsi.go

@ -11,16 +11,23 @@ import (
type RSI struct { type RSI struct {
} }
func (c *RSI) Name() string { // indicator interface
return "rsi" func (c *RSI) Meta() IndicatorMeta {
return IndicatorMeta{
Name: "rsi",
Input: []types.InputArg{
{Name: "window", Type: types.InputTypeUInt, Desc: "窗口大小"},
},
}
} }
func (c *RSI) RequiredSeries(window int16, in types.Input) int16 { func (c *RSI) CandlePeriods(ctx IIndicatorContext) int16 {
return window return ctx.Input().Int16("window")
} }
// Calculate 计算单根k线rsi指标 // Calculate 计算单根k线rsi指标
func (c *RSI) Calculate(ctx IIndicatorContext, window int16) (vector float64) { func (c *RSI) Calculate(ctx IIndicatorContext) (vector float64) {
window := ctx.Input().Int16("window")
// 读k线, 计算 // 读k线, 计算
klineSeries := ctx.Series(0, int16(window)) klineSeries := ctx.Series(0, int16(window))
closeSeries := klineSeries.Close() closeSeries := klineSeries.Close()

16
pkg/indicator/sam.go

@ -12,16 +12,22 @@ type SMA struct {
} }
// indicator interface // indicator interface
func (c *SMA) Name() string { func (c *SMA) Meta() IndicatorMeta {
return "sma" return IndicatorMeta{
Name: "sma",
Input: []types.InputArg{
{Name: "window", Type: types.InputTypeUInt, Desc: "窗口大小"},
},
}
} }
func (c *SMA) RequiredSeries(window int16, in types.Input) int16 { func (c *SMA) CandlePeriods(ctx IIndicatorContext) int16 {
return window return ctx.Input().Int16("window")
} }
// Calculate 计算单根k线sma指标 // Calculate 计算单根k线sma指标
func (c *SMA) Calculate(ctx IIndicatorContext, window int16) (vector float64) { func (c *SMA) Calculate(ctx IIndicatorContext) (vector float64) {
window := ctx.Input().Int16("window")
closeSeries := ctx.Series(0, window).Close() closeSeries := ctx.Series(0, window).Close()
sma := talib.Sma(closeSeries, int(window)) sma := talib.Sma(closeSeries, int(window))
_ = sma[len(sma)-1] _ = sma[len(sma)-1]

37
pkg/indicator/wobv.go

@ -0,0 +1,37 @@
package indicator
import "sig-pub/pkg/types"
// WOBV 波动加权 OBV
// 1. 状态:WOBV_{t-1}。
// 2. 更新:WOBV_t = WOBV_{t-1} + [ (Close - Open) / (High - Low) × Volume_t ]。
// https://www.95sca.cn/archives/76688
// WOBV小策略: https://zhuanlan.zhihu.com/p/422341694
type WOBV struct {
}
func (c *WOBV) Meta() IndicatorMeta {
return IndicatorMeta{
Name: "wobv",
Input: []types.InputArg{}, // todo 无参指标tsdb存储
}
}
func (c *WOBV) CandlePeriods(ctx IIndicatorContext) int16 {
return 2
}
func (c *WOBV) Calculate(ctx IIndicatorContext) (vector float64) {
wobvPrev, ok := ctx.State().Get("wobv", 1)
if !ok {
wobvPrev = 0
}
k := ctx.Get(0)
wf := (k.CloseF64() - k.OpenF64()) / (k.HighF64() - k.LowF64())
wobv := wobvPrev + wf*k.VolF64()
ctx.State().Set("wobv", wobv)
vector = wobv
return
}

6
pkg/strategy/cross_star.go

@ -21,9 +21,9 @@ func (s *CrossStar) Meta() StrategyMeta {
return StrategyMeta{ return StrategyMeta{
Name: "CrossStar", Name: "CrossStar",
Desc: "十字星策略", Desc: "十字星策略",
Args: []Param{ Input: []types.InputArg{
{Name: "rate", Type: ParamTypeUFloat, Desc: "上线影线与基线比例"}, {Name: "rate", Type: types.InputTypeUFloat, Desc: "上线影线与基线比例"},
{Name: "rate2", Type: ParamTypeUFloat, Desc: "上线影线之间比例"}, {Name: "rate2", Type: types.InputTypeUFloat, Desc: "上线影线之间比例"},
}, },
} }
} }

10
pkg/strategy/gold_x.go

@ -19,9 +19,9 @@ func (s *GoldX) Meta() StrategyMeta {
return StrategyMeta{ return StrategyMeta{
Name: "GoldX", Name: "GoldX",
Desc: "金叉策略", Desc: "金叉策略",
Args: []Param{ Input: []types.InputArg{
{Name: "short", Type: ParamTypeUInt, Desc: "短周期"}, {Name: "short", Type: types.InputTypeUInt, Desc: "短周期"},
{Name: "long", Type: ParamTypeUInt, Desc: "长周期"}, {Name: "long", Type: types.InputTypeUInt, Desc: "长周期"},
}, },
} }
} }
@ -41,8 +41,8 @@ func (s *GoldX) RequiredSeries(input types.Input) int16 {
} }
func (s *GoldX) Update(ctx ISingleSigStrategyContext) (side types.Side) { func (s *GoldX) Update(ctx ISingleSigStrategyContext) (side types.Side) {
sma14 := ctx.IndicatorW("sma", s.short) sma14 := ctx.Indicator("sma", s.short)
sma28 := ctx.IndicatorW("sma", s.long) sma28 := ctx.Indicator("sma", s.long)
// 包装方法 crossover/crossunder // 包装方法 crossover/crossunder
s14 := sma14.Series(0, 2) s14 := sma14.Series(0, 2)
s28 := sma28.Series(0, 2) s28 := sma28.Series(0, 2)

8
pkg/strategy/sig_strategy.go

@ -16,7 +16,7 @@ type ISigStrategy interface {
type StrategyMeta struct { type StrategyMeta struct {
Name string `json:"name"` Name string `json:"name"`
Desc string `json:"desc"` Desc string `json:"desc"`
Args []Param `json:"args"` // 参数定义 Input []types.InputArg `json:"input"` // 参数定义
} }
// ISingleSigStrategy 单周期单交易所策略 // ISingleSigStrategy 单周期单交易所策略
@ -34,8 +34,8 @@ type ISingleSigStrategyContext interface {
Get(offset int16) types.Kline Get(offset int16) types.Kline
// Series [offset...end] // Series [offset...end]
Series(offset, count int16) (klines series.Klines) Series(offset, count int16) (klines series.Klines)
// IndicatorW 获取窗口类型指标 // Indicator 获取窗口类型指标
IndicatorW(name string, window int16, args ...any) indicator.IIndicatorSeries Indicator(name string, args ...any) indicator.IIndicatorSeries
} }
// 多周期k线策略接口 // 多周期k线策略接口
@ -54,5 +54,5 @@ type IIntervalSigStrategyContext interface {
// Series [offset...end] // Series [offset...end]
Series(interval types.Interval, offset, count int16) (klines series.Klines) Series(interval types.Interval, offset, count int16) (klines series.Klines)
// 获取窗口类型指标 // 获取窗口类型指标
IndicatorW(interval types.Interval, name string, window int16, args ...any) indicator.IIndicatorSeries Indicator(interval types.Interval, name string, args ...any) indicator.IIndicatorSeries
} }

60
pkg/strategy/sig_strategy_params.go → pkg/strategy/sig_strategy_input_adjust.go

@ -6,66 +6,6 @@ import (
"github.com/spf13/cast" "github.com/spf13/cast"
) )
// 参数类型
type ParamType int8
const (
_ ParamType = iota
ParamTypeBool
ParamTypeFloat
ParamTypeUFloat
ParamTypeInt
ParamTypeUInt
ParamTypeString
ParamTypeSelect // 单选
ParamTypeCheckBox // 多选
)
type Param struct {
Name string `json:"name"`
Desc string `json:"desc"`
Type ParamType `json:"type"` // 参数类型
Options []ParamOption `json:"options"` // 单选/多选选项列表
}
type ParamOption struct {
Name string `json:"name"`
Desc string `json:"desc"`
}
// CastValidate 数据类型校验
func (t Param) TypeValidate(v string) bool {
switch t.Type {
default:
return false
case ParamTypeBool:
if _, e := cast.ToBoolE(v); e != nil {
return false
}
return true
case ParamTypeString:
return true
case ParamTypeInt:
fallthrough
case ParamTypeUInt:
if r, e := cast.ToIntE(v); e != nil {
return false
} else if t.Type == ParamTypeUInt {
return r >= 0
}
return true
case ParamTypeFloat:
fallthrough
case ParamTypeUFloat:
if r, e := cast.ToFloat64E(v); e != nil {
return false
} else if t.Type == ParamTypeUFloat {
return r >= 0
}
return true
}
}
// 策略默认参数 // 策略默认参数
type ISigStrategyDefaultParam interface { type ISigStrategyDefaultParam interface {
DefaultParam() map[string]string DefaultParam() map[string]string

12
pkg/strategy/super_trend.go

@ -28,10 +28,10 @@ func (s *SupertrendBOSWaves) Meta() StrategyMeta {
return StrategyMeta{ return StrategyMeta{
Name: "SupertrendBOSWaves", Name: "SupertrendBOSWaves",
Desc: "曲线半径超级趋势 [BOSWaves] https://www.tradingview.com/script/v0Fr7PAb-Curved-Radius-Supertrend-BOSWaves/", Desc: "曲线半径超级趋势 [BOSWaves] https://www.tradingview.com/script/v0Fr7PAb-Curved-Radius-Supertrend-BOSWaves/",
Args: []Param{ Input: []types.InputArg{
{Name: "atrLength", Type: ParamTypeUInt, Desc: "atr指标长度,14"}, {Name: "atrLength", Type: types.InputTypeUInt, Desc: "atr指标长度,14"},
{Name: "atrMult", Type: ParamTypeUFloat, Desc: "atr倍数,2"}, {Name: "atrMult", Type: types.InputTypeUFloat, Desc: "atr倍数,2"},
{Name: "radiusStrength", Type: ParamTypeUFloat, Desc: ` {Name: "radiusStrength", Type: types.InputTypeUFloat, Desc: `
Controls curve acceleration strength.\n\n" + Controls curve acceleration strength.\n\n" +
"Recommended values by timeframe:\n" + "Recommended values by timeframe:\n" +
"• 1-5min (Scalping): 0.08-0.12\n" + "• 1-5min (Scalping): 0.08-0.12\n" +
@ -43,7 +43,7 @@ func (s *SupertrendBOSWaves) Meta() StrategyMeta {
"Lower = Tighter curves (responsive)\n" + "Lower = Tighter curves (responsive)\n" +
"Higher = Wider curves (smoother) "Higher = Wider curves (smoother)
`}, `},
{Name: "smoothness", Type: ParamTypeUInt, Desc: "Smoothing applied to curved band. Higher = smoother curves, less noise."}, {Name: "smoothness", Type: types.InputTypeUInt, Desc: "Smoothing applied to curved band. Higher = smoother curves, less noise."},
}, },
} }
} }
@ -64,7 +64,7 @@ func (s *SupertrendBOSWaves) Update(ctx ISingleSigStrategyContext) (side types.S
k0 := ctx.Get(0) k0 := ctx.Get(0)
high, low, close := k0.HighF64(), k0.LowF64(), k0.CloseF64() high, low, close := k0.HighF64(), k0.LowF64(), k0.CloseF64()
atr := ctx.IndicatorW("atr", s.atrLength).Get(0) atr := ctx.Indicator("atr", s.atrLength).Get(0)
src := (high + low) / 2 src := (high + low) / 2
// src := k0.HL2() // src := k0.HL2()

53
pkg/types/input.go

@ -50,7 +50,9 @@ func (in Input) get(k string, t string) (r any) {
func (in Input) Float(k string) (v float64) { func (in Input) Float(k string) (v float64) {
if r, ok := in.getCache(k); ok { if r, ok := in.getCache(k); ok {
return r.(float64) if v, ok = r.(float64); ok {
return
}
} }
v, err := cast.ToFloat64E(in.get(k, "float")) v, err := cast.ToFloat64E(in.get(k, "float"))
if err != nil { if err != nil {
@ -62,7 +64,9 @@ func (in Input) Float(k string) (v float64) {
func (in Input) Int(k string) (v int) { func (in Input) Int(k string) (v int) {
if r, ok := in.getCache(k); ok { if r, ok := in.getCache(k); ok {
return r.(int) if v, ok = r.(int); ok {
return
}
} }
v, err := cast.ToIntE(in.get(k, "int")) v, err := cast.ToIntE(in.get(k, "int"))
if err != nil { if err != nil {
@ -74,7 +78,9 @@ func (in Input) Int(k string) (v int) {
func (in Input) Int16(k string) (v int16) { func (in Input) Int16(k string) (v int16) {
if r, ok := in.getCache(k); ok { if r, ok := in.getCache(k); ok {
return r.(int16) if v, ok = r.(int16); ok {
return
}
} }
v, err := cast.ToInt16E(in.get(k, "int16")) v, err := cast.ToInt16E(in.get(k, "int16"))
if err != nil { if err != nil {
@ -83,3 +89,44 @@ func (in Input) Int16(k string) (v int16) {
in.setCache(k, v) in.setCache(k, v)
return return
} }
func (in Input) String(k string) (v string) {
if r, ok := in.getCache(k); ok {
if v, ok = r.(string); ok {
return
}
}
v, err := cast.ToStringE(in.get(k, "string"))
if err != nil {
panic(fmt.Errorf("input string parse error: %s", k))
}
in.setCache(k, v)
return
}
// 参数类型
type InputType int8
const (
_ InputType = iota
InputTypeBool
InputTypeFloat
InputTypeUFloat
InputTypeInt
InputTypeUInt
InputTypeString
InputTypeSelect // 单选
InputTypeCheckBox // 多选
)
type InputArg struct {
Name string `json:"name"`
Desc string `json:"desc"`
Type InputType `json:"type"` // 参数类型
Options []InputOption `json:"options"` // 单选/多选选项列表
}
type InputOption struct {
Name string `json:"name"`
Desc string `json:"desc"`
}

69
pkg/types/ring_series.go

@ -0,0 +1,69 @@
package types
import "fmt"
// RingSeries 环形数组, 后入先出
type RingSeries[T any] struct {
values []T
capacity int // 总长度
length int // 当前长度
head int // 下一个读取位置
tail int // 下一个丢弃位置
full bool // 环形数组是否已满
}
func NewRingSeries[T any](capacity, init int) *RingSeries[T] {
if capacity <= 0 {
panic(fmt.Errorf("ring series capacity must > 0"))
}
return &RingSeries[T]{
capacity: capacity,
values: make([]T, 0, init),
head: -1,
}
}
func (r *RingSeries[T]) Length() int {
return r.length
}
func (r *RingSeries[T]) Push(v T) (ok bool) {
if !r.full {
r.values = append(r.values, v)
r.head++
r.length++
r.full = r.length == r.capacity
return
}
r.values[r.tail] = v
r.head = r.tail
r.tail = (r.tail + 1) % r.capacity
return
}
// Get 0当前, 1前一个
func (r *RingSeries[T]) Get(offset int) (v T, ok bool) {
if offset < 0 || offset >= r.length || r.length == 0 {
return
}
i := r.head - offset
if i < 0 {
i = i + r.capacity
}
return r.values[i], true
}
func (r *RingSeries[T]) Series(offset, count int) (v []T, ok bool) {
if offset < 0 || offset >= r.length || count <= 0 || count > r.length {
return
}
v = make([]T, count)
for i := range count {
v[i], ok = r.Get(offset)
if !ok {
return
}
offset++
}
return
}

30
pkg/types/ring_series_test.go

@ -0,0 +1,30 @@
package types
import (
"fmt"
"testing"
)
func TestRingSeries(t *testing.T) {
rs1 := NewRingSeries[int](1, 1)
rs1.Push(1)
r1, ok := rs1.Get(0)
if !ok {
t.Error(ok)
return
}
if r1 != 1 {
t.Error(r1)
return
}
rs2 := NewRingSeries[int](10, 3)
for i := range 11 {
rs2.Push(i)
}
for i := range 10 {
fmt.Println(rs2.Get(i))
}
fmt.Println("--------------------")
fmt.Println(rs2.Series(0, 1))
}
Loading…
Cancel
Save