blockchain/internal/lib/strategy/ema.go

109 lines
2.2 KiB
Go
Raw Normal View History

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-20 12:35:31 +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
}