2025-08-14 23:41:29 +00:00
|
|
|
|
package strategy
|
|
|
|
|
|
2025-08-17 14:48:13 +00:00
|
|
|
|
import (
|
|
|
|
|
"sync/atomic"
|
2025-08-15 01:36:36 +00:00
|
|
|
|
|
2025-08-17 14:48:13 +00:00
|
|
|
|
"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)))
|
2025-08-14 23:41:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-17 14:48:13 +00:00
|
|
|
|
// =========================
|
|
|
|
|
// 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
|
2025-08-14 23:41:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-17 14:48:13 +00:00
|
|
|
|
func newEMACore(period uint) *emaCore {
|
|
|
|
|
if period == 0 {
|
|
|
|
|
panic("EMA period must be > 0")
|
2025-08-15 01:36:36 +00:00
|
|
|
|
}
|
2025-08-17 14:48:13 +00:00
|
|
|
|
return &emaCore{
|
|
|
|
|
period: period,
|
|
|
|
|
alpha: alphaFromPeriod(period),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Push 僅供單 writer 呼叫
|
|
|
|
|
// 回傳 (當前 EMA, ready)
|
|
|
|
|
func (e *emaCore) Push(x decimal.Decimal) (decimal.Decimal, bool) {
|
|
|
|
|
e.count++
|
2025-08-15 01:36:36 +00:00
|
|
|
|
|
2025-08-17 14:48:13 +00:00
|
|
|
|
// 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
|
2025-08-15 01:36:36 +00:00
|
|
|
|
}
|
2025-08-17 14:48:13 +00:00
|
|
|
|
|
|
|
|
|
oneMinus := decimal.NewFromInt(1).Sub(e.alpha)
|
|
|
|
|
e.value = e.value.Mul(oneMinus).Add(x.Mul(e.alpha))
|
|
|
|
|
return e.value, true
|
2025-08-15 01:36:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-17 14:48:13 +00:00
|
|
|
|
// ===== 快照發布(多讀零鎖) =====
|
|
|
|
|
|
|
|
|
|
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,
|
2025-08-14 23:41:29 +00:00
|
|
|
|
}
|
2025-08-17 14:48:13 +00:00
|
|
|
|
e.snap.Store(snap)
|
|
|
|
|
return snap
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load 多 reader 零鎖讀
|
|
|
|
|
func (e *EMA) Load() EMASnapshot {
|
|
|
|
|
return e.snap.Load().(EMASnapshot)
|
2025-08-14 23:41:29 +00:00
|
|
|
|
}
|