// BOLLINGER BANDS /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <BollingerBandsResult> GetBollingerBands <TQuote>( this IEnumerable <TQuote> quotes, int lookbackPeriods = 20, double standardDeviations = 2) where TQuote : IQuote { // convert quotes List <BasicD> bdList = quotes.ConvertToBasic(CandlePart.Close); // check parameter arguments ValidateBollingerBands(lookbackPeriods, standardDeviations); // initialize List <BollingerBandsResult> results = new(bdList.Count); // roll through quotes for (int i = 0; i < bdList.Count; i++) { BasicD q = bdList[i]; decimal close = (decimal)q.Value; int index = i + 1; BollingerBandsResult r = new() { Date = q.Date }; if (index >= lookbackPeriods) { double[] periodClose = new double[lookbackPeriods]; double sum = 0; int n = 0; for (int p = index - lookbackPeriods; p < index; p++) { BasicD d = bdList[p]; periodClose[n] = d.Value; sum += d.Value; n++; } double periodAvg = sum / lookbackPeriods; double stdDev = Functions.StdDev(periodClose); r.Sma = (decimal)periodAvg; r.UpperBand = (decimal)(periodAvg + (standardDeviations * stdDev)); r.LowerBand = (decimal)(periodAvg - (standardDeviations * stdDev)); r.PercentB = (r.UpperBand == r.LowerBand) ? null : (double)((close - r.LowerBand) / (r.UpperBand - r.LowerBand)); r.ZScore = (stdDev == 0) ? null : (double)(close - r.Sma) / stdDev; r.Width = (periodAvg == 0) ? null : (double)(r.UpperBand - r.LowerBand) / periodAvg; } results.Add(r); } return(results); }
// internals private static List <StdDevResult> CalcStdDev( List <BasicD> bdList, int lookbackPeriods, int?smaPeriods = null) { // check parameter arguments ValidateStdDev(lookbackPeriods, smaPeriods); // initialize List <StdDevResult> results = new(bdList.Count); // roll through quotes for (int i = 0; i < bdList.Count; i++) { BasicD bd = bdList[i]; int index = i + 1; StdDevResult result = new() { Date = bd.Date, }; if (index >= lookbackPeriods) { double[] periodValues = new double[lookbackPeriods]; double sum = 0; int n = 0; for (int p = index - lookbackPeriods; p < index; p++) { BasicD d = bdList[p]; periodValues[n] = d.Value; sum += d.Value; n++; } double periodAvg = sum / lookbackPeriods; result.StdDev = Functions.StdDev(periodValues); result.Mean = periodAvg; result.ZScore = (result.StdDev == 0) ? null : (bd.Value - periodAvg) / result.StdDev; } results.Add(result); // optional SMA if (smaPeriods != null && index >= lookbackPeriods + smaPeriods - 1) { double sumSma = 0; for (int p = index - (int)smaPeriods; p < index; p++) { sumSma += (double)results[p].StdDev; } result.StdDevSma = sumSma / smaPeriods; } } return(results); }
// ULCER INDEX (UI) /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable<UlcerIndexResult> GetUlcerIndex<TQuote>( this IEnumerable<TQuote> quotes, int lookbackPeriods = 14) where TQuote : IQuote { // convert quotes List<BasicD> bdList = quotes.ConvertToBasic(CandlePart.Close); // check parameter arguments ValidateUlcer(lookbackPeriods); // initialize List<UlcerIndexResult> results = new(bdList.Count); // roll through quotes for (int i = 0; i < bdList.Count; i++) { BasicD q = bdList[i]; int index = i + 1; UlcerIndexResult result = new() { Date = q.Date }; if (index >= lookbackPeriods) { double? sumSquared = 0; for (int p = index - lookbackPeriods; p < index; p++) { BasicD d = bdList[p]; int dIndex = p + 1; double maxClose = 0; for (int s = index - lookbackPeriods; s < dIndex; s++) { BasicD dd = bdList[s]; if (dd.Value > maxClose) { maxClose = dd.Value; } } double? percentDrawdown = (maxClose == 0) ? null : 100 * (double)((d.Value - maxClose) / maxClose); sumSquared += percentDrawdown * percentDrawdown; } result.UI = (sumSquared == null) ? null : Math.Sqrt((double)sumSquared / lookbackPeriods); } results.Add(result); } return results; }
// CORRELATION COEFFICIENT /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <CorrResult> GetCorrelation <TQuote>( this IEnumerable <TQuote> quotesA, IEnumerable <TQuote> quotesB, int lookbackPeriods) where TQuote : IQuote { // convert quotes List <BasicD> bdListA = quotesA.ConvertToBasic(CandlePart.Close); List <BasicD> bdListB = quotesB.ConvertToBasic(CandlePart.Close); // check parameter arguments ValidateCorrelation(quotesA, quotesB, lookbackPeriods); // initialize List <CorrResult> results = new(bdListA.Count); // roll through quotes for (int i = 0; i < bdListA.Count; i++) { BasicD a = bdListA[i]; BasicD b = bdListB[i]; int index = i + 1; if (a.Date != b.Date) { throw new InvalidQuotesException(nameof(quotesA), a.Date, "Date sequence does not match. Correlation requires matching dates in provided histories."); } CorrResult r = new() { Date = a.Date }; // calculate correlation if (index >= lookbackPeriods) { double[] dataA = new double[lookbackPeriods]; double[] dataB = new double[lookbackPeriods]; int z = 0; for (int p = index - lookbackPeriods; p < index; p++) { dataA[z] = bdListA[p].Value; dataB[z] = bdListB[p].Value; z++; } r.CalcCorrelation(dataA, dataB); } results.Add(r); } return(results); }
// CHAIKIN MONEY FLOW /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <CmfResult> GetCmf <TQuote>( this IEnumerable <TQuote> quotes, int lookbackPeriods = 20) where TQuote : IQuote { // convert quotes List <BasicD> bdList = quotes.ConvertToBasic(CandlePart.Volume); // check parameter arguments ValidateCmf(lookbackPeriods); // initialize List <CmfResult> results = new(bdList.Count); List <AdlResult> adlResults = GetAdl(quotes).ToList(); // roll through quotes for (int i = 0; i < adlResults.Count; i++) { AdlResult r = adlResults[i]; int index = i + 1; CmfResult result = new() { Date = r.Date, MoneyFlowMultiplier = r.MoneyFlowMultiplier, MoneyFlowVolume = r.MoneyFlowVolume }; if (index >= lookbackPeriods) { double sumMfv = 0; double sumVol = 0; for (int p = index - lookbackPeriods; p < index; p++) { BasicD q = bdList[p]; sumVol += q.Value; AdlResult d = adlResults[p]; sumMfv += (double)d.MoneyFlowVolume; } double avgMfv = sumMfv / lookbackPeriods; double avgVol = sumVol / lookbackPeriods; if (avgVol != 0) { result.Cmf = avgMfv / avgVol; } } results.Add(result); } return(results); }
// AWESOME OSCILLATOR /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <AwesomeResult> GetAwesome <TQuote>( this IEnumerable <TQuote> quotes, int fastPeriods = 5, int slowPeriods = 34) where TQuote : IQuote { // convert quotes List <BasicD> bdList = quotes.ConvertToBasic(CandlePart.HL2); // check parameter arguments ValidateAwesome(fastPeriods, slowPeriods); // initialize int length = bdList.Count; List <AwesomeResult> results = new(); double[] pr = new double[length]; // median price // roll through quotes for (int i = 0; i < length; i++) { BasicD q = bdList[i]; pr[i] = q.Value; int index = i + 1; AwesomeResult r = new() { Date = q.Date }; if (index >= slowPeriods) { double sumSlow = 0; double sumFast = 0; for (int p = index - slowPeriods; p < index; p++) { sumSlow += pr[p]; if (p >= index - fastPeriods) { sumFast += pr[p]; } } r.Oscillator = (sumFast / fastPeriods) - (sumSlow / slowPeriods); r.Normalized = (pr[i] != 0) ? 100 * r.Oscillator / pr[i] : null; } results.Add(r); } return(results); }
// SCHAFF TREND CYCLE (STC) /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <StcResult> GetStc <TQuote>( this IEnumerable <TQuote> quotes, int cyclePeriods = 10, int fastPeriods = 23, int slowPeriods = 50) where TQuote : IQuote { // convert quotes List <BasicD> quotesList = quotes.ConvertToBasic(CandlePart.Close); // check parameter arguments ValidateStc(cyclePeriods, fastPeriods, slowPeriods); // get stochastic of macd IEnumerable <StochResult> stochMacd = quotes .GetMacd(fastPeriods, slowPeriods, 1) .Where(x => x.Macd != null) .Select(x => new Quote { Date = x.Date, High = (decimal)x.Macd, Low = (decimal)x.Macd, Close = (decimal)x.Macd }) .GetStoch(cyclePeriods, 1, 3); // initialize results // to ensure same length as original quotes int length = quotesList.Count; int initPeriods = Math.Min(slowPeriods - 1, length); List <StcResult> results = new(length); for (int i = 0; i < initPeriods; i++) { BasicD q = quotesList[i]; results.Add(new StcResult() { Date = q.Date }); } // add stoch results // TODO: see if List Add works faster results.AddRange( stochMacd .Select(x => new StcResult { Date = x.Date, Stc = x.Oscillator })); return(results); }
// SMOOTHED MOVING AVERAGE /// <include file='./info.xml' path='indicator/type[@name="Main"]/*' /> /// public static IEnumerable <SmmaResult> GetSmma <TQuote>( this IEnumerable <TQuote> quotes, int lookbackPeriods) where TQuote : IQuote { // convert quotes List <BasicD> quotesList = quotes.ConvertToBasic(CandlePart.Close); // check parameter arguments ValidateSmma(lookbackPeriods); // initialize List <SmmaResult> results = new(quotesList.Count); double? prevValue = null; // roll through quotes for (int i = 0; i < quotesList.Count; i++) { BasicD q = quotesList[i]; int index = i + 1; SmmaResult result = new() { Date = q.Date }; // calculate SMMA if (index > lookbackPeriods) { result.Smma = (decimal)((prevValue * (lookbackPeriods - 1)) + q.Value) / lookbackPeriods; } // first SMMA calculated as simple SMA else if (index == lookbackPeriods) { double sumClose = 0; for (int p = index - lookbackPeriods; p < index; p++) { BasicD d = quotesList[p]; sumClose += d.Value; } result.Smma = (decimal)(sumClose / lookbackPeriods); } prevValue = (double?)result.Smma; results.Add(result); } return(results); }
// HURST EXPONENT /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <HurstResult> GetHurst <TQuote>( this IEnumerable <TQuote> quotes, int lookbackPeriods = 100) where TQuote : IQuote { // convert quotes List <BasicD> bdList = quotes.ConvertToBasic(CandlePart.Close); // check parameter arguments ValidateHurst(lookbackPeriods); // initialize int length = bdList.Count; List <HurstResult> results = new(length); // roll through quotes for (int i = 0; i < length; i++) { int index = i + 1; BasicD q = bdList[i]; HurstResult result = new() { Date = q.Date }; if (index > lookbackPeriods) { // get evaluation batch double[] values = new double[lookbackPeriods]; int x = 0; for (int p = index - lookbackPeriods; p < index; p++) { // compile return values if (bdList[p - 1].Value != 0) { values[x] = (bdList[p].Value / bdList[p - 1].Value) - 1; } x++; } // calculate hurst exponent result.HurstExponent = CalcHurst(values); } results.Add(result); } return(results); }
// RATE OF CHANGE (ROC) /// <include file='./info.xml' path='indicator/type[@name="Main"]/*' /> /// public static IEnumerable <RocResult> GetRoc <TQuote>( this IEnumerable <TQuote> quotes, int lookbackPeriods, int?smaPeriods = null) where TQuote : IQuote { // convert quotes List <BasicD> bdList = quotes.ConvertToBasic(CandlePart.Close); // check parameter arguments ValidateRoc(lookbackPeriods, smaPeriods); // initialize List <RocResult> results = new(bdList.Count); // roll through quotes for (int i = 0; i < bdList.Count; i++) { BasicD q = bdList[i]; int index = i + 1; RocResult result = new() { Date = q.Date }; if (index > lookbackPeriods) { BasicD back = bdList[index - lookbackPeriods - 1]; result.Roc = (back.Value == 0) ? null : 100d * (q.Value - back.Value) / back.Value; } results.Add(result); // optional SMA if (smaPeriods != null && index >= lookbackPeriods + smaPeriods) { double?sumSma = 0; for (int p = index - (int)smaPeriods; p < index; p++) { sumSma += results[p].Roc; } result.RocSma = sumSma / smaPeriods; } } return(results); }
// SIMPLE MOVING AVERAGE (EXTENDED VERSION) /// <include file='./info.xml' path='indicator/type[@name="Extended"]/*' /> /// public static IEnumerable <SmaExtendedResult> GetSmaExtended <TQuote>( this IEnumerable <TQuote> quotes, int lookbackPeriods) where TQuote : IQuote { // convert quotes List <BasicD> quotesList = quotes.ConvertToBasic(CandlePart.Close); // initialize List <SmaExtendedResult> results = GetSma(quotes, lookbackPeriods) .Select(x => new SmaExtendedResult { Date = x.Date, Sma = x.Sma }) .ToList(); // roll through quotes for (int i = lookbackPeriods - 1; i < results.Count; i++) { int index = i + 1; SmaExtendedResult r = results[i]; double sma = (double)r.Sma; double sumMad = 0; double sumMse = 0; double?sumMape = 0; for (int p = index - lookbackPeriods; p < index; p++) { BasicD d = quotesList[p]; double close = d.Value; sumMad += Math.Abs(close - sma); sumMse += (close - sma) * (close - sma); sumMape += (close == 0) ? null : Math.Abs(close - sma) / close; } // mean absolute deviation r.Mad = sumMad / lookbackPeriods; // mean squared error r.Mse = sumMse / lookbackPeriods; // mean absolute percent error r.Mape = sumMape / lookbackPeriods; } return(results); }
// standard calculation private static List <EmaResult> CalcEma( this List <BasicD> bdList, int lookbackPeriods) { // check parameter arguments ValidateEma(lookbackPeriods); // initialize int length = bdList.Count; List <EmaResult> results = new(length); double k = 2d / (lookbackPeriods + 1); double?lastEma = 0; int initPeriods = Math.Min(lookbackPeriods, length); for (int i = 0; i < initPeriods; i++) { lastEma += bdList[i].Value; } lastEma /= lookbackPeriods; // roll through quotes for (int i = 0; i < length; i++) { BasicD h = bdList[i]; int index = i + 1; EmaResult result = new() { Date = h.Date }; if (index > lookbackPeriods) { double?ema = lastEma + (k * (h.Value - lastEma)); result.Ema = (decimal?)ema; lastEma = ema; } else if (index == lookbackPeriods) { result.Ema = (decimal?)lastEma; } results.Add(result); } return(results); }
// WEIGHTED MOVING AVERAGE /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <WmaResult> GetWma <TQuote>( this IEnumerable <TQuote> quotes, int lookbackPeriods, CandlePart candlePart = CandlePart.Close) where TQuote : IQuote { // convert quotes List <BasicD> bdList = quotes.ConvertToBasic(candlePart); // check parameter arguments ValidateWma(lookbackPeriods); // initialize List <WmaResult> results = new(bdList.Count); double divisor = (double)lookbackPeriods * (lookbackPeriods + 1) / 2d; // roll through quotes for (int i = 0; i < bdList.Count; i++) { BasicD q = bdList[i]; int index = i + 1; WmaResult result = new() { Date = q.Date }; if (index >= lookbackPeriods) { double wma = 0; for (int p = index - lookbackPeriods; p < index; p++) { BasicD d = bdList[p]; wma += (double)d.Value * (lookbackPeriods - (index - p - 1)) / divisor; } result.Wma = (decimal)wma; } results.Add(result); } return(results); }
public void ConvertToBasic() { // compose basic data List <BasicD> o = quotes.ConvertToBasic(CandlePart.Open); List <BasicD> h = quotes.ConvertToBasic(CandlePart.High); List <BasicD> l = quotes.ConvertToBasic(CandlePart.Low); List <BasicD> c = quotes.ConvertToBasic(CandlePart.Close); List <BasicD> v = quotes.ConvertToBasic(CandlePart.Volume); List <BasicD> x = quotes.ConvertToBasic(CandlePart.HL2); // assertions // should always be the same number of results as there is quotes Assert.AreEqual(502, c.Count); // samples BasicD ro = o[501]; BasicD rh = h[501]; BasicD rl = l[501]; BasicD rc = c[501]; BasicD rv = v[501]; BasicD rx = x[501]; // proper last date DateTime lastDate = DateTime.ParseExact("12/31/2018", "MM/dd/yyyy", EnglishCulture); Assert.AreEqual(lastDate, rc.Date); // last values should be correct Assert.AreEqual(244.92, ro.Value); Assert.AreEqual(245.54, rh.Value); Assert.AreEqual(242.87, rl.Value); Assert.AreEqual(245.28, rc.Value); Assert.AreEqual(147031456, rv.Value); Assert.AreEqual(244.205, rx.Value); }
// internals private static IEnumerable <SmaResult> CalcSma( this List <BasicD> bdList, int lookbackPeriods) { // note: pre-validated // initialize List <SmaResult> results = new(bdList.Count); // roll through quotes for (int i = 0; i < bdList.Count; i++) { BasicD q = bdList[i]; int index = i + 1; SmaResult result = new() { Date = q.Date }; if (index >= lookbackPeriods) { double sumSma = 0; for (int p = index - lookbackPeriods; p < index; p++) { BasicD d = bdList[p]; sumSma += d.Value; } result.Sma = (decimal)sumSma / lookbackPeriods; } results.Add(result); } return(results); }
// BETA COEFFICIENT /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <BetaResult> GetBeta <TQuote>( IEnumerable <TQuote> quotesMarket, IEnumerable <TQuote> quotesEval, int lookbackPeriods, BetaType type = BetaType.Standard) where TQuote : IQuote { // convert quotes List <BasicD> bdListEval = quotesEval.ConvertToBasic(CandlePart.Close); List <BasicD> bdListMrkt = quotesMarket.ConvertToBasic(CandlePart.Close); // check parameter arguments ValidateBeta(quotesMarket, quotesEval, lookbackPeriods); // initialize List <BetaResult> results = new(bdListEval.Count); bool calcSd = type is BetaType.All or BetaType.Standard; bool calcUp = type is BetaType.All or BetaType.Up; bool calcDn = type is BetaType.All or BetaType.Down; // roll through quotes for (int i = 0; i < bdListEval.Count; i++) { BasicD e = bdListEval[i]; BetaResult r = new() { Date = e.Date }; results.Add(r); // skip warmup periods if (i < lookbackPeriods - 1) { continue; } // calculate standard beta if (calcSd) { r.CalcBeta( i, lookbackPeriods, bdListMrkt, bdListEval, BetaType.Standard); } // calculate up/down betas if (i >= lookbackPeriods) { if (calcDn) { r.CalcBeta( i, lookbackPeriods, bdListMrkt, bdListEval, BetaType.Down); } if (calcUp) { r.CalcBeta( i, lookbackPeriods, bdListMrkt, bdListEval, BetaType.Up); } } // ratio and convexity if (type == BetaType.All && r.BetaUp != null && r.BetaDown != null) { r.Ratio = (r.BetaDown != 0) ? r.BetaUp / r.BetaDown : null; r.Convexity = (r.BetaUp - r.BetaDown) * (r.BetaUp - r.BetaDown); } } return(results); }
// MOTHER of ADAPTIVE MOVING AVERAGES (MAMA) /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <MamaResult> GetMama <TQuote>( this IEnumerable <TQuote> quotes, double fastLimit = 0.5, double slowLimit = 0.05) where TQuote : IQuote { // convert quotes List <BasicD> bdList = quotes.ConvertToBasic(CandlePart.HL2); // check parameter arguments ValidateMama(fastLimit, slowLimit); // initialize int length = bdList.Count; List <MamaResult> results = new(length); double sumPr = 0d; double[] pr = new double[length]; // price double[] sm = new double[length]; // smooth double[] dt = new double[length]; // detrender double[] pd = new double[length]; // period double[] q1 = new double[length]; // quadrature double[] i1 = new double[length]; // in-phase double jI; double jQ; double[] q2 = new double[length]; // adj. quadrature double[] i2 = new double[length]; // adj. in-phase double[] re = new double[length]; double[] im = new double[length]; double[] ph = new double[length]; // phase // roll through quotes for (int i = 0; i < length; i++) { BasicD q = bdList[i]; pr[i] = q.Value; MamaResult r = new() { Date = q.Date, }; if (i > 5) { double adj = (0.075 * pd[i - 1]) + 0.54; // smooth and detrender sm[i] = ((4 * pr[i]) + (3 * pr[i - 1]) + (2 * pr[i - 2]) + pr[i - 3]) / 10; dt[i] = ((0.0962 * sm[i]) + (0.5769 * sm[i - 2]) - (0.5769 * sm[i - 4]) - (0.0962 * sm[i - 6])) * adj; // in-phase and quadrature q1[i] = ((0.0962 * dt[i]) + (0.5769 * dt[i - 2]) - (0.5769 * dt[i - 4]) - (0.0962 * dt[i - 6])) * adj; i1[i] = dt[i - 3]; // advance the phases by 90 degrees jI = ((0.0962 * i1[i]) + (0.5769 * i1[i - 2]) - (0.5769 * i1[i - 4]) - (0.0962 * i1[i - 6])) * adj; jQ = ((0.0962 * q1[i]) + (0.5769 * q1[i - 2]) - (0.5769 * q1[i - 4]) - (0.0962 * q1[i - 6])) * adj; // phasor addition for 3-bar averaging i2[i] = i1[i] - jQ; q2[i] = q1[i] + jI; i2[i] = (0.2 * i2[i]) + (0.8 * i2[i - 1]); // smoothing it q2[i] = (0.2 * q2[i]) + (0.8 * q2[i - 1]); // homodyne discriminator re[i] = (i2[i] * i2[i - 1]) + (q2[i] * q2[i - 1]); im[i] = (i2[i] * q2[i - 1]) - (q2[i] * i2[i - 1]); re[i] = (0.2 * re[i]) + (0.8 * re[i - 1]); // smoothing it im[i] = (0.2 * im[i]) + (0.8 * im[i - 1]); // calculate period if (im[i] != 0 && re[i] != 0) { pd[i] = 2 * Math.PI / Math.Atan(im[i] / re[i]); } // adjust period to thresholds pd[i] = (pd[i] > 1.5 * pd[i - 1]) ? 1.5 * pd[i - 1] : pd[i]; pd[i] = (pd[i] < 0.67 * pd[i - 1]) ? 0.67 * pd[i - 1] : pd[i]; pd[i] = (pd[i] < 6d) ? 6d : pd[i]; pd[i] = (pd[i] > 50d) ? 50d : pd[i]; // smooth the period pd[i] = (0.2 * pd[i]) + (0.8 * pd[i - 1]); // determine phase position ph[i] = (i1[i] != 0) ? Math.Atan(q1[i] / i1[i]) * 180 / Math.PI : 0; // change in phase double delta = Math.Max(ph[i - 1] - ph[i], 1d); // adaptive alpha value double alpha = Math.Max(fastLimit / delta, slowLimit); // final indicators r.Mama = (decimal)((alpha * pr[i]) + ((1d - alpha) * (double)results[i - 1].Mama)); r.Fama = (decimal)((0.5d * alpha * (double)r.Mama) + ((1d - (0.5d * alpha)) * (double)results[i - 1].Fama)); } // initialization period else { sumPr += pr[i]; if (i == 5) { r.Mama = (decimal)sumPr / 6m; r.Fama = r.Mama; } pd[i] = 0; sm[i] = 0; dt[i] = 0; i1[i] = 0; q1[i] = 0; i2[i] = 0; q2[i] = 0; re[i] = 0; im[i] = 0; ph[i] = 0; } results.Add(r); } return(results); }
// PRICE VOLUME OSCILLATOR (PVO) /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <PvoResult> GetPvo <TQuote>( this IEnumerable <TQuote> quotes, int fastPeriods = 12, int slowPeriods = 26, int signalPeriods = 9) where TQuote : IQuote { // convert quotes List <BasicD> bdList = quotes.ConvertToBasic(CandlePart.Volume); // check parameter arguments ValidatePvo(fastPeriods, slowPeriods, signalPeriods); // initialize List <EmaResult> emaFast = CalcEma(bdList, fastPeriods); List <EmaResult> emaSlow = CalcEma(bdList, slowPeriods); int length = bdList.Count; List <BasicD> emaDiff = new(); List <PvoResult> results = new(length); // roll through quotes for (int i = 0; i < length; i++) { BasicD h = bdList[i]; EmaResult df = emaFast[i]; EmaResult ds = emaSlow[i]; PvoResult result = new() { Date = h.Date }; if (df?.Ema != null && ds?.Ema != null) { double?pvo = (ds.Ema != 0) ? 100 * (double)((df.Ema - ds.Ema) / ds.Ema) : null; result.Pvo = (decimal?)pvo; // temp data for interim EMA of PVO BasicD diff = new() { Date = h.Date, Value = (double)pvo }; emaDiff.Add(diff); } results.Add(result); } // add signal and histogram to result List <EmaResult> emaSignal = CalcEma(emaDiff, signalPeriods); for (int d = slowPeriods - 1; d < length; d++) { PvoResult r = results[d]; EmaResult ds = emaSignal[d + 1 - slowPeriods]; r.Signal = ds.Ema; r.Histogram = r.Pvo - r.Signal; } return(results); }
// MOVING AVERAGE CONVERGENCE/DIVERGENCE (MACD) OSCILLATOR /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <MacdResult> GetMacd <TQuote>( this IEnumerable <TQuote> quotes, int fastPeriods = 12, int slowPeriods = 26, int signalPeriods = 9) where TQuote : IQuote { // convert quotes List <BasicD> bdList = quotes.ConvertToBasic(CandlePart.Close); // check parameter arguments ValidateMacd(fastPeriods, slowPeriods, signalPeriods); // initialize List <EmaResult> emaFast = CalcEma(bdList, fastPeriods); List <EmaResult> emaSlow = CalcEma(bdList, slowPeriods); int length = bdList.Count; List <BasicD> emaDiff = new(); List <MacdResult> results = new(length); // roll through quotes for (int i = 0; i < length; i++) { BasicD h = bdList[i]; EmaResult df = emaFast[i]; EmaResult ds = emaSlow[i]; MacdResult result = new() { Date = h.Date, FastEma = df.Ema, SlowEma = ds.Ema }; if (df?.Ema != null && ds?.Ema != null) { double macd = (double)(df.Ema - ds.Ema); result.Macd = (decimal)macd; // temp data for interim EMA of macd BasicD diff = new() { Date = h.Date, Value = macd }; emaDiff.Add(diff); } results.Add(result); } // add signal and histogram to result List <EmaResult> emaSignal = CalcEma(emaDiff, signalPeriods); for (int d = slowPeriods - 1; d < length; d++) { MacdResult r = results[d]; EmaResult ds = emaSignal[d + 1 - slowPeriods]; r.Signal = ds.Ema; r.Histogram = r.Macd - r.Signal; } return(results); }
// WILLIAMS ALLIGATOR /// <include file='./info.xml' path='indicator/*' /> /// 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 { // convert quotes List <BasicD> bdList = quotes.ConvertToBasic(CandlePart.HL2); // check parameter arguments ValidateAlligator( jawPeriods, jawOffset, teethPeriods, teethOffset, lipsPeriods, lipsOffset); // initialize int length = bdList.Count; double[] pr = new double[length]; // median price List <AlligatorResult> results = bdList .Select(x => new AlligatorResult { Date = x.Date }) .ToList(); // roll through quotes for (int i = 0; i < length; i++) { BasicD q = bdList[i]; int index = i + 1; pr[i] = q.Value; // only calculate jaw if the array index + offset is still in valid range if (i + jawOffset < length) { AlligatorResult jawResult = results[i + jawOffset]; // calculate alligator's jaw // first value: calculate SMA if (index == jawPeriods) { double sumMedianPrice = 0; for (int p = index - jawPeriods; p < index; p++) { sumMedianPrice += pr[p]; } jawResult.Jaw = (decimal)sumMedianPrice / jawPeriods; } // remaining values: SMMA else if (index > jawPeriods) { double?prevValue = (double?)results[i + jawOffset - 1].Jaw; jawResult.Jaw = (decimal?)((prevValue * (jawPeriods - 1)) + pr[i]) / jawPeriods; } } // only calculate teeth if the array index + offset is still in valid range if (i + teethOffset < length) { AlligatorResult teethResult = results[i + teethOffset]; // calculate alligator's teeth // first value: calculate SMA if (index == teethPeriods) { double sumMedianPrice = 0; for (int p = index - teethPeriods; p < index; p++) { sumMedianPrice += pr[p]; } teethResult.Teeth = (decimal?)sumMedianPrice / teethPeriods; } // remaining values: SMMA else if (index > teethPeriods) { double?prevValue = (double?)results[i + teethOffset - 1].Teeth; teethResult.Teeth = (decimal?)((prevValue * (teethPeriods - 1)) + pr[i]) / teethPeriods; } } // only calculate lips if the array index + offset is still in valid range if (i + lipsOffset < length) { AlligatorResult lipsResult = results[i + lipsOffset]; // calculate alligator's lips // first value: calculate SMA if (index == lipsPeriods) { double sumMedianPrice = 0; for (int p = index - lipsPeriods; p < index; p++) { sumMedianPrice += pr[p]; } lipsResult.Lips = (decimal)sumMedianPrice / lipsPeriods; } // remaining values: SMMA else if (index > lipsPeriods) { double?prevValue = (double?)results[i + lipsOffset - 1].Lips; lipsResult.Lips = (decimal?)((prevValue * (lipsPeriods - 1)) + pr[i]) / lipsPeriods; } } } return(results); }
// TILLSON T3 MOVING AVERAGE (T3) /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <T3Result> GetT3 <TQuote>( this IEnumerable <TQuote> quotes, int lookbackPeriods = 5, double volumeFactor = 0.7) where TQuote : IQuote { // convert quotes List <BasicD> bdList = quotes.ConvertToBasic(CandlePart.Close); // check parameter arguments ValidateT3(lookbackPeriods, volumeFactor); // initialize int length = bdList.Count; List <T3Result> results = new(length); double k = 2d / (lookbackPeriods + 1); double a = volumeFactor; double c1 = -a * a * a; double c2 = (3 * a * a) + (3 * a * a * a); double c3 = (-6 * a * a) - (3 * a) - (3 * a * a * a); double c4 = 1 + (3 * a) + (a * a * a) + (3 * a * a); double e1 = 0, e2 = 0, e3 = 0, e4 = 0, e5 = 0, e6 = 0; double sum1 = 0, sum2 = 0, sum3 = 0, sum4 = 0, sum5 = 0, sum6 = 0; // roll through quotes for (int i = 0; i < length; i++) { BasicD q = bdList[i]; T3Result r = new() { Date = q.Date }; // first smoothing if (i > lookbackPeriods - 1) { e1 += k * (q.Value - e1); // second smoothing if (i > 2 * (lookbackPeriods - 1)) { e2 += k * (e1 - e2); // third smoothing if (i > 3 * (lookbackPeriods - 1)) { e3 += k * (e2 - e3); // fourth smoothing if (i > 4 * (lookbackPeriods - 1)) { e4 += k * (e3 - e4); // fifth smoothing if (i > 5 * (lookbackPeriods - 1)) { e5 += k * (e4 - e5); // sixth smoothing if (i > 6 * (lookbackPeriods - 1)) { e6 += k * (e5 - e6); // T3 moving average r.T3 = (decimal?)((c1 * e6) + (c2 * e5) + (c3 * e4) + (c4 * e3)); } // sixth warmup else { sum6 += e5; if (i == 6 * (lookbackPeriods - 1)) { e6 = sum6 / lookbackPeriods; // initial T3 moving average r.T3 = (decimal?)((c1 * e6) + (c2 * e5) + (c3 * e4) + (c4 * e3)); } } } // fifth warmup else { sum5 += e4; if (i == 5 * (lookbackPeriods - 1)) { sum6 = e5 = sum5 / lookbackPeriods; } } } // fourth warmup else { sum4 += e3; if (i == 4 * (lookbackPeriods - 1)) { sum5 = e4 = sum4 / lookbackPeriods; } } } // third warmup else { sum3 += e2; if (i == 3 * (lookbackPeriods - 1)) { sum4 = e3 = sum3 / lookbackPeriods; } } } // second warmup else { sum2 += e1; if (i == 2 * (lookbackPeriods - 1)) { sum3 = e2 = sum2 / lookbackPeriods; } } } // first warmup else { sum1 += (double)q.Value; if (i == lookbackPeriods - 1) { sum2 = e1 = sum1 / lookbackPeriods; } } results.Add(r); } return(results); }
// VOLATILITY SYSTEM (STOP) /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <VolatilityStopResult> GetVolatilityStop <TQuote>( this IEnumerable <TQuote> quotes, int lookbackPeriods = 7, double multiplier = 3) where TQuote : IQuote { // convert quotes List <BasicD> bdList = quotes.ConvertToBasic(CandlePart.Close); // check parameter arguments ValidateVolatilityStop(lookbackPeriods, multiplier); // initialize int length = bdList.Count; List <VolatilityStopResult> results = new(length); if (length == 0) { return(results); } List <AtrResult> atrList = quotes.GetAtr(lookbackPeriods).ToList(); // initial trend (guess) int initPeriods = Math.Min(length, lookbackPeriods); double sic = (double)bdList[0].Value; bool isLong = (double)bdList[initPeriods - 1].Value > sic; for (int i = 0; i < initPeriods; i++) { BasicD q = bdList[i]; double close = (double)q.Value; sic = isLong ? Math.Max(sic, close) : Math.Min(sic, close); results.Add(new VolatilityStopResult() { Date = q.Date }); } // roll through quotes for (int i = lookbackPeriods; i < length; i++) { BasicD q = bdList[i]; double close = (double)q.Value; // average true range × multiplier constant double arc = (double)atrList[i - 1].Atr * multiplier; VolatilityStopResult r = new() { Date = q.Date, // stop and reverse threshold Sar = (decimal?)(isLong ? sic - arc : sic + arc) }; results.Add(r); // add SAR as separate bands if (isLong) { r.LowerBand = r.Sar; } else { r.UpperBand = r.Sar; } // evaluate stop and reverse if ((isLong && (decimal?)q.Value < r.Sar) || (!isLong && (decimal?)q.Value > r.Sar)) { r.IsStop = true; sic = close; isLong = !isLong; } else { r.IsStop = false; // significant close adjustment // extreme favorable close while in trade sic = isLong ? Math.Max(sic, close) : Math.Min(sic, close); } } // remove first trend to stop, since it is a guess VolatilityStopResult firstStop = results .Where(x => x.IsStop == true) .OrderBy(x => x.Date) .FirstOrDefault(); if (firstStop != null) { int cutIndex = results.IndexOf(firstStop); for (int d = 0; d <= cutIndex; d++) { VolatilityStopResult r = results[d]; r.Sar = null; r.UpperBand = null; r.LowerBand = null; r.IsStop = null; } } return(results); }
// KAUFMAN's ADAPTIVE MOVING AVERAGE /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <KamaResult> GetKama <TQuote>( this IEnumerable <TQuote> quotes, int erPeriods = 10, int fastPeriods = 2, int slowPeriods = 30) where TQuote : IQuote { // convert quotes List <BasicD> quotesList = quotes.ConvertToBasic(CandlePart.Close); // check parameter arguments ValidateKama(erPeriods, fastPeriods, slowPeriods); // initialize List <KamaResult> results = new(quotesList.Count); double scFast = 2d / (fastPeriods + 1); double scSlow = 2d / (slowPeriods + 1); // roll through quotes for (int i = 0; i < quotesList.Count; i++) { BasicD q = quotesList[i]; int index = i + 1; KamaResult r = new() { Date = q.Date }; if (index > erPeriods) { // ER period change double change = Math.Abs(q.Value - quotesList[i - erPeriods].Value); // volatility double sumPV = 0; for (int p = i - erPeriods + 1; p <= i; p++) { sumPV += Math.Abs(quotesList[p].Value - quotesList[p - 1].Value); } if (sumPV != 0) { // efficiency ratio double er = change / sumPV; r.ER = er; // smoothing constant double sc = (er * (scFast - scSlow)) + scSlow; // squared later // kama calculation double?pk = (double?)results[i - 1].Kama; // prior KAMA r.Kama = (decimal?)(pk + (sc * sc * (q.Value - pk))); } // handle flatline case else { r.ER = 0; r.Kama = (decimal?)q.Value; } } // initial value else if (index == erPeriods) { r.Kama = (decimal?)q.Value; } results.Add(r); } return(results); }
// parameter validation private static List <ConnorsRsiResult> CalcConnorsRsiBaseline( List <BasicD> bdList, int rsiPeriods, int rankPeriods) { // initialize List <RsiResult> rsiResults = CalcRsi(bdList, rsiPeriods); int length = bdList.Count; List <ConnorsRsiResult> results = new(length); double?[] gain = new double?[length]; double?lastClose = null; int streak = 0; // compose interim results for (int i = 0; i < length; i++) { BasicD q = bdList[i]; int index = i + 1; ConnorsRsiResult r = new() { Date = q.Date, RsiClose = rsiResults[i].Rsi }; results.Add(r); // bypass for first record if (lastClose == null) { lastClose = q.Value; continue; } // streak of up or down if (q.Value == lastClose) { streak = 0; } else if (q.Value > lastClose) { if (streak >= 0) { streak++; } else { streak = 1; } } else // h.Value < lastClose { if (streak <= 0) { streak--; } else { streak = -1; } } r.Streak = streak; // percentile rank gain[i] = (lastClose <= 0) ? null : (q.Value - lastClose) / lastClose; if (index > rankPeriods) { int qty = 0; for (int p = index - rankPeriods - 1; p < index; p++) { if (gain[p] < gain[i]) { qty++; } } r.PercentRank = 100 * qty / rankPeriods; } lastClose = q.Value; } return(results); }
// TRUE STRENGTH INDEX (TSI) /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <TsiResult> GetTsi <TQuote>( this IEnumerable <TQuote> quotes, int lookbackPeriods = 25, int smoothPeriods = 13, int signalPeriods = 7) where TQuote : IQuote { // convert quotes List <BasicD> bdList = quotes.ConvertToBasic(CandlePart.Close); // check parameter arguments ValidateTsi(lookbackPeriods, smoothPeriods, signalPeriods); // initialize int length = bdList.Count; double mult1 = 2d / (lookbackPeriods + 1); double mult2 = 2d / (smoothPeriods + 1); double multS = 2d / (signalPeriods + 1); double?sumS = 0; List <TsiResult> results = new(length); double[] c = new double[length]; // price change double[] cs1 = new double[length]; // smooth 1 double[] cs2 = new double[length]; // smooth 2 double sumC = 0; double sumC1 = 0; double[] a = new double[length]; // abs of price change double[] as1 = new double[length]; // smooth 1 double[] as2 = new double[length]; // smooth 2 double sumA = 0; double sumA1 = 0; // roll through quotes for (int i = 0; i < length; i++) { BasicD q = bdList[i]; int index = i + 1; TsiResult r = new() { Date = q.Date }; results.Add(r); // skip first period if (i == 0) { continue; } // price change c[i] = q.Value - bdList[i - 1].Value; a[i] = Math.Abs(c[i]); // smoothing if (index > lookbackPeriods + 1) { // first smoothing cs1[i] = ((c[i] - cs1[i - 1]) * mult1) + cs1[i - 1]; as1[i] = ((a[i] - as1[i - 1]) * mult1) + as1[i - 1]; // second smoothing if (index > lookbackPeriods + smoothPeriods) { cs2[i] = ((cs1[i] - cs2[i - 1]) * mult2) + cs2[i - 1]; as2[i] = ((as1[i] - as2[i - 1]) * mult2) + as2[i - 1]; r.Tsi = (as2[i] != 0) ? 100d * (cs2[i] / as2[i]) : null; // signal line if (signalPeriods > 0) { if (index >= lookbackPeriods + smoothPeriods + signalPeriods) { r.Signal = ((r.Tsi - results[i - 1].Signal) * multS) + results[i - 1].Signal; } // initialize signal else { sumS += r.Tsi; if (index == lookbackPeriods + smoothPeriods + signalPeriods - 1) { r.Signal = sumS / signalPeriods; } } } } // prepare second smoothing else { sumC1 += cs1[i]; sumA1 += as1[i]; // inialize second smoothing if (index == lookbackPeriods + smoothPeriods) { cs2[i] = sumC1 / smoothPeriods; as2[i] = sumA1 / smoothPeriods; r.Tsi = (as2[i] != 0) ? 100 * cs2[i] / as2[i] : null; sumS = r.Tsi; } } } // prepare first smoothing else { sumC += c[i]; sumA += a[i]; // initialize first smoothing if (index == lookbackPeriods + 1) { cs1[i] = sumC / lookbackPeriods; as1[i] = sumA / lookbackPeriods; sumC1 = cs1[i]; sumA1 = as1[i]; } } } return(results); }
// internals private static List <RsiResult> CalcRsi(List <BasicD> bdList, int lookbackPeriods) { // check parameter arguments ValidateRsi(lookbackPeriods); // initialize int length = bdList.Count; double avgGain = 0; double avgLoss = 0; List <RsiResult> results = new(length); double[] gain = new double[length]; // gain double[] loss = new double[length]; // loss double lastValue; if (length == 0) { return(results); } else { lastValue = bdList[0].Value; } // roll through quotes for (int i = 0; i < bdList.Count; i++) { BasicD h = bdList[i]; int index = i + 1; RsiResult r = new() { Date = h.Date }; results.Add(r); gain[i] = (h.Value > lastValue) ? h.Value - lastValue : 0; loss[i] = (h.Value < lastValue) ? lastValue - h.Value : 0; lastValue = h.Value; // calculate RSI if (index > lookbackPeriods + 1) { avgGain = ((avgGain * (lookbackPeriods - 1)) + gain[i]) / lookbackPeriods; avgLoss = ((avgLoss * (lookbackPeriods - 1)) + loss[i]) / lookbackPeriods; if (avgLoss > 0) { double rs = avgGain / avgLoss; r.Rsi = 100 - (100 / (1 + rs)); } else { r.Rsi = 100; } } // initialize average gain else if (index == lookbackPeriods + 1) { double sumGain = 0; double sumLoss = 0; for (int p = 1; p <= lookbackPeriods; p++) { sumGain += gain[p]; sumLoss += loss[p]; } avgGain = sumGain / lookbackPeriods; avgLoss = sumLoss / lookbackPeriods; r.Rsi = (avgLoss > 0) ? 100 - (100 / (1 + (avgGain / avgLoss))) : 100; } } return(results); }
// ARNAUD LEGOUX MOVING AVERAGE /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <AlmaResult> GetAlma <TQuote>( this IEnumerable <TQuote> quotes, int lookbackPeriods = 9, double offset = 0.85, double sigma = 6) where TQuote : IQuote { // convert quotes List <BasicD> bdList = quotes.ConvertToBasic(CandlePart.Close); // check parameter arguments ValidateAlma(lookbackPeriods, offset, sigma); // initialize List <AlmaResult> results = new(bdList.Count); // determine price weights double m = offset * (lookbackPeriods - 1); double s = lookbackPeriods / sigma; double[] weight = new double[lookbackPeriods]; double norm = 0; for (int i = 0; i < lookbackPeriods; i++) { double wt = Math.Exp(-((i - m) * (i - m)) / (2 * s * s)); weight[i] = wt; norm += wt; } // roll through quotes for (int i = 0; i < bdList.Count; i++) { BasicD q = bdList[i]; int index = i + 1; AlmaResult r = new() { Date = q.Date }; if (index >= lookbackPeriods) { double weightedSum = 0; int n = 0; for (int p = index - lookbackPeriods; p < index; p++) { BasicD d = bdList[p]; weightedSum += weight[n] * d.Value; n++; } r.Alma = (decimal)(weightedSum / norm); } results.Add(r); } return(results); }
// SLOPE AND LINEAR REGRESSION /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <SlopeResult> GetSlope <TQuote>( this IEnumerable <TQuote> quotes, int lookbackPeriods) where TQuote : IQuote { // convert quotes List <BasicD> bdList = quotes.ConvertToBasic(CandlePart.Close); // check parameter arguments ValidateSlope(lookbackPeriods); // initialize int length = bdList.Count; List <SlopeResult> results = new(length); // roll through quotes for (int i = 0; i < length; i++) { BasicD q = bdList[i]; int index = i + 1; SlopeResult r = new() { Date = q.Date }; results.Add(r); // skip initialization period if (index < lookbackPeriods) { continue; } // get averages for period double sumX = 0; double sumY = 0; for (int p = index - lookbackPeriods; p < index; p++) { BasicD d = bdList[p]; sumX += p + 1d; sumY += d.Value; } double avgX = sumX / lookbackPeriods; double avgY = sumY / lookbackPeriods; // least squares method double sumSqX = 0; double sumSqY = 0; double sumSqXY = 0; for (int p = index - lookbackPeriods; p < index; p++) { BasicD d = bdList[p]; double devX = p + 1d - avgX; double devY = d.Value - avgY; sumSqX += devX * devX; sumSqY += devY * devY; sumSqXY += devX * devY; } r.Slope = sumSqXY / sumSqX; r.Intercept = avgY - (r.Slope * avgX); // calculate Standard Deviation and R-Squared double stdDevX = Math.Sqrt((double)sumSqX / lookbackPeriods); double stdDevY = Math.Sqrt((double)sumSqY / lookbackPeriods); r.StdDev = stdDevY; if (stdDevX * stdDevY != 0) { double arrr = (double)sumSqXY / (stdDevX * stdDevY) / lookbackPeriods; r.RSquared = arrr * arrr; } } // add last Line (y = mx + b) if (length >= lookbackPeriods) { SlopeResult last = results.LastOrDefault(); for (int p = length - lookbackPeriods; p < length; p++) { SlopeResult d = results[p]; d.Line = (decimal?)((last.Slope * (p + 1)) + last.Intercept); } } return(results); }
// HILBERT TRANSFORM - INSTANTANEOUS TRENDLINE (HTL) /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <HtlResult> GetHtTrendline <TQuote>( this IEnumerable <TQuote> quotes) where TQuote : IQuote { // convert quotes List <BasicD> bdList = quotes.ConvertToBasic(CandlePart.HL2); // initialize int length = bdList.Count; List <HtlResult> results = new(length); double[] pr = new double[length]; // price double[] sp = new double[length]; // smooth price double[] dt = new double[length]; // detrender double[] pd = new double[length]; // period double[] q1 = new double[length]; // quadrature double[] i1 = new double[length]; // in-phase double jI; double jQ; double[] q2 = new double[length]; // adj. quadrature double[] i2 = new double[length]; // adj. in-phase double[] re = new double[length]; double[] im = new double[length]; double[] sd = new double[length]; // smooth period double[] it = new double[length]; // instantaneous trend (raw) // roll through quotes for (int i = 0; i < length; i++) { BasicD q = bdList[i]; pr[i] = q.Value; HtlResult r = new() { Date = q.Date, }; if (i > 5) { double adj = (0.075 * pd[i - 1]) + 0.54; // smooth and detrender sp[i] = ((4 * pr[i]) + (3 * pr[i - 1]) + (2 * pr[i - 2]) + pr[i - 3]) / 10; dt[i] = ((0.0962 * sp[i]) + (0.5769 * sp[i - 2]) - (0.5769 * sp[i - 4]) - (0.0962 * sp[i - 6])) * adj; // in-phase and quadrature q1[i] = ((0.0962 * dt[i]) + (0.5769 * dt[i - 2]) - (0.5769 * dt[i - 4]) - (0.0962 * dt[i - 6])) * adj; i1[i] = dt[i - 3]; // advance the phases by 90 degrees jI = ((0.0962 * i1[i]) + (0.5769 * i1[i - 2]) - (0.5769 * i1[i - 4]) - (0.0962 * i1[i - 6])) * adj; jQ = ((0.0962 * q1[i]) + (0.5769 * q1[i - 2]) - (0.5769 * q1[i - 4]) - (0.0962 * q1[i - 6])) * adj; // phasor addition for 3-bar averaging i2[i] = i1[i] - jQ; q2[i] = q1[i] + jI; i2[i] = (0.2 * i2[i]) + (0.8 * i2[i - 1]); // smoothing it q2[i] = (0.2 * q2[i]) + (0.8 * q2[i - 1]); // homodyne discriminator re[i] = (i2[i] * i2[i - 1]) + (q2[i] * q2[i - 1]); im[i] = (i2[i] * q2[i - 1]) - (q2[i] * i2[i - 1]); re[i] = (0.2 * re[i]) + (0.8 * re[i - 1]); // smoothing it im[i] = (0.2 * im[i]) + (0.8 * im[i - 1]); // calculate period if (im[i] != 0 && re[i] != 0) { pd[i] = 2 * Math.PI / Math.Atan(im[i] / re[i]); } // adjust period to thresholds pd[i] = (pd[i] > 1.5 * pd[i - 1]) ? 1.5 * pd[i - 1] : pd[i]; pd[i] = (pd[i] < 0.67 * pd[i - 1]) ? 0.67 * pd[i - 1] : pd[i]; pd[i] = (pd[i] < 6d) ? 6d : pd[i]; pd[i] = (pd[i] > 50d) ? 50d : pd[i]; // smooth the period pd[i] = (0.2 * pd[i]) + (0.8 * pd[i - 1]); sd[i] = (0.33 * pd[i]) + (0.67 * sd[i - 1]); // smooth dominant cycle period int dcPeriods = (int)(sd[i] + 0.5); double sumPr = 0; for (int d = i - dcPeriods + 1; d <= i; d++) { if (d >= 0) { sumPr += pr[d]; } // handle insufficient lookback quotes (trim scope) else { dcPeriods--; } } it[i] = dcPeriods > 0 ? sumPr / dcPeriods : pr[i]; // final indicators r.Trendline = i >= 11 // 12th bar ? (decimal)((4 * it[i]) + (3 * it[i - 1]) + (2 * it[i - 2]) + it[i - 3]) / 10m : (decimal)pr[i]; r.SmoothPrice = (decimal)((4 * pr[i]) + (3 * pr[i - 1]) + (2 * pr[i - 2]) + pr[i - 3]) / 10m; } // initialization period else { r.Trendline = (decimal)pr[i]; r.SmoothPrice = null; pd[i] = 0; sp[i] = 0; dt[i] = 0; i1[i] = 0; q1[i] = 0; i2[i] = 0; q2[i] = 0; re[i] = 0; im[i] = 0; sd[i] = 0; } results.Add(r); } return(results); }
// PRICE RELATIVE STRENGTH /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <PrsResult> GetPrs <TQuote>( this IEnumerable <TQuote> historyBase, IEnumerable <TQuote> historyEval, int?lookbackPeriods = null, int?smaPeriods = null) where TQuote : IQuote { // convert quotes List <BasicD> bdBaseList = historyBase.ConvertToBasic(CandlePart.Close); List <BasicD> bdEvalList = historyEval.ConvertToBasic(CandlePart.Close); // check parameter arguments ValidatePriceRelative(historyBase, historyEval, lookbackPeriods, smaPeriods); // initialize List <PrsResult> results = new(bdEvalList.Count); // roll through quotes for (int i = 0; i < bdEvalList.Count; i++) { BasicD bi = bdBaseList[i]; BasicD ei = bdEvalList[i]; int index = i + 1; if (ei.Date != bi.Date) { throw new InvalidQuotesException(nameof(historyEval), ei.Date, "Date sequence does not match. Price Relative requires matching dates in provided histories."); } PrsResult r = new() { Date = ei.Date, Prs = (bi.Value == 0) ? null : (ei.Value / bi.Value) // relative strength ratio }; results.Add(r); if (lookbackPeriods != null && index > lookbackPeriods) { BasicD bo = bdBaseList[i - (int)lookbackPeriods]; BasicD eo = bdEvalList[i - (int)lookbackPeriods]; if (bo.Value != 0 && eo.Value != 0) { double pctB = (bi.Value - bo.Value) / bo.Value; double pctE = (ei.Value - eo.Value) / eo.Value; r.PrsPercent = pctE - pctB; } } // optional moving average of PRS if (smaPeriods != null && index >= smaPeriods) { double?sumRs = 0; for (int p = index - (int)smaPeriods; p < index; p++) { PrsResult d = results[p]; sumRs += d.Prs; } r.PrsSma = sumRs / smaPeriods; } } return(results); }