blockchain/internal/lib/strategy/bool.go

137 lines
3.5 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 (
"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
}
// pushO(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))
}
/************** BollWelford 版本) **************/
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,
}
}