package strategy import ( "github.com/markcheno/go-talib" "github.com/shopspring/decimal" "math" "testing" ) func dv(v float64) decimal.Decimal { return decimal.NewFromFloat(v) } func genPrices(n int) []float64 { out := make([]float64, n) base := 10.0 for i := 0; i < n; i++ { out[i] = base + float64(i)*0.5 + math.Sin(float64(i)/3.0)*0.7 } return out } func almostEqualDecFloat(dec decimal.Decimal, f float64, tol float64) bool { df, _ := dec.Float64() return math.Abs(df-f) <= tol } // 注意:使用 NewEMAForTalib(period)(First-Price seed)來比對 TA-Lib func TestEMA_MatchesGoTalib(t *testing.T) { prices := genPrices(300) tol := 1e-7 // decimal<->float64 轉換微誤差 periods := []uint{3, 5, 12, 26} for _, p := range periods { t.Run("EMA_p="+decimal.NewFromInt(int64(p)).String(), func(t *testing.T) { ema := NewEMA(p) // 重點:用 TA-Lib 兼容 seed our := make([]decimal.Decimal, len(prices)) ready := make([]bool, len(prices)) for i, px := range prices { out := ema.Update(dv(px)) our[i] = out.Value ready[i] = out.Ready } ref := talib.Ema(prices, int(p)) start := p // talib 第一個有效值在 index = period-1 for i := start; i < uint(len(prices)); i++ { if !ready[i] { t.Fatalf("i=%d: our not ready but talib has value", i) } if !almostEqualDecFloat(our[i], ref[i], tol) { t.Fatalf("i=%d: EMA mismatch: our=%s ref=%f", i, our[i], ref[i]) } } }) } } func TestSMA_MatchesGoTalib(t *testing.T) { prices := genPrices(300) tol := 1e-9 windows := []uint{3, 5, 20, 50} for _, w := range windows { t.Run("SMA_w="+decimal.NewFromInt(int64(w)).String(), func(t *testing.T) { sma := NewSMA(w) our := make([]decimal.Decimal, len(prices)) ready := make([]bool, len(prices)) for i, px := range prices { out := sma.Update(dv(px)) our[i] = out.Value ready[i] = out.Ready } ref := talib.Sma(prices, int(w)) start := w for i := start; i < uint(len(prices)); i++ { if !ready[i] { t.Fatalf("i=%d: our not ready but talib has value", i) } if !almostEqualDecFloat(our[i], ref[i], tol) { t.Fatalf("i=%d: SMA mismatch: our=%s ref=%f", i, our[i], ref[i]) } } }) } }