Skip to content

Commit 0b09ea3

Browse files
authored
support ta.variance (#27)
1 parent 100360f commit 0b09ea3

File tree

4 files changed

+266
-18
lines changed

4 files changed

+266
-18
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ Technical Indicators
138138
| ta.tr | OHLCVSeries.getSeries(OHLCPropTR) |
139139
| ta.tsi | |
140140
| ta.valuewhen | pine.ValueWhen() |
141-
| ta.variance | |
141+
| ta.variance | pine.Variance() |
142142
| ta.vwap | |
143143
| ta.vwma | |
144144
| ta.wad | |

series_stdev.go

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package pine
22

33
import (
44
"fmt"
5-
"math"
65

76
"github.com/pkg/errors"
87
)
@@ -35,33 +34,21 @@ func Stdev(p ValueSeries, l int64) (ValueSeries, error) {
3534
stdev = NewValueSeries()
3635
}
3736

38-
sma, err := SMA(p, l)
39-
if err != nil {
40-
return stdev, errors.Wrap(err, "error getting sma")
41-
}
42-
4337
// current available value
4438
stop := p.GetCurrent()
4539
if stop == nil {
4640
return stdev, nil
4741
}
4842

49-
meanv := sma.Get(stop.t)
50-
if meanv == nil {
43+
if stdev.Get(stop.t) != nil {
5144
return stdev, nil
5245
}
5346

54-
diff := p.SubConst(meanv.v)
55-
sqrt, err := Pow(diff, 2)
47+
vari, err := Variance(p, l)
5648
if err != nil {
57-
return stdev, errors.Wrap(err, "error pow(2)")
49+
return nil, errors.Wrap(err, "error getting variance")
5850
}
59-
sum, err := Sum(sqrt, int(l))
60-
if err != nil {
61-
return stdev, errors.Wrap(err, "error sum")
62-
}
63-
denom := math.Max(float64(l-1), 1)
64-
vari := sum.DivConst(denom)
51+
6552
stdev, err = Pow(vari, 0.5)
6653
if err != nil {
6754
return stdev, errors.Wrap(err, "error pow(0.5)")

series_variance.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package pine
2+
3+
import (
4+
"fmt"
5+
"math"
6+
7+
"github.com/pkg/errors"
8+
)
9+
10+
// Variance generates a ValueSeries of variance.
11+
// Variance is the expectation of the squared deviation of a series from its mean (ta.sma, and it informally measures how far a set of numbers are spread out from their mean.
12+
//
13+
// Simplified formula is v = (1 / (N-1) * sum(xi - x)^2)
14+
//
15+
// Parameters
16+
// p - ValueSeries: source data
17+
// l - lookback: lookback periods [1, ∞)
18+
//
19+
// TradingView's PineScript has an option to use an unbiased estimator, however; this function currently supports biased estimator.
20+
// Any effort to add a bias correction factor is welcome.
21+
//
22+
// Example:
23+
// t=time.Time | 1 | 2 | 3 | 4 | 5 |
24+
// p=ValueSeries | 13 | 15 | 11 | 19 | 21 |
25+
// sma(p, 3) | nil | nil | 13 | 15 | 17 |
26+
// p - sma(p, 3)(t=1) | nil | nil | nil | nil | nil |
27+
// p - sma(p, 3)(t=2) | nil | nil | nil | nil | nil |
28+
// p - sma(p, 3)(t=3) | 0 | 2 | -2 | 6 | 5 |
29+
// p - sma(p, 3)(t=4) | -2 | 0 | -4 | 4 | 6 |
30+
// p - sma(p, 3)(t=5) | -4 | -2 | -6 | 2 | 4 |
31+
// Variance(p, 3) | nil | nil | 4 | 16 | 28 |
32+
func Variance(p ValueSeries, l int64) (ValueSeries, error) {
33+
key := fmt.Sprintf("variance:%s:%d", p.ID(), l)
34+
vari := getCache(key)
35+
if vari == nil {
36+
vari = NewValueSeries()
37+
}
38+
39+
// current available value
40+
stop := p.GetCurrent()
41+
if stop == nil {
42+
return vari, nil
43+
}
44+
45+
if vari.Get(stop.t) != nil {
46+
return vari, nil
47+
}
48+
49+
sma, err := SMA(p, l)
50+
if err != nil {
51+
return vari, errors.Wrap(err, "error getting sma")
52+
}
53+
54+
meanv := sma.Get(stop.t)
55+
if meanv == nil {
56+
return vari, nil
57+
}
58+
59+
diff := p.SubConst(meanv.v)
60+
sqrt, err := Pow(diff, 2)
61+
if err != nil {
62+
return vari, errors.Wrap(err, "error pow(2)")
63+
}
64+
sum, err := Sum(sqrt, int(l))
65+
if err != nil {
66+
return vari, errors.Wrap(err, "error sum")
67+
}
68+
denom := math.Max(float64(l-1), 1)
69+
vari = sum.DivConst(denom)
70+
71+
setCache(key, vari)
72+
73+
vari.SetCurrent(stop.t)
74+
75+
return vari, nil
76+
}

series_variance_test.go

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
package pine
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
"time"
7+
8+
"github.com/pkg/errors"
9+
)
10+
11+
// TestSeriesVarianceNoData tests no data scenario
12+
//
13+
// t=time.Time (no iteration) | |
14+
// p=ValueSeries | |
15+
// variance=ValueSeries | |
16+
func TestSeriesVarianceNoData(t *testing.T) {
17+
18+
start := time.Now()
19+
data := OHLCVTestData(start, 4, 5*60*1000)
20+
21+
series, err := NewOHLCVSeries(data)
22+
if err != nil {
23+
t.Fatal(err)
24+
}
25+
26+
prop := series.GetSeries(OHLCPropClose)
27+
variance, err := Variance(prop, 2)
28+
if err != nil {
29+
t.Fatal(errors.Wrap(err, "error Variance"))
30+
}
31+
if variance == nil {
32+
t.Error("Expected to be non nil but got nil")
33+
}
34+
}
35+
36+
// TestSeriesVarianceNoIteration tests this sceneario where there's no iteration yet
37+
//
38+
// t=time.Time (no iteration) | 1 | 2 | 3 | 4 |
39+
// p=ValueSeries | 14 | 15 | 17 | 18 |
40+
// variance=ValueSeries | | | | |
41+
func TestSeriesVarianceNoIteration(t *testing.T) {
42+
43+
start := time.Now()
44+
data := OHLCVTestData(start, 4, 5*60*1000)
45+
data[0].C = 14
46+
data[1].C = 15
47+
data[2].C = 17
48+
data[3].C = 18
49+
50+
series, err := NewOHLCVSeries(data)
51+
if err != nil {
52+
t.Fatal(err)
53+
}
54+
55+
prop := series.GetSeries(OHLCPropClose)
56+
variance, err := RSI(prop, 2)
57+
if err != nil {
58+
t.Fatal(errors.Wrap(err, "error RSI"))
59+
}
60+
if variance == nil {
61+
t.Error("Expected to be non-nil but got nil")
62+
}
63+
}
64+
65+
// TestSeriesVarianceIteration tests this scneario
66+
//
67+
// t=time.Time | 1 | 2 | 3 | 4 | 5 |
68+
// p=ValueSeries | 13 | 15 | 11 | 19 | 21 |
69+
// sma(p, 3) | nil | nil | 13 | 15 | 17 |
70+
// p - sma(p, 3)(t=1) | nil | nil | nil | nil | nil |
71+
// p - sma(p, 3)(t=2) | nil | nil | nil | nil | nil |
72+
// p - sma(p, 3)(t=3) | 0 | 2 | -2 | 6 | 5 |
73+
// p - sma(p, 3)(t=4) | -2 | 0 | -4 | 4 | 6 |
74+
// p - sma(p, 3)(t=5) | -4 | -2 | -6 | 2 | 4 |
75+
// Variance(p, 3) | nil | nil | 4 | 16 | 28 |
76+
func TestSeriesVarianceIteration(t *testing.T) {
77+
78+
start := time.Now()
79+
data := OHLCVTestData(start, 5, 5*60*1000)
80+
data[0].C = 13
81+
data[1].C = 15
82+
data[2].C = 11
83+
data[3].C = 19
84+
data[4].C = 21
85+
86+
series, err := NewOHLCVSeries(data)
87+
if err != nil {
88+
t.Fatal(err)
89+
}
90+
91+
testTable := []float64{0, 0, 4, 16, 28}
92+
93+
for i, v := range testTable {
94+
series.Next()
95+
96+
prop := series.GetSeries(OHLCPropClose)
97+
variance, err := Variance(prop, 3)
98+
if err != nil {
99+
t.Fatal(errors.Wrap(err, "error Variance"))
100+
}
101+
exp := v
102+
if exp == 0 {
103+
if variance.Val() != nil {
104+
t.Fatalf("expected nil but got non nil: %+v testtable item: %d", *variance.Val(), i)
105+
}
106+
// OK
107+
}
108+
if exp != 0 {
109+
if variance.Val() == nil {
110+
t.Fatalf("expected non nil: %+v but got nil testtable item: %d", exp, i)
111+
}
112+
if fmt.Sprintf("%.04f", exp) != fmt.Sprintf("%.04f", *variance.Val()) {
113+
t.Fatalf("expected %+v but got %+v testtable item: %d", exp, *variance.Val(), i)
114+
}
115+
// OK
116+
}
117+
}
118+
}
119+
120+
// TestSeriesVarianceNotEnoughData tests when the lookback is more than the number of data available
121+
//
122+
// t=time.Time | 1 | 2 | 3 | 4 (here) |
123+
// p=ValueSeries | 14 | 15 | 17 | 18 |
124+
// variance(close, 5) | nil| nil | nil| nil |
125+
func TestSeriesVarianceNotEnoughData(t *testing.T) {
126+
127+
start := time.Now()
128+
data := OHLCVTestData(start, 4, 5*60*1000)
129+
data[0].C = 13
130+
data[1].C = 15
131+
data[2].C = 11
132+
data[3].C = 18
133+
134+
series, err := NewOHLCVSeries(data)
135+
if err != nil {
136+
t.Fatal(err)
137+
}
138+
139+
series.Next()
140+
series.Next()
141+
series.Next()
142+
series.Next()
143+
144+
testTable := []struct {
145+
lookback int
146+
exp *float64
147+
}{
148+
{
149+
lookback: 5,
150+
exp: nil,
151+
},
152+
{
153+
lookback: 6,
154+
exp: nil,
155+
},
156+
}
157+
158+
for i, v := range testTable {
159+
prop := series.GetSeries(OHLCPropClose)
160+
161+
variance, err := Variance(prop, int64(v.lookback))
162+
if err != nil {
163+
t.Fatal(errors.Wrap(err, "error RSI"))
164+
}
165+
if variance == nil {
166+
t.Errorf("Expected to be non nil but got nil at idx: %d", i)
167+
}
168+
if variance.Val() != v.exp {
169+
t.Errorf("Expected to get %+v but got %+v for lookback %+v", v.exp, *variance.Val(), v.lookback)
170+
}
171+
}
172+
}
173+
174+
func BenchmarkVariance(b *testing.B) {
175+
// run the Fib function b.N times
176+
start := time.Now()
177+
data := OHLCVTestData(start, 10000, 5*60*1000)
178+
series, _ := NewOHLCVSeries(data)
179+
vals := series.GetSeries(OHLCPropClose)
180+
181+
for n := 0; n < b.N; n++ {
182+
series.Next()
183+
Variance(vals, 5)
184+
}
185+
}

0 commit comments

Comments
 (0)