blockchain/internal/lib/strategy/ema.go

108 lines
2.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}