mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-15 09:08:04 +00:00
10480 lines
405 KiB
C#
10480 lines
405 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Collections.ObjectModel;
|
||
using System.Diagnostics;
|
||
using System.Diagnostics.CodeAnalysis;
|
||
using System.Globalization;
|
||
using System.Linq;
|
||
using System.Reflection;
|
||
using System.Resources;
|
||
using System.Runtime.CompilerServices;
|
||
using System.Runtime.Versioning;
|
||
|
||
[assembly: CompilationRelaxations(8)]
|
||
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
|
||
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
|
||
[assembly: CLSCompliant(true)]
|
||
[assembly: InternalsVisibleTo("Tests.Indicators")]
|
||
[assembly: InternalsVisibleTo("Tests.Performance")]
|
||
[assembly: TargetFramework(".NETCoreApp,Version=v10.0", FrameworkDisplayName = ".NET 10.0")]
|
||
[assembly: AssemblyCompany("Dave Skender")]
|
||
[assembly: AssemblyConfiguration("Release")]
|
||
[assembly: AssemblyCopyright("@2020 Dave Skender")]
|
||
[assembly: AssemblyDescription("Stock Indicators for .NET. Transform financial market price quotes into technical analysis indicators such as MACD, Stochastic RSI, Average True Range, Parabolic SAR, etc. Nothing more.")]
|
||
[assembly: AssemblyFileVersion("2.7.0.0")]
|
||
[assembly: AssemblyInformationalVersion("2.7.0-43+Branch.main.Sha.e4c40d7cc048936a44d34291729c7772537f65da.e4c40d7cc048936a44d34291729c7772537f65da")]
|
||
[assembly: AssemblyProduct("Stock Indicators for .NET")]
|
||
[assembly: AssemblyTitle("Skender.Stock.Indicators")]
|
||
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/DaveSkender/Stock.Indicators")]
|
||
[assembly: NeutralResourcesLanguage("en-US")]
|
||
[assembly: AssemblyVersion("2.7.0.0")]
|
||
[module: RefSafetyRules(11)]
|
||
namespace Skender.Stock.Indicators;
|
||
|
||
public static class Indicator
|
||
{
|
||
private static readonly CultureInfo invCulture = CultureInfo.InvariantCulture;
|
||
|
||
private static readonly Calendar invCalendar = invCulture.Calendar;
|
||
|
||
private static readonly CalendarWeekRule invCalendarWeekRule = invCulture.DateTimeFormat.CalendarWeekRule;
|
||
|
||
private static readonly DayOfWeek invFirstDayOfWeek = invCulture.DateTimeFormat.FirstDayOfWeek;
|
||
|
||
/// <summary>
|
||
/// Accumulation/Distribution Line (ADL) is a rolling accumulation of Chaikin Money Flow Volume.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Adl/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="smaPeriods">Optional. Number of periods in the moving average of ADL.</param><returns>Time series of ADL values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<AdlResult> GetAdl<TQuote>(this IEnumerable<TQuote> quotes, int? smaPeriods = null) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcAdl(smaPeriods);
|
||
}
|
||
|
||
internal static List<AdlResult> CalcAdl(this List<QuoteD> qdList, int? smaPeriods)
|
||
{
|
||
ValidateAdl(smaPeriods);
|
||
List<AdlResult> list = new List<AdlResult>(qdList.Count);
|
||
double num = 0.0;
|
||
checked
|
||
{
|
||
for (int i = 0; i < qdList.Count; i++)
|
||
{
|
||
QuoteD quoteD = qdList[i];
|
||
double num2 = ((quoteD.High == quoteD.Low) ? 0.0 : ((quoteD.Close - quoteD.Low - (quoteD.High - quoteD.Close)) / (quoteD.High - quoteD.Low)));
|
||
double num3 = num2 * quoteD.Volume;
|
||
double num4 = num3 + num;
|
||
AdlResult adlResult = new AdlResult(quoteD.Date)
|
||
{
|
||
MoneyFlowMultiplier = num2,
|
||
MoneyFlowVolume = num3,
|
||
Adl = num4
|
||
};
|
||
list.Add(adlResult);
|
||
num = num4;
|
||
if (smaPeriods.HasValue && i + 1 >= smaPeriods)
|
||
{
|
||
double? num5 = 0.0;
|
||
for (int j = i + 1 - smaPeriods.Value; j <= i; j++)
|
||
{
|
||
num5 += list[j].Adl;
|
||
}
|
||
adlResult.AdlSma = num5 / (double?)smaPeriods;
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateAdl(int? smaPeriods)
|
||
{
|
||
if (smaPeriods.HasValue && smaPeriods.GetValueOrDefault() <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("smaPeriods", smaPeriods, "SMA periods must be greater than 0 for ADL.");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Directional Movement Index (DMI) and Average Directional Movement Index (ADX) is a measure of price directional movement.
|
||
/// It includes upward and downward indicators, and is often used to measure strength of trend.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Adx/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><returns>Time series of ADX and Plus/Minus Directional values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<AdxResult> GetAdx<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods = 14) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcAdx(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<AdxResult> CalcAdx(this List<QuoteD> qdList, int lookbackPeriods)
|
||
{
|
||
ValidateAdx(lookbackPeriods);
|
||
int count = qdList.Count;
|
||
List<AdxResult> list = new List<AdxResult>(count);
|
||
double num = 0.0;
|
||
double num2 = 0.0;
|
||
double num3 = 0.0;
|
||
double num4 = 0.0;
|
||
double num5 = 0.0;
|
||
double num6 = 0.0;
|
||
double num7 = 0.0;
|
||
double num8 = 0.0;
|
||
double num9 = 0.0;
|
||
double num10 = 0.0;
|
||
double num11 = 0.0;
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
QuoteD quoteD = qdList[i];
|
||
AdxResult adxResult = new AdxResult(quoteD.Date);
|
||
list.Add(adxResult);
|
||
if (i == 0)
|
||
{
|
||
num = quoteD.High;
|
||
num2 = quoteD.Low;
|
||
num3 = quoteD.Close;
|
||
continue;
|
||
}
|
||
double val = Math.Abs(quoteD.High - num3);
|
||
double val2 = Math.Abs(quoteD.Low - num3);
|
||
double num12 = quoteD.High - num;
|
||
double num13 = num2 - quoteD.Low;
|
||
double num14 = Math.Max(quoteD.High - quoteD.Low, Math.Max(val, val2));
|
||
double num15 = ((num12 > num13) ? Math.Max(num12, 0.0) : 0.0);
|
||
double num16 = ((num13 > num12) ? Math.Max(num13, 0.0) : 0.0);
|
||
num = quoteD.High;
|
||
num2 = quoteD.Low;
|
||
num3 = quoteD.Close;
|
||
if (i <= lookbackPeriods)
|
||
{
|
||
num8 += num14;
|
||
num9 += num15;
|
||
num10 += num16;
|
||
}
|
||
if (i < lookbackPeriods)
|
||
{
|
||
continue;
|
||
}
|
||
double num17;
|
||
double num18;
|
||
double num19;
|
||
if (i == lookbackPeriods)
|
||
{
|
||
num17 = num8;
|
||
num18 = num9;
|
||
num19 = num10;
|
||
}
|
||
else
|
||
{
|
||
num17 = num4 - num4 / (double)lookbackPeriods + num14;
|
||
num18 = num5 - num5 / (double)lookbackPeriods + num15;
|
||
num19 = num6 - num6 / (double)lookbackPeriods + num16;
|
||
}
|
||
num4 = num17;
|
||
num5 = num18;
|
||
num6 = num19;
|
||
if (num17 != 0.0)
|
||
{
|
||
double num20 = 100.0 * num18 / num17;
|
||
double num21 = 100.0 * num19 / num17;
|
||
adxResult.Pdi = num20;
|
||
adxResult.Mdi = num21;
|
||
double num22 = ((num20 == num21) ? 0.0 : ((num20 + num21 != 0.0) ? (100.0 * Math.Abs(num20 - num21) / (num20 + num21)) : double.NaN));
|
||
if (i > 2 * lookbackPeriods - 1)
|
||
{
|
||
double num23 = (num7 * (double)(lookbackPeriods - 1) + num22) / (double)lookbackPeriods;
|
||
adxResult.Adx = num23.NaN2Null();
|
||
adxResult.Adxr = (num23 + list[i + 1 - lookbackPeriods].Adx).NaN2Null() / 2.0;
|
||
num7 = num23;
|
||
}
|
||
else if (i == 2 * lookbackPeriods - 1)
|
||
{
|
||
num11 += num22;
|
||
double num23 = num11 / (double)lookbackPeriods;
|
||
adxResult.Adx = num23.NaN2Null();
|
||
num7 = num23;
|
||
}
|
||
else
|
||
{
|
||
num11 += num22;
|
||
}
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateAdx(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 1)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 1 for ADX.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<AdxResult> RemoveWarmupPeriods(this IEnumerable<AdxResult> results)
|
||
{
|
||
int num = results.ToList().FindIndex((AdxResult x) => x.Pdi.HasValue);
|
||
return results.Remove(checked(2 * num + 100));
|
||
}
|
||
|
||
/// <summary>
|
||
/// Williams Alligator is an indicator that transposes multiple moving averages,
|
||
/// showing chart patterns that creator Bill Williams compared to an alligator's
|
||
/// feeding habits when describing market movement.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Alligator/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="jawPeriods">Lookback periods for the Jaw line.</param><param name="jawOffset">Offset periods for the Jaw line.</param><param name="teethPeriods">Lookback periods for the Teeth line.</param><param name="teethOffset">Offset periods for the Teeth line.</param><param name="lipsPeriods">Lookback periods for the Lips line.</param><param name="lipsOffset">Offset periods for the Lips line.</param><returns>Time series of Alligator values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<AlligatorResult> GetAlligator<TQuote>(this IEnumerable<TQuote> quotes, int jawPeriods = 13, int jawOffset = 8, int teethPeriods = 8, int teethOffset = 5, int lipsPeriods = 5, int lipsOffset = 3) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.HL2).CalcAlligator(jawPeriods, jawOffset, teethPeriods, teethOffset, lipsPeriods, lipsOffset);
|
||
}
|
||
|
||
public static IEnumerable<AlligatorResult> GetAlligator(this IEnumerable<IReusableResult> results, int jawPeriods = 13, int jawOffset = 8, int teethPeriods = 8, int teethOffset = 5, int lipsPeriods = 5, int lipsOffset = 3)
|
||
{
|
||
return results.ToTuple().CalcAlligator(jawPeriods, jawOffset, teethPeriods, teethOffset, lipsPeriods, lipsOffset).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<AlligatorResult> GetAlligator(this IEnumerable<(DateTime, double)> priceTuples, int jawPeriods = 13, int jawOffset = 8, int teethPeriods = 8, int teethOffset = 5, int lipsPeriods = 5, int lipsOffset = 3)
|
||
{
|
||
return priceTuples.ToSortedList().CalcAlligator(jawPeriods, jawOffset, teethPeriods, teethOffset, lipsPeriods, lipsOffset);
|
||
}
|
||
|
||
internal static List<AlligatorResult> CalcAlligator(this List<(DateTime Date, double Value)> tpList, int jawPeriods, int jawOffset, int teethPeriods, int teethOffset, int lipsPeriods, int lipsOffset)
|
||
{
|
||
ValidateAlligator(jawPeriods, jawOffset, teethPeriods, teethOffset, lipsPeriods, lipsOffset);
|
||
int count = tpList.Count;
|
||
double[] array = new double[count];
|
||
List<AlligatorResult> list = tpList.Select(((DateTime Date, double Value) x) => new AlligatorResult(x.Date)).ToList();
|
||
checked
|
||
{
|
||
for (int num = 0; num < count; num++)
|
||
{
|
||
double item = tpList[num].Value;
|
||
array[num] = item;
|
||
if (num + jawOffset < count)
|
||
{
|
||
AlligatorResult alligatorResult = list[num + jawOffset];
|
||
if (num + 1 == jawPeriods)
|
||
{
|
||
double num2 = 0.0;
|
||
for (int num3 = num + 1 - jawPeriods; num3 <= num; num3++)
|
||
{
|
||
num2 += array[num3];
|
||
}
|
||
alligatorResult.Jaw = num2 / (double)jawPeriods;
|
||
}
|
||
else if (num + 1 > jawPeriods)
|
||
{
|
||
alligatorResult.Jaw = (list[num + jawOffset - 1].Jaw * (double)(jawPeriods - 1) + array[num]) / (double)jawPeriods;
|
||
}
|
||
alligatorResult.Jaw = alligatorResult.Jaw.NaN2Null();
|
||
}
|
||
if (num + teethOffset < count)
|
||
{
|
||
AlligatorResult alligatorResult2 = list[num + teethOffset];
|
||
if (num + 1 == teethPeriods)
|
||
{
|
||
double num4 = 0.0;
|
||
for (int num5 = num + 1 - teethPeriods; num5 <= num; num5++)
|
||
{
|
||
num4 += array[num5];
|
||
}
|
||
alligatorResult2.Teeth = num4 / (double)teethPeriods;
|
||
}
|
||
else if (num + 1 > teethPeriods)
|
||
{
|
||
alligatorResult2.Teeth = (list[num + teethOffset - 1].Teeth * (double)(teethPeriods - 1) + array[num]) / (double)teethPeriods;
|
||
}
|
||
alligatorResult2.Teeth = alligatorResult2.Teeth.NaN2Null();
|
||
}
|
||
if (num + lipsOffset >= count)
|
||
{
|
||
continue;
|
||
}
|
||
AlligatorResult alligatorResult3 = list[num + lipsOffset];
|
||
if (num + 1 == lipsPeriods)
|
||
{
|
||
double num6 = 0.0;
|
||
for (int num7 = num + 1 - lipsPeriods; num7 <= num; num7++)
|
||
{
|
||
num6 += array[num7];
|
||
}
|
||
alligatorResult3.Lips = num6 / (double)lipsPeriods;
|
||
}
|
||
else if (num + 1 > lipsPeriods)
|
||
{
|
||
alligatorResult3.Lips = (list[num + lipsOffset - 1].Lips * (double)(lipsPeriods - 1) + array[num]) / (double)lipsPeriods;
|
||
}
|
||
alligatorResult3.Lips = alligatorResult3.Lips.NaN2Null();
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateAlligator(int jawPeriods, int jawOffset, int teethPeriods, int teethOffset, int lipsPeriods, int lipsOffset)
|
||
{
|
||
if (jawPeriods <= teethPeriods)
|
||
{
|
||
throw new ArgumentOutOfRangeException("jawPeriods", jawPeriods, "Jaw lookback periods must be greater than Teeth lookback periods for Alligator.");
|
||
}
|
||
if (teethPeriods <= lipsPeriods)
|
||
{
|
||
throw new ArgumentOutOfRangeException("teethPeriods", teethPeriods, "Teeth lookback periods must be greater than Lips lookback periods for Alligator.");
|
||
}
|
||
if (lipsPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lipsPeriods", lipsPeriods, "Lips lookback periods must be greater than 0 for Alligator.");
|
||
}
|
||
if (jawOffset <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("jawOffset", jawOffset, "Jaw offset periods must be greater than 0 for Alligator.");
|
||
}
|
||
if (teethOffset <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("teethOffset", teethOffset, "Jaw offset periods must be greater than 0 for Alligator.");
|
||
}
|
||
if (lipsOffset <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lipsOffset", lipsOffset, "Jaw offset periods must be greater than 0 for Alligator.");
|
||
}
|
||
checked
|
||
{
|
||
if (jawPeriods + jawOffset <= teethPeriods + teethOffset)
|
||
{
|
||
throw new ArgumentOutOfRangeException("jawPeriods", jawPeriods, "Jaw lookback + offset are too small for Alligator.");
|
||
}
|
||
if (teethPeriods + teethOffset <= lipsPeriods + lipsOffset)
|
||
{
|
||
throw new ArgumentOutOfRangeException("teethPeriods", teethPeriods, "Teeth lookback + offset are too small for Alligator.");
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes non-essential records containing null values with unique consideration for
|
||
/// this indicator. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#condense?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator results to evaluate.</param><returns>Time series of
|
||
/// indicator results, condensed.</returns>
|
||
public static IEnumerable<AlligatorResult> Condense(this IEnumerable<AlligatorResult> results)
|
||
{
|
||
List<AlligatorResult> list = results.ToList();
|
||
list.RemoveAll((AlligatorResult x) => !x.Jaw.HasValue && !x.Teeth.HasValue && !x.Lips.HasValue);
|
||
return list.ToSortedList();
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<AlligatorResult> RemoveWarmupPeriods(this IEnumerable<AlligatorResult> results)
|
||
{
|
||
int removePeriods = checked(results.ToList().FindIndex((AlligatorResult x) => x.Jaw.HasValue) + 251);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Arnaud Legoux Moving Average (ALMA) is a Gaussian distribution
|
||
/// weighted moving average of price over a lookback window.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Alma/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><param name="offset">Adjusts smoothness versus responsiveness.</param><param name="sigma">Defines the width of the Gaussian normal distribution.</param><returns>Time series of ALMA values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<AlmaResult> GetAlma<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods = 9, double offset = 0.85, double sigma = 6.0) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcAlma(lookbackPeriods, offset, sigma);
|
||
}
|
||
|
||
public static IEnumerable<AlmaResult> GetAlma(this IEnumerable<IReusableResult> results, int lookbackPeriods = 9, double offset = 0.85, double sigma = 6.0)
|
||
{
|
||
return results.ToTuple().CalcAlma(lookbackPeriods, offset, sigma).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<AlmaResult> GetAlma(this IEnumerable<(DateTime, double)> priceTuples, int lookbackPeriods = 9, double offset = 0.85, double sigma = 6.0)
|
||
{
|
||
return priceTuples.ToSortedList().CalcAlma(lookbackPeriods, offset, sigma);
|
||
}
|
||
|
||
internal static List<AlmaResult> CalcAlma(this List<(DateTime, double)> tpList, int lookbackPeriods, double offset, double sigma)
|
||
{
|
||
ValidateAlma(lookbackPeriods, offset, sigma);
|
||
List<AlmaResult> list = new List<AlmaResult>(tpList.Count);
|
||
checked
|
||
{
|
||
double num = offset * (double)(lookbackPeriods - 1);
|
||
double num2 = (double)lookbackPeriods / sigma;
|
||
double[] array = new double[lookbackPeriods];
|
||
double num3 = 0.0;
|
||
for (int i = 0; i < lookbackPeriods; i++)
|
||
{
|
||
num3 += (array[i] = Math.Exp((0.0 - ((double)i - num) * ((double)i - num)) / (2.0 * num2 * num2)));
|
||
}
|
||
for (int j = 0; j < tpList.Count; j++)
|
||
{
|
||
AlmaResult almaResult = new AlmaResult(tpList[j].Item1);
|
||
list.Add(almaResult);
|
||
if (j + 1 >= lookbackPeriods)
|
||
{
|
||
double? num4 = 0.0;
|
||
int num5 = 0;
|
||
for (int k = j + 1 - lookbackPeriods; k <= j; k++)
|
||
{
|
||
double item = tpList[k].Item2;
|
||
num4 += array[num5] * item;
|
||
num5++;
|
||
}
|
||
almaResult.Alma = (num4 / num3).NaN2Null();
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateAlma(int lookbackPeriods, double offset, double sigma)
|
||
{
|
||
if (lookbackPeriods <= 1)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 1 for ALMA.");
|
||
}
|
||
if ((offset < 0.0 || offset > 1.0) ? true : false)
|
||
{
|
||
throw new ArgumentOutOfRangeException("offset", offset, "Offset must be between 0 and 1 for ALMA.");
|
||
}
|
||
if (sigma <= 0.0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("sigma", sigma, "Sigma must be greater than 0 for ALMA.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<AlmaResult> RemoveWarmupPeriods(this IEnumerable<AlmaResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((AlmaResult x) => x.Alma.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Aroon is a simple oscillator view of how long the new high or low price occured over a lookback window.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Aroon/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><returns>Time series of Aroon Up/Down and Oscillator values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<AroonResult> GetAroon<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods = 25) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcAroon(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<AroonResult> CalcAroon(this List<QuoteD> qdList, int lookbackPeriods)
|
||
{
|
||
ValidateAroon(lookbackPeriods);
|
||
List<AroonResult> list = new List<AroonResult>(qdList.Count);
|
||
checked
|
||
{
|
||
for (int i = 0; i < qdList.Count; i++)
|
||
{
|
||
AroonResult aroonResult = new AroonResult(qdList[i].Date);
|
||
list.Add(aroonResult);
|
||
if (i + 1 <= lookbackPeriods)
|
||
{
|
||
continue;
|
||
}
|
||
double? num = 0.0;
|
||
double? num2 = double.MaxValue;
|
||
int num3 = 0;
|
||
int num4 = 0;
|
||
for (int j = i + 1 - lookbackPeriods - 1; j <= i; j++)
|
||
{
|
||
QuoteD quoteD = qdList[j];
|
||
if (quoteD.High > num)
|
||
{
|
||
num = quoteD.High;
|
||
num3 = j + 1;
|
||
}
|
||
if (quoteD.Low < num2)
|
||
{
|
||
num2 = quoteD.Low;
|
||
num4 = j + 1;
|
||
}
|
||
}
|
||
aroonResult.AroonUp = 100.0 * (double)(lookbackPeriods - (i + 1 - num3)) / (double)lookbackPeriods;
|
||
aroonResult.AroonDown = 100.0 * (double)(lookbackPeriods - (i + 1 - num4)) / (double)lookbackPeriods;
|
||
aroonResult.Oscillator = aroonResult.AroonUp - aroonResult.AroonDown;
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateAroon(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for Aroon.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<AroonResult> RemoveWarmupPeriods(this IEnumerable<AroonResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((AroonResult x) => x.Oscillator.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Average True Range (ATR) is a measure of volatility that captures gaps and limits between periods.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Atr/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><returns>Time series of ATR values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<AtrResult> GetAtr<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods = 14) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcAtr(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<AtrResult> CalcAtr(this List<QuoteD> qdList, int lookbackPeriods)
|
||
{
|
||
ValidateAtr(lookbackPeriods);
|
||
List<AtrResult> list = new List<AtrResult>(qdList.Count);
|
||
double num = double.NaN;
|
||
double num2 = double.NaN;
|
||
double num3 = 0.0;
|
||
checked
|
||
{
|
||
for (int i = 0; i < qdList.Count; i++)
|
||
{
|
||
QuoteD quoteD = qdList[i];
|
||
AtrResult atrResult = new AtrResult(quoteD.Date);
|
||
list.Add(atrResult);
|
||
if (i > 0)
|
||
{
|
||
double val = Math.Abs(quoteD.High - num2);
|
||
double val2 = Math.Abs(quoteD.Low - num2);
|
||
double num4 = Math.Max(quoteD.High - quoteD.Low, Math.Max(val, val2));
|
||
atrResult.Tr = num4;
|
||
if (i > lookbackPeriods)
|
||
{
|
||
double num5 = (num * (double)(lookbackPeriods - 1) + num4) / (double)lookbackPeriods;
|
||
atrResult.Atr = num5;
|
||
atrResult.Atrp = ((quoteD.Close == 0.0) ? ((double?)null) : new double?(num5 / quoteD.Close * 100.0));
|
||
num = num5;
|
||
}
|
||
else if (i == lookbackPeriods)
|
||
{
|
||
num3 += num4;
|
||
double num6 = num3 / (double)lookbackPeriods;
|
||
atrResult.Atr = num6;
|
||
atrResult.Atrp = ((quoteD.Close == 0.0) ? ((double?)null) : new double?(num6 / quoteD.Close * 100.0));
|
||
num = num6;
|
||
}
|
||
else
|
||
{
|
||
num3 += num4;
|
||
}
|
||
num2 = quoteD.Close;
|
||
}
|
||
else
|
||
{
|
||
num2 = quoteD.Close;
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateAtr(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 1)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 1 for Average True Range.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<AtrResult> RemoveWarmupPeriods(this IEnumerable<AtrResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((AtrResult x) => x.Atr.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// ATR Trailing Stop attempts to determine the primary trend of prices by using
|
||
/// Average True Range (ATR) band thresholds. It can indicate a buy/sell signal or a
|
||
/// trailing stop when the trend changes.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/AtrStop/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods for ATR.</param><param name="multiplier">Multiplier sets the ATR band width.</param><param name="endType">Sets basis for stop offsets (Close or High/Low).</param><returns>Time series of ATR Trailing Stop values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<AtrStopResult> GetAtrStop<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods = 21, double multiplier = 3.0, EndType endType = EndType.Close) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcAtrStop(lookbackPeriods, multiplier, endType);
|
||
}
|
||
|
||
internal static List<AtrStopResult> CalcAtrStop(this List<QuoteD> qdList, int lookbackPeriods, double multiplier, EndType endType)
|
||
{
|
||
ValidateAtrStop(lookbackPeriods, multiplier);
|
||
List<AtrStopResult> list = new List<AtrStopResult>(qdList.Count);
|
||
List<AtrResult> list2 = qdList.CalcAtr(lookbackPeriods);
|
||
bool flag = true;
|
||
double? num = null;
|
||
double? num2 = null;
|
||
checked
|
||
{
|
||
for (int i = 0; i < qdList.Count; i++)
|
||
{
|
||
QuoteD quoteD = qdList[i];
|
||
AtrStopResult atrStopResult = new AtrStopResult(quoteD.Date);
|
||
list.Add(atrStopResult);
|
||
if (i >= lookbackPeriods)
|
||
{
|
||
double? atr = list2[i].Atr;
|
||
QuoteD quoteD2 = qdList[i - 1];
|
||
double? num3;
|
||
double? num4;
|
||
if (endType == EndType.Close)
|
||
{
|
||
num3 = quoteD.Close + multiplier * atr;
|
||
num4 = quoteD.Close - multiplier * atr;
|
||
}
|
||
else
|
||
{
|
||
num3 = quoteD.High + multiplier * atr;
|
||
num4 = quoteD.Low - multiplier * atr;
|
||
}
|
||
if (i == lookbackPeriods)
|
||
{
|
||
flag = quoteD.Close >= quoteD2.Close;
|
||
num = num3;
|
||
num2 = num4;
|
||
}
|
||
if (num3 < num || quoteD2.Close > num)
|
||
{
|
||
num = num3;
|
||
}
|
||
if (num4 > num2 || quoteD2.Close < num2)
|
||
{
|
||
num2 = num4;
|
||
}
|
||
if (quoteD.Close <= (flag ? num2 : num))
|
||
{
|
||
atrStopResult.AtrStop = (decimal?)num;
|
||
atrStopResult.BuyStop = (decimal?)num;
|
||
flag = false;
|
||
}
|
||
else
|
||
{
|
||
atrStopResult.AtrStop = (decimal?)num2;
|
||
atrStopResult.SellStop = (decimal?)num2;
|
||
flag = true;
|
||
}
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateAtrStop(int lookbackPeriods, double multiplier)
|
||
{
|
||
if (lookbackPeriods <= 1)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 1 for ATR Trailing Stop.");
|
||
}
|
||
if (multiplier <= 0.0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("multiplier", multiplier, "Multiplier must be greater than 0 for ATR Trailing Stop.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes non-essential records containing null values with unique consideration for
|
||
/// this indicator. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#condense?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator results to evaluate.</param><returns>Time series of
|
||
/// indicator results, condensed.</returns>
|
||
public static IEnumerable<AtrStopResult> Condense(this IEnumerable<AtrStopResult> results)
|
||
{
|
||
List<AtrStopResult> list = results.ToList();
|
||
list.RemoveAll((AtrStopResult x) => !x.AtrStop.HasValue);
|
||
return list.ToSortedList();
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<AtrStopResult> RemoveWarmupPeriods(this IEnumerable<AtrStopResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((AtrStopResult x) => x.AtrStop.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Awesome Oscillator (aka Super AO) is a measure of the gap between a fast and slow period modified moving average.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Awesome/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="fastPeriods">Number of periods in the Fast moving average.</param><param name="slowPeriods">Number of periods in the Slow moving average.</param><returns>Time series of Awesome Oscillator values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<AwesomeResult> GetAwesome<TQuote>(this IEnumerable<TQuote> quotes, int fastPeriods = 5, int slowPeriods = 34) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.HL2).CalcAwesome(fastPeriods, slowPeriods);
|
||
}
|
||
|
||
public static IEnumerable<AwesomeResult> GetAwesome(this IEnumerable<IReusableResult> results, int fastPeriods = 5, int slowPeriods = 34)
|
||
{
|
||
return results.ToTuple().CalcAwesome(fastPeriods, slowPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<AwesomeResult> GetAwesome(this IEnumerable<(DateTime, double)> priceTuples, int fastPeriods = 5, int slowPeriods = 34)
|
||
{
|
||
return priceTuples.ToSortedList().CalcAwesome(fastPeriods, slowPeriods);
|
||
}
|
||
|
||
internal static List<AwesomeResult> CalcAwesome(this List<(DateTime, double)> tpList, int fastPeriods, int slowPeriods)
|
||
{
|
||
ValidateAwesome(fastPeriods, slowPeriods);
|
||
int count = tpList.Count;
|
||
List<AwesomeResult> list = new List<AwesomeResult>(count);
|
||
double[] array = new double[count];
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
var (date, num) = tpList[i];
|
||
array[i] = num;
|
||
AwesomeResult awesomeResult = new AwesomeResult(date);
|
||
list.Add(awesomeResult);
|
||
if (i + 1 < slowPeriods)
|
||
{
|
||
continue;
|
||
}
|
||
double num2 = 0.0;
|
||
double num3 = 0.0;
|
||
for (int j = i + 1 - slowPeriods; j <= i; j++)
|
||
{
|
||
num2 += array[j];
|
||
if (j >= i + 1 - fastPeriods)
|
||
{
|
||
num3 += array[j];
|
||
}
|
||
}
|
||
awesomeResult.Oscillator = (num3 / (double)fastPeriods - num2 / (double)slowPeriods).NaN2Null();
|
||
awesomeResult.Normalized = ((array[i] == 0.0) ? ((double?)null) : (100.0 * awesomeResult.Oscillator / array[i]));
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateAwesome(int fastPeriods, int slowPeriods)
|
||
{
|
||
if (fastPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("slowPeriods", slowPeriods, "Fast periods must be greater than 0 for Awesome Oscillator.");
|
||
}
|
||
if (slowPeriods <= fastPeriods)
|
||
{
|
||
throw new ArgumentOutOfRangeException("slowPeriods", slowPeriods, "Slow periods must be larger than Fast Periods for Awesome Oscillator.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<AwesomeResult> RemoveWarmupPeriods(this IEnumerable<AwesomeResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((AwesomeResult x) => x.Oscillator.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// A simple quote transform.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/BasicQuote/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="candlePart">The OHLCV element or simply calculated value type.</param><returns>Time series of Basic Quote values.</returns><exception cref="T:Skender.Stock.Indicators.InvalidQuotesException">Invalid candle part provided.</exception>
|
||
public static IEnumerable<BasicData> GetBaseQuote<TQuote>(this IEnumerable<TQuote> quotes, CandlePart candlePart = CandlePart.Close) where TQuote : IQuote
|
||
{
|
||
return from q in quotes
|
||
select q.ToBasicData(candlePart) into x
|
||
orderby x.Date
|
||
select x;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Beta shows how strongly one stock responds to systemic volatility of the entire market.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Beta/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">documentation</see>
|
||
/// for more information.
|
||
/// </para>
|
||
/// </summary><typeparam name="TQuote">Configurable Quote type. See Guide for more information.</typeparam><param name="quotesEval">Historical price quotes for Evaluation.</param><param name="quotesMarket">Historical price quotes for Market.</param><param name="lookbackPeriods">Number of periods in the lookback window.</param><param name="type">Type of Beta to calculate.</param><returns>Time series of Beta values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception><exception cref="T:Skender.Stock.Indicators.InvalidQuotesException">Invalid quotes provided.</exception>
|
||
public static IEnumerable<BetaResult> GetBeta<TQuote>(this IEnumerable<TQuote> quotesEval, IEnumerable<TQuote> quotesMarket, int lookbackPeriods, BetaType type = BetaType.Standard) where TQuote : IQuote
|
||
{
|
||
List<(DateTime, double)> tpListEval = quotesEval.ToTuple(CandlePart.Close);
|
||
List<(DateTime, double)> tpListMrkt = quotesMarket.ToTuple(CandlePart.Close);
|
||
return CalcBeta(tpListEval, tpListMrkt, lookbackPeriods, type);
|
||
}
|
||
|
||
public static IEnumerable<BetaResult> GetBeta(this IEnumerable<IReusableResult> evalResults, IEnumerable<IReusableResult> mrktResults, int lookbackPeriods, BetaType type = BetaType.Standard)
|
||
{
|
||
List<(DateTime Date, double Value)> tpListEval = evalResults.ToTuple();
|
||
List<(DateTime, double)> tpListMrkt = mrktResults.ToTuple();
|
||
return CalcBeta(tpListEval, tpListMrkt, lookbackPeriods, type).SyncIndex(evalResults, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<BetaResult> GetBeta(this IEnumerable<(DateTime, double)> evalTuple, IEnumerable<(DateTime, double)> mrktTuple, int lookbackPeriods, BetaType type = BetaType.Standard)
|
||
{
|
||
List<(DateTime, double)> tpListEval = evalTuple.ToSortedList();
|
||
List<(DateTime, double)> tpListMrkt = mrktTuple.ToSortedList();
|
||
return CalcBeta(tpListEval, tpListMrkt, lookbackPeriods, type);
|
||
}
|
||
|
||
internal static List<BetaResult> CalcBeta(List<(DateTime, double)> tpListEval, List<(DateTime, double)> tpListMrkt, int lookbackPeriods, BetaType type = BetaType.Standard)
|
||
{
|
||
ValidateBeta(tpListEval, tpListMrkt, lookbackPeriods);
|
||
int count = tpListEval.Count;
|
||
List<BetaResult> list = new List<BetaResult>(count);
|
||
bool flag = ((type == BetaType.Standard || type == BetaType.All) ? true : false);
|
||
bool flag2 = flag;
|
||
flag = ((type == BetaType.Up || type == BetaType.All) ? true : false);
|
||
bool flag3 = flag;
|
||
flag = (uint)(type - 2) <= 1u;
|
||
bool flag4 = flag;
|
||
double[] array = new double[count];
|
||
double[] array2 = new double[count];
|
||
double num = 0.0;
|
||
double num2 = 0.0;
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
var (dateTime, num3) = tpListEval[i];
|
||
var (dateTime2, num4) = tpListMrkt[i];
|
||
if (dateTime != dateTime2)
|
||
{
|
||
throw new InvalidQuotesException("tpListEval", dateTime, "Date sequence does not match. Beta requires matching dates in provided quotes.");
|
||
}
|
||
array[i] = ((num != 0.0) ? (num3 / num - 1.0) : 0.0);
|
||
array2[i] = ((num2 != 0.0) ? (num4 / num2 - 1.0) : 0.0);
|
||
num = num3;
|
||
num2 = num4;
|
||
}
|
||
for (int j = 0; j < count; j++)
|
||
{
|
||
BetaResult betaResult = new BetaResult(tpListEval[j].Item1)
|
||
{
|
||
ReturnsEval = array[j],
|
||
ReturnsMrkt = array2[j]
|
||
};
|
||
list.Add(betaResult);
|
||
if (j >= lookbackPeriods)
|
||
{
|
||
if (flag2)
|
||
{
|
||
betaResult.CalcBetaWindow(j, lookbackPeriods, array2, array, BetaType.Standard);
|
||
}
|
||
if (flag4)
|
||
{
|
||
betaResult.CalcBetaWindow(j, lookbackPeriods, array2, array, BetaType.Down);
|
||
}
|
||
if (flag3)
|
||
{
|
||
betaResult.CalcBetaWindow(j, lookbackPeriods, array2, array, BetaType.Up);
|
||
}
|
||
if (type == BetaType.All && betaResult.BetaUp.HasValue && betaResult.BetaDown.HasValue)
|
||
{
|
||
betaResult.Ratio = ((betaResult.BetaDown == 0.0) ? ((double?)null) : (betaResult.BetaUp / betaResult.BetaDown));
|
||
betaResult.Convexity = (betaResult.BetaUp - betaResult.BetaDown) * (betaResult.BetaUp - betaResult.BetaDown);
|
||
}
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void CalcBetaWindow(this BetaResult r, int i, int lookbackPeriods, double[] mrktReturns, double[] evalReturns, BetaType type)
|
||
{
|
||
CorrResult corrResult = new CorrResult(r.Date);
|
||
List<double> list = new List<double>(lookbackPeriods);
|
||
List<double> list2 = new List<double>(lookbackPeriods);
|
||
checked
|
||
{
|
||
for (int j = i - lookbackPeriods + 1; j <= i; j++)
|
||
{
|
||
double num = mrktReturns[j];
|
||
double item = evalReturns[j];
|
||
if (type == BetaType.Standard || (type == BetaType.Down && num < 0.0) || (type == BetaType.Up && num > 0.0))
|
||
{
|
||
list.Add(num);
|
||
list2.Add(item);
|
||
}
|
||
}
|
||
if (list.Count <= 0)
|
||
{
|
||
return;
|
||
}
|
||
corrResult.PeriodCorrelation(list.ToArray(), list2.ToArray());
|
||
if (corrResult.Covariance.HasValue && corrResult.VarianceA.HasValue && corrResult.VarianceA != 0.0)
|
||
{
|
||
double? num2 = (corrResult.Covariance / corrResult.VarianceA).NaN2Null();
|
||
switch (type)
|
||
{
|
||
case BetaType.Standard:
|
||
r.Beta = num2;
|
||
break;
|
||
case BetaType.Down:
|
||
r.BetaDown = num2;
|
||
break;
|
||
case BetaType.Up:
|
||
r.BetaUp = num2;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private static void ValidateBeta(List<(DateTime, double)> tpListEval, List<(DateTime, double)> tpListMrkt, int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for Beta.");
|
||
}
|
||
if (tpListEval.Count != tpListMrkt.Count)
|
||
{
|
||
throw new InvalidQuotesException("tpListEval", "Eval quotes should have the same number of Market quotes for Beta.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<BetaResult> RemoveWarmupPeriods(this IEnumerable<BetaResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((BetaResult x) => x.Beta.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Bollinger Bands® depict volatility as standard deviation boundary lines from a moving average of price.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/BollingerBands/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><param name="standardDeviations">Width of bands. Number of Standard Deviations from the moving average.</param><returns>Time series of Bollinger Band and %B values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<BollingerBandsResult> GetBollingerBands<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods = 20, double standardDeviations = 2.0) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcBollingerBands(lookbackPeriods, standardDeviations);
|
||
}
|
||
|
||
public static IEnumerable<BollingerBandsResult> GetBollingerBands(this IEnumerable<IReusableResult> results, int lookbackPeriods = 20, double standardDeviations = 2.0)
|
||
{
|
||
return results.ToTuple().CalcBollingerBands(lookbackPeriods, standardDeviations).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<BollingerBandsResult> GetBollingerBands(this IEnumerable<(DateTime, double)> priceTuples, int lookbackPeriods = 20, double standardDeviations = 2.0)
|
||
{
|
||
return priceTuples.ToSortedList().CalcBollingerBands(lookbackPeriods, standardDeviations);
|
||
}
|
||
|
||
internal static List<BollingerBandsResult> CalcBollingerBands(this List<(DateTime, double)> tpList, int lookbackPeriods, double standardDeviations)
|
||
{
|
||
ValidateBollingerBands(lookbackPeriods, standardDeviations);
|
||
List<BollingerBandsResult> list = new List<BollingerBandsResult>(tpList.Count);
|
||
checked
|
||
{
|
||
for (int i = 0; i < tpList.Count; i++)
|
||
{
|
||
(DateTime, double) tuple = tpList[i];
|
||
DateTime item = tuple.Item1;
|
||
double item2 = tuple.Item2;
|
||
BollingerBandsResult bollingerBandsResult = new BollingerBandsResult(item);
|
||
list.Add(bollingerBandsResult);
|
||
if (i + 1 >= lookbackPeriods)
|
||
{
|
||
double[] array = new double[lookbackPeriods];
|
||
double num = 0.0;
|
||
int num2 = 0;
|
||
for (int j = i + 1 - lookbackPeriods; j <= i; j++)
|
||
{
|
||
num += (array[num2] = tpList[j].Item2);
|
||
num2++;
|
||
}
|
||
double? num3 = (num / (double)lookbackPeriods).NaN2Null();
|
||
double? num4 = array.StdDev().NaN2Null();
|
||
bollingerBandsResult.Sma = num3;
|
||
bollingerBandsResult.UpperBand = num3 + standardDeviations * num4;
|
||
bollingerBandsResult.LowerBand = num3 - standardDeviations * num4;
|
||
bollingerBandsResult.PercentB = ((bollingerBandsResult.UpperBand == bollingerBandsResult.LowerBand) ? ((double?)null) : ((item2 - bollingerBandsResult.LowerBand) / (bollingerBandsResult.UpperBand - bollingerBandsResult.LowerBand)));
|
||
bollingerBandsResult.ZScore = ((num4 == 0.0) ? ((double?)null) : ((item2 - bollingerBandsResult.Sma) / num4));
|
||
bollingerBandsResult.Width = ((num3 == 0.0) ? ((double?)null) : ((bollingerBandsResult.UpperBand - bollingerBandsResult.LowerBand) / num3));
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateBollingerBands(int lookbackPeriods, double standardDeviations)
|
||
{
|
||
if (lookbackPeriods <= 1)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 1 for Bollinger Bands.");
|
||
}
|
||
if (standardDeviations <= 0.0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("standardDeviations", standardDeviations, "Standard Deviations must be greater than 0 for Bollinger Bands.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<BollingerBandsResult> RemoveWarmupPeriods(this IEnumerable<BollingerBandsResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((BollingerBandsResult x) => x.Width.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Balance of Power (aka Balance of Market Power) is a momentum oscillator that depicts the strength of buying and selling pressure.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Bop/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="smoothPeriods">Number of periods for smoothing.</param><returns>Time series of BOP values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<BopResult> GetBop<TQuote>(this IEnumerable<TQuote> quotes, int smoothPeriods = 14) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcBop(smoothPeriods);
|
||
}
|
||
|
||
internal static List<BopResult> CalcBop(this List<QuoteD> qdList, int smoothPeriods)
|
||
{
|
||
ValidateBop(smoothPeriods);
|
||
int count = qdList.Count;
|
||
List<BopResult> list = new List<BopResult>(count);
|
||
double[] array = qdList.Select((QuoteD x) => (x.High == x.Low) ? double.NaN : ((x.Close - x.Open) / (x.High - x.Low))).ToArray();
|
||
checked
|
||
{
|
||
for (int num = 0; num < count; num++)
|
||
{
|
||
BopResult bopResult = new BopResult(qdList[num].Date);
|
||
list.Add(bopResult);
|
||
if (num >= smoothPeriods - 1)
|
||
{
|
||
double num2 = 0.0;
|
||
for (int num3 = num - smoothPeriods + 1; num3 <= num; num3++)
|
||
{
|
||
num2 += array[num3];
|
||
}
|
||
bopResult.Bop = (num2 / (double)smoothPeriods).NaN2Null();
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateBop(int smoothPeriods)
|
||
{
|
||
if (smoothPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("smoothPeriods", smoothPeriods, "Smoothing periods must be greater than 0 for BOP.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<BopResult> RemoveWarmupPeriods(this IEnumerable<BopResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((BopResult x) => x.Bop.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Commodity Channel Index (CCI) is an oscillator depicting deviation from typical price range, often used to identify cyclical trends.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Cci/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><returns>Time series of CCI values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<CciResult> GetCci<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods = 20) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcCci(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<CciResult> CalcCci(this List<QuoteD> qdList, int lookbackPeriods)
|
||
{
|
||
ValidateCci(lookbackPeriods);
|
||
int count = qdList.Count;
|
||
List<CciResult> list = new List<CciResult>(count);
|
||
double[] array = new double[count];
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
QuoteD quoteD = qdList[i];
|
||
array[i] = (quoteD.High + quoteD.Low + quoteD.Close) / 3.0;
|
||
CciResult cciResult = new CciResult(quoteD.Date);
|
||
list.Add(cciResult);
|
||
if (i + 1 >= lookbackPeriods)
|
||
{
|
||
double num = 0.0;
|
||
for (int j = i + 1 - lookbackPeriods; j <= i; j++)
|
||
{
|
||
num += array[j];
|
||
}
|
||
num /= (double)lookbackPeriods;
|
||
double num2 = 0.0;
|
||
for (int k = i + 1 - lookbackPeriods; k <= i; k++)
|
||
{
|
||
num2 += Math.Abs(num - array[k]);
|
||
}
|
||
num2 /= (double)lookbackPeriods;
|
||
cciResult.Cci = ((num2 == 0.0) ? ((double?)null) : ((array[i] - num) / (0.015 * num2)).NaN2Null());
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateCci(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for Commodity Channel Index.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<CciResult> RemoveWarmupPeriods(this IEnumerable<CciResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((CciResult x) => x.Cci.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Chaikin Oscillator is the difference between fast and slow Exponential Moving Averages (EMA) of the Accumulation/Distribution Line (ADL).
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/ChaikinOsc/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="fastPeriods">Number of periods for the ADL fast EMA.</param><param name="slowPeriods">Number of periods for the ADL slow EMA.</param><returns>Time series of Chaikin Oscillator, Money Flow Volume, and ADL values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<ChaikinOscResult> GetChaikinOsc<TQuote>(this IEnumerable<TQuote> quotes, int fastPeriods = 3, int slowPeriods = 10) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcChaikinOsc(fastPeriods, slowPeriods);
|
||
}
|
||
|
||
internal static List<ChaikinOscResult> CalcChaikinOsc(this List<QuoteD> qdList, int fastPeriods, int slowPeriods)
|
||
{
|
||
ValidateChaikinOsc(fastPeriods, slowPeriods);
|
||
List<ChaikinOscResult> list = (from r in qdList.CalcAdl(null)
|
||
select new ChaikinOscResult(r.Date)
|
||
{
|
||
MoneyFlowMultiplier = r.MoneyFlowMultiplier,
|
||
MoneyFlowVolume = r.MoneyFlowVolume,
|
||
Adl = r.Adl
|
||
}).ToList();
|
||
List<(DateTime Date, double)> tpList = list.Select((ChaikinOscResult x) => (Date: x.Date, x.Adl ?? double.NaN)).ToList();
|
||
List<EmaResult> list2 = tpList.CalcEma(slowPeriods);
|
||
List<EmaResult> list3 = tpList.CalcEma(fastPeriods);
|
||
checked
|
||
{
|
||
for (int num = slowPeriods - 1; num < list.Count; num++)
|
||
{
|
||
ChaikinOscResult chaikinOscResult = list[num];
|
||
EmaResult emaResult = list3[num];
|
||
EmaResult emaResult2 = list2[num];
|
||
chaikinOscResult.Oscillator = emaResult.Ema - emaResult2.Ema;
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateChaikinOsc(int fastPeriods, int slowPeriods)
|
||
{
|
||
if (fastPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("fastPeriods", fastPeriods, "Fast lookback periods must be greater than 0 for Chaikin Oscillator.");
|
||
}
|
||
if (slowPeriods <= fastPeriods)
|
||
{
|
||
throw new ArgumentOutOfRangeException("slowPeriods", slowPeriods, "Slow lookback periods must be greater than Fast lookback period for Chaikin Oscillator.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<ChaikinOscResult> RemoveWarmupPeriods(this IEnumerable<ChaikinOscResult> results)
|
||
{
|
||
checked
|
||
{
|
||
int num = results.ToList().FindIndex((ChaikinOscResult x) => x.Oscillator.HasValue) + 1;
|
||
return results.Remove(num + 100);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Chandelier Exit is typically used for stop-loss and can be computed for both long or short types.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Chandelier/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><param name="multiplier">Multiplier.</param><param name="type">Short or Long variant selection.</param><returns>Time series of Chandelier Exit values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<ChandelierResult> GetChandelier<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods = 22, double multiplier = 3.0, ChandelierType type = ChandelierType.Long) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcChandelier(lookbackPeriods, multiplier, type);
|
||
}
|
||
|
||
internal static List<ChandelierResult> CalcChandelier(this List<QuoteD> qdList, int lookbackPeriods, double multiplier, ChandelierType type)
|
||
{
|
||
ValidateChandelier(lookbackPeriods, multiplier);
|
||
int count = qdList.Count;
|
||
List<ChandelierResult> list = new List<ChandelierResult>(count);
|
||
List<AtrResult> list2 = qdList.CalcAtr(lookbackPeriods).ToList();
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
ChandelierResult chandelierResult = new ChandelierResult(qdList[i].Date);
|
||
list.Add(chandelierResult);
|
||
if (i < lookbackPeriods)
|
||
{
|
||
continue;
|
||
}
|
||
double? atr = list2[i].Atr;
|
||
switch (type)
|
||
{
|
||
case ChandelierType.Long:
|
||
{
|
||
double num2 = 0.0;
|
||
for (int k = i + 1 - lookbackPeriods; k <= i; k++)
|
||
{
|
||
QuoteD quoteD2 = qdList[k];
|
||
if (quoteD2.High > num2)
|
||
{
|
||
num2 = quoteD2.High;
|
||
}
|
||
}
|
||
chandelierResult.ChandelierExit = num2 - atr * multiplier;
|
||
break;
|
||
}
|
||
case ChandelierType.Short:
|
||
{
|
||
double num = double.MaxValue;
|
||
for (int j = i + 1 - lookbackPeriods; j <= i; j++)
|
||
{
|
||
QuoteD quoteD = qdList[j];
|
||
if (quoteD.Low < num)
|
||
{
|
||
num = quoteD.Low;
|
||
}
|
||
}
|
||
chandelierResult.ChandelierExit = num + atr * multiplier;
|
||
break;
|
||
}
|
||
default:
|
||
throw new ArgumentOutOfRangeException("type");
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateChandelier(int lookbackPeriods, double multiplier)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for Chandelier Exit.");
|
||
}
|
||
if (multiplier <= 0.0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("multiplier", multiplier, "Multiplier must be greater than 0 for Chandelier Exit.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<ChandelierResult> RemoveWarmupPeriods(this IEnumerable<ChandelierResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((ChandelierResult x) => x.ChandelierExit.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Choppiness Index (CHOP) measures the trendiness or choppiness over N lookback periods
|
||
/// on a scale of 0 to 100.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Chop/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><returns>Time series of CHOP values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<ChopResult> GetChop<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods = 14) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcChop(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<ChopResult> CalcChop(this List<QuoteD> qdList, int lookbackPeriods)
|
||
{
|
||
ValidateChop(lookbackPeriods);
|
||
int count = qdList.Count;
|
||
List<ChopResult> list = new List<ChopResult>(count);
|
||
double[] array = new double[count];
|
||
double[] array2 = new double[count];
|
||
double[] array3 = new double[count];
|
||
checked
|
||
{
|
||
for (int i = 0; i < qdList.Count; i++)
|
||
{
|
||
ChopResult chopResult = new ChopResult(qdList[i].Date);
|
||
list.Add(chopResult);
|
||
if (i <= 0)
|
||
{
|
||
continue;
|
||
}
|
||
array[i] = Math.Max(qdList[i].High, qdList[i - 1].Close);
|
||
array2[i] = Math.Min(qdList[i].Low, qdList[i - 1].Close);
|
||
array3[i] = array[i] - array2[i];
|
||
if (i >= lookbackPeriods)
|
||
{
|
||
double num = array3[i];
|
||
double num2 = array[i];
|
||
double num3 = array2[i];
|
||
for (int j = 1; j < lookbackPeriods; j++)
|
||
{
|
||
num += array3[i - j];
|
||
num2 = Math.Max(num2, array[i - j]);
|
||
num3 = Math.Min(num3, array2[i - j]);
|
||
}
|
||
double num4 = num2 - num3;
|
||
if (num4 != 0.0)
|
||
{
|
||
chopResult.Chop = 100.0 * (Math.Log(num / num4) / Math.Log(lookbackPeriods));
|
||
}
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateChop(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 1)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 1 for CHOP.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<ChopResult> RemoveWarmupPeriods(this IEnumerable<ChopResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((ChopResult x) => x.Chop.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Chaikin Money Flow (CMF) is the simple moving average of Money Flow Volume (MFV).
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Cmf/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods for the MFV moving average.</param><returns>Time series of Chaikin Money Flow and MFV values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<CmfResult> GetCmf<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods = 20) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcCmf(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<CmfResult> CalcCmf(this List<QuoteD> qdList, int lookbackPeriods)
|
||
{
|
||
List<(DateTime, double)> list = qdList.ToTuple(CandlePart.Volume);
|
||
ValidateCmf(lookbackPeriods);
|
||
int count = list.Count;
|
||
List<CmfResult> list2 = new List<CmfResult>(count);
|
||
List<AdlResult> list3 = qdList.CalcAdl(null).ToList();
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
AdlResult adlResult = list3[i];
|
||
CmfResult cmfResult = new CmfResult(adlResult.Date)
|
||
{
|
||
MoneyFlowMultiplier = adlResult.MoneyFlowMultiplier,
|
||
MoneyFlowVolume = adlResult.MoneyFlowVolume
|
||
};
|
||
list2.Add(cmfResult);
|
||
if (i >= lookbackPeriods - 1)
|
||
{
|
||
double? num = 0.0;
|
||
double? num2 = 0.0;
|
||
for (int j = i + 1 - lookbackPeriods; j <= i; j++)
|
||
{
|
||
num2 += list[j].Item2;
|
||
num += list3[j].MoneyFlowVolume;
|
||
}
|
||
double? num3 = num / (double)lookbackPeriods;
|
||
double? num4 = num2 / (double)lookbackPeriods;
|
||
if (num4 != 0.0)
|
||
{
|
||
cmfResult.Cmf = num3 / num4;
|
||
}
|
||
}
|
||
}
|
||
return list2;
|
||
}
|
||
}
|
||
|
||
private static void ValidateCmf(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for Chaikin Money Flow.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<CmfResult> RemoveWarmupPeriods(this IEnumerable<CmfResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((CmfResult x) => x.Cmf.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// The Chande Momentum Oscillator is a momentum indicator depicting the weighted percent of higher prices in financial markets.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Cmo/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><returns>Time series of CMO values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<CmoResult> GetCmo<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcCmo(lookbackPeriods);
|
||
}
|
||
|
||
public static IEnumerable<CmoResult> GetCmo(this IEnumerable<IReusableResult> results, int lookbackPeriods)
|
||
{
|
||
return results.ToTuple().CalcCmo(lookbackPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<CmoResult> GetCmo(this IEnumerable<(DateTime, double)> priceTuples, int lookbackPeriods)
|
||
{
|
||
return priceTuples.ToSortedList().CalcCmo(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<CmoResult> CalcCmo(this List<(DateTime, double)> tpList, int lookbackPeriods)
|
||
{
|
||
ValidateCmo(lookbackPeriods);
|
||
int count = tpList.Count;
|
||
List<CmoResult> list = new List<CmoResult>(count);
|
||
List<(bool?, double)> list2 = new List<(bool?, double)>(count);
|
||
double num = double.NaN;
|
||
if (count > 0)
|
||
{
|
||
list.Add(new CmoResult(tpList[0].Item1));
|
||
list2.Add((null, double.NaN));
|
||
num = tpList[0].Item2;
|
||
}
|
||
checked
|
||
{
|
||
for (int i = 1; i < count; i++)
|
||
{
|
||
(DateTime, double) tuple = tpList[i];
|
||
DateTime item = tuple.Item1;
|
||
double item2 = tuple.Item2;
|
||
CmoResult cmoResult = new CmoResult(item);
|
||
list.Add(cmoResult);
|
||
list2.Add(((item2 > num) ? new bool?(true) : ((item2 < num) ? new bool?(false) : ((bool?)null)), Math.Abs(item2 - num)));
|
||
if (i >= lookbackPeriods)
|
||
{
|
||
double num2 = 0.0;
|
||
double num3 = 0.0;
|
||
for (int j = i - lookbackPeriods + 1; j <= i; j++)
|
||
{
|
||
var (flag, num4) = list2[j];
|
||
if (flag.HasValue)
|
||
{
|
||
if (flag == true)
|
||
{
|
||
num2 += num4;
|
||
}
|
||
else
|
||
{
|
||
num3 += num4;
|
||
}
|
||
}
|
||
}
|
||
cmoResult.Cmo = ((num2 + num3 != 0.0) ? (100.0 * (num2 - num3) / (num2 + num3)).NaN2Null() : ((double?)null));
|
||
}
|
||
num = item2;
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateCmo(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for CMO.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<CmoResult> RemoveWarmupPeriods(this IEnumerable<CmoResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((CmoResult x) => x.Cmo.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// ConnorsRSI is a composite oscillator that incorporates RSI, winning/losing streaks, and percentile gain metrics on scale of 0 to 100.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/ConnorsRsi/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="rsiPeriods">Number of periods in the RSI.</param><param name="streakPeriods">Number of periods for streak RSI.</param><param name="rankPeriods">Number of periods for the percentile ranking.</param><returns>Time series of ConnorsRSI, RSI, Streak RSI, and Percent Rank values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<ConnorsRsiResult> GetConnorsRsi<TQuote>(this IEnumerable<TQuote> quotes, int rsiPeriods = 3, int streakPeriods = 2, int rankPeriods = 100) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcConnorsRsi(rsiPeriods, streakPeriods, rankPeriods);
|
||
}
|
||
|
||
public static IEnumerable<ConnorsRsiResult> GetConnorsRsi(this IEnumerable<IReusableResult> results, int rsiPeriods = 3, int streakPeriods = 2, int rankPeriods = 100)
|
||
{
|
||
return results.ToTuple().CalcConnorsRsi(rsiPeriods, streakPeriods, rankPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<ConnorsRsiResult> GetConnorsRsi(this IEnumerable<(DateTime, double)> priceTuples, int rsiPeriods = 3, int streakPeriods = 2, int rankPeriods = 100)
|
||
{
|
||
return priceTuples.ToSortedList().CalcConnorsRsi(rsiPeriods, streakPeriods, rankPeriods);
|
||
}
|
||
|
||
internal static List<ConnorsRsiResult> CalcConnorsRsi(this List<(DateTime, double)> tpList, int rsiPeriods, int streakPeriods, int rankPeriods)
|
||
{
|
||
ValidateConnorsRsi(rsiPeriods, streakPeriods, rankPeriods);
|
||
List<ConnorsRsiResult> list = tpList.CalcStreak(rsiPeriods, rankPeriods);
|
||
checked
|
||
{
|
||
int num = Math.Max(rsiPeriods, Math.Max(streakPeriods, rankPeriods)) + 2;
|
||
int count = list.Count;
|
||
List<RsiResult> list2 = (from x in list.Remove(Math.Min(count, 1))
|
||
select ((DateTime Date, double))(Date: x.Date, x.Streak)).ToList().CalcRsi(streakPeriods);
|
||
for (int num2 = streakPeriods + 2; num2 < count; num2++)
|
||
{
|
||
ConnorsRsiResult connorsRsiResult = list[num2];
|
||
RsiResult rsiResult = list2[num2 - 1];
|
||
connorsRsiResult.RsiStreak = rsiResult.Rsi;
|
||
if (num2 + 1 >= num)
|
||
{
|
||
connorsRsiResult.ConnorsRsi = (connorsRsiResult.Rsi + connorsRsiResult.RsiStreak + connorsRsiResult.PercentRank) / 3.0;
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static List<ConnorsRsiResult> CalcStreak(this List<(DateTime Date, double Streak)> tpList, int rsiPeriods, int rankPeriods)
|
||
{
|
||
List<RsiResult> list = tpList.CalcRsi(rsiPeriods);
|
||
int count = tpList.Count;
|
||
List<ConnorsRsiResult> list2 = new List<ConnorsRsiResult>(count);
|
||
double[] array = new double[count];
|
||
double num = double.NaN;
|
||
int num2 = 0;
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
(DateTime Date, double Streak) tuple = tpList[i];
|
||
DateTime item = tuple.Date;
|
||
double item2 = tuple.Streak;
|
||
ConnorsRsiResult connorsRsiResult = new ConnorsRsiResult(item)
|
||
{
|
||
Rsi = list[i].Rsi
|
||
};
|
||
list2.Add(connorsRsiResult);
|
||
if (i == 0)
|
||
{
|
||
num = item2;
|
||
continue;
|
||
}
|
||
num2 = (connorsRsiResult.Streak = ((item2 != num) ? ((item2 > num) ? ((num2 < 0) ? 1 : (num2 + 1)) : ((num2 > 0) ? (-1) : (num2 - 1))) : 0));
|
||
array[i] = ((num <= 0.0) ? double.NaN : ((item2 - num) / num));
|
||
if (i + 1 > rankPeriods)
|
||
{
|
||
int num4 = 0;
|
||
for (int j = i - rankPeriods; j <= i; j++)
|
||
{
|
||
if (array[j] < array[i])
|
||
{
|
||
num4++;
|
||
}
|
||
}
|
||
unchecked
|
||
{
|
||
connorsRsiResult.PercentRank = checked(100 * num4) / rankPeriods;
|
||
}
|
||
}
|
||
num = item2;
|
||
}
|
||
return list2;
|
||
}
|
||
}
|
||
|
||
private static void ValidateConnorsRsi(int rsiPeriods, int streakPeriods, int rankPeriods)
|
||
{
|
||
if (rsiPeriods <= 1)
|
||
{
|
||
throw new ArgumentOutOfRangeException("rsiPeriods", rsiPeriods, "RSI period for Close price must be greater than 1 for ConnorsRsi.");
|
||
}
|
||
if (streakPeriods <= 1)
|
||
{
|
||
throw new ArgumentOutOfRangeException("streakPeriods", streakPeriods, "RSI period for Streak must be greater than 1 for ConnorsRsi.");
|
||
}
|
||
if (rankPeriods <= 1)
|
||
{
|
||
throw new ArgumentOutOfRangeException("rankPeriods", rankPeriods, "Percent Rank periods must be greater than 1 for ConnorsRsi.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<ConnorsRsiResult> RemoveWarmupPeriods(this IEnumerable<ConnorsRsiResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((ConnorsRsiResult x) => x.ConnorsRsi.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Correlation Coefficient between two quote histories, based on price.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Correlation/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">documentation</see>
|
||
/// for more information.
|
||
/// </para>
|
||
/// </summary><typeparam name="TQuote">Configurable Quote type. See Guide for more information.</typeparam><param name="quotesA">Historical price quotes A for comparison.</param><param name="quotesB">Historical price quotes B for comparison.</param><param name="lookbackPeriods">Number of periods in the lookback window.</param><returns>
|
||
/// Time series of Correlation Coefficient values.
|
||
/// R², Variance, and Covariance are also included.
|
||
/// </returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception><exception cref="T:Skender.Stock.Indicators.InvalidQuotesException">Invalid quotes provided.</exception>
|
||
public static IEnumerable<CorrResult> GetCorrelation<TQuote>(this IEnumerable<TQuote> quotesA, IEnumerable<TQuote> quotesB, int lookbackPeriods) where TQuote : IQuote
|
||
{
|
||
List<(DateTime, double)> tpListA = quotesA.ToTuple(CandlePart.Close);
|
||
List<(DateTime, double)> tpListB = quotesB.ToTuple(CandlePart.Close);
|
||
return tpListA.CalcCorrelation(tpListB, lookbackPeriods);
|
||
}
|
||
|
||
public static IEnumerable<CorrResult> GetCorrelation(this IEnumerable<IReusableResult> quotesA, IEnumerable<IReusableResult> quotesB, int lookbackPeriods)
|
||
{
|
||
List<(DateTime Date, double Value)> tpListA = quotesA.ToTuple();
|
||
List<(DateTime, double)> tpListB = quotesB.ToTuple();
|
||
return tpListA.CalcCorrelation(tpListB, lookbackPeriods).SyncIndex(quotesA, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<CorrResult> GetCorrelation(this IEnumerable<(DateTime, double)> tuplesA, IEnumerable<(DateTime, double)> tuplesB, int lookbackPeriods)
|
||
{
|
||
List<(DateTime, double)> tpListA = tuplesA.ToSortedList();
|
||
List<(DateTime, double)> tpListB = tuplesB.ToSortedList();
|
||
return tpListA.CalcCorrelation(tpListB, lookbackPeriods);
|
||
}
|
||
|
||
internal static List<CorrResult> CalcCorrelation(this List<(DateTime, double)> tpListA, List<(DateTime, double)> tpListB, int lookbackPeriods)
|
||
{
|
||
ValidateCorrelation(tpListA, tpListB, lookbackPeriods);
|
||
int count = tpListA.Count;
|
||
List<CorrResult> list = new List<CorrResult>(count);
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
DateTime item = tpListA[i].Item1;
|
||
DateTime item2 = tpListB[i].Item1;
|
||
if (item != item2)
|
||
{
|
||
throw new InvalidQuotesException("tpListA", item, "Date sequence does not match. Correlation requires matching dates in provided histories.");
|
||
}
|
||
CorrResult corrResult = new CorrResult(item);
|
||
list.Add(corrResult);
|
||
if (i >= lookbackPeriods - 1)
|
||
{
|
||
double[] array = new double[lookbackPeriods];
|
||
double[] array2 = new double[lookbackPeriods];
|
||
int num = 0;
|
||
for (int j = i + 1 - lookbackPeriods; j <= i; j++)
|
||
{
|
||
array[num] = tpListA[j].Item2;
|
||
array2[num] = tpListB[j].Item2;
|
||
num++;
|
||
}
|
||
corrResult.PeriodCorrelation(array, array2);
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void PeriodCorrelation(this CorrResult r, double[] dataA, double[] dataB)
|
||
{
|
||
int num = dataA.Length;
|
||
double num2 = 0.0;
|
||
double num3 = 0.0;
|
||
double num4 = 0.0;
|
||
double num5 = 0.0;
|
||
double num6 = 0.0;
|
||
for (int i = 0; i < num; i = checked(i + 1))
|
||
{
|
||
double num7 = dataA[i];
|
||
double num8 = dataB[i];
|
||
num2 += num7;
|
||
num3 += num8;
|
||
num4 += num7 * num7;
|
||
num5 += num8 * num8;
|
||
num6 += num7 * num8;
|
||
}
|
||
double num9 = num2 / (double)num;
|
||
double num10 = num3 / (double)num;
|
||
double num11 = num4 / (double)num;
|
||
double num12 = num5 / (double)num;
|
||
double num13 = num6 / (double)num;
|
||
double num14 = num11 - num9 * num9;
|
||
double num15 = num12 - num10 * num10;
|
||
double num16 = num13 - num9 * num10;
|
||
double num17 = Math.Sqrt(num14 * num15);
|
||
r.VarianceA = num14.NaN2Null();
|
||
r.VarianceB = num15.NaN2Null();
|
||
r.Covariance = num16.NaN2Null();
|
||
r.Correlation = ((num17 == 0.0) ? ((double?)null) : (num16 / num17).NaN2Null());
|
||
r.RSquared = r.Correlation * r.Correlation;
|
||
}
|
||
|
||
private static void ValidateCorrelation(List<(DateTime, double)> quotesA, List<(DateTime, double)> quotesB, int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for Correlation.");
|
||
}
|
||
if (quotesA.Count != quotesB.Count)
|
||
{
|
||
throw new InvalidQuotesException("quotesB", "B quotes should have at least as many records as A quotes for Correlation.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<CorrResult> RemoveWarmupPeriods(this IEnumerable<CorrResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((CorrResult x) => x.Correlation.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Double Exponential Moving Average (DEMA) of the price.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Dema/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><returns>Time series of Double EMA values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<DemaResult> GetDema<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcDema(lookbackPeriods);
|
||
}
|
||
|
||
public static IEnumerable<DemaResult> GetDema(this IEnumerable<IReusableResult> results, int lookbackPeriods)
|
||
{
|
||
return results.ToTuple().CalcDema(lookbackPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<DemaResult> GetDema(this IEnumerable<(DateTime, double)> priceTuples, int lookbackPeriods)
|
||
{
|
||
return priceTuples.ToSortedList().CalcDema(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<DemaResult> CalcDema(this List<(DateTime, double)> tpList, int lookbackPeriods)
|
||
{
|
||
ValidateDema(lookbackPeriods);
|
||
int count = tpList.Count;
|
||
List<DemaResult> list = new List<DemaResult>(count);
|
||
checked
|
||
{
|
||
double num = 2.0 / (double)(lookbackPeriods + 1);
|
||
double? num2 = 0.0;
|
||
int num3 = Math.Min(lookbackPeriods, count);
|
||
for (int i = 0; i < num3; i++)
|
||
{
|
||
num2 += tpList[i].Item2;
|
||
}
|
||
num2 /= (double)lookbackPeriods;
|
||
double? num4 = num2;
|
||
for (int j = 0; j < count; j++)
|
||
{
|
||
(DateTime, double) tuple = tpList[j];
|
||
DateTime item = tuple.Item1;
|
||
double item2 = tuple.Item2;
|
||
DemaResult demaResult = new DemaResult(item);
|
||
list.Add(demaResult);
|
||
if (j > lookbackPeriods - 1)
|
||
{
|
||
double? num5 = num2 + num * (item2 - num2);
|
||
double? num6 = num4 + num * (num5 - num4);
|
||
demaResult.Dema = (2.0 * num5 - num6).NaN2Null();
|
||
num2 = num5;
|
||
num4 = num6;
|
||
}
|
||
else if (j == lookbackPeriods - 1)
|
||
{
|
||
demaResult.Dema = 2.0 * num2 - num4;
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateDema(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for DEMA.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<DemaResult> RemoveWarmupPeriods(this IEnumerable<DemaResult> results)
|
||
{
|
||
checked
|
||
{
|
||
int num = results.ToList().FindIndex((DemaResult x) => x.Dema.HasValue) + 1;
|
||
return results.Remove(2 * num + 100);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Doji is a single candlestick pattern where open and close price are virtually identical, representing market indecision.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Patterns/Doji/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="maxPriceChangePercent">Optional. Maximum absolute percent difference in open and close price.</param><returns>Time series of Doji values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<CandleResult> GetDoji<TQuote>(this IEnumerable<TQuote> quotes, double maxPriceChangePercent = 0.1) where TQuote : IQuote
|
||
{
|
||
return quotes.CalcDoji(maxPriceChangePercent);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Doji is a single candlestick pattern where open and close price are virtually identical, representing market indecision.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Patterns/Doji/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="maxPriceChangePercent">Optional. Maximum absolute percent difference in open and close price.</param><returns>Time series of Doji values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
internal static List<CandleResult> CalcDoji<TQuote>(this IEnumerable<TQuote> quotes, double maxPriceChangePercent) where TQuote : IQuote
|
||
{
|
||
ValidateDoji(maxPriceChangePercent);
|
||
List<CandleResult> list = quotes.ToCandleResults();
|
||
maxPriceChangePercent /= 100.0;
|
||
int count = list.Count;
|
||
for (int i = 0; i < count; i = checked(i + 1))
|
||
{
|
||
CandleResult candleResult = list[i];
|
||
if (candleResult.Candle.Open != 0m && Math.Abs((double)(candleResult.Candle.Close / candleResult.Candle.Open) - 1.0) <= maxPriceChangePercent)
|
||
{
|
||
candleResult.Price = candleResult.Candle.Close;
|
||
candleResult.Match = Match.Neutral;
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
|
||
private static void ValidateDoji(double maxPriceChangePercent)
|
||
{
|
||
if ((maxPriceChangePercent < 0.0 || maxPriceChangePercent > 0.5) ? true : false)
|
||
{
|
||
throw new ArgumentOutOfRangeException("maxPriceChangePercent", maxPriceChangePercent, "Maximum Percent Change must be between 0 and 0.5 for Doji (0% to 0.5%).");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Donchian Channels, also called Price Channels, are derived from highest High and lowest Low values over a lookback window.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Donchian/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><returns>Time series of Donchian Channel values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<DonchianResult> GetDonchian<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods = 20) where TQuote : IQuote
|
||
{
|
||
return quotes.ToSortedList().CalcDonchian(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<DonchianResult> CalcDonchian<TQuote>(this List<TQuote> quotesList, int lookbackPeriods) where TQuote : IQuote
|
||
{
|
||
ValidateDonchian(lookbackPeriods);
|
||
int count = quotesList.Count;
|
||
List<DonchianResult> list = new List<DonchianResult>(count);
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
DonchianResult donchianResult = new DonchianResult(quotesList[i].Date);
|
||
list.Add(donchianResult);
|
||
if (i < lookbackPeriods)
|
||
{
|
||
continue;
|
||
}
|
||
decimal num = default(decimal);
|
||
decimal num2 = decimal.MaxValue;
|
||
for (int j = i - lookbackPeriods; j < i; j++)
|
||
{
|
||
TQuote val = quotesList[j];
|
||
if (val.High > num)
|
||
{
|
||
num = val.High;
|
||
}
|
||
if (val.Low < num2)
|
||
{
|
||
num2 = val.Low;
|
||
}
|
||
}
|
||
donchianResult.UpperBand = num;
|
||
donchianResult.LowerBand = num2;
|
||
donchianResult.Centerline = (donchianResult.UpperBand + donchianResult.LowerBand) / (decimal?)2m;
|
||
decimal? centerline = donchianResult.Centerline;
|
||
donchianResult.Width = (((centerline.GetValueOrDefault() == default(decimal)) & centerline.HasValue) ? ((decimal?)null) : ((donchianResult.UpperBand - donchianResult.LowerBand) / donchianResult.Centerline));
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateDonchian(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for Donchian Channel.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes non-essential records containing null values with unique consideration for
|
||
/// this indicator. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#condense?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator results to evaluate.</param><returns>Time series of
|
||
/// indicator results, condensed.</returns>
|
||
public static IEnumerable<DonchianResult> Condense(this IEnumerable<DonchianResult> results)
|
||
{
|
||
List<DonchianResult> list = results.ToList();
|
||
list.RemoveAll((DonchianResult x) => !x.UpperBand.HasValue && !x.LowerBand.HasValue && !x.Centerline.HasValue);
|
||
return list.ToSortedList();
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<DonchianResult> RemoveWarmupPeriods(this IEnumerable<DonchianResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((DonchianResult x) => x.Width.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Detrended Price Oscillator (DPO) depicts the difference between price and an offset simple moving average.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Dpo/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><returns>Time series of DPO values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<DpoResult> GetDpo<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcDpo(lookbackPeriods);
|
||
}
|
||
|
||
public static IEnumerable<DpoResult> GetDpo(this IEnumerable<IReusableResult> results, int lookbackPeriods)
|
||
{
|
||
return results.ToTuple().CalcDpo(lookbackPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<DpoResult> GetDpo(this IEnumerable<(DateTime, double)> priceTuples, int lookbackPeriods)
|
||
{
|
||
return priceTuples.ToSortedList().CalcDpo(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<DpoResult> CalcDpo(this List<(DateTime, double)> tpList, int lookbackPeriods)
|
||
{
|
||
ValidateDpo(lookbackPeriods);
|
||
int count = tpList.Count;
|
||
checked
|
||
{
|
||
int num = unchecked(lookbackPeriods / 2) + 1;
|
||
List<SmaResult> list = tpList.GetSma(lookbackPeriods).ToList();
|
||
List<DpoResult> list2 = new List<DpoResult>(count);
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
(DateTime, double) tuple = tpList[i];
|
||
DateTime item = tuple.Item1;
|
||
double item2 = tuple.Item2;
|
||
DpoResult dpoResult = new DpoResult(item);
|
||
list2.Add(dpoResult);
|
||
if (i >= lookbackPeriods - num - 1 && i < count - num)
|
||
{
|
||
SmaResult smaResult = list[i + num];
|
||
dpoResult.Sma = smaResult.Sma;
|
||
dpoResult.Dpo = ((!smaResult.Sma.HasValue) ? ((double?)null) : (item2 - smaResult.Sma).NaN2Null());
|
||
}
|
||
}
|
||
return list2;
|
||
}
|
||
}
|
||
|
||
private static void ValidateDpo(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for DPO.");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// McGinley Dynamic is a more responsive variant of exponential moving average.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Dynamic/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><param name="kFactor">Optional. Range adjustment factor.</param><returns>Time series of Dynamic values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<DynamicResult> GetDynamic<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods, double kFactor = 0.6) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcDynamic(lookbackPeriods, kFactor);
|
||
}
|
||
|
||
public static IEnumerable<DynamicResult> GetDynamic(this IEnumerable<IReusableResult> results, int lookbackPeriods, double kFactor = 0.6)
|
||
{
|
||
return results.ToTuple().CalcDynamic(lookbackPeriods, kFactor).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<DynamicResult> GetDynamic(this IEnumerable<(DateTime, double)> priceTuples, int lookbackPeriods, double kFactor = 0.6)
|
||
{
|
||
return priceTuples.ToSortedList().CalcDynamic(lookbackPeriods, kFactor);
|
||
}
|
||
|
||
internal static List<DynamicResult> CalcDynamic(this List<(DateTime, double)> tpList, int lookbackPeriods, double kFactor)
|
||
{
|
||
ValidateDynamic(lookbackPeriods, kFactor);
|
||
int num = 1;
|
||
int count = tpList.Count;
|
||
List<DynamicResult> list = new List<DynamicResult>(count);
|
||
if (count == 0)
|
||
{
|
||
return list;
|
||
}
|
||
double num2 = tpList[0].Item2;
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
(DateTime, double) tuple = tpList[i];
|
||
DateTime item = tuple.Item1;
|
||
double item2 = tuple.Item2;
|
||
DynamicResult dynamicResult = new DynamicResult(item);
|
||
list.Add(dynamicResult);
|
||
if (double.IsNaN(item2) || num2 == 0.0)
|
||
{
|
||
num2 = item2;
|
||
num = i + lookbackPeriods;
|
||
continue;
|
||
}
|
||
double num3 = num2 + (item2 - num2) / (kFactor * (double)lookbackPeriods * Math.Pow(item2 / num2, 4.0));
|
||
if (i >= num)
|
||
{
|
||
dynamicResult.Dynamic = num3.NaN2Null();
|
||
}
|
||
num2 = num3;
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateDynamic(int lookbackPeriods, double kFactor)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for DYNAMIC.");
|
||
}
|
||
if (kFactor <= 0.0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("kFactor", kFactor, "K-Factor range adjustment must be greater than 0 for DYNAMIC.");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// The Elder-ray Index depicts buying and selling pressure, also known as Bull and Bear Power.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/ElderRay/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods for the EMA.</param><returns>Time series of Elder-ray Index values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<ElderRayResult> GetElderRay<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods = 13) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcElderRay(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<ElderRayResult> CalcElderRay(this List<QuoteD> qdList, int lookbackPeriods)
|
||
{
|
||
ValidateElderRay(lookbackPeriods);
|
||
List<ElderRayResult> list = (from x in qdList.ToTuple(CandlePart.Close).CalcEma(lookbackPeriods)
|
||
select new ElderRayResult(x.Date)
|
||
{
|
||
Ema = x.Ema
|
||
}).ToList();
|
||
checked
|
||
{
|
||
for (int num = lookbackPeriods - 1; num < qdList.Count; num++)
|
||
{
|
||
QuoteD quoteD = qdList[num];
|
||
ElderRayResult elderRayResult = list[num];
|
||
elderRayResult.BullPower = quoteD.High - elderRayResult.Ema;
|
||
elderRayResult.BearPower = quoteD.Low - elderRayResult.Ema;
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateElderRay(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for Elder-ray Index.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<ElderRayResult> RemoveWarmupPeriods(this IEnumerable<ElderRayResult> results)
|
||
{
|
||
checked
|
||
{
|
||
int num = results.ToList().FindIndex((ElderRayResult x) => x.BullPower.HasValue) + 1;
|
||
return results.Remove(num + 100);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Exponential Moving Average (EMA) of price or any other specified OHLCV element.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Ema/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><returns>Time series of EMA values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<EmaResult> GetEma<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcEma(lookbackPeriods);
|
||
}
|
||
|
||
public static IEnumerable<EmaResult> GetEma(this IEnumerable<IReusableResult> results, int lookbackPeriods)
|
||
{
|
||
return results.ToTuple().CalcEma(lookbackPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<EmaResult> GetEma(this IEnumerable<(DateTime, double)> priceTuples, int lookbackPeriods)
|
||
{
|
||
return priceTuples.ToSortedList().CalcEma(lookbackPeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Extablish a streaming base for Exponential Moving Average (EMA).
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Ema/#streaming?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><returns>EMA base that you can add Quotes to with the .Add(quote) method.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
internal static EmaBase InitEma<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods) where TQuote : IQuote
|
||
{
|
||
return new EmaBase(quotes.ToTuple(CandlePart.Close), lookbackPeriods);
|
||
}
|
||
|
||
internal static EmaBase InitEma(this IEnumerable<IReusableResult> results, int lookbackPeriods)
|
||
{
|
||
return new EmaBase(results.ToTuple(), lookbackPeriods);
|
||
}
|
||
|
||
internal static List<EmaResult> CalcEma(this List<(DateTime, double)> tpList, int lookbackPeriods)
|
||
{
|
||
EmaBase.Validate(lookbackPeriods);
|
||
int count = tpList.Count;
|
||
List<EmaResult> list = new List<EmaResult>(count);
|
||
double num = 0.0;
|
||
checked
|
||
{
|
||
double k = 2.0 / (double)(lookbackPeriods + 1);
|
||
int num2 = Math.Min(lookbackPeriods, count);
|
||
for (int i = 0; i < num2; i++)
|
||
{
|
||
double item = tpList[i].Item2;
|
||
num += item;
|
||
}
|
||
num /= (double)lookbackPeriods;
|
||
for (int j = 0; j < count; j++)
|
||
{
|
||
(DateTime, double) tuple = tpList[j];
|
||
DateTime item2 = tuple.Item1;
|
||
double item3 = tuple.Item2;
|
||
EmaResult emaResult = new EmaResult(item2);
|
||
list.Add(emaResult);
|
||
if (j + 1 > lookbackPeriods)
|
||
{
|
||
double num3 = EmaBase.Increment(item3, num, k);
|
||
emaResult.Ema = num3.NaN2Null();
|
||
num = num3;
|
||
}
|
||
else if (j == lookbackPeriods - 1)
|
||
{
|
||
emaResult.Ema = num.NaN2Null();
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<EmaResult> RemoveWarmupPeriods(this IEnumerable<EmaResult> results)
|
||
{
|
||
checked
|
||
{
|
||
int num = results.ToList().FindIndex((EmaResult x) => x.Ema.HasValue) + 1;
|
||
return results.Remove(num + 100);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Endpoint Moving Average (EPMA), also known as Least Squares Moving Average (LSMA), plots the projected last point of a linear regression lookback window.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Slope/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><returns>Time series of Endpoint Moving Average values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<EpmaResult> GetEpma<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcEpma(lookbackPeriods);
|
||
}
|
||
|
||
public static IEnumerable<EpmaResult> GetEpma(this IEnumerable<IReusableResult> results, int lookbackPeriods)
|
||
{
|
||
return results.ToTuple().CalcEpma(lookbackPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<EpmaResult> GetEpma(this IEnumerable<(DateTime, double)> priceTuples, int lookbackPeriods)
|
||
{
|
||
return priceTuples.ToSortedList().CalcEpma(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<EpmaResult> CalcEpma(this List<(DateTime, double)> tpList, int lookbackPeriods)
|
||
{
|
||
ValidateEpma(lookbackPeriods);
|
||
List<SlopeResult> list = tpList.CalcSlope(lookbackPeriods).ToList();
|
||
int count = list.Count;
|
||
List<EpmaResult> list2 = new List<EpmaResult>(count);
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
SlopeResult slopeResult = list[i];
|
||
EpmaResult item = new EpmaResult(slopeResult.Date)
|
||
{
|
||
Epma = (slopeResult.Slope * (double)(i + 1) + slopeResult.Intercept).NaN2Null()
|
||
};
|
||
list2.Add(item);
|
||
}
|
||
return list2;
|
||
}
|
||
}
|
||
|
||
private static void ValidateEpma(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for Epma.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<EpmaResult> RemoveWarmupPeriods(this IEnumerable<EpmaResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((EpmaResult x) => x.Epma.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Fractal Chaos Bands outline high and low price channels to depict broad less-chaotic price movements. FCB is a channelized depiction of Williams Fractals.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Fcb/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="windowSpan">Number of span periods in the evaluation window.</param><returns>Time series of Fractal Chaos Band and Oscillator values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<FcbResult> GetFcb<TQuote>(this IEnumerable<TQuote> quotes, int windowSpan = 2) where TQuote : IQuote
|
||
{
|
||
return quotes.ToSortedList().CalcFcb(windowSpan);
|
||
}
|
||
|
||
internal static List<FcbResult> CalcFcb<TQuote>(this List<TQuote> quotesList, int windowSpan) where TQuote : IQuote
|
||
{
|
||
ValidateFcb(windowSpan);
|
||
List<FractalResult> list = quotesList.CalcFractal(windowSpan, windowSpan, EndType.HighLow).ToList();
|
||
int count = list.Count;
|
||
List<FcbResult> list2 = new List<FcbResult>(count);
|
||
decimal? num = null;
|
||
decimal? num2 = null;
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
FcbResult fcbResult = new FcbResult(list[i].Date);
|
||
list2.Add(fcbResult);
|
||
if (i >= 2 * windowSpan)
|
||
{
|
||
FractalResult fractalResult = list[i - windowSpan];
|
||
num = fractalResult.FractalBear ?? num;
|
||
num2 = fractalResult.FractalBull ?? num2;
|
||
fcbResult.UpperBand = num;
|
||
fcbResult.LowerBand = num2;
|
||
}
|
||
}
|
||
return list2;
|
||
}
|
||
}
|
||
|
||
private static void ValidateFcb(int windowSpan)
|
||
{
|
||
if (windowSpan < 2)
|
||
{
|
||
throw new ArgumentOutOfRangeException("windowSpan", windowSpan, "Window span must be at least 2 for FCB.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes non-essential records containing null values with unique consideration for
|
||
/// this indicator. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#condense?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator results to evaluate.</param><returns>Time series of
|
||
/// indicator results, condensed.</returns>
|
||
public static IEnumerable<FcbResult> Condense(this IEnumerable<FcbResult> results)
|
||
{
|
||
List<FcbResult> list = results.ToList();
|
||
list.RemoveAll((FcbResult x) => !x.UpperBand.HasValue && !x.LowerBand.HasValue);
|
||
return list.ToSortedList();
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<FcbResult> RemoveWarmupPeriods(this IEnumerable<FcbResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((FcbResult x) => x.UpperBand.HasValue || x.LowerBand.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Ehlers Fisher Transform converts prices into a Gaussian normal distribution.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/FisherTransform/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><returns>Time series of Fisher Transform values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<FisherTransformResult> GetFisherTransform<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods = 10) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.HL2).CalcFisherTransform(lookbackPeriods);
|
||
}
|
||
|
||
public static IEnumerable<FisherTransformResult> GetFisherTransform(this IEnumerable<IReusableResult> results, int lookbackPeriods)
|
||
{
|
||
return results.ToTuple().CalcFisherTransform(lookbackPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<FisherTransformResult> GetFisherTransform(this IEnumerable<(DateTime, double)> priceTuples, int lookbackPeriods)
|
||
{
|
||
return priceTuples.ToSortedList().CalcFisherTransform(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<FisherTransformResult> CalcFisherTransform(this List<(DateTime, double)> tpList, int lookbackPeriods)
|
||
{
|
||
ValidateFisherTransform(lookbackPeriods);
|
||
int count = tpList.Count;
|
||
double[] array = new double[count];
|
||
double[] array2 = new double[count];
|
||
List<FisherTransformResult> list = new List<FisherTransformResult>(count);
|
||
checked
|
||
{
|
||
for (int i = 0; i < tpList.Count; i++)
|
||
{
|
||
var (date, num) = tpList[i];
|
||
array[i] = num;
|
||
double num2 = array[i];
|
||
double num3 = array[i];
|
||
for (int j = Math.Max(i - lookbackPeriods + 1, 0); j <= i; j++)
|
||
{
|
||
num2 = Math.Min(array[j], num2);
|
||
num3 = Math.Max(array[j], num3);
|
||
}
|
||
FisherTransformResult fisherTransformResult = new FisherTransformResult(date);
|
||
list.Add(fisherTransformResult);
|
||
if (i > 0)
|
||
{
|
||
array2[i] = ((num3 != num2) ? (0.66 * ((array[i] - num2) / (num3 - num2) - 0.5) + 0.67 * array2[i - 1]) : 0.0);
|
||
array2[i] = ((array2[i] > 0.99) ? 0.999 : array2[i]);
|
||
array2[i] = ((array2[i] < -0.99) ? (-0.999) : array2[i]);
|
||
fisherTransformResult.Fisher = (0.5 * Math.Log((1.0 + array2[i]) / (1.0 - array2[i])) + 0.5 * list[i - 1].Fisher).NaN2Null();
|
||
fisherTransformResult.Trigger = list[i - 1].Fisher;
|
||
}
|
||
else
|
||
{
|
||
array2[i] = 0.0;
|
||
fisherTransformResult.Fisher = 0.0;
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateFisherTransform(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for Fisher Transform.");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// The Force Index depicts volume-based buying and selling pressure.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/ForceIndex/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods for the EMA of Force Index.</param><returns>Time series of Force Index values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<ForceIndexResult> GetForceIndex<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods = 2) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcForceIndex(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<ForceIndexResult> CalcForceIndex(this List<QuoteD> qdList, int lookbackPeriods)
|
||
{
|
||
ValidateForceIndex(lookbackPeriods);
|
||
int count = qdList.Count;
|
||
List<ForceIndexResult> list = new List<ForceIndexResult>(count);
|
||
double? num = null;
|
||
double? num2 = null;
|
||
double? num3 = 0.0;
|
||
checked
|
||
{
|
||
double num4 = 2.0 / (double)(lookbackPeriods + 1);
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
QuoteD quoteD = qdList[i];
|
||
ForceIndexResult forceIndexResult = new ForceIndexResult(quoteD.Date);
|
||
list.Add(forceIndexResult);
|
||
if (i == 0)
|
||
{
|
||
num = quoteD.Close;
|
||
continue;
|
||
}
|
||
double? num5 = quoteD.Volume * (quoteD.Close - num);
|
||
num = quoteD.Close;
|
||
if (i > lookbackPeriods)
|
||
{
|
||
forceIndexResult.ForceIndex = num2 + num4 * (num5 - num2);
|
||
}
|
||
else
|
||
{
|
||
num3 += num5;
|
||
if (i == lookbackPeriods)
|
||
{
|
||
forceIndexResult.ForceIndex = num3 / (double)lookbackPeriods;
|
||
}
|
||
}
|
||
num2 = forceIndexResult.ForceIndex;
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateForceIndex(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for Force Index.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<ForceIndexResult> RemoveWarmupPeriods(this IEnumerable<ForceIndexResult> results)
|
||
{
|
||
int num = results.ToList().FindIndex((ForceIndexResult x) => x.ForceIndex.HasValue);
|
||
return results.Remove(checked(num + 100));
|
||
}
|
||
|
||
/// <summary>
|
||
/// Williams Fractal is a retrospective price pattern that identifies a central high or low point over a lookback window.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Fractal/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="windowSpan">Number of span periods to the left and right of the evaluation period.</param><param name="endType">Determines use of Close or High/Low wicks for points.</param><returns>Time series of Williams Fractal Bull/Bear values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<FractalResult> GetFractal<TQuote>(this IEnumerable<TQuote> quotes, int windowSpan = 2, EndType endType = EndType.HighLow) where TQuote : IQuote
|
||
{
|
||
return quotes.ToSortedList().CalcFractal(windowSpan, windowSpan, endType);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Williams Fractal is a retrospective price pattern that identifies a central high or low point over a lookback window.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Fractal/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="leftSpan">Number of span periods to the left of the evaluation period.</param><param name="rightSpan">Number of span periods to the right of the evaluation period.</param><param name="endType">Determines use of Close or High/Low wicks for points.</param><returns>Time series of Williams Fractal Bull/Bear values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<FractalResult> GetFractal<TQuote>(this IEnumerable<TQuote> quotes, int leftSpan, int rightSpan, EndType endType = EndType.HighLow) where TQuote : IQuote
|
||
{
|
||
return quotes.ToSortedList().CalcFractal(leftSpan, rightSpan, endType);
|
||
}
|
||
|
||
internal static List<FractalResult> CalcFractal<TQuote>(this List<TQuote> quotesList, int leftSpan, int rightSpan, EndType endType) where TQuote : IQuote
|
||
{
|
||
ValidateFractal(Math.Min(leftSpan, rightSpan));
|
||
List<FractalResult> list = new List<FractalResult>(quotesList.Count);
|
||
checked
|
||
{
|
||
for (int i = 0; i < quotesList.Count; i++)
|
||
{
|
||
TQuote val = quotesList[i];
|
||
FractalResult fractalResult = new FractalResult(val.Date);
|
||
list.Add(fractalResult);
|
||
if (i + 1 <= leftSpan || i + 1 > quotesList.Count - rightSpan)
|
||
{
|
||
continue;
|
||
}
|
||
bool flag = true;
|
||
bool flag2 = true;
|
||
decimal num = ((endType == EndType.Close) ? val.Close : val.High);
|
||
decimal num2 = ((endType == EndType.Close) ? val.Close : val.Low);
|
||
for (int j = i - leftSpan; j <= i + rightSpan; j++)
|
||
{
|
||
if (j != i)
|
||
{
|
||
TQuote val2 = quotesList[j];
|
||
decimal num3 = ((endType == EndType.Close) ? val2.Close : val2.High);
|
||
decimal num4 = ((endType == EndType.Close) ? val2.Close : val2.Low);
|
||
if (num <= num3)
|
||
{
|
||
flag = false;
|
||
}
|
||
if (num2 >= num4)
|
||
{
|
||
flag2 = false;
|
||
}
|
||
}
|
||
}
|
||
if (flag)
|
||
{
|
||
fractalResult.FractalBear = num;
|
||
}
|
||
if (flag2)
|
||
{
|
||
fractalResult.FractalBull = num2;
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateFractal(int windowSpan)
|
||
{
|
||
if (windowSpan < 2)
|
||
{
|
||
throw new ArgumentOutOfRangeException("windowSpan", windowSpan, "Window span must be at least 2 for Fractal.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes non-essential records containing null values with unique consideration for
|
||
/// this indicator. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#condense?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator results to evaluate.</param><returns>Time series of
|
||
/// indicator results, condensed.</returns>
|
||
public static IEnumerable<FractalResult> Condense(this IEnumerable<FractalResult> results)
|
||
{
|
||
List<FractalResult> list = results.ToList();
|
||
list.RemoveAll((FractalResult x) => !x.FractalBull.HasValue && !x.FractalBear.HasValue);
|
||
return list.ToSortedList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gator Oscillator is an expanded view of Williams Alligator.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Gator/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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><returns>Time series of Gator values.</returns>
|
||
public static IEnumerable<GatorResult> GetGator<TQuote>(this IEnumerable<TQuote> quotes) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.HL2).GetAlligator().ToList()
|
||
.CalcGator();
|
||
}
|
||
|
||
public static IEnumerable<GatorResult> GetGator(this IEnumerable<AlligatorResult> alligator)
|
||
{
|
||
return alligator.ToList().CalcGator();
|
||
}
|
||
|
||
public static IEnumerable<GatorResult> GetGator(this IEnumerable<IReusableResult> results)
|
||
{
|
||
return results.ToTuple().GetAlligator().ToList()
|
||
.CalcGator()
|
||
.SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<GatorResult> GetGator(this IEnumerable<(DateTime, double)> priceTuples)
|
||
{
|
||
return priceTuples.ToSortedList().GetAlligator().ToList()
|
||
.CalcGator();
|
||
}
|
||
|
||
internal static List<GatorResult> CalcGator(this List<AlligatorResult> alligator)
|
||
{
|
||
List<GatorResult> list = alligator.Select((AlligatorResult x) => new GatorResult(x.Date)
|
||
{
|
||
Upper = (x.Jaw - x.Teeth).Abs(),
|
||
Lower = 0.0 - (x.Teeth - x.Lips).Abs()
|
||
}).ToList();
|
||
checked
|
||
{
|
||
for (int num = 1; num < list.Count; num++)
|
||
{
|
||
GatorResult gatorResult = list[num];
|
||
GatorResult gatorResult2 = list[num - 1];
|
||
gatorResult.UpperIsExpanding = (gatorResult2.Upper.HasValue ? new bool?(gatorResult.Upper > gatorResult2.Upper) : ((bool?)null));
|
||
gatorResult.LowerIsExpanding = (gatorResult2.Lower.HasValue ? new bool?(gatorResult.Lower < gatorResult2.Lower) : ((bool?)null));
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes non-essential records containing null values with unique consideration for
|
||
/// this indicator. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#condense?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator results to evaluate.</param><returns>Time series of
|
||
/// indicator results, condensed.</returns>
|
||
public static IEnumerable<GatorResult> Condense(this IEnumerable<GatorResult> results)
|
||
{
|
||
List<GatorResult> list = results.ToList();
|
||
list.RemoveAll((GatorResult x) => !x.Upper.HasValue && !x.Lower.HasValue);
|
||
return list.ToSortedList();
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<GatorResult> RemoveWarmupPeriods(this IEnumerable<GatorResult> results)
|
||
{
|
||
return results.Remove(150);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Heikin-Ashi is a modified candlestick pattern that uses prior day for smoothing.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/HeikinAshi/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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><returns>Time series of Heikin-Ashi candlestick values.</returns>
|
||
public static IEnumerable<HeikinAshiResult> GetHeikinAshi<TQuote>(this IEnumerable<TQuote> quotes) where TQuote : IQuote
|
||
{
|
||
return quotes.ToSortedList().CalcHeikinAshi();
|
||
}
|
||
|
||
internal static List<HeikinAshiResult> CalcHeikinAshi<TQuote>(this List<TQuote> quotesList) where TQuote : IQuote
|
||
{
|
||
int count = quotesList.Count;
|
||
List<HeikinAshiResult> list = new List<HeikinAshiResult>(count);
|
||
decimal num = decimal.MinValue;
|
||
decimal num2 = decimal.MinValue;
|
||
if (count > 0)
|
||
{
|
||
TQuote val = quotesList[0];
|
||
num = val.Open;
|
||
num2 = val.Close;
|
||
}
|
||
for (int i = 0; i < count; i = checked(i + 1))
|
||
{
|
||
TQuote val2 = quotesList[i];
|
||
decimal num3 = (val2.Open + val2.High + val2.Low + val2.Close) / 4m;
|
||
decimal num4 = (num + num2) / 2m;
|
||
decimal high = new decimal[3] { val2.High, num4, num3 }.Max();
|
||
decimal low = new decimal[3] { val2.Low, num4, num3 }.Min();
|
||
HeikinAshiResult item = new HeikinAshiResult(val2.Date)
|
||
{
|
||
Open = num4,
|
||
High = high,
|
||
Low = low,
|
||
Close = num3,
|
||
Volume = val2.Volume
|
||
};
|
||
list.Add(item);
|
||
num = num4;
|
||
num2 = num3;
|
||
}
|
||
return list;
|
||
}
|
||
|
||
public static IEnumerable<Quote> ToQuotes(this IEnumerable<HeikinAshiResult> results)
|
||
{
|
||
return (from x in results
|
||
select new Quote
|
||
{
|
||
Date = x.Date,
|
||
Open = x.Open,
|
||
High = x.High,
|
||
Low = x.Low,
|
||
Close = x.Close,
|
||
Volume = x.Volume
|
||
} into x
|
||
orderby x.Date
|
||
select x).ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Hull Moving Average (HMA) is a modified weighted average of price over N lookback periods that reduces lag.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Hma/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><returns>Time series of HMA values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<HmaResult> GetHma<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcHma(lookbackPeriods);
|
||
}
|
||
|
||
public static IEnumerable<HmaResult> GetHma(this IEnumerable<IReusableResult> results, int lookbackPeriods)
|
||
{
|
||
return results.ToTuple().CalcHma(lookbackPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<HmaResult> GetHma(this IEnumerable<(DateTime, double)> priceTuples, int lookbackPeriods)
|
||
{
|
||
return priceTuples.ToSortedList().CalcHma(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<HmaResult> CalcHma(this List<(DateTime, double)> tpList, int lookbackPeriods)
|
||
{
|
||
ValidateHma(lookbackPeriods);
|
||
int num = checked(lookbackPeriods - 1);
|
||
List<(DateTime, double)> list = new List<(DateTime, double)>();
|
||
List<WmaResult> list2 = tpList.GetWma(lookbackPeriods).ToList();
|
||
List<WmaResult> list3 = tpList.GetWma(lookbackPeriods / 2).ToList();
|
||
checked
|
||
{
|
||
for (int i = 0; i < tpList.Count; i++)
|
||
{
|
||
DateTime item = tpList[i].Item1;
|
||
WmaResult wmaResult = list2[i];
|
||
WmaResult wmaResult2 = list3[i];
|
||
if (i >= num)
|
||
{
|
||
(DateTime, double) item2 = (item, wmaResult2.Wma.Null2NaN() * 2.0 - wmaResult.Wma.Null2NaN());
|
||
list.Add(item2);
|
||
}
|
||
}
|
||
int lookbackPeriods2 = (int)Math.Sqrt(lookbackPeriods);
|
||
List<HmaResult> list4 = (from x in tpList.Take(num)
|
||
select new HmaResult(x.Item1)).ToList();
|
||
List<HmaResult> collection = (from x in list.CalcWma(lookbackPeriods2)
|
||
select new HmaResult(x.Date)
|
||
{
|
||
Hma = x.Wma
|
||
}).ToList();
|
||
list4.AddRange(collection);
|
||
return list4.ToSortedList();
|
||
}
|
||
}
|
||
|
||
private static void ValidateHma(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 1)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 1 for HMA.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<HmaResult> RemoveWarmupPeriods(this IEnumerable<HmaResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((HmaResult x) => x.Hma.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Hilbert Transform Instantaneous Trendline (HTL) is a 5-period trendline of high/low price that uses signal processing to reduce noise.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/HtTrendline/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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><returns>Time series of HTL values and smoothed price.</returns>
|
||
public static IEnumerable<HtlResult> GetHtTrendline<TQuote>(this IEnumerable<TQuote> quotes) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.HL2).CalcHtTrendline();
|
||
}
|
||
|
||
public static IEnumerable<HtlResult> GetHtTrendline(this IEnumerable<IReusableResult> results)
|
||
{
|
||
return results.ToTuple().CalcHtTrendline().SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<HtlResult> GetHtTrendline(this IEnumerable<(DateTime, double)> priceTuples)
|
||
{
|
||
return priceTuples.ToSortedList().CalcHtTrendline();
|
||
}
|
||
|
||
internal static List<HtlResult> CalcHtTrendline(this List<(DateTime, double)> tpList)
|
||
{
|
||
int count = tpList.Count;
|
||
List<HtlResult> list = new List<HtlResult>(count);
|
||
double[] array = new double[count];
|
||
double[] array2 = new double[count];
|
||
double[] array3 = new double[count];
|
||
double[] array4 = new double[count];
|
||
double[] array5 = new double[count];
|
||
double[] array6 = new double[count];
|
||
double[] array7 = new double[count];
|
||
double[] array8 = new double[count];
|
||
double[] array9 = new double[count];
|
||
double[] array10 = new double[count];
|
||
double[] array11 = new double[count];
|
||
double[] array12 = new double[count];
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
var (date, num) = tpList[i];
|
||
array[i] = num;
|
||
HtlResult htlResult = new HtlResult(date);
|
||
list.Add(htlResult);
|
||
if (i > 5)
|
||
{
|
||
double num2 = 0.075 * array4[i - 1] + 0.54;
|
||
array2[i] = (4.0 * array[i] + 3.0 * array[i - 1] + 2.0 * array[i - 2] + array[i - 3]) / 10.0;
|
||
array3[i] = (0.0962 * array2[i] + 0.5769 * array2[i - 2] - 0.5769 * array2[i - 4] - 0.0962 * array2[i - 6]) * num2;
|
||
array5[i] = (0.0962 * array3[i] + 0.5769 * array3[i - 2] - 0.5769 * array3[i - 4] - 0.0962 * array3[i - 6]) * num2;
|
||
array6[i] = array3[i - 3];
|
||
double num3 = (0.0962 * array6[i] + 0.5769 * array6[i - 2] - 0.5769 * array6[i - 4] - 0.0962 * array6[i - 6]) * num2;
|
||
double num4 = (0.0962 * array5[i] + 0.5769 * array5[i - 2] - 0.5769 * array5[i - 4] - 0.0962 * array5[i - 6]) * num2;
|
||
array8[i] = array6[i] - num4;
|
||
array7[i] = array5[i] + num3;
|
||
array8[i] = 0.2 * array8[i] + 0.8 * array8[i - 1];
|
||
array7[i] = 0.2 * array7[i] + 0.8 * array7[i - 1];
|
||
array9[i] = array8[i] * array8[i - 1] + array7[i] * array7[i - 1];
|
||
array10[i] = array8[i] * array7[i - 1] - array7[i] * array8[i - 1];
|
||
array9[i] = 0.2 * array9[i] + 0.8 * array9[i - 1];
|
||
array10[i] = 0.2 * array10[i] + 0.8 * array10[i - 1];
|
||
array4[i] = ((array10[i] != 0.0 && array9[i] != 0.0) ? (Math.PI * 2.0 / Math.Atan(array10[i] / array9[i])) : 0.0);
|
||
array4[i] = ((array4[i] > 1.5 * array4[i - 1]) ? (1.5 * array4[i - 1]) : array4[i]);
|
||
array4[i] = ((array4[i] < 0.67 * array4[i - 1]) ? (0.67 * array4[i - 1]) : array4[i]);
|
||
array4[i] = ((array4[i] < 6.0) ? 6.0 : array4[i]);
|
||
array4[i] = ((array4[i] > 50.0) ? 50.0 : array4[i]);
|
||
array4[i] = 0.2 * array4[i] + 0.8 * array4[i - 1];
|
||
array11[i] = 0.33 * array4[i] + 0.67 * array11[i - 1];
|
||
int num5 = (int)(double.IsNaN(array11[i]) ? 0.0 : (array11[i] + 0.5));
|
||
double num6 = 0.0;
|
||
for (int j = i - num5 + 1; j <= i; j++)
|
||
{
|
||
if (j >= 0)
|
||
{
|
||
num6 += array[j];
|
||
}
|
||
else
|
||
{
|
||
num5--;
|
||
}
|
||
}
|
||
array12[i] = ((num5 > 0) ? (num6 / (double)num5) : array[i]);
|
||
htlResult.DcPeriods = ((num5 > 0) ? new int?(num5) : ((int?)null));
|
||
htlResult.Trendline = ((i >= 11) ? ((4.0 * array12[i] + 3.0 * array12[i - 1] + 2.0 * array12[i - 2] + array12[i - 3]) / 10.0).NaN2Null() : array[i].NaN2Null());
|
||
htlResult.SmoothPrice = ((4.0 * array[i] + 3.0 * array[i - 1] + 2.0 * array[i - 2] + array[i - 3]) / 10.0).NaN2Null();
|
||
}
|
||
else
|
||
{
|
||
htlResult.Trendline = array[i].NaN2Null();
|
||
htlResult.SmoothPrice = null;
|
||
array4[i] = 0.0;
|
||
array2[i] = 0.0;
|
||
array3[i] = 0.0;
|
||
array6[i] = 0.0;
|
||
array5[i] = 0.0;
|
||
array8[i] = 0.0;
|
||
array7[i] = 0.0;
|
||
array9[i] = 0.0;
|
||
array10[i] = 0.0;
|
||
array11[i] = 0.0;
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<HtlResult> RemoveWarmupPeriods(this IEnumerable<HtlResult> results)
|
||
{
|
||
return results.Remove(100);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Hurst Exponent is a measure of randomness, trending, and mean-reverting tendencies of incremental return values.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Hurst/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of lookback periods.</param><returns>Time series of Hurst Exponent values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<HurstResult> GetHurst<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods = 100) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcHurst(lookbackPeriods);
|
||
}
|
||
|
||
public static IEnumerable<HurstResult> GetHurst(this IEnumerable<IReusableResult> results, int lookbackPeriods)
|
||
{
|
||
return results.ToTuple().CalcHurst(lookbackPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<HurstResult> GetHurst(this IEnumerable<(DateTime, double)> priceTuples, int lookbackPeriods)
|
||
{
|
||
return priceTuples.ToSortedList().CalcHurst(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<HurstResult> CalcHurst(this List<(DateTime, double)> tpList, int lookbackPeriods)
|
||
{
|
||
ValidateHurst(lookbackPeriods);
|
||
int count = tpList.Count;
|
||
List<HurstResult> list = new List<HurstResult>(count);
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
HurstResult hurstResult = new HurstResult(tpList[i].Item1);
|
||
list.Add(hurstResult);
|
||
if (i + 1 > lookbackPeriods)
|
||
{
|
||
double[] array = new double[lookbackPeriods];
|
||
int num = 0;
|
||
double num2 = tpList[i - lookbackPeriods].Item2;
|
||
for (int j = i + 1 - lookbackPeriods; j <= i; j++)
|
||
{
|
||
double item = tpList[j].Item2;
|
||
array[num] = ((num2 != 0.0) ? (item / num2 - 1.0) : double.NaN);
|
||
num2 = item;
|
||
num++;
|
||
}
|
||
hurstResult.HurstExponent = CalcHurstWindow(array).NaN2Null();
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static double CalcHurstWindow(double[] values)
|
||
{
|
||
int num = values.Length;
|
||
int num2 = 0;
|
||
int num3 = 0;
|
||
int num4 = 1;
|
||
checked
|
||
{
|
||
while (num4 <= 32 && unchecked(num / num4) >= 8)
|
||
{
|
||
num2 = num4;
|
||
num3++;
|
||
num4 *= 2;
|
||
}
|
||
double[] array = new double[num3];
|
||
double[] array2 = new double[num3];
|
||
int num5 = 0;
|
||
for (int num6 = 1; num6 <= num2; num6 *= 2)
|
||
{
|
||
int num7 = unchecked(num / num6);
|
||
double num8 = 0.0;
|
||
int num9 = num - num7 * num6;
|
||
for (int i = 1; i <= num6; i++)
|
||
{
|
||
double num10 = 0.0;
|
||
for (int j = num9; j < num9 + num7; j++)
|
||
{
|
||
num10 += values[j];
|
||
}
|
||
double num11 = num10 / (double)num7;
|
||
double num12 = 0.0;
|
||
double num13 = 0.0;
|
||
double num14 = values[num9] - num11;
|
||
double num15 = values[num9] - num11;
|
||
for (int k = num9; k < num9 + num7; k++)
|
||
{
|
||
double num16 = values[k] - num11;
|
||
num12 += num16;
|
||
num15 = ((num12 < num15) ? num12 : num15);
|
||
num14 = ((num12 > num14) ? num12 : num14);
|
||
num13 += num16 * num16;
|
||
}
|
||
double num17 = num14 - num15;
|
||
double num18 = Math.Sqrt(num13 / (double)num7);
|
||
double num19 = ((num18 != 0.0) ? (num17 / num18) : 0.0);
|
||
num8 += num19;
|
||
num9 += num7;
|
||
}
|
||
array2[num5] = Math.Log10(num7);
|
||
array[num5] = Math.Log10(num8 / (double)num6);
|
||
num5++;
|
||
}
|
||
return Numerix.Slope(array2, array);
|
||
}
|
||
}
|
||
|
||
private static void ValidateHurst(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods < 20)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be at least 20 for Hurst Exponent.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<HurstResult> RemoveWarmupPeriods(this IEnumerable<HurstResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((HurstResult x) => x.HurstExponent.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Ichimoku Cloud, also known as Ichimoku Kinkō Hyō, is a collection of indicators that depict support and resistance, momentum, and trend direction.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Ichimoku/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="tenkanPeriods">Number of periods in the Tenkan-sen midpoint evaluation.</param><param name="kijunPeriods">Number of periods in the shorter Kijun-sen midpoint evaluation. This value is also used to offset Senkou and Chinkou spans.</param><param name="senkouBPeriods">Number of periods in the longer Senkou leading span B midpoint evaluation.</param><returns>Time series of Ichimoku Cloud values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<IchimokuResult> GetIchimoku<TQuote>(this IEnumerable<TQuote> quotes, int tenkanPeriods = 9, int kijunPeriods = 26, int senkouBPeriods = 52) where TQuote : IQuote
|
||
{
|
||
return quotes.ToSortedList().CalcIchimoku(tenkanPeriods, kijunPeriods, senkouBPeriods, kijunPeriods, kijunPeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Ichimoku Cloud, also known as Ichimoku Kinkō Hyō, is a collection of indicators that depict support and resistance, momentum, and trend direction.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Ichimoku/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="tenkanPeriods">Number of periods in the Tenkan-sen midpoint evaluation.</param><param name="kijunPeriods">Number of periods in the shorter Kijun-sen midpoint evaluation.</param><param name="senkouBPeriods">Number of periods in the longer Senkou leading span B midpoint evaluation.</param><param name="offsetPeriods">Number of periods to displace the Senkou and Chikou Spans.</param><returns>Time series of Ichimoku Cloud values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<IchimokuResult> GetIchimoku<TQuote>(this IEnumerable<TQuote> quotes, int tenkanPeriods, int kijunPeriods, int senkouBPeriods, int offsetPeriods) where TQuote : IQuote
|
||
{
|
||
return quotes.ToSortedList().CalcIchimoku(tenkanPeriods, kijunPeriods, senkouBPeriods, offsetPeriods, offsetPeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Ichimoku Cloud, also known as Ichimoku Kinkō Hyō, is a collection of indicators that depict support and resistance, momentum, and trend direction.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Ichimoku/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="tenkanPeriods">Number of periods in the Tenkan-sen midpoint evaluation.</param><param name="kijunPeriods">Number of periods in the shorter Kijun-sen midpoint evaluation.</param><param name="senkouBPeriods">Number of periods in the longer Senkou leading span B midpoint evaluation.</param><param name="senkouOffset">Number of periods to displace the Senkou Spans.</param><param name="chikouOffset">Number of periods in displace the Chikou Span.</param><returns>Time series of Ichimoku Cloud values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<IchimokuResult> GetIchimoku<TQuote>(this IEnumerable<TQuote> quotes, int tenkanPeriods, int kijunPeriods, int senkouBPeriods, int senkouOffset, int chikouOffset) where TQuote : IQuote
|
||
{
|
||
return quotes.ToSortedList().CalcIchimoku(tenkanPeriods, kijunPeriods, senkouBPeriods, senkouOffset, chikouOffset);
|
||
}
|
||
|
||
internal static List<IchimokuResult> CalcIchimoku<TQuote>(this List<TQuote> quotesList, int tenkanPeriods, int kijunPeriods, int senkouBPeriods, int senkouOffset, int chikouOffset) where TQuote : IQuote
|
||
{
|
||
ValidateIchimoku(tenkanPeriods, kijunPeriods, senkouBPeriods, senkouOffset, chikouOffset);
|
||
int count = quotesList.Count;
|
||
List<IchimokuResult> list = new List<IchimokuResult>(count);
|
||
checked
|
||
{
|
||
int num = Math.Max(2 * senkouOffset, Math.Max(tenkanPeriods, kijunPeriods)) - 1;
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
IchimokuResult ichimokuResult = new IchimokuResult(quotesList[i].Date);
|
||
list.Add(ichimokuResult);
|
||
CalcIchimokuTenkanSen(i, quotesList, ichimokuResult, tenkanPeriods);
|
||
CalcIchimokuKijunSen(i, quotesList, ichimokuResult, kijunPeriods);
|
||
if (i >= num)
|
||
{
|
||
IchimokuResult ichimokuResult2 = list[i - senkouOffset];
|
||
if (ichimokuResult2 != null && ichimokuResult2.TenkanSen.HasValue && ichimokuResult2.KijunSen.HasValue)
|
||
{
|
||
ichimokuResult.SenkouSpanA = (ichimokuResult2.TenkanSen + ichimokuResult2.KijunSen) / (decimal?)2;
|
||
}
|
||
}
|
||
CalcIchimokuSenkouB(i, quotesList, ichimokuResult, senkouOffset, senkouBPeriods);
|
||
if (i + chikouOffset < quotesList.Count)
|
||
{
|
||
ichimokuResult.ChikouSpan = quotesList[i + chikouOffset].Close;
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void CalcIchimokuTenkanSen<TQuote>(int i, List<TQuote> quotesList, IchimokuResult result, int tenkanPeriods) where TQuote : IQuote
|
||
{
|
||
checked
|
||
{
|
||
if (i < tenkanPeriods - 1)
|
||
{
|
||
return;
|
||
}
|
||
decimal num = default(decimal);
|
||
decimal num2 = decimal.MaxValue;
|
||
for (int j = i - tenkanPeriods + 1; j <= i; j++)
|
||
{
|
||
TQuote val = quotesList[j];
|
||
if (val.High > num)
|
||
{
|
||
num = val.High;
|
||
}
|
||
if (val.Low < num2)
|
||
{
|
||
num2 = val.Low;
|
||
}
|
||
}
|
||
result.TenkanSen = ((num2 == decimal.MaxValue) ? ((decimal?)null) : new decimal?((num2 + num) / 2m));
|
||
}
|
||
}
|
||
|
||
private static void CalcIchimokuKijunSen<TQuote>(int i, List<TQuote> quotesList, IchimokuResult result, int kijunPeriods) where TQuote : IQuote
|
||
{
|
||
checked
|
||
{
|
||
if (i < kijunPeriods - 1)
|
||
{
|
||
return;
|
||
}
|
||
decimal num = default(decimal);
|
||
decimal num2 = decimal.MaxValue;
|
||
for (int j = i - kijunPeriods + 1; j <= i; j++)
|
||
{
|
||
TQuote val = quotesList[j];
|
||
if (val.High > num)
|
||
{
|
||
num = val.High;
|
||
}
|
||
if (val.Low < num2)
|
||
{
|
||
num2 = val.Low;
|
||
}
|
||
}
|
||
result.KijunSen = ((num2 == decimal.MaxValue) ? ((decimal?)null) : new decimal?((num2 + num) / 2m));
|
||
}
|
||
}
|
||
|
||
private static void CalcIchimokuSenkouB<TQuote>(int i, List<TQuote> quotesList, IchimokuResult result, int senkouOffset, int senkouBPeriods) where TQuote : IQuote
|
||
{
|
||
checked
|
||
{
|
||
if (i < senkouOffset + senkouBPeriods - 1)
|
||
{
|
||
return;
|
||
}
|
||
decimal num = default(decimal);
|
||
decimal num2 = decimal.MaxValue;
|
||
for (int j = i - senkouOffset - senkouBPeriods + 1; j <= i - senkouOffset; j++)
|
||
{
|
||
TQuote val = quotesList[j];
|
||
if (val.High > num)
|
||
{
|
||
num = val.High;
|
||
}
|
||
if (val.Low < num2)
|
||
{
|
||
num2 = val.Low;
|
||
}
|
||
}
|
||
result.SenkouSpanB = ((num2 == decimal.MaxValue) ? ((decimal?)null) : new decimal?((num2 + num) / 2m));
|
||
}
|
||
}
|
||
|
||
private static void ValidateIchimoku(int tenkanPeriods, int kijunPeriods, int senkouBPeriods, int senkouOffset, int chikouOffset)
|
||
{
|
||
if (tenkanPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("tenkanPeriods", tenkanPeriods, "Tenkan periods must be greater than 0 for Ichimoku Cloud.");
|
||
}
|
||
if (kijunPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("kijunPeriods", kijunPeriods, "Kijun periods must be greater than 0 for Ichimoku Cloud.");
|
||
}
|
||
if (senkouBPeriods <= kijunPeriods)
|
||
{
|
||
throw new ArgumentOutOfRangeException("senkouBPeriods", senkouBPeriods, "Senkou B periods must be greater than Kijun periods for Ichimoku Cloud.");
|
||
}
|
||
if (senkouOffset < 0 || chikouOffset < 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("senkouOffset", senkouOffset, "Senkou and Chikou offset periods must be non-negative for Ichimoku Cloud.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes non-essential records containing null values with unique consideration for
|
||
/// this indicator. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#condense?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator results to evaluate.</param><returns>Time series of
|
||
/// indicator results, condensed.</returns>
|
||
public static IEnumerable<IchimokuResult> Condense(this IEnumerable<IchimokuResult> results)
|
||
{
|
||
List<IchimokuResult> list = results.ToList();
|
||
list.RemoveAll((IchimokuResult x) => !x.TenkanSen.HasValue && !x.KijunSen.HasValue && !x.SenkouSpanA.HasValue && !x.SenkouSpanB.HasValue && !x.ChikouSpan.HasValue);
|
||
return list.ToSortedList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Kaufman’s Adaptive Moving Average (KAMA) is an volatility adaptive moving average of price over configurable lookback periods.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Kama/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="erPeriods">Number of Efficiency Ratio (volatility) periods.</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 KAMA values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<KamaResult> GetKama<TQuote>(this IEnumerable<TQuote> quotes, int erPeriods = 10, int fastPeriods = 2, int slowPeriods = 30) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcKama(erPeriods, fastPeriods, slowPeriods);
|
||
}
|
||
|
||
public static IEnumerable<KamaResult> GetKama(this IEnumerable<IReusableResult> results, int erPeriods = 10, int fastPeriods = 2, int slowPeriods = 30)
|
||
{
|
||
return results.ToTuple().CalcKama(erPeriods, fastPeriods, slowPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<KamaResult> GetKama(this IEnumerable<(DateTime, double)> priceTuples, int erPeriods = 10, int fastPeriods = 2, int slowPeriods = 30)
|
||
{
|
||
return priceTuples.ToSortedList().CalcKama(erPeriods, fastPeriods, slowPeriods);
|
||
}
|
||
|
||
internal static List<KamaResult> CalcKama(this List<(DateTime, double)> tpList, int erPeriods, int fastPeriods, int slowPeriods)
|
||
{
|
||
ValidateKama(erPeriods, fastPeriods, slowPeriods);
|
||
int count = tpList.Count;
|
||
List<KamaResult> list = new List<KamaResult>(count);
|
||
checked
|
||
{
|
||
double num = 2.0 / (double)(fastPeriods + 1);
|
||
double num2 = 2.0 / (double)(slowPeriods + 1);
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
(DateTime, double) tuple = tpList[i];
|
||
DateTime item = tuple.Item1;
|
||
double item2 = tuple.Item2;
|
||
KamaResult kamaResult = new KamaResult(item);
|
||
list.Add(kamaResult);
|
||
if (i + 1 > erPeriods)
|
||
{
|
||
double num3 = Math.Abs(item2 - tpList[i - erPeriods].Item2);
|
||
double num4 = 0.0;
|
||
for (int j = i - erPeriods + 1; j <= i; j++)
|
||
{
|
||
num4 += Math.Abs(tpList[j].Item2 - tpList[j - 1].Item2);
|
||
}
|
||
if (num4 != 0.0)
|
||
{
|
||
double num5 = num3 / num4;
|
||
kamaResult.ER = num5.NaN2Null();
|
||
double num6 = num5 * (num - num2) + num2;
|
||
double? kama = list[i - 1].Kama;
|
||
kamaResult.Kama = (kama + num6 * num6 * (item2 - kama)).NaN2Null();
|
||
}
|
||
else
|
||
{
|
||
kamaResult.ER = 0.0;
|
||
kamaResult.Kama = item2.NaN2Null();
|
||
}
|
||
}
|
||
else if (i + 1 == erPeriods)
|
||
{
|
||
kamaResult.Kama = item2.NaN2Null();
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateKama(int erPeriods, int fastPeriods, int slowPeriods)
|
||
{
|
||
if (erPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("erPeriods", erPeriods, "Efficiency Ratio periods must be greater than 0 for KAMA.");
|
||
}
|
||
if (fastPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("fastPeriods", fastPeriods, "Fast EMA periods must be greater than 0 for KAMA.");
|
||
}
|
||
if (slowPeriods <= fastPeriods)
|
||
{
|
||
throw new ArgumentOutOfRangeException("slowPeriods", slowPeriods, "Slow EMA periods must be greater than Fast EMA period for KAMA.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<KamaResult> RemoveWarmupPeriods(this IEnumerable<KamaResult> results)
|
||
{
|
||
int num = results.ToList().FindIndex((KamaResult x) => x.ER.HasValue);
|
||
return results.Remove(checked(Math.Max(num + 100, 10 * num)));
|
||
}
|
||
|
||
/// <summary>
|
||
/// Keltner Channels are based on an EMA centerline and ATR band widths. See also STARC Bands for an SMA centerline equivalent.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Keltner/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="emaPeriods">Number of periods for the centerline EMA.</param><param name="multiplier">ATR multiplier sets the width of the channel.</param><param name="atrPeriods">Number of periods in the ATR evaluation.</param><returns>Time series of Keltner Channel values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<KeltnerResult> GetKeltner<TQuote>(this IEnumerable<TQuote> quotes, int emaPeriods = 20, double multiplier = 2.0, int atrPeriods = 10) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcKeltner(emaPeriods, multiplier, atrPeriods);
|
||
}
|
||
|
||
internal static List<KeltnerResult> CalcKeltner(this List<QuoteD> qdList, int emaPeriods, double multiplier, int atrPeriods)
|
||
{
|
||
ValidateKeltner(emaPeriods, multiplier, atrPeriods);
|
||
int count = qdList.Count;
|
||
List<KeltnerResult> list = new List<KeltnerResult>(count);
|
||
List<EmaResult> list2 = qdList.ToTuple(CandlePart.Close).CalcEma(emaPeriods).ToList();
|
||
List<AtrResult> list3 = qdList.CalcAtr(atrPeriods).ToList();
|
||
int num = Math.Max(emaPeriods, atrPeriods);
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
KeltnerResult keltnerResult = new KeltnerResult(qdList[i].Date);
|
||
list.Add(keltnerResult);
|
||
if (i + 1 >= num)
|
||
{
|
||
EmaResult emaResult = list2[i];
|
||
double? num2 = list3[i].Atr * multiplier;
|
||
keltnerResult.UpperBand = emaResult.Ema + num2;
|
||
keltnerResult.LowerBand = emaResult.Ema - num2;
|
||
keltnerResult.Centerline = emaResult.Ema;
|
||
keltnerResult.Width = ((keltnerResult.Centerline == 0.0) ? ((double?)null) : ((keltnerResult.UpperBand - keltnerResult.LowerBand) / keltnerResult.Centerline));
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateKeltner(int emaPeriods, double multiplier, int atrPeriods)
|
||
{
|
||
if (emaPeriods <= 1)
|
||
{
|
||
throw new ArgumentOutOfRangeException("emaPeriods", emaPeriods, "EMA periods must be greater than 1 for Keltner Channel.");
|
||
}
|
||
if (atrPeriods <= 1)
|
||
{
|
||
throw new ArgumentOutOfRangeException("atrPeriods", atrPeriods, "ATR periods must be greater than 1 for Keltner Channel.");
|
||
}
|
||
if (multiplier <= 0.0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("multiplier", multiplier, "Multiplier must be greater than 0 for Keltner Channel.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes non-essential records containing null values with unique consideration for
|
||
/// this indicator. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#condense?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator results to evaluate.</param><returns>Time series of
|
||
/// indicator results, condensed.</returns>
|
||
public static IEnumerable<KeltnerResult> Condense(this IEnumerable<KeltnerResult> results)
|
||
{
|
||
List<KeltnerResult> list = results.ToList();
|
||
list.RemoveAll((KeltnerResult x) => !x.UpperBand.HasValue && !x.LowerBand.HasValue && !x.Centerline.HasValue);
|
||
return list.ToSortedList();
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<KeltnerResult> RemoveWarmupPeriods(this IEnumerable<KeltnerResult> results)
|
||
{
|
||
checked
|
||
{
|
||
int num = results.ToList().FindIndex((KeltnerResult x) => x.Width.HasValue) + 1;
|
||
return results.Remove(Math.Max(2 * num, num + 100));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Klinger Oscillator depicts volume-based divergence between short and long-term money flow.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Klinger/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="fastPeriods">Number of periods for the short EMA.</param><param name="slowPeriods">Number of periods for the long EMA.</param><param name="signalPeriods">Number of periods Signal line.</param><returns>Time series of Klinger Oscillator values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<KvoResult> GetKvo<TQuote>(this IEnumerable<TQuote> quotes, int fastPeriods = 34, int slowPeriods = 55, int signalPeriods = 13) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcKvo(fastPeriods, slowPeriods, signalPeriods);
|
||
}
|
||
|
||
internal static List<KvoResult> CalcKvo(this List<QuoteD> qdList, int fastPeriods, int slowPeriods, int signalPeriods)
|
||
{
|
||
ValidateKlinger(fastPeriods, slowPeriods, signalPeriods);
|
||
int count = qdList.Count;
|
||
List<KvoResult> list = new List<KvoResult>(count);
|
||
double[] array = new double[count];
|
||
double[] array2 = new double[count];
|
||
double[] array3 = new double[count];
|
||
double[] array4 = new double[count];
|
||
double[] array5 = new double[count];
|
||
double[] array6 = new double[count];
|
||
double[] array7 = new double[count];
|
||
checked
|
||
{
|
||
double num = 2.0 / (double)(fastPeriods + 1);
|
||
double num2 = 2.0 / (double)(slowPeriods + 1);
|
||
double num3 = 2.0 / (double)(signalPeriods + 1);
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
QuoteD quoteD = qdList[i];
|
||
KvoResult kvoResult = new KvoResult(quoteD.Date);
|
||
list.Add(kvoResult);
|
||
array2[i] = quoteD.High + quoteD.Low + quoteD.Close;
|
||
array3[i] = quoteD.High - quoteD.Low;
|
||
if (i <= 0)
|
||
{
|
||
continue;
|
||
}
|
||
array[i] = ((array2[i] > array2[i - 1]) ? 1 : (-1));
|
||
if (i <= 1)
|
||
{
|
||
array4[i] = 0.0;
|
||
continue;
|
||
}
|
||
array4[i] = ((array[i] == array[i - 1]) ? (array4[i - 1] + array3[i]) : (array3[i - 1] + array3[i]));
|
||
array5[i] = ((array3[i] == array4[i] || quoteD.Volume == 0.0) ? 0.0 : ((array3[i] == 0.0) ? (quoteD.Volume * 2.0 * array[i] * 100.0) : ((array4[i] != 0.0) ? (quoteD.Volume * Math.Abs(2.0 * (array3[i] / array4[i] - 1.0)) * array[i] * 100.0) : array5[i - 1])));
|
||
if (i > fastPeriods + 1)
|
||
{
|
||
array6[i] = array5[i] * num + array6[i - 1] * (1.0 - num);
|
||
}
|
||
else if (i == fastPeriods + 1)
|
||
{
|
||
double num4 = 0.0;
|
||
for (int j = 2; j <= i; j++)
|
||
{
|
||
num4 += array5[j];
|
||
}
|
||
array6[i] = num4 / (double)fastPeriods;
|
||
}
|
||
if (i > slowPeriods + 1)
|
||
{
|
||
array7[i] = array5[i] * num2 + array7[i - 1] * (1.0 - num2);
|
||
}
|
||
else if (i == slowPeriods + 1)
|
||
{
|
||
double num5 = 0.0;
|
||
for (int k = 2; k <= i; k++)
|
||
{
|
||
num5 += array5[k];
|
||
}
|
||
array7[i] = num5 / (double)slowPeriods;
|
||
}
|
||
if (i < slowPeriods + 1)
|
||
{
|
||
continue;
|
||
}
|
||
kvoResult.Oscillator = array6[i] - array7[i];
|
||
if (i > slowPeriods + signalPeriods)
|
||
{
|
||
kvoResult.Signal = kvoResult.Oscillator * num3 + list[i - 1].Signal * (1.0 - num3);
|
||
}
|
||
else if (i == slowPeriods + signalPeriods)
|
||
{
|
||
double? num6 = 0.0;
|
||
for (int l = slowPeriods + 1; l <= i; l++)
|
||
{
|
||
num6 += list[l].Oscillator;
|
||
}
|
||
kvoResult.Signal = num6 / (double)signalPeriods;
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateKlinger(int fastPeriods, int slowPeriods, int signalPeriods)
|
||
{
|
||
if (fastPeriods <= 2)
|
||
{
|
||
throw new ArgumentOutOfRangeException("fastPeriods", fastPeriods, "Fast (short) Periods must be greater than 2 for Klinger Oscillator.");
|
||
}
|
||
if (slowPeriods <= fastPeriods)
|
||
{
|
||
throw new ArgumentOutOfRangeException("slowPeriods", slowPeriods, "Slow (long) Periods must be greater than Fast Periods for Klinger Oscillator.");
|
||
}
|
||
if (signalPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("signalPeriods", signalPeriods, "Signal Periods must be greater than 0 for Klinger Oscillator.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<KvoResult> RemoveWarmupPeriods(this IEnumerable<KvoResult> results)
|
||
{
|
||
checked
|
||
{
|
||
int num = results.ToList().FindIndex((KvoResult x) => x.Oscillator.HasValue) - 1;
|
||
return results.Remove(num + 150);
|
||
}
|
||
}
|
||
|
||
internal static List<MacdResult> CalcMacd(this List<(DateTime, double)> tpList, int fastPeriods, int slowPeriods, int signalPeriods)
|
||
{
|
||
ValidateMacd(fastPeriods, slowPeriods, signalPeriods);
|
||
List<EmaResult> list = tpList.CalcEma(fastPeriods);
|
||
List<EmaResult> list2 = tpList.CalcEma(slowPeriods);
|
||
int count = tpList.Count;
|
||
List<(DateTime, double)> list3 = new List<(DateTime, double)>();
|
||
List<MacdResult> list4 = new List<MacdResult>(count);
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
DateTime item = tpList[i].Item1;
|
||
EmaResult emaResult = list[i];
|
||
EmaResult emaResult2 = list2[i];
|
||
MacdResult macdResult = new MacdResult(item)
|
||
{
|
||
FastEma = emaResult.Ema,
|
||
SlowEma = emaResult2.Ema
|
||
};
|
||
list4.Add(macdResult);
|
||
if (i >= slowPeriods - 1)
|
||
{
|
||
double num = (emaResult.Ema - emaResult2.Ema).Null2NaN();
|
||
macdResult.Macd = num.NaN2Null();
|
||
(DateTime, double) item2 = (item, num);
|
||
list3.Add(item2);
|
||
}
|
||
}
|
||
List<EmaResult> list5 = list3.CalcEma(signalPeriods);
|
||
for (int j = slowPeriods - 1; j < count; j++)
|
||
{
|
||
MacdResult macdResult2 = list4[j];
|
||
EmaResult emaResult3 = list5[j + 1 - slowPeriods];
|
||
macdResult2.Signal = emaResult3.Ema.NaN2Null();
|
||
macdResult2.Histogram = (macdResult2.Macd - macdResult2.Signal).NaN2Null();
|
||
}
|
||
return list4;
|
||
}
|
||
}
|
||
|
||
private static void ValidateMacd(int fastPeriods, int slowPeriods, int signalPeriods)
|
||
{
|
||
if (fastPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("fastPeriods", fastPeriods, "Fast periods must be greater than 0 for MACD.");
|
||
}
|
||
if (signalPeriods < 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("signalPeriods", signalPeriods, "Signal periods must be greater than or equal to 0 for MACD.");
|
||
}
|
||
if (slowPeriods <= fastPeriods)
|
||
{
|
||
throw new ArgumentOutOfRangeException("slowPeriods", slowPeriods, "Slow periods must be greater than the fast period for MACD.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<MacdResult> RemoveWarmupPeriods(this IEnumerable<MacdResult> results)
|
||
{
|
||
checked
|
||
{
|
||
int num = results.ToList().FindIndex((MacdResult x) => x.Signal.HasValue) + 2;
|
||
return results.Remove(num + 250);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Moving Average Convergence/Divergence (MACD) is a simple oscillator view of two converging/diverging exponential moving averages.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Macd/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="fastPeriods">Number of periods in the Fast EMA.</param><param name="slowPeriods">Number of periods in the Slow EMA.</param><param name="signalPeriods">Number of periods for the Signal moving average.</param><returns>Time series of MACD values, including MACD, Signal, and Histogram.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<MacdResult> GetMacd<TQuote>(this IEnumerable<TQuote> quotes, int fastPeriods = 12, int slowPeriods = 26, int signalPeriods = 9) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcMacd(fastPeriods, slowPeriods, signalPeriods);
|
||
}
|
||
|
||
public static IEnumerable<MacdResult> GetMacd(this IEnumerable<IReusableResult> results, int fastPeriods = 12, int slowPeriods = 26, int signalPeriods = 9)
|
||
{
|
||
return results.ToTuple().CalcMacd(fastPeriods, slowPeriods, signalPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<MacdResult> GetMacd(this IEnumerable<(DateTime, double)> priceTuples, int fastPeriods = 12, int slowPeriods = 26, int signalPeriods = 9)
|
||
{
|
||
return priceTuples.ToSortedList().CalcMacd(fastPeriods, slowPeriods, signalPeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Moving Average Envelopes is a price band overlay that is offset from the moving average of price over a lookback window.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/MaEnvelopes/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><param name="percentOffset">Percent offset for envelope width.</param><param name="movingAverageType">Moving average type (e.g. EMA, HMA, TEMA, etc.).</param><returns>Time series of MA Envelopes values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<MaEnvelopeResult> GetMaEnvelopes<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods, double percentOffset = 2.5, MaType movingAverageType = MaType.SMA) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcMaEnvelopes(lookbackPeriods, percentOffset, movingAverageType);
|
||
}
|
||
|
||
public static IEnumerable<MaEnvelopeResult> GetMaEnvelopes(this IEnumerable<IReusableResult> results, int lookbackPeriods, double percentOffset = 2.5, MaType movingAverageType = MaType.SMA)
|
||
{
|
||
return results.ToTuple().CalcMaEnvelopes(lookbackPeriods, percentOffset, movingAverageType).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<MaEnvelopeResult> GetMaEnvelopes(this IEnumerable<(DateTime, double)> priceTuples, int lookbackPeriods, double percentOffset = 2.5, MaType movingAverageType = MaType.SMA)
|
||
{
|
||
return priceTuples.ToSortedList().CalcMaEnvelopes(lookbackPeriods, percentOffset, movingAverageType);
|
||
}
|
||
|
||
internal static IEnumerable<MaEnvelopeResult> CalcMaEnvelopes(this List<(DateTime, double)> tpList, int lookbackPeriods, double percentOffset, MaType movingAverageType)
|
||
{
|
||
ValidateMaEnvelopes(percentOffset);
|
||
double offsetRatio = percentOffset / 100.0;
|
||
return movingAverageType switch
|
||
{
|
||
MaType.ALMA => tpList.MaEnvAlma(lookbackPeriods, offsetRatio),
|
||
MaType.DEMA => tpList.MaEnvDema(lookbackPeriods, offsetRatio),
|
||
MaType.EMA => tpList.MaEnvEma(lookbackPeriods, offsetRatio),
|
||
MaType.EPMA => tpList.MaEnvEpma(lookbackPeriods, offsetRatio),
|
||
MaType.HMA => tpList.MaEnvHma(lookbackPeriods, offsetRatio),
|
||
MaType.SMA => tpList.MaEnvSma(lookbackPeriods, offsetRatio),
|
||
MaType.SMMA => tpList.MaEnvSmma(lookbackPeriods, offsetRatio),
|
||
MaType.TEMA => tpList.MaEnvTema(lookbackPeriods, offsetRatio),
|
||
MaType.WMA => tpList.MaEnvWma(lookbackPeriods, offsetRatio),
|
||
_ => throw new ArgumentOutOfRangeException("movingAverageType", movingAverageType, string.Format(invCulture, "Moving Average Envelopes does not support {0}.", Enum.GetName(typeof(MaType), movingAverageType))),
|
||
};
|
||
}
|
||
|
||
private static IEnumerable<MaEnvelopeResult> MaEnvAlma(this List<(DateTime, double)> tpList, int lookbackPeriods, double offsetRatio)
|
||
{
|
||
return from x in tpList.GetAlma(lookbackPeriods)
|
||
select new MaEnvelopeResult(x.Date)
|
||
{
|
||
Centerline = x.Alma,
|
||
UpperEnvelope = x.Alma + x.Alma * offsetRatio,
|
||
LowerEnvelope = x.Alma - x.Alma * offsetRatio
|
||
};
|
||
}
|
||
|
||
private static IEnumerable<MaEnvelopeResult> MaEnvDema(this List<(DateTime, double)> tpList, int lookbackPeriods, double offsetRatio)
|
||
{
|
||
return from x in tpList.GetDema(lookbackPeriods)
|
||
select new MaEnvelopeResult(x.Date)
|
||
{
|
||
Centerline = x.Dema,
|
||
UpperEnvelope = x.Dema + x.Dema * offsetRatio,
|
||
LowerEnvelope = x.Dema - x.Dema * offsetRatio
|
||
};
|
||
}
|
||
|
||
private static IEnumerable<MaEnvelopeResult> MaEnvEma(this List<(DateTime, double)> tpList, int lookbackPeriods, double offsetRatio)
|
||
{
|
||
return from x in tpList.GetEma(lookbackPeriods)
|
||
select new MaEnvelopeResult(x.Date)
|
||
{
|
||
Centerline = x.Ema,
|
||
UpperEnvelope = x.Ema + x.Ema * offsetRatio,
|
||
LowerEnvelope = x.Ema - x.Ema * offsetRatio
|
||
};
|
||
}
|
||
|
||
private static IEnumerable<MaEnvelopeResult> MaEnvEpma(this List<(DateTime, double)> tpList, int lookbackPeriods, double offsetRatio)
|
||
{
|
||
return from x in tpList.GetEpma(lookbackPeriods)
|
||
select new MaEnvelopeResult(x.Date)
|
||
{
|
||
Centerline = x.Epma,
|
||
UpperEnvelope = x.Epma + x.Epma * offsetRatio,
|
||
LowerEnvelope = x.Epma - x.Epma * offsetRatio
|
||
};
|
||
}
|
||
|
||
private static IEnumerable<MaEnvelopeResult> MaEnvHma(this List<(DateTime, double)> tpList, int lookbackPeriods, double offsetRatio)
|
||
{
|
||
return from x in tpList.GetHma(lookbackPeriods)
|
||
select new MaEnvelopeResult(x.Date)
|
||
{
|
||
Centerline = x.Hma,
|
||
UpperEnvelope = x.Hma + x.Hma * offsetRatio,
|
||
LowerEnvelope = x.Hma - x.Hma * offsetRatio
|
||
};
|
||
}
|
||
|
||
private static IEnumerable<MaEnvelopeResult> MaEnvSma(this List<(DateTime, double)> tpList, int lookbackPeriods, double offsetRatio)
|
||
{
|
||
return from x in tpList.GetSma(lookbackPeriods)
|
||
select new MaEnvelopeResult(x.Date)
|
||
{
|
||
Centerline = x.Sma,
|
||
UpperEnvelope = x.Sma + x.Sma * offsetRatio,
|
||
LowerEnvelope = x.Sma - x.Sma * offsetRatio
|
||
};
|
||
}
|
||
|
||
private static IEnumerable<MaEnvelopeResult> MaEnvSmma(this List<(DateTime, double)> tpList, int lookbackPeriods, double offsetRatio)
|
||
{
|
||
return from x in tpList.GetSmma(lookbackPeriods)
|
||
select new MaEnvelopeResult(x.Date)
|
||
{
|
||
Centerline = x.Smma,
|
||
UpperEnvelope = x.Smma + x.Smma * offsetRatio,
|
||
LowerEnvelope = x.Smma - x.Smma * offsetRatio
|
||
};
|
||
}
|
||
|
||
private static IEnumerable<MaEnvelopeResult> MaEnvTema(this List<(DateTime, double)> tpList, int lookbackPeriods, double offsetRatio)
|
||
{
|
||
return from x in tpList.GetTema(lookbackPeriods)
|
||
select new MaEnvelopeResult(x.Date)
|
||
{
|
||
Centerline = x.Tema,
|
||
UpperEnvelope = x.Tema + x.Tema * offsetRatio,
|
||
LowerEnvelope = x.Tema - x.Tema * offsetRatio
|
||
};
|
||
}
|
||
|
||
private static IEnumerable<MaEnvelopeResult> MaEnvWma(this List<(DateTime, double)> tpList, int lookbackPeriods, double offsetRatio)
|
||
{
|
||
return from x in tpList.GetWma(lookbackPeriods)
|
||
select new MaEnvelopeResult(x.Date)
|
||
{
|
||
Centerline = x.Wma,
|
||
UpperEnvelope = x.Wma + x.Wma * offsetRatio,
|
||
LowerEnvelope = x.Wma - x.Wma * offsetRatio
|
||
};
|
||
}
|
||
|
||
private static void ValidateMaEnvelopes(double percentOffset)
|
||
{
|
||
if (percentOffset <= 0.0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("percentOffset", percentOffset, "Percent Offset must be greater than 0 for Moving Average Envelopes.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes non-essential records containing null values with unique consideration for
|
||
/// this indicator. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#condense?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator results to evaluate.</param><returns>Time series of
|
||
/// indicator results, condensed.</returns>
|
||
public static IEnumerable<MaEnvelopeResult> Condense(this IEnumerable<MaEnvelopeResult> results)
|
||
{
|
||
List<MaEnvelopeResult> list = results.ToList();
|
||
list.RemoveAll((MaEnvelopeResult x) => !x.UpperEnvelope.HasValue && !x.LowerEnvelope.HasValue && !x.Centerline.HasValue);
|
||
return list.ToSortedList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// MESA Adaptive Moving Average (MAMA) is a 5-period adaptive moving average of high/low price.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Mama/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="fastLimit">Fast limit threshold.</param><param name="slowLimit">Slow limit threshold.</param><returns>Time series of MAMA values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<MamaResult> GetMama<TQuote>(this IEnumerable<TQuote> quotes, double fastLimit = 0.5, double slowLimit = 0.05) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.HL2).CalcMama(fastLimit, slowLimit);
|
||
}
|
||
|
||
public static IEnumerable<MamaResult> GetMama(this IEnumerable<IReusableResult> results, double fastLimit = 0.5, double slowLimit = 0.05)
|
||
{
|
||
return results.ToTuple().CalcMama(fastLimit, slowLimit).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<MamaResult> GetMama(this IEnumerable<(DateTime, double)> priceTuples, double fastLimit = 0.5, double slowLimit = 0.05)
|
||
{
|
||
return priceTuples.ToSortedList().CalcMama(fastLimit, slowLimit);
|
||
}
|
||
|
||
internal static List<MamaResult> CalcMama(this List<(DateTime, double)> tpList, double fastLimit, double slowLimit)
|
||
{
|
||
ValidateMama(fastLimit, slowLimit);
|
||
int count = tpList.Count;
|
||
List<MamaResult> list = new List<MamaResult>(count);
|
||
double num = 0.0;
|
||
double[] array = new double[count];
|
||
double[] array2 = new double[count];
|
||
double[] array3 = new double[count];
|
||
double[] array4 = new double[count];
|
||
double[] array5 = new double[count];
|
||
double[] array6 = new double[count];
|
||
double[] array7 = new double[count];
|
||
double[] array8 = new double[count];
|
||
double[] array9 = new double[count];
|
||
double[] array10 = new double[count];
|
||
double[] array11 = new double[count];
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
var (date, num2) = tpList[i];
|
||
array[i] = num2;
|
||
MamaResult mamaResult = new MamaResult(date);
|
||
list.Add(mamaResult);
|
||
if (i > 5)
|
||
{
|
||
double num3 = 0.075 * array4[i - 1] + 0.54;
|
||
array2[i] = (4.0 * array[i] + 3.0 * array[i - 1] + 2.0 * array[i - 2] + array[i - 3]) / 10.0;
|
||
array3[i] = (0.0962 * array2[i] + 0.5769 * array2[i - 2] - 0.5769 * array2[i - 4] - 0.0962 * array2[i - 6]) * num3;
|
||
array5[i] = (0.0962 * array3[i] + 0.5769 * array3[i - 2] - 0.5769 * array3[i - 4] - 0.0962 * array3[i - 6]) * num3;
|
||
array6[i] = array3[i - 3];
|
||
double num4 = (0.0962 * array6[i] + 0.5769 * array6[i - 2] - 0.5769 * array6[i - 4] - 0.0962 * array6[i - 6]) * num3;
|
||
double num5 = (0.0962 * array5[i] + 0.5769 * array5[i - 2] - 0.5769 * array5[i - 4] - 0.0962 * array5[i - 6]) * num3;
|
||
array8[i] = array6[i] - num5;
|
||
array7[i] = array5[i] + num4;
|
||
array8[i] = 0.2 * array8[i] + 0.8 * array8[i - 1];
|
||
array7[i] = 0.2 * array7[i] + 0.8 * array7[i - 1];
|
||
array9[i] = array8[i] * array8[i - 1] + array7[i] * array7[i - 1];
|
||
array10[i] = array8[i] * array7[i - 1] - array7[i] * array8[i - 1];
|
||
array9[i] = 0.2 * array9[i] + 0.8 * array9[i - 1];
|
||
array10[i] = 0.2 * array10[i] + 0.8 * array10[i - 1];
|
||
array4[i] = ((array10[i] != 0.0 && array9[i] != 0.0) ? (Math.PI * 2.0 / Math.Atan(array10[i] / array9[i])) : 0.0);
|
||
array4[i] = ((array4[i] > 1.5 * array4[i - 1]) ? (1.5 * array4[i - 1]) : array4[i]);
|
||
array4[i] = ((array4[i] < 0.67 * array4[i - 1]) ? (0.67 * array4[i - 1]) : array4[i]);
|
||
array4[i] = ((array4[i] < 6.0) ? 6.0 : array4[i]);
|
||
array4[i] = ((array4[i] > 50.0) ? 50.0 : array4[i]);
|
||
array4[i] = 0.2 * array4[i] + 0.8 * array4[i - 1];
|
||
array11[i] = ((array6[i] != 0.0) ? (Math.Atan(array5[i] / array6[i]) * 180.0 / Math.PI) : 0.0);
|
||
double num6 = Math.Max(array11[i - 1] - array11[i], 1.0);
|
||
double num7 = Math.Max(fastLimit / num6, slowLimit);
|
||
mamaResult.Mama = (num7 * array[i] + (1.0 - num7) * list[i - 1].Mama).NaN2Null();
|
||
mamaResult.Fama = (0.5 * num7 * mamaResult.Mama + (1.0 - 0.5 * num7) * list[i - 1].Fama).NaN2Null();
|
||
}
|
||
else
|
||
{
|
||
num += array[i];
|
||
if (i == 5)
|
||
{
|
||
mamaResult.Mama = (num / 6.0).NaN2Null();
|
||
mamaResult.Fama = mamaResult.Mama;
|
||
}
|
||
array4[i] = 0.0;
|
||
array2[i] = 0.0;
|
||
array3[i] = 0.0;
|
||
array6[i] = 0.0;
|
||
array5[i] = 0.0;
|
||
array8[i] = 0.0;
|
||
array7[i] = 0.0;
|
||
array9[i] = 0.0;
|
||
array10[i] = 0.0;
|
||
array11[i] = 0.0;
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateMama(double fastLimit, double slowLimit)
|
||
{
|
||
if (fastLimit <= slowLimit || fastLimit >= 1.0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("fastLimit", fastLimit, "Fast Limit must be greater than Slow Limit and less than 1 for MAMA.");
|
||
}
|
||
if (slowLimit <= 0.0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("slowLimit", slowLimit, "Slow Limit must be greater than 0 for MAMA.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<MamaResult> RemoveWarmupPeriods(this IEnumerable<MamaResult> results)
|
||
{
|
||
return results.Remove(50);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Marubozu is a single candlestick pattern that has no wicks, representing consistent directional movement.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Patterns/Marubozu/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="minBodyPercent">Optional. Minimum candle body size as percentage.</param><returns>Time series of Marubozu values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<CandleResult> GetMarubozu<TQuote>(this IEnumerable<TQuote> quotes, double minBodyPercent = 95.0) where TQuote : IQuote
|
||
{
|
||
return quotes.CalcMarubozu(minBodyPercent);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Marubozu is a single candlestick pattern that has no wicks, representing consistent directional movement.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Patterns/Marubozu/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="minBodyPercent">Optional. Minimum candle body size as percentage.</param><returns>Time series of Marubozu values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
internal static List<CandleResult> CalcMarubozu<TQuote>(this IEnumerable<TQuote> quotes, double minBodyPercent) where TQuote : IQuote
|
||
{
|
||
ValidateMarubozu(minBodyPercent);
|
||
List<CandleResult> list = quotes.ToCandleResults();
|
||
minBodyPercent /= 100.0;
|
||
int count = list.Count;
|
||
for (int i = 0; i < count; i = checked(i + 1))
|
||
{
|
||
CandleResult candleResult = list[i];
|
||
if (candleResult.Candle.BodyPct >= minBodyPercent)
|
||
{
|
||
candleResult.Price = candleResult.Candle.Close;
|
||
candleResult.Match = (candleResult.Candle.IsBullish ? Match.BullSignal : Match.BearSignal);
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
|
||
private static void ValidateMarubozu(double minBodyPercent)
|
||
{
|
||
if (minBodyPercent > 100.0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("minBodyPercent", minBodyPercent, "Minimum Body Percent must be less than 100 for Marubozu (<=100%).");
|
||
}
|
||
if (minBodyPercent < 80.0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("minBodyPercent", minBodyPercent, "Minimum Body Percent must at least 80 (80%) for Marubozu and is usually greater than 90 (90%).");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Money Flow Index (MFI) is a price-volume oscillator that shows buying and selling momentum.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Mfi/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><returns>Time series of MFI values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<MfiResult> GetMfi<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods = 14) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcMfi(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<MfiResult> CalcMfi(this List<QuoteD> qdList, int lookbackPeriods)
|
||
{
|
||
ValidateMfi(lookbackPeriods);
|
||
int count = qdList.Count;
|
||
List<MfiResult> list = new List<MfiResult>(count);
|
||
double[] array = new double[count];
|
||
double[] array2 = new double[count];
|
||
int[] array3 = new int[count];
|
||
double? num = null;
|
||
checked
|
||
{
|
||
for (int i = 0; i < qdList.Count; i++)
|
||
{
|
||
QuoteD quoteD = qdList[i];
|
||
MfiResult item = new MfiResult(quoteD.Date);
|
||
list.Add(item);
|
||
array[i] = (quoteD.High + quoteD.Low + quoteD.Close) / 3.0;
|
||
array2[i] = array[i] * quoteD.Volume;
|
||
if (!num.HasValue || array[i] == num)
|
||
{
|
||
array3[i] = 0;
|
||
}
|
||
else if (array[i] > num)
|
||
{
|
||
array3[i] = 1;
|
||
}
|
||
else if (array[i] < num)
|
||
{
|
||
array3[i] = -1;
|
||
}
|
||
num = array[i];
|
||
}
|
||
for (int j = lookbackPeriods; j < list.Count; j++)
|
||
{
|
||
MfiResult mfiResult = list[j];
|
||
double num2 = 0.0;
|
||
double num3 = 0.0;
|
||
for (int k = j + 1 - lookbackPeriods; k <= j; k++)
|
||
{
|
||
if (array3[k] == 1)
|
||
{
|
||
num2 += array2[k];
|
||
}
|
||
else if (array3[k] == -1)
|
||
{
|
||
num3 += array2[k];
|
||
}
|
||
}
|
||
if (num3 != 0.0)
|
||
{
|
||
mfiResult.Mfi = 100.0 - 100.0 / (1.0 + new double?(num2 / num3));
|
||
}
|
||
else
|
||
{
|
||
mfiResult.Mfi = 100.0;
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateMfi(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 1)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 1 for MFI.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<MfiResult> RemoveWarmupPeriods(this IEnumerable<MfiResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((MfiResult x) => x.Mfi.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// On-balance Volume (OBV) is a rolling accumulation of volume based on Close price direction.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Obv/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="smaPeriods">Optional. Number of periods for an SMA of the OBV line.</param><returns>Time series of OBV values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<ObvResult> GetObv<TQuote>(this IEnumerable<TQuote> quotes, int? smaPeriods = null) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcObv(smaPeriods);
|
||
}
|
||
|
||
internal static List<ObvResult> CalcObv(this List<QuoteD> qdList, int? smaPeriods)
|
||
{
|
||
ValidateObv(smaPeriods);
|
||
List<ObvResult> list = new List<ObvResult>(qdList.Count);
|
||
double num = double.NaN;
|
||
double num2 = 0.0;
|
||
checked
|
||
{
|
||
for (int i = 0; i < qdList.Count; i++)
|
||
{
|
||
QuoteD quoteD = qdList[i];
|
||
if (!double.IsNaN(num) && quoteD.Close != num)
|
||
{
|
||
if (quoteD.Close > num)
|
||
{
|
||
num2 += quoteD.Volume;
|
||
}
|
||
else if (quoteD.Close < num)
|
||
{
|
||
num2 -= quoteD.Volume;
|
||
}
|
||
}
|
||
ObvResult obvResult = new ObvResult(quoteD.Date)
|
||
{
|
||
Obv = num2
|
||
};
|
||
list.Add(obvResult);
|
||
num = quoteD.Close;
|
||
if (smaPeriods.HasValue && i + 1 > smaPeriods)
|
||
{
|
||
double? num3 = 0.0;
|
||
for (int j = i + 1 - smaPeriods.Value; j <= i; j++)
|
||
{
|
||
num3 += list[j].Obv;
|
||
}
|
||
obvResult.ObvSma = num3 / (double?)smaPeriods;
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateObv(int? smaPeriods)
|
||
{
|
||
if (smaPeriods.HasValue && smaPeriods.GetValueOrDefault() <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("smaPeriods", smaPeriods, "SMA periods must be greater than 0 for OBV.");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Parabolic SAR (stop and reverse) is a price-time based indicator used to determine trend direction and reversals.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/ParabolicSar/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="accelerationStep">Incremental step size.</param><param name="maxAccelerationFactor">Maximum step threshold.</param><returns>Time series of Parabolic SAR values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<ParabolicSarResult> GetParabolicSar<TQuote>(this IEnumerable<TQuote> quotes, double accelerationStep = 0.02, double maxAccelerationFactor = 0.2) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcParabolicSar(accelerationStep, maxAccelerationFactor, accelerationStep);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Parabolic SAR (stop and reverse) is a price-time based indicator used to determine trend direction and reversals.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/ParabolicSar/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="accelerationStep">Incremental step size.</param><param name="maxAccelerationFactor">Maximum step threshold.</param><param name="initialFactor">Initial starting acceleration factor.</param><returns>Time series of Parabolic SAR values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<ParabolicSarResult> GetParabolicSar<TQuote>(this IEnumerable<TQuote> quotes, double accelerationStep, double maxAccelerationFactor, double initialFactor) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcParabolicSar(accelerationStep, maxAccelerationFactor, initialFactor);
|
||
}
|
||
|
||
internal static List<ParabolicSarResult> CalcParabolicSar(this List<QuoteD> qdList, double accelerationStep, double maxAccelerationFactor, double initialFactor)
|
||
{
|
||
ValidateParabolicSar(accelerationStep, maxAccelerationFactor, initialFactor);
|
||
int count = qdList.Count;
|
||
List<ParabolicSarResult> list = new List<ParabolicSarResult>(count);
|
||
if (count == 0)
|
||
{
|
||
return list;
|
||
}
|
||
QuoteD quoteD = qdList[0];
|
||
double num = initialFactor;
|
||
double num2 = quoteD.High;
|
||
double num3 = quoteD.Low;
|
||
bool flag = true;
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
QuoteD quoteD2 = qdList[i];
|
||
ParabolicSarResult parabolicSarResult = new ParabolicSarResult(quoteD2.Date);
|
||
list.Add(parabolicSarResult);
|
||
if (i == 0)
|
||
{
|
||
continue;
|
||
}
|
||
if (flag)
|
||
{
|
||
double num4 = num3 + num * (num2 - num3);
|
||
if (i >= 2)
|
||
{
|
||
double val = Math.Min(qdList[i - 1].Low, qdList[i - 2].Low);
|
||
num4 = Math.Min(num4, val);
|
||
}
|
||
if (quoteD2.Low < num4)
|
||
{
|
||
parabolicSarResult.IsReversal = true;
|
||
parabolicSarResult.Sar = num2;
|
||
flag = false;
|
||
num = initialFactor;
|
||
num2 = quoteD2.Low;
|
||
}
|
||
else
|
||
{
|
||
parabolicSarResult.IsReversal = false;
|
||
parabolicSarResult.Sar = num4;
|
||
if (quoteD2.High > num2)
|
||
{
|
||
num2 = quoteD2.High;
|
||
num = Math.Min(num + accelerationStep, maxAccelerationFactor);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
double num5 = num3 - num * (num3 - num2);
|
||
if (i >= 2)
|
||
{
|
||
double val2 = Math.Max(qdList[i - 1].High, qdList[i - 2].High);
|
||
num5 = Math.Max(num5, val2);
|
||
}
|
||
if (quoteD2.High > num5)
|
||
{
|
||
parabolicSarResult.IsReversal = true;
|
||
parabolicSarResult.Sar = num2;
|
||
flag = true;
|
||
num = initialFactor;
|
||
num2 = quoteD2.High;
|
||
}
|
||
else
|
||
{
|
||
parabolicSarResult.IsReversal = false;
|
||
parabolicSarResult.Sar = num5;
|
||
if (quoteD2.Low < num2)
|
||
{
|
||
num2 = quoteD2.Low;
|
||
num = Math.Min(num + accelerationStep, maxAccelerationFactor);
|
||
}
|
||
}
|
||
}
|
||
num3 = parabolicSarResult.Sar.Value;
|
||
}
|
||
ParabolicSarResult parabolicSarResult2 = (from x in list
|
||
where x.IsReversal == true
|
||
orderby x.Date
|
||
select x).FirstOrDefault();
|
||
int num6 = ((parabolicSarResult2 != null) ? list.IndexOf(parabolicSarResult2) : (count - 1));
|
||
for (int num7 = 0; num7 <= num6; num7++)
|
||
{
|
||
ParabolicSarResult parabolicSarResult3 = list[num7];
|
||
parabolicSarResult3.Sar = null;
|
||
parabolicSarResult3.IsReversal = null;
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateParabolicSar(double accelerationStep, double maxAccelerationFactor, double initialFactor)
|
||
{
|
||
if (accelerationStep <= 0.0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("accelerationStep", accelerationStep, "Acceleration Step must be greater than 0 for Parabolic SAR.");
|
||
}
|
||
if (maxAccelerationFactor <= 0.0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("maxAccelerationFactor", maxAccelerationFactor, "Max Acceleration Factor must be greater than 0 for Parabolic SAR.");
|
||
}
|
||
if (accelerationStep > maxAccelerationFactor)
|
||
{
|
||
string message = string.Format(invCulture, "Acceleration Step cannot be larger than the Max Acceleration Factor ({0}) for Parabolic SAR.", maxAccelerationFactor);
|
||
throw new ArgumentOutOfRangeException("accelerationStep", accelerationStep, message);
|
||
}
|
||
if (initialFactor <= 0.0 || initialFactor > maxAccelerationFactor)
|
||
{
|
||
throw new ArgumentOutOfRangeException("initialFactor", initialFactor, "Initial Factor must be greater than 0 and not larger than Max Acceleration Factor for Parabolic SAR.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<ParabolicSarResult> RemoveWarmupPeriods(this IEnumerable<ParabolicSarResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((ParabolicSarResult x) => x.Sar.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Pivot Points depict support and resistance levels, based on the prior lookback window. You can specify window size (e.g. month, week, day, etc).
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/PivotPoints/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="windowSize">Calendar size of the lookback window.</param><param name="pointType">Pivot Point type.</param><returns>Time series of Pivot Points values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<PivotPointsResult> GetPivotPoints<TQuote>(this IEnumerable<TQuote> quotes, PeriodSize windowSize, PivotPointType pointType = PivotPointType.Standard) where TQuote : IQuote
|
||
{
|
||
return quotes.ToSortedList().CalcPivotPoints(windowSize, pointType);
|
||
}
|
||
|
||
internal static List<PivotPointsResult> CalcPivotPoints<TQuote>(this List<TQuote> quotesList, PeriodSize windowSize, PivotPointType pointType) where TQuote : IQuote
|
||
{
|
||
int count = quotesList.Count;
|
||
List<PivotPointsResult> list = new List<PivotPointsResult>(count);
|
||
PivotPointsResult pivotPointsResult = new PivotPointsResult();
|
||
if (count == 0)
|
||
{
|
||
return list;
|
||
}
|
||
TQuote val = quotesList[0];
|
||
int num = GetWindowNumber(val.Date, windowSize);
|
||
bool flag = true;
|
||
decimal num2 = val.High;
|
||
decimal num3 = val.Low;
|
||
decimal open = val.Open;
|
||
decimal close = val.Close;
|
||
for (int i = 0; i < count; i = checked(i + 1))
|
||
{
|
||
TQuote val2 = quotesList[i];
|
||
PivotPointsResult pivotPointsResult2 = new PivotPointsResult
|
||
{
|
||
Date = val2.Date
|
||
};
|
||
int windowNumber = GetWindowNumber(val2.Date, windowSize);
|
||
if (windowNumber != num)
|
||
{
|
||
num = windowNumber;
|
||
flag = false;
|
||
if (pointType == PivotPointType.Woodie)
|
||
{
|
||
open = val2.Open;
|
||
}
|
||
pivotPointsResult = GetPivotPoint<PivotPointsResult>(pointType, open, num2, num3, close);
|
||
open = val2.Open;
|
||
num2 = val2.High;
|
||
num3 = val2.Low;
|
||
}
|
||
if (!flag)
|
||
{
|
||
pivotPointsResult2.PP = pivotPointsResult?.PP;
|
||
pivotPointsResult2.S1 = pivotPointsResult?.S1;
|
||
pivotPointsResult2.S2 = pivotPointsResult?.S2;
|
||
pivotPointsResult2.S3 = pivotPointsResult?.S3;
|
||
pivotPointsResult2.S4 = pivotPointsResult?.S4;
|
||
pivotPointsResult2.R1 = pivotPointsResult?.R1;
|
||
pivotPointsResult2.R2 = pivotPointsResult?.R2;
|
||
pivotPointsResult2.R3 = pivotPointsResult?.R3;
|
||
pivotPointsResult2.R4 = pivotPointsResult?.R4;
|
||
}
|
||
list.Add(pivotPointsResult2);
|
||
num2 = ((val2.High > num2) ? val2.High : num2);
|
||
num3 = ((val2.Low < num3) ? val2.Low : num3);
|
||
close = val2.Close;
|
||
}
|
||
return list;
|
||
}
|
||
|
||
internal static TPivotPoint GetPivotPointStandard<TPivotPoint>(decimal high, decimal low, decimal close) where TPivotPoint : IPivotPoint, new()
|
||
{
|
||
decimal num = (high + low + close) / 3m;
|
||
TPivotPoint result = new TPivotPoint();
|
||
decimal? pP = num;
|
||
result.PP = pP;
|
||
decimal? s = num * 2m - high;
|
||
result.S1 = s;
|
||
decimal? s2 = num - (high - low);
|
||
result.S2 = s2;
|
||
decimal? s3 = low - 2m * (high - num);
|
||
result.S3 = s3;
|
||
decimal? r = num * 2m - low;
|
||
result.R1 = r;
|
||
decimal? r2 = num + (high - low);
|
||
result.R2 = r2;
|
||
decimal? r3 = high + 2m * (num - low);
|
||
result.R3 = r3;
|
||
return result;
|
||
}
|
||
|
||
internal static TPivotPoint GetPivotPointCamarilla<TPivotPoint>(decimal high, decimal low, decimal close) where TPivotPoint : IPivotPoint, new()
|
||
{
|
||
TPivotPoint result = new TPivotPoint();
|
||
decimal? pP = close;
|
||
result.PP = pP;
|
||
decimal? s = close - 0.0916666666666666666666666667m * (high - low);
|
||
result.S1 = s;
|
||
decimal? s2 = close - 0.1833333333333333333333333333m * (high - low);
|
||
result.S2 = s2;
|
||
decimal? s3 = close - 0.275m * (high - low);
|
||
result.S3 = s3;
|
||
decimal? s4 = close - 0.55m * (high - low);
|
||
result.S4 = s4;
|
||
decimal? r = close + 0.0916666666666666666666666667m * (high - low);
|
||
result.R1 = r;
|
||
decimal? r2 = close + 0.1833333333333333333333333333m * (high - low);
|
||
result.R2 = r2;
|
||
decimal? r3 = close + 0.275m * (high - low);
|
||
result.R3 = r3;
|
||
decimal? r4 = close + 0.55m * (high - low);
|
||
result.R4 = r4;
|
||
return result;
|
||
}
|
||
|
||
internal static TPivotPoint GetPivotPointDemark<TPivotPoint>(decimal open, decimal high, decimal low, decimal close) where TPivotPoint : IPivotPoint, new()
|
||
{
|
||
decimal? num = ((close < open) ? (high + 2m * low + close) : ((close > open) ? (2m * high + low + close) : (high + low + 2m * close)));
|
||
TPivotPoint result = new TPivotPoint();
|
||
decimal? pP = num / (decimal?)4;
|
||
result.PP = pP;
|
||
decimal? s = num / (decimal?)2 - (decimal?)high;
|
||
result.S1 = s;
|
||
decimal? r = num / (decimal?)2 - (decimal?)low;
|
||
result.R1 = r;
|
||
return result;
|
||
}
|
||
|
||
internal static TPivotPoint GetPivotPointFibonacci<TPivotPoint>(decimal high, decimal low, decimal close) where TPivotPoint : IPivotPoint, new()
|
||
{
|
||
decimal num = (high + low + close) / 3m;
|
||
TPivotPoint result = new TPivotPoint();
|
||
decimal? pP = num;
|
||
result.PP = pP;
|
||
decimal? s = num - 0.382m * (high - low);
|
||
result.S1 = s;
|
||
decimal? s2 = num - 0.618m * (high - low);
|
||
result.S2 = s2;
|
||
decimal? s3 = num - 1.000m * (high - low);
|
||
result.S3 = s3;
|
||
decimal? r = num + 0.382m * (high - low);
|
||
result.R1 = r;
|
||
decimal? r2 = num + 0.618m * (high - low);
|
||
result.R2 = r2;
|
||
decimal? r3 = num + 1.000m * (high - low);
|
||
result.R3 = r3;
|
||
return result;
|
||
}
|
||
|
||
internal static TPivotPoint GetPivotPointWoodie<TPivotPoint>(decimal currentOpen, decimal high, decimal low) where TPivotPoint : IPivotPoint, new()
|
||
{
|
||
decimal num = (high + low + 2m * currentOpen) / 4m;
|
||
TPivotPoint result = new TPivotPoint();
|
||
decimal? pP = num;
|
||
result.PP = pP;
|
||
decimal? s = num * 2m - high;
|
||
result.S1 = s;
|
||
decimal? s2 = num - high + low;
|
||
result.S2 = s2;
|
||
decimal? s3 = low - 2m * (high - num);
|
||
result.S3 = s3;
|
||
decimal? r = num * 2m - low;
|
||
result.R1 = r;
|
||
decimal? r2 = num + high - low;
|
||
result.R2 = r2;
|
||
decimal? r3 = high + 2m * (num - low);
|
||
result.R3 = r3;
|
||
return result;
|
||
}
|
||
|
||
internal static TPivotPoint GetPivotPoint<TPivotPoint>(PivotPointType pointType, decimal open, decimal high, decimal low, decimal close) where TPivotPoint : IPivotPoint, new()
|
||
{
|
||
return pointType switch
|
||
{
|
||
PivotPointType.Standard => GetPivotPointStandard<TPivotPoint>(high, low, close),
|
||
PivotPointType.Camarilla => GetPivotPointCamarilla<TPivotPoint>(high, low, close),
|
||
PivotPointType.Demark => GetPivotPointDemark<TPivotPoint>(open, high, low, close),
|
||
PivotPointType.Fibonacci => GetPivotPointFibonacci<TPivotPoint>(high, low, close),
|
||
PivotPointType.Woodie => GetPivotPointWoodie<TPivotPoint>(open, high, low),
|
||
_ => throw new ArgumentOutOfRangeException("pointType", pointType, "Invalid pointType provided."),
|
||
};
|
||
}
|
||
|
||
private static int GetWindowNumber(DateTime d, PeriodSize windowSize)
|
||
{
|
||
return windowSize switch
|
||
{
|
||
PeriodSize.Month => d.Month,
|
||
PeriodSize.Week => invCalendar.GetWeekOfYear(d, invCalendarWeekRule, invFirstDayOfWeek),
|
||
PeriodSize.Day => d.Day,
|
||
PeriodSize.OneHour => d.Hour,
|
||
_ => throw new ArgumentOutOfRangeException("windowSize", windowSize, string.Format(invCulture, "Pivot Points does not support PeriodSize of {0}. See documentation for valid options.", Enum.GetName(typeof(PeriodSize), windowSize))),
|
||
};
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<PivotPointsResult> RemoveWarmupPeriods(this IEnumerable<PivotPointsResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((PivotPointsResult x) => x.PP.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Pivots is an extended version of Williams Fractal that includes identification of Higher High, Lower Low, Higher Low, and Lower Low trends between pivots in a lookback window.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Pivots/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="leftSpan">Number of span periods to the left of the evaluation period.</param><param name="rightSpan">Number of span periods to the right of the evaluation period.</param><param name="maxTrendPeriods">Number of periods in the lookback window.</param><param name="endType">Determines use of Close or High/Low wicks for points.</param><returns>Time series of Pivots values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<PivotsResult> GetPivots<TQuote>(this IEnumerable<TQuote> quotes, int leftSpan = 2, int rightSpan = 2, int maxTrendPeriods = 20, EndType endType = EndType.HighLow) where TQuote : IQuote
|
||
{
|
||
return quotes.ToSortedList().CalcPivots(leftSpan, rightSpan, maxTrendPeriods, endType);
|
||
}
|
||
|
||
internal static List<PivotsResult> CalcPivots<TQuote>(this List<TQuote> quotesList, int leftSpan, int rightSpan, int maxTrendPeriods, EndType endType) where TQuote : IQuote
|
||
{
|
||
ValidatePivots(leftSpan, rightSpan, maxTrendPeriods);
|
||
List<PivotsResult> list = (from x in quotesList.CalcFractal(leftSpan, rightSpan, endType)
|
||
select new PivotsResult(x.Date)
|
||
{
|
||
HighPoint = x.FractalBear,
|
||
LowPoint = x.FractalBull
|
||
}).ToList();
|
||
int? num = null;
|
||
decimal? num2 = null;
|
||
int? num3 = null;
|
||
decimal? num4 = null;
|
||
checked
|
||
{
|
||
for (int num5 = leftSpan; num5 <= list.Count - rightSpan; num5++)
|
||
{
|
||
PivotsResult pivotsResult = list[num5];
|
||
if (num < num5 - maxTrendPeriods)
|
||
{
|
||
num = null;
|
||
num2 = null;
|
||
}
|
||
if (num3 < num5 - maxTrendPeriods)
|
||
{
|
||
num3 = null;
|
||
num4 = null;
|
||
}
|
||
if (pivotsResult.HighPoint.HasValue)
|
||
{
|
||
if (num.HasValue && !(pivotsResult.HighPoint == num2))
|
||
{
|
||
PivotTrend value = ((!(pivotsResult.HighPoint > num2)) ? PivotTrend.LH : PivotTrend.HH);
|
||
list[num.Value].HighLine = num2;
|
||
decimal? num6 = (pivotsResult.HighPoint - num2) / (decimal?)(num5 - num);
|
||
for (int num7 = num.Value + 1; num7 <= num5; num7++)
|
||
{
|
||
list[num7].HighTrend = value;
|
||
list[num7].HighLine = pivotsResult.HighPoint + num6 * (decimal?)(num7 - num5);
|
||
}
|
||
}
|
||
num = num5;
|
||
num2 = pivotsResult.HighPoint;
|
||
}
|
||
if (!pivotsResult.LowPoint.HasValue)
|
||
{
|
||
continue;
|
||
}
|
||
if (num3.HasValue && !(pivotsResult.LowPoint == num4))
|
||
{
|
||
PivotTrend value2 = ((pivotsResult.LowPoint > num4) ? PivotTrend.HL : PivotTrend.LL);
|
||
list[num3.Value].LowLine = num4;
|
||
decimal? num8 = (pivotsResult.LowPoint - num4) / (decimal?)(num5 - num3);
|
||
for (int num9 = num3.Value + 1; num9 <= num5; num9++)
|
||
{
|
||
list[num9].LowTrend = value2;
|
||
list[num9].LowLine = pivotsResult.LowPoint + num8 * (decimal?)(num9 - num5);
|
||
}
|
||
}
|
||
num3 = num5;
|
||
num4 = pivotsResult.LowPoint;
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
internal static void ValidatePivots(int leftSpan, int rightSpan, int maxTrendPeriods, string caller = "Pivots")
|
||
{
|
||
if (rightSpan < 2)
|
||
{
|
||
throw new ArgumentOutOfRangeException("rightSpan", rightSpan, "Right span must be at least 2 for " + caller + ".");
|
||
}
|
||
if (leftSpan < 2)
|
||
{
|
||
throw new ArgumentOutOfRangeException("leftSpan", leftSpan, "Left span must be at least 2 for " + caller + ".");
|
||
}
|
||
if (maxTrendPeriods <= leftSpan)
|
||
{
|
||
throw new ArgumentOutOfRangeException("leftSpan", leftSpan, "Lookback periods must be greater than the Left window span for " + caller + ".");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes non-essential records containing null values with unique consideration for
|
||
/// this indicator. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#condense?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator results to evaluate.</param><returns>Time series of
|
||
/// indicator results, condensed.</returns>
|
||
public static IEnumerable<PivotsResult> Condense(this IEnumerable<PivotsResult> results)
|
||
{
|
||
List<PivotsResult> list = results.ToList();
|
||
list.RemoveAll((PivotsResult x) => !x.HighPoint.HasValue && !x.LowPoint.HasValue);
|
||
return list.ToSortedList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Price Momentum Oscillator (PMO) is double-smoothed ROC based momentum indicator.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Pmo/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="timePeriods">Number of periods for ROC EMA smoothing.</param><param name="smoothPeriods">Number of periods for PMO EMA smoothing.</param><param name="signalPeriods">Number of periods for Signal line EMA.</param><returns>Time series of PMO values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<PmoResult> GetPmo<TQuote>(this IEnumerable<TQuote> quotes, int timePeriods = 35, int smoothPeriods = 20, int signalPeriods = 10) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcPmo(timePeriods, smoothPeriods, signalPeriods);
|
||
}
|
||
|
||
public static IEnumerable<PmoResult> GetPmo(this IEnumerable<IReusableResult> results, int timePeriods = 35, int smoothPeriods = 20, int signalPeriods = 10)
|
||
{
|
||
return results.ToTuple().CalcPmo(timePeriods, smoothPeriods, signalPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<PmoResult> GetPmo(this IEnumerable<(DateTime, double)> priceTuples, int timePeriods = 35, int smoothPeriods = 20, int signalPeriods = 10)
|
||
{
|
||
return priceTuples.ToSortedList().CalcPmo(timePeriods, smoothPeriods, signalPeriods);
|
||
}
|
||
|
||
internal static List<PmoResult> CalcPmo(this List<(DateTime, double)> tpList, int timePeriods, int smoothPeriods, int signalPeriods)
|
||
{
|
||
ValidatePmo(timePeriods, smoothPeriods, signalPeriods);
|
||
List<PmoResult> list = tpList.CalcPmoRocEma(timePeriods);
|
||
double num = 2.0 / (double)smoothPeriods;
|
||
double? num2 = null;
|
||
checked
|
||
{
|
||
int num3 = timePeriods + smoothPeriods;
|
||
for (int i = num3 - 1; i < list.Count; i++)
|
||
{
|
||
PmoResult pmoResult = list[i];
|
||
if (i + 1 > num3)
|
||
{
|
||
pmoResult.Pmo = (pmoResult.RocEma - num2) * num + num2;
|
||
}
|
||
else if (i + 1 == num3)
|
||
{
|
||
double? num4 = 0.0;
|
||
for (int j = i + 1 - smoothPeriods; j <= i; j++)
|
||
{
|
||
num4 += list[j].RocEma;
|
||
}
|
||
pmoResult.Pmo = num4 / (double)smoothPeriods;
|
||
}
|
||
num2 = pmoResult.Pmo;
|
||
}
|
||
CalcPmoSignal(list, timePeriods, smoothPeriods, signalPeriods);
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static List<PmoResult> CalcPmoRocEma(this List<(DateTime, double)> tpList, int timePeriods)
|
||
{
|
||
double num = 2.0 / (double)timePeriods;
|
||
double? num2 = null;
|
||
List<RocResult> list = tpList.CalcRoc(1, null).ToList();
|
||
List<PmoResult> list2 = new List<PmoResult>();
|
||
checked
|
||
{
|
||
int num3 = timePeriods + 1;
|
||
for (int i = 0; i < list.Count; i++)
|
||
{
|
||
RocResult rocResult = list[i];
|
||
PmoResult pmoResult = new PmoResult(rocResult.Date);
|
||
list2.Add(pmoResult);
|
||
if (i + 1 > num3)
|
||
{
|
||
pmoResult.RocEma = rocResult.Roc * num + num2 * (1.0 - num);
|
||
}
|
||
else if (i + 1 == num3)
|
||
{
|
||
double? num4 = 0.0;
|
||
for (int j = i + 1 - timePeriods; j <= i; j++)
|
||
{
|
||
num4 += list[j].Roc;
|
||
}
|
||
pmoResult.RocEma = num4 / (double)timePeriods;
|
||
}
|
||
num2 = pmoResult.RocEma;
|
||
pmoResult.RocEma *= 10.0;
|
||
}
|
||
return list2;
|
||
}
|
||
}
|
||
|
||
private static void CalcPmoSignal(List<PmoResult> results, int timePeriods, int smoothPeriods, int signalPeriods)
|
||
{
|
||
checked
|
||
{
|
||
double num = 2.0 / (double)(signalPeriods + 1);
|
||
double? num2 = null;
|
||
int num3 = timePeriods + smoothPeriods + signalPeriods - 1;
|
||
for (int i = num3 - 1; i < results.Count; i++)
|
||
{
|
||
PmoResult pmoResult = results[i];
|
||
if (i + 1 > num3)
|
||
{
|
||
pmoResult.Signal = (pmoResult.Pmo - num2) * num + num2;
|
||
}
|
||
else if (i + 1 == num3)
|
||
{
|
||
double? num4 = 0.0;
|
||
for (int j = i + 1 - signalPeriods; j <= i; j++)
|
||
{
|
||
num4 += results[j].Pmo;
|
||
}
|
||
pmoResult.Signal = num4 / (double)signalPeriods;
|
||
}
|
||
num2 = pmoResult.Signal;
|
||
}
|
||
}
|
||
}
|
||
|
||
private static void ValidatePmo(int timePeriods, int smoothPeriods, int signalPeriods)
|
||
{
|
||
if (timePeriods <= 1)
|
||
{
|
||
throw new ArgumentOutOfRangeException("timePeriods", timePeriods, "Time periods must be greater than 1 for PMO.");
|
||
}
|
||
if (smoothPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("smoothPeriods", smoothPeriods, "Smoothing periods must be greater than 0 for PMO.");
|
||
}
|
||
if (signalPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("signalPeriods", signalPeriods, "Signal periods must be greater than 0 for PMO.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<PmoResult> RemoveWarmupPeriods(this IEnumerable<PmoResult> results)
|
||
{
|
||
checked
|
||
{
|
||
int num = results.ToList().FindIndex((PmoResult x) => x.Pmo.HasValue) + 1;
|
||
return results.Remove(num + 250);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Price Relative Strength (PRS), also called Comparative Relative Strength,
|
||
/// shows the ratio of two quote histories. It is often used to compare
|
||
/// against a market index or sector ETF. When using the optional lookbackPeriods,
|
||
/// this also return relative percent change over the specified periods.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Prs/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">documentation</see>
|
||
/// for more information.
|
||
/// </para>
|
||
/// </summary><typeparam name="TQuote">Configurable Quote type. See Guide for more information.</typeparam><param name="quotesEval">Historical price quotes for evaluation.</param><param name="quotesBase">This is usually market index data, but could be any baseline data that you might use for comparison.</param><param name="lookbackPeriods">Optional. Number of periods for % difference.</param><param name="smaPeriods">Optional. Number of periods for a PRS SMA signal line.</param><returns>Time series of PRS values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception><exception cref="T:Skender.Stock.Indicators.InvalidQuotesException">Invalid quotes provided.</exception>
|
||
public static IEnumerable<PrsResult> GetPrs<TQuote>(this IEnumerable<TQuote> quotesEval, IEnumerable<TQuote> quotesBase, int? lookbackPeriods = null, int? smaPeriods = null) where TQuote : IQuote
|
||
{
|
||
List<(DateTime, double)> tpListBase = quotesBase.ToTuple(CandlePart.Close);
|
||
return CalcPrs(quotesEval.ToTuple(CandlePart.Close), tpListBase, lookbackPeriods, smaPeriods);
|
||
}
|
||
|
||
public static IEnumerable<PrsResult> GetPrs(this IEnumerable<IReusableResult> quotesEval, IEnumerable<IReusableResult> quotesBase, int? lookbackPeriods = null, int? smaPeriods = null)
|
||
{
|
||
List<(DateTime Date, double Value)> tpListEval = quotesEval.ToTuple();
|
||
List<(DateTime, double)> tpListBase = quotesBase.ToTuple();
|
||
return CalcPrs(tpListEval, tpListBase, lookbackPeriods, smaPeriods).SyncIndex(quotesEval, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<PrsResult> GetPrs(this IEnumerable<(DateTime, double)> tupleEval, IEnumerable<(DateTime, double)> tupleBase, int? lookbackPeriods = null, int? smaPeriods = null)
|
||
{
|
||
List<(DateTime, double)> tpListBase = tupleBase.ToSortedList();
|
||
return CalcPrs(tupleEval.ToSortedList(), tpListBase, lookbackPeriods, smaPeriods);
|
||
}
|
||
|
||
internal static List<PrsResult> CalcPrs(List<(DateTime, double)> tpListEval, List<(DateTime, double)> tpListBase, int? lookbackPeriods = null, int? smaPeriods = null)
|
||
{
|
||
ValidatePriceRelative(tpListEval, tpListBase, lookbackPeriods, smaPeriods);
|
||
List<PrsResult> list = new List<PrsResult>(tpListEval.Count);
|
||
checked
|
||
{
|
||
for (int i = 0; i < tpListEval.Count; i++)
|
||
{
|
||
var (dateTime, num) = tpListBase[i];
|
||
var (dateTime2, num2) = tpListEval[i];
|
||
if (dateTime2 != dateTime)
|
||
{
|
||
throw new InvalidQuotesException("tpListEval", dateTime2, "Date sequence does not match. Price Relative requires matching dates in provided histories.");
|
||
}
|
||
PrsResult prsResult = new PrsResult(dateTime2)
|
||
{
|
||
Prs = ((num == 0.0) ? ((double?)null) : (num2 / num).NaN2Null())
|
||
};
|
||
list.Add(prsResult);
|
||
if (lookbackPeriods.HasValue && i + 1 > lookbackPeriods)
|
||
{
|
||
double item = tpListBase[i - lookbackPeriods.Value].Item2;
|
||
double item2 = tpListEval[i - lookbackPeriods.Value].Item2;
|
||
if (item != 0.0 && item2 != 0.0)
|
||
{
|
||
double? num3 = (num - item) / item;
|
||
prsResult.PrsPercent = (new double?((num2 - item2) / item2) - num3).NaN2Null();
|
||
}
|
||
}
|
||
if (smaPeriods.HasValue && i + 1 >= smaPeriods)
|
||
{
|
||
double? num4 = 0.0;
|
||
for (int j = i + 1 - smaPeriods.Value; j <= i; j++)
|
||
{
|
||
num4 += list[j].Prs;
|
||
}
|
||
prsResult.PrsSma = (num4 / (double?)smaPeriods).NaN2Null();
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidatePriceRelative(List<(DateTime, double)> quotesEval, List<(DateTime, double)> quotesBase, int? lookbackPeriods, int? smaPeriods)
|
||
{
|
||
if (lookbackPeriods.HasValue && lookbackPeriods.GetValueOrDefault() <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for Price Relative Strength.");
|
||
}
|
||
if (smaPeriods.HasValue && smaPeriods.GetValueOrDefault() <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("smaPeriods", smaPeriods, "SMA periods must be greater than 0 for Price Relative Strength.");
|
||
}
|
||
int count = quotesEval.Count;
|
||
int count2 = quotesBase.Count;
|
||
int? num = lookbackPeriods;
|
||
if (num.HasValue && count < num)
|
||
{
|
||
string message = "Insufficient quotes provided for Price Relative Strength. " + string.Format(invCulture, "You provided {0} periods of quotes when at least {1} are required.", count, num);
|
||
throw new InvalidQuotesException("quotesEval", message);
|
||
}
|
||
if (count2 != count)
|
||
{
|
||
throw new InvalidQuotesException("quotesBase", "Base quotes should have at least as many records as Eval quotes for PRS.");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Percentage Volume Oscillator (PVO) is a simple oscillator view of two converging/diverging exponential moving averages of Volume.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Pvo/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="fastPeriods">Number of periods in the Fast moving average.</param><param name="slowPeriods">Number of periods in the Slow moving average.</param><param name="signalPeriods">Number of periods for the PVO SMA signal line.</param><returns>Time series of PVO values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<PvoResult> GetPvo<TQuote>(this IEnumerable<TQuote> quotes, int fastPeriods = 12, int slowPeriods = 26, int signalPeriods = 9) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Volume).CalcPvo(fastPeriods, slowPeriods, signalPeriods);
|
||
}
|
||
|
||
internal static List<PvoResult> CalcPvo(this List<(DateTime, double)> tpList, int fastPeriods, int slowPeriods, int signalPeriods)
|
||
{
|
||
ValidatePvo(fastPeriods, slowPeriods, signalPeriods);
|
||
List<EmaResult> list = tpList.CalcEma(fastPeriods);
|
||
List<EmaResult> list2 = tpList.CalcEma(slowPeriods);
|
||
int count = tpList.Count;
|
||
List<(DateTime, double)> list3 = new List<(DateTime, double)>();
|
||
List<PvoResult> list4 = new List<PvoResult>(count);
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
DateTime item = tpList[i].Item1;
|
||
EmaResult emaResult = list[i];
|
||
EmaResult emaResult2 = list2[i];
|
||
PvoResult pvoResult = new PvoResult(item);
|
||
list4.Add(pvoResult);
|
||
if (i >= slowPeriods - 1)
|
||
{
|
||
double? num = (pvoResult.Pvo = ((emaResult2.Ema == 0.0) ? ((double?)null) : (100.0 * ((emaResult.Ema - emaResult2.Ema) / emaResult2.Ema))));
|
||
(DateTime, double) item2 = (item, (!num.HasValue) ? 0.0 : num.Value);
|
||
list3.Add(item2);
|
||
}
|
||
}
|
||
List<EmaResult> list5 = list3.CalcEma(signalPeriods);
|
||
for (int j = slowPeriods - 1; j < count; j++)
|
||
{
|
||
PvoResult pvoResult2 = list4[j];
|
||
EmaResult emaResult3 = list5[j + 1 - slowPeriods];
|
||
pvoResult2.Signal = emaResult3.Ema;
|
||
pvoResult2.Histogram = pvoResult2.Pvo - pvoResult2.Signal;
|
||
}
|
||
return list4;
|
||
}
|
||
}
|
||
|
||
private static void ValidatePvo(int fastPeriods, int slowPeriods, int signalPeriods)
|
||
{
|
||
if (fastPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("fastPeriods", fastPeriods, "Fast periods must be greater than 0 for PVO.");
|
||
}
|
||
if (signalPeriods < 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("signalPeriods", signalPeriods, "Signal periods must be greater than or equal to 0 for PVO.");
|
||
}
|
||
if (slowPeriods <= fastPeriods)
|
||
{
|
||
throw new ArgumentOutOfRangeException("slowPeriods", slowPeriods, "Slow periods must be greater than the fast period for PVO.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<PvoResult> RemoveWarmupPeriods(this IEnumerable<PvoResult> results)
|
||
{
|
||
checked
|
||
{
|
||
int num = results.ToList().FindIndex((PvoResult x) => x.Signal.HasValue) + 2;
|
||
return results.Remove(num + 250);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Renko Chart is a modified Japanese candlestick pattern that uses time-lapsed bricks.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Renko/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="brickSize">Fixed brick size ($).</param><param name="endType">End type. See documentation.</param><returns>Time series of Renko Chart candlestick values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<RenkoResult> GetRenko<TQuote>(this IEnumerable<TQuote> quotes, decimal brickSize, EndType endType = EndType.Close) where TQuote : IQuote
|
||
{
|
||
return quotes.ToSortedList().CalcRenko(brickSize, endType);
|
||
}
|
||
|
||
internal static List<RenkoResult> CalcRenko<TQuote>(this List<TQuote> quotesList, decimal brickSize, EndType endType) where TQuote : IQuote
|
||
{
|
||
ValidateRenko(brickSize);
|
||
int count = quotesList.Count;
|
||
List<RenkoResult> list = new List<RenkoResult>(count);
|
||
if (count == 0)
|
||
{
|
||
return list;
|
||
}
|
||
TQuote val = quotesList[0];
|
||
bool flag = true;
|
||
int decimalPlaces = brickSize.GetDecimalPlaces();
|
||
checked
|
||
{
|
||
decimal num = Math.Round(val.Close, Math.Max(decimalPlaces - 1, 0));
|
||
decimal num2 = decimal.MinValue;
|
||
decimal num3 = decimal.MaxValue;
|
||
decimal num4 = default(decimal);
|
||
RenkoResult renkoResult = new RenkoResult(val.Date)
|
||
{
|
||
Open = num,
|
||
Close = num
|
||
};
|
||
for (int i = 1; i < count; i++)
|
||
{
|
||
TQuote q = quotesList[i];
|
||
if (flag)
|
||
{
|
||
num2 = q.High;
|
||
num3 = q.Low;
|
||
num4 = q.Volume;
|
||
}
|
||
else
|
||
{
|
||
num2 = ((q.High > num2) ? q.High : num2);
|
||
num3 = ((q.Low < num3) ? q.Low : num3);
|
||
num4 += q.Volume;
|
||
}
|
||
int newBricks = GetNewBricks(endType, q, renkoResult, brickSize);
|
||
int num5 = Math.Abs(newBricks);
|
||
for (int j = 0; j < num5; j++)
|
||
{
|
||
bool isUp = newBricks >= 0;
|
||
decimal close;
|
||
if (newBricks > 0)
|
||
{
|
||
num = Math.Max(renkoResult.Open, renkoResult.Close);
|
||
close = num + brickSize;
|
||
}
|
||
else
|
||
{
|
||
num = Math.Min(renkoResult.Open, renkoResult.Close);
|
||
close = num - brickSize;
|
||
}
|
||
RenkoResult renkoResult2 = new RenkoResult(q.Date)
|
||
{
|
||
Open = num,
|
||
High = num2,
|
||
Low = num3,
|
||
Close = close,
|
||
Volume = num4 / (decimal)num5,
|
||
IsUp = isUp
|
||
};
|
||
list.Add(renkoResult2);
|
||
renkoResult = renkoResult2;
|
||
}
|
||
flag = num5 != 0;
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static int GetNewBricks<TQuote>(EndType endType, TQuote q, RenkoResult lastBrick, decimal brickSize) where TQuote : IQuote
|
||
{
|
||
decimal num = Math.Max(lastBrick.Open, lastBrick.Close);
|
||
decimal num2 = Math.Min(lastBrick.Open, lastBrick.Close);
|
||
switch (endType)
|
||
{
|
||
case EndType.Close:
|
||
return (q.Close > num) ? ((int)((q.Close - num) / brickSize)) : ((q.Close < num2) ? ((int)((q.Close - num2) / brickSize)) : 0);
|
||
case EndType.HighLow:
|
||
{
|
||
decimal num3 = (q.High - num) / brickSize;
|
||
decimal num4 = (num2 - q.Low) / brickSize;
|
||
return (int)((num3 >= num4) ? num3 : (-num4));
|
||
}
|
||
default:
|
||
throw new ArgumentOutOfRangeException("endType");
|
||
}
|
||
}
|
||
|
||
private static void ValidateRenko(decimal brickSize)
|
||
{
|
||
if (brickSize <= 0m)
|
||
{
|
||
throw new ArgumentOutOfRangeException("brickSize", brickSize, "Brick size must be greater than 0 for Renko Charts.");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// The ATR Renko Chart is a modified Japanese candlestick pattern based on Average True Range brick size.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Renko/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="atrPeriods">Lookback periods for the ATR evaluation.</param><param name="endType">End type. See documentation.</param><returns>Time series of Renko Chart candlestick values.</returns>
|
||
public static IEnumerable<RenkoResult> GetRenkoAtr<TQuote>(this IEnumerable<TQuote> quotes, int atrPeriods, EndType endType = EndType.Close) where TQuote : IQuote
|
||
{
|
||
return quotes.ToSortedList().CalcRenkoAtr(atrPeriods, endType);
|
||
}
|
||
|
||
internal static List<RenkoResult> CalcRenkoAtr<TQuote>(this List<TQuote> quotesList, int atrPeriods, EndType endType = EndType.Close) where TQuote : IQuote
|
||
{
|
||
double? num = quotesList.ToQuoteD().CalcAtr(atrPeriods).LastOrDefault()?.Atr;
|
||
decimal num2 = ((!num.HasValue) ? 0m : ((decimal)num.Value));
|
||
if (!(num2 == 0m))
|
||
{
|
||
return quotesList.CalcRenko(num2, endType);
|
||
}
|
||
return new List<RenkoResult>();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Rate of Change (ROC), also known as Momentum Oscillator, is the percent change of price over a lookback window.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Roc/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><param name="smaPeriods">Optional. Number of periods for an ROC SMA signal line.</param><returns>Time series of ROC values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<RocResult> GetRoc<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods, int? smaPeriods = null) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcRoc(lookbackPeriods, smaPeriods);
|
||
}
|
||
|
||
public static IEnumerable<RocResult> GetRoc(this IEnumerable<IReusableResult> results, int lookbackPeriods, int? smaPeriods = null)
|
||
{
|
||
return results.ToTuple().CalcRoc(lookbackPeriods, smaPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<RocResult> GetRoc(this IEnumerable<(DateTime, double)> priceTuples, int lookbackPeriods, int? smaPeriods = null)
|
||
{
|
||
return priceTuples.ToSortedList().CalcRoc(lookbackPeriods, smaPeriods);
|
||
}
|
||
|
||
internal static List<RocResult> CalcRoc(this List<(DateTime, double)> tpList, int lookbackPeriods, int? smaPeriods)
|
||
{
|
||
ValidateRoc(lookbackPeriods, smaPeriods);
|
||
List<RocResult> list = new List<RocResult>(tpList.Count);
|
||
checked
|
||
{
|
||
for (int i = 0; i < tpList.Count; i++)
|
||
{
|
||
(DateTime, double) tuple = tpList[i];
|
||
DateTime item = tuple.Item1;
|
||
double item2 = tuple.Item2;
|
||
RocResult rocResult = new RocResult(item);
|
||
list.Add(rocResult);
|
||
if (i + 1 > lookbackPeriods)
|
||
{
|
||
double item3 = tpList[i - lookbackPeriods].Item2;
|
||
rocResult.Momentum = (item2 - item3).NaN2Null();
|
||
rocResult.Roc = ((item3 == 0.0) ? ((double?)null) : (100.0 * rocResult.Momentum / item3).NaN2Null());
|
||
}
|
||
if (smaPeriods.HasValue && i >= lookbackPeriods + smaPeriods - 1)
|
||
{
|
||
double? num = 0.0;
|
||
for (int j = i + 1 - smaPeriods.Value; j <= i; j++)
|
||
{
|
||
num += list[j].Roc;
|
||
}
|
||
rocResult.RocSma = num / (double?)smaPeriods;
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateRoc(int lookbackPeriods, int? smaPeriods)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for ROC.");
|
||
}
|
||
if (smaPeriods.HasValue && smaPeriods.GetValueOrDefault() <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("smaPeriods", smaPeriods, "SMA periods must be greater than 0 for ROC.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<RocResult> RemoveWarmupPeriods(this IEnumerable<RocResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((RocResult x) => x.Roc.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Rate of Change with Bands (ROCWB) is the percent change of price over a lookback window with standard deviation bands.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Roc/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><param name="emaPeriods">Number of periods for the ROC EMA line.</param><param name="stdDevPeriods">Number of periods the standard deviation for upper/lower band lines.</param><returns>Time series of ROCWB values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<RocWbResult> GetRocWb<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods, int emaPeriods, int stdDevPeriods) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcRocWb(lookbackPeriods, emaPeriods, stdDevPeriods);
|
||
}
|
||
|
||
public static IEnumerable<RocWbResult> GetRocWb(this IEnumerable<IReusableResult> results, int lookbackPeriods, int emaPeriods, int stdDevPeriods)
|
||
{
|
||
return results.ToTuple().CalcRocWb(lookbackPeriods, emaPeriods, stdDevPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<RocWbResult> GetRocWb(this IEnumerable<(DateTime, double)> priceTuples, int lookbackPeriods, int emaPeriods, int stdDevPeriods)
|
||
{
|
||
return priceTuples.ToSortedList().CalcRocWb(lookbackPeriods, emaPeriods, stdDevPeriods);
|
||
}
|
||
|
||
internal static List<RocWbResult> CalcRocWb(this List<(DateTime, double)> tpList, int lookbackPeriods, int emaPeriods, int stdDevPeriods)
|
||
{
|
||
ValidateRocWb(lookbackPeriods, emaPeriods, stdDevPeriods);
|
||
List<RocWbResult> list = (from x in tpList.CalcRoc(lookbackPeriods, null)
|
||
select new RocWbResult(x.Date)
|
||
{
|
||
Roc = x.Roc
|
||
}).ToList();
|
||
checked
|
||
{
|
||
double num = 2.0 / (double)(emaPeriods + 1);
|
||
double? num2 = 0.0;
|
||
int count = list.Count;
|
||
if (count > lookbackPeriods)
|
||
{
|
||
int num3 = Math.Min(lookbackPeriods + emaPeriods, count);
|
||
for (int num4 = lookbackPeriods; num4 < num3; num4++)
|
||
{
|
||
num2 += list[num4].Roc;
|
||
}
|
||
num2 /= (double)emaPeriods;
|
||
}
|
||
double?[] array = list.Select((RocWbResult x) => x.Roc * x.Roc).ToArray();
|
||
for (int num5 = lookbackPeriods; num5 < count; num5++)
|
||
{
|
||
RocWbResult rocWbResult = list[num5];
|
||
if (num5 + 1 > lookbackPeriods + emaPeriods)
|
||
{
|
||
rocWbResult.RocEma = num2 + num * (rocWbResult.Roc - num2);
|
||
num2 = rocWbResult.RocEma;
|
||
}
|
||
else if (num5 + 1 == lookbackPeriods + emaPeriods)
|
||
{
|
||
rocWbResult.RocEma = num2;
|
||
}
|
||
if (num5 + 1 >= lookbackPeriods + stdDevPeriods)
|
||
{
|
||
double? num6 = 0.0;
|
||
for (int num7 = num5 - stdDevPeriods + 1; num7 <= num5; num7++)
|
||
{
|
||
num6 += array[num7];
|
||
}
|
||
if (num6.HasValue)
|
||
{
|
||
rocWbResult.LowerBand = 0.0 - (rocWbResult.UpperBand = Math.Sqrt(num6.Value / (double)stdDevPeriods));
|
||
}
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateRocWb(int lookbackPeriods, int emaPeriods, int stdDevPeriods)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for ROC with Bands.");
|
||
}
|
||
if (emaPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("emaPeriods", emaPeriods, "EMA periods must be greater than 0 for ROC.");
|
||
}
|
||
if (stdDevPeriods <= 0 || stdDevPeriods > lookbackPeriods)
|
||
{
|
||
throw new ArgumentOutOfRangeException("stdDevPeriods", stdDevPeriods, "Standard Deviation periods must be greater than 0 and less than lookback period for ROC with Bands.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<RocWbResult> RemoveWarmupPeriods(this IEnumerable<RocWbResult> results)
|
||
{
|
||
checked
|
||
{
|
||
int num = results.ToList().FindIndex((RocWbResult x) => x.RocEma.HasValue) + 1;
|
||
return results.Remove(num + 100);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Rolling Pivot Points is a modern update to traditional fixed calendar window Pivot Points.
|
||
/// It depicts support and resistance levels, based on a defined rolling window and offset.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/RollingPivots/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="windowPeriods">Number of periods in the evaluation window.</param><param name="offsetPeriods">Number of periods to offset the window from the current period.</param><param name="pointType">Pivot Point type.</param><returns>Time series of Rolling Pivot Points values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<RollingPivotsResult> GetRollingPivots<TQuote>(this IEnumerable<TQuote> quotes, int windowPeriods, int offsetPeriods, PivotPointType pointType = PivotPointType.Standard) where TQuote : IQuote
|
||
{
|
||
return quotes.ToSortedList().CalcRollingPivots(windowPeriods, offsetPeriods, pointType);
|
||
}
|
||
|
||
internal static List<RollingPivotsResult> CalcRollingPivots<TQuote>(this List<TQuote> quotesList, int windowPeriods, int offsetPeriods, PivotPointType pointType) where TQuote : IQuote
|
||
{
|
||
ValidateRollingPivots(windowPeriods, offsetPeriods);
|
||
int count = quotesList.Count;
|
||
List<RollingPivotsResult> list = new List<RollingPivotsResult>(count);
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
TQuote val = quotesList[i];
|
||
RollingPivotsResult rollingPivotsResult = new RollingPivotsResult
|
||
{
|
||
Date = val.Date
|
||
};
|
||
if (i >= windowPeriods + offsetPeriods)
|
||
{
|
||
int num = i - windowPeriods - offsetPeriods;
|
||
TQuote val2 = quotesList[num];
|
||
decimal num2 = val2.High;
|
||
decimal num3 = val2.Low;
|
||
decimal close = quotesList[i - offsetPeriods - 1].Close;
|
||
for (int j = num; j <= i - offsetPeriods - 1; j++)
|
||
{
|
||
TQuote val3 = quotesList[j];
|
||
num2 = ((val3.High > num2) ? val3.High : num2);
|
||
num3 = ((val3.Low < num3) ? val3.Low : num3);
|
||
}
|
||
RollingPivotsResult pivotPoint = GetPivotPoint<RollingPivotsResult>(pointType, val.Open, num2, num3, close);
|
||
rollingPivotsResult.PP = pivotPoint.PP;
|
||
rollingPivotsResult.S1 = pivotPoint.S1;
|
||
rollingPivotsResult.S2 = pivotPoint.S2;
|
||
rollingPivotsResult.S3 = pivotPoint.S3;
|
||
rollingPivotsResult.S4 = pivotPoint.S4;
|
||
rollingPivotsResult.R1 = pivotPoint.R1;
|
||
rollingPivotsResult.R2 = pivotPoint.R2;
|
||
rollingPivotsResult.R3 = pivotPoint.R3;
|
||
rollingPivotsResult.R4 = pivotPoint.R4;
|
||
}
|
||
list.Add(rollingPivotsResult);
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateRollingPivots(int windowPeriods, int offsetPeriods)
|
||
{
|
||
if (windowPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("windowPeriods", windowPeriods, "Window periods must be greater than 0 for Rolling Pivot Points.");
|
||
}
|
||
if (offsetPeriods < 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("offsetPeriods", offsetPeriods, "Offset periods must be greater than or equal to 0 for Rolling Pivot Points.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<RollingPivotsResult> RemoveWarmupPeriods(this IEnumerable<RollingPivotsResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((RollingPivotsResult x) => x.PP.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Relative Strength Index (RSI) measures strength of the winning/losing streak over N lookback periods
|
||
/// on a scale of 0 to 100, to depict overbought and oversold conditions.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Rsi/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><returns>Time series of RSI values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<RsiResult> GetRsi<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods = 14) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcRsi(lookbackPeriods);
|
||
}
|
||
|
||
public static IEnumerable<RsiResult> GetRsi(this IEnumerable<IReusableResult> results, int lookbackPeriods = 14)
|
||
{
|
||
return results.ToTuple().CalcRsi(lookbackPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<RsiResult> GetRsi(this IEnumerable<(DateTime, double)> priceTuples, int lookbackPeriods = 14)
|
||
{
|
||
return priceTuples.ToSortedList().CalcRsi(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<RsiResult> CalcRsi(this List<(DateTime Date, double Value)> tpList, int lookbackPeriods)
|
||
{
|
||
ValidateRsi(lookbackPeriods);
|
||
int count = tpList.Count;
|
||
double num = 0.0;
|
||
double num2 = 0.0;
|
||
List<RsiResult> list = new List<RsiResult>(count);
|
||
double[] array = new double[count];
|
||
double[] array2 = new double[count];
|
||
if (count == 0)
|
||
{
|
||
return list;
|
||
}
|
||
double num3 = tpList[0].Value;
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
(DateTime Date, double Value) tuple = tpList[i];
|
||
DateTime item = tuple.Date;
|
||
double item2 = tuple.Value;
|
||
RsiResult rsiResult = new RsiResult(item);
|
||
list.Add(rsiResult);
|
||
array[i] = ((item2 > num3) ? (item2 - num3) : 0.0);
|
||
array2[i] = ((item2 < num3) ? (num3 - item2) : 0.0);
|
||
num3 = item2;
|
||
if (i > lookbackPeriods)
|
||
{
|
||
num = (num * (double)(lookbackPeriods - 1) + array[i]) / (double)lookbackPeriods;
|
||
num2 = (num2 * (double)(lookbackPeriods - 1) + array2[i]) / (double)lookbackPeriods;
|
||
if (num2 > 0.0)
|
||
{
|
||
double num4 = num / num2;
|
||
rsiResult.Rsi = 100.0 - 100.0 / (1.0 + num4);
|
||
}
|
||
else
|
||
{
|
||
rsiResult.Rsi = 100.0;
|
||
}
|
||
}
|
||
else if (i == lookbackPeriods)
|
||
{
|
||
double num5 = 0.0;
|
||
double num6 = 0.0;
|
||
for (int j = 1; j <= lookbackPeriods; j++)
|
||
{
|
||
num5 += array[j];
|
||
num6 += array2[j];
|
||
}
|
||
num = num5 / (double)lookbackPeriods;
|
||
num2 = num6 / (double)lookbackPeriods;
|
||
rsiResult.Rsi = ((num2 > 0.0) ? (100.0 - 100.0 / (1.0 + num / num2)) : 100.0);
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateRsi(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods < 1)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for RSI.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<RsiResult> RemoveWarmupPeriods(this IEnumerable<RsiResult> results)
|
||
{
|
||
int num = results.ToList().FindIndex((RsiResult x) => x.Rsi.HasValue);
|
||
return results.Remove(checked(10 * num));
|
||
}
|
||
|
||
/// <summary>
|
||
/// Slope of the best fit line is determined by an ordinary least-squares simple linear regression on price.
|
||
/// It can be used to help identify trend strength and direction.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Slope/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><returns>Time series of Slope values, including Slope, Standard Deviation, R², and a best-fit Line (for the last lookback segment).</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<SlopeResult> GetSlope<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcSlope(lookbackPeriods);
|
||
}
|
||
|
||
public static IEnumerable<SlopeResult> GetSlope(this IEnumerable<IReusableResult> results, int lookbackPeriods)
|
||
{
|
||
return results.ToTuple().CalcSlope(lookbackPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<SlopeResult> GetSlope(this IEnumerable<(DateTime, double)> priceTuples, int lookbackPeriods)
|
||
{
|
||
return priceTuples.ToSortedList().CalcSlope(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<SlopeResult> CalcSlope(this List<(DateTime, double)> tpList, int lookbackPeriods)
|
||
{
|
||
ValidateSlope(lookbackPeriods);
|
||
int count = tpList.Count;
|
||
List<SlopeResult> list = new List<SlopeResult>(count);
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
SlopeResult slopeResult = new SlopeResult(tpList[i].Item1);
|
||
list.Add(slopeResult);
|
||
if (i + 1 >= lookbackPeriods)
|
||
{
|
||
double num = 0.0;
|
||
double num2 = 0.0;
|
||
for (int j = i - lookbackPeriods + 1; j <= i; j++)
|
||
{
|
||
double item = tpList[j].Item2;
|
||
num += (double)j + 1.0;
|
||
num2 += item;
|
||
}
|
||
double num3 = num / (double)lookbackPeriods;
|
||
double num4 = num2 / (double)lookbackPeriods;
|
||
double num5 = 0.0;
|
||
double num6 = 0.0;
|
||
double num7 = 0.0;
|
||
for (int k = i - lookbackPeriods + 1; k <= i; k++)
|
||
{
|
||
double item2 = tpList[k].Item2;
|
||
double num8 = (double)k + 1.0 - num3;
|
||
double num9 = item2 - num4;
|
||
num5 += num8 * num8;
|
||
num6 += num9 * num9;
|
||
num7 += num8 * num9;
|
||
}
|
||
slopeResult.Slope = (num7 / num5).NaN2Null();
|
||
slopeResult.Intercept = (num4 - slopeResult.Slope * num3).NaN2Null();
|
||
double num10 = Math.Sqrt(num5 / (double)lookbackPeriods);
|
||
double num11 = Math.Sqrt(num6 / (double)lookbackPeriods);
|
||
slopeResult.StdDev = num11.NaN2Null();
|
||
if (num10 * num11 != 0.0)
|
||
{
|
||
double num12 = num7 / (num10 * num11) / (double)lookbackPeriods;
|
||
slopeResult.RSquared = (num12 * num12).NaN2Null();
|
||
}
|
||
}
|
||
}
|
||
if (count >= lookbackPeriods)
|
||
{
|
||
SlopeResult slopeResult2 = list.LastOrDefault();
|
||
for (int l = count - lookbackPeriods; l < count; l++)
|
||
{
|
||
list[l].Line = (decimal?)(slopeResult2?.Slope * (double)(l + 1) + slopeResult2?.Intercept).NaN2Null();
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateSlope(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 1)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 1 for Slope/Linear Regression.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<SlopeResult> RemoveWarmupPeriods(this IEnumerable<SlopeResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((SlopeResult x) => x.Slope.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
internal static IEnumerable<SmaAnalysis> CalcSmaAnalysis(this List<(DateTime, double)> tpList, int lookbackPeriods)
|
||
{
|
||
List<SmaAnalysis> list = (from x in tpList.CalcSma(lookbackPeriods)
|
||
select new SmaAnalysis(x.Date)
|
||
{
|
||
Sma = x.Sma
|
||
}).ToList();
|
||
checked
|
||
{
|
||
for (int num = lookbackPeriods - 1; num < list.Count; num++)
|
||
{
|
||
SmaAnalysis smaAnalysis = list[num];
|
||
double num2 = ((!smaAnalysis.Sma.HasValue) ? double.NaN : smaAnalysis.Sma.Value);
|
||
double num3 = 0.0;
|
||
double num4 = 0.0;
|
||
double num5 = 0.0;
|
||
for (int num6 = num + 1 - lookbackPeriods; num6 <= num; num6++)
|
||
{
|
||
double item = tpList[num6].Item2;
|
||
num3 += Math.Abs(item - num2);
|
||
num4 += (item - num2) * (item - num2);
|
||
num5 += ((item == 0.0) ? double.NaN : (Math.Abs(item - num2) / item));
|
||
}
|
||
smaAnalysis.Mad = (num3 / (double)lookbackPeriods).NaN2Null();
|
||
smaAnalysis.Mse = (num4 / (double)lookbackPeriods).NaN2Null();
|
||
smaAnalysis.Mape = (num5 / (double)lookbackPeriods).NaN2Null();
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Simple Moving Average (SMA) of the price.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Sma/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><returns>Time series of SMA values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<SmaResult> GetSma<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcSma(lookbackPeriods);
|
||
}
|
||
|
||
public static IEnumerable<SmaResult> GetSma(this IEnumerable<IReusableResult> results, int lookbackPeriods)
|
||
{
|
||
return results.ToTuple().CalcSma(lookbackPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<SmaResult> GetSma(this IEnumerable<(DateTime, double)> priceTuples, int lookbackPeriods)
|
||
{
|
||
return priceTuples.ToSortedList().CalcSma(lookbackPeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Simple Moving Average (SMA) is the average of price over a lookback window. This extended variant includes mean absolute deviation (MAD), mean square error (MSE), and mean absolute percentage error (MAPE).
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Sma/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><returns>Time series of SMA, MAD, MSE, and MAPE values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<SmaAnalysis> GetSmaAnalysis<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcSmaAnalysis(lookbackPeriods);
|
||
}
|
||
|
||
public static IEnumerable<SmaAnalysis> GetSmaAnalysis(this IEnumerable<IReusableResult> results, int lookbackPeriods)
|
||
{
|
||
return results.ToTuple().CalcSmaAnalysis(lookbackPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<SmaAnalysis> GetSmaAnalysis(this IEnumerable<(DateTime, double)> priceTuples, int lookbackPeriods)
|
||
{
|
||
return priceTuples.ToSortedList().CalcSmaAnalysis(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<SmaResult> CalcSma(this List<(DateTime, double)> tpList, int lookbackPeriods)
|
||
{
|
||
ValidateSma(lookbackPeriods);
|
||
List<SmaResult> list = new List<SmaResult>(tpList.Count);
|
||
checked
|
||
{
|
||
for (int i = 0; i < tpList.Count; i++)
|
||
{
|
||
SmaResult smaResult = new SmaResult(tpList[i].Item1);
|
||
list.Add(smaResult);
|
||
if (i + 1 >= lookbackPeriods)
|
||
{
|
||
double num = 0.0;
|
||
for (int j = i + 1 - lookbackPeriods; j <= i; j++)
|
||
{
|
||
double item = tpList[j].Item2;
|
||
num += item;
|
||
}
|
||
smaResult.Sma = (num / (double)lookbackPeriods).NaN2Null();
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateSma(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for SMA.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<SmaResult> RemoveWarmupPeriods(this IEnumerable<SmaResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((SmaResult x) => x.Sma.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<SmaAnalysis> RemoveWarmupPeriods(this IEnumerable<SmaAnalysis> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((SmaAnalysis x) => x.Sma.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Stochastic Momentum Index is a double-smoothed variant of the Stochastic Oscillator on a scale from -100 to 100.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Smi/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods for the Stochastic lookback.</param><param name="firstSmoothPeriods">Number of periods in the first smoothing.</param><param name="secondSmoothPeriods">Number of periods in the second smoothing.</param><param name="signalPeriods">Number of periods in the EMA of SMI.</param><returns>Time series of Stochastic Momentum Index values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<SmiResult> GetSmi<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods = 13, int firstSmoothPeriods = 25, int secondSmoothPeriods = 2, int signalPeriods = 3) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcSmi(lookbackPeriods, firstSmoothPeriods, secondSmoothPeriods, signalPeriods);
|
||
}
|
||
|
||
internal static List<SmiResult> CalcSmi(this List<QuoteD> qdList, int lookbackPeriods, int firstSmoothPeriods, int secondSmoothPeriods, int signalPeriods)
|
||
{
|
||
ValidateSmi(lookbackPeriods, firstSmoothPeriods, secondSmoothPeriods, signalPeriods);
|
||
int count = qdList.Count;
|
||
List<SmiResult> list = new List<SmiResult>(count);
|
||
checked
|
||
{
|
||
double num = 2.0 / (double)(firstSmoothPeriods + 1);
|
||
double num2 = 2.0 / (double)(secondSmoothPeriods + 1);
|
||
double num3 = 2.0 / (double)(signalPeriods + 1);
|
||
double num4 = 0.0;
|
||
double num5 = 0.0;
|
||
double num6 = 0.0;
|
||
double num7 = 0.0;
|
||
double num8 = 0.0;
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
QuoteD quoteD = qdList[i];
|
||
SmiResult smiResult = new SmiResult(quoteD.Date);
|
||
list.Add(smiResult);
|
||
if (i + 1 < lookbackPeriods)
|
||
{
|
||
continue;
|
||
}
|
||
double num9 = double.MinValue;
|
||
double num10 = double.MaxValue;
|
||
for (int j = i + 1 - lookbackPeriods; j <= i; j++)
|
||
{
|
||
QuoteD quoteD2 = qdList[j];
|
||
if (quoteD2.High > num9)
|
||
{
|
||
num9 = quoteD2.High;
|
||
}
|
||
if (quoteD2.Low < num10)
|
||
{
|
||
num10 = quoteD2.Low;
|
||
}
|
||
}
|
||
double num11 = quoteD.Close - 0.5 * (num9 + num10);
|
||
double num12 = num9 - num10;
|
||
if (i + 1 == lookbackPeriods)
|
||
{
|
||
num4 = num11;
|
||
num5 = num4;
|
||
num6 = num12;
|
||
num7 = num6;
|
||
}
|
||
double num13 = num4 + num * (num11 - num4);
|
||
double num14 = num6 + num * (num12 - num6);
|
||
double num15 = num5 + num2 * (num13 - num5);
|
||
double num16 = num7 + num2 * (num14 - num7);
|
||
double num17 = 100.0 * (num15 / (0.5 * num16));
|
||
smiResult.Smi = num17;
|
||
if (i + 1 == lookbackPeriods)
|
||
{
|
||
num8 = num17;
|
||
}
|
||
double num18 = num8 + num3 * (num17 - num8);
|
||
smiResult.Signal = num18;
|
||
num4 = num13;
|
||
num5 = num15;
|
||
num6 = num14;
|
||
num7 = num16;
|
||
num8 = num18;
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateSmi(int lookbackPeriods, int firstSmoothPeriods, int secondSmoothPeriods, int signalPeriods)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for SMI.");
|
||
}
|
||
if (firstSmoothPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("firstSmoothPeriods", firstSmoothPeriods, "Smoothing periods must be greater than 0 for SMI.");
|
||
}
|
||
if (secondSmoothPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("secondSmoothPeriods", secondSmoothPeriods, "Smoothing periods must be greater than 0 for SMI.");
|
||
}
|
||
if (signalPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("signalPeriods", signalPeriods, "Signal periods must be greater than 0 for SMI.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<SmiResult> RemoveWarmupPeriods(this IEnumerable<SmiResult> results)
|
||
{
|
||
int num = results.ToList().FindIndex((SmiResult x) => x.Smi.HasValue);
|
||
return results.Remove(checked(num + 2 + 100));
|
||
}
|
||
|
||
/// <summary>
|
||
/// Smoothed Moving Average (SMMA) is the average of price over a lookback window using a smoothing method.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Smma/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><returns>Time series of SMMA values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<SmmaResult> GetSmma<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcSmma(lookbackPeriods);
|
||
}
|
||
|
||
public static IEnumerable<SmmaResult> GetSmma(this IEnumerable<IReusableResult> results, int lookbackPeriods)
|
||
{
|
||
return results.ToTuple().CalcSmma(lookbackPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<SmmaResult> GetSmma(this IEnumerable<(DateTime, double)> priceTuples, int lookbackPeriods)
|
||
{
|
||
return priceTuples.ToSortedList().CalcSmma(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<SmmaResult> CalcSmma(this List<(DateTime, double)> tpList, int lookbackPeriods)
|
||
{
|
||
ValidateSmma(lookbackPeriods);
|
||
int count = tpList.Count;
|
||
List<SmmaResult> list = new List<SmmaResult>(count);
|
||
double num = double.NaN;
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
double num2 = double.NaN;
|
||
(DateTime, double) tuple = tpList[i];
|
||
DateTime item = tuple.Item1;
|
||
double item2 = tuple.Item2;
|
||
SmmaResult smmaResult = new SmmaResult(item);
|
||
list.Add(smmaResult);
|
||
if (i + 1 > lookbackPeriods)
|
||
{
|
||
num2 = (num * (double)(lookbackPeriods - 1) + item2) / (double)lookbackPeriods;
|
||
smmaResult.Smma = num2.NaN2Null();
|
||
}
|
||
else if (i + 1 == lookbackPeriods)
|
||
{
|
||
double num3 = 0.0;
|
||
for (int j = i + 1 - lookbackPeriods; j <= i; j++)
|
||
{
|
||
double item3 = tpList[j].Item2;
|
||
num3 += item3;
|
||
}
|
||
num2 = num3 / (double)lookbackPeriods;
|
||
smmaResult.Smma = num2.NaN2Null();
|
||
}
|
||
num = num2;
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateSmma(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for SMMA.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<SmmaResult> RemoveWarmupPeriods(this IEnumerable<SmmaResult> results)
|
||
{
|
||
checked
|
||
{
|
||
int num = results.ToList().FindIndex((SmmaResult x) => x.Smma.HasValue) + 1;
|
||
return results.Remove(num + 100);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Stoller Average Range Channel (STARC) Bands, are based on an SMA centerline and ATR band widths.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/StarcBands/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="smaPeriods">Number of periods for the centerline SMA.</param><param name="multiplier">ATR multiplier sets the width of the channel.</param><param name="atrPeriods">Number of periods in the ATR evaluation.</param><returns>Time series of STARC Bands values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<StarcBandsResult> GetStarcBands<TQuote>(this IEnumerable<TQuote> quotes, int smaPeriods, double multiplier = 2.0, int atrPeriods = 10) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcStarcBands(smaPeriods, multiplier, atrPeriods);
|
||
}
|
||
|
||
internal static List<StarcBandsResult> CalcStarcBands(this List<QuoteD> qdList, int smaPeriods, double multiplier, int atrPeriods)
|
||
{
|
||
ValidateStarcBands(smaPeriods, multiplier, atrPeriods);
|
||
List<AtrResult> list = qdList.CalcAtr(atrPeriods);
|
||
List<StarcBandsResult> list2 = (from x in qdList.ToTuple(CandlePart.Close).CalcSma(smaPeriods)
|
||
select new StarcBandsResult(x.Date)
|
||
{
|
||
Centerline = x.Sma
|
||
}).ToList();
|
||
checked
|
||
{
|
||
for (int num = Math.Max(smaPeriods, atrPeriods) - 1; num < list2.Count; num++)
|
||
{
|
||
StarcBandsResult starcBandsResult = list2[num];
|
||
AtrResult atrResult = list[num];
|
||
starcBandsResult.UpperBand = starcBandsResult.Centerline + multiplier * atrResult.Atr;
|
||
starcBandsResult.LowerBand = starcBandsResult.Centerline - multiplier * atrResult.Atr;
|
||
}
|
||
return list2;
|
||
}
|
||
}
|
||
|
||
private static void ValidateStarcBands(int smaPeriods, double multiplier, int atrPeriods)
|
||
{
|
||
if (smaPeriods <= 1)
|
||
{
|
||
throw new ArgumentOutOfRangeException("smaPeriods", smaPeriods, "EMA periods must be greater than 1 for STARC Bands.");
|
||
}
|
||
if (atrPeriods <= 1)
|
||
{
|
||
throw new ArgumentOutOfRangeException("atrPeriods", atrPeriods, "ATR periods must be greater than 1 for STARC Bands.");
|
||
}
|
||
if (multiplier <= 0.0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("multiplier", multiplier, "Multiplier must be greater than 0 for STARC Bands.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes non-essential records containing null values with unique consideration for
|
||
/// this indicator. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#condense?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator results to evaluate.</param><returns>Time series of
|
||
/// indicator results, condensed.</returns>
|
||
public static IEnumerable<StarcBandsResult> Condense(this IEnumerable<StarcBandsResult> results)
|
||
{
|
||
List<StarcBandsResult> list = results.ToList();
|
||
list.RemoveAll((StarcBandsResult x) => !x.UpperBand.HasValue && !x.LowerBand.HasValue && !x.Centerline.HasValue);
|
||
return list.ToSortedList();
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<StarcBandsResult> RemoveWarmupPeriods(this IEnumerable<StarcBandsResult> results)
|
||
{
|
||
checked
|
||
{
|
||
int num = results.ToList().FindIndex((StarcBandsResult x) => x.UpperBand.HasValue || x.LowerBand.HasValue) + 1;
|
||
return results.Remove(num + 150);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Schaff Trend Cycle is a stochastic oscillator view of two converging/diverging exponential moving averages.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Stc/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<StcResult> GetStc<TQuote>(this IEnumerable<TQuote> quotes, int cyclePeriods = 10, int fastPeriods = 23, int slowPeriods = 50) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcStc(cyclePeriods, fastPeriods, slowPeriods);
|
||
}
|
||
|
||
public static IEnumerable<StcResult> GetStc(this IEnumerable<IReusableResult> results, int cyclePeriods = 10, int fastPeriods = 23, int slowPeriods = 50)
|
||
{
|
||
return results.ToTuple().CalcStc(cyclePeriods, fastPeriods, slowPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<StcResult> GetStc(this IEnumerable<(DateTime, double)> priceTuples, int cyclePeriods = 10, int fastPeriods = 23, int slowPeriods = 50)
|
||
{
|
||
return priceTuples.ToSortedList().CalcStc(cyclePeriods, fastPeriods, slowPeriods);
|
||
}
|
||
|
||
internal static List<StcResult> CalcStc(this List<(DateTime, double)> tpList, int cyclePeriods, int fastPeriods, int slowPeriods)
|
||
{
|
||
ValidateStc(cyclePeriods, fastPeriods, slowPeriods);
|
||
int count = tpList.Count;
|
||
checked
|
||
{
|
||
int num = Math.Min(slowPeriods - 1, count);
|
||
List<StcResult> list = new List<StcResult>(count);
|
||
for (int i = 0; i < num; i++)
|
||
{
|
||
DateTime item = tpList[i].Item1;
|
||
list.Add(new StcResult(item));
|
||
}
|
||
List<StochResult> list2 = (from x in tpList.CalcMacd(fastPeriods, slowPeriods, 1).Remove(num)
|
||
select new QuoteD
|
||
{
|
||
Date = x.Date,
|
||
High = x.Macd.Null2NaN(),
|
||
Low = x.Macd.Null2NaN(),
|
||
Close = x.Macd.Null2NaN()
|
||
}).ToList().CalcStoch(cyclePeriods, 1, 3, 3.0, 2.0, MaType.SMA);
|
||
for (int num2 = 0; num2 < list2.Count; num2++)
|
||
{
|
||
StochResult stochResult = list2[num2];
|
||
list.Add(new StcResult(stochResult.Date)
|
||
{
|
||
Stc = stochResult.Oscillator
|
||
});
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateStc(int cyclePeriods, int fastPeriods, int slowPeriods)
|
||
{
|
||
if (cyclePeriods < 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("cyclePeriods", cyclePeriods, "Trend Cycle periods must be greater than or equal to 0 for STC.");
|
||
}
|
||
if (fastPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("fastPeriods", fastPeriods, "Fast periods must be greater than 0 for STC.");
|
||
}
|
||
if (slowPeriods <= fastPeriods)
|
||
{
|
||
throw new ArgumentOutOfRangeException("slowPeriods", slowPeriods, "Slow periods must be greater than the fast period for STC.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<StcResult> RemoveWarmupPeriods(this IEnumerable<StcResult> results)
|
||
{
|
||
int num = results.ToList().FindIndex((StcResult x) => x.Stc.HasValue);
|
||
return results.Remove(checked(num + 250));
|
||
}
|
||
|
||
/// <summary>
|
||
/// Rolling Standard Deviation of price over a lookback window.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/StdDev/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><param name="smaPeriods">Optional. Number of periods in the Standard Deviation SMA signal line.</param><returns>Time series of Standard Deviations values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<StdDevResult> GetStdDev<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods, int? smaPeriods = null) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcStdDev(lookbackPeriods, smaPeriods);
|
||
}
|
||
|
||
public static IEnumerable<StdDevResult> GetStdDev(this IEnumerable<IReusableResult> results, int lookbackPeriods, int? smaPeriods = null)
|
||
{
|
||
return results.ToTuple().CalcStdDev(lookbackPeriods, smaPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<StdDevResult> GetStdDev(this IEnumerable<(DateTime, double)> priceTuples, int lookbackPeriods, int? smaPeriods = null)
|
||
{
|
||
return priceTuples.ToSortedList().CalcStdDev(lookbackPeriods, smaPeriods);
|
||
}
|
||
|
||
internal static List<StdDevResult> CalcStdDev(this List<(DateTime, double)> tpList, int lookbackPeriods, int? smaPeriods)
|
||
{
|
||
ValidateStdDev(lookbackPeriods, smaPeriods);
|
||
int count = tpList.Count;
|
||
List<StdDevResult> list = new List<StdDevResult>(count);
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
(DateTime, double) tuple = tpList[i];
|
||
DateTime item = tuple.Item1;
|
||
double item2 = tuple.Item2;
|
||
StdDevResult stdDevResult = new StdDevResult(item);
|
||
list.Add(stdDevResult);
|
||
if (i + 1 >= lookbackPeriods)
|
||
{
|
||
double[] array = new double[lookbackPeriods];
|
||
double num = 0.0;
|
||
int num2 = 0;
|
||
for (int j = i + 1 - lookbackPeriods; j <= i; j++)
|
||
{
|
||
num += (array[num2] = tpList[j].Item2);
|
||
num2++;
|
||
}
|
||
double num3 = num / (double)lookbackPeriods;
|
||
stdDevResult.StdDev = array.StdDev().NaN2Null();
|
||
stdDevResult.Mean = num3.NaN2Null();
|
||
stdDevResult.ZScore = ((stdDevResult.StdDev == 0.0) ? ((double?)null) : ((item2 - num3) / stdDevResult.StdDev));
|
||
}
|
||
if (smaPeriods.HasValue && i >= lookbackPeriods + smaPeriods - 2)
|
||
{
|
||
double? num4 = 0.0;
|
||
for (int k = i + 1 - smaPeriods.Value; k <= i; k++)
|
||
{
|
||
num4 += list[k].StdDev;
|
||
}
|
||
stdDevResult.StdDevSma = (num4 / (double?)smaPeriods).NaN2Null();
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateStdDev(int lookbackPeriods, int? smaPeriods)
|
||
{
|
||
if (lookbackPeriods <= 1)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 1 for Standard Deviation.");
|
||
}
|
||
if (smaPeriods.HasValue && smaPeriods.GetValueOrDefault() <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("smaPeriods", smaPeriods, "SMA periods must be greater than 0 for Standard Deviation.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<StdDevResult> RemoveWarmupPeriods(this IEnumerable<StdDevResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((StdDevResult x) => x.StdDev.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Standard Deviation Channels are based on an linear regression centerline and standard deviations band widths.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/StdDevChannels/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Size of the evaluation window.</param><param name="stdDeviations">Width of bands. Number of Standard Deviations from the regression line.</param><returns>Time series of Standard Deviation Channels values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<StdDevChannelsResult> GetStdDevChannels<TQuote>(this IEnumerable<TQuote> quotes, int? lookbackPeriods = 20, double stdDeviations = 2.0) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcStdDevChannels(lookbackPeriods, stdDeviations);
|
||
}
|
||
|
||
public static IEnumerable<StdDevChannelsResult> GetStdDevChannels(this IEnumerable<IReusableResult> results, int? lookbackPeriods = 20, double stdDeviations = 2.0)
|
||
{
|
||
return results.ToTuple().CalcStdDevChannels(lookbackPeriods, stdDeviations).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<StdDevChannelsResult> GetStdDevChannels(this IEnumerable<(DateTime, double)> priceTuples, int? lookbackPeriods = 20, double stdDeviations = 2.0)
|
||
{
|
||
return priceTuples.ToSortedList().CalcStdDevChannels(lookbackPeriods, stdDeviations);
|
||
}
|
||
|
||
internal static List<StdDevChannelsResult> CalcStdDevChannels(this List<(DateTime, double)> tpList, int? lookbackPeriods, double stdDeviations)
|
||
{
|
||
int valueOrDefault = lookbackPeriods.GetValueOrDefault();
|
||
if (!lookbackPeriods.HasValue)
|
||
{
|
||
valueOrDefault = tpList.Count;
|
||
lookbackPeriods = valueOrDefault;
|
||
}
|
||
ValidateStdDevChannels(lookbackPeriods, stdDeviations);
|
||
List<SlopeResult> list = tpList.CalcSlope(lookbackPeriods.Value);
|
||
int count = list.Count;
|
||
List<StdDevChannelsResult> list2 = list.Select((SlopeResult x) => new StdDevChannelsResult(x.Date)).ToList();
|
||
checked
|
||
{
|
||
for (int num = count - 1; num >= lookbackPeriods - 1; num -= lookbackPeriods.Value)
|
||
{
|
||
SlopeResult slopeResult = list[num];
|
||
double? num2 = stdDeviations * slopeResult.StdDev;
|
||
for (int num3 = num - lookbackPeriods.Value + 1; num3 <= num; num3++)
|
||
{
|
||
if (num3 >= 0)
|
||
{
|
||
StdDevChannelsResult stdDevChannelsResult = list2[num3];
|
||
stdDevChannelsResult.Centerline = slopeResult.Slope * (double)(num3 + 1) + slopeResult.Intercept;
|
||
stdDevChannelsResult.UpperChannel = stdDevChannelsResult.Centerline + num2;
|
||
stdDevChannelsResult.LowerChannel = stdDevChannelsResult.Centerline - num2;
|
||
stdDevChannelsResult.BreakPoint = num3 == num - lookbackPeriods + 1;
|
||
}
|
||
}
|
||
}
|
||
return list2;
|
||
}
|
||
}
|
||
|
||
private static void ValidateStdDevChannels(int? lookbackPeriods, double stdDeviations)
|
||
{
|
||
if (lookbackPeriods <= 1)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 1 for Standard Deviation Channels.");
|
||
}
|
||
if (stdDeviations <= 0.0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("stdDeviations", stdDeviations, "Standard Deviations must be greater than 0 for Standard Deviation Channels.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes non-essential records containing null values with unique consideration for
|
||
/// this indicator. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#condense?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator results to evaluate.</param><returns>Time series of
|
||
/// indicator results, condensed.</returns>
|
||
public static IEnumerable<StdDevChannelsResult> Condense(this IEnumerable<StdDevChannelsResult> results)
|
||
{
|
||
List<StdDevChannelsResult> list = results.ToList();
|
||
list.RemoveAll((StdDevChannelsResult x) => !x.UpperChannel.HasValue && !x.LowerChannel.HasValue && !x.Centerline.HasValue && !x.BreakPoint);
|
||
return list.ToSortedList();
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<StdDevChannelsResult> RemoveWarmupPeriods(this IEnumerable<StdDevChannelsResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((StdDevChannelsResult x) => x.UpperChannel.HasValue || x.LowerChannel.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Stochastic Oscillator is a momentum indicator that looks back N periods to produce a scale of 0 to 100.
|
||
/// %J is also included for the KDJ Index extension.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Stoch/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods for the Oscillator.</param><param name="signalPeriods">Smoothing period for the %D signal line.</param><param name="smoothPeriods">Smoothing period for the %K Oscillator. Use 3 for Slow or 1 for Fast.</param><returns>Time series of Stochastic Oscillator values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<StochResult> GetStoch<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods = 14, int signalPeriods = 3, int smoothPeriods = 3) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcStoch(lookbackPeriods, signalPeriods, smoothPeriods, 3.0, 2.0, MaType.SMA);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Stochastic Oscillator is a momentum indicator that looks back N periods to produce a scale of 0 to 100.
|
||
/// %J is also included for the KDJ Index extension.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Stoch/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods for the Oscillator.</param><param name="signalPeriods">Smoothing period for the %D signal line.</param><param name="smoothPeriods">Smoothing period for the %K Oscillator. Use 3 for Slow or 1 for Fast.</param><param name="kFactor">Weight of %K in the %J calculation. Default is 3.</param><param name="dFactor">Weight of %K in the %J calculation. Default is 2.</param><param name="movingAverageType">Type of moving average to use. Default is MaType.SMA. See docs for instructions and options.</param><returns>Time series of Stochastic Oscillator values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<StochResult> GetStoch<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods, int signalPeriods, int smoothPeriods, double kFactor, double dFactor, MaType movingAverageType) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcStoch(lookbackPeriods, signalPeriods, smoothPeriods, kFactor, dFactor, movingAverageType);
|
||
}
|
||
|
||
internal static List<StochResult> CalcStoch(this List<QuoteD> qdList, int lookbackPeriods, int signalPeriods, int smoothPeriods, double kFactor, double dFactor, MaType movingAverageType)
|
||
{
|
||
ValidateStoch(lookbackPeriods, signalPeriods, smoothPeriods, kFactor, dFactor, movingAverageType);
|
||
int count = qdList.Count;
|
||
List<StochResult> list = new List<StochResult>(count);
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
QuoteD quoteD = qdList[i];
|
||
StochResult stochResult = new StochResult(quoteD.Date);
|
||
list.Add(stochResult);
|
||
if (i + 1 < lookbackPeriods)
|
||
{
|
||
continue;
|
||
}
|
||
double num = double.MinValue;
|
||
double num2 = double.MaxValue;
|
||
for (int j = i + 1 - lookbackPeriods; j <= i; j++)
|
||
{
|
||
QuoteD quoteD2 = qdList[j];
|
||
if (quoteD2.High > num)
|
||
{
|
||
num = quoteD2.High;
|
||
}
|
||
if (quoteD2.Low < num2)
|
||
{
|
||
num2 = quoteD2.Low;
|
||
}
|
||
}
|
||
stochResult.Oscillator = ((num2 != num) ? (100.0 * (quoteD.Close - num2) / (num - num2)) : 0.0);
|
||
stochResult.Oscillator = stochResult.Oscillator.NaN2Null();
|
||
}
|
||
if (smoothPeriods > 1)
|
||
{
|
||
list = SmoothOscillator(list, count, lookbackPeriods, smoothPeriods, movingAverageType);
|
||
}
|
||
if (count < lookbackPeriods - 1)
|
||
{
|
||
return list;
|
||
}
|
||
int num3 = lookbackPeriods + smoothPeriods + signalPeriods - 2;
|
||
double? num4 = null;
|
||
for (int k = lookbackPeriods - 1; k < count; k++)
|
||
{
|
||
StochResult stochResult2 = list[k];
|
||
if (signalPeriods <= 1)
|
||
{
|
||
stochResult2.Signal = stochResult2.Oscillator;
|
||
}
|
||
else if (k + 1 >= num3 && movingAverageType == MaType.SMA)
|
||
{
|
||
double? num5 = 0.0;
|
||
for (int l = k + 1 - signalPeriods; l <= k; l++)
|
||
{
|
||
num5 += list[l].Oscillator;
|
||
}
|
||
stochResult2.Signal = num5 / (double)signalPeriods;
|
||
}
|
||
else if (k >= lookbackPeriods - 1 && movingAverageType == MaType.SMMA)
|
||
{
|
||
double? num6 = num4;
|
||
if (!num6.HasValue)
|
||
{
|
||
num4 = list[k].Oscillator;
|
||
}
|
||
num4 = (stochResult2.Signal = (num4 * (double)(signalPeriods - 1) + list[k].Oscillator) / (double)signalPeriods);
|
||
}
|
||
stochResult2.PercentJ = kFactor * stochResult2.Oscillator - dFactor * stochResult2.Signal;
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static List<StochResult> SmoothOscillator(List<StochResult> results, int length, int lookbackPeriods, int smoothPeriods, MaType movingAverageType)
|
||
{
|
||
double?[] array = new double?[length];
|
||
checked
|
||
{
|
||
switch (movingAverageType)
|
||
{
|
||
case MaType.SMA:
|
||
{
|
||
for (int j = lookbackPeriods + smoothPeriods - 2; j < length; j++)
|
||
{
|
||
double? num3 = 0.0;
|
||
for (int k = j + 1 - smoothPeriods; k <= j; k++)
|
||
{
|
||
num3 += results[k].Oscillator;
|
||
}
|
||
array[j] = num3 / (double)smoothPeriods;
|
||
}
|
||
break;
|
||
}
|
||
case MaType.SMMA:
|
||
{
|
||
double? num = results[lookbackPeriods - 1].Oscillator;
|
||
for (int i = lookbackPeriods - 1; i < length; i++)
|
||
{
|
||
double? num2 = num;
|
||
if (!num2.HasValue)
|
||
{
|
||
num = results[i].Oscillator;
|
||
}
|
||
num = (array[i] = (num * (double)(smoothPeriods - 1) + results[i].Oscillator) / (double)smoothPeriods);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
for (int l = 0; l < length; l++)
|
||
{
|
||
results[l].Oscillator = array[l];
|
||
}
|
||
return results;
|
||
}
|
||
}
|
||
|
||
private static void ValidateStoch(int lookbackPeriods, int signalPeriods, int smoothPeriods, double kFactor, double dFactor, MaType movingAverageType)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for Stochastic.");
|
||
}
|
||
if (signalPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("signalPeriods", signalPeriods, "Signal periods must be greater than 0 for Stochastic.");
|
||
}
|
||
if (smoothPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("smoothPeriods", smoothPeriods, "Smooth periods must be greater than 0 for Stochastic.");
|
||
}
|
||
if (kFactor <= 0.0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("kFactor", kFactor, "kFactor must be greater than 0 for Stochastic.");
|
||
}
|
||
if (dFactor <= 0.0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("dFactor", dFactor, "dFactor must be greater than 0 for Stochastic.");
|
||
}
|
||
if (movingAverageType != MaType.SMA && movingAverageType != MaType.SMMA)
|
||
{
|
||
throw new ArgumentOutOfRangeException("dFactor", dFactor, "Stochastic only supports SMA and SMMA moving average types.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<StochResult> RemoveWarmupPeriods(this IEnumerable<StochResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((StochResult x) => x.Oscillator.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Stochastic RSI is a Stochastic interpretation of the Relative Strength Index.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/StochRsi/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="rsiPeriods">Number of periods for the RSI.</param><param name="stochPeriods">Number of periods for the Stochastic.</param><param name="signalPeriods">Number of periods for the Stochastic RSI SMA signal line.</param><param name="smoothPeriods">Number of periods for Stochastic Smoothing. Use 1 for Fast or 3 for Slow.</param><returns>Time series of Stochastic RSI values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<StochRsiResult> GetStochRsi<TQuote>(this IEnumerable<TQuote> quotes, int rsiPeriods, int stochPeriods, int signalPeriods, int smoothPeriods = 1) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcStochRsi(rsiPeriods, stochPeriods, signalPeriods, smoothPeriods);
|
||
}
|
||
|
||
public static IEnumerable<StochRsiResult> GetStochRsi(this IEnumerable<IReusableResult> results, int rsiPeriods, int stochPeriods, int signalPeriods, int smoothPeriods)
|
||
{
|
||
return results.ToTuple().CalcStochRsi(rsiPeriods, stochPeriods, signalPeriods, smoothPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<StochRsiResult> GetStochRsi(this IEnumerable<(DateTime, double)> priceTuples, int rsiPeriods, int stochPeriods, int signalPeriods, int smoothPeriods)
|
||
{
|
||
return priceTuples.ToSortedList().CalcStochRsi(rsiPeriods, stochPeriods, signalPeriods, smoothPeriods);
|
||
}
|
||
|
||
internal static List<StochRsiResult> CalcStochRsi(this List<(DateTime, double)> tpList, int rsiPeriods, int stochPeriods, int signalPeriods, int smoothPeriods)
|
||
{
|
||
ValidateStochRsi(rsiPeriods, stochPeriods, signalPeriods, smoothPeriods);
|
||
int count = tpList.Count;
|
||
checked
|
||
{
|
||
int num = Math.Min(rsiPeriods + stochPeriods - 1, count);
|
||
List<StochRsiResult> list = new List<StochRsiResult>(count);
|
||
for (int i = 0; i < num; i++)
|
||
{
|
||
DateTime item = tpList[i].Item1;
|
||
list.Add(new StochRsiResult(item));
|
||
}
|
||
List<StochResult> list2 = (from x in tpList.CalcRsi(rsiPeriods).Remove(Math.Min(rsiPeriods, count))
|
||
select new QuoteD
|
||
{
|
||
Date = x.Date,
|
||
High = x.Rsi.Null2NaN(),
|
||
Low = x.Rsi.Null2NaN(),
|
||
Close = x.Rsi.Null2NaN()
|
||
}).ToList().CalcStoch(stochPeriods, signalPeriods, smoothPeriods, 3.0, 2.0, MaType.SMA).ToList();
|
||
for (int num2 = rsiPeriods + stochPeriods - 1; num2 < count; num2++)
|
||
{
|
||
StochResult stochResult = list2[num2 - rsiPeriods];
|
||
list.Add(new StochRsiResult(stochResult.Date)
|
||
{
|
||
StochRsi = stochResult.Oscillator,
|
||
Signal = stochResult.Signal
|
||
});
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateStochRsi(int rsiPeriods, int stochPeriods, int signalPeriods, int smoothPeriods)
|
||
{
|
||
if (rsiPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("rsiPeriods", rsiPeriods, "RSI periods must be greater than 0 for Stochastic RSI.");
|
||
}
|
||
if (stochPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("stochPeriods", stochPeriods, "STOCH periods must be greater than 0 for Stochastic RSI.");
|
||
}
|
||
if (signalPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("signalPeriods", signalPeriods, "Signal periods must be greater than 0 for Stochastic RSI.");
|
||
}
|
||
if (smoothPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("smoothPeriods", smoothPeriods, "Smooth periods must be greater than 0 for Stochastic RSI.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<StochRsiResult> RemoveWarmupPeriods(this IEnumerable<StochRsiResult> results)
|
||
{
|
||
checked
|
||
{
|
||
int num = results.ToList().FindIndex((StochRsiResult x) => x.StochRsi.HasValue) + 2;
|
||
return results.Remove(num + 100);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// SuperTrend attempts to determine the primary trend of prices by using
|
||
/// Average True Range (ATR) band thresholds around an HL2 midline. It can indicate a buy/sell signal or a
|
||
/// trailing stop when the trend changes.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/SuperTrend/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods for ATR.</param><param name="multiplier">Multiplier sets the ATR band width.</param><returns>Time series of SuperTrend values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<SuperTrendResult> GetSuperTrend<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods = 10, double multiplier = 3.0) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcSuperTrend(lookbackPeriods, multiplier);
|
||
}
|
||
|
||
internal static List<SuperTrendResult> CalcSuperTrend(this List<QuoteD> qdList, int lookbackPeriods, double multiplier)
|
||
{
|
||
ValidateSuperTrend(lookbackPeriods, multiplier);
|
||
List<SuperTrendResult> list = new List<SuperTrendResult>(qdList.Count);
|
||
List<AtrResult> list2 = qdList.CalcAtr(lookbackPeriods);
|
||
bool flag = true;
|
||
double? num = null;
|
||
double? num2 = null;
|
||
checked
|
||
{
|
||
for (int i = 0; i < qdList.Count; i++)
|
||
{
|
||
QuoteD quoteD = qdList[i];
|
||
SuperTrendResult superTrendResult = new SuperTrendResult(quoteD.Date);
|
||
list.Add(superTrendResult);
|
||
if (i >= lookbackPeriods)
|
||
{
|
||
double? num3 = (quoteD.High + quoteD.Low) / 2.0;
|
||
double? atr = list2[i].Atr;
|
||
double? num4 = qdList[i - 1].Close;
|
||
double? num5 = num3 + multiplier * atr;
|
||
double? num6 = num3 - multiplier * atr;
|
||
if (i == lookbackPeriods)
|
||
{
|
||
flag = quoteD.Close >= num3;
|
||
num = num5;
|
||
num2 = num6;
|
||
}
|
||
if (num5 < num || num4 > num)
|
||
{
|
||
num = num5;
|
||
}
|
||
if (num6 > num2 || num4 < num2)
|
||
{
|
||
num2 = num6;
|
||
}
|
||
if (quoteD.Close <= (flag ? num2 : num))
|
||
{
|
||
superTrendResult.SuperTrend = (decimal?)num;
|
||
superTrendResult.UpperBand = (decimal?)num;
|
||
flag = false;
|
||
}
|
||
else
|
||
{
|
||
superTrendResult.SuperTrend = (decimal?)num2;
|
||
superTrendResult.LowerBand = (decimal?)num2;
|
||
flag = true;
|
||
}
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateSuperTrend(int lookbackPeriods, double multiplier)
|
||
{
|
||
if (lookbackPeriods <= 1)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 1 for SuperTrend.");
|
||
}
|
||
if (multiplier <= 0.0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("multiplier", multiplier, "Multiplier must be greater than 0 for SuperTrend.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes non-essential records containing null values with unique consideration for
|
||
/// this indicator. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#condense?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator results to evaluate.</param><returns>Time series of
|
||
/// indicator results, condensed.</returns>
|
||
public static IEnumerable<SuperTrendResult> Condense(this IEnumerable<SuperTrendResult> results)
|
||
{
|
||
List<SuperTrendResult> list = results.ToList();
|
||
list.RemoveAll((SuperTrendResult x) => !x.SuperTrend.HasValue);
|
||
return list.ToSortedList();
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<SuperTrendResult> RemoveWarmupPeriods(this IEnumerable<SuperTrendResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((SuperTrendResult x) => x.SuperTrend.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Tillson T3 is a smooth moving average that reduces both lag and overshooting.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/T3/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods for the EMA smoothing.</param><param name="volumeFactor">Size of the Volume Factor.</param><returns>Time series of T3 values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<T3Result> GetT3<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods = 5, double volumeFactor = 0.7) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcT3(lookbackPeriods, volumeFactor);
|
||
}
|
||
|
||
public static IEnumerable<T3Result> GetT3(this IEnumerable<IReusableResult> results, int lookbackPeriods = 5, double volumeFactor = 0.7)
|
||
{
|
||
return results.ToTuple().CalcT3(lookbackPeriods, volumeFactor).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<T3Result> GetT3(this IEnumerable<(DateTime, double)> priceTuples, int lookbackPeriods = 5, double volumeFactor = 0.7)
|
||
{
|
||
return priceTuples.ToSortedList().CalcT3(lookbackPeriods, volumeFactor);
|
||
}
|
||
|
||
internal static List<T3Result> CalcT3(this List<(DateTime, double)> tpList, int lookbackPeriods, double volumeFactor)
|
||
{
|
||
ValidateT3(lookbackPeriods, volumeFactor);
|
||
int count = tpList.Count;
|
||
List<T3Result> list = new List<T3Result>(count);
|
||
if (count == 0)
|
||
{
|
||
return list;
|
||
}
|
||
checked
|
||
{
|
||
double num = 2.0 / (double)(lookbackPeriods + 1);
|
||
double num2 = (0.0 - volumeFactor) * volumeFactor * volumeFactor;
|
||
double num3 = 3.0 * volumeFactor * volumeFactor + 3.0 * volumeFactor * volumeFactor * volumeFactor;
|
||
double num4 = -6.0 * volumeFactor * volumeFactor - 3.0 * volumeFactor - 3.0 * volumeFactor * volumeFactor * volumeFactor;
|
||
double num5 = 1.0 + 3.0 * volumeFactor + volumeFactor * volumeFactor * volumeFactor + 3.0 * volumeFactor * volumeFactor;
|
||
(DateTime, double) tuple = tpList[0];
|
||
double? num6 = tuple.Item2;
|
||
double? num8;
|
||
double? num9;
|
||
double? num10;
|
||
double? num11;
|
||
double? num7 = (num8 = (num9 = (num10 = (num11 = num6))));
|
||
list.Add(new T3Result(tuple.Item1)
|
||
{
|
||
T3 = tuple.Item2
|
||
});
|
||
for (int i = 1; i < count; i++)
|
||
{
|
||
(DateTime, double) tuple2 = tpList[i];
|
||
DateTime item = tuple2.Item1;
|
||
double item2 = tuple2.Item2;
|
||
T3Result t3Result = new T3Result(item);
|
||
list.Add(t3Result);
|
||
num7 += num * (item2 - num7);
|
||
num8 += num * (num7 - num8);
|
||
num9 += num * (num8 - num9);
|
||
num10 += num * (num9 - num10);
|
||
num11 += num * (num10 - num11);
|
||
num6 += num * (num11 - num6);
|
||
t3Result.T3 = (num2 * num6 + num3 * num11 + num4 * num10 + num5 * num9).NaN2Null();
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateT3(int lookbackPeriods, double volumeFactor)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for T3.");
|
||
}
|
||
if (volumeFactor <= 0.0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("volumeFactor", volumeFactor, "Volume Factor must be greater than 0 for T3.");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Triple Exponential Moving Average (TEMA) of the price. Note: TEMA is often confused with the alternative TRIX oscillator.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Tema/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><returns>Time series of Triple EMA values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<TemaResult> GetTema<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcTema(lookbackPeriods);
|
||
}
|
||
|
||
public static IEnumerable<TemaResult> GetTema(this IEnumerable<IReusableResult> results, int lookbackPeriods)
|
||
{
|
||
return results.ToTuple().CalcTema(lookbackPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<TemaResult> GetTema(this IEnumerable<(DateTime, double)> priceTuples, int lookbackPeriods)
|
||
{
|
||
return priceTuples.ToSortedList().CalcTema(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<TemaResult> CalcTema(this List<(DateTime, double)> tpList, int lookbackPeriods)
|
||
{
|
||
ValidateTema(lookbackPeriods);
|
||
int count = tpList.Count;
|
||
List<TemaResult> list = new List<TemaResult>(count);
|
||
checked
|
||
{
|
||
double num = 2.0 / (double)(lookbackPeriods + 1);
|
||
double? num2 = 0.0;
|
||
int num3 = Math.Min(lookbackPeriods, count);
|
||
for (int i = 0; i < num3; i++)
|
||
{
|
||
num2 += tpList[i].Item2;
|
||
}
|
||
num2 /= (double)lookbackPeriods;
|
||
double? num5;
|
||
double? num4 = (num5 = num2);
|
||
for (int j = 0; j < count; j++)
|
||
{
|
||
(DateTime, double) tuple = tpList[j];
|
||
DateTime item = tuple.Item1;
|
||
double item2 = tuple.Item2;
|
||
TemaResult temaResult = new TemaResult(item);
|
||
list.Add(temaResult);
|
||
if (j > lookbackPeriods - 1)
|
||
{
|
||
double? num6 = num2 + num * (item2 - num2);
|
||
double? num7 = num4 + num * (num6 - num4);
|
||
double? num8 = num5 + num * (num7 - num5);
|
||
temaResult.Tema = (3.0 * num6 - 3.0 * num7 + num8).NaN2Null();
|
||
num2 = num6;
|
||
num4 = num7;
|
||
num5 = num8;
|
||
}
|
||
else if (j == lookbackPeriods - 1)
|
||
{
|
||
temaResult.Tema = (3.0 * num2 - 3.0 * num4 + num5).NaN2Null();
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateTema(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for TEMA.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<TemaResult> RemoveWarmupPeriods(this IEnumerable<TemaResult> results)
|
||
{
|
||
checked
|
||
{
|
||
int num = results.ToList().FindIndex((TemaResult x) => x.Tema.HasValue) + 1;
|
||
return results.Remove(3 * num + 100);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// True Range (TR) is a measure of volatility that captures gaps and limits between periods.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Atr/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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><returns>Time series of True Range (TR) values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<TrResult> GetTr<TQuote>(this IEnumerable<TQuote> quotes) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcTr();
|
||
}
|
||
|
||
internal static List<TrResult> CalcTr(this List<QuoteD> qdList)
|
||
{
|
||
List<TrResult> list = new List<TrResult>(qdList.Count);
|
||
double num = double.NaN;
|
||
for (int i = 0; i < qdList.Count; i = checked(i + 1))
|
||
{
|
||
QuoteD quoteD = qdList[i];
|
||
TrResult trResult = new TrResult(quoteD.Date);
|
||
list.Add(trResult);
|
||
if (i == 0)
|
||
{
|
||
num = quoteD.Close;
|
||
continue;
|
||
}
|
||
double val = Math.Abs(quoteD.High - num);
|
||
double val2 = Math.Abs(quoteD.Low - num);
|
||
trResult.Tr = Math.Max(quoteD.High - quoteD.Low, Math.Max(val, val2));
|
||
num = quoteD.Close;
|
||
}
|
||
return list;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Triple EMA Oscillator (TRIX) is the rate of change for a 3 EMA smoothing of the price over a lookback window. TRIX is often confused with TEMA.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Trix/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><param name="signalPeriods">Optional. Number of periods for a TRIX SMA signal line.</param><returns>Time series of TRIX values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<TrixResult> GetTrix<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods, int? signalPeriods = null) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcTrix(lookbackPeriods, signalPeriods);
|
||
}
|
||
|
||
public static IEnumerable<TrixResult> GetTrix(this IEnumerable<IReusableResult> results, int lookbackPeriods, int? signalPeriods = null)
|
||
{
|
||
return results.ToTuple().CalcTrix(lookbackPeriods, signalPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<TrixResult> GetTrix(this IEnumerable<(DateTime, double)> priceTuples, int lookbackPeriods, int? signalPeriods = null)
|
||
{
|
||
return priceTuples.ToSortedList().CalcTrix(lookbackPeriods, signalPeriods);
|
||
}
|
||
|
||
internal static List<TrixResult> CalcTrix(this List<(DateTime, double)> tpList, int lookbackPeriods, int? signalPeriods)
|
||
{
|
||
ValidateTrix(lookbackPeriods);
|
||
int count = tpList.Count;
|
||
List<TrixResult> list = new List<TrixResult>(count);
|
||
checked
|
||
{
|
||
double num = 2.0 / (double)(lookbackPeriods + 1);
|
||
double? num2 = 0.0;
|
||
int num3 = Math.Min(lookbackPeriods, count);
|
||
for (int i = 0; i < num3; i++)
|
||
{
|
||
num2 += tpList[i].Item2;
|
||
}
|
||
num2 /= (double)num3;
|
||
double? num5;
|
||
double? num4 = (num5 = num2);
|
||
for (int j = 0; j < count; j++)
|
||
{
|
||
(DateTime, double) tuple = tpList[j];
|
||
DateTime item = tuple.Item1;
|
||
double item2 = tuple.Item2;
|
||
TrixResult trixResult = new TrixResult(item);
|
||
list.Add(trixResult);
|
||
if (j >= lookbackPeriods)
|
||
{
|
||
double? num6 = num2 + num * (item2 - num2);
|
||
double? obj = num4 + num * (num6 - num4);
|
||
double? num7 = num5 + num * (obj - num5);
|
||
trixResult.Ema3 = num7.NaN2Null();
|
||
trixResult.Trix = (100.0 * (num7 - num5) / num5).NaN2Null();
|
||
num2 = num6;
|
||
num4 = obj;
|
||
num5 = num7;
|
||
}
|
||
CalcTrixSignal(signalPeriods, j, lookbackPeriods, list);
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void CalcTrixSignal(int? signalPeriods, int i, int lookbackPeriods, List<TrixResult> results)
|
||
{
|
||
checked
|
||
{
|
||
if (signalPeriods.HasValue && i >= lookbackPeriods + signalPeriods - 1)
|
||
{
|
||
double? num = 0.0;
|
||
for (int j = i + 1 - signalPeriods.Value; j <= i; j++)
|
||
{
|
||
num += results[j].Trix;
|
||
}
|
||
results[i].Signal = num / (double?)signalPeriods;
|
||
}
|
||
}
|
||
}
|
||
|
||
private static void ValidateTrix(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for TRIX.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<TrixResult> RemoveWarmupPeriods(this IEnumerable<TrixResult> results)
|
||
{
|
||
int num = results.ToList().FindIndex((TrixResult x) => x.Trix.HasValue);
|
||
return results.Remove(checked(3 * num + 100));
|
||
}
|
||
|
||
/// <summary>
|
||
/// True Strength Index (TSI) is a momentum oscillator that depicts trends in price changes.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Tsi/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods for the first EMA.</param><param name="smoothPeriods">Number of periods in the second smoothing.</param><param name="signalPeriods">Number of periods in the TSI SMA signal line.</param><returns>Time series of TSI values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<TsiResult> GetTsi<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods = 25, int smoothPeriods = 13, int signalPeriods = 7) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcTsi(lookbackPeriods, smoothPeriods, signalPeriods);
|
||
}
|
||
|
||
public static IEnumerable<TsiResult> GetTsi(this IEnumerable<IReusableResult> results, int lookbackPeriods = 25, int smoothPeriods = 13, int signalPeriods = 7)
|
||
{
|
||
return results.ToTuple().CalcTsi(lookbackPeriods, smoothPeriods, signalPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<TsiResult> GetTsi(this IEnumerable<(DateTime, double)> priceTuples, int lookbackPeriods = 25, int smoothPeriods = 13, int signalPeriods = 7)
|
||
{
|
||
return priceTuples.ToSortedList().CalcTsi(lookbackPeriods, smoothPeriods, signalPeriods);
|
||
}
|
||
|
||
internal static List<TsiResult> CalcTsi(this List<(DateTime, double)> tpList, int lookbackPeriods, int smoothPeriods, int signalPeriods)
|
||
{
|
||
ValidateTsi(lookbackPeriods, smoothPeriods, signalPeriods);
|
||
int count = tpList.Count;
|
||
checked
|
||
{
|
||
double num = 2.0 / (double)(lookbackPeriods + 1);
|
||
double num2 = 2.0 / (double)(smoothPeriods + 1);
|
||
double num3 = 2.0 / (double)(signalPeriods + 1);
|
||
double num4 = 0.0;
|
||
List<TsiResult> list = new List<TsiResult>(count);
|
||
double[] array = new double[count];
|
||
double[] array2 = new double[count];
|
||
double[] array3 = new double[count];
|
||
double num5 = 0.0;
|
||
double num6 = 0.0;
|
||
double[] array4 = new double[count];
|
||
double[] array5 = new double[count];
|
||
double[] array6 = new double[count];
|
||
double num7 = 0.0;
|
||
double num8 = 0.0;
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
(DateTime, double) tuple = tpList[i];
|
||
DateTime item = tuple.Item1;
|
||
double item2 = tuple.Item2;
|
||
TsiResult tsiResult = new TsiResult(item);
|
||
list.Add(tsiResult);
|
||
if (i == 0)
|
||
{
|
||
continue;
|
||
}
|
||
array[i] = item2 - tpList[i - 1].Item2;
|
||
array4[i] = Math.Abs(array[i]);
|
||
if (i > lookbackPeriods)
|
||
{
|
||
array2[i] = (array[i] - array2[i - 1]) * num + array2[i - 1];
|
||
array5[i] = (array4[i] - array5[i - 1]) * num + array5[i - 1];
|
||
if (i + 1 > lookbackPeriods + smoothPeriods)
|
||
{
|
||
array3[i] = (array2[i] - array3[i - 1]) * num2 + array3[i - 1];
|
||
array6[i] = (array5[i] - array6[i - 1]) * num2 + array6[i - 1];
|
||
double num9 = ((array6[i] != 0.0) ? (100.0 * (array3[i] / array6[i])) : double.NaN);
|
||
tsiResult.Tsi = num9.NaN2Null();
|
||
if (signalPeriods > 0)
|
||
{
|
||
int num10 = lookbackPeriods + smoothPeriods + signalPeriods - 1;
|
||
if (i >= num10)
|
||
{
|
||
tsiResult.Signal = ((num9 - list[i - 1].Signal) * num3).NaN2Null() + list[i - 1].Signal;
|
||
}
|
||
else if (i == num10 - 1)
|
||
{
|
||
num4 += num9;
|
||
tsiResult.Signal = num4 / (double)signalPeriods;
|
||
}
|
||
else
|
||
{
|
||
num4 += num9;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
num6 += array2[i];
|
||
num8 += array5[i];
|
||
if (i + 1 == lookbackPeriods + smoothPeriods)
|
||
{
|
||
array3[i] = num6 / (double)smoothPeriods;
|
||
array6[i] = num8 / (double)smoothPeriods;
|
||
double num11 = ((array6[i] != 0.0) ? (100.0 * array3[i] / array6[i]) : double.NaN);
|
||
tsiResult.Tsi = num11;
|
||
num4 = num11;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
num5 += array[i];
|
||
num7 += array4[i];
|
||
if (i == lookbackPeriods)
|
||
{
|
||
array2[i] = num5 / (double)lookbackPeriods;
|
||
array5[i] = num7 / (double)lookbackPeriods;
|
||
num6 = array2[i];
|
||
num8 = array5[i];
|
||
}
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateTsi(int lookbackPeriods, int smoothPeriods, int signalPeriods)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for TSI.");
|
||
}
|
||
if (smoothPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("smoothPeriods", smoothPeriods, "Smoothing periods must be greater than 0 for TSI.");
|
||
}
|
||
if (signalPeriods < 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("signalPeriods", signalPeriods, "Signal periods must be greater than or equal to 0 for TSI.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<TsiResult> RemoveWarmupPeriods(this IEnumerable<TsiResult> results)
|
||
{
|
||
checked
|
||
{
|
||
int num = results.ToList().FindIndex((TsiResult x) => x.Tsi.HasValue) + 1;
|
||
return results.Remove(num + 250);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Ulcer Index (UI) is a measure of downside price volatility over a lookback window.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/UlcerIndex/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><returns>Time series of Ulcer Index values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<UlcerIndexResult> GetUlcerIndex<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods = 14) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcUlcerIndex(lookbackPeriods);
|
||
}
|
||
|
||
public static IEnumerable<UlcerIndexResult> GetUlcerIndex(this IEnumerable<IReusableResult> results, int lookbackPeriods)
|
||
{
|
||
return results.ToTuple().CalcUlcerIndex(lookbackPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<UlcerIndexResult> GetUlcerIndex(this IEnumerable<(DateTime, double)> priceTuples, int lookbackPeriods)
|
||
{
|
||
return priceTuples.ToSortedList().CalcUlcerIndex(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<UlcerIndexResult> CalcUlcerIndex(this List<(DateTime, double)> tpList, int lookbackPeriods)
|
||
{
|
||
ValidateUlcer(lookbackPeriods);
|
||
List<UlcerIndexResult> list = new List<UlcerIndexResult>(tpList.Count);
|
||
checked
|
||
{
|
||
for (int i = 0; i < tpList.Count; i++)
|
||
{
|
||
UlcerIndexResult ulcerIndexResult = new UlcerIndexResult(tpList[i].Item1);
|
||
list.Add(ulcerIndexResult);
|
||
if (i + 1 < lookbackPeriods)
|
||
{
|
||
continue;
|
||
}
|
||
double num = 0.0;
|
||
for (int j = i + 1 - lookbackPeriods; j <= i; j++)
|
||
{
|
||
double item = tpList[j].Item2;
|
||
int num2 = j + 1;
|
||
double num3 = 0.0;
|
||
for (int k = i + 1 - lookbackPeriods; k < num2; k++)
|
||
{
|
||
double item2 = tpList[k].Item2;
|
||
if (item2 > num3)
|
||
{
|
||
num3 = item2;
|
||
}
|
||
}
|
||
double num4 = ((num3 == 0.0) ? double.NaN : (100.0 * ((item - num3) / num3)));
|
||
num += num4 * num4;
|
||
}
|
||
ulcerIndexResult.UI = Math.Sqrt(num / (double)lookbackPeriods).NaN2Null();
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateUlcer(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for Ulcer Index.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<UlcerIndexResult> RemoveWarmupPeriods(this IEnumerable<UlcerIndexResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((UlcerIndexResult x) => x.UI.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Ultimate Oscillator uses several lookback periods to weigh buying power against True Range price to produce on oversold / overbought oscillator.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Ultimate/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="shortPeriods">Number of periods in the smallest window.</param><param name="middlePeriods">Number of periods in the middle-sized window.</param><param name="longPeriods">Number of periods in the largest window.</param><returns>Time series of Ultimate Oscillator values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<UltimateResult> GetUltimate<TQuote>(this IEnumerable<TQuote> quotes, int shortPeriods = 7, int middlePeriods = 14, int longPeriods = 28) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcUltimate(shortPeriods, middlePeriods, longPeriods);
|
||
}
|
||
|
||
internal static List<UltimateResult> CalcUltimate(this List<QuoteD> qdList, int shortPeriods, int middlePeriods, int longPeriods)
|
||
{
|
||
ValidateUltimate(shortPeriods, middlePeriods, longPeriods);
|
||
int count = qdList.Count;
|
||
List<UltimateResult> list = new List<UltimateResult>(count);
|
||
double[] array = new double[count];
|
||
double[] array2 = new double[count];
|
||
double val = 0.0;
|
||
checked
|
||
{
|
||
for (int i = 0; i < qdList.Count; i++)
|
||
{
|
||
QuoteD quoteD = qdList[i];
|
||
UltimateResult ultimateResult = new UltimateResult(quoteD.Date);
|
||
list.Add(ultimateResult);
|
||
if (i > 0)
|
||
{
|
||
array[i] = quoteD.Close - Math.Min(quoteD.Low, val);
|
||
array2[i] = Math.Max(quoteD.High, val) - Math.Min(quoteD.Low, val);
|
||
}
|
||
if (i >= longPeriods)
|
||
{
|
||
double num = 0.0;
|
||
double num2 = 0.0;
|
||
double num3 = 0.0;
|
||
double num4 = 0.0;
|
||
double num5 = 0.0;
|
||
double num6 = 0.0;
|
||
for (int j = i + 1 - longPeriods; j <= i; j++)
|
||
{
|
||
int num7 = j + 1;
|
||
if (num7 > i + 1 - shortPeriods)
|
||
{
|
||
num += array[j];
|
||
num4 += array2[j];
|
||
}
|
||
if (num7 > i + 1 - middlePeriods)
|
||
{
|
||
num2 += array[j];
|
||
num5 += array2[j];
|
||
}
|
||
num3 += array[j];
|
||
num6 += array2[j];
|
||
}
|
||
double num8 = ((num4 == 0.0) ? double.NaN : (num / num4));
|
||
double num9 = ((num5 == 0.0) ? double.NaN : (num2 / num5));
|
||
double num10 = ((num6 == 0.0) ? double.NaN : (num3 / num6));
|
||
ultimateResult.Ultimate = (100.0 * (4.0 * num8 + 2.0 * num9 + num10) / 7.0).NaN2Null();
|
||
}
|
||
val = quoteD.Close;
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateUltimate(int shortPeriods, int middleAverage, int longPeriods)
|
||
{
|
||
if (shortPeriods <= 0 || middleAverage <= 0 || longPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("longPeriods", longPeriods, "Average periods must be greater than 0 for Ultimate Oscillator.");
|
||
}
|
||
if (shortPeriods >= middleAverage || middleAverage >= longPeriods)
|
||
{
|
||
throw new ArgumentOutOfRangeException("middleAverage", middleAverage, "Average periods must be increasingly larger than each other for Ultimate Oscillator.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<UltimateResult> RemoveWarmupPeriods(this IEnumerable<UltimateResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((UltimateResult x) => x.Ultimate.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Volatility Stop is an ATR based indicator used to determine trend direction, stops, and reversals.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/VolatilityStop/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><param name="multiplier">ATR offset amount.</param><returns>Time series of Volatility Stop values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<VolatilityStopResult> GetVolatilityStop<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods = 7, double multiplier = 3.0) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcVolatilityStop(lookbackPeriods, multiplier);
|
||
}
|
||
|
||
internal static List<VolatilityStopResult> CalcVolatilityStop(this List<QuoteD> qdList, int lookbackPeriods, double multiplier)
|
||
{
|
||
List<(DateTime, double)> list = qdList.ToTuple(CandlePart.Close);
|
||
ValidateVolatilityStop(lookbackPeriods, multiplier);
|
||
int count = list.Count;
|
||
List<VolatilityStopResult> list2 = new List<VolatilityStopResult>(count);
|
||
if (count == 0)
|
||
{
|
||
return list2;
|
||
}
|
||
List<AtrResult> list3 = qdList.CalcAtr(lookbackPeriods);
|
||
int num = Math.Min(count, lookbackPeriods);
|
||
double num2 = list[0].Item2;
|
||
checked
|
||
{
|
||
bool flag = list[num - 1].Item2 > num2;
|
||
for (int i = 0; i < num; i++)
|
||
{
|
||
(DateTime, double) tuple = list[i];
|
||
DateTime item = tuple.Item1;
|
||
double item2 = tuple.Item2;
|
||
num2 = (flag ? Math.Max(num2, item2) : Math.Min(num2, item2));
|
||
list2.Add(new VolatilityStopResult(item));
|
||
}
|
||
for (int j = lookbackPeriods; j < count; j++)
|
||
{
|
||
(DateTime, double) tuple2 = list[j];
|
||
DateTime item3 = tuple2.Item1;
|
||
double item4 = tuple2.Item2;
|
||
double? num3 = list3[j - 1].Atr * multiplier;
|
||
VolatilityStopResult volatilityStopResult = new VolatilityStopResult(item3)
|
||
{
|
||
Sar = ((!flag) ? (num2 + num3) : (num2 - num3))
|
||
};
|
||
list2.Add(volatilityStopResult);
|
||
if (flag)
|
||
{
|
||
volatilityStopResult.LowerBand = volatilityStopResult.Sar;
|
||
}
|
||
else
|
||
{
|
||
volatilityStopResult.UpperBand = volatilityStopResult.Sar;
|
||
}
|
||
if ((flag && item4 < volatilityStopResult.Sar) || (!flag && item4 > volatilityStopResult.Sar))
|
||
{
|
||
volatilityStopResult.IsStop = true;
|
||
num2 = item4;
|
||
flag = !flag;
|
||
}
|
||
else
|
||
{
|
||
volatilityStopResult.IsStop = false;
|
||
num2 = (flag ? Math.Max(num2, item4) : Math.Min(num2, item4));
|
||
}
|
||
}
|
||
VolatilityStopResult volatilityStopResult2 = (from x in list2
|
||
where x.IsStop == true
|
||
orderby x.Date
|
||
select x).FirstOrDefault();
|
||
if (volatilityStopResult2 != null)
|
||
{
|
||
int num4 = list2.IndexOf(volatilityStopResult2);
|
||
for (int num5 = 0; num5 <= num4; num5++)
|
||
{
|
||
VolatilityStopResult volatilityStopResult3 = list2[num5];
|
||
volatilityStopResult3.Sar = null;
|
||
volatilityStopResult3.UpperBand = null;
|
||
volatilityStopResult3.LowerBand = null;
|
||
volatilityStopResult3.IsStop = null;
|
||
}
|
||
}
|
||
return list2;
|
||
}
|
||
}
|
||
|
||
private static void ValidateVolatilityStop(int lookbackPeriods, double multiplier)
|
||
{
|
||
if (lookbackPeriods <= 1)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 1 for Volatility Stop.");
|
||
}
|
||
if (multiplier <= 0.0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("multiplier", multiplier, "ATR Multiplier must be greater than 0 for Volatility Stop.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<VolatilityStopResult> RemoveWarmupPeriods(this IEnumerable<VolatilityStopResult> results)
|
||
{
|
||
int val = results.ToList().FindIndex((VolatilityStopResult x) => x.Sar.HasValue);
|
||
val = Math.Max(100, val);
|
||
return results.Remove(val);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Vortex Indicator (VI) is a measure of price directional movement.
|
||
/// It includes positive and negative indicators, and is often used to identify trends and reversals.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Vortex/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><returns>Time series of VI+ and VI- vortex movement indicator values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<VortexResult> GetVortex<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcVortex(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<VortexResult> CalcVortex(this List<QuoteD> qdList, int lookbackPeriods)
|
||
{
|
||
ValidateVortex(lookbackPeriods);
|
||
int count = qdList.Count;
|
||
List<VortexResult> list = new List<VortexResult>(count);
|
||
double[] array = new double[count];
|
||
double[] array2 = new double[count];
|
||
double[] array3 = new double[count];
|
||
double num = 0.0;
|
||
double num2 = 0.0;
|
||
double num3 = 0.0;
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
QuoteD quoteD = qdList[i];
|
||
VortexResult vortexResult = new VortexResult(quoteD.Date);
|
||
list.Add(vortexResult);
|
||
if (i == 0)
|
||
{
|
||
num = quoteD.High;
|
||
num2 = quoteD.Low;
|
||
num3 = quoteD.Close;
|
||
continue;
|
||
}
|
||
double val = Math.Abs(quoteD.High - num3);
|
||
double val2 = Math.Abs(quoteD.Low - num3);
|
||
array[i] = Math.Max(quoteD.High - quoteD.Low, Math.Max(val, val2));
|
||
array2[i] = Math.Abs(quoteD.High - num2);
|
||
array3[i] = Math.Abs(quoteD.Low - num);
|
||
num = quoteD.High;
|
||
num2 = quoteD.Low;
|
||
num3 = quoteD.Close;
|
||
if (i + 1 > lookbackPeriods)
|
||
{
|
||
double num4 = 0.0;
|
||
double num5 = 0.0;
|
||
double num6 = 0.0;
|
||
for (int j = i + 1 - lookbackPeriods; j <= i; j++)
|
||
{
|
||
num4 += array[j];
|
||
num5 += array2[j];
|
||
num6 += array3[j];
|
||
}
|
||
if (num4 != 0.0)
|
||
{
|
||
vortexResult.Pvi = num5 / num4;
|
||
vortexResult.Nvi = num6 / num4;
|
||
}
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateVortex(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 1)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 1 for VI.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes non-essential records containing null values with unique consideration for
|
||
/// this indicator. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#condense?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator results to evaluate.</param><returns>Time series of
|
||
/// indicator results, condensed.</returns>
|
||
public static IEnumerable<VortexResult> Condense(this IEnumerable<VortexResult> results)
|
||
{
|
||
List<VortexResult> list = results.ToList();
|
||
list.RemoveAll((VortexResult x) => !x.Pvi.HasValue && !x.Nvi.HasValue);
|
||
return list.ToSortedList();
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<VortexResult> RemoveWarmupPeriods(this IEnumerable<VortexResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((VortexResult x) => x.Pvi.HasValue || x.Nvi.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Volume Weighted Average Price (VWAP) is a Volume weighted average of price, typically used on intraday data.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Vwap/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="startDate">Optional anchor date. If not provided, the first date in quotes is used.</param><returns>Time series of VWAP values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<VwapResult> GetVwap<TQuote>(this IEnumerable<TQuote> quotes, DateTime? startDate = null) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcVwap(startDate);
|
||
}
|
||
|
||
internal static List<VwapResult> CalcVwap(this List<QuoteD> qdList, DateTime? startDate = null)
|
||
{
|
||
ValidateVwap(qdList, startDate);
|
||
int count = qdList.Count;
|
||
List<VwapResult> list = new List<VwapResult>(count);
|
||
if (count == 0)
|
||
{
|
||
return list;
|
||
}
|
||
DateTime valueOrDefault = startDate.GetValueOrDefault();
|
||
if (!startDate.HasValue)
|
||
{
|
||
valueOrDefault = qdList[0].Date;
|
||
startDate = valueOrDefault;
|
||
}
|
||
double? num = 0.0;
|
||
double? num2 = 0.0;
|
||
for (int i = 0; i < count; i = checked(i + 1))
|
||
{
|
||
QuoteD quoteD = qdList[i];
|
||
double? num3 = quoteD.Volume;
|
||
double? num4 = quoteD.High;
|
||
double? num5 = quoteD.Low;
|
||
double? num6 = quoteD.Close;
|
||
VwapResult vwapResult = new VwapResult(quoteD.Date);
|
||
list.Add(vwapResult);
|
||
valueOrDefault = quoteD.Date;
|
||
DateTime? dateTime = startDate;
|
||
if (valueOrDefault >= dateTime)
|
||
{
|
||
num += num3;
|
||
num2 += num3 * (num4 + num5 + num6) / 3.0;
|
||
vwapResult.Vwap = ((num == 0.0) ? ((double?)null) : (num2 / num));
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
|
||
private static void ValidateVwap(List<QuoteD> quotesList, DateTime? startDate)
|
||
{
|
||
if (quotesList.Count == 0 || !(startDate < quotesList[0].Date))
|
||
{
|
||
return;
|
||
}
|
||
throw new ArgumentOutOfRangeException("startDate", startDate, "Start Date must be within the quotes range for VWAP.");
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<VwapResult> RemoveWarmupPeriods(this IEnumerable<VwapResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((VwapResult x) => x.Vwap.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Volume Weighted Moving Average is the volume adjusted average price over a lookback window.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Vwma/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><returns>Time series of Volume Weighted Moving Average values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<VwmaResult> GetVwma<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcVwma(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<VwmaResult> CalcVwma(this List<QuoteD> qdList, int lookbackPeriods)
|
||
{
|
||
ValidateVwma(lookbackPeriods);
|
||
int count = qdList.Count;
|
||
List<VwmaResult> list = new List<VwmaResult>(count);
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
VwmaResult vwmaResult = new VwmaResult(qdList[i].Date);
|
||
list.Add(vwmaResult);
|
||
if (i + 1 >= lookbackPeriods)
|
||
{
|
||
double? num = 0.0;
|
||
double? num2 = 0.0;
|
||
for (int j = i + 1 - lookbackPeriods; j <= i; j++)
|
||
{
|
||
QuoteD quoteD = qdList[j];
|
||
double? num3 = quoteD.Close;
|
||
double? num4 = quoteD.Volume;
|
||
num += num3 * num4;
|
||
num2 += num4;
|
||
}
|
||
vwmaResult.Vwma = ((num2 == 0.0) ? ((double?)null) : (num / num2));
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateVwma(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for Vwma.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<VwmaResult> RemoveWarmupPeriods(this IEnumerable<VwmaResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((VwmaResult x) => x.Vwma.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Williams %R momentum indicator is a stochastic oscillator with scale of -100 to 0. It is exactly the same as the Fast variant of Stochastic Oscillator, but with a different scaling.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/WilliamsR/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><returns>Time series of Williams %R values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<WilliamsResult> GetWilliamsR<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods = 14) where TQuote : IQuote
|
||
{
|
||
return quotes.ToQuoteD().CalcWilliamsR(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<WilliamsResult> CalcWilliamsR(this List<QuoteD> qdList, int lookbackPeriods)
|
||
{
|
||
ValidateWilliam(lookbackPeriods);
|
||
return (from s in qdList.CalcStoch(lookbackPeriods, 1, 1, 3.0, 2.0, MaType.SMA)
|
||
select new WilliamsResult(s.Date)
|
||
{
|
||
WilliamsR = s.Oscillator - 100.0
|
||
}).ToList();
|
||
}
|
||
|
||
private static void ValidateWilliam(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for William %R.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<WilliamsResult> RemoveWarmupPeriods(this IEnumerable<WilliamsResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((WilliamsResult x) => x.WilliamsR.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Weighted Moving Average (WMA) is the linear weighted average of price over N lookback periods. This also called Linear Weighted Moving Average (LWMA).
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Wma/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="lookbackPeriods">Number of periods in the lookback window.</param><returns>Time series of WMA values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<WmaResult> GetWma<TQuote>(this IEnumerable<TQuote> quotes, int lookbackPeriods) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(CandlePart.Close).CalcWma(lookbackPeriods);
|
||
}
|
||
|
||
public static IEnumerable<WmaResult> GetWma(this IEnumerable<IReusableResult> results, int lookbackPeriods)
|
||
{
|
||
return results.ToTuple().CalcWma(lookbackPeriods).SyncIndex(results, SyncType.Prepend);
|
||
}
|
||
|
||
public static IEnumerable<WmaResult> GetWma(this IEnumerable<(DateTime, double)> priceTuples, int lookbackPeriods)
|
||
{
|
||
return priceTuples.ToSortedList().CalcWma(lookbackPeriods);
|
||
}
|
||
|
||
internal static List<WmaResult> CalcWma(this List<(DateTime, double)> tpList, int lookbackPeriods)
|
||
{
|
||
ValidateWma(lookbackPeriods);
|
||
List<WmaResult> list = new List<WmaResult>(tpList.Count);
|
||
checked
|
||
{
|
||
double num = (double)lookbackPeriods * (double)(lookbackPeriods + 1) / 2.0;
|
||
for (int i = 0; i < tpList.Count; i++)
|
||
{
|
||
WmaResult wmaResult = new WmaResult(tpList[i].Item1);
|
||
list.Add(wmaResult);
|
||
if (i + 1 >= lookbackPeriods)
|
||
{
|
||
double num2 = 0.0;
|
||
for (int j = i + 1 - lookbackPeriods; j <= i; j++)
|
||
{
|
||
double item = tpList[j].Item2;
|
||
num2 += item * (double)(lookbackPeriods - (i + 1 - j - 1)) / num;
|
||
}
|
||
wmaResult.Wma = num2.NaN2Null();
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static void ValidateWma(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for WMA.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes the recommended quantity of results from the beginning of the results list
|
||
/// using a reverse-engineering approach. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator
|
||
/// results to evaluate.</param><returns>Time
|
||
/// series of results, pruned.</returns>
|
||
public static IEnumerable<WmaResult> RemoveWarmupPeriods(this IEnumerable<WmaResult> results)
|
||
{
|
||
int removePeriods = results.ToList().FindIndex((WmaResult x) => x.Wma.HasValue);
|
||
return results.Remove(removePeriods);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Zig Zag is a price chart overlay that simplifies the up and down movements and transitions based on a percent change smoothing threshold.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/ZigZag/#content?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="endType">Determines use of Close or High/Low wicks for extreme points.</param><param name="percentChange">Percent price change to set threshold for minimum size movements.</param><returns>Time series of Zig Zag values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<ZigZagResult> GetZigZag<TQuote>(this IEnumerable<TQuote> quotes, EndType endType = EndType.Close, decimal percentChange = 5m) where TQuote : IQuote
|
||
{
|
||
return quotes.ToSortedList().CalcZigZag(endType, percentChange);
|
||
}
|
||
|
||
internal static List<ZigZagResult> CalcZigZag<TQuote>(this List<TQuote> quotesList, EndType endType = EndType.Close, decimal percentChange = 5m) where TQuote : IQuote
|
||
{
|
||
ValidateZigZag(percentChange);
|
||
int count = quotesList.Count;
|
||
List<ZigZagResult> list = new List<ZigZagResult>(count);
|
||
if (count == 0)
|
||
{
|
||
return list;
|
||
}
|
||
TQuote q = quotesList[0];
|
||
ZigZagEval zigZagEval = GetZigZagEval(endType, 1, q);
|
||
decimal num = percentChange / 100m;
|
||
ZigZagPoint zigZagPoint = new ZigZagPoint
|
||
{
|
||
Index = zigZagEval.Index,
|
||
Value = q.Close,
|
||
PointType = "U"
|
||
};
|
||
ZigZagPoint zigZagPoint2 = new ZigZagPoint
|
||
{
|
||
Index = zigZagEval.Index,
|
||
Value = zigZagEval.High,
|
||
PointType = "H"
|
||
};
|
||
ZigZagPoint zigZagPoint3 = new ZigZagPoint
|
||
{
|
||
Index = zigZagEval.Index,
|
||
Value = zigZagEval.Low,
|
||
PointType = "L"
|
||
};
|
||
int num2 = count;
|
||
checked
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
TQuote q2 = quotesList[i];
|
||
int index = i + 1;
|
||
zigZagEval = GetZigZagEval(endType, index, q2);
|
||
decimal? value = zigZagPoint3.Value;
|
||
decimal? num3 = (((value.GetValueOrDefault() == default(decimal)) & value.HasValue) ? ((decimal?)null) : ((zigZagEval.High - zigZagPoint3.Value) / zigZagPoint3.Value));
|
||
decimal? value2 = zigZagPoint2.Value;
|
||
decimal? num4 = (((value2.GetValueOrDefault() == default(decimal)) & value2.HasValue) ? ((decimal?)null) : ((zigZagPoint2.Value - zigZagEval.Low) / zigZagPoint2.Value));
|
||
value = num3;
|
||
decimal num5 = num;
|
||
if (((value.GetValueOrDefault() >= num5) & value.HasValue) && num3 > num4)
|
||
{
|
||
zigZagPoint.Index = zigZagPoint3.Index;
|
||
zigZagPoint.Value = zigZagPoint3.Value;
|
||
zigZagPoint.PointType = zigZagPoint3.PointType;
|
||
break;
|
||
}
|
||
value2 = num4;
|
||
num5 = num;
|
||
if (((value2.GetValueOrDefault() >= num5) & value2.HasValue) && num4 > num3)
|
||
{
|
||
zigZagPoint.Index = zigZagPoint2.Index;
|
||
zigZagPoint.Value = zigZagPoint2.Value;
|
||
zigZagPoint.PointType = zigZagPoint2.PointType;
|
||
break;
|
||
}
|
||
}
|
||
ZigZagResult item = new ZigZagResult(q.Date);
|
||
list.Add(item);
|
||
while (zigZagPoint.Index < num2)
|
||
{
|
||
ZigZagPoint nextPoint = EvaluateNextPoint(quotesList, endType, num, zigZagPoint);
|
||
string pointType = zigZagPoint.PointType;
|
||
DrawZigZagLine(list, quotesList, zigZagPoint, nextPoint);
|
||
DrawRetraceLine(list, pointType, zigZagPoint3, zigZagPoint2, nextPoint);
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
private static ZigZagPoint EvaluateNextPoint<TQuote>(List<TQuote> quotesList, EndType endType, decimal changeThreshold, ZigZagPoint lastPoint) where TQuote : IQuote
|
||
{
|
||
bool flag = lastPoint.PointType == "L";
|
||
ZigZagPoint zigZagPoint = new ZigZagPoint
|
||
{
|
||
Index = lastPoint.Index,
|
||
Value = lastPoint.Value,
|
||
PointType = (flag ? "H" : "L")
|
||
};
|
||
checked
|
||
{
|
||
for (int i = lastPoint.Index; i < quotesList.Count; i++)
|
||
{
|
||
TQuote q = quotesList[i];
|
||
int num = i + 1;
|
||
ZigZagEval zigZagEval = GetZigZagEval(endType, num, q);
|
||
decimal? num2;
|
||
if (flag)
|
||
{
|
||
if (zigZagEval.High >= zigZagPoint.Value)
|
||
{
|
||
zigZagPoint.Index = zigZagEval.Index;
|
||
zigZagPoint.Value = zigZagEval.High;
|
||
num2 = default(decimal);
|
||
}
|
||
else
|
||
{
|
||
decimal? value = zigZagPoint.Value;
|
||
num2 = (((value.GetValueOrDefault() == default(decimal)) & value.HasValue) ? ((decimal?)null) : ((zigZagPoint.Value - zigZagEval.Low) / zigZagPoint.Value));
|
||
}
|
||
}
|
||
else if (zigZagEval.Low <= zigZagPoint.Value)
|
||
{
|
||
zigZagPoint.Index = zigZagEval.Index;
|
||
zigZagPoint.Value = zigZagEval.Low;
|
||
num2 = default(decimal);
|
||
}
|
||
else
|
||
{
|
||
decimal? value = zigZagPoint.Value;
|
||
num2 = (((value.GetValueOrDefault() == default(decimal)) & value.HasValue) ? ((decimal?)null) : ((zigZagEval.High - zigZagPoint.Value) / zigZagPoint.Value));
|
||
}
|
||
decimal? num3 = num2;
|
||
decimal num4 = changeThreshold;
|
||
if ((num3.GetValueOrDefault() >= num4) & num3.HasValue)
|
||
{
|
||
return zigZagPoint;
|
||
}
|
||
if (num == quotesList.Count)
|
||
{
|
||
zigZagPoint.Index = num;
|
||
zigZagPoint.Value = (flag ? zigZagEval.High : zigZagEval.Low);
|
||
zigZagPoint.PointType = null;
|
||
}
|
||
}
|
||
return zigZagPoint;
|
||
}
|
||
}
|
||
|
||
private static void DrawZigZagLine<TQuote>(List<ZigZagResult> results, List<TQuote> quotesList, ZigZagPoint lastPoint, ZigZagPoint nextPoint) where TQuote : IQuote
|
||
{
|
||
checked
|
||
{
|
||
if (nextPoint.Index != lastPoint.Index)
|
||
{
|
||
decimal? num = (nextPoint.Value - lastPoint.Value) / (decimal?)(nextPoint.Index - lastPoint.Index);
|
||
for (int i = lastPoint.Index; i < nextPoint.Index; i++)
|
||
{
|
||
TQuote val = quotesList[i];
|
||
int num2 = i + 1;
|
||
ZigZagResult item = new ZigZagResult(val.Date)
|
||
{
|
||
ZigZag = ((lastPoint.Index == 1 && num2 != nextPoint.Index) ? ((decimal?)null) : (lastPoint.Value + num * (decimal?)(num2 - lastPoint.Index))),
|
||
PointType = ((num2 == nextPoint.Index) ? nextPoint.PointType : null)
|
||
};
|
||
results.Add(item);
|
||
}
|
||
}
|
||
lastPoint.Index = nextPoint.Index;
|
||
lastPoint.Value = nextPoint.Value;
|
||
lastPoint.PointType = nextPoint.PointType;
|
||
}
|
||
}
|
||
|
||
private static void DrawRetraceLine(List<ZigZagResult> results, string lastDirection, ZigZagPoint lastLowPoint, ZigZagPoint lastHighPoint, ZigZagPoint nextPoint)
|
||
{
|
||
ZigZagPoint zigZagPoint = new ZigZagPoint();
|
||
if (lastDirection == "L")
|
||
{
|
||
zigZagPoint.Index = lastHighPoint.Index;
|
||
zigZagPoint.Value = lastHighPoint.Value;
|
||
lastHighPoint.Index = nextPoint.Index;
|
||
lastHighPoint.Value = nextPoint.Value;
|
||
}
|
||
else if (lastDirection == "H")
|
||
{
|
||
zigZagPoint.Index = lastLowPoint.Index;
|
||
zigZagPoint.Value = lastLowPoint.Value;
|
||
lastLowPoint.Index = nextPoint.Index;
|
||
lastLowPoint.Value = nextPoint.Value;
|
||
}
|
||
if (lastDirection == "U" || zigZagPoint.Index == 1 || nextPoint.Index == zigZagPoint.Index)
|
||
{
|
||
return;
|
||
}
|
||
checked
|
||
{
|
||
decimal? num = (nextPoint.Value - zigZagPoint.Value) / (decimal?)(nextPoint.Index - zigZagPoint.Index);
|
||
for (int i = zigZagPoint.Index - 1; i < nextPoint.Index; i++)
|
||
{
|
||
ZigZagResult zigZagResult = results[i];
|
||
int num2 = i + 1;
|
||
if (lastDirection == "L")
|
||
{
|
||
zigZagResult.RetraceHigh = zigZagPoint.Value + num * (decimal?)(num2 - zigZagPoint.Index);
|
||
}
|
||
else if (lastDirection == "H")
|
||
{
|
||
zigZagResult.RetraceLow = zigZagPoint.Value + num * (decimal?)(num2 - zigZagPoint.Index);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private static ZigZagEval GetZigZagEval<TQuote>(EndType endType, int index, TQuote q) where TQuote : IQuote
|
||
{
|
||
ZigZagEval zigZagEval = new ZigZagEval
|
||
{
|
||
Index = index
|
||
};
|
||
switch (endType)
|
||
{
|
||
case EndType.Close:
|
||
zigZagEval.Low = q.Close;
|
||
zigZagEval.High = q.Close;
|
||
break;
|
||
case EndType.HighLow:
|
||
zigZagEval.Low = q.Low;
|
||
zigZagEval.High = q.High;
|
||
break;
|
||
default:
|
||
throw new ArgumentOutOfRangeException("endType");
|
||
}
|
||
return zigZagEval;
|
||
}
|
||
|
||
private static void ValidateZigZag(decimal percentChange)
|
||
{
|
||
if (percentChange <= 0m)
|
||
{
|
||
throw new ArgumentOutOfRangeException("percentChange", percentChange, "Percent change must be greater than 0 for ZIGZAG.");
|
||
}
|
||
}
|
||
|
||
/// <summary> Removes non-essential records containing null values with unique consideration for
|
||
/// this indicator. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#condense?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="results">Indicator results to evaluate.</param><returns>Time series of
|
||
/// indicator results, condensed.</returns>
|
||
public static IEnumerable<ZigZagResult> Condense(this IEnumerable<ZigZagResult> results)
|
||
{
|
||
List<ZigZagResult> list = results.ToList();
|
||
list.RemoveAll((ZigZagResult x) => x.PointType == null);
|
||
return list.ToSortedList();
|
||
}
|
||
|
||
[ExcludeFromCodeCoverage]
|
||
[Obsolete("'ToBasicTuple(..)' was deprecated.", false)]
|
||
public static List<(DateTime, double)> ToBasicTuple<TQuote>(this IEnumerable<TQuote> quotes, CandlePart candlePart) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(candlePart);
|
||
}
|
||
|
||
[ExcludeFromCodeCoverage]
|
||
[Obsolete("Rename 'ToResultTuple(..)' to 'ToTuple(..)' to fix.", false)]
|
||
public static List<(DateTime Date, double Value)> ToResultTuple(this IEnumerable<IReusableResult> basicData)
|
||
{
|
||
return basicData.ToTuple();
|
||
}
|
||
|
||
[ExcludeFromCodeCoverage]
|
||
[Obsolete("Rename 'ToTupleCollection(..)' to 'ToTupleChainable(..)' to fix.", false)]
|
||
public static Collection<(DateTime Date, double Value)> ToTupleCollection(this IEnumerable<IReusableResult> reusable)
|
||
{
|
||
return reusable.ToTupleChainable();
|
||
}
|
||
|
||
[ExcludeFromCodeCoverage]
|
||
[Obsolete("Rename 'ToTupleCollection(NullTo..)' to either 'ToTupleNaN(..)' or 'ToTupleNull(..)' to fix.", false)]
|
||
public static Collection<(DateTime Date, double? Value)> ToTupleCollection(this IEnumerable<IReusableResult> reusable, NullTo nullTo)
|
||
{
|
||
List<IReusableResult> list = reusable.ToSortedList();
|
||
int count = list.Count;
|
||
Collection<(DateTime, double?)> collection = new Collection<(DateTime, double?)>();
|
||
for (int i = 0; i < count; i = checked(i + 1))
|
||
{
|
||
IReusableResult reusableResult = list[i];
|
||
collection.Add((reusableResult.Date, reusableResult.Value.Null2NaN()));
|
||
}
|
||
return collection;
|
||
}
|
||
|
||
[ExcludeFromCodeCoverage]
|
||
[Obsolete("Change 'GetStarcBands()' to 'GetStarcBands(20)' to fix.", false)]
|
||
public static IEnumerable<StarcBandsResult> GetStarcBands<TQuote>(this IEnumerable<TQuote> quotes) where TQuote : IQuote
|
||
{
|
||
return quotes.GetStarcBands(20);
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class AdlResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? MoneyFlowMultiplier { get; set; }
|
||
|
||
public double? MoneyFlowVolume { get; set; }
|
||
|
||
public double Adl { get; set; }
|
||
|
||
public double? AdlSma { get; set; }
|
||
|
||
double? IReusableResult.Value => Adl;
|
||
|
||
public AdlResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class AdxResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Pdi { get; set; }
|
||
|
||
public double? Mdi { get; set; }
|
||
|
||
public double? Adx { get; set; }
|
||
|
||
public double? Adxr { get; set; }
|
||
|
||
double? IReusableResult.Value => Adx;
|
||
|
||
public AdxResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class AlligatorResult : ResultBase
|
||
{
|
||
public double? Jaw { get; set; }
|
||
|
||
public double? Teeth { get; set; }
|
||
|
||
public double? Lips { get; set; }
|
||
|
||
public AlligatorResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class AlmaResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Alma { get; set; }
|
||
|
||
double? IReusableResult.Value => Alma;
|
||
|
||
public AlmaResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class AroonResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? AroonUp { get; set; }
|
||
|
||
public double? AroonDown { get; set; }
|
||
|
||
public double? Oscillator { get; set; }
|
||
|
||
double? IReusableResult.Value => Oscillator;
|
||
|
||
public AroonResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class AtrResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Tr { get; set; }
|
||
|
||
public double? Atr { get; set; }
|
||
|
||
public double? Atrp { get; set; }
|
||
|
||
double? IReusableResult.Value => Atrp;
|
||
|
||
public AtrResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class AtrStopResult : ResultBase
|
||
{
|
||
public decimal? AtrStop { get; set; }
|
||
|
||
public decimal? BuyStop { get; set; }
|
||
|
||
public decimal? SellStop { get; set; }
|
||
|
||
public AtrStopResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class AwesomeResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Oscillator { get; set; }
|
||
|
||
public double? Normalized { get; set; }
|
||
|
||
double? IReusableResult.Value => Oscillator;
|
||
|
||
public AwesomeResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
public interface IBasicData
|
||
{
|
||
DateTime Date { get; }
|
||
|
||
double Value { get; }
|
||
}
|
||
public class BasicData : ISeries, IBasicData, IReusableResult
|
||
{
|
||
public DateTime Date { get; set; }
|
||
|
||
public double Value { get; set; }
|
||
|
||
double? IReusableResult.Value => Value;
|
||
}
|
||
[Serializable]
|
||
public sealed class BetaResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Beta { get; set; }
|
||
|
||
public double? BetaUp { get; set; }
|
||
|
||
public double? BetaDown { get; set; }
|
||
|
||
public double? Ratio { get; set; }
|
||
|
||
public double? Convexity { get; set; }
|
||
|
||
public double? ReturnsEval { get; set; }
|
||
|
||
public double? ReturnsMrkt { get; set; }
|
||
|
||
double? IReusableResult.Value => Beta;
|
||
|
||
public BetaResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
public enum BetaType
|
||
{
|
||
Standard,
|
||
Up,
|
||
Down,
|
||
All
|
||
}
|
||
[Serializable]
|
||
public sealed class BollingerBandsResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Sma { get; set; }
|
||
|
||
public double? UpperBand { get; set; }
|
||
|
||
public double? LowerBand { get; set; }
|
||
|
||
public double? PercentB { get; set; }
|
||
|
||
public double? ZScore { get; set; }
|
||
|
||
public double? Width { get; set; }
|
||
|
||
double? IReusableResult.Value => PercentB;
|
||
|
||
public BollingerBandsResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class BopResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Bop { get; set; }
|
||
|
||
double? IReusableResult.Value => Bop;
|
||
|
||
public BopResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class CciResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Cci { get; set; }
|
||
|
||
double? IReusableResult.Value => Cci;
|
||
|
||
public CciResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class ChaikinOscResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? MoneyFlowMultiplier { get; set; }
|
||
|
||
public double? MoneyFlowVolume { get; set; }
|
||
|
||
public double? Adl { get; set; }
|
||
|
||
public double? Oscillator { get; set; }
|
||
|
||
double? IReusableResult.Value => Oscillator;
|
||
|
||
public ChaikinOscResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class ChandelierResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? ChandelierExit { get; set; }
|
||
|
||
double? IReusableResult.Value => ChandelierExit;
|
||
|
||
public ChandelierResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
public enum ChandelierType
|
||
{
|
||
Long,
|
||
Short
|
||
}
|
||
[Serializable]
|
||
public sealed class ChopResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Chop { get; set; }
|
||
|
||
double? IReusableResult.Value => Chop;
|
||
|
||
public ChopResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class CmfResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? MoneyFlowMultiplier { get; set; }
|
||
|
||
public double? MoneyFlowVolume { get; set; }
|
||
|
||
public double? Cmf { get; set; }
|
||
|
||
double? IReusableResult.Value => Cmf;
|
||
|
||
public CmfResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class CmoResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Cmo { get; set; }
|
||
|
||
double? IReusableResult.Value => Cmo;
|
||
|
||
public CmoResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class ConnorsRsiResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Rsi { get; set; }
|
||
|
||
public double? RsiStreak { get; set; }
|
||
|
||
public double? PercentRank { get; set; }
|
||
|
||
public double? ConnorsRsi { get; set; }
|
||
|
||
internal int Streak { get; set; }
|
||
|
||
double? IReusableResult.Value => ConnorsRsi;
|
||
|
||
public ConnorsRsiResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class CorrResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? VarianceA { get; set; }
|
||
|
||
public double? VarianceB { get; set; }
|
||
|
||
public double? Covariance { get; set; }
|
||
|
||
public double? Correlation { get; set; }
|
||
|
||
public double? RSquared { get; set; }
|
||
|
||
double? IReusableResult.Value => Correlation;
|
||
|
||
public CorrResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class DemaResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Dema { get; set; }
|
||
|
||
double? IReusableResult.Value => Dema;
|
||
|
||
public DemaResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class DonchianResult : ResultBase
|
||
{
|
||
public decimal? UpperBand { get; set; }
|
||
|
||
public decimal? Centerline { get; set; }
|
||
|
||
public decimal? LowerBand { get; set; }
|
||
|
||
public decimal? Width { get; set; }
|
||
|
||
public DonchianResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class DpoResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Sma { get; set; }
|
||
|
||
public double? Dpo { get; set; }
|
||
|
||
double? IReusableResult.Value => Dpo;
|
||
|
||
public DpoResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class DynamicResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Dynamic { get; set; }
|
||
|
||
double? IReusableResult.Value => Dynamic;
|
||
|
||
public DynamicResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class ElderRayResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Ema { get; set; }
|
||
|
||
public double? BullPower { get; set; }
|
||
|
||
public double? BearPower { get; set; }
|
||
|
||
double? IReusableResult.Value => BullPower + BearPower;
|
||
|
||
public ElderRayResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class EmaResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Ema { get; set; }
|
||
|
||
double? IReusableResult.Value => Ema;
|
||
|
||
public EmaResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
public class EmaBase
|
||
{
|
||
internal double K { get; set; }
|
||
|
||
internal List<EmaResult> ProtectedResults { get; set; }
|
||
|
||
public IEnumerable<EmaResult> Results => ProtectedResults;
|
||
|
||
internal EmaBase(IEnumerable<(DateTime, double)> tpQuotes, int lookbackPeriods)
|
||
{
|
||
K = 2.0 / (double)checked(lookbackPeriods + 1);
|
||
ProtectedResults = tpQuotes.ToSortedList().CalcEma(lookbackPeriods);
|
||
}
|
||
|
||
public IEnumerable<EmaResult> Add(Quote quote, CandlePart candlePart = CandlePart.Close)
|
||
{
|
||
if (quote == null)
|
||
{
|
||
throw new InvalidQuotesException("quote", quote, "No quote provided.");
|
||
}
|
||
(DateTime, double) tuple = quote.ToTuple(candlePart);
|
||
return Add(tuple);
|
||
}
|
||
|
||
public IEnumerable<EmaResult> Add((DateTime Date, double Value) tuple)
|
||
{
|
||
checked
|
||
{
|
||
int num = ProtectedResults.Count - 1;
|
||
EmaResult emaResult = ProtectedResults[num];
|
||
if (tuple.Date == emaResult.Date)
|
||
{
|
||
EmaResult emaResult2 = ProtectedResults[num - 1];
|
||
double lastEma = ((!emaResult2.Ema.HasValue) ? double.NaN : emaResult2.Ema.Value);
|
||
emaResult.Ema = Increment(tuple.Value, lastEma, K);
|
||
}
|
||
else if (tuple.Date > emaResult.Date)
|
||
{
|
||
double lastEma2 = ((!emaResult.Ema.HasValue) ? double.NaN : emaResult.Ema.Value);
|
||
double value = Increment(tuple.Value, lastEma2, K);
|
||
EmaResult item = new EmaResult(tuple.Date)
|
||
{
|
||
Ema = value
|
||
};
|
||
ProtectedResults.Add(item);
|
||
}
|
||
return Results;
|
||
}
|
||
}
|
||
|
||
internal static double Increment(double newValue, double lastEma, double k)
|
||
{
|
||
return lastEma + k * (newValue - lastEma);
|
||
}
|
||
|
||
internal static void Validate(int lookbackPeriods)
|
||
{
|
||
if (lookbackPeriods <= 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException("lookbackPeriods", lookbackPeriods, "Lookback periods must be greater than 0 for EMA.");
|
||
}
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class EpmaResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Epma { get; set; }
|
||
|
||
double? IReusableResult.Value => Epma;
|
||
|
||
public EpmaResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class FcbResult : ResultBase
|
||
{
|
||
public decimal? UpperBand { get; set; }
|
||
|
||
public decimal? LowerBand { get; set; }
|
||
|
||
public FcbResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class FisherTransformResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Fisher { get; set; }
|
||
|
||
public double? Trigger { get; set; }
|
||
|
||
double? IReusableResult.Value => Fisher;
|
||
|
||
public FisherTransformResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class ForceIndexResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? ForceIndex { get; set; }
|
||
|
||
double? IReusableResult.Value => ForceIndex;
|
||
|
||
public ForceIndexResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class FractalResult : ResultBase
|
||
{
|
||
public decimal? FractalBear { get; set; }
|
||
|
||
public decimal? FractalBull { get; set; }
|
||
|
||
public FractalResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public class GatorResult : ResultBase
|
||
{
|
||
public double? Upper { get; set; }
|
||
|
||
public double? Lower { get; set; }
|
||
|
||
public bool? UpperIsExpanding { get; set; }
|
||
|
||
public bool? LowerIsExpanding { get; set; }
|
||
|
||
public GatorResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class HeikinAshiResult : ResultBase, IQuote, ISeries
|
||
{
|
||
public decimal Open { get; set; }
|
||
|
||
public decimal High { get; set; }
|
||
|
||
public decimal Low { get; set; }
|
||
|
||
public decimal Close { get; set; }
|
||
|
||
public decimal Volume { get; set; }
|
||
|
||
public HeikinAshiResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class HmaResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Hma { get; set; }
|
||
|
||
double? IReusableResult.Value => Hma;
|
||
|
||
public HmaResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class HtlResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public int? DcPeriods { get; set; }
|
||
|
||
public double? Trendline { get; set; }
|
||
|
||
public double? SmoothPrice { get; set; }
|
||
|
||
double? IReusableResult.Value => Trendline;
|
||
|
||
public HtlResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class HurstResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? HurstExponent { get; set; }
|
||
|
||
double? IReusableResult.Value => HurstExponent;
|
||
|
||
public HurstResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class IchimokuResult : ResultBase
|
||
{
|
||
public decimal? TenkanSen { get; set; }
|
||
|
||
public decimal? KijunSen { get; set; }
|
||
|
||
public decimal? SenkouSpanA { get; set; }
|
||
|
||
public decimal? SenkouSpanB { get; set; }
|
||
|
||
public decimal? ChikouSpan { get; set; }
|
||
|
||
public IchimokuResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class KamaResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? ER { get; set; }
|
||
|
||
public double? Kama { get; set; }
|
||
|
||
double? IReusableResult.Value => Kama;
|
||
|
||
public KamaResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class KeltnerResult : ResultBase
|
||
{
|
||
public double? UpperBand { get; set; }
|
||
|
||
public double? Centerline { get; set; }
|
||
|
||
public double? LowerBand { get; set; }
|
||
|
||
public double? Width { get; set; }
|
||
|
||
public KeltnerResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class KvoResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Oscillator { get; set; }
|
||
|
||
public double? Signal { get; set; }
|
||
|
||
double? IReusableResult.Value => Oscillator;
|
||
|
||
internal KvoResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class MacdResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Macd { get; set; }
|
||
|
||
public double? Signal { get; set; }
|
||
|
||
public double? Histogram { get; set; }
|
||
|
||
public double? FastEma { get; set; }
|
||
|
||
public double? SlowEma { get; set; }
|
||
|
||
double? IReusableResult.Value => Macd;
|
||
|
||
public MacdResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class MaEnvelopeResult : ResultBase
|
||
{
|
||
public double? Centerline { get; set; }
|
||
|
||
public double? UpperEnvelope { get; set; }
|
||
|
||
public double? LowerEnvelope { get; set; }
|
||
|
||
public MaEnvelopeResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class MamaResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Mama { get; set; }
|
||
|
||
public double? Fama { get; set; }
|
||
|
||
double? IReusableResult.Value => Mama;
|
||
|
||
public MamaResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class MfiResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Mfi { get; set; }
|
||
|
||
double? IReusableResult.Value => Mfi;
|
||
|
||
public MfiResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class ObvResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double Obv { get; set; }
|
||
|
||
public double? ObvSma { get; set; }
|
||
|
||
double? IReusableResult.Value => Obv;
|
||
|
||
public ObvResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class ParabolicSarResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Sar { get; set; }
|
||
|
||
public bool? IsReversal { get; set; }
|
||
|
||
double? IReusableResult.Value => Sar;
|
||
|
||
public ParabolicSarResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
internal interface IPivotPoint
|
||
{
|
||
decimal? R4 { get; set; }
|
||
|
||
decimal? R3 { get; set; }
|
||
|
||
decimal? R2 { get; set; }
|
||
|
||
decimal? R1 { get; set; }
|
||
|
||
decimal? PP { get; set; }
|
||
|
||
decimal? S1 { get; set; }
|
||
|
||
decimal? S2 { get; set; }
|
||
|
||
decimal? S3 { get; set; }
|
||
|
||
decimal? S4 { get; set; }
|
||
}
|
||
[Serializable]
|
||
public sealed class PivotPointsResult : ResultBase, IPivotPoint
|
||
{
|
||
public decimal? R4 { get; set; }
|
||
|
||
public decimal? R3 { get; set; }
|
||
|
||
public decimal? R2 { get; set; }
|
||
|
||
public decimal? R1 { get; set; }
|
||
|
||
public decimal? PP { get; set; }
|
||
|
||
public decimal? S1 { get; set; }
|
||
|
||
public decimal? S2 { get; set; }
|
||
|
||
public decimal? S3 { get; set; }
|
||
|
||
public decimal? S4 { get; set; }
|
||
}
|
||
public enum PivotPointType
|
||
{
|
||
Standard,
|
||
Camarilla,
|
||
Demark,
|
||
Fibonacci,
|
||
Woodie
|
||
}
|
||
[Serializable]
|
||
public class PivotsResult : ResultBase
|
||
{
|
||
public decimal? HighPoint { get; set; }
|
||
|
||
public decimal? LowPoint { get; set; }
|
||
|
||
public decimal? HighLine { get; set; }
|
||
|
||
public decimal? LowLine { get; set; }
|
||
|
||
public PivotTrend? HighTrend { get; set; }
|
||
|
||
public PivotTrend? LowTrend { get; set; }
|
||
|
||
public PivotsResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
public enum PivotTrend
|
||
{
|
||
HH,
|
||
LH,
|
||
HL,
|
||
LL
|
||
}
|
||
[Serializable]
|
||
public sealed class PmoResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Pmo { get; set; }
|
||
|
||
public double? Signal { get; set; }
|
||
|
||
internal double? RocEma { get; set; }
|
||
|
||
double? IReusableResult.Value => Pmo;
|
||
|
||
public PmoResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class PrsResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Prs { get; set; }
|
||
|
||
public double? PrsSma { get; set; }
|
||
|
||
public double? PrsPercent { get; set; }
|
||
|
||
double? IReusableResult.Value => Prs;
|
||
|
||
public PrsResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class PvoResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Pvo { get; set; }
|
||
|
||
public double? Signal { get; set; }
|
||
|
||
public double? Histogram { get; set; }
|
||
|
||
double? IReusableResult.Value => Pvo;
|
||
|
||
public PvoResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class RenkoResult : ResultBase, IQuote, ISeries
|
||
{
|
||
public decimal Open { get; set; }
|
||
|
||
public decimal High { get; set; }
|
||
|
||
public decimal Low { get; set; }
|
||
|
||
public decimal Close { get; set; }
|
||
|
||
public decimal Volume { get; set; }
|
||
|
||
public bool IsUp { get; set; }
|
||
|
||
public RenkoResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class RocResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Momentum { get; set; }
|
||
|
||
public double? Roc { get; set; }
|
||
|
||
public double? RocSma { get; set; }
|
||
|
||
double? IReusableResult.Value => Roc;
|
||
|
||
public RocResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class RocWbResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Roc { get; set; }
|
||
|
||
public double? RocEma { get; set; }
|
||
|
||
public double? UpperBand { get; set; }
|
||
|
||
public double? LowerBand { get; set; }
|
||
|
||
double? IReusableResult.Value => Roc;
|
||
|
||
public RocWbResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class RollingPivotsResult : ResultBase, IPivotPoint
|
||
{
|
||
public decimal? R4 { get; set; }
|
||
|
||
public decimal? R3 { get; set; }
|
||
|
||
public decimal? R2 { get; set; }
|
||
|
||
public decimal? R1 { get; set; }
|
||
|
||
public decimal? PP { get; set; }
|
||
|
||
public decimal? S1 { get; set; }
|
||
|
||
public decimal? S2 { get; set; }
|
||
|
||
public decimal? S3 { get; set; }
|
||
|
||
public decimal? S4 { get; set; }
|
||
}
|
||
[Serializable]
|
||
public sealed class RsiResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Rsi { get; set; }
|
||
|
||
double? IReusableResult.Value => Rsi;
|
||
|
||
public RsiResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class SlopeResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Slope { get; set; }
|
||
|
||
public double? Intercept { get; set; }
|
||
|
||
public double? StdDev { get; set; }
|
||
|
||
public double? RSquared { get; set; }
|
||
|
||
public decimal? Line { get; set; }
|
||
|
||
double? IReusableResult.Value => Slope;
|
||
|
||
public SlopeResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class SmaResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Sma { get; set; }
|
||
|
||
double? IReusableResult.Value => Sma;
|
||
|
||
public SmaResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class SmaAnalysis : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Sma { get; set; }
|
||
|
||
public double? Mad { get; set; }
|
||
|
||
public double? Mse { get; set; }
|
||
|
||
public double? Mape { get; set; }
|
||
|
||
double? IReusableResult.Value => Sma;
|
||
|
||
public SmaAnalysis(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class SmiResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Smi { get; set; }
|
||
|
||
public double? Signal { get; set; }
|
||
|
||
double? IReusableResult.Value => Smi;
|
||
|
||
public SmiResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class SmmaResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Smma { get; set; }
|
||
|
||
double? IReusableResult.Value => Smma;
|
||
|
||
public SmmaResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class StarcBandsResult : ResultBase
|
||
{
|
||
public double? UpperBand { get; set; }
|
||
|
||
public double? Centerline { get; set; }
|
||
|
||
public double? LowerBand { get; set; }
|
||
|
||
public StarcBandsResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class StcResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Stc { get; set; }
|
||
|
||
double? IReusableResult.Value => Stc;
|
||
|
||
public StcResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class StdDevResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? StdDev { get; set; }
|
||
|
||
public double? Mean { get; set; }
|
||
|
||
public double? ZScore { get; set; }
|
||
|
||
public double? StdDevSma { get; set; }
|
||
|
||
double? IReusableResult.Value => StdDev;
|
||
|
||
public StdDevResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class StdDevChannelsResult : ResultBase
|
||
{
|
||
public double? Centerline { get; set; }
|
||
|
||
public double? UpperChannel { get; set; }
|
||
|
||
public double? LowerChannel { get; set; }
|
||
|
||
public bool BreakPoint { get; set; }
|
||
|
||
public StdDevChannelsResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// Stochastic indicator results includes aliases for those who prefer the simpler K,D,J outputs.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/Stoch/#response?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">documentation</see>
|
||
/// for more information.
|
||
/// </para>
|
||
/// </summary><remarks>
|
||
/// Standard output properties:
|
||
/// <list type="table">
|
||
/// <item>
|
||
/// <term>Oscillator</term>
|
||
/// <description>%K Oscillator over prior lookback periods.</description>
|
||
/// </item>
|
||
/// <item>
|
||
/// <term>Signal</term>
|
||
/// <description>%D Simple moving average of %K Oscillator.</description>
|
||
/// </item>
|
||
/// <item>
|
||
/// <term>PercentJ</term>
|
||
/// <description>
|
||
/// %J is the weighted divergence of %K and %D: %J=3×%K-2×%D
|
||
/// </description>
|
||
/// </item>
|
||
/// </list>
|
||
/// These are the aliases of the above properties:
|
||
/// <list type="table">
|
||
/// <item>
|
||
/// <term>K</term>
|
||
/// <description>Same as Oscillator.</description>
|
||
/// </item>
|
||
/// <item>
|
||
/// <term>D</term>
|
||
/// <description>Same as Signal.</description>
|
||
/// </item>
|
||
/// <item>
|
||
/// <term>J</term>
|
||
/// <description>Same as PercentJ.</description>
|
||
/// </item>
|
||
/// </list>
|
||
/// </remarks>
|
||
[Serializable]
|
||
public sealed class StochResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Oscillator { get; set; }
|
||
|
||
public double? Signal { get; set; }
|
||
|
||
public double? PercentJ { get; set; }
|
||
|
||
public double? K => Oscillator;
|
||
|
||
public double? D => Signal;
|
||
|
||
public double? J => PercentJ;
|
||
|
||
double? IReusableResult.Value => Oscillator;
|
||
|
||
public StochResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class StochRsiResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? StochRsi { get; set; }
|
||
|
||
public double? Signal { get; set; }
|
||
|
||
double? IReusableResult.Value => StochRsi;
|
||
|
||
public StochRsiResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class SuperTrendResult : ResultBase
|
||
{
|
||
public decimal? SuperTrend { get; set; }
|
||
|
||
public decimal? UpperBand { get; set; }
|
||
|
||
public decimal? LowerBand { get; set; }
|
||
|
||
public SuperTrendResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class T3Result : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? T3 { get; set; }
|
||
|
||
double? IReusableResult.Value => T3;
|
||
|
||
public T3Result(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class TemaResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Tema { get; set; }
|
||
|
||
double? IReusableResult.Value => Tema;
|
||
|
||
public TemaResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class TrResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Tr { get; set; }
|
||
|
||
double? IReusableResult.Value => Tr;
|
||
|
||
public TrResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class TrixResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Ema3 { get; set; }
|
||
|
||
public double? Trix { get; set; }
|
||
|
||
public double? Signal { get; set; }
|
||
|
||
double? IReusableResult.Value => Trix;
|
||
|
||
public TrixResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class TsiResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Tsi { get; set; }
|
||
|
||
public double? Signal { get; set; }
|
||
|
||
double? IReusableResult.Value => Tsi;
|
||
|
||
public TsiResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class UlcerIndexResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? UI { get; set; }
|
||
|
||
double? IReusableResult.Value => UI;
|
||
|
||
public UlcerIndexResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class UltimateResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Ultimate { get; set; }
|
||
|
||
double? IReusableResult.Value => Ultimate;
|
||
|
||
public UltimateResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class VolatilityStopResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Sar { get; set; }
|
||
|
||
public bool? IsStop { get; set; }
|
||
|
||
public double? UpperBand { get; set; }
|
||
|
||
public double? LowerBand { get; set; }
|
||
|
||
double? IReusableResult.Value => Sar;
|
||
|
||
public VolatilityStopResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class VortexResult : ResultBase
|
||
{
|
||
public double? Pvi { get; set; }
|
||
|
||
public double? Nvi { get; set; }
|
||
|
||
public VortexResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class VwapResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Vwap { get; set; }
|
||
|
||
double? IReusableResult.Value => Vwap;
|
||
|
||
public VwapResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class VwmaResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Vwma { get; set; }
|
||
|
||
double? IReusableResult.Value => Vwma;
|
||
|
||
public VwmaResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class WilliamsResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? WilliamsR { get; set; }
|
||
|
||
double? IReusableResult.Value => WilliamsR;
|
||
|
||
public WilliamsResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class WmaResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public double? Wma { get; set; }
|
||
|
||
double? IReusableResult.Value => Wma;
|
||
|
||
public WmaResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class ZigZagResult : ResultBase, IReusableResult, ISeries
|
||
{
|
||
public decimal? ZigZag { get; set; }
|
||
|
||
public string? PointType { get; set; }
|
||
|
||
public decimal? RetraceHigh { get; set; }
|
||
|
||
public decimal? RetraceLow { get; set; }
|
||
|
||
double? IReusableResult.Value => (double?)ZigZag;
|
||
|
||
public ZigZagResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
}
|
||
}
|
||
internal class ZigZagEval
|
||
{
|
||
internal int Index { get; set; }
|
||
|
||
internal decimal? High { get; set; }
|
||
|
||
internal decimal? Low { get; set; }
|
||
}
|
||
internal class ZigZagPoint
|
||
{
|
||
internal int Index { get; set; }
|
||
|
||
internal decimal? Value { get; set; }
|
||
|
||
internal string? PointType { get; set; }
|
||
}
|
||
public static class Candlesticks
|
||
{
|
||
public static IEnumerable<CandleResult> Condense(this IEnumerable<CandleResult> candleResults)
|
||
{
|
||
return candleResults.Where((CandleResult candle) => candle.Match != Match.None).ToList();
|
||
}
|
||
|
||
public static CandleProperties ToCandle<TQuote>(this TQuote quote) where TQuote : IQuote
|
||
{
|
||
return new CandleProperties
|
||
{
|
||
Date = quote.Date,
|
||
Open = quote.Open,
|
||
High = quote.High,
|
||
Low = quote.Low,
|
||
Close = quote.Close,
|
||
Volume = quote.Volume
|
||
};
|
||
}
|
||
|
||
public static IEnumerable<CandleProperties> ToCandles<TQuote>(this IEnumerable<TQuote> quotes) where TQuote : IQuote
|
||
{
|
||
return (from x in quotes
|
||
select x.ToCandle() into x
|
||
orderby x.Date
|
||
select x).ToList();
|
||
}
|
||
|
||
internal static List<CandleResult> ToCandleResults<TQuote>(this IEnumerable<TQuote> quotes) where TQuote : IQuote
|
||
{
|
||
return (from x in quotes
|
||
select new CandleResult(x.Date)
|
||
{
|
||
Match = Match.None,
|
||
Candle = x.ToCandle()
|
||
} into x
|
||
orderby x.Date
|
||
select x).ToList();
|
||
}
|
||
}
|
||
[Serializable]
|
||
public class CandleProperties : Quote
|
||
{
|
||
public decimal? Size => base.High - base.Low;
|
||
|
||
public decimal? Body => (base.Open > base.Close) ? (base.Open - base.Close) : (base.Close - base.Open);
|
||
|
||
public decimal? UpperWick => base.High - ((base.Open > base.Close) ? base.Open : base.Close);
|
||
|
||
public decimal? LowerWick => ((base.Open > base.Close) ? base.Close : base.Open) - base.Low;
|
||
|
||
public double? BodyPct
|
||
{
|
||
get
|
||
{
|
||
decimal? size = Size;
|
||
if ((size.GetValueOrDefault() == default(decimal)) & size.HasValue)
|
||
{
|
||
return 1.0;
|
||
}
|
||
return (double?)(Body / Size);
|
||
}
|
||
}
|
||
|
||
public double? UpperWickPct
|
||
{
|
||
get
|
||
{
|
||
decimal? size = Size;
|
||
if ((size.GetValueOrDefault() == default(decimal)) & size.HasValue)
|
||
{
|
||
return 1.0;
|
||
}
|
||
return (double?)(UpperWick / Size);
|
||
}
|
||
}
|
||
|
||
public double? LowerWickPct
|
||
{
|
||
get
|
||
{
|
||
decimal? size = Size;
|
||
if ((size.GetValueOrDefault() == default(decimal)) & size.HasValue)
|
||
{
|
||
return 1.0;
|
||
}
|
||
return (double?)(LowerWick / Size);
|
||
}
|
||
}
|
||
|
||
public bool IsBullish => base.Close > base.Open;
|
||
|
||
public bool IsBearish => base.Close < base.Open;
|
||
}
|
||
[Serializable]
|
||
public class CandleResult : ResultBase
|
||
{
|
||
public decimal? Price { get; set; }
|
||
|
||
public Match Match { get; set; }
|
||
|
||
public CandleProperties Candle { get; set; }
|
||
|
||
public CandleResult(DateTime date)
|
||
{
|
||
base.Date = date;
|
||
Candle = new CandleProperties();
|
||
}
|
||
}
|
||
public enum CandlePart
|
||
{
|
||
Open,
|
||
High,
|
||
Low,
|
||
Close,
|
||
Volume,
|
||
HL2,
|
||
HLC3,
|
||
OC2,
|
||
OHL3,
|
||
OHLC4
|
||
}
|
||
public enum EndType
|
||
{
|
||
Close,
|
||
HighLow
|
||
}
|
||
public enum Match
|
||
{
|
||
BullConfirmed = 200,
|
||
BullSignal = 100,
|
||
BullBasis = 10,
|
||
Neutral = 1,
|
||
None = 0,
|
||
BearBasis = -10,
|
||
BearSignal = -100,
|
||
BearConfirmed = -200
|
||
}
|
||
public enum MaType
|
||
{
|
||
ALMA,
|
||
DEMA,
|
||
EPMA,
|
||
EMA,
|
||
HMA,
|
||
KAMA,
|
||
MAMA,
|
||
SMA,
|
||
SMMA,
|
||
TEMA,
|
||
WMA
|
||
}
|
||
public enum PeriodSize
|
||
{
|
||
Month,
|
||
Week,
|
||
Day,
|
||
FourHours,
|
||
TwoHours,
|
||
OneHour,
|
||
ThirtyMinutes,
|
||
FifteenMinutes,
|
||
FiveMinutes,
|
||
ThreeMinutes,
|
||
TwoMinutes,
|
||
OneMinute
|
||
}
|
||
public enum SyncType
|
||
{
|
||
Prepend,
|
||
AppendOnly,
|
||
RemoveOnly,
|
||
FullMatch
|
||
}
|
||
public static class Pruning
|
||
{
|
||
/// <summary> Removes a specific quantity from the beginning of the time series list.
|
||
/// <para>
|
||
/// See <see href="https://dotnet.StockIndicators.dev/utilities/#remove-warmup-periods?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">documentation</see> for more information.
|
||
/// </para>
|
||
/// </summary><typeparam name="T">Any series type.</typeparam><param name="series">Collection to evaluate.</param><param name="removePeriods">Exact quantity to remove from the beginning of the series.</param><returns>Time series, pruned.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<T> RemoveWarmupPeriods<T>(this IEnumerable<T> series, int removePeriods)
|
||
{
|
||
if (removePeriods >= 0)
|
||
{
|
||
return series.Remove(removePeriods);
|
||
}
|
||
throw new ArgumentOutOfRangeException("removePeriods", removePeriods, "If specified, the Remove Periods value must be greater than or equal to 0.");
|
||
}
|
||
|
||
internal static List<T> Remove<T>(this IEnumerable<T> series, int removePeriods)
|
||
{
|
||
List<T> list = series.ToList();
|
||
if (list.Count <= removePeriods)
|
||
{
|
||
return new List<T>();
|
||
}
|
||
if (removePeriods > 0)
|
||
{
|
||
for (int i = 0; i < removePeriods; i = checked(i + 1))
|
||
{
|
||
list.RemoveAt(0);
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
public static class Seeking
|
||
{
|
||
/// <summary> Finds time series values on a specific date.
|
||
/// <para>
|
||
/// See <see href="https://dotnet.StockIndicators.dev/utilities/#find-indicator-result-by-date?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">documentation</see> for more information.
|
||
/// </para>
|
||
/// </summary><typeparam name="TSeries">Any series type.</typeparam><param name="series">Time series to evaluate.</param><param name="lookupDate">Exact date to lookup.</param><returns>First
|
||
/// record in the series on the date specified.</returns>
|
||
public static TSeries? Find<TSeries>(this IEnumerable<TSeries> series, DateTime lookupDate) where TSeries : ISeries
|
||
{
|
||
return series.FirstOrDefault((TSeries x) => x.Date == lookupDate);
|
||
}
|
||
}
|
||
public interface ISeries
|
||
{
|
||
DateTime Date { get; }
|
||
}
|
||
public static class Sorting
|
||
{
|
||
public static Collection<TSeries> ToSortedCollection<TSeries>(this IEnumerable<TSeries> series) where TSeries : ISeries
|
||
{
|
||
return series.OrderBy((TSeries x) => x.Date).ToCollection();
|
||
}
|
||
|
||
internal static List<TSeries> ToSortedList<TSeries>(this IEnumerable<TSeries> series) where TSeries : ISeries
|
||
{
|
||
return series.OrderBy((TSeries x) => x.Date).ToList();
|
||
}
|
||
}
|
||
internal static class Transforms
|
||
{
|
||
internal static Collection<T> ToCollection<T>(this IEnumerable<T> source)
|
||
{
|
||
if (source == null)
|
||
{
|
||
throw new ArgumentNullException("source");
|
||
}
|
||
Collection<T> collection = new Collection<T>();
|
||
foreach (T item in source)
|
||
{
|
||
collection.Add(item);
|
||
}
|
||
return collection;
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// Nullable <c>System.<see cref="T:System.Math" /></c> functions.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// <c>System.Math</c> infamously does not allow
|
||
/// or handle nullable input values.
|
||
/// Instead of adding repetitive inline defensive code,
|
||
/// we're using these equivalents. Most are simple wrappers.
|
||
/// </remarks>
|
||
public static class NullMath
|
||
{
|
||
/// <summary>
|
||
/// Returns the absolute value of a nullable double.
|
||
/// </summary>
|
||
/// <param name="value">The nullable double value.</param>
|
||
/// <returns>The absolute value, or null if the input is null.</returns>
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
public static double? Abs(this double? value)
|
||
{
|
||
if (!value.HasValue)
|
||
{
|
||
return null;
|
||
}
|
||
if (!(value.GetValueOrDefault() < 0.0))
|
||
{
|
||
return value;
|
||
}
|
||
return 0.0 - value.GetValueOrDefault();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Rounds a nullable decimal value to a specified number of fractional digits.
|
||
/// </summary>
|
||
/// <param name="value">The nullable decimal value.</param>
|
||
/// <param name="digits">The number of fractional digits.</param>
|
||
/// <returns>The rounded value, or null if the input is null.</returns>
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
public static decimal? Round(this decimal? value, int digits)
|
||
{
|
||
if (!value.HasValue)
|
||
{
|
||
return null;
|
||
}
|
||
return Math.Round(value.GetValueOrDefault(), digits);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Rounds a nullable double value to a specified number of fractional digits.
|
||
/// </summary>
|
||
/// <param name="value">The nullable double value.</param>
|
||
/// <param name="digits">The number of fractional digits.</param>
|
||
/// <returns>The rounded value, or null if the input is null.</returns>
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
public static double? Round(this double? value, int digits)
|
||
{
|
||
if (!value.HasValue)
|
||
{
|
||
return null;
|
||
}
|
||
return Math.Round(value.GetValueOrDefault(), digits);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Rounds a double value to a specified number of fractional digits.
|
||
/// It is an extension alias of <see cref="M:System.Math.Round(System.Double,System.Int32)" />
|
||
/// </summary>
|
||
/// <param name="value">The double value.</param>
|
||
/// <param name="digits">The number of fractional digits.</param>
|
||
/// <returns>The rounded value.</returns>
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
public static double Round(this double value, int digits)
|
||
{
|
||
return Math.Round(value, digits);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Rounds a decimal value to a specified number of fractional digits.
|
||
/// It is an extension alias of <see cref="M:System.Math.Round(System.Decimal,System.Int32)" />
|
||
/// </summary>
|
||
/// <param name="value">The decimal value.</param>
|
||
/// <param name="digits">The number of fractional digits.</param>
|
||
/// <returns>The rounded value.</returns>
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
public static decimal Round(this decimal value, int digits)
|
||
{
|
||
return Math.Round(value, digits);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Converts a nullable double value to NaN if it is null.
|
||
/// </summary>
|
||
/// <param name="value">The nullable double value.</param>
|
||
/// <returns>The value, or NaN if the input is null.</returns>
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
public static double Null2NaN(this double? value)
|
||
{
|
||
return value ?? double.NaN;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Converts a nullable decimal value to NaN if it is null.
|
||
/// </summary>
|
||
/// <param name="value">The nullable decimal value.</param>
|
||
/// <returns>The value as a double, or NaN if the input is null.</returns>
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
public static double Null2NaN(this decimal? value)
|
||
{
|
||
return ((double?)value) ?? double.NaN;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Converts a nullable double value to null if it is NaN.
|
||
/// </summary>
|
||
/// <param name="value">The nullable double value.</param>
|
||
/// <returns>The value, or null if the input is NaN.</returns>
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
public static double? NaN2Null(this double? value)
|
||
{
|
||
if (!value.HasValue || !double.IsNaN(value.GetValueOrDefault()))
|
||
{
|
||
return value;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Converts a double value to null if it is NaN.
|
||
/// </summary>
|
||
/// <param name="value">The double value.</param>
|
||
/// <returns>The value, or null if the input is NaN.</returns>
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
public static double? NaN2Null(this double value)
|
||
{
|
||
if (!double.IsNaN(value))
|
||
{
|
||
return value;
|
||
}
|
||
return null;
|
||
}
|
||
}
|
||
public static class Numerix
|
||
{
|
||
public static double StdDev(this double[] values)
|
||
{
|
||
if (values == null)
|
||
{
|
||
throw new ArgumentNullException("values", "StdDev values cannot be null.");
|
||
}
|
||
double result = 0.0;
|
||
int num = values.Length;
|
||
checked
|
||
{
|
||
if (num > 1)
|
||
{
|
||
double num2 = 0.0;
|
||
for (int i = 0; i < num; i++)
|
||
{
|
||
num2 += values[i];
|
||
}
|
||
double num3 = num2 / (double)num;
|
||
double num4 = 0.0;
|
||
for (int j = 0; j < num; j++)
|
||
{
|
||
double num5 = values[j];
|
||
num4 += (num5 - num3) * (num5 - num3);
|
||
}
|
||
result = Math.Sqrt(num4 / (double)num);
|
||
}
|
||
return result;
|
||
}
|
||
}
|
||
|
||
public static double Slope(double[] x, double[] y)
|
||
{
|
||
if (x == null)
|
||
{
|
||
throw new ArgumentNullException("x", "Slope X values cannot be null.");
|
||
}
|
||
if (y == null)
|
||
{
|
||
throw new ArgumentNullException("y", "Slope Y values cannot be null.");
|
||
}
|
||
if (x.Length != y.Length)
|
||
{
|
||
throw new ArgumentException("Slope X and Y arrays must be the same size.");
|
||
}
|
||
int num = x.Length;
|
||
double num2 = 0.0;
|
||
double num3 = 0.0;
|
||
checked
|
||
{
|
||
for (int i = 0; i < num; i++)
|
||
{
|
||
num2 += x[i];
|
||
num3 += y[i];
|
||
}
|
||
double num4 = num2 / (double)num;
|
||
double num5 = num3 / (double)num;
|
||
double num6 = 0.0;
|
||
double num7 = 0.0;
|
||
for (int j = 0; j < num; j++)
|
||
{
|
||
double num8 = x[j] - num4;
|
||
double num9 = y[j] - num5;
|
||
num6 += num8 * num8;
|
||
num7 += num8 * num9;
|
||
}
|
||
return num7 / num6;
|
||
}
|
||
}
|
||
|
||
internal static DateTime RoundDown(this DateTime dateTime, TimeSpan interval)
|
||
{
|
||
checked
|
||
{
|
||
if (!(interval == TimeSpan.Zero))
|
||
{
|
||
return dateTime.AddTicks(-unchecked(dateTime.Ticks % interval.Ticks));
|
||
}
|
||
return dateTime;
|
||
}
|
||
}
|
||
|
||
internal static TimeSpan ToTimeSpan(this PeriodSize periodSize)
|
||
{
|
||
return periodSize switch
|
||
{
|
||
PeriodSize.OneMinute => TimeSpan.FromMinutes(1L),
|
||
PeriodSize.TwoMinutes => TimeSpan.FromMinutes(2L),
|
||
PeriodSize.ThreeMinutes => TimeSpan.FromMinutes(3L),
|
||
PeriodSize.FiveMinutes => TimeSpan.FromMinutes(5L),
|
||
PeriodSize.FifteenMinutes => TimeSpan.FromMinutes(15L),
|
||
PeriodSize.ThirtyMinutes => TimeSpan.FromMinutes(30L),
|
||
PeriodSize.OneHour => TimeSpan.FromHours(1),
|
||
PeriodSize.TwoHours => TimeSpan.FromHours(2),
|
||
PeriodSize.FourHours => TimeSpan.FromHours(4),
|
||
PeriodSize.Day => TimeSpan.FromDays(1),
|
||
PeriodSize.Week => TimeSpan.FromDays(7),
|
||
_ => TimeSpan.Zero,
|
||
};
|
||
}
|
||
|
||
internal static int GetDecimalPlaces(this decimal n)
|
||
{
|
||
n = Math.Abs(n);
|
||
n -= (decimal)(int)n;
|
||
int num = 0;
|
||
while (n > 0m)
|
||
{
|
||
num = checked(num + 1);
|
||
n *= 10m;
|
||
n -= (decimal)(int)n;
|
||
}
|
||
return num;
|
||
}
|
||
}
|
||
public enum NullTo
|
||
{
|
||
NaN,
|
||
Null
|
||
}
|
||
public static class QuoteUtility
|
||
{
|
||
private static readonly CultureInfo invCulture = CultureInfo.InvariantCulture;
|
||
|
||
/// <summary>
|
||
/// Converts historical quotes into larger bar sizes.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/utilities/#resize-quote-history?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="newSize">PeriodSize enum representing the new bar size.</param><returns>Time series of historical quote values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<Quote> Aggregate<TQuote>(this IEnumerable<TQuote> quotes, PeriodSize newSize) where TQuote : IQuote
|
||
{
|
||
if (newSize != PeriodSize.Month)
|
||
{
|
||
TimeSpan timeSpan = newSize.ToTimeSpan();
|
||
return quotes.Aggregate(timeSpan);
|
||
}
|
||
return from x in quotes
|
||
orderby x.Date
|
||
group x by new DateTime(x.Date.Year, x.Date.Month, 1) into x
|
||
select new Quote
|
||
{
|
||
Date = x.Key,
|
||
Open = x.First().Open,
|
||
High = x.Max((TQuote t) => t.High),
|
||
Low = x.Min((TQuote t) => t.Low),
|
||
Close = x.Last().Close,
|
||
Volume = x.Sum((TQuote t) => t.Volume)
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// Converts historical quotes into larger bar sizes.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/utilities/#resize-quote-history?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="timeSpan">TimeSpan representing the new bar size.</param><returns>Time series of historical quote values.</returns><exception cref="T:System.ArgumentOutOfRangeException">Invalid parameter value provided.</exception>
|
||
public static IEnumerable<Quote> Aggregate<TQuote>(this IEnumerable<TQuote> quotes, TimeSpan timeSpan) where TQuote : IQuote
|
||
{
|
||
if (timeSpan <= TimeSpan.Zero)
|
||
{
|
||
throw new ArgumentOutOfRangeException("timeSpan", timeSpan, "Quotes Aggregation must use a usable new size value (see documentation for options).");
|
||
}
|
||
return from x in quotes
|
||
orderby x.Date
|
||
group x by x.Date.RoundDown(timeSpan) into x
|
||
select new Quote
|
||
{
|
||
Date = x.Key,
|
||
Open = x.First().Open,
|
||
High = x.Max((TQuote t) => t.High),
|
||
Low = x.Min((TQuote t) => t.Low),
|
||
Close = x.Last().Close,
|
||
Volume = x.Sum((TQuote t) => t.Volume)
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// Optionally select which candle part to use in the calculation.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/indicators/BasicQuote/#candlepart-options?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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="candlePart">The OHLCV element or simply calculated value type.</param><returns>Time series of Quote tuple values.</returns><exception cref="T:Skender.Stock.Indicators.InvalidQuotesException">Invalid candle part provided.</exception>
|
||
public static IEnumerable<(DateTime Date, double Value)> Use<TQuote>(this IEnumerable<TQuote> quotes, CandlePart candlePart = CandlePart.Close) where TQuote : IQuote
|
||
{
|
||
return quotes.Select((TQuote x) => x.ToTuple(candlePart));
|
||
}
|
||
|
||
public static Collection<(DateTime, double)> ToTupleCollection<TQuote>(this IEnumerable<TQuote> quotes, CandlePart candlePart) where TQuote : IQuote
|
||
{
|
||
return quotes.ToTuple(candlePart).ToCollection();
|
||
}
|
||
|
||
internal static List<(DateTime, double)> ToTuple<TQuote>(this IEnumerable<TQuote> quotes, CandlePart candlePart) where TQuote : IQuote
|
||
{
|
||
return (from x in quotes
|
||
orderby x.Date
|
||
select x.ToTuple(candlePart)).ToList();
|
||
}
|
||
|
||
public static Collection<(DateTime, double)> ToSortedCollection(this IEnumerable<(DateTime date, double value)> tuples)
|
||
{
|
||
return tuples.ToSortedList().ToCollection();
|
||
}
|
||
|
||
internal static List<(DateTime, double)> ToSortedList(this IEnumerable<(DateTime date, double value)> tuples)
|
||
{
|
||
return tuples.OrderBy(((DateTime date, double value) x) => x.date).ToList();
|
||
}
|
||
|
||
internal static List<QuoteD> ToQuoteD<TQuote>(this IEnumerable<TQuote> quotes) where TQuote : IQuote
|
||
{
|
||
return (from x in quotes
|
||
select new QuoteD
|
||
{
|
||
Date = x.Date,
|
||
Open = (double)x.Open,
|
||
High = (double)x.High,
|
||
Low = (double)x.Low,
|
||
Close = (double)x.Close,
|
||
Volume = (double)x.Volume
|
||
} into x
|
||
orderby x.Date
|
||
select x).ToList();
|
||
}
|
||
|
||
internal static List<(DateTime, double)> ToTuple(this List<QuoteD> qdList, CandlePart candlePart)
|
||
{
|
||
return (from x in qdList
|
||
orderby x.Date
|
||
select x.ToTuple(candlePart)).ToList();
|
||
}
|
||
|
||
internal static (DateTime date, double value) ToTuple<TQuote>(this TQuote q, CandlePart candlePart) where TQuote : IQuote
|
||
{
|
||
return candlePart switch
|
||
{
|
||
CandlePart.Open => (date: q.Date, value: (double)q.Open),
|
||
CandlePart.High => (date: q.Date, value: (double)q.High),
|
||
CandlePart.Low => (date: q.Date, value: (double)q.Low),
|
||
CandlePart.Close => (date: q.Date, value: (double)q.Close),
|
||
CandlePart.Volume => (date: q.Date, value: (double)q.Volume),
|
||
CandlePart.HL2 => (date: q.Date, value: (double)(q.High + q.Low) / 2.0),
|
||
CandlePart.HLC3 => (date: q.Date, value: (double)(q.High + q.Low + q.Close) / 3.0),
|
||
CandlePart.OC2 => (date: q.Date, value: (double)(q.Open + q.Close) / 2.0),
|
||
CandlePart.OHL3 => (date: q.Date, value: (double)(q.Open + q.High + q.Low) / 3.0),
|
||
CandlePart.OHLC4 => (date: q.Date, value: (double)(q.Open + q.High + q.Low + q.Close) / 4.0),
|
||
_ => throw new ArgumentOutOfRangeException("candlePart", candlePart, "Invalid candlePart provided."),
|
||
};
|
||
}
|
||
|
||
internal static BasicData ToBasicData<TQuote>(this TQuote q, CandlePart candlePart) where TQuote : IQuote
|
||
{
|
||
return candlePart switch
|
||
{
|
||
CandlePart.Open => new BasicData
|
||
{
|
||
Date = q.Date,
|
||
Value = (double)q.Open
|
||
},
|
||
CandlePart.High => new BasicData
|
||
{
|
||
Date = q.Date,
|
||
Value = (double)q.High
|
||
},
|
||
CandlePart.Low => new BasicData
|
||
{
|
||
Date = q.Date,
|
||
Value = (double)q.Low
|
||
},
|
||
CandlePart.Close => new BasicData
|
||
{
|
||
Date = q.Date,
|
||
Value = (double)q.Close
|
||
},
|
||
CandlePart.Volume => new BasicData
|
||
{
|
||
Date = q.Date,
|
||
Value = (double)q.Volume
|
||
},
|
||
CandlePart.HL2 => new BasicData
|
||
{
|
||
Date = q.Date,
|
||
Value = (double)(q.High + q.Low) / 2.0
|
||
},
|
||
CandlePart.HLC3 => new BasicData
|
||
{
|
||
Date = q.Date,
|
||
Value = (double)(q.High + q.Low + q.Close) / 3.0
|
||
},
|
||
CandlePart.OC2 => new BasicData
|
||
{
|
||
Date = q.Date,
|
||
Value = (double)(q.Open + q.Close) / 2.0
|
||
},
|
||
CandlePart.OHL3 => new BasicData
|
||
{
|
||
Date = q.Date,
|
||
Value = (double)(q.Open + q.High + q.Low) / 3.0
|
||
},
|
||
CandlePart.OHLC4 => new BasicData
|
||
{
|
||
Date = q.Date,
|
||
Value = (double)(q.Open + q.High + q.Low + q.Close) / 4.0
|
||
},
|
||
_ => throw new ArgumentOutOfRangeException("candlePart", candlePart, "Invalid candlePart provided."),
|
||
};
|
||
}
|
||
|
||
internal static (DateTime, double) ToTuple(this QuoteD q, CandlePart candlePart)
|
||
{
|
||
return candlePart switch
|
||
{
|
||
CandlePart.Open => (q.Date, q.Open),
|
||
CandlePart.High => (q.Date, q.High),
|
||
CandlePart.Low => (q.Date, q.Low),
|
||
CandlePart.Close => (q.Date, q.Close),
|
||
CandlePart.Volume => (q.Date, q.Volume),
|
||
CandlePart.HL2 => (q.Date, (q.High + q.Low) / 2.0),
|
||
CandlePart.HLC3 => (q.Date, (q.High + q.Low + q.Close) / 3.0),
|
||
CandlePart.OC2 => (q.Date, (q.Open + q.Close) / 2.0),
|
||
CandlePart.OHL3 => (q.Date, (q.Open + q.High + q.Low) / 3.0),
|
||
CandlePart.OHLC4 => (q.Date, (q.Open + q.High + q.Low + q.Close) / 4.0),
|
||
_ => throw new ArgumentOutOfRangeException("candlePart", candlePart, "Invalid candlePart provided."),
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// Validate historical quotes.
|
||
/// <para>
|
||
/// See
|
||
/// <see href="https://dotnet.StockIndicators.dev/utilities/#validate-quote-history?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">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><returns>Time series of historical quote values.</returns><exception cref="T:Skender.Stock.Indicators.InvalidQuotesException">Validation check failed.</exception>
|
||
public static IEnumerable<TQuote> Validate<TQuote>(this IEnumerable<TQuote> quotes) where TQuote : IQuote
|
||
{
|
||
List<TQuote> list = quotes.ToSortedList();
|
||
DateTime dateTime = DateTime.MinValue;
|
||
foreach (TQuote item in list)
|
||
{
|
||
if (dateTime == item.Date)
|
||
{
|
||
throw new InvalidQuotesException("Duplicate date found on " + item.Date.ToString("o", invCulture) + ".");
|
||
}
|
||
dateTime = item.Date;
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
public class InvalidQuotesException : ArgumentOutOfRangeException
|
||
{
|
||
public InvalidQuotesException()
|
||
{
|
||
}
|
||
|
||
public InvalidQuotesException(string? paramName)
|
||
: base(paramName)
|
||
{
|
||
}
|
||
|
||
public InvalidQuotesException(string? message, Exception? innerException)
|
||
: base(message, innerException)
|
||
{
|
||
}
|
||
|
||
public InvalidQuotesException(string? paramName, string? message)
|
||
: base(paramName, message)
|
||
{
|
||
}
|
||
|
||
public InvalidQuotesException(string? paramName, object? actualValue, string? message)
|
||
: base(paramName, actualValue, message)
|
||
{
|
||
}
|
||
}
|
||
public interface IQuote : ISeries
|
||
{
|
||
decimal Open { get; }
|
||
|
||
decimal High { get; }
|
||
|
||
decimal Low { get; }
|
||
|
||
decimal Close { get; }
|
||
|
||
decimal Volume { get; }
|
||
}
|
||
[Serializable]
|
||
public class Quote : IQuote, ISeries
|
||
{
|
||
public DateTime Date { get; set; }
|
||
|
||
public decimal Open { get; set; }
|
||
|
||
public decimal High { get; set; }
|
||
|
||
public decimal Low { get; set; }
|
||
|
||
public decimal Close { get; set; }
|
||
|
||
public decimal Volume { get; set; }
|
||
}
|
||
[Serializable]
|
||
internal class QuoteD
|
||
{
|
||
internal DateTime Date { get; set; }
|
||
|
||
internal double Open { get; set; }
|
||
|
||
internal double High { get; set; }
|
||
|
||
internal double Low { get; set; }
|
||
|
||
internal double Close { get; set; }
|
||
|
||
internal double Volume { get; set; }
|
||
}
|
||
public interface IReusableResult : ISeries
|
||
{
|
||
double? Value { get; }
|
||
}
|
||
[Serializable]
|
||
public abstract class ResultBase : ISeries
|
||
{
|
||
public DateTime Date { get; set; }
|
||
}
|
||
public static class ResultUtility
|
||
{
|
||
/// <summary>
|
||
/// Forces indicator results to have the same date-based records as another result baseline.
|
||
/// <para>
|
||
/// This utility is undocumented.
|
||
/// </para>
|
||
/// </summary><typeparam name="TResultA">Any indicator result series type to be transformed.</typeparam><typeparam name="TResultB">Any indicator result series type to be matched.</typeparam><param name="syncMe">The indicator result series to be modified.</param><param name="toMatch">The indicator result series to compare for matching.</param><param name="syncType">Synchronization behavior See options in SyncType enum.</param><returns>Indicator result series, synchronized to a comparator match.</returns><exception cref="T:System.ArgumentOutOfRangeException">
|
||
/// Invalid parameter value provided.
|
||
/// </exception>
|
||
public static IEnumerable<TResultA> SyncIndex<TResultA, TResultB>(this IEnumerable<TResultA> syncMe, IEnumerable<TResultB> toMatch, SyncType syncType = SyncType.FullMatch) where TResultA : ISeries where TResultB : ISeries
|
||
{
|
||
List<TResultA> list = syncMe.ToSortedList();
|
||
List<TResultB> list2 = toMatch.ToSortedList();
|
||
if (list.Count == 0 || list2.Count == 0)
|
||
{
|
||
return new List<TResultA>();
|
||
}
|
||
bool flag = false;
|
||
bool flag2 = false;
|
||
bool flag3 = false;
|
||
switch (syncType)
|
||
{
|
||
case SyncType.Prepend:
|
||
flag = true;
|
||
break;
|
||
case SyncType.AppendOnly:
|
||
flag = (flag2 = true);
|
||
break;
|
||
case SyncType.RemoveOnly:
|
||
flag3 = true;
|
||
break;
|
||
case SyncType.FullMatch:
|
||
flag = (flag2 = (flag3 = true));
|
||
break;
|
||
default:
|
||
throw new ArgumentOutOfRangeException("syncType");
|
||
}
|
||
Type type = list[0].GetType();
|
||
if (flag || flag2)
|
||
{
|
||
List<TResultA> list3 = new List<TResultA>();
|
||
for (int i = 0; i < list2.Count; i = checked(i + 1))
|
||
{
|
||
TResultB val = list2[i];
|
||
if (list.Find(val.Date) == null)
|
||
{
|
||
TResultA val2 = (TResultA)Activator.CreateInstance(type, val.Date);
|
||
if (val2 != null)
|
||
{
|
||
list3.Add(val2);
|
||
}
|
||
}
|
||
else if (!flag2)
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
list.AddRange(list3);
|
||
}
|
||
if (flag3)
|
||
{
|
||
List<TResultA> list4 = new List<TResultA>();
|
||
for (int j = 0; j < list.Count; j = checked(j + 1))
|
||
{
|
||
TResultA item = list[j];
|
||
if (list2.Find(item.Date) == null)
|
||
{
|
||
list4.Add(item);
|
||
}
|
||
}
|
||
list.RemoveAll(list4.Contains);
|
||
}
|
||
return list.ToSortedList();
|
||
}
|
||
|
||
/// <summary> Removes non-essential records containing null or NaN values. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#condense?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><typeparam name="TResult">Any result
|
||
/// type.</typeparam><param name="results">Indicator results to evaluate.</param><returns>Time series of indicator results,
|
||
/// condensed.</returns>
|
||
public static IEnumerable<TResult> Condense<TResult>(this IEnumerable<TResult> results) where TResult : IReusableResult
|
||
{
|
||
List<TResult> list = results.ToList();
|
||
list.RemoveAll(delegate(TResult x)
|
||
{
|
||
double? value = x.Value;
|
||
return (!value.HasValue || double.IsNaN(value.GetValueOrDefault())) ? true : false;
|
||
});
|
||
return list.ToSortedList();
|
||
}
|
||
|
||
/// <summary>Converts results into a reusable tuple with warmup periods removed and nulls converted
|
||
/// to NaN. <para> See <see href="https://dotnet.StockIndicators.dev/utilities/#using-tuple-results?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="reusable">Indicator results to evaluate.</param><returns>Collection of non-nullable tuple time series of results, without null warmup periods.</returns>
|
||
public static Collection<(DateTime Date, double Value)> ToTupleChainable(this IEnumerable<IReusableResult> reusable)
|
||
{
|
||
return reusable.ToTuple().ToCollection();
|
||
}
|
||
|
||
internal static List<(DateTime Date, double Value)> ToTuple(this IEnumerable<IReusableResult> reusable)
|
||
{
|
||
List<(DateTime, double)> list = new List<(DateTime, double)>();
|
||
List<IReusableResult> list2 = reusable.ToList();
|
||
for (int num = list2.FindIndex((IReusableResult x) => x.Value.HasValue); num < list2.Count; num = checked(num + 1))
|
||
{
|
||
IReusableResult reusableResult = list2[num];
|
||
list.Add((reusableResult.Date, reusableResult.Value.Null2NaN()));
|
||
}
|
||
return list.OrderBy<(DateTime, double), DateTime>(((DateTime date, double value) x) => x.date).ToList();
|
||
}
|
||
|
||
/// <summary>Converts results into a tuple collection with non-nullable NaN to replace null values. <para>
|
||
/// See <see href="https://dotnet.StockIndicators.dev/utilities/#using-tuple-results?utm_source=library&utm_medium=inline-help&utm_campaign=embedded">
|
||
/// documentation</see> for more information. </para>
|
||
/// </summary><param name="reusable">Indicator results to evaluate.</param><returns>Collection of tuple time series of
|
||
/// results with specified handling of nulls, without pruning.</returns>
|
||
public static Collection<(DateTime Date, double Value)> ToTupleNaN(this IEnumerable<IReusableResult> reusable)
|
||
{
|
||
List<IReusableResult> list = reusable.ToSortedList();
|
||
int count = list.Count;
|
||
Collection<(DateTime, double)> collection = new Collection<(DateTime, double)>();
|
||
for (int i = 0; i < count; i = checked(i + 1))
|
||
{
|
||
IReusableResult reusableResult = list[i];
|
||
collection.Add((reusableResult.Date, reusableResult.Value.Null2NaN()));
|
||
}
|
||
return collection;
|
||
}
|
||
}
|