89 lines
2.2 KiB
Go
89 lines
2.2 KiB
Go
|
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])
|
|||
|
}
|
|||
|
}
|
|||
|
})
|
|||
|
}
|
|||
|
}
|