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