-
Notifications
You must be signed in to change notification settings - Fork 246
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
ba81804
commit 41ffde8
Showing
5 changed files
with
291 additions
and
0 deletions.
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
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,14 @@ | ||
using System; | ||
|
||
namespace Skender.Stock.Indicators | ||
{ | ||
[Serializable] | ||
public class IchimokuResult : ResultBase | ||
{ | ||
public decimal? TenkanSen { get; set; } // conversion line | ||
public decimal? KijunSen { get; set; } // base line | ||
public decimal? SenkouSpanA { get; set; } // leading span A | ||
public decimal? SenkauSpanB { get; set; } // leading span B | ||
public decimal? ChikouSpan { get; set; } // lagging span | ||
} | ||
} |
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,139 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
|
||
namespace Skender.Stock.Indicators | ||
{ | ||
public static partial class Indicator | ||
{ | ||
// ICHIMOKU CLOUD | ||
public static IEnumerable<IchimokuResult> GetIchimoku(IEnumerable<Quote> history, | ||
int signalPeriod = 9, int shortSpanPeriod = 26, int longSpanPeriod = 52) | ||
{ | ||
|
||
// clean quotes | ||
history = Cleaners.PrepareHistory(history); | ||
|
||
// check parameters | ||
ValidateIchimoku(history, signalPeriod, shortSpanPeriod, longSpanPeriod); | ||
|
||
// initialize | ||
List<IchimokuResult> results = new List<IchimokuResult>(); | ||
|
||
// roll through history | ||
foreach (Quote h in history) | ||
{ | ||
|
||
IchimokuResult result = new IchimokuResult | ||
{ | ||
Index = (int)h.Index, | ||
Date = h.Date | ||
}; | ||
|
||
// tenkan-sen conversion line | ||
if (h.Index >= signalPeriod) | ||
{ | ||
|
||
List<Quote> tenkanPeriod = history | ||
.Where(x => x.Index > (h.Index - signalPeriod) && x.Index <= h.Index) | ||
.ToList(); | ||
|
||
decimal max = tenkanPeriod.Select(x => x.High).Max(); | ||
decimal min = tenkanPeriod.Select(x => x.Low).Min(); | ||
|
||
result.TenkanSen = (min + max) / 2; | ||
} | ||
|
||
// kijun-sen base line | ||
if (h.Index >= shortSpanPeriod) | ||
{ | ||
List<Quote> kijunPeriod = history | ||
.Where(x => x.Index > (h.Index - shortSpanPeriod) && x.Index <= h.Index) | ||
.ToList(); | ||
|
||
decimal max = kijunPeriod.Select(x => x.High).Max(); | ||
decimal min = kijunPeriod.Select(x => x.Low).Min(); | ||
|
||
result.KijunSen = (min + max) / 2; | ||
} | ||
|
||
// senkou span A | ||
if (h.Index >= 2 * shortSpanPeriod) | ||
{ | ||
|
||
IchimokuResult skq = results | ||
.Where(x => x.Index == h.Index - shortSpanPeriod) | ||
.FirstOrDefault(); | ||
|
||
if (skq != null && skq.TenkanSen != null && skq.KijunSen != null) | ||
{ | ||
result.SenkouSpanA = (skq.TenkanSen + skq.KijunSen) / 2; | ||
} | ||
} | ||
|
||
// senkou span B | ||
if (h.Index >= shortSpanPeriod + longSpanPeriod) | ||
{ | ||
List<Quote> senkauPeriod = history | ||
.Where(x => x.Index > (h.Index - shortSpanPeriod - longSpanPeriod) | ||
&& x.Index <= h.Index - shortSpanPeriod) | ||
.ToList(); | ||
|
||
decimal max = senkauPeriod.Select(x => x.High).Max(); | ||
decimal min = senkauPeriod.Select(x => x.Low).Min(); | ||
|
||
result.SenkauSpanB = (min + max) / 2; | ||
} | ||
|
||
// chikou line | ||
Quote chikouQuote = history | ||
.Where(x => x.Index == h.Index + shortSpanPeriod) | ||
.FirstOrDefault(); | ||
|
||
if (chikouQuote != null) | ||
{ | ||
result.ChikouSpan = chikouQuote.Close; | ||
} | ||
|
||
results.Add(result); | ||
} | ||
|
||
return results; | ||
} | ||
|
||
|
||
private static void ValidateIchimoku(IEnumerable<Quote> history, | ||
int signalPeriod, int shortSpanPeriod, int longSpanPeriod) | ||
{ | ||
|
||
// check parameters | ||
if (signalPeriod <= 0) | ||
{ | ||
throw new BadParameterException("Signal period must be greater than 0 for ICHIMOKU."); | ||
} | ||
|
||
if (shortSpanPeriod <= 0) | ||
{ | ||
throw new BadParameterException("Short span period must be greater than 0 for ICHIMOKU."); | ||
} | ||
|
||
if (longSpanPeriod <= shortSpanPeriod) | ||
{ | ||
throw new BadParameterException("Long span period must be greater than small span period for ICHIMOKU."); | ||
} | ||
|
||
// check history | ||
int qtyHistory = history.Count(); | ||
int minHistory = Math.Max(signalPeriod, Math.Max(shortSpanPeriod, longSpanPeriod)); | ||
if (qtyHistory < minHistory) | ||
{ | ||
throw new BadHistoryException("Insufficient history provided for ICHIMOKU. " + | ||
string.Format(cultureProvider, | ||
"You provided {0} periods of history when at least {1} is required.", | ||
qtyHistory, minHistory)); | ||
} | ||
|
||
} | ||
} | ||
|
||
} |
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,57 @@ | ||
# Ichimoku Cloud | ||
|
||
The Ichimoku Cloud, also known as Ichimoku Kinkō Hyō, is a collection of indicators that depict support and resistance, momentum, and trend direction. | ||
[More info ...](https://school.stockcharts.com/doku.php?id=technical_indicators:ichimoku_cloud) | ||
|
||
```csharp | ||
// usage | ||
IEnumerable<IchimokuResult> results = Indicator.GetIchimoku(history, lookbackPeriod); | ||
``` | ||
|
||
## Parameters | ||
|
||
| name | type | notes | ||
| -- |-- |-- | ||
| `history` | IEnumerable\<[Quote](../../GUIDE.md#quote)\> | Historical Quotes data should be at any consistent frequency (day, hour, minute, etc). You must supply at least the maximum of `N` or `S` or `L` periods of `history`; though, given the leading and lagging nature, we recommend notably more. | ||
| `signalPeriod` | int | Number of periods (`N`) in the Tenkan-sen midpoint evaluation. Must be greater than 0. Default is 9. | ||
| `shortSpanPeriod` | int | Number of periods (`S`) in the shorter Kijun-sen midpoint evaluation. It also sets the Chikou span lag/shift. Must be greater than 0. Default is 26. | ||
| `longSpanPeriod` | int | Number of periods (`L`) in the longer Senkou leading span B midpoint evaluation. Must be greater than `S`. Default is 52. | ||
|
||
## Response | ||
|
||
```csharp | ||
IEnumerable<IchimokuResult> | ||
``` | ||
|
||
The first `N-1`, `S-1`, and `L-1` periods will have various `null` values since there's not enough data to calculate. We always return the same number of elements as there are in the historical quotes. | ||
|
||
### IchimokuResult | ||
|
||
| name | type | notes | ||
| -- |-- |-- | ||
| `Index` | int | Sequence of dates | ||
| `Date` | DateTime | Date | ||
| `TenkanSen` | decimal | Conversion / signal line | ||
| `KijunSen` | decimal | Base line | ||
| `SenkouSpanA` | decimal | Leading span A | ||
| `SenkauSpanB` | decimal | Leading span B | ||
| `ChikouSpan` | decimal | Lagging span | ||
|
||
## Example | ||
|
||
```csharp | ||
// fetch historical quotes from your favorite feed, in Quote format | ||
IEnumerable<Quote> history = GetHistoryFromFeed("MSFT"); | ||
|
||
// calculate ICHIMOKU(9,26,52) | ||
IEnumerable<IchimokuResult> results = Indicator.GetIchimoku(history,9,26,52); | ||
|
||
// use results as needed | ||
DateTime evalDate = DateTime.Parse("12/31/2018"); | ||
IchimokuResult result = results.Where(x=>x.Date==evalDate).FirstOrDefault(); | ||
Console.WriteLine("Tenkan-sen on {0} was ${1}", result.Date, result.TenkanSen); | ||
``` | ||
|
||
```bash | ||
Tenkan-sen on 12/31/2018 was $241.26 | ||
``` |
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,80 @@ | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
using Skender.Stock.Indicators; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
|
||
namespace StockIndicators.Tests | ||
{ | ||
[TestClass] | ||
public class IchimokuTests : TestBase | ||
{ | ||
|
||
[TestMethod()] | ||
public void GetIchimokuTest() | ||
{ | ||
int signalPeriod = 9; | ||
int shortSpanPeriod = 26; | ||
int longSpanPeriod = 52; | ||
|
||
IEnumerable<IchimokuResult> results = Indicator.GetIchimoku( | ||
history, signalPeriod, shortSpanPeriod, longSpanPeriod); | ||
|
||
// assertions | ||
|
||
// proper quantities | ||
// should always be the same number of results as there is history | ||
Assert.AreEqual(502, results.Count()); | ||
Assert.AreEqual(494, results.Where(x => x.TenkanSen != null).Count()); | ||
Assert.AreEqual(477, results.Where(x => x.KijunSen != null).Count()); | ||
Assert.AreEqual(451, results.Where(x => x.SenkouSpanA != null).Count()); | ||
Assert.AreEqual(425, results.Where(x => x.SenkauSpanB != null).Count()); | ||
Assert.AreEqual(476, results.Where(x => x.ChikouSpan != null).Count()); | ||
|
||
// sample values | ||
IchimokuResult r1 = results.Where(x => x.Index == 476).FirstOrDefault(); | ||
Assert.AreEqual(265.575m, r1.TenkanSen); | ||
Assert.AreEqual(263.965m, r1.KijunSen); | ||
Assert.AreEqual(274.9475m, r1.SenkouSpanA); | ||
Assert.AreEqual(274.95m, r1.SenkauSpanB); | ||
Assert.AreEqual(245.28m, r1.ChikouSpan); | ||
|
||
IchimokuResult r2 = results.Where(x => x.Index == 502).FirstOrDefault(); | ||
Assert.AreEqual(241.26m, r2.TenkanSen); | ||
Assert.AreEqual(251.505m, r2.KijunSen); | ||
Assert.AreEqual(264.77m, r2.SenkouSpanA); | ||
Assert.AreEqual(269.82m, r2.SenkauSpanB); | ||
Assert.AreEqual(null, r2.ChikouSpan); | ||
} | ||
|
||
|
||
/* EXCEPTIONS */ | ||
|
||
[TestMethod()] | ||
[ExpectedException(typeof(BadParameterException), "Bad signal period.")] | ||
public void BadSignalPeriod() | ||
{ | ||
Indicator.GetIchimoku(history, 0); | ||
} | ||
|
||
[TestMethod()] | ||
[ExpectedException(typeof(BadParameterException), "Bad short span period.")] | ||
public void BadShortSpanPeriod() | ||
{ | ||
Indicator.GetIchimoku(history, 9, 0, 52); | ||
} | ||
|
||
[TestMethod()] | ||
[ExpectedException(typeof(BadParameterException), "Bad long span period.")] | ||
public void BadLongSpanPeriod() | ||
{ | ||
Indicator.GetIchimoku(history, 9, 26, 26); | ||
} | ||
|
||
[TestMethod()] | ||
[ExpectedException(typeof(BadHistoryException), "Insufficient history.")] | ||
public void InsufficientHistory() | ||
{ | ||
Indicator.GetIchimoku(history.Where(x => x.Index < 52), 9, 26, 52); | ||
} | ||
} | ||
} |