-
Notifications
You must be signed in to change notification settings - Fork 250
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
bed49a8
commit 716e60b
Showing
11 changed files
with
332 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
--- | ||
title: Schaff Trend Cycle | ||
permalink: /indicators/Stc/ | ||
layout: default | ||
--- | ||
|
||
# {{ page.title }} | ||
|
||
Created by Doug Schaff, [Schaff Trend Cycle](https://www.investopedia.com/articles/forex/10/schaff-trend-cycle-indicator.asp) is a stochastic oscillator view of two converging/diverging exponential moving averages (a.k.a MACD). | ||
[[Discuss] :speech_balloon:]({{site.github.repository_url}}/discussions/570 "Community discussion about this indicator") | ||
|
||
![image]({{site.baseurl}}/assets/charts/Stc.png) | ||
|
||
```csharp | ||
// usage | ||
IEnumerable<StcResult> results = | ||
quotes.GetStc(cyclePeriods, fastPeriods, slowPeriods); | ||
``` | ||
|
||
## Parameters | ||
|
||
| name | type | notes | ||
| -- |-- |-- | ||
| `cyclePeriods` | int | Number of periods (`C`) for the Trend Cycle. Must be greater than or equal to 0. Default is 10. | ||
| `fastPeriods` | int | Number of periods (`F`) for the faster moving average. Must be greater than 0. Default is 23. | ||
| `slowPeriods` | int | Number of periods (`S`) for the slower moving average. Must be greater than `fastPeriods`. Default is 50. | ||
|
||
### Historical quotes requirements | ||
|
||
You must have at least `2×(S+C)` or `S+C+100` worth of `quotes`, whichever is more. Since this uses a smoothing technique, we recommend you use at least `S+C+250` data points prior to the intended usage date for better precision. | ||
|
||
`quotes` is an `IEnumerable<TQuote>` collection of historical price quotes. It should have a consistent frequency (day, hour, minute, etc). See [the Guide]({{site.baseurl}}/guide/#historical-quotes) for more information. | ||
|
||
## Response | ||
|
||
```csharp | ||
IEnumerable<StcResult> | ||
``` | ||
|
||
- This method returns a time series of all available indicator values for the `quotes` provided. | ||
- It always returns the same number of elements as there are in the historical quotes. | ||
- It does not return a single incremental indicator value. | ||
- The first `S+C` slow periods will have `null` values since there's not enough data to calculate. | ||
|
||
:hourglass: **Convergence Warning**: The first `S+C+250` periods will have decreasing magnitude, convergence-related precision errors that can be as high as ~5% deviation in indicator values for earlier periods. | ||
|
||
### StcResult | ||
|
||
| name | type | notes | ||
| -- |-- |-- | ||
| `Date` | DateTime | Date | ||
| `Stc` | decimal | Schaff Trend Cycle | ||
|
||
### Utilities | ||
|
||
- [.Find(lookupDate)]({{site.baseurl}}/utilities#find-indicator-result-by-date) | ||
- [.RemoveWarmupPeriods()]({{site.baseurl}}/utilities#remove-warmup-periods) | ||
- [.RemoveWarmupPeriods(qty)]({{site.baseurl}}/utilities#remove-warmup-periods) | ||
|
||
See [Utilities and Helpers]({{site.baseurl}}/utilities#utilities-for-indicator-results) for more information. | ||
|
||
## Example | ||
|
||
```csharp | ||
// fetch historical quotes from your feed (your method) | ||
IEnumerable<Quote> quotes = GetHistoryFromFeed("SPY"); | ||
|
||
// calculate STC(12,26,9) | ||
IEnumerable<StcResult> results = quotes.GetStc(10,23,50); | ||
``` |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
using System; | ||
|
||
namespace Skender.Stock.Indicators | ||
{ | ||
[Serializable] | ||
public class StcResult : ResultBase | ||
{ | ||
public decimal? Stc { get; set; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
|
||
namespace Skender.Stock.Indicators | ||
{ | ||
public static partial class Indicator | ||
{ | ||
// SCHAFF TREND CYCLE (STC) | ||
/// <include file='./info.xml' path='indicator/*' /> | ||
/// | ||
public static IEnumerable<StcResult> GetStc<TQuote>( | ||
this IEnumerable<TQuote> quotes, | ||
int cyclePeriods = 10, | ||
int fastPeriods = 23, | ||
int slowPeriods = 50) | ||
where TQuote : IQuote | ||
{ | ||
|
||
// sort quotes | ||
List<TQuote> quotesList = quotes.Sort(); | ||
|
||
// check parameter arguments | ||
ValidateStc(quotes, cyclePeriods, fastPeriods, slowPeriods); | ||
|
||
// get stochastic of macd | ||
IEnumerable<StochResult> stochMacd = quotes | ||
.GetMacd(fastPeriods, slowPeriods, 1) | ||
.Where(x => x.Macd != null) | ||
.Select(x => new Quote | ||
{ | ||
Date = x.Date, | ||
High = (decimal)x.Macd, | ||
Low = (decimal)x.Macd, | ||
Close = (decimal)x.Macd | ||
}) | ||
.GetStoch(cyclePeriods, 1, 3); | ||
|
||
// initialize results | ||
// to ensure same length as original quotes | ||
List<StcResult> results = new(quotesList.Count); | ||
|
||
for (int i = 0; i < slowPeriods - 1; i++) | ||
{ | ||
TQuote q = quotesList[i]; | ||
results.Add(new StcResult() { Date = q.Date }); | ||
} | ||
|
||
// add stoch results | ||
// TODO: see if List Add works faster | ||
results.AddRange( | ||
stochMacd | ||
.Select(x => new StcResult | ||
{ | ||
Date = x.Date, | ||
Stc = x.Oscillator | ||
})); | ||
|
||
return results; | ||
} | ||
|
||
|
||
// remove recommended periods | ||
/// <include file='../../_common/Results/info.xml' path='info/type[@name="Prune"]/*' /> | ||
/// | ||
public static IEnumerable<StcResult> RemoveWarmupPeriods( | ||
this IEnumerable<StcResult> results) | ||
{ | ||
int n = results | ||
.ToList() | ||
.FindIndex(x => x.Stc != null); | ||
|
||
return results.Remove(n + 250); | ||
} | ||
|
||
|
||
// parameter validation | ||
private static void ValidateStc<TQuote>( | ||
IEnumerable<TQuote> quotes, | ||
int cyclePeriods, | ||
int fastPeriods, | ||
int slowPeriods) | ||
where TQuote : IQuote | ||
{ | ||
|
||
// check parameter arguments | ||
if (cyclePeriods < 0) | ||
{ | ||
throw new ArgumentOutOfRangeException(nameof(cyclePeriods), cyclePeriods, | ||
"Trend Cycle periods must be greater than or equal to 0 for STC."); | ||
} | ||
|
||
if (fastPeriods <= 0) | ||
{ | ||
throw new ArgumentOutOfRangeException(nameof(fastPeriods), fastPeriods, | ||
"Fast periods must be greater than 0 for STC."); | ||
} | ||
|
||
if (slowPeriods <= fastPeriods) | ||
{ | ||
throw new ArgumentOutOfRangeException(nameof(slowPeriods), slowPeriods, | ||
"Slow periods must be greater than the fast period for STC."); | ||
} | ||
|
||
// check quotes | ||
int qtyHistory = quotes.Count(); | ||
int minHistory = Math.Max(2 * (slowPeriods + cyclePeriods), slowPeriods + cyclePeriods + 100); | ||
if (qtyHistory < minHistory) | ||
{ | ||
string message = "Insufficient quotes provided for STC. " + | ||
string.Format(EnglishCulture, | ||
"You provided {0} periods of quotes when at least {1} are required. " | ||
+ "Since this uses a smoothing technique, " | ||
+ "we recommend you use at least {2} data points prior to the intended " | ||
+ "usage date for better precision.", qtyHistory, minHistory, slowPeriods + 250); | ||
|
||
throw new BadQuotesException(nameof(quotes), message); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
|
||
<indicator> | ||
<summary> | ||
Schaff Trend Cycle is a stochastic oscillator view of two converging/diverging exponential moving averages. | ||
<para> | ||
See | ||
<see href="https://daveskender.github.io/Stock.Indicators/indicators/Stc/#content">documentation</see> | ||
for more information. | ||
</para> | ||
</summary> | ||
<typeparam name="TQuote">Configurable Quote type. See Guide for more information.</typeparam> | ||
<param name="quotes">Historical price quotes.</param> | ||
<param name="cyclePeriods">Number of periods for the Trend Cycle.</param> | ||
<param name="fastPeriods">Number of periods in the Fast EMA.</param> | ||
<param name="slowPeriods">Number of periods in the Slow EMA.</param> | ||
<returns>Time series of MACD values, including MACD, Signal, and Histogram.</returns> | ||
<exception cref="ArgumentOutOfRangeException">Invalid parameter value provided.</exception> | ||
<exception cref="BadQuotesException">Insufficient quotes provided.</exception> | ||
</indicator> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
using Skender.Stock.Indicators; | ||
|
||
namespace Internal.Tests | ||
{ | ||
[TestClass] | ||
public class Stc : TestBase | ||
{ | ||
|
||
[TestMethod] | ||
public void Standard() | ||
{ | ||
int cyclePeriods = 9; | ||
int fastPeriods = 12; | ||
int slowPeriods = 26; | ||
|
||
List<StcResult> results = | ||
quotes.GetStc(cyclePeriods, fastPeriods, slowPeriods) | ||
.ToList(); | ||
|
||
foreach (StcResult r in results) | ||
{ | ||
Console.WriteLine($"{r.Date:d},{r.Stc:N4}"); | ||
} | ||
|
||
// assertions | ||
|
||
// proper quantities | ||
// should always be the same number of results as there is quotes | ||
Assert.AreEqual(502, results.Count); | ||
Assert.AreEqual(467, results.Where(x => x.Stc != null).Count()); | ||
|
||
// sample values | ||
StcResult r34 = results[34]; | ||
Assert.IsNull(r34.Stc); | ||
|
||
StcResult r35 = results[35]; | ||
Assert.AreEqual(100m, r35.Stc); | ||
|
||
StcResult r49 = results[49]; | ||
Assert.AreEqual(0.8370m, Math.Round((decimal)r49.Stc, 4)); | ||
|
||
StcResult r249 = results[249]; | ||
Assert.AreEqual(27.7340m, Math.Round((decimal)r249.Stc, 4)); | ||
|
||
StcResult last = results.LastOrDefault(); | ||
Assert.AreEqual(19.2544m, Math.Round((decimal)last.Stc, 4)); | ||
} | ||
|
||
[TestMethod] | ||
public void BadData() | ||
{ | ||
IEnumerable<StcResult> r = badQuotes.GetStc(10, 23, 50); | ||
Assert.AreEqual(502, r.Count()); | ||
} | ||
|
||
[TestMethod] | ||
public void Removed() | ||
{ | ||
int cyclePeriods = 9; | ||
int fastPeriods = 12; | ||
int slowPeriods = 26; | ||
|
||
List<StcResult> results = | ||
quotes.GetStc(cyclePeriods, fastPeriods, slowPeriods) | ||
.RemoveWarmupPeriods() | ||
.ToList(); | ||
|
||
// assertions | ||
Assert.AreEqual(502 - (slowPeriods + cyclePeriods + 250), results.Count); | ||
|
||
StcResult last = results.LastOrDefault(); | ||
Assert.AreEqual(19.2544m, Math.Round((decimal)last.Stc, 4)); | ||
} | ||
|
||
[TestMethod] | ||
public void Exceptions() | ||
{ | ||
// bad fast period | ||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => | ||
Indicator.GetStc(quotes, 9, 0, 26)); | ||
|
||
// bad slow periods must be larger than faster period | ||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => | ||
Indicator.GetStc(quotes, 9, 12, 12)); | ||
|
||
// bad signal period | ||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => | ||
Indicator.GetStc(quotes, -1, 12, 26)); | ||
|
||
// insufficient quotes 2×(S+P) | ||
Assert.ThrowsException<BadQuotesException>(() => | ||
Indicator.GetStc(TestData.GetDefault(409), 5, 12, 200)); | ||
|
||
// insufficient quotes S+P+100 | ||
Assert.ThrowsException<BadQuotesException>(() => | ||
Indicator.GetStc(TestData.GetDefault(134), 9, 12, 26)); | ||
} | ||
} | ||
} |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters