104 lines
2.2 KiB
Go
104 lines
2.2 KiB
Go
package strategy
|
||
|
||
import (
|
||
"sync/atomic"
|
||
|
||
"github.com/shopspring/decimal"
|
||
)
|
||
|
||
// =========================
|
||
// SMA(固定窗寬;單寫多讀 + atomic 快照)
|
||
// =========================
|
||
|
||
// smaCore:單 writer 的核心計算(ring buffer + 滑動平均)
|
||
type smaCore struct {
|
||
window uint
|
||
|
||
// 狀態(單 writer)
|
||
buf []decimal.Decimal // ring buffer
|
||
head int // 下一個覆蓋位置
|
||
size uint // 目前填充數量(<= window)
|
||
sum decimal.Decimal
|
||
ready bool
|
||
}
|
||
|
||
func newSMACore(window uint) *smaCore {
|
||
if window == 0 {
|
||
panic("SMA window must be > 0")
|
||
}
|
||
|
||
return &smaCore{
|
||
window: window,
|
||
buf: make([]decimal.Decimal, window),
|
||
}
|
||
}
|
||
|
||
// Push 只能被**單一** goroutine(writer)呼叫。
|
||
func (s *smaCore) Push(x decimal.Decimal) (decimal.Decimal, bool) {
|
||
// 填充期
|
||
if s.size < s.window {
|
||
s.buf[s.head] = x
|
||
s.sum = s.sum.Add(x)
|
||
s.head = (s.head + 1) % int(s.window)
|
||
s.size++
|
||
if s.size == s.window {
|
||
s.ready = true
|
||
}
|
||
|
||
return s.sum.Div(decimal.NewFromInt(int64(s.size))), s.ready
|
||
}
|
||
|
||
// 滿窗:移除最舊,加入最新
|
||
old := s.buf[s.head]
|
||
s.buf[s.head] = x
|
||
s.head = (s.head + 1) % int(s.window)
|
||
|
||
s.sum = s.sum.Sub(old).Add(x)
|
||
avg := s.sum.Div(decimal.NewFromInt(int64(s.window)))
|
||
|
||
return avg, true
|
||
}
|
||
|
||
// SMASnapshot 對外發佈的不可變快照(多 reader 零鎖 Load)
|
||
type SMASnapshot struct {
|
||
Window uint
|
||
Value decimal.Decimal
|
||
Ready bool
|
||
LastInput decimal.Decimal
|
||
Size uint // 當前填充數量(<=Window)
|
||
}
|
||
|
||
// SMA 對外使用的快照發佈器(單寫多讀)
|
||
type SMA struct {
|
||
core *smaCore
|
||
|
||
last decimal.Decimal
|
||
snap atomic.Value // holds SMASnapshot
|
||
}
|
||
|
||
func NewSMA(window uint) *SMA {
|
||
s := &SMA{core: newSMACore(window)}
|
||
s.snap.Store(SMASnapshot{Window: window})
|
||
return s
|
||
}
|
||
|
||
// Update 僅允許**單一 writer**呼叫
|
||
func (s *SMA) Update(x decimal.Decimal) SMASnapshot {
|
||
val, ready := s.core.Push(x)
|
||
s.last = x
|
||
ss := SMASnapshot{
|
||
Window: s.core.window,
|
||
Value: val,
|
||
Ready: ready,
|
||
LastInput: x,
|
||
Size: s.core.size,
|
||
}
|
||
s.snap.Store(ss)
|
||
return ss
|
||
}
|
||
|
||
// Load 多 reader 零鎖讀最新快照
|
||
func (s *SMA) Load() SMASnapshot {
|
||
return s.snap.Load().(SMASnapshot)
|
||
}
|