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