32 changed files with 921 additions and 168 deletions
@ -0,0 +1,46 @@ |
|||||||
|
package backtest |
||||||
|
|
||||||
|
import ( |
||||||
|
"sig-pub/api/pb" |
||||||
|
"sig-pub/pkg/indicator" |
||||||
|
"sig-pub/pkg/strategy" |
||||||
|
"sig-pub/pkg/types" |
||||||
|
) |
||||||
|
|
||||||
|
type TradingPlanInputRacer struct { |
||||||
|
sigStrategyType strategy.SigStrategyType |
||||||
|
sigStrategy strategy.ISigStrategy |
||||||
|
indicatorReg *indicator.IndicatorRegistry |
||||||
|
exchangeClient pb.ExchangeServiceClient |
||||||
|
} |
||||||
|
|
||||||
|
func NewTradingPlanInputRacer( |
||||||
|
sigType strategy.SigStrategyType, |
||||||
|
strategy strategy.ISigStrategy, |
||||||
|
indicatorReg *indicator.IndicatorRegistry, |
||||||
|
exchangeClient pb.ExchangeServiceClient, |
||||||
|
) *TradingPlanInputRacer { |
||||||
|
return &TradingPlanInputRacer{ |
||||||
|
sigStrategyType: sigType, |
||||||
|
sigStrategy: strategy, |
||||||
|
indicatorReg: indicatorReg, |
||||||
|
exchangeClient: exchangeClient, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (r *TradingPlanInputRacer) Init() (err error) { |
||||||
|
a := types.InputRange{} |
||||||
|
_ = a |
||||||
|
// plan := &entity.TradePlan{}
|
||||||
|
// plan.SigStrategy
|
||||||
|
// SigStrategyParam
|
||||||
|
// CloseStrategyParam
|
||||||
|
// TradeStrategyParam
|
||||||
|
// RiskStrategyParam
|
||||||
|
// tester := NewTradingPlanBacktester(1, nil, nil, nil)
|
||||||
|
// tester.Init(10000, *plan)
|
||||||
|
// result, err := tester.Backtest(nil, nil)
|
||||||
|
// _, _ = result, err
|
||||||
|
|
||||||
|
return |
||||||
|
} |
||||||
@ -0,0 +1,236 @@ |
|||||||
|
package types |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"github.com/bytedance/sonic" |
||||||
|
) |
||||||
|
|
||||||
|
// InputGroup 参数输入组合, 梯度下降
|
||||||
|
// type InputGroup struct {
|
||||||
|
// Inputs []InputRange `json:"inputs"`
|
||||||
|
// }
|
||||||
|
|
||||||
|
// InputRange 参数输入范围
|
||||||
|
// SuperTrend fast 1 [10,20,1]
|
||||||
|
// SuperTrend slow 1 [10,20,1]
|
||||||
|
// SuperTrend singal 1 [10,20,1]
|
||||||
|
type InputRange struct { |
||||||
|
Name string `json:"name"` // 参数名 names
|
||||||
|
Type int32 `json:"type"` // 1.range, 2.enum, 3.simple group
|
||||||
|
Value string `json:"value"` // range => [10,20,1](min,max,step); enum => [1,2,3,4,5]; simple group => [[2025-10-01,2025-12-31],[2025-01-01,2025-12-31]]
|
||||||
|
} |
||||||
|
|
||||||
|
func (ir InputRange) NewInputGen() (gen InputGen, err error) { |
||||||
|
switch ir.Type { |
||||||
|
case 0: |
||||||
|
gen = new(inputFixedGen) |
||||||
|
case 1: |
||||||
|
gen = new(inputStepGen) |
||||||
|
case 2: |
||||||
|
gen = new(inputEnumGen) |
||||||
|
case 3: |
||||||
|
gen = new(simpleInputGroupGen) |
||||||
|
} |
||||||
|
if gen != nil { |
||||||
|
err = gen.init(ir.Name, ir.Value) |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
type InputGen interface { |
||||||
|
init(name, value string) (err error) |
||||||
|
Next() (in Input, ok bool) // 生成下一个值
|
||||||
|
Values() []Input |
||||||
|
} |
||||||
|
|
||||||
|
// inputFixedGen 单个固定值
|
||||||
|
type inputFixedGen struct { |
||||||
|
name, r string |
||||||
|
got bool |
||||||
|
} |
||||||
|
|
||||||
|
func (c *inputFixedGen) init(name, r string) (err error) { |
||||||
|
c.name = name |
||||||
|
c.r = r |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
func (c *inputFixedGen) Next() (in Input, ok bool) { |
||||||
|
if c.got { |
||||||
|
return |
||||||
|
} |
||||||
|
return Input{c.name: c.r}, true |
||||||
|
} |
||||||
|
|
||||||
|
func (c *inputFixedGen) Values() (vs []Input) { |
||||||
|
in, ok := c.Next() |
||||||
|
if !ok { |
||||||
|
return |
||||||
|
} |
||||||
|
vs = append(vs, in) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// inputStepGen 数值范围生成器 "min,max,step" -> "0,10,2"
|
||||||
|
type inputStepGen struct { |
||||||
|
name string |
||||||
|
genType int8 // 1.float64 2.int64
|
||||||
|
fmin, fmax, fstep float64 |
||||||
|
imin, imax, istep int64 |
||||||
|
} |
||||||
|
|
||||||
|
func (c *inputStepGen) init(name, r string) (err error) { |
||||||
|
c.name = name |
||||||
|
r = strings.TrimSuffix(strings.TrimPrefix(r, "["), "]") |
||||||
|
vs := strings.Split(r, ",") |
||||||
|
if len(vs) != 3 { |
||||||
|
return fmt.Errorf("step generator format error") |
||||||
|
} |
||||||
|
min, max, step := vs[0], vs[1], vs[2] |
||||||
|
// any float use float
|
||||||
|
if strings.Contains(min, ".") || strings.Contains(max, ".") || strings.Contains(step, ".") { |
||||||
|
c.genType = 1 |
||||||
|
if c.fmin, err = strconv.ParseFloat(min, 64); err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
if c.fmax, err = strconv.ParseFloat(max, 64); err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
if c.fstep, err = strconv.ParseFloat(step, 64); err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
} else { |
||||||
|
c.genType = 2 |
||||||
|
if c.imin, err = strconv.ParseInt(min, 10, 64); err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
if c.imax, err = strconv.ParseInt(max, 10, 64); err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
if c.istep, err = strconv.ParseInt(step, 10, 64); err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// Next 生成下一个值
|
||||||
|
func (c *inputStepGen) Next() (in Input, ok bool) { |
||||||
|
switch c.genType { |
||||||
|
case 1: |
||||||
|
if c.fmin <= c.fmax { |
||||||
|
v := c.fmin |
||||||
|
c.fmin += c.fstep |
||||||
|
return Input{c.name: v}, true |
||||||
|
} |
||||||
|
case 2: |
||||||
|
if c.imin <= c.imax { |
||||||
|
v := c.imin |
||||||
|
c.imin += c.istep |
||||||
|
return Input{c.name: v}, true |
||||||
|
} |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
func (c *inputStepGen) Values() (vs []Input) { |
||||||
|
for { |
||||||
|
v, ok := c.Next() |
||||||
|
if !ok { |
||||||
|
break |
||||||
|
} |
||||||
|
vs = append(vs, v) |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
type inputEnumGen struct { |
||||||
|
name string |
||||||
|
enums []string |
||||||
|
index int |
||||||
|
} |
||||||
|
|
||||||
|
func (c *inputEnumGen) init(name, r string) (err error) { |
||||||
|
c.name = name |
||||||
|
if r == "" { |
||||||
|
return fmt.Errorf("enum value empty") |
||||||
|
} |
||||||
|
r = strings.TrimSuffix(strings.TrimPrefix(r, "["), "]") |
||||||
|
c.enums = strings.Split(r, ",") |
||||||
|
if len(c.enums) == 0 { |
||||||
|
return |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// Next 生成下一个值
|
||||||
|
func (c *inputEnumGen) Next() (in Input, ok bool) { |
||||||
|
if c.index >= len(c.enums) { |
||||||
|
return |
||||||
|
} |
||||||
|
v := c.enums[c.index] |
||||||
|
c.index++ |
||||||
|
return Input{c.name: v}, true |
||||||
|
} |
||||||
|
|
||||||
|
func (c *inputEnumGen) Values() (vs []Input) { |
||||||
|
vs = make([]Input, 0, len(c.enums)) |
||||||
|
for _, v := range c.enums { |
||||||
|
vs = append(vs, Input{c.name: v}) |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
type simpleInputGroupGen struct { |
||||||
|
names []string |
||||||
|
enums [][]any |
||||||
|
index int |
||||||
|
} |
||||||
|
|
||||||
|
func (c *simpleInputGroupGen) init(name, r string) (err error) { |
||||||
|
c.names = strings.Split(name, ",") |
||||||
|
if r == "" { |
||||||
|
return errors.New("empty values") |
||||||
|
} |
||||||
|
if err = sonic.UnmarshalString(r, &c.enums); err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
if len(c.enums) == 0 { |
||||||
|
return errors.New("empty values") |
||||||
|
} |
||||||
|
for i, enums := range c.enums { |
||||||
|
if len(enums) != len(c.names) { |
||||||
|
err = fmt.Errorf("simple input group enums %d, length %d not match names %#v", i, len(enums), c.names) |
||||||
|
} |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// Next 生成下一个值
|
||||||
|
func (c *simpleInputGroupGen) Next() (in Input, ok bool) { |
||||||
|
if c.index >= len(c.enums) { |
||||||
|
return |
||||||
|
} |
||||||
|
enums := c.enums[c.index] |
||||||
|
c.index++ |
||||||
|
in = make(Input, len(c.names)) |
||||||
|
for i, name := range c.names { |
||||||
|
in[name] = enums[i] |
||||||
|
} |
||||||
|
return in, true |
||||||
|
} |
||||||
|
|
||||||
|
func (c *simpleInputGroupGen) Values() (vs []Input) { |
||||||
|
for { |
||||||
|
v, ok := c.Next() |
||||||
|
if !ok { |
||||||
|
break |
||||||
|
} |
||||||
|
vs = append(vs, v) |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
@ -1,30 +0,0 @@ |
|||||||
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)) |
|
||||||
} |
|
||||||
@ -0,0 +1,23 @@ |
|||||||
|
package codec |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"testing" |
||||||
|
) |
||||||
|
|
||||||
|
type Config struct { |
||||||
|
Fm float64 |
||||||
|
} |
||||||
|
|
||||||
|
func TestMapstructure(t *testing.T) { |
||||||
|
// var arr1 [][]float64
|
||||||
|
// arr2T := reflect.SliceOf(reflect.SliceOf(reflect.TypeOf(1.0)))
|
||||||
|
// fmt.Println(reflect.TypeOf(arr1) == arr2T, arr2T.Kind())
|
||||||
|
|
||||||
|
cfg := &Config{} |
||||||
|
err := MapDecode(map[string]any{"fm": "3.1415"}, cfg) |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
fmt.Printf("%#v\n", cfg) |
||||||
|
} |
||||||
@ -0,0 +1,60 @@ |
|||||||
|
package codec |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"reflect" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"github.com/bytedance/sonic" |
||||||
|
"github.com/go-viper/mapstructure/v2" |
||||||
|
) |
||||||
|
|
||||||
|
func MapDecode(input, output any) (err error) { |
||||||
|
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ |
||||||
|
Result: output, |
||||||
|
WeaklyTypedInput: true, // 开启弱类型转换
|
||||||
|
DecodeHook: mapstructure.ComposeDecodeHookFunc(StringToNumberHook()), |
||||||
|
}) |
||||||
|
if err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
err = decoder.Decode(input) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// 方案1:最推荐 - 字符串 → 数字(int/uint/float)全覆盖
|
||||||
|
func StringToNumberHook() mapstructure.DecodeHookFunc { |
||||||
|
return mapstructure.DecodeHookFuncType(func(from reflect.Type, to reflect.Type, data interface{}) (interface{}, error) { |
||||||
|
fmt.Println("hook1") |
||||||
|
if from.Kind() == reflect.String { |
||||||
|
str := data.(string) |
||||||
|
str = strings.TrimSpace(str) |
||||||
|
switch to.Kind() { |
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
||||||
|
return strconv.ParseInt(str, 10, 64) |
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: |
||||||
|
return strconv.ParseUint(str, 10, 64) |
||||||
|
case reflect.Float32, reflect.Float64: |
||||||
|
return strconv.ParseFloat(str, 64) |
||||||
|
} |
||||||
|
|
||||||
|
// string to []float64, [][]float64
|
||||||
|
if to.Kind() == reflect.Slice { |
||||||
|
switch to { |
||||||
|
case reflect.SliceOf(reflect.TypeOf(float64(0.0))): |
||||||
|
var v []float64 |
||||||
|
if err := sonic.UnmarshalString(str, &v); err == nil { |
||||||
|
return v, nil |
||||||
|
} |
||||||
|
case reflect.SliceOf(reflect.SliceOf(reflect.TypeOf(float64(0.0)))): |
||||||
|
var v [][]float64 |
||||||
|
if err := sonic.UnmarshalString(str, &v); err == nil { |
||||||
|
return v, nil |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return data, nil |
||||||
|
}) |
||||||
|
} |
||||||
@ -0,0 +1,99 @@ |
|||||||
|
package collect |
||||||
|
|
||||||
|
// CartesianCount 笛卡尔积组合数
|
||||||
|
func CartesianCount[T any](sets ...[]T) (total int) { |
||||||
|
total = 1 |
||||||
|
for _, set := range sets { |
||||||
|
total *= len(set) |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// CartesianProduct 笛卡尔积(返回所有可能的组合)
|
||||||
|
// 输入: [][]T 类型的二维切片
|
||||||
|
// 输出: []T 类型的切片组成的切片
|
||||||
|
func CartesianProduct[T any](sets ...[]T) [][]T { |
||||||
|
if len(sets) == 0 { |
||||||
|
return [][]T{{}} |
||||||
|
} |
||||||
|
|
||||||
|
// 初始化为第一个集合的所有单元素组合
|
||||||
|
result := make([][]T, len(sets[0])) |
||||||
|
for i, v := range sets[0] { |
||||||
|
result[i] = []T{v} |
||||||
|
} |
||||||
|
|
||||||
|
// 依次处理后续每一组
|
||||||
|
for _, set := range sets[1:] { |
||||||
|
if len(set) == 0 { |
||||||
|
return [][]T{} |
||||||
|
} |
||||||
|
|
||||||
|
newResult := make([][]T, 0, len(result)*len(set)) |
||||||
|
|
||||||
|
for _, prev := range result { |
||||||
|
for _, curr := range set { |
||||||
|
// 预分配空间,避免频繁扩容
|
||||||
|
combined := make([]T, 0, len(prev)+1) |
||||||
|
combined = append(combined, prev...) |
||||||
|
combined = append(combined, curr) |
||||||
|
newResult = append(newResult, combined) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
result = newResult |
||||||
|
} |
||||||
|
|
||||||
|
return result |
||||||
|
} |
||||||
|
|
||||||
|
// CartesianYield 生成笛卡尔积的所有组合,并为每个组合调用回调函数
|
||||||
|
// callback: func(comb []T) bool - 处理当前组合,返回 true 继续生成,返回 false 停止
|
||||||
|
// return: 生成的组合总数
|
||||||
|
func CartesianYield[T any](sets [][]T, callback func([]T) bool) int { |
||||||
|
if len(sets) == 0 { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
// 检查是否有空集
|
||||||
|
for _, s := range sets { |
||||||
|
if len(s) == 0 { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 初始化索引计数器
|
||||||
|
indices := make([]int, len(sets)) |
||||||
|
comb := make([]T, len(sets)) // 复用缓冲区,避免每次分配
|
||||||
|
|
||||||
|
count := 0 |
||||||
|
for { |
||||||
|
// 构建当前组合
|
||||||
|
for i, idx := range indices { |
||||||
|
comb[i] = sets[i][idx] |
||||||
|
} |
||||||
|
|
||||||
|
// 调用回调
|
||||||
|
count++ |
||||||
|
if !callback(comb) { |
||||||
|
break // 停止生成
|
||||||
|
} |
||||||
|
|
||||||
|
// 递增索引,像计数器一样
|
||||||
|
i := len(indices) - 1 |
||||||
|
for i >= 0 { |
||||||
|
indices[i]++ |
||||||
|
if indices[i] < len(sets[i]) { |
||||||
|
break |
||||||
|
} |
||||||
|
indices[i] = 0 |
||||||
|
i-- |
||||||
|
} |
||||||
|
|
||||||
|
if i < 0 { |
||||||
|
break // 所有组合已生成
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return count |
||||||
|
} |
||||||
Loading…
Reference in new issue