137 lines
3.5 KiB
Go
137 lines
3.5 KiB
Go
package strategy
|
||
|
||
import (
|
||
"github.com/shopspring/decimal"
|
||
"math"
|
||
)
|
||
|
||
/************** O(1) 滾動標準差:Welford + ring buffer **************/
|
||
|
||
// 滾動統計(固定窗寬):維持 mean 與 M2(平方離差和)
|
||
// 支援「移除最舊樣本」+「新增最新樣本」的 O(1) 更新
|
||
type rollingWelford struct {
|
||
n int // 窗寬
|
||
buf []decimal.Decimal // 環狀緩衝(固定長度 n)
|
||
head int // 下一個覆寫位置(最舊元素位置)
|
||
size int // 當前已填入數量(<= n)
|
||
mean decimal.Decimal // 目前視窗內的平均值
|
||
m2 decimal.Decimal // 目前視窗內的平方離差和(∑(x-mean)^2)
|
||
ready bool // 是否已填滿 n
|
||
}
|
||
|
||
func newRollingWelford(n int) *rollingWelford {
|
||
if n <= 0 {
|
||
panic("rollingWelford window must be > 0")
|
||
}
|
||
return &rollingWelford{
|
||
n: n,
|
||
buf: make([]decimal.Decimal, n),
|
||
}
|
||
}
|
||
|
||
// add 一個樣本(標準 Welford 加法)
|
||
func (rw *rollingWelford) add(x decimal.Decimal) {
|
||
// n_old = size, n_new = size+1
|
||
nOld := decimal.NewFromInt(int64(rw.size))
|
||
nNew := nOld.Add(decimal.NewFromInt(1))
|
||
|
||
delta := x.Sub(rw.mean)
|
||
meanNew := rw.mean.Add(delta.Div(nNew))
|
||
delta2 := x.Sub(meanNew)
|
||
rw.m2 = rw.m2.Add(delta.Mul(delta2))
|
||
rw.mean = meanNew
|
||
|
||
if rw.size < rw.n {
|
||
rw.size++
|
||
if rw.size == rw.n {
|
||
rw.ready = true
|
||
}
|
||
}
|
||
}
|
||
|
||
// remove 一個樣本(Welford 反向移除公式)
|
||
func (rw *rollingWelford) remove(x decimal.Decimal) {
|
||
if rw.size <= 0 {
|
||
return
|
||
}
|
||
// n_old = size, n_new = size-1
|
||
nOld := decimal.NewFromInt(int64(rw.size))
|
||
nNew := nOld.Sub(decimal.NewFromInt(1))
|
||
if nNew.LessThanOrEqual(decimal.Zero) {
|
||
// 視窗將清空
|
||
rw.size = 0
|
||
rw.mean = decimal.Zero
|
||
rw.m2 = decimal.Zero
|
||
rw.ready = false
|
||
return
|
||
}
|
||
delta := x.Sub(rw.mean) // (x - mean_old)
|
||
meanNew := rw.mean.Sub(delta.Div(nNew)) // mean_new = mean_old - delta / n_new
|
||
// M2_new = M2_old - delta*(x - mean_new)
|
||
rw.m2 = rw.m2.Sub(delta.Mul(x.Sub(meanNew)))
|
||
rw.mean = meanNew
|
||
rw.size--
|
||
rw.ready = rw.size == rw.n // 正常來說 size==n-1,移除後會在 add 後再回到 n
|
||
}
|
||
|
||
// push:O(1) 更新(移除最舊 + 新增最新)
|
||
func (rw *rollingWelford) push(x decimal.Decimal) {
|
||
if rw.size == rw.n {
|
||
old := rw.buf[rw.head]
|
||
rw.remove(old)
|
||
}
|
||
rw.buf[rw.head] = x
|
||
rw.head = (rw.head + 1) % rw.n
|
||
rw.add(x)
|
||
}
|
||
|
||
// mid() = 平均;std() = sqrt(variance)
|
||
// 這裡採「母體方差」分母 = n(與很多圖表預設一致;如需樣本用 n-1)
|
||
func (rw *rollingWelford) mid() decimal.Decimal { return rw.mean }
|
||
|
||
func (rw *rollingWelford) std() decimal.Decimal {
|
||
if !rw.ready || rw.size == 0 {
|
||
return decimal.Zero
|
||
}
|
||
nDec := decimal.NewFromInt(int64(rw.n))
|
||
variance := rw.m2.Div(nDec)
|
||
f, _ := variance.Float64()
|
||
return decimal.NewFromFloat(math.Sqrt(f))
|
||
}
|
||
|
||
/************** Boll(Welford 版本) **************/
|
||
|
||
type BollW struct {
|
||
rw *rollingWelford
|
||
}
|
||
|
||
type BollWOut struct {
|
||
Mid, Upper, Lower, Std decimal.Decimal
|
||
Ready bool
|
||
}
|
||
|
||
func NewBollW(n int) *BollW {
|
||
return &BollW{rw: newRollingWelford(n)}
|
||
}
|
||
|
||
// Push 輸入收盤價 close 與 k 倍標準差(典型 2.0)
|
||
// 回傳:上下軌、均線、標準差、是否就緒(前 n 根為 false)
|
||
func (b *BollW) Push(close, k decimal.Decimal) BollWOut {
|
||
b.rw.push(close)
|
||
if !b.rw.ready {
|
||
return BollWOut{Ready: false}
|
||
}
|
||
mid := b.rw.mid()
|
||
std := b.rw.std()
|
||
|
||
upper := mid.Add(k.Mul(std))
|
||
lower := mid.Sub(k.Mul(std))
|
||
return BollWOut{
|
||
Mid: mid,
|
||
Upper: upper,
|
||
Lower: lower,
|
||
Std: std,
|
||
Ready: true,
|
||
}
|
||
}
|