You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

77 lines
2.1 KiB

package backtest
import (
"math"
"testing"
"time"
)
func approxEqual(a, b, eps float64) bool {
return math.Abs(a-b) <= eps
}
func TestSharpeFromEquitySnapshots(t *testing.T) {
t.Run("insufficient snapshots", func(t *testing.T) {
var snaps []*EquitySnapshot
if v := sharpeFromEquitySnapshots(snaps, 0.01); v != 0 {
t.Fatalf("expected 0 for empty snapshots, got %v", v)
}
snaps = append(snaps, &EquitySnapshot{Ts: time.Now().UnixMilli(), Equity: 100})
if v := sharpeFromEquitySnapshots(snaps, 0.01); v != 0 {
t.Fatalf("expected 0 for single snapshot, got %v", v)
}
})
t.Run("zero volatility returns", func(t *testing.T) {
// two identical returns -> sd == 0 -> expect 0
now := time.Now().UnixMilli()
snaps := []*EquitySnapshot{
{Ts: now, Equity: 100},
{Ts: now + 86400*1000, Equity: 102}, // +2%
{Ts: now + 2*86400*1000, Equity: 104.04}, // +2%
}
if v := sharpeFromEquitySnapshots(snaps, 0.01); v != 0 {
t.Fatalf("expected 0 for zero-volatility returns, got %v", v)
}
})
t.Run("known case matches manual calc", func(t *testing.T) {
// create three snapshots with returns r1=1%, r2=-0.5%, r3=2%
now := time.Now().UnixMilli()
snaps := []*EquitySnapshot{
{Ts: now, Equity: 100.0},
{Ts: now + 86400*1000, Equity: 101.0}, // +1%
{Ts: now + 2*86400*1000, Equity: 100.495}, // -0.5% from 101
{Ts: now + 3*86400*1000, Equity: 102.5049}, // +2% from prev
}
got := sharpeFromEquitySnapshots(snaps, 0.01)
// manual compute
const secsYear = 365.0 * 24.0 * 3600.0
returns := []float64{0.01, -0.005, 0.02}
dt := 86400.0
periodsPerYear := secsYear / dt
rfPeriod := 0.01 / periodsPerYear
excess := make([]float64, len(returns))
for i := range returns {
excess[i] = returns[i] - rfPeriod
}
mean := 0.0
for _, v := range excess {
mean += v
}
mean /= float64(len(excess))
s := 0.0
for _, v := range excess {
d := v - mean
s += d * d
}
sd := math.Sqrt(s / float64(len(excess)-1))
want := mean / sd * math.Sqrt(periodsPerYear)
if !approxEqual(got, want, 1e-9) {
t.Fatalf("sharpe mismatch got=%v want=%v", got, want)
}
})
}