Merge pull request #731 from zenixls2/feature/stats

add tearsheet backend api (Sharpe)
This commit is contained in:
Zenix 2022-06-30 09:00:23 +09:00 committed by GitHub
commit c9e3e5a7cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 612 additions and 81 deletions

View File

@ -14,6 +14,7 @@ Accumulation/Distribution Indicator (A/D)
*/
//go:generate callbackgen -type AD
type AD struct {
types.SeriesBase
types.IntervalWindow
Values types.Float64Slice
PrePrice float64
@ -23,6 +24,9 @@ type AD struct {
}
func (inc *AD) Update(high, low, cloze, volume float64) {
if len(inc.Values) == 0 {
inc.SeriesBase.Series = inc
}
var moneyFlowVolume float64
if high == low {
moneyFlowVolume = 0
@ -53,7 +57,7 @@ func (inc *AD) Length() int {
return len(inc.Values)
}
var _ types.Series = &AD{}
var _ types.SeriesExtend = &AD{}
func (inc *AD) calculateAndUpdate(kLines []types.KLine) {
for _, k := range kLines {

View File

@ -13,11 +13,12 @@ import (
// @param sigma: the standard deviation applied to the combo line. This makes the combo line sharper
//go:generate callbackgen -type ALMA
type ALMA struct {
types.SeriesBase
types.IntervalWindow // required
Offset float64 // required: recommend to be 5
Sigma int // required: recommend to be 0.5
Weight []float64
Sum float64
weight []float64
sum float64
input []float64
Values types.Float64Slice
UpdateCallbacks []func(value float64)
@ -27,16 +28,17 @@ const MaxNumOfALMA = 5_000
const MaxNumOfALMATruncateSize = 100
func (inc *ALMA) Update(value float64) {
if inc.Weight == nil {
inc.Weight = make([]float64, inc.Window)
if inc.weight == nil {
inc.SeriesBase.Series = inc
inc.weight = make([]float64, inc.Window)
m := inc.Offset * (float64(inc.Window) - 1.)
s := float64(inc.Window) / float64(inc.Sigma)
inc.Sum = 0.
inc.sum = 0.
for i := 0; i < inc.Window; i++ {
diff := float64(i) - m
wt := math.Exp(-diff * diff / 2. / s / s)
inc.Sum += wt
inc.Weight[i] = wt
inc.sum += wt
inc.weight[i] = wt
}
}
inc.input = append(inc.input, value)
@ -44,9 +46,9 @@ func (inc *ALMA) Update(value float64) {
weightedSum := 0.0
inc.input = inc.input[len(inc.input)-inc.Window:]
for i := 0; i < inc.Window; i++ {
weightedSum += inc.Weight[inc.Window-i-1] * inc.input[i]
weightedSum += inc.weight[inc.Window-i-1] * inc.input[i]
}
inc.Values.Push(weightedSum / inc.Sum)
inc.Values.Push(weightedSum / inc.sum)
if len(inc.Values) > MaxNumOfALMA {
inc.Values = inc.Values[MaxNumOfALMATruncateSize-1:]
}
@ -71,6 +73,8 @@ func (inc *ALMA) Length() int {
return len(inc.Values)
}
var _ types.SeriesExtend = &ALMA{}
func (inc *ALMA) calculateAndUpdate(allKLines []types.KLine) {
if inc.input == nil {
for _, k := range allKLines {

View File

@ -9,6 +9,7 @@ import (
//go:generate callbackgen -type ATR
type ATR struct {
types.SeriesBase
types.IntervalWindow
PercentageVolatility types.Float64Slice
@ -25,6 +26,7 @@ func (inc *ATR) Update(high, low, cloze float64) {
}
if inc.RMA == nil {
inc.SeriesBase.Series = inc
inc.RMA = &RMA{
IntervalWindow: types.IntervalWindow{Window: inc.Window},
Adjust: true,
@ -73,7 +75,7 @@ func (inc *ATR) Length() int {
return inc.RMA.Length()
}
var _ types.Series = &ATR{}
var _ types.SeriesExtend = &ATR{}
func (inc *ATR) CalculateAndUpdate(kLines []types.KLine) {
for _, k := range kLines {

View File

@ -41,20 +41,20 @@ type BOLL struct {
type BandType int
func (inc *BOLL) GetUpBand() types.Series {
return &inc.UpBand
func (inc *BOLL) GetUpBand() types.SeriesExtend {
return types.NewSeries(&inc.UpBand)
}
func (inc *BOLL) GetDownBand() types.Series {
return &inc.DownBand
func (inc *BOLL) GetDownBand() types.SeriesExtend {
return types.NewSeries(&inc.DownBand)
}
func (inc *BOLL) GetSMA() types.Series {
return &inc.SMA
func (inc *BOLL) GetSMA() types.SeriesExtend {
return types.NewSeries(&inc.SMA)
}
func (inc *BOLL) GetStdDev() types.Series {
return &inc.StdDev
func (inc *BOLL) GetStdDev() types.SeriesExtend {
return types.NewSeries(&inc.StdDev)
}
func (inc *BOLL) LastUpBand() float64 {

View File

@ -12,6 +12,7 @@ import (
// with modification of ddof=0 to let standard deviation to be divided by N instead of N-1
//go:generate callbackgen -type CCI
type CCI struct {
types.SeriesBase
types.IntervalWindow
Input types.Float64Slice
TypicalPrice types.Float64Slice
@ -23,6 +24,7 @@ type CCI struct {
func (inc *CCI) Update(value float64) {
if len(inc.TypicalPrice) == 0 {
inc.SeriesBase.Series = inc
inc.TypicalPrice.Push(value)
inc.Input.Push(value)
return
@ -75,7 +77,7 @@ func (inc *CCI) Length() int {
return len(inc.Values)
}
var _ types.Series = &CCI{}
var _ types.SeriesExtend = &CCI{}
var three = fixedpoint.NewFromInt(3)

View File

@ -8,6 +8,7 @@ import (
// Refer: https://en.wikipedia.org/wiki/Moving_average
//go:generate callbackgen -type CA
type CA struct {
types.SeriesBase
Interval types.Interval
Values types.Float64Slice
length float64
@ -15,11 +16,15 @@ type CA struct {
}
func (inc *CA) Update(x float64) {
if len(inc.Values) == 0 {
inc.SeriesBase.Series = inc
}
newVal := (inc.Values.Last()*inc.length + x) / (inc.length + 1.)
inc.length += 1
inc.Values.Push(newVal)
if len(inc.Values) > MaxNumOfEWMA {
inc.Values = inc.Values[MaxNumOfEWMATruncateSize-1:]
inc.length = float64(len(inc.Values))
}
}
@ -41,7 +46,7 @@ func (inc *CA) Length() int {
return len(inc.Values)
}
var _ types.Series = &CA{}
var _ types.SeriesExtend = &CA{}
func (inc *CA) calculateAndUpdate(allKLines []types.KLine) {
for _, k := range allKLines {

View File

@ -10,6 +10,7 @@ import (
//go:generate callbackgen -type DEMA
type DEMA struct {
types.IntervalWindow
types.SeriesBase
Values types.Float64Slice
a1 *EWMA
a2 *EWMA
@ -19,6 +20,7 @@ type DEMA struct {
func (inc *DEMA) Update(value float64) {
if len(inc.Values) == 0 {
inc.SeriesBase.Series = inc
inc.a1 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}}
inc.a2 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}}
}
@ -46,7 +48,7 @@ func (inc *DEMA) Length() int {
return len(inc.Values)
}
var _ types.Series = &DEMA{}
var _ types.SeriesExtend = &DEMA{}
func (inc *DEMA) calculateAndUpdate(allKLines []types.KLine) {
if inc.a1 == nil {

View File

@ -17,11 +17,11 @@ type DMI struct {
types.IntervalWindow
ADXSmoothing int
atr *ATR
DMP types.UpdatableSeries
DMN types.UpdatableSeries
DMP types.UpdatableSeriesExtend
DMN types.UpdatableSeriesExtend
DIPlus *types.Queue
DIMinus *types.Queue
ADX types.UpdatableSeries
ADX types.UpdatableSeriesExtend
PrevHigh, PrevLow float64
UpdateCallbacks []func(diplus, diminus, adx float64)
}
@ -71,15 +71,15 @@ func (inc *DMI) Update(high, low, cloze float64) {
}
func (inc *DMI) GetDIPlus() types.Series {
func (inc *DMI) GetDIPlus() types.SeriesExtend {
return inc.DIPlus
}
func (inc *DMI) GetDIMinus() types.Series {
func (inc *DMI) GetDIMinus() types.SeriesExtend {
return inc.DIMinus
}
func (inc *DMI) GetADX() types.Series {
func (inc *DMI) GetADX() types.SeriesExtend {
return inc.ADX
}

View File

@ -11,6 +11,7 @@ import (
// could be used in Monte Carlo Simulations
//go:generate callbackgen -type Drift
type Drift struct {
types.SeriesBase
types.IntervalWindow
chng *types.Queue
Values types.Float64Slice
@ -22,6 +23,7 @@ type Drift struct {
func (inc *Drift) Update(value float64) {
if inc.chng == nil {
inc.SeriesBase.Series = inc
inc.SMA = &SMA{IntervalWindow: types.IntervalWindow{Interval: inc.Interval, Window: inc.Window}}
inc.chng = types.NewQueue(inc.Window)
inc.LastValue = value
@ -64,7 +66,7 @@ func (inc *Drift) Length() int {
return inc.Values.Length()
}
var _ types.Series = &Drift{}
var _ types.SeriesExtend = &Drift{}
func (inc *Drift) calculateAndUpdate(allKLines []types.KLine) {
if inc.chng == nil {

View File

@ -9,6 +9,7 @@ import (
//go:generate callbackgen -type EMV
type EMV struct {
types.SeriesBase
types.IntervalWindow
prevH float64
prevL float64
@ -25,6 +26,7 @@ func (inc *EMV) Update(high, low, vol float64) {
inc.EMVScale = DefaultEMVScale
}
if inc.prevH == 0 || inc.Values == nil {
inc.SeriesBase.Series = inc
inc.prevH = high
inc.prevL = low
inc.Values = &SMA{IntervalWindow: inc.IntervalWindow}
@ -59,7 +61,7 @@ func (inc *EMV) Length() int {
return inc.Values.Length()
}
var _ types.Series = &EMV{}
var _ types.SeriesExtend = &EMV{}
func (inc *EMV) calculateAndUpdate(allKLines []types.KLine) {
if inc.Values == nil {

View File

@ -16,6 +16,7 @@ const MaxNumOfEWMATruncateSize = 100
//go:generate callbackgen -type EWMA
type EWMA struct {
types.IntervalWindow
types.SeriesBase
Values types.Float64Slice
LastOpenTime time.Time
@ -26,6 +27,7 @@ func (inc *EWMA) Update(value float64) {
var multiplier = 2.0 / float64(1+inc.Window)
if len(inc.Values) == 0 {
inc.SeriesBase.Series = inc
inc.Values.Push(value)
return
} else if len(inc.Values) > MaxNumOfEWMA {
@ -136,4 +138,4 @@ func (inc *EWMA) Bind(updater KLineWindowUpdater) {
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
}
var _ types.Series = &EWMA{}
var _ types.SeriesExtend = &EWMA{}

View File

@ -10,6 +10,7 @@ import (
// Refer URL: https://fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/hull-moving-average
//go:generate callbackgen -type HULL
type HULL struct {
types.SeriesBase
types.IntervalWindow
ma1 *EWMA
ma2 *EWMA
@ -20,6 +21,7 @@ type HULL struct {
func (inc *HULL) Update(value float64) {
if inc.result == nil {
inc.SeriesBase.Series = inc
inc.ma1 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window / 2}}
inc.ma2 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}}
inc.result = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, int(math.Sqrt(float64(inc.Window)))}}
@ -50,7 +52,7 @@ func (inc *HULL) Length() int {
return inc.result.Length()
}
var _ types.Series = &HULL{}
var _ types.SeriesExtend = &HULL{}
// TODO: should we just ignore the possible overlapping?
func (inc *HULL) calculateAndUpdate(allKLines []types.KLine) {

View File

@ -12,6 +12,7 @@ import (
// 3. resistance
// of the market data, defined with series interface
type Line struct {
types.SeriesBase
types.IntervalWindow
start float64
end float64
@ -63,7 +64,7 @@ func (l *Line) SetXY2(index int, value float64) {
}
func NewLine(startIndex int, startValue float64, endIndex int, endValue float64, interval types.Interval) *Line {
return &Line{
line := &Line{
start: startValue,
end: endValue,
startIndex: startIndex,
@ -71,6 +72,8 @@ func NewLine(startIndex int, startValue float64, endIndex int, endValue float64,
currentTime: time.Time{},
Interval: interval,
}
line.SeriesBase.Series = line
return line
}
var _ types.Series = &Line{}
var _ types.SeriesExtend = &Line{}

View File

@ -87,6 +87,7 @@ func (inc *MACD) Bind(updater KLineWindowUpdater) {
}
type MACDValues struct {
types.SeriesBase
*MACD
}
@ -109,10 +110,12 @@ func (inc *MACDValues) Length() int {
return len(inc.Values)
}
func (inc *MACD) MACD() types.Series {
return &MACDValues{inc}
func (inc *MACD) MACD() types.SeriesExtend {
out := &MACDValues{MACD: inc}
out.SeriesBase.Series = out
return out
}
func (inc *MACD) Singals() types.Series {
func (inc *MACD) Singals() types.SeriesExtend {
return &inc.SignalLine
}

View File

@ -14,6 +14,7 @@ On-Balance Volume (OBV) Definition
*/
//go:generate callbackgen -type OBV
type OBV struct {
types.SeriesBase
types.IntervalWindow
Values types.Float64Slice
PrePrice float64
@ -24,6 +25,7 @@ type OBV struct {
func (inc *OBV) Update(price, volume float64) {
if len(inc.Values) == 0 {
inc.SeriesBase.Series = inc
inc.PrePrice = price
inc.Values.Push(volume)
return
@ -43,6 +45,15 @@ func (inc *OBV) Last() float64 {
return inc.Values[len(inc.Values)-1]
}
func (inc *OBV) Index(i int) float64 {
if len(inc.Values)-i <= 0 {
return 0.0
}
return inc.Values[len(inc.Values)-i-1]
}
var _ types.SeriesExtend = &OBV{}
func (inc *OBV) calculateAndUpdate(kLines []types.KLine) {
for _, k := range kLines {
if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) {

View File

@ -11,6 +11,7 @@ import (
// Refer: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.ewm.html#pandas-dataframe-ewm
//go:generate callbackgen -type RMA
type RMA struct {
types.SeriesBase
types.IntervalWindow
Values types.Float64Slice
counter int
@ -24,6 +25,7 @@ type RMA struct {
func (inc *RMA) Update(x float64) {
lambda := 1 / float64(inc.Window)
if inc.counter == 0 {
inc.SeriesBase.Series = inc
inc.sum = 1
inc.tmp = x
} else {
@ -60,7 +62,7 @@ func (inc *RMA) Length() int {
return len(inc.Values)
}
var _ types.Series = &RMA{}
var _ types.SeriesExtend = &RMA{}
func (inc *RMA) calculateAndUpdate(kLines []types.KLine) {
for _, k := range kLines {

View File

@ -14,6 +14,7 @@ https://www.investopedia.com/terms/r/rsi.asp
*/
//go:generate callbackgen -type RSI
type RSI struct {
types.SeriesBase
types.IntervalWindow
Values types.Float64Slice
Prices types.Float64Slice
@ -25,6 +26,9 @@ type RSI struct {
}
func (inc *RSI) Update(price float64) {
if len(inc.Prices) == 0 {
inc.SeriesBase.Series = inc
}
inc.Prices.Push(price)
if len(inc.Prices) < inc.Window+1 {
@ -74,7 +78,7 @@ func (inc *RSI) Length() int {
return len(inc.Values)
}
var _ types.Series = &RSI{}
var _ types.SeriesExtend = &RSI{}
func (inc *RSI) calculateAndUpdate(kLines []types.KLine) {
for _, k := range kLines {

View File

@ -16,6 +16,7 @@ var zeroTime time.Time
//go:generate callbackgen -type SMA
type SMA struct {
types.SeriesBase
types.IntervalWindow
Values types.Float64Slice
Cache types.Float64Slice
@ -44,10 +45,13 @@ func (inc *SMA) Length() int {
return len(inc.Values)
}
var _ types.Series = &SMA{}
var _ types.SeriesExtend = &SMA{}
func (inc *SMA) Update(value float64) {
if len(inc.Cache) < inc.Window {
if len(inc.Cache) == 0 {
inc.SeriesBase.Series = inc
}
inc.Cache = append(inc.Cache, value)
if len(inc.Cache) == inc.Window {
inc.Values = append(inc.Values, types.Mean(&inc.Cache))

View File

@ -20,6 +20,7 @@ import (
//
//go:generate callbackgen -type SSF
type SSF struct {
types.SeriesBase
types.IntervalWindow
Poles int
c1 float64
@ -34,6 +35,7 @@ type SSF struct {
func (inc *SSF) Update(value float64) {
if inc.Poles == 3 {
if inc.Values == nil {
inc.SeriesBase.Series = inc
x := math.Pi / float64(inc.Window)
a0 := math.Exp(-x)
b0 := 2. * a0 * math.Cos(math.Sqrt(3.)*x)
@ -53,6 +55,7 @@ func (inc *SSF) Update(value float64) {
inc.Values.Push(result)
} else { // poles == 2
if inc.Values == nil {
inc.SeriesBase.Series = inc
x := math.Pi * math.Sqrt(2.) / float64(inc.Window)
a0 := math.Exp(-x)
inc.c3 = -a0 * a0
@ -88,7 +91,7 @@ func (inc *SSF) Last() float64 {
return inc.Values.Last()
}
var _ types.Series = &SSF{}
var _ types.SeriesExtend = &SSF{}
func (inc *SSF) calculateAndUpdate(allKLines []types.KLine) {
if inc.Values != nil {

View File

@ -12,6 +12,7 @@ var logst = logrus.WithField("indicator", "supertrend")
//go:generate callbackgen -type Supertrend
type Supertrend struct {
types.SeriesBase
types.IntervalWindow
ATRMultiplier float64 `json:"atrMultiplier"`
@ -54,6 +55,10 @@ func (inc *Supertrend) Update(highPrice, lowPrice, closePrice float64) {
panic("window must be greater than 0")
}
if inc.AverageTrueRange == nil {
inc.SeriesBase.Series = inc
}
// Start with DirectionUp
if inc.trend != types.DirectionUp && inc.trend != types.DirectionDown {
inc.trend = types.DirectionUp
@ -120,7 +125,7 @@ func (inc *Supertrend) GetSignal() types.Direction {
return inc.tradeSignal
}
var _ types.Series = &Supertrend{}
var _ types.SeriesExtend = &Supertrend{}
func (inc *Supertrend) calculateAndUpdate(kLines []types.KLine) {
for _, k := range kLines {

View File

@ -9,6 +9,7 @@ import (
//go:generate callbackgen -type TEMA
type TEMA struct {
types.SeriesBase
types.IntervalWindow
Values types.Float64Slice
A1 *EWMA
@ -20,6 +21,7 @@ type TEMA struct {
func (inc *TEMA) Update(value float64) {
if len(inc.Values) == 0 {
inc.SeriesBase.Series = inc
inc.A1 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}}
inc.A2 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}}
inc.A3 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}}
@ -51,7 +53,7 @@ func (inc *TEMA) Length() int {
return len(inc.Values)
}
var _ types.Series = &TEMA{}
var _ types.SeriesExtend = &TEMA{}
func (inc *TEMA) calculateAndUpdate(allKLines []types.KLine) {
if inc.A1 == nil {

View File

@ -10,6 +10,7 @@ const defaultVolumeFactor = 0.7
// Refer URL: https://tradingpedia.com/forex-trading-indicator/t3-moving-average-indicator/
//go:generate callbackgen -type TILL
type TILL struct {
types.SeriesBase
types.IntervalWindow
VolumeFactor float64
e1 *EWMA
@ -30,6 +31,7 @@ func (inc *TILL) Update(value float64) {
if inc.VolumeFactor == 0 {
inc.VolumeFactor = defaultVolumeFactor
}
inc.SeriesBase.Series = inc
inc.e1 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}}
inc.e2 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}}
inc.e3 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}}

View File

@ -8,6 +8,7 @@ import (
// Refer URL: https://ja.wikipedia.org/wiki/移動平均
//go:generate callbackgen -type TMA
type TMA struct {
types.SeriesBase
types.IntervalWindow
s1 *SMA
s2 *SMA
@ -16,6 +17,7 @@ type TMA struct {
func (inc *TMA) Update(value float64) {
if inc.s1 == nil {
inc.SeriesBase.Series = inc
w := (inc.Window + 1) / 2
inc.s1 = &SMA{IntervalWindow: types.IntervalWindow{inc.Interval, w}}
inc.s2 = &SMA{IntervalWindow: types.IntervalWindow{inc.Interval, w}}
@ -46,7 +48,7 @@ func (inc *TMA) Length() int {
return inc.s2.Length()
}
var _ types.Series = &TMA{}
var _ types.SeriesExtend = &TMA{}
func (inc *TMA) calculateAndUpdate(allKLines []types.KLine) {
if inc.s1 == nil {

View File

@ -10,6 +10,7 @@ import (
// Refer URL: https://metatrader5.com/en/terminal/help/indicators/trend_indicators/vida
//go:generate callbackgen -type VIDYA
type VIDYA struct {
types.SeriesBase
types.IntervalWindow
Values types.Float64Slice
input types.Float64Slice
@ -19,6 +20,7 @@ type VIDYA struct {
func (inc *VIDYA) Update(value float64) {
if inc.Values.Length() == 0 {
inc.SeriesBase.Series = inc
inc.Values.Push(value)
inc.input.Push(value)
return
@ -66,7 +68,7 @@ func (inc *VIDYA) Length() int {
return inc.Values.Length()
}
var _ types.Series = &VIDYA{}
var _ types.SeriesExtend = &VIDYA{}
func (inc *VIDYA) calculateAndUpdate(allKLines []types.KLine) {
if inc.input.Length() == 0 {

View File

@ -17,6 +17,7 @@ const MaxNumOfVOLTruncateSize = 100
//go:generate callbackgen -type VOLATILITY
type VOLATILITY struct {
types.SeriesBase
types.IntervalWindow
Values types.Float64Slice
EndTime time.Time
@ -31,6 +32,19 @@ func (inc *VOLATILITY) Last() float64 {
return inc.Values[len(inc.Values)-1]
}
func (inc *VOLATILITY) Index(i int) float64 {
if len(inc.Values)-i <= 0 {
return 0.0
}
return inc.Values[len(inc.Values)-i-1]
}
func (inc *VOLATILITY) Length() int {
return len(inc.Values)
}
var _ types.SeriesExtend = &VOLATILITY{}
func (inc *VOLATILITY) calculateAndUpdate(klines []types.KLine) {
if len(klines) < inc.Window {
return
@ -42,6 +56,9 @@ func (inc *VOLATILITY) calculateAndUpdate(klines []types.KLine) {
if inc.EndTime != zeroTime && lastKLine.GetEndTime().Before(inc.EndTime) {
return
}
if len(inc.Values) == 0 {
inc.SeriesBase.Series = inc
}
var recentT = klines[end-(inc.Window-1) : end+1]

View File

@ -17,6 +17,7 @@ Volume-Weighted Average Price (VWAP) Explained
*/
//go:generate callbackgen -type VWAP
type VWAP struct {
types.SeriesBase
types.IntervalWindow
Values types.Float64Slice
Prices types.Float64Slice
@ -29,6 +30,9 @@ type VWAP struct {
}
func (inc *VWAP) Update(price, volume float64) {
if len(inc.Prices) == 0 {
inc.SeriesBase.Series = inc
}
inc.Prices.Push(price)
inc.Volumes.Push(volume)
@ -65,7 +69,7 @@ func (inc *VWAP) Length() int {
return len(inc.Values)
}
var _ types.Series = &VWAP{}
var _ types.SeriesExtend = &VWAP{}
func (inc *VWAP) calculateAndUpdate(kLines []types.KLine) {
var priceF = KLineTypicalPriceMapper

View File

@ -20,6 +20,7 @@ Volume Weighted Moving Average
*/
//go:generate callbackgen -type VWMA
type VWMA struct {
types.SeriesBase
types.IntervalWindow
Values types.Float64Slice
EndTime time.Time
@ -46,7 +47,7 @@ func (inc *VWMA) Length() int {
return len(inc.Values)
}
var _ types.Series = &VWMA{}
var _ types.SeriesExtend = &VWMA{}
func KLinePriceVolumeMapper(k types.KLine) float64 {
return k.Close.Mul(k.Volume).Float64()
@ -81,6 +82,10 @@ func (inc *VWMA) calculateAndUpdate(kLines []types.KLine) {
return
}
if len(inc.Values) == 0 {
inc.SeriesBase.Series = inc
}
vwma := pv / v
inc.Values.Push(vwma)

View File

@ -14,6 +14,7 @@ const MaxNumOfWWMATruncateSize = 100
//go:generate callbackgen -type WWMA
type WWMA struct {
types.SeriesBase
types.IntervalWindow
Values types.Float64Slice
LastOpenTime time.Time
@ -23,6 +24,7 @@ type WWMA struct {
func (inc *WWMA) Update(value float64) {
if len(inc.Values) == 0 {
inc.SeriesBase.Series = inc
inc.Values.Push(value)
return
} else if len(inc.Values) > MaxNumOfWWMA {
@ -85,4 +87,4 @@ func (inc *WWMA) Bind(updater KLineWindowUpdater) {
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
}
var _ types.Series = &WWMA{}
var _ types.SeriesExtend = &WWMA{}

View File

@ -9,6 +9,7 @@ import (
//go:generate callbackgen -type ZLEMA
type ZLEMA struct {
types.SeriesBase
types.IntervalWindow
data types.Float64Slice
@ -41,6 +42,7 @@ func (inc *ZLEMA) Length() int {
func (inc *ZLEMA) Update(value float64) {
if inc.lag == 0 || inc.zlema == nil {
inc.SeriesBase.Series = inc
inc.zlema = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}}
inc.lag = int((float64(inc.Window)-1.)/2. + 0.5)
}
@ -55,7 +57,7 @@ func (inc *ZLEMA) Update(value float64) {
inc.zlema.Update(emaData)
}
var _ types.Series = &ZLEMA{}
var _ types.SeriesExtend = &ZLEMA{}
func (inc *ZLEMA) calculateAndUpdate(allKLines []types.KLine) {
if inc.zlema == nil {

1
pkg/statistics/omega.go Normal file
View File

@ -0,0 +1 @@
package statistics

34
pkg/statistics/sharp.go Normal file
View File

@ -0,0 +1,34 @@
package statistics
import (
"math"
"github.com/c9s/bbgo/pkg/types"
)
// Sharpe: Calcluates the sharpe ratio of access returns
//
// @param periods (int): Freq. of returns (252/365 for daily, 12 for monthy)
// @param annualize (bool): return annualize sharpe?
// @param smart (bool): return smart sharpe ratio
func Sharpe(returns types.Series, periods int, annualize bool, smart bool) float64 {
data := returns
num := data.Length()
if types.Lowest(data, num) >= 0 && types.Highest(data, num) > 1 {
data = types.PercentageChange(returns)
}
divisor := types.Stdev(data, data.Length(), 1)
if smart {
sum := 0.
coef := math.Abs(types.Correlation(data, types.Shift(data, 1), num-1))
for i := 1; i < num; i++ {
sum += float64(num-i) / float64(num) * math.Pow(coef, float64(i))
}
divisor = divisor * math.Sqrt(1.+2.*sum)
}
result := types.Mean(data) / divisor
if annualize {
return result * math.Sqrt(float64(periods))
}
return result
}

View File

@ -0,0 +1,27 @@
package statistics
import (
"github.com/c9s/bbgo/pkg/types"
"github.com/stretchr/testify/assert"
"testing"
)
/*
python
import quantstats as qx
import pandas as pd
print(qx.stats.sharpe(pd.Series([0.01, 0.1, 0.001]), 0, 0, False, False))
print(qx.stats.sharpe(pd.Series([0.01, 0.1, 0.001]), 0, 252, False, False))
print(qx.stats.sharpe(pd.Series([0.01, 0.1, 0.001]), 0, 252, True, False))
*/
func TestSharpe(t *testing.T) {
var a types.Series = &types.Float64Slice{0.01, 0.1, 0.001}
output := Sharpe(a, 0, false, false)
assert.InDelta(t, output, 0.67586, 0.0001)
output = Sharpe(a, 252, false, false)
assert.InDelta(t, output, 0.67586, 0.0001)
output = Sharpe(a, 252, true, false)
assert.InDelta(t, output, 10.7289, 0.0001)
}

View File

@ -0,0 +1 @@
package statistics

View File

@ -63,11 +63,11 @@ type Strategy struct {
atr *indicator.ATR
emv *indicator.EMV
ccis *CCISTOCH
ma5 types.Series
ma34 types.Series
ewo types.Series
ewoSignal types.Series
ewoHistogram types.Series
ma5 types.SeriesExtend
ma34 types.SeriesExtend
ewo types.SeriesExtend
ewoSignal types.SeriesExtend
ewoHistogram types.SeriesExtend
ewoChangeRate float64
heikinAshi *HeikinAshi
peakPrice fixedpoint.Value
@ -331,12 +331,12 @@ func (s *Strategy) SetupIndicators(store *bbgo.MarketDataStore) {
evwma34.UpdateVal(price, vol)
}
})
s.ma5 = evwma5
s.ma34 = evwma34
s.ma5 = types.NewSeries(evwma5)
s.ma34 = types.NewSeries(evwma34)
}
s.ewo = types.Mul(types.Minus(types.Div(s.ma5, s.ma34), 1.0), 100.)
s.ewoHistogram = types.Minus(s.ma5, s.ma34)
s.ewo = s.ma5.Div(s.ma34).Minus(1.0).Mul(100.)
s.ewoHistogram = s.ma5.Minus(s.ma34)
windowSignal := types.IntervalWindow{Interval: s.Interval, Window: s.SignalWindow}
if s.UseEma {
sig := &indicator.EWMA{IntervalWindow: windowSignal}
@ -365,7 +365,7 @@ func (s *Strategy) SetupIndicators(store *bbgo.MarketDataStore) {
if sig.Length() == 0 {
// lazy init
ewoVals := types.Reverse(s.ewo)
ewoVals := s.ewo.Reverse()
for _, ewoValue := range ewoVals {
sig.Update(ewoValue)
}
@ -385,7 +385,7 @@ func (s *Strategy) SetupIndicators(store *bbgo.MarketDataStore) {
}
if sig.Length() == 0 {
// lazy init
ewoVals := types.Reverse(s.ewo)
ewoVals := s.ewo.Reverse()
for i, ewoValue := range ewoVals {
vol := window.Volume().Index(i)
sig.PV.Update(ewoValue * vol)
@ -397,7 +397,7 @@ func (s *Strategy) SetupIndicators(store *bbgo.MarketDataStore) {
sig.V.Update(vol)
}
})
s.ewoSignal = sig
s.ewoSignal = types.NewSeries(sig)
}
}

View File

@ -11,15 +11,18 @@ import (
// Super basic Series type that simply holds the float64 data
// with size limit (the only difference compare to float64slice)
type Queue struct {
SeriesBase
arr []float64
size int
}
func NewQueue(size int) *Queue {
return &Queue{
out := &Queue{
arr: make([]float64, 0, size),
size: size,
}
out.SeriesBase.Series = out
return out
}
func (inc *Queue) Last() float64 {
@ -47,7 +50,7 @@ func (inc *Queue) Update(v float64) {
}
}
var _ Series = &Queue{}
var _ SeriesExtend = &Queue{}
// Float64Indicator is the indicators (SMA and EWMA) that we want to use are returning float64 data.
type Float64Indicator interface {
@ -82,24 +85,24 @@ type SeriesExtend interface {
Array(limit ...int) (result []float64)
Reverse(limit ...int) (result Float64Slice)
Change(offset ...int) SeriesExtend
Stdev(length int) float64
PercentageChange(offset ...int) SeriesExtend
Stdev(params ...int) float64
Rolling(window int) *RollingResult
Shift(offset int) SeriesExtend
Skew(length int) float64
Variance(length int) float64
Covariance(b Series, length int) float64
Correlation(b Series, length int, method ...CorrFunc) float64
Rank(length int) SeriesExtend
}
type IndexFuncType func(int) float64
type LastFuncType func() float64
type LengthFuncType func() int
type SeriesBase struct {
index IndexFuncType
last LastFuncType
length LengthFuncType
Series
}
func NewSeries(a Series) SeriesExtend {
return &SeriesBase{
index: a.Index,
last: a.Last,
length: a.Length,
Series: a,
}
}
@ -108,6 +111,11 @@ type UpdatableSeries interface {
Update(float64)
}
type UpdatableSeriesExtend interface {
SeriesExtend
Update(float64)
}
// The interface maps to pinescript basic type `series` for bool type
// Access the internal historical data from the latest to the oldest
// Index(0) always maps to Last()
@ -595,14 +603,282 @@ func Change(a Series, offset ...int) SeriesExtend {
return NewSeries(&ChangeResult{a, o})
}
func Stdev(a Series, length int) float64 {
type PercentageChangeResult struct {
a Series
offset int
}
func (c *PercentageChangeResult) Last() float64 {
if c.offset >= c.a.Length() {
return 0
}
return c.a.Last()/c.a.Index(c.offset) - 1
}
func (c *PercentageChangeResult) Index(i int) float64 {
if i+c.offset >= c.a.Length() {
return 0
}
return c.a.Index(i)/c.a.Index(i+c.offset) - 1
}
func (c *PercentageChangeResult) Length() int {
length := c.a.Length()
if length >= c.offset {
return length - c.offset
}
return 0
}
// Percentage change between current and a prior element, a / a[offset] - 1.
// offset: if not give, offset is 1.
func PercentageChange(a Series, offset ...int) SeriesExtend {
o := 1
if len(offset) > 0 {
o = offset[0]
}
return NewSeries(&PercentageChangeResult{a, o})
}
func Stdev(a Series, params ...int) float64 {
length := a.Length()
if len(params) > 0 {
if params[0] < length {
length = params[0]
}
}
ddof := 0
if len(params) > 1 {
ddof = params[1]
}
avg := Mean(a, length)
s := .0
for i := 0; i < length; i++ {
diff := a.Index(i) - avg
s += diff * diff
}
return math.Sqrt(s / float64(length))
return math.Sqrt(s / float64(length-ddof))
}
type CorrFunc func(Series, Series, int) float64
func Kendall(a, b Series, length int) float64 {
if a.Length() < length {
length = a.Length()
}
if b.Length() < length {
length = b.Length()
}
aRanks := Rank(a, length)
bRanks := Rank(b, length)
concordant, discordant := 0, 0
for i := 0; i < length; i++ {
for j := i + 1; j < length; j++ {
value := (aRanks.Index(i) - aRanks.Index(j)) * (bRanks.Index(i) - bRanks.Index(j))
if value > 0 {
concordant++
} else {
discordant++
}
}
}
return float64(concordant-discordant) * 2.0 / float64(length*(length-1))
}
func Rank(a Series, length int) SeriesExtend {
if length > a.Length() {
length = a.Length()
}
rank := make([]float64, length)
mapper := make([]float64, length+1)
for i := length - 1; i >= 0; i-- {
ii := a.Index(i)
counter := 0.
for j := 0; j < length; j++ {
if a.Index(j) <= ii {
counter += 1.
}
}
rank[i] = counter
mapper[int(counter)] += 1.
}
output := NewQueue(length)
for i := length - 1; i >= 0; i-- {
output.Update(rank[i] - (mapper[int(rank[i])]-1.)/2)
}
return output
}
func Pearson(a, b Series, length int) float64 {
if a.Length() < length {
length = a.Length()
}
if b.Length() < length {
length = b.Length()
}
x := make([]float64, length)
y := make([]float64, length)
for i := 0; i < length; i++ {
x[i] = a.Index(i)
y[i] = b.Index(i)
}
return stat.Correlation(x, y, nil)
}
func Spearman(a, b Series, length int) float64 {
if a.Length() < length {
length = a.Length()
}
if b.Length() < length {
length = b.Length()
}
aRank := Rank(a, length)
bRank := Rank(b, length)
return Pearson(aRank, bRank, length)
}
// similar to pandas.Series.corr() function.
//
// method could either be `types.Pearson`, `types.Spearman` or `types.Kendall`
func Correlation(a Series, b Series, length int, method ...CorrFunc) float64 {
var runner CorrFunc
if len(method) == 0 {
runner = Pearson
} else {
runner = method[0]
}
return runner(a, b, length)
}
// similar to pandas.Series.cov() function with ddof=0
//
// Compute covariance with Series
func Covariance(a Series, b Series, length int) float64 {
if a.Length() < length {
length = a.Length()
}
if b.Length() < length {
length = b.Length()
}
meana := Mean(a, length)
meanb := Mean(b, length)
sum := 0.0
for i := 0; i < length; i++ {
sum += (a.Index(i) - meana) * (b.Index(i) - meanb)
}
sum /= float64(length)
return sum
}
func Variance(a Series, length int) float64 {
return Covariance(a, a, length)
}
// similar to pandas.Series.skew() function.
//
// Return unbiased skew over input series
func Skew(a Series, length int) float64 {
if length > a.Length() {
length = a.Length()
}
mean := Mean(a, length)
sum2 := 0.0
sum3 := 0.0
for i := 0; i < length; i++ {
diff := a.Index(i) - mean
sum2 += diff * diff
sum3 += diff * diff * diff
}
if length <= 2 || sum2 == 0 {
return math.NaN()
}
l := float64(length)
return l * math.Sqrt(l-1) / (l - 2) * sum3 / math.Pow(sum2, 1.5)
}
type ShiftResult struct {
a Series
offset int
}
func (inc *ShiftResult) Last() float64 {
if inc.offset < 0 {
return 0
}
if inc.offset > inc.a.Length() {
return 0
}
return inc.a.Index(inc.offset)
}
func (inc *ShiftResult) Index(i int) float64 {
if inc.offset+i < 0 {
return 0
}
if inc.offset+i > inc.a.Length() {
return 0
}
return inc.a.Index(inc.offset + i)
}
func (inc *ShiftResult) Length() int {
return inc.a.Length() - inc.offset
}
func Shift(a Series, offset int) SeriesExtend {
return NewSeries(&ShiftResult{a, offset})
}
type RollingResult struct {
a Series
window int
}
type SliceView struct {
a Series
start int
length int
}
func (s *SliceView) Last() float64 {
return s.a.Index(s.start)
}
func (s *SliceView) Index(i int) float64 {
if i >= s.length {
return 0
}
return s.a.Index(i + s.start)
}
func (s *SliceView) Length() int {
return s.length
}
var _ Series = &SliceView{}
func (r *RollingResult) Last() SeriesExtend {
return NewSeries(&SliceView{r.a, 0, r.window})
}
func (r *RollingResult) Index(i int) SeriesExtend {
if i*r.window > r.a.Length() {
return nil
}
return NewSeries(&SliceView{r.a, i * r.window, r.window})
}
func (r *RollingResult) Length() int {
mod := r.a.Length() % r.window
if mod > 0 {
return r.a.Length()/r.window + 1
} else {
return r.a.Length() / r.window
}
}
func Rolling(a Series, window int) *RollingResult {
return &RollingResult{a, window}
}
// TODO: ta.linreg

View File

@ -33,3 +33,54 @@ func TestFloat64Slice(t *testing.T) {
b = append(b, 3.0)
assert.Equal(t, c.Last(), 1.)
}
/*
python
import pandas as pd
s1 = pd.Series([.2, 0., .6, .2, .2])
s2 = pd.Series([.3, .6, .0, .1])
print(s1.corr(s2, method='pearson'))
print(s1.corr(s2, method='spearman')
print(s1.corr(s2, method='kendall'))
print(s1.rank())
*/
func TestCorr(t *testing.T) {
var a = Float64Slice{.2, .0, .6, .2}
var b = Float64Slice{.3, .6, .0, .1}
corr := Correlation(&a, &b, 4, Pearson)
assert.InDelta(t, corr, -0.8510644, 0.001)
out := Rank(&a, 4)
assert.Equal(t, out.Index(0), 2.5)
assert.Equal(t, out.Index(1), 4.0)
corr = Correlation(&a, &b, 4, Spearman)
assert.InDelta(t, corr, -0.94868, 0.001)
}
/*
python
import pandas as pd
s1 = pd.Series([.2, 0., .6, .2, .2])
s2 = pd.Series([.3, .6, .0, .1])
print(s1.cov(s2, ddof=0))
*/
func TestCov(t *testing.T) {
var a = Float64Slice{.2, .0, .6, .2}
var b = Float64Slice{.3, .6, .0, .1}
cov := Covariance(&a, &b, 4)
assert.InDelta(t, cov, -0.042499, 0.001)
}
/*
python
import pandas as pd
s1 = pd.Series([.2, 0., .6, .2, .2])
print(s1.skew())
*/
func TestSkew(t *testing.T) {
var a = Float64Slice{.2, .0, .6, .2}
sk := Skew(&a, 4)
assert.InDelta(t, sk, 1.129338, 0.001)
}

View File

@ -1,15 +1,24 @@
package types
func (s *SeriesBase) Index(i int) float64 {
return s.index(i)
if s.Series == nil {
return 0
}
return s.Series.Index(i)
}
func (s *SeriesBase) Last() float64 {
return s.last()
if s.Series == nil {
return 0
}
return s.Series.Last()
}
func (s *SeriesBase) Length() int {
return s.length()
if s.Series == nil {
return 0
}
return s.Series.Length()
}
func (s *SeriesBase) Sum(limit ...int) float64 {
@ -80,6 +89,38 @@ func (s *SeriesBase) Change(offset ...int) SeriesExtend {
return Change(s, offset...)
}
func (s *SeriesBase) Stdev(length int) float64 {
return Stdev(s, length)
func (s *SeriesBase) PercentageChange(offset ...int) SeriesExtend {
return PercentageChange(s, offset...)
}
func (s *SeriesBase) Stdev(params ...int) float64 {
return Stdev(s, params...)
}
func (s *SeriesBase) Rolling(window int) *RollingResult {
return Rolling(s, window)
}
func (s *SeriesBase) Shift(offset int) SeriesExtend {
return Shift(s, offset)
}
func (s *SeriesBase) Skew(length int) float64 {
return Skew(s, length)
}
func (s *SeriesBase) Variance(length int) float64 {
return Variance(s, length)
}
func (s *SeriesBase) Covariance(b Series, length int) float64 {
return Covariance(s, b, length)
}
func (s *SeriesBase) Correlation(b Series, length int, method ...CorrFunc) float64 {
return Correlation(s, b, length, method...)
}
func (s *SeriesBase) Rank(length int) SeriesExtend {
return Rank(s, length)
}