Skip to content

Commit 7aca34b

Browse files
authored
Trend: Schaff Trend Cycle (STC) (#301)
# Describe Request Trend: Schaff Trend Cycle (STC). Fixed #53 # Change Type New indicator. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added Slow Stochastic indicator for trend analysis * Added Schaff Trend Cycle (STC) indicator for trend analysis * **Documentation** * Updated trend indicators reference with new indicator definitions <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 2312e55 commit 7aca34b

File tree

6 files changed

+530
-1
lines changed

6 files changed

+530
-1
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ The following list of indicators are currently supported by this package:
4949
- Parabolic SAR
5050
- [Random Index (KDJ)](trend/README.md#type-kdj)
5151
- [Stochastic](trend/README.md#type-stochastic)
52+
- [Slow Stochastic](trend/README.md#type-slowstochastic)
53+
- [Schaff Trend Cycle (STC)](trend/README.md#type-stc)
5254
- [Rolling Moving Average (RMA)](trend/README.md#type-rma)
5355
- [Simple Moving Average (SMA)](trend/README.md#type-sma)
5456
- [Since Change](helper/README.md#func-since)

trend/README.md

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ The information provided on this project is strictly for informational purposes
2828
- [type Apo](<#Apo>)
2929
- [func NewApo\[T helper.Number\]\(\) \*Apo\[T\]](<#NewApo>)
3030
- [func \(apo \*Apo\[T\]\) Compute\(c \<\-chan T\) \<\-chan T](<#Apo[T].Compute>)
31+
- [func \(apo \*Apo\[T\]\) IdlePeriod\(\) int](<#Apo[T].IdlePeriod>)
3132
- [type Aroon](<#Aroon>)
3233
- [func NewAroon\[T helper.Number\]\(\) \*Aroon\[T\]](<#NewAroon>)
3334
- [func \(a \*Aroon\[T\]\) Compute\(high, low \<\-chan T\) \(\<\-chan T, \<\-chan T\)](<#Aroon[T].Compute>)
@@ -126,6 +127,11 @@ The information provided on this project is strictly for informational purposes
126127
- [func \(r \*Roc\[T\]\) Compute\(values \<\-chan T\) \<\-chan T](<#Roc[T].Compute>)
127128
- [func \(r \*Roc\[T\]\) IdlePeriod\(\) int](<#Roc[T].IdlePeriod>)
128129
- [func \(r \*Roc\[T\]\) String\(\) string](<#Roc[T].String>)
130+
- [type SlowStochastic](<#SlowStochastic>)
131+
- [func NewSlowStochastic\[T helper.Number\]\(\) \*SlowStochastic\[T\]](<#NewSlowStochastic>)
132+
- [func NewSlowStochasticWithPeriod\[T helper.Number\]\(period, kPeriod, dPeriod int\) \*SlowStochastic\[T\]](<#NewSlowStochasticWithPeriod>)
133+
- [func \(s \*SlowStochastic\[T\]\) Compute\(values \<\-chan T\) \(\<\-chan T, \<\-chan T\)](<#SlowStochastic[T].Compute>)
134+
- [func \(s \*SlowStochastic\[T\]\) IdlePeriod\(\) int](<#SlowStochastic[T].IdlePeriod>)
129135
- [type Sma](<#Sma>)
130136
- [func NewSma\[T helper.Number\]\(\) \*Sma\[T\]](<#NewSma>)
131137
- [func NewSmaWithPeriod\[T helper.Number\]\(period int\) \*Sma\[T\]](<#NewSmaWithPeriod>)
@@ -138,6 +144,11 @@ The information provided on this project is strictly for informational purposes
138144
- [func \(s \*Smma\[T\]\) Compute\(c \<\-chan T\) \<\-chan T](<#Smma[T].Compute>)
139145
- [func \(s \*Smma\[T\]\) IdlePeriod\(\) int](<#Smma[T].IdlePeriod>)
140146
- [func \(s \*Smma\[T\]\) String\(\) string](<#Smma[T].String>)
147+
- [type Stc](<#Stc>)
148+
- [func NewStc\[T helper.Number\]\(\) \*Stc\[T\]](<#NewStc>)
149+
- [func NewStcWithPeriod\[T helper.Number\]\(fastPeriod, slowPeriod, kPeriod, dPeriod int\) \*Stc\[T\]](<#NewStcWithPeriod>)
150+
- [func \(s \*Stc\[T\]\) Compute\(c \<\-chan T\) \<\-chan T](<#Stc[T].Compute>)
151+
- [func \(s \*Stc\[T\]\) IdlePeriod\(\) int](<#Stc[T].IdlePeriod>)
141152
- [type Stochastic](<#Stochastic>)
142153
- [func NewStochastic\[T helper.Number\]\(\) \*Stochastic\[T\]](<#NewStochastic>)
143154
- [func NewStochasticWithPeriod\[T helper.Number\]\(period int\) \*Stochastic\[T\]](<#NewStochasticWithPeriod>)
@@ -318,6 +329,39 @@ const (
318329
)
319330
```
320331

332+
<a name="DefaultSlowStochasticPeriod"></a>
333+
334+
```go
335+
const (
336+
// DefaultSlowStochasticPeriod is the default period for the Slow Stochastic indicator.
337+
DefaultSlowStochasticPeriod = 10
338+
339+
// DefaultSlowStochasticKPeriod is the default period for the Fast %K SMA smoothing.
340+
DefaultSlowStochasticKPeriod = 3
341+
342+
// DefaultSlowStochasticDPeriod is the default period for the Slow %D SMA.
343+
DefaultSlowStochasticDPeriod = 3
344+
)
345+
```
346+
347+
<a name="DefaultStcFastPeriod"></a>
348+
349+
```go
350+
const (
351+
// DefaultStcFastPeriod is the default fast EMA period for STC.
352+
DefaultStcFastPeriod = 23
353+
354+
// DefaultStcSlowPeriod is the default slow EMA period for STC.
355+
DefaultStcSlowPeriod = 50
356+
357+
// DefaultStcKPeriod is the default period for the Stochastic %K.
358+
DefaultStcKPeriod = 10
359+
360+
// DefaultStcDPeriod is the default period for the Stochastic %D.
361+
DefaultStcDPeriod = 3
362+
)
363+
```
364+
321365
<a name="DefaultStochasticPeriod"></a>
322366

323367
```go
@@ -484,6 +528,15 @@ func (apo *Apo[T]) Compute(c <-chan T) <-chan T
484528

485529
Compute function takes a channel of numbers and computes the APO over the specified period.
486530

531+
<a name="Apo[T].IdlePeriod"></a>
532+
### func \(\*Apo\[T\]\) [IdlePeriod](<https://github.com/cinar/indicator/blob/master/trend/apo.go#L86>)
533+
534+
```go
535+
func (apo *Apo[T]) IdlePeriod() int
536+
```
537+
538+
IdlePeriod is the initial period that APO won't yield any results.
539+
487540
<a name="Aroon"></a>
488541
## type [Aroon](<https://github.com/cinar/indicator/blob/master/trend/aroon.go#L30-L33>)
489542

@@ -1686,6 +1739,73 @@ func (r *Roc[T]) String() string
16861739

16871740
String is the string representation of the ROC.
16881741

1742+
<a name="SlowStochastic"></a>
1743+
## type [SlowStochastic](<https://github.com/cinar/indicator/blob/master/trend/slow_stochastic.go#L32-L41>)
1744+
1745+
SlowStochastic represents the configuration parameters for calculating the Slow Stochastic indicator. This applies additional smoothing to the Fast Stochastic values.
1746+
1747+
```
1748+
Fast %K = Stochastic(values, period)
1749+
Slow %K = SMA(Fast %K, kPeriod)
1750+
Slow %D = SMA(Slow %K, dPeriod)
1751+
```
1752+
1753+
Example:
1754+
1755+
```
1756+
s := trend.NewSlowStochastic[float64]()
1757+
k, d := s.Compute(values)
1758+
```
1759+
1760+
```go
1761+
type SlowStochastic[T helper.Number] struct {
1762+
// Period is the period for the min/max calculation.
1763+
Period int
1764+
1765+
// KPeriod is the period for smoothing Fast %K to get Slow %K.
1766+
KPeriod int
1767+
1768+
// DPeriod is the period for smoothing Slow %K to get Slow %D.
1769+
DPeriod int
1770+
}
1771+
```
1772+
1773+
<a name="NewSlowStochastic"></a>
1774+
### func [NewSlowStochastic](<https://github.com/cinar/indicator/blob/master/trend/slow_stochastic.go#L44>)
1775+
1776+
```go
1777+
func NewSlowStochastic[T helper.Number]() *SlowStochastic[T]
1778+
```
1779+
1780+
NewSlowStochastic function initializes a new SlowStochastic instance with the default parameters.
1781+
1782+
<a name="NewSlowStochasticWithPeriod"></a>
1783+
### func [NewSlowStochasticWithPeriod](<https://github.com/cinar/indicator/blob/master/trend/slow_stochastic.go#L53>)
1784+
1785+
```go
1786+
func NewSlowStochasticWithPeriod[T helper.Number](period, kPeriod, dPeriod int) *SlowStochastic[T]
1787+
```
1788+
1789+
NewSlowStochasticWithPeriod function initializes a new SlowStochastic instance with the given periods.
1790+
1791+
<a name="SlowStochastic[T].Compute"></a>
1792+
### func \(\*SlowStochastic\[T\]\) [Compute](<https://github.com/cinar/indicator/blob/master/trend/slow_stochastic.go#L63>)
1793+
1794+
```go
1795+
func (s *SlowStochastic[T]) Compute(values <-chan T) (<-chan T, <-chan T)
1796+
```
1797+
1798+
Compute function takes a channel of numbers and computes the Slow Stochastic indicator. Returns Slow %K and Slow %D.
1799+
1800+
<a name="SlowStochastic[T].IdlePeriod"></a>
1801+
### func \(\*SlowStochastic\[T\]\) [IdlePeriod](<https://github.com/cinar/indicator/blob/master/trend/slow_stochastic.go#L101>)
1802+
1803+
```go
1804+
func (s *SlowStochastic[T]) IdlePeriod() int
1805+
```
1806+
1807+
IdlePeriod is the initial period that Slow Stochastic won't yield any results.
1808+
16891809
<a name="Sma"></a>
16901810
## type [Sma](<https://github.com/cinar/indicator/blob/master/trend/sma.go#L26-L29>)
16911811

@@ -1823,6 +1943,87 @@ func (s *Smma[T]) String() string
18231943

18241944
String is the string representation of the SMMA.
18251945

1946+
<a name="Stc"></a>
1947+
## type [Stc](<https://github.com/cinar/indicator/blob/master/trend/stc.go#L43-L61>)
1948+
1949+
Stc represents the configuration parameters for calculating the Schaff Trend Cycle \(STC\) indicator. It combines MACD with stochastic oscillators to identify trend direction and potential entry points.
1950+
1951+
```
1952+
EMA1 = EMA(values, fastPeriod)
1953+
EMA2 = EMA(values, slowPeriod)
1954+
MACD = EMA1 - EMA2
1955+
1956+
%K = Stochastic %K of MACD with kPeriod
1957+
%D = Stochastic %D of MACD with dPeriod
1958+
1959+
STC = 100 * (MACD - %K) / (%D - %K)
1960+
```
1961+
1962+
Example:
1963+
1964+
```
1965+
stc := trend.NewStc[float64]()
1966+
result := stc.Compute(closings)
1967+
```
1968+
1969+
```go
1970+
type Stc[T helper.Number] struct {
1971+
// FastPeriod is the period for the fast EMA.
1972+
FastPeriod int
1973+
1974+
// SlowPeriod is the period for the slow EMA.
1975+
SlowPeriod int
1976+
1977+
// KPeriod is the period for the Stochastic %K.
1978+
KPeriod int
1979+
1980+
// DPeriod is the period for the Stochastic %D.
1981+
DPeriod int
1982+
1983+
// Apo is the APO instance for MACD calculation.
1984+
Apo *Apo[T]
1985+
1986+
// Stochastic is the Stochastic instance.
1987+
Stochastic *Stochastic[T]
1988+
}
1989+
```
1990+
1991+
<a name="NewStc"></a>
1992+
### func [NewStc](<https://github.com/cinar/indicator/blob/master/trend/stc.go#L64>)
1993+
1994+
```go
1995+
func NewStc[T helper.Number]() *Stc[T]
1996+
```
1997+
1998+
NewStc function initializes a new STC instance with the default parameters.
1999+
2000+
<a name="NewStcWithPeriod"></a>
2001+
### func [NewStcWithPeriod](<https://github.com/cinar/indicator/blob/master/trend/stc.go#L74>)
2002+
2003+
```go
2004+
func NewStcWithPeriod[T helper.Number](fastPeriod, slowPeriod, kPeriod, dPeriod int) *Stc[T]
2005+
```
2006+
2007+
NewStcWithPeriod function initializes a new STC instance with the given periods.
2008+
2009+
<a name="Stc[T].Compute"></a>
2010+
### func \(\*Stc\[T\]\) [Compute](<https://github.com/cinar/indicator/blob/master/trend/stc.go#L93>)
2011+
2012+
```go
2013+
func (s *Stc[T]) Compute(c <-chan T) <-chan T
2014+
```
2015+
2016+
Compute function takes a channel of numbers and computes the STC indicator.
2017+
2018+
<a name="Stc[T].IdlePeriod"></a>
2019+
### func \(\*Stc\[T\]\) [IdlePeriod](<https://github.com/cinar/indicator/blob/master/trend/stc.go#L138>)
2020+
2021+
```go
2022+
func (s *Stc[T]) IdlePeriod() int
2023+
```
2024+
2025+
IdlePeriod is the initial period that STC won't yield any results.
2026+
18262027
<a name="Stochastic"></a>
18272028
## type [Stochastic](<https://github.com/cinar/indicator/blob/master/trend/stochastic.go#L30-L36>)
18282029

trend/apo.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,16 @@ func (apo *Apo[T]) Compute(c <-chan T) <-chan T {
7373
fastEma := NewEma[T]()
7474
fastEma.Period = apo.FastPeriod
7575
cs[0] = fastEma.Compute(cs[0])
76-
cs[0] = helper.Skip(cs[0], apo.SlowPeriod - apo.FastPeriod)
76+
cs[0] = helper.Skip(cs[0], apo.SlowPeriod-apo.FastPeriod)
7777

7878
slowEma := NewEma[T]()
7979
slowEma.Period = apo.SlowPeriod
8080
cs[1] = slowEma.Compute(cs[1])
8181

8282
return helper.Subtract(cs[0], cs[1])
8383
}
84+
85+
// IdlePeriod is the initial period that APO won't yield any results.
86+
func (apo *Apo[T]) IdlePeriod() int {
87+
return apo.SlowPeriod - 1
88+
}

trend/slow_stochastic.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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+
// DefaultSlowStochasticPeriod is the default period for the Slow Stochastic indicator.
11+
DefaultSlowStochasticPeriod = 10
12+
13+
// DefaultSlowStochasticKPeriod is the default period for the Fast %K SMA smoothing.
14+
DefaultSlowStochasticKPeriod = 3
15+
16+
// DefaultSlowStochasticDPeriod is the default period for the Slow %D SMA.
17+
DefaultSlowStochasticDPeriod = 3
18+
)
19+
20+
// SlowStochastic represents the configuration parameters for calculating
21+
// the Slow Stochastic indicator. This applies additional smoothing to the
22+
// Fast Stochastic values.
23+
//
24+
// Fast %K = Stochastic(values, period)
25+
// Slow %K = SMA(Fast %K, kPeriod)
26+
// Slow %D = SMA(Slow %K, dPeriod)
27+
//
28+
// Example:
29+
//
30+
// s := trend.NewSlowStochastic[float64]()
31+
// k, d := s.Compute(values)
32+
type SlowStochastic[T helper.Number] struct {
33+
// Period is the period for the min/max calculation.
34+
Period int
35+
36+
// KPeriod is the period for smoothing Fast %K to get Slow %K.
37+
KPeriod int
38+
39+
// DPeriod is the period for smoothing Slow %K to get Slow %D.
40+
DPeriod int
41+
}
42+
43+
// NewSlowStochastic function initializes a new SlowStochastic instance with the default parameters.
44+
func NewSlowStochastic[T helper.Number]() *SlowStochastic[T] {
45+
return &SlowStochastic[T]{
46+
Period: DefaultSlowStochasticPeriod,
47+
KPeriod: DefaultSlowStochasticKPeriod,
48+
DPeriod: DefaultSlowStochasticDPeriod,
49+
}
50+
}
51+
52+
// NewSlowStochasticWithPeriod function initializes a new SlowStochastic instance with the given periods.
53+
func NewSlowStochasticWithPeriod[T helper.Number](period, kPeriod, dPeriod int) *SlowStochastic[T] {
54+
return &SlowStochastic[T]{
55+
Period: period,
56+
KPeriod: kPeriod,
57+
DPeriod: dPeriod,
58+
}
59+
}
60+
61+
// Compute function takes a channel of numbers and computes the Slow Stochastic indicator.
62+
// Returns Slow %K and Slow %D.
63+
func (s *SlowStochastic[T]) Compute(values <-chan T) (<-chan T, <-chan T) {
64+
movingMin := NewMovingMinWithPeriod[T](s.Period)
65+
movingMax := NewMovingMaxWithPeriod[T](s.Period)
66+
67+
values = helper.Buffered(values, s.Period)
68+
inputs := helper.Duplicate(values, 3)
69+
70+
lowestSplice := helper.Duplicate(
71+
movingMin.Compute(inputs[0]),
72+
2,
73+
)
74+
75+
highest := movingMax.Compute(inputs[1])
76+
77+
skipped := helper.Skip(inputs[2], movingMin.IdlePeriod())
78+
79+
fastK := helper.MultiplyBy(
80+
helper.Divide(
81+
helper.Subtract(skipped, lowestSplice[0]),
82+
helper.Subtract(highest, lowestSplice[1]),
83+
),
84+
100,
85+
)
86+
87+
slowKSma := NewSmaWithPeriod[T](s.KPeriod)
88+
slowK := slowKSma.Compute(fastK)
89+
90+
slowKDuplicate := helper.Duplicate(slowK, 2)
91+
92+
slowDSma := NewSmaWithPeriod[T](s.DPeriod)
93+
slowD := slowDSma.Compute(slowKDuplicate[0])
94+
95+
slowKDuplicate[1] = helper.Skip(slowKDuplicate[1], s.DPeriod-1)
96+
97+
return slowKDuplicate[1], slowD
98+
}
99+
100+
// IdlePeriod is the initial period that Slow Stochastic won't yield any results.
101+
func (s *SlowStochastic[T]) IdlePeriod() int {
102+
return s.Period + s.KPeriod + s.DPeriod - 3
103+
}

0 commit comments

Comments
 (0)