2025-08-14 23:41:29 +00:00
|
|
|
|
package strategy
|
|
|
|
|
|
2025-08-17 14:48:13 +00:00
|
|
|
|
import (
|
|
|
|
|
"sync/atomic"
|
2025-08-14 23:41:29 +00:00
|
|
|
|
|
2025-08-17 14:48:13 +00:00
|
|
|
|
"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),
|
|
|
|
|
}
|
2025-08-14 23:41:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-17 14:48:13 +00:00
|
|
|
|
// 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
|
|
|
|
|
}
|
2025-08-14 23:41:29 +00:00
|
|
|
|
|
2025-08-17 14:48:13 +00:00
|
|
|
|
return s.sum.Div(decimal.NewFromInt(int64(s.size))), s.ready
|
2025-08-14 23:41:29 +00:00
|
|
|
|
}
|
2025-08-17 14:48:13 +00:00
|
|
|
|
|
|
|
|
|
// 滿窗:移除最舊,加入最新
|
|
|
|
|
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
|
2025-08-14 23:41:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-17 14:48:13 +00:00
|
|
|
|
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,
|
2025-08-14 23:41:29 +00:00
|
|
|
|
}
|
2025-08-17 14:48:13 +00:00
|
|
|
|
s.snap.Store(ss)
|
|
|
|
|
return ss
|
|
|
|
|
}
|
2025-08-15 01:36:36 +00:00
|
|
|
|
|
2025-08-17 14:48:13 +00:00
|
|
|
|
// Load 多 reader 零鎖讀最新快照
|
|
|
|
|
func (s *SMA) Load() SMASnapshot {
|
|
|
|
|
return s.snap.Load().(SMASnapshot)
|
2025-08-14 23:41:29 +00:00
|
|
|
|
}
|