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