/// <summary> /// Calculate Stochastic Oscillator, as described here: /// <see href="https://en.wikipedia.org/wiki/Stochastic_oscillator"/> /// </summary> /// <param name="series">input time series</param> /// <param name="n">oscillator period</param> /// <param name="parentId">caller cache id, optional</param> /// <param name="memberName">caller's member name, optional</param> /// <param name="lineNumber">caller line number, optional</param> /// <returns>Stochastic Oscillator as time series</returns> public static _StochasticOscillator StochasticOscillator(this ITimeSeries <double> series, int n = 14, CacheId parentId = null, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { var cacheId = new CacheId(parentId, memberName, lineNumber, series.GetHashCode(), n); var container = Cache <_StochasticOscillator> .GetData( cacheId, () => new _StochasticOscillator()); double hh = series .Highest(n, cacheId)[0]; double ll = series .Lowest(n, cacheId)[0]; double price = series[0]; container.PercentK = IndicatorsBasic.BufferedLambda( v => 100.0 * (price - ll) / Math.Max(1e-10, hh - ll), 50.0, cacheId); container.PercentD = container.PercentK .SMA(3, cacheId); return(container); }
/// <summary> /// Calculate Kaufman's Adaptive Moving Average, as described here: /// <see href="https://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:kaufman_s_adaptive_moving_average"/> /// </summary> /// <param name="series">input time series</param> /// <param name="erPeriod">period for efficiency ratio</param> /// <param name="fastEma">period for fast EMA</param> /// <param name="slowEma">period for slow EMA</param> /// <param name="parentId">caller cache id, optional</param> /// <param name="memberName">caller's member name, optional</param> /// <param name="lineNumber">caller line number, optional</param> /// <returns>KAMA as time series</returns> public static ITimeSeries <double> KAMA(this ITimeSeries <double> series, int erPeriod = 10, int fastEma = 2, int slowEma = 30, CacheId parentId = null, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { var cacheId = new CacheId(parentId, memberName, lineNumber, series.GetHashCode(), erPeriod, fastEma, slowEma); // TODO: we should be able to remove the try/ catch blocks here return(IndicatorsBasic.BufferedLambda( (v) => { try { double scFast = 2.0 / (1.0 + fastEma); double scSlow = 2.0 / (1.0 + slowEma); double change = Math.Abs(series[0] - series[erPeriod]); double volatility = Enumerable.Range(0, erPeriod) .Sum(t => Math.Abs(series[t] - series[t + 1])); double efficiencyRatio = change / Math.Max(1e-10, volatility); double smoothingConstant = Math.Pow(efficiencyRatio * (scFast - scSlow) + scSlow, 2); double r = smoothingConstant * (series[0] - v) + v; return double.IsNaN(r) ? 0.0 : r; } catch (Exception) { // we get here when we access bars too far in the past return series[0]; } }, series[0], cacheId)); }
/// <summary> /// Calculate Pearson Correlation Coefficient. /// <see href="https://en.wikipedia.org/wiki/Pearson_correlation_coefficient"/> /// </summary> /// <param name="series">this time series</param> /// <param name="other">other time series</param> /// <param name="n">number of bars</param> /// <param name="subSample">distance between bars</param> /// <param name="parentId">caller cache id, optional</param> /// <param name="memberName">caller's member name, optional</param> /// <param name="lineNumber">caller line number, optional</param> /// <returns>correlation coefficient time series</returns> public static ITimeSeries <double> Correlation(this ITimeSeries <double> series, ITimeSeries <double> other, int n, int subSample = 1, CacheId parentId = null, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { var cacheId = new CacheId(parentId, memberName, lineNumber, series.GetHashCode(), other.GetHashCode(), n); var x = series; var y = other; double sum = n / 2.0 * (n + 1.0); return(IndicatorsBasic.BufferedLambda( (v) => { var avgX = Enumerable.Range(0, n) .Average(t => x[t * subSample]); var avgY = Enumerable.Range(0, n) .Average(t => y[t * subSample]); var covXY = Enumerable.Range(0, n) .Sum(t => (x[t * subSample] - avgX) * (y[t * subSample] - avgY)) / n; var varX = Enumerable.Range(0, n) .Sum(t => Math.Pow(x[t * subSample] - avgX, 2.0)) / n; var varY = Enumerable.Range(0, n) .Sum(t => Math.Pow(y[t * subSample] - avgY, 2.0)) / n; var corr = covXY / Math.Max(1e-99, Math.Sqrt(varX) * Math.Sqrt(varY)); return corr; }, 0.0, cacheId)); }
/// <summary> /// Calculate Covariance. /// <see href="https://en.wikipedia.org/wiki/Covariance"/> /// </summary> /// <param name="series">this time series</param> /// <param name="other">other time series</param> /// <param name="n">number of bars</param> /// <param name="subSample">distance between bars</param> /// <param name="parentId">caller cache id, optional</param> /// <param name="memberName">caller's member name, optional</param> /// <param name="lineNumber">caller line number, optional</param> /// <returns>covariance time series</returns> public static ITimeSeries <double> Covariance(this ITimeSeries <double> series, ITimeSeries <double> other, int n, int subSample = 1, CacheId parentId = null, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { var cacheId = new CacheId(parentId, memberName, lineNumber, series.GetHashCode(), other.GetHashCode(), n); var x = series; var y = other; return(IndicatorsBasic.BufferedLambda( (v) => { var avgX = Enumerable.Range(0, n) .Average(t => x[t * subSample]); var avgY = Enumerable.Range(0, n) .Average(t => y[t * subSample]); var covXY = Enumerable.Range(0, n) .Sum(t => (x[t * subSample] - avgX) * (y[t * subSample] - avgY)) / (n - 1.0); return covXY; }, 0.0, cacheId)); }
/// <summary> /// Calculate Money Flow Index indicator /// <see href="https://en.wikipedia.org/wiki/Money_flow_index"/> /// </summary> /// <param name="series">input time series</param> /// <param name="n">calculation period</param> /// <param name="parentId">caller cache id, optional</param> /// <param name="memberName">caller's member name, optional</param> /// <param name="lineNumber">caller line number, optional</param> /// <returns>MFI time series</returns> public static ITimeSeries <double> MoneyFlowIndex(this Instrument series, int n, CacheId parentId = null, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { var cacheId = new CacheId(parentId, memberName, lineNumber, series.GetHashCode(), n); var typicalPrice = series.TypicalPrice(); var postiveMoneyFlow = IndicatorsBasic.BufferedLambda( prev => typicalPrice[0] > typicalPrice[1] ? typicalPrice[0] * series.Volume[0] : 0.0, 0.0, cacheId); var negativeMoneyFlow = IndicatorsBasic.BufferedLambda( prev => typicalPrice[0] < typicalPrice[1] ? typicalPrice[0] * series.Volume[0] : 0.0, 0.0, cacheId); return(postiveMoneyFlow .SMA(n, cacheId) .Divide( postiveMoneyFlow .SMA(n, cacheId) .Add( negativeMoneyFlow .SMA(n, cacheId), cacheId), cacheId) .Multiply(100.0, cacheId)); }
/// <summary> /// Accumulation/ distribution index as described here: /// <see href="https://en.wikipedia.org/wiki/Accumulation/distribution_index"/> /// </summary> /// <param name="series">input time series</param> /// <param name="parentId">caller cache id, optional</param> /// <param name="memberName">caller's member name, optional</param> /// <param name="lineNumber">caller line number, optional</param> /// <returns>accumulation/ distribution index as time series</returns> public static ITimeSeries <double> AccumulationDistributionIndex(this Instrument series, CacheId parentId = null, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { var cacheId = new CacheId(parentId, memberName, lineNumber, series.GetHashCode()); return(IndicatorsBasic.BufferedLambda( (v) => v + series.Volume[0] * series.CLV()[0], 0.0, cacheId)); }
/// <summary> /// Calculate simple momentum: m = p[0] / p[n] - 1 /// </summary> /// <param name="series">input time series</param> /// <param name="n">number of bars for regression</param> /// <param name="parentId">caller cache id, optional</param> /// <param name="memberName">caller's member name, optional</param> /// <param name="lineNumber">caller line number, optional</param> /// <returns>simple momentum, as time series</returns> public static ITimeSeries <double> SimpleMomentum(this ITimeSeries <double> series, int n = 21, CacheId parentId = null, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { var cacheId = new CacheId(parentId, memberName, lineNumber, series.GetHashCode(), n.GetHashCode()); return(IndicatorsBasic.BufferedLambda( prev => series[0] / series[n] - 1.0, 0.0, cacheId)); }
/// <summary> /// Calculate Simple Moving Average as described here: /// <see href="https://en.wikipedia.org/wiki/Moving_average#Simple_moving_average"/> /// </summary> /// <param name="series">input time series</param> /// <param name="n">averaging length</param> /// <param name="parentId">caller cache id, optional</param> /// <param name="memberName">caller's member name, optional</param> /// <param name="lineNumber">caller line number, optional</param> /// <returns>SMA time series</returns> public static ITimeSeries <double> SMA(this ITimeSeries <double> series, int n, CacheId parentId = null, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { var cacheId = new CacheId(parentId, memberName, lineNumber, series.GetHashCode(), n); return(IndicatorsBasic.BufferedLambda( v => Enumerable.Range(0, n) .Sum(t => series[t]) / n, series[0], cacheId)); }
/// <summary> /// Calculate On-Balance Volume indicator. /// <see href="https://en.wikipedia.org/wiki/On-balance_volume"/> /// </summary> /// <param name="series">input time series</param> /// <param name="parentId">caller cache id, optional</param> /// <param name="memberName">caller's member name, optional</param> /// <param name="lineNumber">caller line number, optional</param> /// <returns>OBV time series</returns> public static ITimeSeries <double> OnBalanceVolume(this Instrument series, CacheId parentId = null, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { var cacheId = new CacheId(parentId, memberName, lineNumber, series.GetHashCode()); return(IndicatorsBasic.BufferedLambda( prev => series.Close[0] > series.Close[1] ? prev + series.Volume[0] : prev - series.Volume[0], 0, cacheId)); }
/// <summary> /// Calculate standard deviation, based on exponentially weighted filters. This is an /// incremental calculation, based on Tony Finch, which is very fast and efficient. /// </summary> /// <param name="series">input time series</param> /// <param name="n">filtering length</param> /// <param name="parentId">caller cache id, optional</param> /// <param name="memberName">caller's member name, optional</param> /// <param name="lineNumber">caller line number, optional</param> /// <returns>variance as time series</returns> public static _SemiDeviation SemiDeviation(this ITimeSeries <double> series, int n = 10, CacheId parentId = null, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { var cacheId = new CacheId(parentId, memberName, lineNumber, series.GetHashCode(), n); var container = Cache <_SemiDeviation> .GetData( cacheId, () => new _SemiDeviation()); container.Average = series.SMA(n); container.Downside = IndicatorsBasic.BufferedLambda( v => { var downSeries = Enumerable.Range(0, n) .Where(t => series[t] < container.Average[0]); if (downSeries.Count() == 0) { return(0.0); } else { return(Math.Sqrt(downSeries .Average(t => Math.Pow(series[t] - container.Average[0], 2.0)))); } }, 0.0, cacheId); container.Upside = IndicatorsBasic.BufferedLambda( v => { var upSeries = Enumerable.Range(0, n) .Where(t => series[t] > container.Average[0]); if (upSeries.Count() == 0) { return(0.0); } else { return(Math.Sqrt(upSeries .Average(t => Math.Pow(series[t] - container.Average[0], 2.0)))); } }, 0.0, cacheId); return(container); }
/// <summary> /// Calculate return over maximum drawdown. /// </summary> /// <param name="series">input time series</param> /// <param name="n">length of observation window</param> /// <param name="parentId">caller cache id, optional</param> /// <param name="memberName">caller's member name, optional</param> /// <param name="lineNumber">caller line number, optional</param> /// <returns>RoMaD</returns> public static ITimeSeries <double> ReturnOnMaxDrawdown(this ITimeSeries <double> series, int n, CacheId parentId = null, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { var cacheId = new CacheId(parentId, memberName, lineNumber, series.GetHashCode(), n); return(IndicatorsBasic.BufferedLambda( (p) => { double ret = series[0] / series[n] - 1.0; double dd = series.MaxDrawdown(n)[0]; return ret / Math.Max(1e-3, dd); }, 0.0, cacheId)); }
/// <summary> /// Calculate Average Directional Movement Index. /// <see href="https://en.wikipedia.org/wiki/Average_directional_movement_index"/> /// </summary> /// <param name="series">input OHLC time series</param> /// <param name="n">smoothing length</param> /// <param name="parentId">caller cache id, optional</param> /// <param name="memberName">caller's member name, optional</param> /// <param name="lineNumber">caller line number, optional</param> /// <returns>ADX time series</returns> public static ITimeSeries <double> ADX(this Instrument series, int n = 14, CacheId parentId = null, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { var cacheId = new CacheId(parentId, memberName, lineNumber, series.GetHashCode(), n); var upMove = Math.Max(0.0, series.High[0] - series.High[1]); var downMove = Math.Max(0.0, series.Low[1] - series.Low[0]); var plusDM = IndicatorsBasic.BufferedLambda( prev => upMove > downMove ? upMove : 0.0, 0.0, cacheId); var minusDM = IndicatorsBasic.BufferedLambda( prev => downMove > upMove ? downMove : 0.0, 0.0, cacheId); //var atr = series.AverageTrueRange(n); // +DI = 100 * Smoothed+DM / ATR var plusDI = plusDM .EMA(n, cacheId); //.Divide(atr) //.Multiply(100.0); // -DI = 100 * Smoothed-DM / ATR var minusDI = minusDM .EMA(n, cacheId); //.Divide(atr) //.Multiply(100.0); // DX = Abs(+DI - -DI) / (+DI + -DI) var DX = IndicatorsBasic.BufferedLambda( prev => 100.0 * Math.Abs(plusDI[0] - minusDI[0]) / (plusDI[0] + minusDI[0]), 0.0, cacheId); // ADX = (13 * ADX[1] + DX) / 14 var ADX = DX .EMA(n, cacheId); //.Multiply(100.0); return(ADX); }
/// <summary> /// Calculate Williams %R, as described here: /// <see href="https://en.wikipedia.org/wiki/Williams_%25R"/> /// </summary> /// <param name="series">input time series</param> /// <param name="n">period</param> /// <param name="parentId">caller cache id, optional</param> /// <param name="memberName">caller's member name, optional</param> /// <param name="lineNumber">caller line number, optional</param> /// <returns>Williams %R as time series</returns> public static ITimeSeries <double> WilliamsPercentR(this ITimeSeries <double> series, int n = 10, CacheId parentId = null, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { var cacheId = new CacheId(parentId, memberName, lineNumber, series.GetHashCode(), n); return(IndicatorsBasic.BufferedLambda( (v) => { double hh = series.Highest(n)[0]; double ll = series.Lowest(n)[0]; double price = series[0]; return -100.0 * (hh - price) / Math.Max(1e-10, hh - ll); }, -50.0, cacheId)); }
/// <summary> /// Calculate volatility estimate from recent trading range. /// </summary> /// <param name="series">input time series</param> /// <param name="n">length of calculation window</param> /// <param name="parentId">caller cache id, optional</param> /// <param name="memberName">caller's member name, optional</param> /// <param name="lineNumber">caller line number, optional</param> /// <returns>volatility as time series</returns> public static ITimeSeries <double> VolatilityFromRange(this ITimeSeries <double> series, int n, CacheId parentId = null, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { var cacheId = new CacheId(parentId, memberName, lineNumber, series.GetHashCode(), n); return(IndicatorsBasic.BufferedLambda( (v) => { double hi = series.Highest(n)[0]; double lo = series.Lowest(n)[0]; return 0.80 * Math.Sqrt(1.0 / n) * Math.Log(hi / lo); }, 0.0, cacheId)); }
/// <summary> /// Calculate Exponential Moving Average, as described here: /// <see href="https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average"/> /// </summary> /// <param name="series">input time series</param> /// <param name="n">averaging length</param> /// <param name="parentId">caller cache id, optional</param> /// <param name="memberName">caller's member name, optional</param> /// <param name="lineNumber">caller line number, optional</param> /// <returns>EMA time series</returns> public static ITimeSeries <double> EMA(this ITimeSeries <double> series, int n, CacheId parentId = null, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { var cacheId = new CacheId(parentId, memberName, lineNumber, series.GetHashCode(), n); double alpha = 2.0 / (Math.Max(1, n) + 1); return(IndicatorsBasic.BufferedLambda( (v) => { double r = alpha * (series[0] - v) + v; return double.IsNaN(r) ? 0.0 : r; }, series[0], cacheId)); }
/// <summary> /// Calculate equally-weighted market benchmark. /// </summary> /// <param name="market">enumerable of instruments making up market</param> /// <param name="parentId">caller cache id, optional</param> /// <param name="memberName">caller's member name, optional</param> /// <param name="lineNumber">caller line number, optional</param> /// <returns>benchmark time series</returns> public static ITimeSeries <double> Benchmark(this IEnumerable <Instrument> market, CacheId parentId = null, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { // TODO: need to figure out, if we want to include the market in the calcualtion // of the cache id. for now, we decided to _not_ include the market, // as instruments included might change, for the _same_ market var cacheId = new CacheId(parentId, memberName, lineNumber ); return(IndicatorsBasic.BufferedLambda( (p) => { double todaysLogReturn = market .Average(i => i.Close.LogReturn()[0]); return p * Math.Exp(todaysLogReturn); }, 1.0, cacheId)); }
/// <summary> /// Return maximum drawdown. /// </summary> /// <param name="series">input time series</param> /// <param name="n">length of observation window</param> /// <param name="parentId">caller cache id, optional</param> /// <param name="memberName">caller's member name, optional</param> /// <param name="lineNumber">caller line number, optional</param> /// <returns>maximum drawdown as time series</returns> public static ITimeSeries <double> MaxDrawdown(this ITimeSeries <double> series, int n, CacheId parentId = null, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { var cacheId = new CacheId(parentId, memberName, lineNumber, series.GetHashCode(), n); return(IndicatorsBasic.BufferedLambda( (p) => { double highestHigh = 0.0; double maxDrawdown = 0.0; for (int t = n - 1; t >= 0; t--) { highestHigh = Math.Max(highestHigh, series[t]); maxDrawdown = Math.Max(maxDrawdown, 1.0 - series[t] / highestHigh); } return maxDrawdown; }, 0.0, cacheId)); }
/// <summary> /// Calculate historical standard deviation. /// </summary> /// <param name="series">input time series</param> /// <param name="n">length</param> /// <param name="parentId">caller cache id, optional</param> /// <param name="memberName">caller's member name, optional</param> /// <param name="lineNumber">caller line number, optional</param> /// <returns>standard deviation as time series</returns> public static ITimeSeries <double> StandardDeviation(this ITimeSeries <double> series, int n = 10, CacheId parentId = null, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { var cacheId = new CacheId(parentId, memberName, lineNumber, series.GetHashCode(), n); // see https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance // TODO: (1) rewrite using Linq. See WMA implementation // (2) we should be able to remove try/catch blocks return(IndicatorsBasic.BufferedLambda( (v) => { double sum = 0.0; double sum2 = 0.0; int num = 0; try { for (int t = 0; t < n; t++) { sum += series[t]; sum2 += series[t] * series[t]; num++; } } catch (Exception) { // we get here when we access bars too far in the past } double variance = num > 1 ? (sum2 - sum * sum / num) / (num - 1) : 0.0; return Math.Sqrt(Math.Max(0.0, variance)); }, 0.0, cacheId)); }
/// <summary> /// Calculate Bollinger Bands, as described here: /// <see href="https://traderhq.com/ultimate-guide-to-bollinger-bands/"/>. /// </summary> /// <param name="series">input time series</param> /// <param name="n">length of calculation</param> /// <param name="stdev">width of bands</param> /// <param name="parentId">caller cache id, optional</param> /// <param name="memberName">caller's member name, optional</param> /// <param name="lineNumber">caller line number, optional</param> /// <returns>Bollinger Band time series</returns> public static _BollingerBands BollingerBands(this ITimeSeries <double> series, int n = 20, double stdev = 2.0, CacheId parentId = null, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { var cacheId = new CacheId(parentId, memberName, lineNumber, series.GetHashCode(), n, stdev.GetHashCode()); var container = Cache <_BollingerBands> .GetData( cacheId, () => new _BollingerBands()); var stdevSeries = series.StandardDeviation(n, cacheId).Multiply(stdev, cacheId); container.Middle = series.SMA(n, cacheId); container.Upper = container.Middle.Add(stdevSeries, cacheId); container.Lower = container.Middle.Subtract(stdevSeries, cacheId); container.PercentB = IndicatorsBasic.BufferedLambda( prev => (series[0] - container.Lower[0]) / Math.Max(1e-10, container.Upper[0] - container.Lower[0]), 0.0, cacheId); return(container); }
/// <summary> /// Calculate logarithmic momentum: m = Ln(p[0] / p[n]) /// </summary> /// <param name="series">input time series</param> /// <param name="n">number of bars for regression</param> /// <param name="parentId">caller cache id, optional</param> /// <param name="memberName">caller's member name, optional</param> /// <param name="lineNumber">caller line number, optional</param> /// <returns>log momentum, as time series</returns> public static ITimeSeries <double> LogMomentum(this ITimeSeries <double> series, int n = 21, CacheId parentId = null, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { var cacheId = new CacheId(parentId, memberName, lineNumber, series.GetHashCode(), n.GetHashCode()); #if true return(IndicatorsBasic.BufferedLambda( prev => Math.Log(series[0] / series[n]), 0.0, cacheId)); #else // retired 04/02/2019 return(series .Divide(series .Delay(n, cacheId) .Max(1e-10, cacheId), cacheId) .Log(cacheId) .Divide(n, cacheId)); #endif }
/// <summary> /// Calculate True Strength Index of input time series, as described here: /// <see href="https://en.wikipedia.org/wiki/True_strength_index"/> /// </summary> /// <param name="series">input time series</param> /// <param name="r">smoothing period for momentum</param> /// <param name="s">smoothing period for smoothed momentum</param> /// <param name="parentId">caller cache id, optional</param> /// <param name="memberName">caller's member name, optional</param> /// <param name="lineNumber">caller line number, optional</param> /// <returns>TSI time series</returns> public static ITimeSeries <double> TSI(this ITimeSeries <double> series, int r = 25, int s = 13, CacheId parentId = null, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { var cacheId = new CacheId(parentId, memberName, lineNumber, series.GetHashCode(), r, s); ITimeSeries <double> momentum = series .Return(cacheId); double numerator = momentum .EMA(r, cacheId) .EMA(s, cacheId)[0]; double denominator = momentum .AbsValue(cacheId) .EMA(r, cacheId) .EMA(s, cacheId)[0]; return(IndicatorsBasic.BufferedLambda( v => 100.0 * numerator / Math.Max(1e-10, denominator), 0.5, cacheId)); }
/// <summary> /// Calculate Relative Strength Index, as described here: /// <see href="https://en.wikipedia.org/wiki/Relative_strength_index"/> /// </summary> /// <param name="series">input time series</param> /// <param name="n">smoothing period</param> /// <param name="parentId">caller cache id, optional</param> /// <param name="memberName">caller's member name, optional</param> /// <param name="lineNumber">caller line number, optional</param> /// <returns>RSI time series</returns> public static ITimeSeries <double> RSI(this ITimeSeries <double> series, int n = 14, CacheId parentId = null, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { var cacheId = new CacheId(parentId, memberName, lineNumber, series.GetHashCode(), n); ITimeSeries <double> returns = series.Return(cacheId); double avgUp = returns .Max(0.0, cacheId) .EMA(n, cacheId)[0]; double avgDown = -returns .Min(0.0, cacheId) .EMA(n, cacheId)[0]; double rs = avgUp / Math.Max(1e-10, avgDown); return(IndicatorsBasic.BufferedLambda( v => 100.0 - 100.0 / (1 + rs), 50.0, cacheId)); }
/// <summary> /// Calculate Commodity Channel Index of input time series, as described here: /// <see href="https://en.wikipedia.org/wiki/Commodity_channel_index"/> /// </summary> /// <param name="series">input time series</param> /// <param name="n">averaging length</param> /// <param name="parentId">caller cache id, optional</param> /// <param name="memberName">caller's member name, optional</param> /// <param name="lineNumber">caller line number, optional</param> /// <returns>CCI time series</returns> public static ITimeSeries <double> CCI(this ITimeSeries <double> series, int n = 20, CacheId parentId = null, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { var cacheId = new CacheId(parentId, memberName, lineNumber, series.GetHashCode(), n); return(IndicatorsBasic.BufferedLambda( (v) => { ITimeSeries <double> delta = series .Subtract( series .SMA(n, cacheId), cacheId); ITimeSeries <double> meanDeviation = delta .AbsValue(cacheId) .SMA(n, cacheId); return delta[0] / Math.Max(1e-10, 0.015 * meanDeviation[0]); }, 0.5, cacheId)); }