package strategy import ( "sync/atomic" "github.com/shopspring/decimal" ) // α = 2 / (N+1) 平滑係數 func alphaFromPeriod(n uint) decimal.Decimal { return decimal.NewFromInt(2). Div(decimal.NewFromInt(int64(n)).Add(decimal.NewFromInt(1))) } // ========================= // EMA(單寫;快照多讀) // Seed: SMA 作為初始化 // ========================= type emaCore struct { period uint alpha decimal.Decimal // 狀態(單 writer 更新) value decimal.Decimal ready bool count uint sum decimal.Decimal // warm-up 期間用於 SMA seed } func newEMACore(period uint) *emaCore { if period == 0 { panic("EMA period must be > 0") } return &emaCore{ period: period, alpha: alphaFromPeriod(period), } } // Push 僅供單 writer 呼叫 // 回傳 (當前 EMA, ready) func (e *emaCore) Push(x decimal.Decimal) (decimal.Decimal, bool) { e.count++ // Warm-up:用 SMA 作為 seed if !e.ready { e.sum = e.sum.Add(x) if e.count == e.period { e.value = e.sum.Div(decimal.NewFromInt(int64(e.period))) e.ready = true } return e.value, e.ready } oneMinus := decimal.NewFromInt(1).Sub(e.alpha) e.value = e.value.Mul(oneMinus).Add(x.Mul(e.alpha)) return e.value, true } // ===== 快照發布(多讀零鎖) ===== type EMASnapshot struct { Period uint Alpha string // 十進位字串,方便落盤或序列化 Value decimal.Decimal // 目前 EMA Ready bool LastInput decimal.Decimal // 最近一次輸入值 Count uint // 已處理資料點 } type EMA struct { core *emaCore last decimal.Decimal snap atomic.Value // holds EMASnapshot } func NewEMA(period uint) *EMA { c := newEMACore(period) e := &EMA{core: c} e.snap.Store(EMASnapshot{ Period: period, Alpha: c.alpha.String(), }) return e } // Update 單 writer 呼叫 func (e *EMA) Update(x decimal.Decimal) EMASnapshot { val, ready := e.core.Push(x) e.last = x snap := EMASnapshot{ Period: e.core.period, Alpha: e.core.alpha.String(), Value: val, Ready: ready, LastInput: x, Count: e.core.count, } e.snap.Store(snap) return snap } // Load 多 reader 零鎖讀 func (e *EMA) Load() EMASnapshot { return e.snap.Load().(EMASnapshot) }