feature: add sharpe function implementation

This commit is contained in:
zenix 2022-06-21 19:47:14 +09:00
parent d8d77cec1e
commit 69533c0397
3 changed files with 143 additions and 4 deletions

View File

@ -1,14 +1,34 @@
package statistics package statistics
import ( import (
"math"
"github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/types"
) )
// Sharpe: Calcluates the sharpe ratio of access returns // Sharpe: Calcluates the sharpe ratio of access returns
// //
// @param rf (float): Risk-free rate expressed as a yearly (annualized) return
// @param periods (int): Freq. of returns (252/365 for daily, 12 for monthy) // @param periods (int): Freq. of returns (252/365 for daily, 12 for monthy)
// @param annualize (bool): return annualize sharpe? // @param annualize (bool): return annualize sharpe?
// @param smart (bool): return smart sharpe ratio // @param smart (bool): return smart sharpe ratio
func Sharpe(returns types.Series, rf float64, periods int, annualize bool, smart bool) { 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

@ -633,14 +633,24 @@ func PercentageChange(a Series, offset ...int) Series {
return &PercentageChangeResult{a, o} return &PercentageChangeResult{a, o}
} }
func Stdev(a Series, length int) float64 { 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) avg := Mean(a, length)
s := .0 s := .0
for i := 0; i < length; i++ { for i := 0; i < length; i++ {
diff := a.Index(i) - avg diff := a.Index(i) - avg
s += diff * diff s += diff * diff
} }
return math.Sqrt(s / float64(length)) return math.Sqrt(s / float64(length - ddof))
} }
type CorrFunc func(Series, Series, int) float64 type CorrFunc func(Series, Series, int) float64
@ -780,4 +790,86 @@ func Skew(a Series, length int) float64 {
return l * math.Sqrt(l-1) / (l - 2) * sum3 / math.Pow(sum2, 1.5) 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) Series {
return &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() Series {
return &SliceView{r.a, 0, r.window}
}
func (r *RollingResult) Index(i int) Series {
if i * r.window > r.a.Length() {
return nil
}
return &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 // TODO: ta.linreg