63 lines
1.8 KiB
Go
63 lines
1.8 KiB
Go
package strategy
|
||
|
||
import "github.com/shopspring/decimal"
|
||
|
||
// RSI 使用 Wilder 的平均漲跌(非簡單平均),更貼近交易軟體常見計法
|
||
type RSI struct {
|
||
n int
|
||
prevC decimal.Decimal // 前一根收盤價
|
||
initCount int // 初始化用:先累積前 n 根的總漲/總跌
|
||
avgGain decimal.Decimal // 平滑後的平均上漲
|
||
avgLoss decimal.Decimal // 平滑後的平均下跌
|
||
ok bool // 是否有 prevC
|
||
}
|
||
|
||
func NewRSI(n int) *RSI { return &RSI{n: n} }
|
||
|
||
// Push 餵入一根K線,回傳 (RSI值, 是否就緒)
|
||
// 注意:前 n 根會回傳就緒=false;之後才可信
|
||
func (r *RSI) Push(c CandleForStrategy) (decimal.Decimal, bool) {
|
||
if !r.ok {
|
||
r.prevC = c.C
|
||
r.ok = true
|
||
return decimal.Zero, false
|
||
}
|
||
// 價差
|
||
chg := c.C.Sub(r.prevC)
|
||
r.prevC = c.C
|
||
|
||
// 區分上漲與下跌
|
||
gain := decimal.Max(chg, decimal.Zero)
|
||
loss := decimal.Max(chg.Neg(), decimal.Zero)
|
||
|
||
// 初始化階段:先把前 n 根的平均值建好
|
||
if r.initCount < r.n {
|
||
r.avgGain = r.avgGain.Add(gain)
|
||
r.avgLoss = r.avgLoss.Add(loss)
|
||
r.initCount++
|
||
if r.initCount == r.n {
|
||
r.avgGain = r.avgGain.Div(decimal.NewFromInt(int64(r.n)))
|
||
r.avgLoss = r.avgLoss.Div(decimal.NewFromInt(int64(r.n)))
|
||
return r.calc(), true
|
||
}
|
||
return decimal.Zero, false
|
||
}
|
||
|
||
// Wilder 平滑:新的平均 = (舊平均*(n-1) + 當期值) / n
|
||
nDec := decimal.NewFromInt(int64(r.n))
|
||
r.avgGain = (r.avgGain.Mul(nDec.Sub(decimal.NewFromInt(1))).Add(gain)).Div(nDec)
|
||
r.avgLoss = (r.avgLoss.Mul(nDec.Sub(decimal.NewFromInt(1))).Add(loss)).Div(nDec)
|
||
|
||
return r.calc(), true
|
||
}
|
||
|
||
func (r *RSI) calc() decimal.Decimal {
|
||
if r.avgLoss.IsZero() {
|
||
return decimal.NewFromInt(100) // 沒有下跌時,RSI=100
|
||
}
|
||
rs := r.avgGain.Div(r.avgLoss)
|
||
one := decimal.NewFromInt(1)
|
||
hundred := decimal.NewFromInt(100)
|
||
return hundred.Sub(hundred.Div(one.Add(rs)))
|
||
}
|