// EXPONENTIAL MOVING AVERAGE public static IEnumerable <EmaResult> GetEma(IEnumerable <Quote> history, int lookbackPeriod) { // clean quotes history = Cleaners.PrepareHistory(history); // initialize results List <EmaResult> results = new List <EmaResult>(); // initialize EMA decimal k = 2 / (decimal)(lookbackPeriod + 1); decimal lastEma = history .Where(x => x.Index < lookbackPeriod) .Select(x => x.Close) .Average(); // roll through history foreach (Quote h in history) { EmaResult result = new EmaResult { Index = (int)h.Index, Date = h.Date }; if (h.Index >= lookbackPeriod) { result.Ema = lastEma + k * (h.Close - lastEma); lastEma = (decimal)result.Ema; } results.Add(result); } return(results); }
// RATE OF CHANGE (ROC) public static IEnumerable <RocResult> GetRoc(IEnumerable <Quote> history, int lookbackPeriod) { // clean quotes List <Quote> historyList = Cleaners.PrepareHistory(history).ToList(); // check parameters ValidateRoc(history, lookbackPeriod); // initialize List <RocResult> results = new List <RocResult>(); // roll through history for (int i = 0; i < historyList.Count; i++) { Quote h = historyList[i]; RocResult result = new RocResult { Index = (int)h.Index, Date = h.Date }; if (h.Index > lookbackPeriod) { Quote back = historyList[(int)h.Index - lookbackPeriod - 1]; result.Roc = 100 * (h.Close - back.Close) / back.Close; } results.Add(result); } return(results); }
// RATE OF CHANGE (ROC) public static IEnumerable <RocResult> GetRoc(IEnumerable <Quote> history, int lookbackPeriod) { // clean quotes history = Cleaners.PrepareHistory(history); // check parameters ValidateRoc(history, lookbackPeriod); // initialize List <RocResult> results = new List <RocResult>(); // roll through history foreach (Quote h in history) { RocResult result = new RocResult { Index = (int)h.Index, Date = h.Date }; if (h.Index > lookbackPeriod) { Quote back = history .Where(x => x.Index == h.Index - lookbackPeriod) .FirstOrDefault(); result.Roc = 100 * (h.Close - back.Close) / back.Close; } results.Add(result); } return(results); }
// BETA COEFFICIENT public static IEnumerable <BetaResult> GetBeta( IEnumerable <Quote> historyMarket, IEnumerable <Quote> historyEval, int lookbackPeriod) { // clean quotes historyMarket = Cleaners.PrepareHistory(historyMarket); historyEval = Cleaners.PrepareHistory(historyEval); // initialize results List <BetaResult> results = new List <BetaResult>(); // get prerequisite data IEnumerable <CorrResult> correlation = GetCorrelation(historyMarket, historyEval, lookbackPeriod); // roll through history for interim data foreach (Quote e in historyEval) { BetaResult result = new BetaResult { Index = (int)e.Index, Date = e.Date }; // calculate beta, if available CorrResult c = correlation.Where(x => x.Date == e.Date).FirstOrDefault(); if (c.Covariance != null && c.VarianceA != null && c.VarianceA != 0) { result.Beta = c.Covariance / c.VarianceA; } results.Add(result); } return(results); }
// SIMPLE MOVING AVERAGE public static IEnumerable <SmaResult> GetSma(IEnumerable <Quote> history, int lookbackPeriod) { // clean quotes history = Cleaners.PrepareHistory(history); // initialize results List <SmaResult> results = new List <SmaResult>(); // roll through history foreach (Quote h in history) { SmaResult result = new SmaResult { Index = (int)h.Index, Date = h.Date }; if (h.Index >= lookbackPeriod) { IEnumerable <decimal> period = history .Where(x => x.Index <= h.Index && x.Index > (h.Index - lookbackPeriod)) .Select(x => x.Close); result.Sma = period.Average(); } results.Add(result); } return(results); }
// WILLIAM %R OSCILLATOR public static IEnumerable <WilliamResult> GetWilliamR(IEnumerable <Quote> history, int lookbackPeriod = 14) { // clean quotes history = Cleaners.PrepareHistory(history); // validate parameters ValidateWilliam(history, lookbackPeriod); // initialize List <WilliamResult> results = new List <WilliamResult>(); IEnumerable <StochResult> stoch = GetStoch(history, lookbackPeriod, 1, 1); // fast variant // convert Stochastic to William %R foreach (StochResult s in stoch) { WilliamResult result = new WilliamResult { Index = s.Index, Date = s.Date, WilliamR = s.Oscillator - 100, IsIncreasing = s.IsIncreasing }; results.Add(result); } return(results); }
// CORRELATION COEFFICIENT public static IEnumerable <CorrResult> GetCorrelation( IEnumerable <Quote> historyA, IEnumerable <Quote> historyB, int lookbackPeriod) { // clean quotes historyA = Cleaners.PrepareHistory(historyA); historyB = Cleaners.PrepareHistory(historyB); // check exceptions int qtyHistory = historyA.Count(); int minHistory = lookbackPeriod; if (qtyHistory < minHistory) { throw new BadHistoryException("Insufficient history provided for Correlation. " + string.Format("You provided {0} periods of history when {1} is required.", qtyHistory, minHistory)); } // initialize List <CorrResult> results = new List <CorrResult>(); // roll through history for interim data foreach (Quote a in historyA) { Quote b = historyB.Where(x => x.Date == a.Date).FirstOrDefault(); if (b == null) { throw new BadHistoryException("Correlation requires matching dates in provided histories. {0} not found in historyB."); } CorrResult result = new CorrResult { Index = (int)a.Index, Date = a.Date, PriceA = a.Close, PriceB = b.Close // other values calculated in class properties }; results.Add(result); } // compute correlation foreach (CorrResult r in results.Where(x => x.Index >= lookbackPeriod)) { IEnumerable <CorrResult> period = results.Where(x => x.Index > (r.Index - lookbackPeriod) && x.Index <= r.Index); decimal avgA = period.Select(x => x.PriceA).Average(); decimal avgB = period.Select(x => x.PriceB).Average(); decimal avgA2 = period.Select(x => x.PriceA2).Average(); decimal avgB2 = period.Select(x => x.PriceB2).Average(); decimal avgAB = period.Select(x => x.PriceAB).Average(); r.VarianceA = avgA2 - avgA * avgA; r.VarianceB = avgB2 - avgB * avgB; r.Covariance = avgAB - avgA * avgB; r.Correlation = r.Covariance / (decimal)Math.Sqrt((double)(r.VarianceA * r.VarianceB)); } return(results); }
// Standard Deviation public static IEnumerable <StdDevResult> GetStdDev(IEnumerable <Quote> history, int lookbackPeriod) { // clean quotes history = Cleaners.PrepareHistory(history); // initialize results List <StdDevResult> results = new List <StdDevResult>(); // roll through history and compute lookback standard deviation foreach (Quote h in history) { StdDevResult result = new StdDevResult { Index = (int)h.Index, Date = h.Date }; if (h.Index >= lookbackPeriod) { IEnumerable <double> period = history .Where(x => x.Index <= h.Index && x.Index > (h.Index - lookbackPeriod)) .Select(x => (double)x.Close); result.StdDev = (decimal)Functions.StdDev(period); } results.Add(result); } return(results); }
// SIMPLE MOVING AVERAGE of VOLUME public static IEnumerable <VolSmaResult> GetVolSma(IEnumerable <Quote> history, int lookbackPeriod) { // clean quotes and initialize results List <VolSmaResult> results = Cleaners.PrepareHistory(history) .Select(x => new VolSmaResult { Index = (int)x.Index, Date = x.Date, Volume = x.Volume }) .ToList(); // check parameters ValidateVolSma(history, lookbackPeriod); // roll through history for (int i = lookbackPeriod - 1; i < results.Count; i++) { VolSmaResult h = results[i]; decimal sumVolSma = 0m; for (int p = (int)h.Index - lookbackPeriod; p < h.Index; p++) { VolSmaResult q = results[p]; sumVolSma += q.Volume; } h.VolSma = sumVolSma / lookbackPeriod; } return(results); }
// MOVING AVERAGE CONVERGENCE/DIVERGENCE (MACD) OSCILLATOR public static IEnumerable <MacdResult> GetMacd(IEnumerable <Quote> history, int fastPeriod = 12, int slowPeriod = 26, int signalPeriod = 9) { // clean quotes history = Cleaners.PrepareHistory(history); // check parameters ValidateMacd(history, fastPeriod, slowPeriod, signalPeriod); // initialize List <Quote> historyList = history.ToList(); List <EmaResult> emaFast = GetEma(history, fastPeriod).ToList(); List <EmaResult> emaSlow = GetEma(history, slowPeriod).ToList(); List <BasicData> emaDiff = new List <BasicData>(); List <MacdResult> results = new List <MacdResult>(); for (int i = 0; i < historyList.Count; i++) { Quote h = historyList[i]; EmaResult df = emaFast[i]; EmaResult ds = emaSlow[i]; MacdResult result = new MacdResult { Index = (int)h.Index, Date = h.Date }; if (df?.Ema != null && ds?.Ema != null) { decimal macd = (decimal)df.Ema - (decimal)ds.Ema; result.Macd = macd; // temp data for interim EMA of macd BasicData diff = new BasicData { Index = h.Index - slowPeriod + 1, Date = h.Date, Value = macd }; emaDiff.Add(diff); } results.Add(result); } // add signal and histogram to result List <EmaResult> emaSignal = CalcEma(emaDiff, signalPeriod).ToList(); foreach (MacdResult r in results.Where(x => x.Index >= slowPeriod)) { EmaResult ds = emaSignal[r.Index - slowPeriod]; r.Signal = ds.Ema; r.Histogram = r.Macd - r.Signal; } return(results); }
// ON-BALANCE VOLUME public static IEnumerable <AdlResult> GetAdl(IEnumerable <Quote> history) { // clean quotes Cleaners.PrepareHistory(history); // check parameters ValidateAdl(history); // initialize List <AdlResult> results = new List <AdlResult>(); decimal prevAdl = 0; // get money flow multiplier foreach (Quote h in history) { decimal mfm = (h.High == h.Low) ? 0 : ((h.Close - h.Low) - (h.High - h.Close)) / (h.High - h.Low); decimal mfv = mfm * h.Volume; decimal adl = mfv + prevAdl; AdlResult result = new AdlResult { Index = (int)h.Index, Date = h.Date, MoneyFlowMultiplier = mfm, MoneyFlowVolume = mfv, Adl = adl }; results.Add(result); prevAdl = adl; } return(results); }
// CHANDELIER EXIT public static IEnumerable <ChandelierResult> GetChandelier( IEnumerable <Quote> history, int lookbackPeriod = 22, decimal multiplier = 3.0m, ChandelierType type = ChandelierType.Long) { // clean quotes Cleaners.PrepareHistory(history); // validate inputs ValidateChandelier(history, lookbackPeriod, multiplier); // initialize List <Quote> historyList = history.ToList(); List <ChandelierResult> results = new List <ChandelierResult>(); List <AtrResult> atrResult = GetAtr(history, lookbackPeriod).ToList(); // uses ATR // roll through history for (int i = 0; i < historyList.Count; i++) { Quote h = historyList[i]; ChandelierResult result = new ChandelierResult { Index = (int)h.Index, Date = h.Date }; // add exit values if (h.Index >= lookbackPeriod) { List <Quote> period = historyList .Where(x => x.Index > (h.Index - lookbackPeriod) && x.Index <= h.Index) .ToList(); decimal atr = (decimal)atrResult[i].Atr; switch (type) { case ChandelierType.Long: decimal maxHigh = period.Select(x => x.High).Max(); result.ChandelierExit = maxHigh - atr * multiplier; break; case ChandelierType.Short: decimal minLow = period.Select(x => x.Low).Min(); result.ChandelierExit = minLow + atr * multiplier; break; default: break; } } results.Add(result); } return(results); }
// STOCHASTIC RSI public static IEnumerable <StochRsiResult> GetStochRsi(IEnumerable <Quote> history, int lookbackPeriod = 14) { // clean quotes history = Cleaners.PrepareHistory(history); // check exceptions int qtyHistory = history.Count(); int minHistory = 2 * lookbackPeriod; if (qtyHistory < minHistory) { throw new BadHistoryException("Insufficient history provided for Stochastic RSI. " + string.Format("You provided {0} periods of history when {1} is required.", qtyHistory, minHistory)); } // initialize List <StochRsiResult> results = new List <StochRsiResult>(); IEnumerable <RsiResult> rsiResults = GetRsi(history, lookbackPeriod); // calculate foreach (Quote h in history) { StochRsiResult result = new StochRsiResult { Index = (int)h.Index, Date = h.Date, }; if (h.Index >= 2 * lookbackPeriod) { IEnumerable <RsiResult> period = rsiResults.Where(x => x.Index <= h.Index && x.Index > (h.Index - lookbackPeriod)); float?rsi = period.Where(x => x.Index == h.Index).FirstOrDefault().Rsi; float?rsiHigh = period.Select(x => x.Rsi).Max(); float?rsiLow = period.Select(x => x.Rsi).Min(); result.StochRsi = (rsi - rsiLow) / (rsiHigh - rsiLow); } results.Add(result); } // add direction float?lastRSI = 0; foreach (StochRsiResult r in results.Where(x => x.Index >= 2 * lookbackPeriod).OrderBy(d => d.Index)) { if (r.Index >= lookbackPeriod) { r.IsIncreasing = (r.StochRsi >= lastRSI) ? true : false; } lastRSI = r.StochRsi; } return(results); }
// BOLLINGER BANDS public static IEnumerable <BollingerBandsResult> GetBollingerBands(IEnumerable <Quote> history, int lookbackPeriod = 20, decimal standardDeviations = 2) { // clean quotes history = Cleaners.PrepareHistory(history); // check exceptions int qtyHistory = history.Count(); int minHistory = lookbackPeriod; if (qtyHistory < minHistory) { throw new BadHistoryException("Insufficient history provided for Bollinger Bands. " + string.Format("You provided {0} periods of history when {1} is required.", qtyHistory, minHistory)); } // initialize List <BollingerBandsResult> results = new List <BollingerBandsResult>(); IEnumerable <SmaResult> sma = GetSma(history, lookbackPeriod); decimal?prevUpperBand = null; decimal?prevLowerBand = null; // roll through history foreach (Quote h in history) { BollingerBandsResult result = new BollingerBandsResult { Index = (int)h.Index, Date = h.Date }; if (h.Index >= lookbackPeriod) { IEnumerable <double> periodClose = history .Where(x => x.Index > (h.Index - lookbackPeriod) && x.Index <= h.Index) .Select(x => (double)x.Close); double stdDev = Functions.StdDev(periodClose); result.Sma = sma.Where(x => x.Date == h.Date).Select(x => x.Sma).FirstOrDefault(); result.UpperBand = result.Sma + standardDeviations * (decimal)stdDev; result.LowerBand = result.Sma - standardDeviations * (decimal)stdDev; if (prevUpperBand != null && prevLowerBand != null) { result.IsDiverging = ((decimal)result.UpperBand - (decimal)result.LowerBand) > ((decimal)prevUpperBand - (decimal)prevLowerBand) ? true : false; } // for next iteration prevUpperBand = result.UpperBand; prevLowerBand = result.LowerBand; } results.Add(result); } return(results); }
// AROON OSCILLATOR public static IEnumerable <AroonResult> GetAroon(IEnumerable <Quote> history, int lookbackPeriod = 25) { // clean quotes List <Quote> historyList = Cleaners.PrepareHistory(history).ToList(); // validate parameters ValidateAroon(history, lookbackPeriod); // initialize List <AroonResult> results = new List <AroonResult>(); // roll through history for (int i = 0; i < historyList.Count; i++) { Quote h = historyList[i]; AroonResult result = new AroonResult { Index = (int)h.Index, Date = h.Date }; // add aroons if (h.Index > lookbackPeriod) { decimal lastHighPrice = 0; decimal lastLowPrice = decimal.MaxValue; int lastHighIndex = 0; int lastLowIndex = 0; for (int p = (int)h.Index - lookbackPeriod - 1; p < h.Index; p++) { Quote d = historyList[p]; if (d.High > lastHighPrice) { lastHighPrice = d.High; lastHighIndex = (int)d.Index; } if (d.Low < lastLowPrice) { lastLowPrice = d.Low; lastLowIndex = (int)d.Index; } } result.AroonUp = 100 * (decimal)(lookbackPeriod - (h.Index - lastHighIndex)) / lookbackPeriod; result.AroonDown = 100 * (decimal)(lookbackPeriod - (h.Index - lastLowIndex)) / lookbackPeriod; result.Oscillator = result.AroonUp - result.AroonDown; } results.Add(result); } return(results); }
// STOCHASTIC RSI public static IEnumerable <StochRsiResult> GetStochRsi(IEnumerable <Quote> history, int rsiPeriod, int stochPeriod, int signalPeriod, int smoothPeriod = 1) { // clean quotes history = Cleaners.PrepareHistory(history); // validate parameters ValidateStochRsi(history, rsiPeriod, stochPeriod, signalPeriod, smoothPeriod); // initialize List <StochRsiResult> results = new List <StochRsiResult>(); // get RSI IEnumerable <RsiResult> rsiResults = GetRsi(history, rsiPeriod); // convert rsi to quote format List <Quote> rsiQuotes = rsiResults .Where(x => x.Rsi != null) .Select(x => new Quote { Index = null, Date = x.Date, High = (decimal)x.Rsi, Low = (decimal)x.Rsi, Close = (decimal)x.Rsi }) .ToList(); // get Stochastic of RSI IEnumerable <StochResult> stoResults = GetStoch(rsiQuotes, stochPeriod, signalPeriod, smoothPeriod); // compose foreach (RsiResult r in rsiResults) { StochRsiResult result = new StochRsiResult { Index = r.Index, Date = r.Date }; if (r.Index >= rsiPeriod + stochPeriod) { StochResult sto = stoResults .Where(x => x.Index == r.Index - stochPeriod) .FirstOrDefault(); result.StochRsi = sto.Oscillator; result.Signal = sto.Signal; result.IsIncreasing = sto.IsIncreasing; } results.Add(result); } return(results); }
// AVERAGE TRUE RANGE public static IEnumerable <AtrResult> GetAtr(IEnumerable <Quote> history, int lookbackPeriod = 14) { // clean quotes history = Cleaners.PrepareHistory(history); // initialize results List <AtrResult> results = new List <AtrResult>(); decimal prevAtr = 0; decimal prevClose = 0; decimal highMinusPrevClose = 0; decimal lowMinusPrevClose = 0; decimal sumTr = 0; // roll through history foreach (Quote h in history) { AtrResult result = new AtrResult { Index = (int)h.Index, Date = h.Date }; if (h.Index > 1) { highMinusPrevClose = Math.Abs(h.High - prevClose); lowMinusPrevClose = Math.Abs(h.Low - prevClose); } decimal tr = Math.Max((h.High - h.Low), Math.Max(highMinusPrevClose, lowMinusPrevClose)); result.Tr = tr; if (h.Index > lookbackPeriod) { // calculate ATR result.Atr = (prevAtr * (lookbackPeriod - 1) + tr) / lookbackPeriod; prevAtr = (decimal)result.Atr; } else if (h.Index == lookbackPeriod) { // initialize ATR sumTr += tr; result.Atr = sumTr / lookbackPeriod; prevAtr = (decimal)result.Atr; } else { // only used for periods before ATR initialization sumTr += tr; } results.Add(result); prevClose = h.Close; } return(results); }
// AROON OSCILLATOR public static IEnumerable <AroonResult> GetAroon(IEnumerable <Quote> history, int lookbackPeriod = 25) { // clean quotes Cleaners.PrepareHistory(history); // validate parameters ValidateAroon(history, lookbackPeriod); // initialize List <Quote> historyList = history.ToList(); List <AroonResult> results = new List <AroonResult>(); // roll through history for (int i = 0; i < historyList.Count; i++) { Quote h = historyList[i]; AroonResult result = new AroonResult { Index = (int)h.Index, Date = h.Date }; // add aroons if (h.Index >= lookbackPeriod) { List <Quote> period = historyList .Where(x => x.Index <= h.Index && x.Index > (h.Index - lookbackPeriod)) .ToList(); decimal lastHighPrice = period.Select(x => x.High).Max(); decimal lastLowPrice = period.Select(x => x.Low).Min(); int lastHighIndex = period .Where(x => x.High == lastHighPrice) .OrderBy(x => x.Index) // implies "new" high, so not picking new tie for high .Select(x => (int)x.Index) .FirstOrDefault(); int lastLowIndex = period .Where(x => x.Low == lastLowPrice) .OrderBy(x => x.Index) .Select(x => (int)x.Index) .FirstOrDefault(); result.AroonUp = 100 * (decimal)(lookbackPeriod - (h.Index - lastHighIndex)) / lookbackPeriod; result.AroonDown = 100 * (decimal)(lookbackPeriod - (h.Index - lastLowIndex)) / lookbackPeriod; result.Oscillator = result.AroonUp - result.AroonDown; } results.Add(result); } return(results); }
// DONCHIAN CHANNEL public static IEnumerable <DonchianResult> GetDonchian( IEnumerable <Quote> history, int lookbackPeriod = 20) { // clean quotes List <Quote> historyList = Cleaners.PrepareHistory(history).ToList(); // validate parameters ValidateDonchian(history, lookbackPeriod); // initialize List <DonchianResult> results = new List <DonchianResult>(); // roll through history for (int i = 0; i < historyList.Count; i++) { Quote h = historyList[i]; DonchianResult result = new DonchianResult { Index = (int)h.Index, Date = h.Date }; if (h.Index >= lookbackPeriod) { decimal highHigh = 0; decimal lowLow = decimal.MaxValue; for (int p = (int)h.Index - lookbackPeriod; p < h.Index; p++) { Quote d = historyList[p]; if (d.High > highHigh) { highHigh = d.High; } if (d.Low < lowLow) { lowLow = d.Low; } } result.UpperBand = highHigh; result.LowerBand = lowLow; result.Centerline = (result.UpperBand + result.LowerBand) / 2; result.Width = (result.Centerline == 0) ? null : (result.UpperBand - result.LowerBand) / result.Centerline; } results.Add(result); } return(results); }
// ULCER INDEX (UI) public static IEnumerable <UlcerIndexResult> GetUlcerIndex(IEnumerable <Quote> history, int lookbackPeriod = 14) { // clean quotes history = Cleaners.PrepareHistory(history); // exceptions int qtyHistory = history.Count(); int minHistory = lookbackPeriod; if (qtyHistory < minHistory) { throw new BadHistoryException("Insufficient history provided for Ulcer Index. " + string.Format("You provided {0} periods of history when {1} is required.", qtyHistory, minHistory)); } // initialize List <UlcerIndexResult> results = new List <UlcerIndexResult>(); // preliminary data foreach (Quote h in history) { UlcerIndexResult result = new UlcerIndexResult { Index = (int)h.Index, Date = h.Date }; if (h.Index >= lookbackPeriod) { IEnumerable <Quote> period = history .Where(x => x.Index > (h.Index - lookbackPeriod) && x.Index <= h.Index); double sumSquared = 0; foreach (Quote p in period) { decimal maxClose = period .Where(x => x.Index <= p.Index) .Select(x => x.Close) .Max(); decimal percentDrawdown = 100 * (p.Close - maxClose) / maxClose; sumSquared += (double)(percentDrawdown * percentDrawdown); } result.UI = (decimal)Math.Sqrt(sumSquared / lookbackPeriod); } results.Add(result); } return(results); }
// BOLLINGER BANDS public static IEnumerable <BollingerBandsResult> GetBollingerBands( IEnumerable <Quote> history, int lookbackPeriod = 20, decimal standardDeviations = 2) { // clean quotes List <Quote> historyList = Cleaners.PrepareHistory(history).ToList(); // validate parameters ValidateBollingerBands(history, lookbackPeriod, standardDeviations); // initialize List <BollingerBandsResult> results = new List <BollingerBandsResult>(); // roll through history for (int i = 0; i < historyList.Count; i++) { Quote h = historyList[i]; BollingerBandsResult r = new BollingerBandsResult { Index = (int)h.Index, Date = h.Date }; if (h.Index >= lookbackPeriod) { double[] periodClose = new double[lookbackPeriod]; decimal sum = 0m; int n = 0; for (int p = (int)h.Index - lookbackPeriod; p < h.Index; p++) { Quote d = historyList[p]; periodClose[n] = (double)d.Close; sum += d.Close; n++; } decimal periodAvg = sum / lookbackPeriod; decimal stdDev = (decimal)Functions.StdDev(periodClose); r.Sma = periodAvg; r.UpperBand = periodAvg + standardDeviations * stdDev; r.LowerBand = periodAvg - standardDeviations * stdDev; r.PercentB = (h.Close - r.LowerBand) / (r.UpperBand - r.LowerBand); r.ZScore = (stdDev == 0) ? null : (h.Close - r.Sma) / stdDev; r.Width = (r.Sma == 0) ? null : (r.UpperBand - r.LowerBand) / r.Sma; } results.Add(r); } return(results); }
// ON-BALANCE VOLUME public static IEnumerable <ObvResult> GetObv(IEnumerable <Quote> history, int?smaPeriod = null) { // clean quotes history = Cleaners.PrepareHistory(history); // check parameters ValidateObv(history, smaPeriod); // initialize List <ObvResult> results = new List <ObvResult>(); decimal?prevClose = null; decimal obv = 0; foreach (Quote h in history) { if (prevClose == null || h.Close == prevClose) { // no change to OBV } else if (h.Close > prevClose) { obv += h.Volume; } else if (h.Close < prevClose) { obv -= h.Volume; } ObvResult result = new ObvResult { Index = (int)h.Index, Date = h.Date, Obv = obv }; results.Add(result); prevClose = h.Close; // optional SMA if (smaPeriod != null && h.Index > smaPeriod) { decimal sumSma = 0m; for (int p = (int)h.Index - (int)smaPeriod; p < h.Index; p++) { sumSma += results[p].Obv; } result.Sma = sumSma / smaPeriod; } } return(results); }
// BOLLINGER BANDS public static IEnumerable <BollingerBandsResult> GetBollingerBands( IEnumerable <Quote> history, int lookbackPeriod = 20, decimal standardDeviations = 2) { // clean quotes history = Cleaners.PrepareHistory(history); // validate parameters ValidateBollingerBands(history, lookbackPeriod, standardDeviations); // initialize List <BollingerBandsResult> results = new List <BollingerBandsResult>(); decimal?prevUpperBand = null; decimal?prevLowerBand = null; // roll through history foreach (Quote h in history) { BollingerBandsResult result = new BollingerBandsResult { Index = (int)h.Index, Date = h.Date }; if (h.Index >= lookbackPeriod) { IEnumerable <double> periodClose = history .Where(x => x.Index > (h.Index - lookbackPeriod) && x.Index <= h.Index) .Select(x => (double)x.Close); double stdDev = Functions.StdDev(periodClose); result.Sma = (decimal)periodClose.Average(); result.UpperBand = result.Sma + standardDeviations * (decimal)stdDev; result.LowerBand = result.Sma - standardDeviations * (decimal)stdDev; result.ZScore = (stdDev == 0) ? null : (h.Close - result.Sma) / (decimal)stdDev; result.Width = (result.Sma == 0) ? null : (result.UpperBand - result.LowerBand) / result.Sma; if (prevUpperBand != null && prevLowerBand != null) { result.IsDiverging = ((decimal)result.UpperBand - (decimal)result.LowerBand) > ((decimal)prevUpperBand - (decimal)prevLowerBand); } // for next iteration prevUpperBand = result.UpperBand; prevLowerBand = result.LowerBand; } results.Add(result); } return(results); }
// RELATIVE STRENGTH INDEX public static IEnumerable <RsiResult> GetRsi(IEnumerable <Quote> history, int lookbackPeriod = 14) { // clean quotes history = Cleaners.PrepareHistory(history); // initialize decimal lastClose = history.First().Close; List <RsiResult> results = new List <RsiResult>(); // load gain data foreach (Quote h in history) { RsiResult result = new RsiResult { Index = (int)h.Index, Date = h.Date, Gain = (lastClose < h.Close) ? (float)(h.Close - lastClose) : 0, Loss = (lastClose > h.Close) ? (float)(lastClose - h.Close) : 0 }; results.Add(result); lastClose = h.Close; } // initialize average gain float avgGain = results.Where(x => x.Index <= lookbackPeriod).Select(g => g.Gain).Average(); float avgLoss = results.Where(x => x.Index <= lookbackPeriod).Select(g => g.Loss).Average(); // initial RSI for trend analysis float lastRSI = (avgLoss > 0) ? 100 - (100 / (1 + (avgGain / avgLoss))) : 100; // calculate RSI foreach (RsiResult r in results.Where(x => x.Index >= lookbackPeriod).OrderBy(d => d.Index)) { avgGain = (avgGain * (lookbackPeriod - 1) + r.Gain) / lookbackPeriod; avgLoss = (avgLoss * (lookbackPeriod - 1) + r.Loss) / lookbackPeriod; if (avgLoss > 0) { float rs = avgGain / avgLoss; r.Rsi = 100 - (100 / (1 + rs)); } else { r.Rsi = 100; } r.IsIncreasing = (r.Rsi >= lastRSI) ? true : false; lastRSI = (float)r.Rsi; } return(results); }
// DONCHIAN CHANNEL public static IEnumerable <DonchianResult> GetDonchian( IEnumerable <Quote> history, int lookbackPeriod = 20) { // clean quotes history = Cleaners.PrepareHistory(history); // validate parameters ValidateDonchian(history, lookbackPeriod); // initialize List <DonchianResult> results = new List <DonchianResult>(); decimal?prevWidth = null; // roll through history foreach (Quote h in history) { DonchianResult result = new DonchianResult { Index = (int)h.Index, Date = h.Date }; if (h.Index >= lookbackPeriod) { IEnumerable <Quote> period = history .Where(x => x.Index > (h.Index - lookbackPeriod) && x.Index <= h.Index); result.UpperBand = period.Select(h => h.High).Max(); result.LowerBand = period.Select(l => l.Low).Min(); result.Centerline = (result.UpperBand + result.LowerBand) / 2; result.Width = (result.Centerline == 0) ? null : (result.UpperBand - result.LowerBand) / result.Centerline; if (prevWidth != null) { if (result.Width == prevWidth) { result.IsDiverging = null; } else { result.IsDiverging = (result.Width > prevWidth); } } // for next iteration prevWidth = result.Width; } results.Add(result); } return(results); }
// ICHIMOKU CLOUD public static IEnumerable <IchimokuResult> GetIchimoku(IEnumerable <Quote> history, int signalPeriod = 9, int shortSpanPeriod = 26, int longSpanPeriod = 52) { // clean quotes List <Quote> historyList = Cleaners.PrepareHistory(history).ToList(); // check parameters ValidateIchimoku(history, signalPeriod, shortSpanPeriod, longSpanPeriod); // initialize List <IchimokuResult> results = new List <IchimokuResult>(); // roll through history for (int i = 0; i < historyList.Count; i++) { Quote h = historyList[i]; IchimokuResult result = new IchimokuResult { Index = (int)h.Index, Date = h.Date }; // tenkan-sen conversion line CalcIchimokuTenkanSen(historyList, result, h, signalPeriod); // kijun-sen base line CalcIchimokuKijunSen(historyList, result, h, shortSpanPeriod); // senkou span A if (h.Index >= 2 * shortSpanPeriod) { IchimokuResult skq = results[(int)h.Index - shortSpanPeriod - 1]; if (skq != null && skq.TenkanSen != null && skq.KijunSen != null) { result.SenkouSpanA = (skq.TenkanSen + skq.KijunSen) / 2; } } // senkou span B CalcIchimokuSenkouB(historyList, result, h, shortSpanPeriod, longSpanPeriod); // chikou line if (h.Index + shortSpanPeriod <= historyList.Count) { result.ChikouSpan = historyList[(int)h.Index + shortSpanPeriod - 1].Close; } results.Add(result); } return(results); }
// CHAIKIN MONEY FLOW public static IEnumerable <CmfResult> GetCmf(IEnumerable <Quote> history, int lookbackPeriod = 20) { // clean quotes List <Quote> historyList = Cleaners.PrepareHistory(history).ToList(); // check parameters ValidateCmf(history, lookbackPeriod); // initialize List <CmfResult> results = new List <CmfResult>(); List <AdlResult> adlResults = GetAdl(history).ToList(); // roll through history for (int i = 0; i < adlResults.Count; i++) { AdlResult r = adlResults[i]; CmfResult result = new CmfResult { Index = r.Index, Date = r.Date, MoneyFlowMultiplier = r.MoneyFlowMultiplier, MoneyFlowVolume = r.MoneyFlowVolume }; if (r.Index >= lookbackPeriod) { decimal sumMfv = 0; decimal sumVol = 0; for (int p = r.Index - lookbackPeriod; p < r.Index; p++) { Quote h = historyList[p]; sumVol += h.Volume; AdlResult d = adlResults[p]; sumMfv += d.MoneyFlowVolume; } decimal avgMfv = sumMfv / lookbackPeriod; decimal avgVol = sumVol / lookbackPeriod; if (avgVol != 0) { result.Cmf = avgMfv / avgVol; } } results.Add(result); } return(results); }
// SIMPLE MOVING AVERAGE public static IEnumerable <ChaikinOscResult> GetChaikinOsc( IEnumerable <Quote> history, int fastPeriod = 3, int slowPeriod = 10) { // clean quotes history = Cleaners.PrepareHistory(history); // check parameters ValidateChaikinOsc(history, fastPeriod, slowPeriod); // initialize List <ChaikinOscResult> results = new List <ChaikinOscResult>(); IEnumerable <AdlResult> adlResults = GetAdl(history); // EMA of ADL IEnumerable <BasicData> adlBasicData = adlResults .Select(x => new BasicData { Index = x.Index, Date = x.Date, Value = x.Adl }); IEnumerable <EmaResult> adlEmaSlow = CalcEma(adlBasicData, slowPeriod); IEnumerable <EmaResult> adlEmaFast = CalcEma(adlBasicData, fastPeriod); // roll through history foreach (AdlResult r in adlResults) { ChaikinOscResult result = new ChaikinOscResult { Index = r.Index, Date = r.Date, MoneyFlowMultiplier = r.MoneyFlowMultiplier, MoneyFlowVolume = r.MoneyFlowVolume, Adl = r.Adl }; // add Oscillator if (r.Index >= slowPeriod) { EmaResult f = adlEmaFast.Where(x => x.Index == r.Index).FirstOrDefault(); EmaResult s = adlEmaSlow.Where(x => x.Index == r.Index).FirstOrDefault(); result.Oscillator = f.Ema - s.Ema; } results.Add(result); } return(results); }
// SIMPLE MOVING AVERAGE public static IEnumerable <SmaResult> GetSma(IEnumerable <Quote> history, int lookbackPeriod) { // clean quotes history = Cleaners.PrepareHistory(history); // check parameters ValidateSma(history, lookbackPeriod); // initialize List <SmaResult> results = new List <SmaResult>(); // roll through history foreach (Quote h in history) { SmaResult result = new SmaResult { Index = (int)h.Index, Date = h.Date }; if (h.Index >= lookbackPeriod) { IEnumerable <Quote> period = history .Where(x => x.Index <= h.Index && x.Index > (h.Index - lookbackPeriod)); // simple moving average result.Sma = period .Select(x => x.Close) .Average(); // mean absolute deviation result.Mad = period .Select(x => Math.Abs(x.Close - (decimal)result.Sma)) .Average(); // mean squared error result.Mse = period .Select(x => (x.Close - (decimal)result.Sma) * (x.Close - (decimal)result.Sma)) .Average(); // mean absolute percent error result.Mape = period .Where(x => x.Close != 0) .Select(x => Math.Abs(x.Close - (decimal)result.Sma) / x.Close) .Average(); } results.Add(result); } return(results); }
// ULCER INDEX (UI) public static IEnumerable <UlcerIndexResult> GetUlcerIndex(IEnumerable <Quote> history, int lookbackPeriod = 14) { // clean quotes Cleaners.PrepareHistory(history); // validate parameters ValidateUlcer(history, lookbackPeriod); // initialize List <Quote> historyList = history.ToList(); List <UlcerIndexResult> results = new List <UlcerIndexResult>(); // roll through history for (int i = 0; i < historyList.Count; i++) { Quote h = historyList[i]; UlcerIndexResult result = new UlcerIndexResult { Index = (int)h.Index, Date = h.Date }; if (h.Index >= lookbackPeriod) { List <Quote> period = historyList .Where(x => x.Index > (h.Index - lookbackPeriod) && x.Index <= h.Index) .ToList(); double sumSquared = 0; foreach (Quote p in period) { decimal maxClose = period .Where(x => x.Index <= p.Index) .Select(x => x.Close) .Max(); decimal percentDrawdown = 100 * (p.Close - maxClose) / maxClose; sumSquared += (double)(percentDrawdown * percentDrawdown); } result.UI = (decimal)Math.Sqrt(sumSquared / lookbackPeriod); } results.Add(result); } return(results); }