Skip to content

Commit 5f98d4d

Browse files
committed
Stochastic Oscillator indicator added.
1 parent 165730c commit 5f98d4d

File tree

4 files changed

+139
-64
lines changed

4 files changed

+139
-64
lines changed

README.md

+19-12
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Indicator is a Golang module providing various stock technical analysis indicato
2020
- [Actual True Range (ATR)](#actual-true-range-atr)
2121
- [Chandelier Exit](#chandelier-exit)
2222
- [Ichimoku Cloud](#ichimoku-cloud)
23+
- [Stochastic Oscillator](#stochastic-oscillator)
2324

2425
## Usage
2526

@@ -94,12 +95,9 @@ middleBand, upperBand, lowerBand := indicator.BollingerBands(close)
9495

9596
#### Bollinger Band Width
9697

97-
The [BollingerBandWidth](https://pkg.go.dev/github.com/cinar/indicator#BollingerBandWidth) function
98-
measures the percentage difference between the upper band and the lower band. It decreases as
99-
Bollinger Bands narrows and increases as Bollinger Bands widens.
98+
The [BollingerBandWidth](https://pkg.go.dev/github.com/cinar/indicator#BollingerBandWidth) function measures the percentage difference between the upper band and the lower band. It decreases as Bollinger Bands narrows and increases as Bollinger Bands widens.
10099

101-
During a period of rising price volatility the band width widens, and during a period of low market
102-
volatility band width contracts.
100+
During a period of rising price volatility the band width widens, and during a period of low market volatility band width contracts.
103101

104102
```
105103
Band Width = (Upper Band - Lower Band) / Middle Band
@@ -161,8 +159,7 @@ rs, rsi := indicator.Rsi(close)
161159

162160
#### On-Balance Volume (OBV)
163161

164-
The [Obv](https://pkg.go.dev/github.com/cinar/indicator#Obv) function calculates a technical trading
165-
momentum indicator that uses volume flow to predict changes in stock price.
162+
The [Obv](https://pkg.go.dev/github.com/cinar/indicator#Obv) function calculates a technical trading momentum indicator that uses volume flow to predict changes in stock price.
166163

167164
```
168165
volume, if Close > Close-Prev
@@ -176,9 +173,7 @@ result := indicator.Obv(close, volume)
176173

177174
#### Actual True Range (ATR)
178175

179-
The [Atr](https://pkg.go.dev/github.com/cinar/indicator#Atr) function calculates a technical
180-
analysis indicator that measures market volatility by decomposing the entire range of stock
181-
prices for that period.
176+
The [Atr](https://pkg.go.dev/github.com/cinar/indicator#Atr) function calculates a technical analysis indicator that measures market volatility by decomposing the entire range of stock prices for that period.
182177

183178
```
184179
TR = Max((High - Low), (High - Close), (Close - Low))
@@ -191,8 +186,7 @@ tr, atr := indicator.Atr(14, high, low, close)
191186

192187
#### Chandelier Exit
193188

194-
The [ChandelierExit](https://pkg.go.dev/github.com/cinar/indicator#ChandelierExit) function sets a
195-
trailing stop-loss based on the Average True Value (ATR).
189+
The [ChandelierExit](https://pkg.go.dev/github.com/cinar/indicator#ChandelierExit) function sets a trailing stop-loss based on the Average True Value (ATR).
196190

197191
```
198192
Chandelier Exit Long = 22-Period SMA High - ATR(22) * 3
@@ -219,6 +213,19 @@ Chikou Span (Lagging Span) = Close plotted 26 days in the past.
219213
conversionLine, baseLine, leadingSpanA, leadingSpanB, laggingLine := indicator.IchimokuCloud(high, low, close)
220214
```
221215

216+
#### Stochastic Oscillator
217+
218+
The [StochasticOscillator](https://pkg.go.dev/github.com/cinar/indicator#StochasticOscillator) function calculates a momentum indicator that shows the location of the close relative to high-low range over a set number of periods.
219+
220+
```
221+
K = (Close - Lowest Low) / (Highest High - Lowest Low) * 100
222+
D = 3-Period SMA of K
223+
```
224+
225+
```Golang
226+
k, d := indicator.StochasticOscillator(high, low, close)
227+
```
228+
222229
## License
223230

224231
The source code is provided under MIT License.

helper.go

+48-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
11
package indicator
22

3-
// Multiply values with multipler.
4-
func multiply(values []float64, multiplier float64) []float64 {
3+
// Check values same size.
4+
func checkSameSize(values ...[]float64) {
5+
if len(values) < 2 {
6+
return
7+
}
8+
9+
n := len(values[0])
10+
11+
for i := 1; i < len(values); i++ {
12+
if len(values[i]) != n {
13+
panic("not all same size")
14+
}
15+
}
16+
}
17+
18+
// Multiply values by multipler.
19+
func multiplyBy(values []float64, multiplier float64) []float64 {
520
result := make([]float64, len(values))
621

722
for i, value := range values {
@@ -11,16 +26,40 @@ func multiply(values []float64, multiplier float64) []float64 {
1126
return result
1227
}
1328

14-
// Divide values with divider.
15-
func divide(values []float64, divider float64) []float64 {
16-
return multiply(values, float64(1)/divider)
29+
// Multiply values1 and values2.
30+
func multiply(values1, values2 []float64) []float64 {
31+
checkSameSize(values1, values2)
32+
33+
result := make([]float64, len(values1))
34+
35+
for i := 0; i < len(result); i++ {
36+
result[i] = values1[i] * values2[i]
37+
}
38+
39+
return result
40+
}
41+
42+
// Divide values by divider.
43+
func divideBy(values []float64, divider float64) []float64 {
44+
return multiplyBy(values, float64(1)/divider)
45+
}
46+
47+
// Divide values1 by values2.
48+
func divide(values1, values2 []float64) []float64 {
49+
checkSameSize(values1, values2)
50+
51+
result := make([]float64, len(values1))
52+
53+
for i := 0; i < len(result); i++ {
54+
result[i] = values1[i] / values2[i]
55+
}
56+
57+
return result
1758
}
1859

1960
// Add values1 and values2.
2061
func add(values1, values2 []float64) []float64 {
21-
if len(values1) != len(values2) {
22-
panic("not the same length")
23-
}
62+
checkSameSize(values1, values2)
2463

2564
result := make([]float64, len(values1))
2665
for i := 0; i < len(result); i++ {
@@ -32,7 +71,7 @@ func add(values1, values2 []float64) []float64 {
3271

3372
// Substract values2 from values1.
3473
func substract(values1, values2 []float64) []float64 {
35-
substract := multiply(values2, float64(-1))
74+
substract := multiplyBy(values2, float64(-1))
3675
return add(values1, substract)
3776
}
3877

helper_test.go

+43-25
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import (
44
"testing"
55
)
66

7-
func TestMultiply(t *testing.T) {
7+
func TestMultiplyBy(t *testing.T) {
88
values := []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
99
multiplier := float64(2)
1010

11-
result := multiply(values, multiplier)
11+
result := multiplyBy(values, multiplier)
1212
if len(result) != len(values) {
1313
t.Fatal("result not same size")
1414
}
@@ -23,11 +23,29 @@ func TestMultiply(t *testing.T) {
2323
}
2424
}
2525

26-
func TestDivide(t *testing.T) {
26+
func TestMultiply(t *testing.T) {
27+
values1 := []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
28+
values2 := []float64{2, 4, 2, 4, 2, 4, 2, 4, 2, 4}
29+
expected := []float64{2, 8, 6, 16, 10, 24, 14, 32, 18, 40}
30+
31+
actual := multiply(values1, values2)
32+
33+
if len(actual) != len(expected) {
34+
t.Fatalf("actual %d expected %d", len(actual), len(expected))
35+
}
36+
37+
for i := 0; i < len(actual); i++ {
38+
if actual[i] != expected[i] {
39+
t.Fatalf("at %d actual %f expected %f", i, actual[i], expected[i])
40+
}
41+
}
42+
}
43+
44+
func TestDivideBy(t *testing.T) {
2745
values := []float64{2, 4, 6, 8, 10, 12, 14, 16, 18, 20}
2846
divider := float64(2)
2947

30-
result := divide(values, divider)
48+
result := divideBy(values, divider)
3149
if len(result) != len(values) {
3250
t.Fatal("result not same size")
3351
}
@@ -42,6 +60,21 @@ func TestDivide(t *testing.T) {
4260
}
4361
}
4462

63+
func TestDivide(t *testing.T) {
64+
values1 := []float64{2, 8, 6, 16, 10, 24, 14, 32, 18, 40}
65+
values2 := []float64{2, 4, 2, 4, 2, 4, 2, 4, 2, 4}
66+
expected := []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
67+
68+
actual := divide(values1, values2)
69+
checkSameSize(actual, expected)
70+
71+
for i := 0; i < len(actual); i++ {
72+
if actual[i] != expected[i] {
73+
t.Fatalf("at %d actual %f expected %f", i, actual[i], expected[i])
74+
}
75+
}
76+
}
77+
4578
func TestAddWithDifferentSizes(t *testing.T) {
4679
values1 := []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
4780
values2 := []float64{1, 2, 3, 4, 5}
@@ -59,9 +92,7 @@ func TestAdd(t *testing.T) {
5992
values := []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
6093

6194
result := add(values, values)
62-
if len(result) != len(values) {
63-
t.Fatal("result not same size")
64-
}
95+
checkSameSize(result, values)
6596

6697
for i := 0; i < len(result); i++ {
6798
expected := values[i] + values[i]
@@ -78,9 +109,7 @@ func TestSubstract(t *testing.T) {
78109
values2 := []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
79110

80111
result := substract(values1, values2)
81-
if len(result) != len(values1) {
82-
t.Fatal("result not same size")
83-
}
112+
checkSameSize(result, values1)
84113

85114
for i := 0; i < len(result); i++ {
86115
expected := values1[i] - values2[i]
@@ -108,10 +137,7 @@ func TestDiff(t *testing.T) {
108137
before := 1
109138

110139
actual := diff(values, before)
111-
112-
if len(actual) != len(expected) {
113-
t.Fatalf("actual %d expected %d", len(actual), len(expected))
114-
}
140+
checkSameSize(actual, expected)
115141

116142
for i := 0; i < len(actual); i++ {
117143
if actual[i] != expected[i] {
@@ -127,20 +153,15 @@ func TestGroupPositivesAndNegatives(t *testing.T) {
127153

128154
actualPositives, actualNegatives := groupPositivesAndNegatives(values)
129155

130-
if len(actualPositives) != len(expectedPositives) {
131-
t.Fatalf("actual positives %d expected positives %d", len(actualPositives), len(expectedPositives))
132-
}
156+
checkSameSize(actualPositives, expectedPositives)
157+
checkSameSize(actualNegatives, expectedNegatives)
133158

134159
for i := 0; i < len(actualPositives); i++ {
135160
if actualPositives[i] != expectedPositives[i] {
136161
t.Fatalf("at %d actual positive %f expected positive %f", i, actualPositives[i], expectedPositives[i])
137162
}
138163
}
139164

140-
if len(actualNegatives) != len(expectedNegatives) {
141-
t.Fatalf("actual positives %d expected positives %d", len(actualNegatives), len(expectedNegatives))
142-
}
143-
144165
for i := 0; i < len(actualNegatives); i++ {
145166
if actualNegatives[i] != expectedNegatives[i] {
146167
t.Fatalf("at %d actual negative %f expected negative %f", i, actualNegatives[i], expectedNegatives[i])
@@ -154,10 +175,7 @@ func TestShiftRight(t *testing.T) {
154175
period := 4
155176

156177
actual := shiftRight(period, values)
157-
158-
if len(actual) != len(expected) {
159-
t.Fatalf("actual %d expected %d", len(actual), len(expected))
160-
}
178+
checkSameSize(actual, expected)
161179

162180
for i := 0; i < len(actual); i++ {
163181
if actual[i] != expected[i] {

indicator.go

+29-18
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func Macd(close []float64) ([]float64, []float64) {
2828
// Returns middle band, upper band, lower band.
2929
func BollingerBands(close []float64) ([]float64, []float64, []float64) {
3030
std := Std(20, close)
31-
std2 := multiply(std, 2)
31+
std2 := multiplyBy(std, 2)
3232

3333
middleBand := Sma(20, close)
3434
upperBand := add(middleBand, std2)
@@ -48,9 +48,7 @@ func BollingerBands(close []float64) ([]float64, []float64, []float64) {
4848
//
4949
// Returns bandWidth, bandWidthEma90
5050
func BollingerBandWidth(middleBand, upperBand, lowerBand []float64) ([]float64, []float64) {
51-
if len(middleBand) != len(upperBand) || len(upperBand) != len(lowerBand) {
52-
panic("bands not same size")
53-
}
51+
checkSameSize(middleBand, upperBand, lowerBand)
5452

5553
bandWidth := make([]float64, len(middleBand))
5654
for i := 0; i < len(bandWidth); i++ {
@@ -69,7 +67,7 @@ func BollingerBandWidth(middleBand, upperBand, lowerBand []float64) ([]float64,
6967
//
7068
// Returns ao.
7169
func AwesomeOscillator(low, high []float64) []float64 {
72-
medianPrice := divide(add(low, high), float64(2))
70+
medianPrice := divideBy(add(low, high), float64(2))
7371
sma5 := Sma(5, medianPrice)
7472
sma34 := Sma(34, medianPrice)
7573
ao := substract(sma5, sma34)
@@ -106,9 +104,7 @@ func WilliamsR(low, high, close []float64) []float64 {
106104
//
107105
// Returns typical price, 20-Period SMA.
108106
func TypicalPrice(low, high, close []float64) ([]float64, []float64) {
109-
if len(high) != len(low) || len(low) != len(close) {
110-
panic("not all same size")
111-
}
107+
checkSameSize(high, low, close)
112108

113109
sma20 := Sma(20, close)
114110

@@ -180,9 +176,7 @@ func Obv(close []float64, volume []int64) []int64 {
180176
//
181177
// Returns tr, atr
182178
func Atr(period int, high, low, close []float64) ([]float64, []float64) {
183-
if len(high) != len(low) || len(low) != len(close) {
184-
panic("not all same size")
185-
}
179+
checkSameSize(high, low, close)
186180

187181
tr := make([]float64, len(close))
188182

@@ -228,15 +222,32 @@ func ChandelierExit(high, low, close []float64) ([]float64, []float64) {
228222
//
229223
// Returns conversionLine, baseLine, leadingSpanA, leadingSpanB, laggingSpan
230224
func IchimokuCloud(high, low, close []float64) ([]float64, []float64, []float64, []float64, []float64) {
231-
if len(high) != len(low) || len(low) != len(close) {
232-
panic("not all same size")
233-
}
225+
checkSameSize(high, low, close)
234226

235-
conversionLine := divide(add(Max(9, high), Min(9, low)), float64(2))
236-
baseLine := divide(add(Max(26, high), Min(26, low)), float64(2))
237-
leadingSpanA := divide(add(conversionLine, baseLine), float64(2))
238-
leadingSpanB := divide(add(Max(52, high), Min(52, low)), float64(2))
227+
conversionLine := divideBy(add(Max(9, high), Min(9, low)), float64(2))
228+
baseLine := divideBy(add(Max(26, high), Min(26, low)), float64(2))
229+
leadingSpanA := divideBy(add(conversionLine, baseLine), float64(2))
230+
leadingSpanB := divideBy(add(Max(52, high), Min(52, low)), float64(2))
239231
laggingLine := shiftRight(26, close)
240232

241233
return conversionLine, baseLine, leadingSpanA, leadingSpanB, laggingLine
242234
}
235+
236+
// Stochastic Oscillator. It is a momentum indicator that shows the location of the close
237+
// relative to high-low range over a set number of periods.
238+
//
239+
// K = (Close - Lowest Low) / (Highest High - Lowest Low) * 100
240+
// D = 3-Period SMA of K
241+
//
242+
// Returns k, d
243+
func StochasticOscillator(high, low, close []float64) ([]float64, []float64) {
244+
checkSameSize(high, low, close)
245+
246+
highestHigh14 := Max(14, high)
247+
lowestLow14 := Min(15, low)
248+
249+
k := divide(substract(close, lowestLow14), multiplyBy(substract(highestHigh14, lowestLow14), float64(100)))
250+
d := Sma(3, k)
251+
252+
return k, d
253+
}

0 commit comments

Comments
 (0)