// AROON OSCILLATOR /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <AroonResult> GetAroon <TQuote>( this IEnumerable <TQuote> quotes, int lookbackPeriods = 25) where TQuote : IQuote { // convert quotes List <QuoteD> quotesList = quotes.ConvertToList(); // check parameter arguments ValidateAroon(lookbackPeriods); // initialize List <AroonResult> results = new(quotesList.Count); // roll through quotes for (int i = 0; i < quotesList.Count; i++) { QuoteD q = quotesList[i]; int index = i + 1; AroonResult result = new() { Date = q.Date }; // add aroons if (index > lookbackPeriods) { double lastHighPrice = 0; double lastLowPrice = double.MaxValue; int lastHighIndex = 0; int lastLowIndex = 0; for (int p = index - lookbackPeriods - 1; p < index; p++) { QuoteD d = quotesList[p]; if (d.High > lastHighPrice) { lastHighPrice = d.High; lastHighIndex = p + 1; } if (d.Low < lastLowPrice) { lastLowPrice = d.Low; lastLowIndex = p + 1; } } result.AroonUp = 100 * (decimal)(lookbackPeriods - (index - lastHighIndex)) / lookbackPeriods; result.AroonDown = 100 * (decimal)(lookbackPeriods - (index - lastLowIndex)) / lookbackPeriods; result.Oscillator = result.AroonUp - result.AroonDown; } results.Add(result); } return(results); }
// ON-BALANCE VOLUME /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <ObvResult> GetObv <TQuote>( this IEnumerable <TQuote> quotes, int?smaPeriods = null) where TQuote : IQuote { // convert quotes List <QuoteD> quotesList = quotes.ConvertToList(); // check parameter arguments ValidateObv(smaPeriods); // initialize List <ObvResult> results = new(quotesList.Count); double?prevClose = null; double obv = 0; // roll through quotes for (int i = 0; i < quotesList.Count; i++) { QuoteD q = quotesList[i]; int index = i + 1; if (prevClose == null || q.Close == prevClose) { // no change to OBV } else if (q.Close > prevClose) { obv += q.Volume; } else if (q.Close < prevClose) { obv -= q.Volume; } ObvResult result = new() { Date = q.Date, Obv = obv }; results.Add(result); prevClose = q.Close; // optional SMA if (smaPeriods != null && index > smaPeriods) { double sumSma = 0; for (int p = index - (int)smaPeriods; p < index; p++) { sumSma += results[p].Obv; } result.ObvSma = sumSma / smaPeriods; } } return(results); }
// COMMODITY CHANNEL INDEX /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <CciResult> GetCci <TQuote>( this IEnumerable <TQuote> quotes, int lookbackPeriods = 20) where TQuote : IQuote { // convert quotes List <QuoteD> quotesList = quotes.ConvertToList(); // check parameter arguments ValidateCci(lookbackPeriods); // initialize List <CciResult> results = new(quotesList.Count); // roll through quotes for (int i = 0; i < quotesList.Count; i++) { QuoteD q = quotesList[i]; int index = i + 1; CciResult result = new() { Date = q.Date, Tp = (q.High + q.Low + q.Close) / 3 }; results.Add(result); if (index >= lookbackPeriods) { // average TP over lookback double avgTp = 0; for (int p = index - lookbackPeriods; p < index; p++) { CciResult d = results[p]; avgTp += (double)d.Tp; } avgTp /= lookbackPeriods; // average Deviation over lookback double avgDv = 0; for (int p = index - lookbackPeriods; p < index; p++) { CciResult d = results[p]; avgDv += Math.Abs(avgTp - (double)d.Tp); } avgDv /= lookbackPeriods; result.Cci = (avgDv == 0) ? null : (result.Tp - avgTp) / (0.015 * avgDv); } } return(results); }
// VOLUME WEIGHTED AVERAGE PRICE /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <VwapResult> GetVwap <TQuote>( this IEnumerable <TQuote> quotes, DateTime?startDate = null) where TQuote : IQuote { // convert quotes List <QuoteD> quotesList = quotes.ConvertToList(); // check parameter arguments ValidateVwap(quotesList, startDate); // initialize int length = quotesList.Count; List <VwapResult> results = new(length); if (length == 0) { return(results); } startDate = (startDate == null) ? quotesList[0].Date : startDate; double?cumVolume = 0; double?cumVolumeTP = 0; // roll through quotes for (int i = 0; i < length; i++) { QuoteD q = quotesList[i]; double?v = q.Volume; double?h = q.High; double?l = q.Low; double?c = q.Close; VwapResult r = new() { Date = q.Date }; if (q.Date >= startDate) { cumVolume += v; cumVolumeTP += v * (h + l + c) / 3; r.Vwap = (cumVolume != 0) ? (decimal?)(cumVolumeTP / cumVolume) : null; } results.Add(r); } return(results); }
// ACCUMULATION/DISTRIBUTION LINE /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <AdlResult> GetAdl <TQuote>( this IEnumerable <TQuote> quotes, int?smaPeriods = null) where TQuote : IQuote { // convert quotes List <QuoteD> quotesList = quotes.ConvertToList(); // check parameter arguments ValidateAdl(smaPeriods); // initialize List <AdlResult> results = new(quotesList.Count); double prevAdl = 0; // roll through quotes for (int i = 0; i < quotesList.Count; i++) { QuoteD q = quotesList[i]; int index = i + 1; double mfm = (q.High == q.Low) ? 0 : (q.Close - q.Low - (q.High - q.Close)) / (q.High - q.Low); double mfv = mfm * q.Volume; double adl = mfv + prevAdl; AdlResult result = new() { Date = q.Date, MoneyFlowMultiplier = mfm, MoneyFlowVolume = mfv, Adl = adl }; results.Add(result); prevAdl = adl; // optional SMA if (smaPeriods != null && index >= smaPeriods) { double sumSma = 0; for (int p = index - (int)smaPeriods; p < index; p++) { sumSma += results[p].Adl; } result.AdlSma = sumSma / smaPeriods; } } return(results); }
// VOLUME WEIGHTED MOVING AVERAGE /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <VwmaResult> GetVwma <TQuote>( this IEnumerable <TQuote> quotes, int lookbackPeriods) where TQuote : IQuote { // convert quotes List <QuoteD> quotesList = quotes.ConvertToList(); // check parameter arguments ValidateVwma(lookbackPeriods); // initialize int length = quotesList.Count; List <VwmaResult> results = new(length); // roll through quotes for (int i = 0; i < length; i++) { QuoteD q = quotesList[i]; int index = i + 1; VwmaResult result = new() { Date = q.Date }; if (index >= lookbackPeriods) { double?sumCl = 0; double?sumVl = 0; for (int p = index - lookbackPeriods; p < index; p++) { QuoteD d = quotesList[p]; double?c = d.Close; double?v = d.Volume; sumCl += c * v; sumVl += v; } result.Vwma = sumVl != 0 ? (decimal?)(sumCl / sumVl) : null; } results.Add(result); } return(results); }
// CHANDELIER EXIT /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <ChandelierResult> GetChandelier <TQuote>( this IEnumerable <TQuote> quotes, int lookbackPeriods = 22, double multiplier = 3, ChandelierType type = ChandelierType.Long) where TQuote : IQuote { // convert quotes List <QuoteD> quotesList = quotes.ConvertToList(); // check parameter arguments ValidateChandelier(lookbackPeriods, multiplier); // initialize List <ChandelierResult> results = new(quotesList.Count); List <AtrResult> atrResult = GetAtr(quotes, lookbackPeriods).ToList(); // uses ATR // roll through quotes for (int i = 0; i < quotesList.Count; i++) { QuoteD q = quotesList[i]; int index = i + 1; ChandelierResult result = new() { Date = q.Date }; // add exit values if (index >= lookbackPeriods) { double?atr = (double?)atrResult[i].Atr; switch (type) { case ChandelierType.Long: double maxHigh = 0; for (int p = index - lookbackPeriods; p < index; p++) { QuoteD d = quotesList[p]; if (d.High > maxHigh) { maxHigh = d.High; } } result.ChandelierExit = (decimal?)(maxHigh - (atr * multiplier)); break; case ChandelierType.Short: double minLow = double.MaxValue; for (int p = index - lookbackPeriods; p < index; p++) { QuoteD d = quotesList[p]; if (d.Low < minLow) { minLow = d.Low; } } result.ChandelierExit = (decimal?)(minLow + (atr * multiplier)); break; default: break; } } results.Add(result); } return(results); }
// ULTIMATE OSCILLATOR /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <UltimateResult> GetUltimate <TQuote>( this IEnumerable <TQuote> quotes, int shortPeriods = 7, int middlePeriods = 14, int longPeriods = 28) where TQuote : IQuote { // convert quotes List <QuoteD> quotesList = quotes.ConvertToList(); // check parameter arguments ValidateUltimate(shortPeriods, middlePeriods, longPeriods); // initialize int length = quotesList.Count; List <UltimateResult> results = new(length); double[] bp = new double[length]; // buying pressure double[] tr = new double[length]; // true range double priorClose = 0; // roll through quotes for (int i = 0; i < quotesList.Count; i++) { QuoteD q = quotesList[i]; int index = i + 1; UltimateResult r = new() { Date = q.Date }; results.Add(r); if (i > 0) { bp[i] = q.Close - Math.Min(q.Low, priorClose); tr[i] = Math.Max(q.High, priorClose) - Math.Min(q.Low, priorClose); } if (index >= longPeriods + 1) { double sumBP1 = 0; double sumBP2 = 0; double sumBP3 = 0; double sumTR1 = 0; double sumTR2 = 0; double sumTR3 = 0; for (int p = index - longPeriods; p < index; p++) { int pIndex = p + 1; // short aggregate if (pIndex > index - shortPeriods) { sumBP1 += bp[p]; sumTR1 += tr[p]; } // middle aggregate if (pIndex > index - middlePeriods) { sumBP2 += bp[p]; sumTR2 += tr[p]; } // long aggregate sumBP3 += bp[p]; sumTR3 += tr[p]; } double?avg1 = (sumTR1 == 0) ? null : sumBP1 / sumTR1; double?avg2 = (sumTR2 == 0) ? null : sumBP2 / sumTR2; double?avg3 = (sumTR3 == 0) ? null : sumBP3 / sumTR3; r.Ultimate = (decimal?)(100d * ((4d * avg1) + (2d * avg2) + avg3) / 7d); } priorClose = q.Close; } return(results); }
// MONEY FLOW INDEX /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <MfiResult> GetMfi <TQuote>( this IEnumerable <TQuote> quotes, int lookbackPeriods = 14) where TQuote : IQuote { // convert quotes List <QuoteD> quotesList = quotes.ConvertToList(); // check parameter arguments ValidateMfi(lookbackPeriods); // initialize int length = quotesList.Count; List <MfiResult> results = new(length); double[] tp = new double[length]; // true price double[] mf = new double[length]; // raw MF value int[] direction = new int[length]; // direction double?prevTP = null; // roll through quotes, to get preliminary data for (int i = 0; i < quotesList.Count; i++) { QuoteD q = quotesList[i]; MfiResult result = new() { Date = q.Date }; // true price tp[i] = (q.High + q.Low + q.Close) / 3; // raw money flow mf[i] = tp[i] * q.Volume; // direction if (prevTP == null || tp[i] == prevTP) { direction[i] = 0; } else if (tp[i] > prevTP) { direction[i] = 1; } else if (tp[i] < prevTP) { direction[i] = -1; } results.Add(result); prevTP = tp[i]; } // add money flow index for (int i = lookbackPeriods; i < results.Count; i++) { MfiResult r = results[i]; int index = i + 1; double sumPosMFs = 0; double sumNegMFs = 0; for (int p = index - lookbackPeriods; p < index; p++) { if (direction[p] == 1) { sumPosMFs += mf[p]; } else if (direction[p] == -1) { sumNegMFs += mf[p]; } } // handle no negative case if (sumNegMFs == 0) { r.Mfi = 100; continue; } // calculate MFI normally decimal mfRatio = (decimal)(sumPosMFs / sumNegMFs); r.Mfi = 100 - (100 / (1 + mfRatio)); } return(results); }
// STOCHASTIC MOMENTUM INDEX /// <include file='./info.xml' path='indicator/type[@name="Main"]/*' /> /// public static IEnumerable <SmiResult> GetSmi <TQuote>( this IEnumerable <TQuote> quotes, int lookbackPeriods, int firstSmoothPeriods, int secondSmoothPeriods, int signalPeriods = 3) where TQuote : IQuote { // convert quotes List <QuoteD> quotesList = quotes.ConvertToList(); // check parameter arguments ValidateSmi( lookbackPeriods, firstSmoothPeriods, secondSmoothPeriods, signalPeriods); // initialize int length = quotesList.Count; List <SmiResult> results = new(length); double k1 = 2d / (firstSmoothPeriods + 1); double k2 = 2d / (secondSmoothPeriods + 1); double kS = 2d / (signalPeriods + 1); double lastSmEma1 = 0; double lastSmEma2 = 0; double lastHlEma1 = 0; double lastHlEma2 = 0; double lastSignal = 0; // roll through quotes for (int i = 0; i < length; i++) { QuoteD q = quotesList[i]; int index = i + 1; SmiResult r = new() { Date = q.Date }; if (index >= lookbackPeriods) { double hH = double.MinValue; double lL = double.MaxValue; for (int p = index - lookbackPeriods; p < index; p++) { QuoteD x = quotesList[p]; if (x.High > hH) { hH = x.High; } if (x.Low < lL) { lL = x.Low; } } double sm = q.Close - (0.5d * (hH + lL)); double hl = hH - lL; // initialize last EMA values if (index == lookbackPeriods) { lastSmEma1 = sm; lastSmEma2 = lastSmEma1; lastHlEma1 = hl; lastHlEma2 = lastHlEma1; } // first smoothing double smEma1 = lastSmEma1 + (k1 * (sm - lastSmEma1)); double hlEma1 = lastHlEma1 + (k1 * (hl - lastHlEma1)); // second smoothing double smEma2 = lastSmEma2 + (k2 * (smEma1 - lastSmEma2)); double hlEma2 = lastHlEma2 + (k2 * (hlEma1 - lastHlEma2)); // stochastic momentum index double smi = 100 * (smEma2 / (0.5 * hlEma2)); r.Smi = (decimal)smi; // initialize signal line if (index == lookbackPeriods) { lastSignal = smi; } // signal line double signal = lastSignal + (kS * (smi - lastSignal)); r.Signal = (decimal)signal; // carryover values lastSmEma1 = smEma1; lastSmEma2 = smEma2; lastHlEma1 = hlEma1; lastHlEma2 = hlEma2; lastSignal = signal; } results.Add(r); } return(results); }
// KLINGER VOLUME OSCILLATOR /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <KvoResult> GetKvo <TQuote>( this IEnumerable <TQuote> quotes, int fastPeriods = 34, int slowPeriods = 55, int signalPeriods = 13) where TQuote : IQuote { // convert quotes List <QuoteD> quotesList = quotes.ConvertToList(); // check parameter arguments ValidateKlinger(fastPeriods, slowPeriods, signalPeriods); // initialize int length = quotesList.Count; List <KvoResult> results = new(length); double[] hlc = new double[length]; // trend basis double[] t = new double[length]; // trend direction double[] dm = new double[length]; // daily measurement double[] cm = new double[length]; // cumulative measurement double?[] vf = new double?[length]; // volume force (VF) double?[] vfFastEma = new double?[length]; // EMA of VF (short-term) double?[] vfSlowEma = new double?[length]; // EMA of VP (long-term) // EMA multipliers double kFast = 2d / (fastPeriods + 1); double kSlow = 2d / (slowPeriods + 1); double kSignal = 2d / (signalPeriods + 1); // roll through quotes for (int i = 0; i < length; i++) { QuoteD q = quotesList[i]; int index = i + 1; KvoResult r = new() { Date = q.Date }; results.Add(r); // trend basis comparator hlc[i] = q.High + q.Low + q.Close; // daily measurement dm[i] = q.High - q.Low; if (i <= 0) { continue; } // trend direction t[i] = (hlc[i] > hlc[i - 1]) ? 1 : -1; if (i <= 1) { cm[i] = 0; continue; } // cumulative measurement cm[i] = (t[i] == t[i - 1]) ? (cm[i - 1] + dm[i]) : (dm[i - 1] + dm[i]); // volume force (VF) vf[i] = (dm[i] == cm[i] || q.Volume == 0) ? 0 : (dm[i] == 0) ? q.Volume * 2d * t[i] * 100d : (cm[i] != 0) ? q.Volume * Math.Abs(2d * ((dm[i] / cm[i]) - 1)) * t[i] * 100d : vf[i - 1]; // fast-period EMA of VF if (index > fastPeriods + 2) { vfFastEma[i] = (vf[i] * kFast) + (vfFastEma[i - 1] * (1 - kFast)); } else if (index == fastPeriods + 2) { double?sum = 0; for (int p = 2; p <= i; p++) { sum += vf[p]; } vfFastEma[i] = sum / fastPeriods; } // slow-period EMA of VF if (index > slowPeriods + 2) { vfSlowEma[i] = (vf[i] * kSlow) + (vfSlowEma[i - 1] * (1 - kSlow)); } else if (index == slowPeriods + 2) { double?sum = 0; for (int p = 2; p <= i; p++) { sum += vf[p]; } vfSlowEma[i] = sum / slowPeriods; } // Klinger Oscillator if (index >= slowPeriods + 2) { r.Oscillator = vfFastEma[i] - vfSlowEma[i]; // Signal if (index > slowPeriods + signalPeriods + 1) { r.Signal = (r.Oscillator * kSignal) + (results[i - 1].Signal * (1 - kSignal)); } else if (index == slowPeriods + signalPeriods + 1) { double?sum = 0; for (int p = slowPeriods + 1; p <= i; p++) { sum += results[p].Oscillator; } r.Signal = sum / signalPeriods; } } } return(results); }
/// <include file='./info.xml' path='indicator/type[@name="Extended"]/*' /> /// public static IEnumerable <StochResult> GetStoch <TQuote>( this IEnumerable <TQuote> quotes, int lookbackPeriods, int signalPeriods, int smoothPeriods, decimal kFactor, decimal dFactor, MaType movingAverageType) where TQuote : IQuote { // convert quotes List <QuoteD> quotesList = quotes.ConvertToList(); // check parameter arguments ValidateStoch( lookbackPeriods, signalPeriods, smoothPeriods, kFactor, dFactor, movingAverageType); // initialize int length = quotesList.Count; List <StochResult> results = new(length); // roll through quotes for (int i = 0; i < length; i++) { QuoteD q = quotesList[i]; int index = i + 1; StochResult result = new() { Date = q.Date }; if (index >= lookbackPeriods) { double?highHigh = double.MinValue; double?lowLow = double.MaxValue; for (int p = index - lookbackPeriods; p < index; p++) { QuoteD x = quotesList[p]; if (x.High > highHigh) { highHigh = x.High; } if (x.Low < lowLow) { lowLow = x.Low; } } result.Oscillator = lowLow != highHigh ? 100 * (decimal?)((q.Close - lowLow) / (highHigh - lowLow)) : 0; } results.Add(result); } // smooth the oscillator if (smoothPeriods > 1) { results = SmoothOscillator( results, length, lookbackPeriods, smoothPeriods, movingAverageType); } // handle insufficient length if (length < lookbackPeriods - 1) { return(results); } // signal (%D) and %J int signalIndex = lookbackPeriods + smoothPeriods + signalPeriods - 2; double?s = (double?)results[lookbackPeriods - 1].Oscillator; for (int i = lookbackPeriods - 1; i < length; i++) { StochResult r = results[i]; int index = i + 1; // add signal if (signalPeriods <= 1) { r.Signal = r.Oscillator; } // SMA case else if (index >= signalIndex && movingAverageType is MaType.SMA) { double?sumOsc = 0; for (int p = index - signalPeriods; p < index; p++) { StochResult x = results[p]; sumOsc += (double?)x.Oscillator; } r.Signal = (decimal?)(sumOsc / signalPeriods); } // SMMA case else if (i >= lookbackPeriods - 1 && movingAverageType is MaType.SMMA) { s = (s == null) ? (double?)results[i].Oscillator : s; // reset if null s = ((s * (signalPeriods - 1)) + (double?)results[i].Oscillator) / signalPeriods; r.Signal = (decimal?)s; } // %J r.PercentJ = (kFactor * r.Oscillator) - (dFactor * r.Signal); } return(results); }
// VORTEX INDICATOR /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <VortexResult> GetVortex <TQuote>( this IEnumerable <TQuote> quotes, int lookbackPeriods) where TQuote : IQuote { // convert quotes List <QuoteD> quotesList = quotes.ConvertToList(); // check parameter arguments ValidateVortex(lookbackPeriods); // initialize int length = quotesList.Count; List <VortexResult> results = new(length); double[] tr = new double[length]; double[] pvm = new double[length]; double[] nvm = new double[length]; double prevHigh = 0; double prevLow = 0; double prevClose = 0; // roll through quotes for (int i = 0; i < length; i++) { QuoteD q = quotesList[i]; int index = i + 1; VortexResult result = new() { Date = q.Date }; // skip first period if (index == 1) { results.Add(result); prevHigh = q.High; prevLow = q.Low; prevClose = q.Close; continue; } // trend information double highMinusPrevClose = Math.Abs(q.High - prevClose); double lowMinusPrevClose = Math.Abs(q.Low - prevClose); tr[i] = Math.Max(q.High - q.Low, Math.Max(highMinusPrevClose, lowMinusPrevClose)); pvm[i] = Math.Abs(q.High - prevLow); nvm[i] = Math.Abs(q.Low - prevHigh); prevHigh = q.High; prevLow = q.Low; prevClose = q.Close; // vortex indicator if (index > lookbackPeriods) { double sumTr = 0; double sumPvm = 0; double sumNvm = 0; for (int p = index - lookbackPeriods; p < index; p++) { sumTr += tr[p]; sumPvm += pvm[p]; sumNvm += nvm[p]; } if (sumTr is not 0) { result.Pvi = sumPvm / sumTr; result.Nvi = sumNvm / sumTr; } } results.Add(result); } return(results); }
// FORCE INDEX /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <ForceIndexResult> GetForceIndex <TQuote>( this IEnumerable <TQuote> quotes, int lookbackPeriods) where TQuote : IQuote { // convert quotes List <QuoteD> quotesList = quotes.ConvertToList(); // check parameter arguments ValidateForceIndex(lookbackPeriods); // initialize int length = quotesList.Count; List <ForceIndexResult> results = new(length); double?prevClose = null, prevFI = null, sumRawFI = 0; double k = 2d / (lookbackPeriods + 1); // roll through quotes for (int i = 0; i < length; i++) { QuoteD q = quotesList[i]; int index = i + 1; ForceIndexResult r = new() { Date = q.Date }; results.Add(r); // skip first period if (i == 0) { prevClose = q.Close; continue; } // raw Force Index double?rawFI = q.Volume * (q.Close - prevClose); prevClose = q.Close; // calculate EMA if (index > lookbackPeriods + 1) { r.ForceIndex = prevFI + (k * (rawFI - prevFI)); } // initialization period else { sumRawFI += rawFI; // first EMA value if (index == lookbackPeriods + 1) { r.ForceIndex = sumRawFI / lookbackPeriods; } } prevFI = r.ForceIndex; } return(results); }
// SUPERTREND /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <SuperTrendResult> GetSuperTrend <TQuote>( this IEnumerable <TQuote> quotes, int lookbackPeriods = 10, double multiplier = 3) where TQuote : IQuote { // convert quotes List <QuoteD> quotesList = quotes.ConvertToList(); // check parameter arguments ValidateSuperTrend(lookbackPeriods, multiplier); // initialize List <SuperTrendResult> results = new(quotesList.Count); List <AtrResult> atrResults = GetAtr(quotes, lookbackPeriods).ToList(); bool isBullish = true; double?upperBand = null; double?lowerBand = null; // roll through quotes for (int i = 0; i < quotesList.Count; i++) { QuoteD q = quotesList[i]; SuperTrendResult r = new() { Date = q.Date }; if (i >= lookbackPeriods - 1) { double mid = (q.High + q.Low) / 2; double atr = (double)atrResults[i].Atr; double prevClose = quotesList[i - 1].Close; // potential bands double upperEval = mid + (multiplier * atr); double lowerEval = mid - (multiplier * atr); // initial values if (i == lookbackPeriods - 1) { isBullish = q.Close >= mid; upperBand = upperEval; lowerBand = lowerEval; } // new upper band if (upperEval < upperBand || prevClose > upperBand) { upperBand = upperEval; } // new lower band if (lowerEval > lowerBand || prevClose < lowerBand) { lowerBand = lowerEval; } // supertrend if (q.Close <= (isBullish ? lowerBand : upperBand)) { r.SuperTrend = (decimal?)upperBand; r.UpperBand = (decimal?)upperBand; isBullish = false; } else { r.SuperTrend = (decimal?)lowerBand; r.LowerBand = (decimal?)lowerBand; isBullish = true; } } results.Add(r); } return(results); }
// AVERAGE DIRECTIONAL INDEX /// <include file='./info.xml' path='indicator/*' /> /// public static IEnumerable <AdxResult> GetAdx <TQuote>( this IEnumerable <TQuote> quotes, int lookbackPeriods = 14) where TQuote : IQuote { // convert quotes List <QuoteD> quotesList = quotes.ConvertToList(); // check parameter arguments ValidateAdx(lookbackPeriods); // initialize List <AdxResult> results = new(quotesList.Count); List <AtrResult> atr = GetAtr(quotes, lookbackPeriods).ToList(); // get True Range info double prevHigh = 0; double prevLow = 0; double prevTrs = 0; // smoothed double prevPdm = 0; double prevMdm = 0; double prevAdx = 0; double sumTr = 0; double sumPdm = 0; double sumMdm = 0; double sumDx = 0; // roll through quotes for (int i = 0; i < quotesList.Count; i++) { QuoteD q = quotesList[i]; int index = i + 1; AdxResult result = new() { Date = q.Date }; results.Add(result); // skip first period if (index == 1) { prevHigh = q.High; prevLow = q.Low; continue; } double tr = (double)atr[i].Tr; double pdm1 = (q.High - prevHigh) > (prevLow - q.Low) ? Math.Max(q.High - prevHigh, 0) : 0; double mdm1 = (prevLow - q.Low) > (q.High - prevHigh) ? Math.Max(prevLow - q.Low, 0) : 0; prevHigh = q.High; prevLow = q.Low; // initialization period if (index <= lookbackPeriods + 1) { sumTr += tr; sumPdm += pdm1; sumMdm += mdm1; } // skip DM initialization period if (index <= lookbackPeriods) { continue; } // smoothed true range and directional movement double trs; double pdm; double mdm; if (index == lookbackPeriods + 1) { trs = sumTr; pdm = sumPdm; mdm = sumMdm; } else { trs = prevTrs - (prevTrs / lookbackPeriods) + tr; pdm = prevPdm - (prevPdm / lookbackPeriods) + pdm1; mdm = prevMdm - (prevMdm / lookbackPeriods) + mdm1; } prevTrs = trs; prevPdm = pdm; prevMdm = mdm; if (trs == 0) { continue; } // directional increments double pdi = 100 * pdm / trs; double mdi = 100 * mdm / trs; result.Pdi = pdi; result.Mdi = mdi; if (pdi + mdi == 0) { continue; } // calculate ADX double dx = 100 * Math.Abs(pdi - mdi) / (pdi + mdi); double adx; if (index > 2 * lookbackPeriods) { adx = ((prevAdx * (lookbackPeriods - 1)) + dx) / lookbackPeriods; result.Adx = adx; double?priorAdx = results[index - lookbackPeriods].Adx; result.Adxr = (adx + priorAdx) / 2; prevAdx = adx; } // initial ADX else if (index == 2 * lookbackPeriods) { sumDx += dx; adx = sumDx / lookbackPeriods; result.Adx = adx; prevAdx = adx; } // ADX initialization period else { sumDx += dx; } } return(results); }