Skip to content

Commit 2312e55

Browse files
authored
Stochastic indicator (#300)
# Describe Request Stochastic indicator is added. # Change Type New indicator. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added a new Stochastic indicator to the trend indicators collection with configurable period settings and dual output values (%K and %D). * **Documentation** * Updated README with comprehensive documentation for the new Stochastic indicator and its configuration options. * **Tests** * Added unit tests for the Stochastic indicator with validation against test data. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 6e7c58a commit 2312e55

File tree

5 files changed

+233
-0
lines changed

5 files changed

+233
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ The following list of indicators are currently supported by this package:
4848
- [Moving Sum](trend/README.md#type-movingsum)
4949
- Parabolic SAR
5050
- [Random Index (KDJ)](trend/README.md#type-kdj)
51+
- [Stochastic](trend/README.md#type-stochastic)
5152
- [Rolling Moving Average (RMA)](trend/README.md#type-rma)
5253
- [Simple Moving Average (SMA)](trend/README.md#type-sma)
5354
- [Since Change](helper/README.md#func-since)

trend/README.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,11 @@ The information provided on this project is strictly for informational purposes
138138
- [func \(s \*Smma\[T\]\) Compute\(c \<\-chan T\) \<\-chan T](<#Smma[T].Compute>)
139139
- [func \(s \*Smma\[T\]\) IdlePeriod\(\) int](<#Smma[T].IdlePeriod>)
140140
- [func \(s \*Smma\[T\]\) String\(\) string](<#Smma[T].String>)
141+
- [type Stochastic](<#Stochastic>)
142+
- [func NewStochastic\[T helper.Number\]\(\) \*Stochastic\[T\]](<#NewStochastic>)
143+
- [func NewStochasticWithPeriod\[T helper.Number\]\(period int\) \*Stochastic\[T\]](<#NewStochasticWithPeriod>)
144+
- [func \(s \*Stochastic\[T\]\) Compute\(values \<\-chan T\) \(\<\-chan T, \<\-chan T\)](<#Stochastic[T].Compute>)
145+
- [func \(s \*Stochastic\[T\]\) IdlePeriod\(\) int](<#Stochastic[T].IdlePeriod>)
141146
- [type Tema](<#Tema>)
142147
- [func NewTema\[T helper.Number\]\(\) \*Tema\[T\]](<#NewTema>)
143148
- [func \(t \*Tema\[T\]\) Compute\(c \<\-chan T\) \<\-chan T](<#Tema[T].Compute>)
@@ -313,6 +318,18 @@ const (
313318
)
314319
```
315320

321+
<a name="DefaultStochasticPeriod"></a>
322+
323+
```go
324+
const (
325+
// DefaultStochasticPeriod is the default period for the Stochastic indicator.
326+
DefaultStochasticPeriod = 10
327+
328+
// DefaultStochasticSmaPeriod is the default period for the SMA of %K.
329+
DefaultStochasticSmaPeriod = 3
330+
)
331+
```
332+
316333
<a name="DefaultTsiFirstSmoothingPeriod"></a>
317334

318335
```go
@@ -1806,6 +1823,69 @@ func (s *Smma[T]) String() string
18061823

18071824
String is the string representation of the SMMA.
18081825

1826+
<a name="Stochastic"></a>
1827+
## type [Stochastic](<https://github.com/cinar/indicator/blob/master/trend/stochastic.go#L30-L36>)
1828+
1829+
Stochastic represents the configuration parameters for calculating the Stochastic indicator on a single input series. This is different from the Stochastic Oscillator which operates on high, low, and close. This generic version is useful for applying stochastic calculation to any series, such as MACD values in the Schaff Trend Cycle \(STC\).
1830+
1831+
```
1832+
K = (Value - Min(Value, period)) / (Max(Value, period) - Min(Value, period)) * 100
1833+
D = SMA(K, dPeriod)
1834+
```
1835+
1836+
Example:
1837+
1838+
```
1839+
s := trend.NewStochastic[float64]()
1840+
k, d := s.Compute(values)
1841+
```
1842+
1843+
```go
1844+
type Stochastic[T helper.Number] struct {
1845+
// Period is the period for the min/max calculation.
1846+
Period int
1847+
1848+
// Sma is the SMA instance for %D calculation.
1849+
Sma *Sma[T]
1850+
}
1851+
```
1852+
1853+
<a name="NewStochastic"></a>
1854+
### func [NewStochastic](<https://github.com/cinar/indicator/blob/master/trend/stochastic.go#L39>)
1855+
1856+
```go
1857+
func NewStochastic[T helper.Number]() *Stochastic[T]
1858+
```
1859+
1860+
NewStochastic function initializes a new Stochastic instance with the default parameters.
1861+
1862+
<a name="NewStochasticWithPeriod"></a>
1863+
### func [NewStochasticWithPeriod](<https://github.com/cinar/indicator/blob/master/trend/stochastic.go#L44>)
1864+
1865+
```go
1866+
func NewStochasticWithPeriod[T helper.Number](period int) *Stochastic[T]
1867+
```
1868+
1869+
NewStochasticWithPeriod function initializes a new Stochastic instance with the given period.
1870+
1871+
<a name="Stochastic[T].Compute"></a>
1872+
### func \(\*Stochastic\[T\]\) [Compute](<https://github.com/cinar/indicator/blob/master/trend/stochastic.go#L53>)
1873+
1874+
```go
1875+
func (s *Stochastic[T]) Compute(values <-chan T) (<-chan T, <-chan T)
1876+
```
1877+
1878+
Compute function takes a channel of numbers and computes the Stochastic indicator. Returns %K and %D.
1879+
1880+
<a name="Stochastic[T].IdlePeriod"></a>
1881+
### func \(\*Stochastic\[T\]\) [IdlePeriod](<https://github.com/cinar/indicator/blob/master/trend/stochastic.go#L87>)
1882+
1883+
```go
1884+
func (s *Stochastic[T]) IdlePeriod() int
1885+
```
1886+
1887+
IdlePeriod is the initial period that Stochastic won't yield any results.
1888+
18091889
<a name="Tema"></a>
18101890
## type [Tema](<https://github.com/cinar/indicator/blob/master/trend/tema.go#L18-L22>)
18111891

trend/stochastic.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright (c) 2021-2026 Onur Cinar.
2+
// The source code is provided under GNU AGPLv3 License.
3+
// https://github.com/cinar/indicator
4+
5+
package trend
6+
7+
import "github.com/cinar/indicator/v2/helper"
8+
9+
const (
10+
// DefaultStochasticPeriod is the default period for the Stochastic indicator.
11+
DefaultStochasticPeriod = 10
12+
13+
// DefaultStochasticSmaPeriod is the default period for the SMA of %K.
14+
DefaultStochasticSmaPeriod = 3
15+
)
16+
17+
// Stochastic represents the configuration parameters for calculating
18+
// the Stochastic indicator on a single input series. This is different
19+
// from the Stochastic Oscillator which operates on high, low, and close.
20+
// This generic version is useful for applying stochastic calculation to
21+
// any series, such as MACD values in the Schaff Trend Cycle (STC).
22+
//
23+
// K = (Value - Min(Value, period)) / (Max(Value, period) - Min(Value, period)) * 100
24+
// D = SMA(K, dPeriod)
25+
//
26+
// Example:
27+
//
28+
// s := trend.NewStochastic[float64]()
29+
// k, d := s.Compute(values)
30+
type Stochastic[T helper.Number] struct {
31+
// Period is the period for the min/max calculation.
32+
Period int
33+
34+
// Sma is the SMA instance for %D calculation.
35+
Sma *Sma[T]
36+
}
37+
38+
// NewStochastic function initializes a new Stochastic instance with the default parameters.
39+
func NewStochastic[T helper.Number]() *Stochastic[T] {
40+
return NewStochasticWithPeriod[T](DefaultStochasticPeriod)
41+
}
42+
43+
// NewStochasticWithPeriod function initializes a new Stochastic instance with the given period.
44+
func NewStochasticWithPeriod[T helper.Number](period int) *Stochastic[T] {
45+
return &Stochastic[T]{
46+
Period: period,
47+
Sma: NewSmaWithPeriod[T](DefaultStochasticSmaPeriod),
48+
}
49+
}
50+
51+
// Compute function takes a channel of numbers and computes the Stochastic indicator.
52+
// Returns %K and %D.
53+
func (s *Stochastic[T]) Compute(values <-chan T) (<-chan T, <-chan T) {
54+
movingMin := NewMovingMinWithPeriod[T](s.Period)
55+
movingMax := NewMovingMaxWithPeriod[T](s.Period)
56+
57+
values = helper.Buffered(values, s.Period)
58+
inputs := helper.Duplicate(values, 3)
59+
60+
lowestSplice := helper.Duplicate(
61+
movingMin.Compute(inputs[0]),
62+
2,
63+
)
64+
65+
highest := movingMax.Compute(inputs[1])
66+
67+
skipped := helper.Skip(inputs[2], movingMin.IdlePeriod())
68+
69+
kSplice := helper.Duplicate(
70+
helper.MultiplyBy(
71+
helper.Divide(
72+
helper.Subtract(skipped, lowestSplice[0]),
73+
helper.Subtract(highest, lowestSplice[1]),
74+
),
75+
100,
76+
),
77+
2,
78+
)
79+
80+
d := s.Sma.Compute(kSplice[0])
81+
kSplice[1] = helper.Skip(kSplice[1], s.Sma.IdlePeriod())
82+
83+
return kSplice[1], d
84+
}
85+
86+
// IdlePeriod is the initial period that Stochastic won't yield any results.
87+
func (s *Stochastic[T]) IdlePeriod() int {
88+
return s.Period + s.Sma.Period - 2
89+
}

trend/stochastic_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) 2021-2026 Onur Cinar.
2+
// The source code is provided under GNU AGPLv3 License.
3+
// https://github.com/cinar/indicator
4+
5+
package trend_test
6+
7+
import (
8+
"testing"
9+
10+
"github.com/cinar/indicator/v2/helper"
11+
"github.com/cinar/indicator/v2/trend"
12+
)
13+
14+
func TestStochastic(t *testing.T) {
15+
type Data struct {
16+
Value float64
17+
K float64
18+
D float64
19+
}
20+
21+
input, err := helper.ReadFromCsvFile[Data]("testdata/stochastic.csv")
22+
if err != nil {
23+
t.Fatal(err)
24+
}
25+
26+
inputs := helper.Duplicate(input, 3)
27+
values := helper.Map(inputs[0], func(d *Data) float64 { return d.Value })
28+
expectedK := helper.Map(inputs[1], func(d *Data) float64 { return d.K })
29+
expectedD := helper.Map(inputs[2], func(d *Data) float64 { return d.D })
30+
31+
s := trend.NewStochastic[float64]()
32+
actualK, actualD := s.Compute(values)
33+
actualK = helper.RoundDigits(actualK, 2)
34+
actualD = helper.RoundDigits(actualD, 2)
35+
36+
expectedK = helper.Skip(expectedK, s.IdlePeriod())
37+
expectedD = helper.Skip(expectedD, s.IdlePeriod())
38+
39+
err = helper.CheckEquals(actualK, expectedK, actualD, expectedD)
40+
if err != nil {
41+
t.Fatal(err)
42+
}
43+
}

trend/testdata/stochastic.csv

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Value,K,D
2+
111.00,100.00,100.00
3+
109.00,75.00,91.67
4+
112.00,100.00,91.67
5+
113.00,100.00,91.67
6+
115.00,100.00,100.00
7+
114.00,87.50,95.83
8+
116.00,100.00,95.83
9+
118.00,100.00,95.83
10+
117.00,88.89,96.30
11+
119.00,100.00,96.30
12+
120.00,100.00,96.30
13+
118.00,75.00,91.67
14+
121.00,100.00,91.67
15+
122.00,100.00,91.67
16+
120.00,75.00,91.67
17+
123.00,100.00,91.67
18+
125.00,100.00,91.67
19+
124.00,87.50,95.83
20+
126.00,100.00,95.83

0 commit comments

Comments
 (0)