// SLOPE AND LINEAR REGRESSION public static IEnumerable <SlopeResult> GetSlope(IEnumerable <Quote> history, int lookbackPeriod) { // clean quotes List <Quote> historyList = Cleaners.PrepareHistory(history).ToList(); // validate parameters ValidateSlope(history, lookbackPeriod); // initialize List <SlopeResult> results = new List <SlopeResult>(); // roll through history for interim data for (int i = 0; i < historyList.Count; i++) { Quote h = historyList[i]; SlopeResult r = new SlopeResult { Index = (int)h.Index, Date = h.Date }; results.Add(r); // skip initialization period if (h.Index < lookbackPeriod) { continue; } // get averages for period decimal sumX = 0m; decimal sumY = 0m; for (int p = r.Index - lookbackPeriod; p < r.Index; p++) { Quote d = historyList[p]; sumX += (decimal)d.Index; sumY += d.Close; } decimal avgX = sumX / lookbackPeriod; decimal avgY = sumY / lookbackPeriod; // least squares method decimal sumSqX = 0m; decimal sumSqY = 0m; decimal sumSqXY = 0m; for (int p = r.Index - lookbackPeriod; p < r.Index; p++) { Quote d = historyList[p]; decimal devX = ((decimal)d.Index - avgX); decimal devY = (d.Close - 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 / lookbackPeriod); double stdDevY = Math.Sqrt((double)sumSqY / lookbackPeriod); r.StdDev = stdDevY; if (stdDevX * stdDevY != 0) { double R = ((double)sumSqXY / (stdDevX * stdDevY)) / lookbackPeriod; r.RSquared = R * R; } } // add last Line (y = mx + b) SlopeResult last = results[historyList.Count - 1]; for (int p = last.Index - lookbackPeriod; p < last.Index; p++) { SlopeResult d = results[p]; d.Line = last.Slope * d.Index + last.Intercept; } return(results); }
// RATE OF CHANGE (PMO) public static IEnumerable <PmoResult> GetPmo( IEnumerable <Quote> history, int timePeriod = 35, int smoothingPeriod = 20, int signalPeriod = 10) { // clean quotes Cleaners.PrepareHistory(history); // check parameters ValidatePmo(history, timePeriod, smoothingPeriod, signalPeriod); // initialize List <PmoResult> results = new List <PmoResult>(); List <RocResult> roc = GetRoc(history, 1).ToList(); int startIndex = 0; decimal smoothingMultiplier = 2m / timePeriod; decimal smoothingConstant = 2m / smoothingPeriod; decimal signalConstant = 2m / (signalPeriod + 1); decimal?lastRocEma = null; decimal?lastPmo = null; decimal?lastSignal = null; // get ROC EMA variant startIndex = timePeriod + 1; for (int i = 0; i < roc.Count; i++) { RocResult r = roc[i]; PmoResult result = new PmoResult { Index = r.Index, Date = r.Date }; if (r.Index > startIndex) { result.RocEma = r.Roc * smoothingMultiplier + lastRocEma * (1 - smoothingMultiplier); } else if (r.Index == startIndex) { result.RocEma = roc .Where(x => x.Index > r.Index - timePeriod && x.Index <= r.Index) .ToList() .Select(x => x.Roc) .Average(); } lastRocEma = result.RocEma; result.RocEma *= 10; results.Add(result); } // calculate PMO startIndex = timePeriod + smoothingPeriod; for (int i = startIndex - 1; i < results.Count; i++) { PmoResult p = results[i]; if (p.Index > startIndex) { p.Pmo = (p.RocEma - lastPmo) * smoothingConstant + lastPmo; } else if (p.Index == startIndex) { p.Pmo = results .Where(x => x.Index > p.Index - smoothingPeriod && x.Index <= p.Index) .ToList() .Select(x => x.RocEma) .Average(); } lastPmo = p.Pmo; } // add Signal startIndex = timePeriod + smoothingPeriod + signalPeriod - 1; for (int i = startIndex - 1; i < results.Count; i++) { PmoResult p = results[i]; if (p.Index > startIndex) { p.Signal = (p.Pmo - lastSignal) * signalConstant + lastSignal; } else if (p.Index == startIndex) { p.Signal = results .Where(x => x.Index > p.Index - signalPeriod && x.Index <= p.Index) .ToList() .Select(x => x.Pmo) .Average(); } lastSignal = p.Signal; } return(results); }
// STOCHASTIC OSCILLATOR public static IEnumerable <StochResult> GetStoch(IEnumerable <Quote> history, int lookbackPeriod = 14, int signalPeriod = 3, int smoothPeriod = 3) { // clean quotes history = Cleaners.PrepareHistory(history); // validate parameters ValidateStoch(history, lookbackPeriod, signalPeriod, smoothPeriod); // initialize List <StochResult> results = new List <StochResult>(); // oscillator foreach (Quote h in history) { StochResult result = new StochResult { Index = (int)h.Index, Date = h.Date }; if (h.Index >= lookbackPeriod) { decimal lowLow = history.Where(x => x.Index > (h.Index - lookbackPeriod) && x.Index <= h.Index) .Select(v => v.Low) .Min(); decimal highHigh = history.Where(x => x.Index > (h.Index - lookbackPeriod) && x.Index <= h.Index) .Select(v => v.High) .Max(); if (lowLow != highHigh) { result.Oscillator = 100 * ((h.Close - lowLow) / (highHigh - lowLow)); } else { result.Oscillator = 0; } } results.Add(result); } // smooth the oscillator if (smoothPeriod > 1) { results = SmoothOscillator(results, lookbackPeriod, smoothPeriod); } // signal and period direction info decimal?lastOsc = null; bool? lastIsIncreasing = null; foreach (StochResult r in results .Where(x => x.Index >= (lookbackPeriod + smoothPeriod - 1)) .OrderBy(x => x.Index)) { // add signal if (r.Index >= lookbackPeriod + smoothPeriod + signalPeriod - 2) { r.Signal = results.Where(x => x.Index > (r.Index - signalPeriod) && x.Index <= r.Index) .Select(v => v.Oscillator) .Average(); } // add direction if (lastOsc != null) { if (r.Oscillator > lastOsc) { r.IsIncreasing = true; } else if (r.Oscillator < lastOsc) { r.IsIncreasing = false; } else { // no change, keep trend r.IsIncreasing = lastIsIncreasing; } } lastOsc = (decimal)r.Oscillator; lastIsIncreasing = r.IsIncreasing; } return(results); }
// CONNORS RSI public static IEnumerable <ConnorsRsiResult> GetConnorsRsi( IEnumerable <Quote> history, int rsiPeriod = 3, int streakPeriod = 2, int rankPeriod = 100) { // convert history to basic format IEnumerable <BasicData> bd = Cleaners.ConvertHistoryToBasic(history, "C"); // check parameters ValidateConnorsRsi(bd, rsiPeriod, streakPeriod, rankPeriod); // initialize List <ConnorsRsiResult> results = new List <ConnorsRsiResult>(); IEnumerable <RsiResult> rsiResults = CalcRsi(bd, rsiPeriod); int startPeriod = Math.Max(rsiPeriod, Math.Max(streakPeriod, rankPeriod)) + 2; decimal?lastClose = null; decimal streak = 0; // compose interim results foreach (BasicData h in bd) { ConnorsRsiResult result = new ConnorsRsiResult { Index = (int)h.Index, Date = h.Date, RsiClose = rsiResults.Where(x => x.Index == h.Index).FirstOrDefault().Rsi }; // bypass for first record if (lastClose == null) { lastClose = h.Value; results.Add(result); continue; } // streak of up or down if (h.Value == lastClose) { streak = 0; } else if (h.Value > lastClose) { if (streak >= 0) { streak++; } else { streak = 1; } } else // h.Value < lastClose { if (streak <= 0) { streak--; } else { streak = -1; } } result.Streak = streak; // percentile rank result.PeriodGain = (decimal)((lastClose <= 0) ? null : (h.Value - lastClose) / lastClose); if (h.Index > rankPeriod) { IEnumerable <ConnorsRsiResult> period = results .Where(x => x.Index >= (h.Index - rankPeriod) && x.Index < h.Index); result.PercentRank = (decimal)100 * period .Where(x => x.PeriodGain < result.PeriodGain).Count() / rankPeriod; } results.Add(result); lastClose = h.Value; } // RSI of streak List <BasicData> bdStreak = results .Where(x => x.Streak != null) .Select(x => new BasicData { Index = null, Date = x.Date, Value = (decimal)x.Streak }) .ToList(); IEnumerable <RsiResult> rsiStreakResults = CalcRsi(bdStreak, streakPeriod); // compose final results foreach (ConnorsRsiResult r in results.Where(x => x.Index >= streakPeriod + 2)) { r.RsiStreak = rsiStreakResults .Where(x => x.Index == r.Index - 1) .FirstOrDefault() .Rsi; if (r.Index >= startPeriod) { r.ConnorsRsi = (r.RsiClose + r.RsiStreak + r.PercentRank) / 3; } } return(results); }
// ZIG ZAG public static IEnumerable <ZigZagResult> GetZigZag( IEnumerable <Quote> history, ZigZagType type = ZigZagType.Close, decimal percentChange = 5) { // clean quotes Cleaners.PrepareHistory(history); // check parameters ValidateZigZag(history, percentChange); // initialize List <Quote> historyList = history.ToList(); List <ZigZagResult> results = new List <ZigZagResult>(); decimal changeThreshold = percentChange / 100m; Quote firstQuote = historyList[0]; ZigZagEval eval = GetZigZagEval(type, firstQuote); ZigZagPoint lastPoint = new ZigZagPoint { Index = eval.Index, Value = firstQuote.Close, PointType = "U" }; ZigZagPoint lastHighPoint = new ZigZagPoint { Index = eval.Index, Value = eval.High, PointType = "H" }; ZigZagPoint lastLowPoint = new ZigZagPoint { Index = eval.Index, Value = eval.Low, PointType = "L" }; int finalPointIndex = historyList.Select(x => (int)x.Index).Max(); // roll through history until to find initial trend for (int i = 0; i < historyList.Count; i++) { Quote h = historyList[i]; eval = GetZigZagEval(type, h); decimal changeUp = (eval.High - lastLowPoint.Value) / lastLowPoint.Value; decimal changeDn = (lastHighPoint.Value - eval.Low) / lastHighPoint.Value; if (changeUp >= changeThreshold && changeUp > changeDn) { lastPoint.Index = lastLowPoint.Index; lastPoint.Value = lastLowPoint.Value; lastPoint.PointType = lastLowPoint.PointType; break; } if (changeDn >= changeThreshold && changeDn > changeUp) { lastPoint.Index = lastHighPoint.Index; lastPoint.Value = lastHighPoint.Value; lastPoint.PointType = lastHighPoint.PointType; break; } } // add first point to results ZigZagResult firstResult = new ZigZagResult { Index = (int)firstQuote.Index, Date = firstQuote.Date }; results.Add(firstResult); // find and draw lines while (lastPoint.Index < finalPointIndex) { ZigZagPoint nextPoint = EvaluateNextPoint(historyList, type, changeThreshold, lastPoint); string lastDirection = lastPoint.PointType; // draw line (and reset last point) DrawZigZagLine(results, historyList, lastPoint, nextPoint); // draw retrace line (and reset last high/low point) DrawRetraceLine(results, lastDirection, lastLowPoint, lastHighPoint, nextPoint); } return(results); }
// SIMPLE MOVING AVERAGE public static IEnumerable <SmaResult> GetSma( IEnumerable <Quote> history, int lookbackPeriod, bool extended = false) { // clean quotes Cleaners.PrepareHistory(history); // check parameters ValidateSma(history, lookbackPeriod); // initialize List <Quote> historyList = history.ToList(); List <SmaResult> results = new List <SmaResult>(); // roll through history for (int i = 0; i < historyList.Count; i++) { Quote h = historyList[i]; SmaResult result = new SmaResult { 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(); // simple moving average result.Sma = period .Select(x => x.Close) .Average(); // add optional extended values if (extended) { // 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); }
private static IEnumerable <RsiResult> CalcRsi(IEnumerable <BasicData> basicData, int lookbackPeriod = 14) { // clean data List <BasicData> bdList = Cleaners.PrepareBasicData(basicData).ToList(); // check parameters ValidateRsi(basicData, lookbackPeriod); // initialize decimal lastValue = bdList[0].Value; List <RsiResult> results = new List <RsiResult>(); // load gain data for (int i = 0; i < bdList.Count; i++) { BasicData h = bdList[i]; RsiResult result = new RsiResult { Index = (int)h.Index, Date = h.Date, Gain = (h.Value > lastValue) ? h.Value - lastValue : 0, Loss = (h.Value < lastValue) ? lastValue - h.Value : 0 }; results.Add(result); lastValue = h.Value; } // initialize average gain decimal avgGain = results.Where(x => x.Index <= lookbackPeriod).Select(g => g.Gain).Average(); decimal avgLoss = results.Where(x => x.Index <= lookbackPeriod).Select(g => g.Loss).Average(); // initial first record decimal lastRSI = (avgLoss > 0) ? 100 - (100 / (1 + (avgGain / avgLoss))) : 100; RsiResult first = results.Where(x => x.Index == lookbackPeriod + 1).FirstOrDefault(); first.Rsi = lastRSI; // calculate RSI foreach (RsiResult r in results.Where(x => x.Index > (lookbackPeriod + 1))) { avgGain = (avgGain * (lookbackPeriod - 1) + r.Gain) / lookbackPeriod; avgLoss = (avgLoss * (lookbackPeriod - 1) + r.Loss) / lookbackPeriod; if (avgLoss > 0) { decimal rs = avgGain / avgLoss; r.Rsi = 100 - (100 / (1 + rs)); } else { r.Rsi = 100; } lastRSI = (decimal)r.Rsi; } return(results); }
// AVERAGE TRUE RANGE public static IEnumerable <AtrResult> GetAtr(IEnumerable <Quote> history, int lookbackPeriod = 14) { // clean quotes Cleaners.PrepareHistory(history); // validate parameters ValidateAtr(history, lookbackPeriod); // 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; result.Atrp = (h.Close == 0) ? null : (result.Atr / h.Close) * 100; prevAtr = (decimal)result.Atr; } else if (h.Index == lookbackPeriod) { // initialize ATR sumTr += tr; result.Atr = sumTr / lookbackPeriod; result.Atrp = (h.Close == 0) ? null : (result.Atr / h.Close) * 100; prevAtr = (decimal)result.Atr; } else { // only used for periods before ATR initialization sumTr += tr; } results.Add(result); prevClose = h.Close; } return(results); }
// HULL MOVING AVERAGE public static IEnumerable <HmaResult> GetHma(IEnumerable <Quote> history, int lookbackPeriod) { // clean quotes Cleaners.PrepareHistory(history); // check parameters ValidateHma(history, lookbackPeriod); // initialize List <Quote> historyList = history.ToList(); List <Quote> synthHistory = new List <Quote>(); List <WmaResult> wmaN1 = GetWma(history, lookbackPeriod).ToList(); List <WmaResult> wmaN2 = GetWma(history, lookbackPeriod / 2).ToList(); // create interim synthetic history // roll through history for (int i = 0; i < historyList.Count; i++) { Quote h = historyList[i]; Quote sh = new Quote { Date = h.Date }; WmaResult w1 = wmaN1[i]; WmaResult w2 = wmaN2[i]; if (w1.Wma != null && w2.Wma != null) { sh.Close = (decimal)(w2.Wma * 2m - w1.Wma); synthHistory.Add(sh); } } // initialize results, add back truncated null results int sqN = (int)Math.Sqrt(lookbackPeriod); int shiftQty = lookbackPeriod - 1; List <HmaResult> results = historyList .Select(x => new HmaResult { Index = (int)x.Index, Date = x.Date }) .Where(x => x.Index <= shiftQty) .ToList(); // calculate final HMA = WMA with period SQRT(n) List <HmaResult> hmaResults = GetWma(synthHistory, sqN) .Select(x => new HmaResult { Index = x.Index + shiftQty, Date = x.Date, Hma = x.Wma }) .ToList(); // add WMA to results results.AddRange(hmaResults); results = results.OrderBy(x => x.Index).ToList(); return(results); }
// ICHIMOKU CLOUD public static IEnumerable <IchimokuResult> GetIchimoku(IEnumerable <Quote> history, int signalPeriod = 9, int shortSpanPeriod = 26, int longSpanPeriod = 52) { // clean quotes Cleaners.PrepareHistory(history); // check parameters ValidateIchimoku(history, signalPeriod, shortSpanPeriod, longSpanPeriod); // initialize List <Quote> historyList = history.ToList(); 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 if (h.Index >= signalPeriod) { List <Quote> tenkanPeriod = historyList .Where(x => x.Index > (h.Index - signalPeriod) && x.Index <= h.Index) .ToList(); decimal max = tenkanPeriod.Select(x => x.High).Max(); decimal min = tenkanPeriod.Select(x => x.Low).Min(); result.TenkanSen = (min + max) / 2; } // kijun-sen base line if (h.Index >= shortSpanPeriod) { List <Quote> kijunPeriod = historyList .Where(x => x.Index > (h.Index - shortSpanPeriod) && x.Index <= h.Index) .ToList(); decimal max = kijunPeriod.Select(x => x.High).Max(); decimal min = kijunPeriod.Select(x => x.Low).Min(); result.KijunSen = (min + max) / 2; } // senkou span A if (h.Index >= 2 * shortSpanPeriod) { IchimokuResult skq = results[(int)h.Index - shortSpanPeriod - 1]; if (skq != null && skq.TenkanSen != null && skq.KijunSen != null) { result.SenkouSpanA = (skq.TenkanSen + skq.KijunSen) / 2; } } // senkou span B if (h.Index >= shortSpanPeriod + longSpanPeriod) { List <Quote> senkauPeriod = historyList .Where(x => x.Index > (h.Index - shortSpanPeriod - longSpanPeriod) && x.Index <= h.Index - shortSpanPeriod) .ToList(); decimal max = senkauPeriod.Select(x => x.High).Max(); decimal min = senkauPeriod.Select(x => x.Low).Min(); result.SenkauSpanB = (min + max) / 2; } // chikou line if (h.Index + shortSpanPeriod <= historyList.Count) { result.ChikouSpan = historyList[(int)h.Index + shortSpanPeriod - 1].Close; } results.Add(result); } return(results); }
private static IEnumerable <RsiResult> CalcRsi(IEnumerable <BasicData> basicData, int lookbackPeriod = 14) { // clean data basicData = Cleaners.PrepareBasicData(basicData); // check parameters ValidateRsi(basicData, lookbackPeriod); // initialize decimal lastValue = basicData.First().Value; List <RsiResult> results = new List <RsiResult>(); // load gain data foreach (BasicData h in basicData) { RsiResult result = new RsiResult { Index = (int)h.Index, Date = h.Date, Gain = (h.Value > lastValue) ? h.Value - lastValue : 0, Loss = (h.Value < lastValue) ? lastValue - h.Value : 0 }; results.Add(result); lastValue = h.Value; } // initialize average gain decimal avgGain = results.Where(x => x.Index <= lookbackPeriod).Select(g => g.Gain).Average(); decimal avgLoss = results.Where(x => x.Index <= lookbackPeriod).Select(g => g.Loss).Average(); // initial first record decimal lastRSI = (avgLoss > 0) ? 100 - (100 / (1 + (avgGain / avgLoss))) : 100; bool? lastIsIncreasing = null; RsiResult first = results.Where(x => x.Index == lookbackPeriod + 1).FirstOrDefault(); first.Rsi = lastRSI; // calculate RSI foreach (RsiResult r in results.Where(x => x.Index > (lookbackPeriod + 1)).OrderBy(d => d.Index)) { avgGain = (avgGain * (lookbackPeriod - 1) + r.Gain) / lookbackPeriod; avgLoss = (avgLoss * (lookbackPeriod - 1) + r.Loss) / lookbackPeriod; if (avgLoss > 0) { decimal rs = avgGain / avgLoss; r.Rsi = 100 - (100 / (1 + rs)); } else { r.Rsi = 100; } if (r.Rsi > lastRSI) { r.IsIncreasing = true; } else if (r.Rsi < lastRSI) { r.IsIncreasing = false; } else { // no change, keep trend r.IsIncreasing = lastIsIncreasing; } lastRSI = (decimal)r.Rsi; lastIsIncreasing = r.IsIncreasing; } return(results); }
// STOCHASTIC RSI public static IEnumerable <StochRsiResult> GetStochRsi(IEnumerable <Quote> history, int lookbackPeriod = 14) { // clean quotes history = Cleaners.PrepareHistory(history); // validate parameters ValidateStochRsi(history, lookbackPeriod); // 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; bool? lastIsIncreasing = null; foreach (StochRsiResult r in results.Where(x => x.Index >= 2 * lookbackPeriod).OrderBy(d => d.Index)) { if (r.Index >= 2 * lookbackPeriod + 1) { if (r.StochRsi > lastRSI) { r.IsIncreasing = true; } else if (r.StochRsi < lastRSI) { r.IsIncreasing = false; } else { // no change, keep trend r.IsIncreasing = lastIsIncreasing; } } lastRSI = r.StochRsi; lastIsIncreasing = r.IsIncreasing; } 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 IEnumerable <EmaResult> emaFast = GetEma(history, fastPeriod); IEnumerable <EmaResult> emaSlow = GetEma(history, slowPeriod); List <Quote> emaDiff = new List <Quote>(); List <MacdResult> results = new List <MacdResult>(); foreach (Quote h in history) { EmaResult df = emaFast.Where(x => x.Date == h.Date).FirstOrDefault(); EmaResult ds = emaSlow.Where(x => x.Date == h.Date).FirstOrDefault(); 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 Quote diff = new Quote { Date = h.Date, Index = h.Index - slowPeriod, Close = macd }; emaDiff.Add(diff); } results.Add(result); } IEnumerable <EmaResult> emaSignal = GetEma(emaDiff, signalPeriod); decimal?prevMacd = null; decimal?prevSignal = null; // add signal, histogram to result foreach (MacdResult r in results) { EmaResult ds = emaSignal.Where(x => x.Date == r.Date).FirstOrDefault(); if (ds?.Ema == null) { continue; } r.Signal = ds.Ema; r.Histogram = r.Macd - r.Signal; // trend and divergence if (prevMacd != null && prevSignal != null) { r.IsBullish = (r.Macd > r.Signal); r.IsDiverging = (Math.Abs((decimal)r.Macd - (decimal)r.Signal) > Math.Abs((decimal)prevMacd - (decimal)prevSignal)); } // store for next iteration prevMacd = r.Macd; prevSignal = r.Signal; } return(results); }
// Money Flow Index public static IEnumerable <MfiResult> GetMfi(IEnumerable <Quote> history, int lookbackPeriod = 14) { // clean quotes List <Quote> historyList = Cleaners.PrepareHistory(history).ToList(); // check parameters ValidateMfi(history, lookbackPeriod); // initialize List <MfiResult> results = new List <MfiResult>(); decimal?prevTP = null; // preliminary data for (int i = 0; i < historyList.Count; i++) { Quote h = historyList[i]; MfiResult result = new MfiResult { Index = (int)h.Index, Date = h.Date, TruePrice = (h.High + h.Low + h.Close) / 3 }; // raw money flow result.RawMF = result.TruePrice * h.Volume; // direction if (prevTP == null || result.TruePrice == prevTP) { result.Direction = 0; } else if (result.TruePrice > prevTP) { result.Direction = 1; } else if (result.TruePrice < prevTP) { result.Direction = -1; } results.Add(result); prevTP = result.TruePrice; } // add money flow index for (int i = lookbackPeriod; i < results.Count; i++) { MfiResult r = results[i]; decimal sumPosMFs = 0; decimal sumNegMFs = 0; for (int p = r.Index - lookbackPeriod; p < r.Index; p++) { MfiResult d = results[p]; if (d.Direction == 1) { sumPosMFs += d.RawMF; } else if (d.Direction == -1) { sumNegMFs += d.RawMF; } } // handle no negative case if (sumNegMFs == 0) { r.Mfi = 100; continue; } // calculate MFI normally decimal mfRatio = sumPosMFs / sumNegMFs; r.Mfi = 100 - (100 / (1 + mfRatio)); } return(results); }
// CORRELATION COEFFICIENT public static IEnumerable <CorrResult> GetCorrelation( IEnumerable <Quote> historyA, IEnumerable <Quote> historyB, int lookbackPeriod) { // clean quotes List <Quote> historyListA = Cleaners.PrepareHistory(historyA).ToList(); List <Quote> historyListB = Cleaners.PrepareHistory(historyB).ToList(); // validate parameters ValidateCorrelation(historyA, historyB, lookbackPeriod); // initialize List <CorrResult> results = new List <CorrResult>(); // roll through history for interim data for (int i = 0; i < historyListA.Count; i++) { Quote a = historyListA[i]; Quote b = historyListB[i]; if (a.Date != b.Date) { throw new BadHistoryException( "Date sequence does not match. Correlation requires matching dates in provided histories."); } CorrResult r = new CorrResult { Index = (int)a.Index, Date = a.Date, PriceA = a.Close, PriceB = b.Close, PriceA2 = a.Close * a.Close, PriceB2 = b.Close * b.Close, PriceAB = a.Close * b.Close }; results.Add(r); // compute correlation if (i + 1 >= lookbackPeriod) { decimal sumPriceA = 0m; decimal sumPriceB = 0m; decimal sumPriceA2 = 0m; decimal sumPriceB2 = 0m; decimal sumPriceAB = 0m; for (int p = r.Index - lookbackPeriod; p < r.Index; p++) { CorrResult d = results[p]; sumPriceA += d.PriceA; sumPriceB += d.PriceB; sumPriceA2 += d.PriceA2; sumPriceB2 += d.PriceB2; sumPriceAB += d.PriceAB; } decimal avgA = sumPriceA / lookbackPeriod; decimal avgB = sumPriceB / lookbackPeriod; decimal avgA2 = sumPriceA2 / lookbackPeriod; decimal avgB2 = sumPriceB2 / lookbackPeriod; decimal avgAB = sumPriceAB / lookbackPeriod; 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)); r.RSquared = r.Correlation * r.Correlation; } } return(results); }
// ULTIMATE OSCILLATOR public static IEnumerable <UltimateResult> GetUltimate( IEnumerable <Quote> history, int shortPeriod = 7, int middlePeriod = 14, int longPeriod = 28) { // clean quotes List <Quote> historyList = Cleaners.PrepareHistory(history).ToList(); // check parameters ValidateUltimate(history, shortPeriod, middlePeriod, longPeriod); // initialize List <UltimateResult> results = new List <UltimateResult>(); decimal priorClose = 0; // roll through history for (int i = 0; i < historyList.Count; i++) { Quote h = historyList[i]; UltimateResult r = new UltimateResult { Index = (int)h.Index, Date = h.Date }; results.Add(r); if (i > 0) { r.Bp = h.Close - Math.Min(h.Low, priorClose); r.Tr = Math.Max(h.High, priorClose) - Math.Min(h.Low, priorClose); } if (h.Index >= longPeriod + 1) { decimal sumBP1 = 0m; decimal sumBP2 = 0m; decimal sumBP3 = 0m; decimal sumTR1 = 0m; decimal sumTR2 = 0m; decimal sumTR3 = 0m; for (int p = (int)h.Index - longPeriod; p < h.Index; p++) { UltimateResult pr = results[p]; // short aggregate if (pr.Index > h.Index - shortPeriod) { sumBP1 += (decimal)pr.Bp; sumTR1 += (decimal)pr.Tr; } // middle aggregate if (pr.Index > h.Index - middlePeriod) { sumBP2 += (decimal)pr.Bp; sumTR2 += (decimal)pr.Tr; } // long aggregate sumBP3 += (decimal)pr.Bp; sumTR3 += (decimal)pr.Tr; } decimal avg1 = sumBP1 / sumTR1; decimal avg2 = sumBP2 / sumTR2; decimal avg3 = sumBP3 / sumTR3; r.Ultimate = 100 * (4m * avg1 + 2m * avg2 + avg3) / 7m; } priorClose = h.Close; } return(results); }
// SIMPLE MOVING AVERAGE public static IEnumerable <SmaResult> GetSma( IEnumerable <Quote> history, int lookbackPeriod, bool extended = false) { // clean quotes List <Quote> historyList = Cleaners.PrepareHistory(history).ToList(); // check parameters ValidateSma(history, lookbackPeriod); // initialize List <SmaResult> results = new List <SmaResult>(); // roll through history for (int i = 0; i < historyList.Count; i++) { Quote h = historyList[i]; SmaResult result = new SmaResult { Index = (int)h.Index, Date = h.Date }; if (h.Index >= lookbackPeriod) { decimal sumSma = 0m; for (int p = (int)h.Index - lookbackPeriod; p < h.Index; p++) { Quote d = historyList[p]; sumSma += d.Close; } result.Sma = sumSma / lookbackPeriod; // add optional extended values if (extended) { decimal sumMad = 0m; decimal sumMse = 0m; decimal sumMape = 0m; for (int p = (int)h.Index - lookbackPeriod; p < h.Index; p++) { Quote d = historyList[p]; sumMad += Math.Abs(d.Close - (decimal)result.Sma); sumMse += (d.Close - (decimal)result.Sma) * (d.Close - (decimal)result.Sma); sumMape += Math.Abs(d.Close - (decimal)result.Sma) / d.Close; } // mean absolute deviation result.Mad = sumMad / lookbackPeriod; // mean squared error result.Mse = sumMse / lookbackPeriod; // mean absolute percent error result.Mape = sumMape / lookbackPeriod; } } results.Add(result); } return(results); }
// STOCHASTIC OSCILLATOR public static IEnumerable <StochResult> GetStoch(IEnumerable <Quote> history, int lookbackPeriod = 14, int signalPeriod = 3, int smoothPeriod = 3) { // clean quotes Cleaners.PrepareHistory(history); // validate parameters ValidateStoch(history, lookbackPeriod, signalPeriod, smoothPeriod); // initialize List <Quote> historyList = history.ToList(); List <StochResult> results = new List <StochResult>(); // oscillator for (int i = 0; i < historyList.Count; i++) { Quote h = historyList[i]; StochResult result = new StochResult { 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(); decimal lowLow = period.Select(v => v.Low).Min(); decimal highHigh = period.Select(v => v.High).Max(); if (lowLow != highHigh) { result.Oscillator = 100 * ((h.Close - lowLow) / (highHigh - lowLow)); } else { result.Oscillator = 0; } } results.Add(result); } // smooth the oscillator if (smoothPeriod > 1) { results = SmoothOscillator(results, lookbackPeriod, smoothPeriod); } // signal and period direction info int stochIndex = lookbackPeriod + smoothPeriod - 1; foreach (StochResult r in results.Where(x => x.Index >= stochIndex)) { // add signal int signalIndex = lookbackPeriod + smoothPeriod + signalPeriod - 2; if (signalPeriod <= 1) { r.Signal = r.Oscillator; } else if (r.Index >= signalIndex) { r.Signal = results .Where(x => x.Index > (r.Index - signalPeriod) && x.Index <= r.Index) .ToList() .Select(v => v.Oscillator) .Average(); } } return(results); }
// PARABOLIC SAR public static IEnumerable <ParabolicSarResult> GetParabolicSar( IEnumerable <Quote> history, decimal accelerationStep = (decimal)0.02, decimal maxAccelerationFactor = (decimal)0.2) { // clean quotes Cleaners.PrepareHistory(history); // check parameters ValidateParabolicSar(history, accelerationStep, maxAccelerationFactor); // initialize List <Quote> historyList = history.ToList(); List <ParabolicSarResult> results = new List <ParabolicSarResult>(); Quote first = historyList[0]; decimal accelerationFactor = accelerationStep; decimal extremePoint = first.High; decimal priorSar = first.Low; bool isRising = true; // initial guess // roll through history for (int i = 0; i < historyList.Count; i++) { Quote h = historyList[i]; ParabolicSarResult result = new ParabolicSarResult { Index = (int)h.Index, Date = h.Date }; // skip first one if (h.Index == 1) { results.Add(result); continue; } // was rising if (isRising) { decimal currentSar = priorSar + accelerationFactor * (extremePoint - priorSar); // turn down if (h.Low < currentSar) { result.IsReversal = true; result.Sar = extremePoint; isRising = false; accelerationFactor = accelerationStep; extremePoint = h.Low; } // continue rising else { result.IsReversal = false; result.Sar = currentSar; // SAR cannot be higher than last two lows if (i >= 2) { decimal minLastTwo = Math.Min(historyList[i - 1].Low, historyList[i - 2].Low); result.Sar = Math.Min((decimal)result.Sar, minLastTwo); } else { result.Sar = (decimal)result.Sar; } if (h.High > extremePoint) { extremePoint = h.High; accelerationFactor = Math.Min(accelerationFactor += accelerationStep, maxAccelerationFactor); } } } // was falling else { decimal currentSar = priorSar - accelerationFactor * (priorSar - extremePoint); // turn up if (h.High > currentSar) { result.IsReversal = true; result.Sar = extremePoint; isRising = true; accelerationFactor = accelerationStep; extremePoint = h.High; } // continue falling else { result.IsReversal = false; result.Sar = currentSar; // SAR cannot be lower than last two highs if (i >= 2) { decimal maxLastTwo = Math.Max(historyList[i - 1].High, historyList[i - 2].High); result.Sar = Math.Max((decimal)result.Sar, maxLastTwo); } else { result.Sar = (decimal)result.Sar; } if (h.Low < extremePoint) { extremePoint = h.Low; accelerationFactor = Math.Min(accelerationFactor += accelerationStep, maxAccelerationFactor); } } } priorSar = (decimal)result.Sar; results.Add(result); } // remove first trend to reversal, since it is an invalid guess ParabolicSarResult firstReversal = results .Where(x => x.IsReversal == true) .OrderBy(x => x.Index) .FirstOrDefault(); if (firstReversal != null) { foreach (ParabolicSarResult r in results.Where(x => x.Index <= firstReversal.Index)) { r.Sar = null; r.IsReversal = null; } } return(results); }
// AVERAGE DIRECTIONAL INDEX public static IEnumerable <AdxResult> GetAdx(IEnumerable <Quote> history, int lookbackPeriod = 14) { // clean quotes Cleaners.PrepareHistory(history); // verify parameters ValidateAdx(history, lookbackPeriod); // initialize results and working variables List <Quote> historyList = history.ToList(); List <AdxResult> results = new List <AdxResult>(); List <AtrResult> atrResults = GetAtr(history, lookbackPeriod).ToList(); // uses True Range value decimal prevHigh = 0; decimal prevLow = 0; decimal prevTrs = 0; // smoothed decimal prevPdm = 0; decimal prevMdm = 0; decimal prevAdx = 0; decimal sumTr = 0; decimal sumPdm = 0; decimal sumMdm = 0; decimal sumDx = 0; // roll through history for (int i = 0; i < historyList.Count; i++) { Quote h = historyList[i]; AdxResult result = new AdxResult { Index = (int)h.Index, Date = h.Date }; // skip first period if (h.Index == 1) { results.Add(result); prevHigh = h.High; prevLow = h.Low; continue; } decimal tr = (decimal)atrResults[i].Tr; decimal pdm1 = (h.High - prevHigh) > (prevLow - h.Low) ? Math.Max(h.High - prevHigh, 0) : 0; decimal mdm1 = (prevLow - h.Low) > (h.High - prevHigh) ? Math.Max(prevLow - h.Low, 0) : 0; prevHigh = h.High; prevLow = h.Low; // initialization period if (h.Index <= lookbackPeriod + 1) { sumTr += tr; sumPdm += pdm1; sumMdm += mdm1; } // skip DM initialization period if (h.Index <= lookbackPeriod) { results.Add(result); continue; } // smoothed true range and directional movement decimal trs; decimal pdm; decimal mdm; if (h.Index == lookbackPeriod + 1) { trs = sumTr / lookbackPeriod; pdm = sumPdm / lookbackPeriod; mdm = sumMdm / lookbackPeriod; } else { trs = prevTrs - (prevTrs / lookbackPeriod) + tr; pdm = prevPdm - (prevPdm / lookbackPeriod) + pdm1; mdm = prevMdm - (prevMdm / lookbackPeriod) + mdm1; } prevTrs = trs; prevPdm = pdm; prevMdm = mdm; // directional increments decimal pdi = 100 * pdm / trs; decimal mdi = 100 * mdm / trs; decimal dx = 100 * Math.Abs(pdi - mdi) / (pdi + mdi); result.Pdi = pdi; result.Mdi = mdi; // calculate ADX decimal adx; if (h.Index > 2 * lookbackPeriod) { adx = (prevAdx * (lookbackPeriod - 1) + dx) / lookbackPeriod; result.Adx = adx; prevAdx = adx; } // initial ADX else if (h.Index == 2 * lookbackPeriod) { sumDx += dx; adx = sumDx / lookbackPeriod; result.Adx = adx; prevAdx = adx; } // ADX initialization period else { sumDx += dx; } results.Add(result); } return(results); }
// HEIKIN-ASHI public static IEnumerable <HeikinAshiResult> GetHeikinAshi(IEnumerable <Quote> history) { // clean quotes history = Cleaners.PrepareHistory(history); // check parameters ValidateHeikinAshi(history); // initialize List <HeikinAshiResult> results = new List <HeikinAshiResult>(); decimal?prevOpen = null; decimal?prevClose = null; bool prevTrend = false; foreach (Quote h in history) { // close decimal close = (h.Open + h.High + h.Low + h.Close) / 4; // open decimal open = (prevOpen == null) ? (h.Open + h.Close) / 2 : (decimal)(prevOpen + prevClose) / 2; // high decimal[] arrH = { h.High, open, close }; decimal high = arrH.Max(); // low decimal[] arrL = { h.Low, open, close }; decimal low = arrL.Min(); // trend (bullish (buy / green), bearish (sell / red) // strength (size of directional shadow / no shadow is strong) bool trend; decimal strength; if (close > open) { trend = true; strength = open - low; } else if (close < open) { trend = false; strength = high - open; } else { trend = prevTrend; strength = high - low; } HeikinAshiResult result = new HeikinAshiResult { Index = (int)h.Index, Date = h.Date, Open = open, High = high, Low = low, Close = close, IsBullish = trend, Weakness = strength, }; results.Add(result); // save for next iteration prevOpen = open; prevClose = close; prevTrend = trend; } return(results); }
private static IEnumerable <RsiResult> CalcRsi(IEnumerable <BasicData> basicData, int lookbackPeriod = 14) { // clean data List <BasicData> bdList = Cleaners.PrepareBasicData(basicData).ToList(); // check parameters ValidateRsi(basicData, lookbackPeriod); // initialize decimal lastValue = bdList[0].Value; decimal avgGain = 0m; decimal avgLoss = 0m; List <RsiResult> results = new List <RsiResult>(); // roll through history for (int i = 0; i < bdList.Count; i++) { BasicData h = bdList[i]; RsiResult r = new RsiResult { Index = (int)h.Index, Date = h.Date, Gain = (h.Value > lastValue) ? h.Value - lastValue : 0, Loss = (h.Value < lastValue) ? lastValue - h.Value : 0 }; results.Add(r); lastValue = h.Value; // calculate RSI if (h.Index > lookbackPeriod + 1) { avgGain = (avgGain * (lookbackPeriod - 1) + r.Gain) / lookbackPeriod; avgLoss = (avgLoss * (lookbackPeriod - 1) + r.Loss) / lookbackPeriod; if (avgLoss > 0) { decimal rs = avgGain / avgLoss; r.Rsi = 100 - (100 / (1 + rs)); } else { r.Rsi = 100; } } // initialize average gain else if (h.Index == lookbackPeriod + 1) { decimal sumGain = 0; decimal sumLoss = 0; for (int p = 0; p < lookbackPeriod; p++) { RsiResult d = results[p]; sumGain += d.Gain; sumLoss += d.Loss; } avgGain = sumGain / lookbackPeriod; avgLoss = sumLoss / lookbackPeriod; r.Rsi = (avgLoss > 0) ? 100 - (100 / (1 + (avgGain / avgLoss))) : 100; } } return(results); }
// AVERAGE TRUE RANGE public static IEnumerable <AtrResult> GetAtr(IEnumerable <Quote> history, int lookbackPeriod = 14) { // clean quotes history = Cleaners.PrepareHistory(history); // check exceptions int qtyHistory = history.Count(); int minHistory = lookbackPeriod + 1; if (qtyHistory < minHistory) { throw new BadHistoryException("Insufficient history provided for ATR. " + string.Format("You provided {0} periods of history when {1} is required. " , qtyHistory, minHistory)); } // 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); }