Overall Statistics |
Total Trades 38 Average Win 2.77% Average Loss -3.36% Compounding Annual Return -7.663% Drawdown 32.100% Expectancy -0.232 Net Profit -27.848% Sharpe Ratio -0.552 Loss Rate 58% Win Rate 42% Profit-Loss Ratio 0.83 Alpha -0.105 Beta 0.197 Annual Standard Deviation 0.129 Annual Variance 0.017 Information Ratio -1.376 Tracking Error 0.176 Treynor Ratio -0.362 |
using System; using System.Collections; using System.Collections.Generic; using QuantConnect.Securities; using QuantConnect.Models; namespace QuantConnect { public class TrailingStop { TradeBar mBar = new TradeBar(); decimal mTrailingStopDollars = 0.0m; decimal mBuyPrice = 0.0m; decimal mStop = 0.0m; bool mWentInFavour = false; bool mIsLong = true; public TrailingStop(TradeBar iBar, bool iIsLong = true, decimal ADR = 0, decimal iInitialADRStopRatio = 0.1m, decimal iTrailingStopDollars = 0.10m) { mIsLong = iIsLong; mBar = iBar; mTrailingStopDollars = iTrailingStopDollars; mBuyPrice = iBar.Close; mWentInFavour = false; if (mIsLong) { mStop = iBar.Low - iInitialADRStopRatio * ADR; } else { mStop = iBar.High + iInitialADRStopRatio * ADR; } } public decimal Stop { get{ return mStop;} } public bool StopSignal () { bool wReturn; if(mIsLong) { wReturn = mBar.Low <= mStop; } else { wReturn = mBar.High >= mStop; } return wReturn; } public bool Update(TradeBar iNewBar) { bool wReturn = false; if (mIsLong) { mWentInFavour = mWentInFavour || iNewBar.Close > mBuyPrice; if (mWentInFavour && (iNewBar.Low - mTrailingStopDollars) > mStop) { mStop = System.Math.Min(iNewBar.Low, mBar.Low) - mTrailingStopDollars; } wReturn = iNewBar.Low <= mStop; } else { mWentInFavour = mWentInFavour || iNewBar.Close < mBuyPrice; if (mWentInFavour && (iNewBar.High + mTrailingStopDollars) < mStop) { mStop = System.Math.Max(iNewBar.High, mBar.High) + mTrailingStopDollars; } wReturn = iNewBar.High >= mStop; } mBar = iNewBar; return wReturn; } public void Renew(TradeBar iBar, bool iIsLong = true, decimal ADR = 0, decimal iInitialADRStopRatio = 0.1m, decimal iTrailingStopDollars = 0.10m) { mIsLong = iIsLong; mBar = iBar; mTrailingStopDollars = iTrailingStopDollars; mBuyPrice = iBar.Close; mWentInFavour = false; mStop = iBar.Low - iInitialADRStopRatio * ADR; } } }
using System; using System.Collections; using System.Collections.Generic; using QuantConnect.Securities; using QuantConnect.Models; /* See: http://www.tradingmarkets.com/recent/a_simple_day_trading_strategy-641402.html TODO: - Try implementing trailing stops - Implement other stop criterias given in the site above - Use more stocks. */ namespace QuantConnect { // Name your algorithm class anything, as long as it inherits QCAlgorithm public class ShortSPXUAlgorithm : QCAlgorithm { //---------------------------------------------------- // Parameters //---------------------------------------------------- static int cIntervalMinutes = 390; static int cExitHour = 20; static int cEnterHour = 0; static int cSellAfterNPeriods = 5; static bool cLiquidateAtEndOfDay = false; static string cSymbol = "ERX"; static bool mAllowLonging = true; static bool mAllowShorting = true; static bool mValidateWithDailySignal = true; //---------------------------------------------------- bool mFirstPass = true; Consolidator TimeFrameBar = new Consolidator(TimeSpan.FromMinutes(cIntervalMinutes)); // Custom time frame bar bool mIsLong = false; bool mIsShort = false; TradeBar mPreviousData = new TradeBar(); TradeBar mDailyBar = new TradeBar(); TrailingStop mTrailingStop; int mPeriodsElapsedSinceEntry = 0; //---------------------------------------------------- // Indicators //---------------------------------------------------- TradeBar mHeikenAshiBar = new TradeBar(); // Heiken-Ashi bar MovingAverageConvergenceDivergence MACD = new MovingAverageConvergenceDivergence(12,26,9); // MACD Group BollingerBands BB = new BollingerBands(12,1.8m); // Bollinger bands SimpleMovingAverage ADR = new SimpleMovingAverage(7); // Average daily range TradeBar mHeikenAshiBar_Daily = new TradeBar(); // Heiken-Ashi bar MovingAverageConvergenceDivergence MACD_Daily = new MovingAverageConvergenceDivergence(12,26,9); // MACD Group //decimal mYesterdaysMACDDivergence = 0; BollingerBands BB_Daily = new BollingerBands(12,1.8m); // Bollinger bands ConnorsRSI CRSI = new ConnorsRSI(3,2,100); RelativeStrengthIndex RSI2 = new RelativeStrengthIndex(2, 1, true); SimpleMovingAverage SMA200 = new SimpleMovingAverage(200); SimpleMovingAverage SMA5 = new SimpleMovingAverage(5); AverageDirectionalIndex ADX = new AverageDirectionalIndex(14); //---------------------------------------------------- //Initialize the data and resolution you require for your strategy: public override void Initialize() { SetStartDate(2010, 10, 1); //SetEndDate(2014, 10, 28); SetEndDate(DateTime.Now.Date.AddDays(-1)); SetCash(30000); AddSecurity(SecurityType.Equity, cSymbol, Resolution.Minute); } //Data Event Handler: New data arrives here. "TradeBars" type is a dictionary of strings so you can access it by symbol. public void OnData(TradeBars data) { if ((mIsLong && (LongExitSignal(data[cSymbol]) || ShortEntrySignal(data[cSymbol])) || (mIsShort&& (ShortExitSignal(data[cSymbol]) || LongEntrySignal(data[cSymbol]))))) { Order(cSymbol, -Securities[cSymbol].Holdings.Quantity); mIsLong = false; mIsShort = false; } if (mDailyBar.Open == 0) mDailyBar.Open = data[cSymbol].Open; mDailyBar.High = Math.Max(mDailyBar.High, data[cSymbol].High); mDailyBar.Low = Math.Min(mDailyBar.Low, data[cSymbol].Low); if (TimeFrameBar.Update(data[cSymbol])) { UpdateIndicators(TimeFrameBar.Bar); if (data[cSymbol].Time.TimeOfDay.Hours >= cEnterHour && data[cSymbol].Time.TimeOfDay.Hours < cExitHour) { if (!mIsLong && !mIsShort && mAllowLonging && LongEntrySignal(TimeFrameBar.Bar)) { mIsLong = true; mIsShort = false; Order(cSymbol, (int)Math.Floor(Portfolio.Cash / data[cSymbol].Close) ); mTrailingStop = new TrailingStop(TimeFrameBar.Bar, true, ADR.SMA, 0.1m, 0.10m); mPeriodsElapsedSinceEntry = 0; } else if (!mIsLong && !mIsShort && mAllowShorting && ShortEntrySignal(TimeFrameBar.Bar)) { mIsLong = false; mIsShort = true; Order(cSymbol, -(int)Math.Floor(Portfolio.Cash / data[cSymbol].Close) ); mTrailingStop = new TrailingStop(TimeFrameBar.Bar, false, ADR.SMA, 0.1m, 0.10m); mPeriodsElapsedSinceEntry = 0; } } } // End the day if (!mFirstPass && mPreviousData.Time.Date < data[cSymbol].Time.Date) { // Process end of day mDailyBar.Close = mPreviousData.Close; UpdateDailyIndicators(mDailyBar); mDailyBar.Open = 0; mDailyBar.High = Decimal.MinValue; mDailyBar.Low = Decimal.MaxValue; mDailyBar.Close = 0; } if (!mFirstPass && (data[cSymbol].Time.TimeOfDay.Hours >= cExitHour || mPreviousData.Time.Date < data[cSymbol].Time.Date)) { // Process end of day myEndOfDay(); } mPreviousData = data[cSymbol]; mFirstPass = false; } private void myEndOfDay() { if (cLiquidateAtEndOfDay) { // Liquidate all at end of day Order(cSymbol, -Securities[cSymbol].Holdings.Quantity); mIsLong = false; mIsShort = false; } } private void UpdateIndicators(TradeBar Bar) { getHeikenAshi(Bar, ref mHeikenAshiBar); MACD.AddSample(Bar.Close); BB.AddSample(Bar.Close); CRSI.AddSample(Bar); RSI2.AddSample(Bar); SMA200.AddSample(Bar.Close); SMA5.AddSample(Bar.Close); ADX.AddSample(Bar); ++mPeriodsElapsedSinceEntry; if (mIsLong || mIsShort) mTrailingStop.Update(Bar); } private void UpdateDailyIndicators(TradeBar Bar) { //mYesterdaysMACDDivergence = MACD_Daily.Divergence; MACD_Daily.AddSample(Bar.Close); BB_Daily.AddSample(Bar.Close); getHeikenAshi(Bar, ref mHeikenAshiBar_Daily); ADR.AddSample(mDailyBar.High-mDailyBar.Low); } private bool LongEntrySignal(TradeBar Bar) { bool wSignal = true; wSignal &= BB.Ready; wSignal &= MACD.Ready; wSignal &= ADR.Ready; wSignal &= CRSI.Ready; wSignal &= SMA200.Ready; wSignal &= SMA5.Ready; wSignal &= ADX.Ready; wSignal &= Bar.Close > SMA200.SMA; wSignal &= Bar.Close < SMA5.SMA; wSignal &= CRSI.CRSI <= 5; //wSignal &= ADX.ADX > 30; //wSignal &= RSI2.RSI <= 5; bool wDailySignal = true; if (mValidateWithDailySignal) wSignal &= wDailySignal; return wSignal; } private bool LongExitSignal(TradeBar Bar) { bool wSignal = true; wSignal &= (mTrailingStop.StopSignal() || (mPeriodsElapsedSinceEntry >= cSellAfterNPeriods)); //wSignal &= (mPeriodsElapsedSinceEntry >= cSellAfterNPeriods); return wSignal; } private bool ShortEntrySignal(TradeBar Bar) { bool wSignal = true; wSignal &= BB.Ready; wSignal &= MACD.Ready; wSignal &= ADR.Ready; wSignal &= CRSI.Ready; wSignal &= SMA200.Ready; wSignal &= SMA5.Ready; wSignal &= ADX.Ready; wSignal &= Bar.Close < SMA200.SMA; wSignal &= Bar.Close > SMA5.SMA; wSignal &= CRSI.CRSI >= 95; //wSignal &= ADX.ADX > 30; //wSignal &= RSI2.RSI >= 95; bool wDailySignal = true; if (mValidateWithDailySignal) wSignal &= wDailySignal; return wSignal; } private bool ShortExitSignal(TradeBar Bar) { bool wSignal = true; wSignal &= (mTrailingStop.StopSignal() || (mPeriodsElapsedSinceEntry >= cSellAfterNPeriods)); //wSignal &= (mPeriodsElapsedSinceEntry >= cSellAfterNPeriods); return wSignal; } private bool isHammer(TradeBar Bar, decimal MaxUpperTailRatio = 0.1m) { decimal wRange = Bar.High - Bar.Low; decimal wMaxUpperTail = MaxUpperTailRatio * wRange; decimal wUpperTail = Bar.High - System.Math.Max(Bar.Open, Bar.Close); return wUpperTail <= wMaxUpperTail; } private bool isFallingStar(TradeBar Bar, decimal MaxLowerTailRatio = 0.1m) { decimal wRange = Bar.High - Bar.Low; decimal wMaxLowerTail = MaxLowerTailRatio * wRange; decimal wLowerTail = System.Math.Min(Bar.Open, Bar.Close) - Bar.Low; return wLowerTail <= wMaxLowerTail; } private bool isDoji(TradeBar Bar, decimal MaxBodyRatio = 0.5m) { if (isHammer(Bar) || isFallingStar(Bar)) { return false; } else { decimal wRange = Bar.High - Bar.Low; decimal wMaxBody = MaxBodyRatio * wRange; decimal wBody = Math.Abs(Bar.Open - Bar.Close); return (wBody <= wMaxBody); } } private void getHeikenAshi(TradeBar RegularBar, ref TradeBar HeikenAshiBar) { HeikenAshiBar.Time = RegularBar.Time; HeikenAshiBar.Open = (HeikenAshiBar.Open + HeikenAshiBar.Close) / 2; HeikenAshiBar.Close = (RegularBar.Open + RegularBar.High + RegularBar.Low + RegularBar.Close) / 4; HeikenAshiBar.High = System.Math.Max(RegularBar.High, System.Math.Max(HeikenAshiBar.Open, HeikenAshiBar.Close)); HeikenAshiBar.Low = System.Math.Min(RegularBar.Low, System.Math.Min(HeikenAshiBar.Open, HeikenAshiBar.Close)); } } }
using System; using System.Collections; using System.Collections.Generic; using QuantConnect.Securities; using QuantConnect.Models; namespace QuantConnect { /* * TimeSpanConsolidator Helper Routine: Assemble generic timespan bar lengths: e.g. 10 minutes: * * 1. Setup the new Consolidator class with the timespan period: * var _consolidator = new Consolidator(TimeSpan.FromMinutes(10)); * * 2. Add in the data with the update routine. It will return true when bar ready * if (_consolidator.Update(data["MSFT"])) { UseBar } */ public class Consolidator { private TradeBar _resultBar; private TradeBar _workingBar; private DateTime _start; private TimeSpan _period; //Result: public TradeBar Bar { get { return _resultBar; } } //Constructor: Set the period we'd like to scan public Consolidator(TimeSpan span) { this._period = span; this._resultBar = new TradeBar(); this._workingBar = new TradeBar(new DateTime(), "", Decimal.Zero, Decimal.MinValue, Decimal.MaxValue, 0, 0); } //Submit this bar, return true if we've started a new one. public bool Update(TradeBar newBar) { //Intialize: if (_start == new DateTime()) { _start = newBar.Time; } //While we're less than end date, keep adding to this bar: if (newBar.Time < (_start + _period)) { //Building bar: AddToBar(newBar); return false; } else { //Completed bar: start new one: _resultBar = _workingBar; //Create a new bar: _workingBar = new TradeBar(newBar.Time, newBar.Symbol, Decimal.Zero, Decimal.MinValue, Decimal.MaxValue, 0, 0); //Start of this bar: _start = newBar.Time; AddToBar(newBar); return true; } } //Add to a tradebar private void AddToBar(TradeBar newBar) { //Add this data to working bar: if (_workingBar.Time == new DateTime()) _workingBar.Time = newBar.Time; if (_workingBar.Symbol == "") _workingBar.Symbol = newBar.Symbol; if (_workingBar.Open == Decimal.Zero) _workingBar.Open = newBar.Open; if (newBar.High > _workingBar.High) _workingBar.High = newBar.High; if (newBar.Low < _workingBar.Low) _workingBar.Low = newBar.Low; _workingBar.Close = newBar.Close; _workingBar.Volume = newBar.Volume; } } }
using System; using System.Collections; using System.Collections.Generic; using QuantConnect.Securities; using QuantConnect.Models; namespace QuantConnect { /* * MACD Indicator - Moving Average Convergence Divergence * Typically called like: macd(12,26,9); * Where 12 days = Fast EMA * 26 days = Slow EMA * 9 days = EMA of the difference (fastEMA - slowEMA). * * Use in your code by calling: * * MovingAverageConvergenceDivergence macd = MovingAverageConvergenceDivergence(12,26,9); * * macd.AddSample(price); * * if (macd.Ready) { .... macd.MACD_Signal .....} */ public class MovingAverageConvergenceDivergence { /* * Initialize the MACD with X Period. */ public MovingAverageConvergenceDivergence(int fastEMA, int slowEMA, int signalEMA) { this._slowPeriod = slowEMA; this._fastPeriod = fastEMA; this._signalPeriod = signalEMA; this._minimumSamplesBeforeReady = 4 * slowEMA; } /* * Signal of MACD: x-EMA of MACD */ public decimal MACD_Signal { get { return _signalEMA; } } /* * RAW MACD Values: emaFast - emaSlow. */ public decimal MACD { get { return _fastEMA-_slowEMA; } } /* * Divergence = MACD - Signal */ public decimal Divergence { get { return MACD - MACD_Signal; } } /* * Flag to tell user if indicator read for usage. */ public bool Ready { get { return (_samples >= _minimumSamplesBeforeReady); } } //Private working variables: private decimal _slowPeriod = 0, _fastPeriod = 0, _signalPeriod = 0; private decimal _slowEMA = 0, _fastEMA = 0, _signalEMA = 0; private long _minimumSamplesBeforeReady = 0, _samples = 0; /* * Add a Price to the MACD and recalculate its values: */ public void AddSample(decimal price) { //Wait till we're ready before using this indicator: if (_samples < _minimumSamplesBeforeReady) _samples++; //Calculate the updated EMA's if (_samples == 1) { _slowEMA = price; _fastEMA = price; _signalEMA = 0; } else { //Now apply forumla to update all of them: _slowEMA = UpdateEMA(price, _slowPeriod, _slowEMA); _fastEMA = UpdateEMA(price, _fastPeriod, _fastEMA); _signalEMA = UpdateEMA(MACD, _signalPeriod, _signalEMA); } } /* * Calculate and return the new EMA value: */ private decimal UpdateEMA(decimal sample, decimal period, decimal previousValue) { decimal factor = (2m / (period + 1m)); return factor * sample + (1-factor) * previousValue; } /* * Wrapper for the decimal function to accept tradebars. */ public void AddSample(TradeBar bar) { AddSample(bar.Close); } } }
using System; using System.Collections; using System.Collections.Generic; using System.Collections.Concurrent; using System.Text; using System.Linq; using QuantConnect.Models; namespace QuantConnect { /* * SMA Indicator: Online calculator for faster backtesting. * * To use this indicator: * 1. Create an instance of it in your algorithm: * SMA sma10 = new SMA(10); * * 2. Push in data with AddSample: * decimal sma = sma10.AddSample(data["spy"].Close); * * 3. If you're sensitive to the precise SMA values you push wait until the indicator is Ready. */ public class SimpleMovingAverage { //Class Variables: private int period, samples; private decimal sma, sum, divisor; private FixedSizedQueue<decimal> sampleQueue; // Initialise the Simple Moving Average public SimpleMovingAverage(int period) { this.period = period; this.samples = 0; this.sampleQueue = new FixedSizedQueue<decimal>(period); } //Public Result Access: Current value of the SMA. public decimal SMA { get{ return sma;} } //Public Result Access: Track the number of samples: public int Samples { get { return samples; } } //Public Result Access: We've got sufficient data samples to know its the SMA. public bool Ready { get { return samples >= period; } } // Online implementation of simple moving average public decimal AddSample(decimal quote) { samples++; sum += quote; //Add this sample to the SMA, subtract the previous sampleQueue.Enqueue(quote); if (sampleQueue.Size == period) { //"last" is a dequeued item: minus it from the SMA. sum -= sampleQueue.LastDequeued; } //When less than period samples, only divide by the number of samples. if (samples < period) { divisor = samples; } else { divisor = period; } sma = sum / divisor; return sma; } //Fixed length queue that dumps things off when no more space in queue. private class FixedSizedQueue<T> : ConcurrentQueue<T> { public int Size { get; private set; } public T LastDequeued { get; private set; } public FixedSizedQueue(int size) { Size = size; } public new void Enqueue(T obj) { base.Enqueue(obj); lock (this) { if (base.Count > Size) { T outObj; base.TryDequeue(out outObj); LastDequeued = outObj; } } } } } }
using System; using System.Collections; using System.Collections.Generic; using System.Collections.Concurrent; using System.Text; using System.Linq; using QuantConnect.Models; namespace QuantConnect { /* * Bollinger Bands Indicator: Online calculator for faster backtesting. * * To use this indicator: * 1. Create an instance of it in your algorithm: * BollingerBands BB = new BollingerBands(20,2); * * 2. Push in data with AddSample: * BB.AddSample(data["spy"].Close); * * 3. If you're sensitive to the precise Bollinger bands values you push, wait until the indicator is Ready. */ public class BollingerBands { //Class Variables: private int samples; private decimal Upper, Lower, Bandwidth, NumStdDevs; private StandardDeviation sd; // Initialise the Bollinger Bands public BollingerBands(int period = 20, decimal NumStdDevs = 2) { this.NumStdDevs = NumStdDevs; this.samples = 0; this.sd = new StandardDeviation(period); } //Public Result Access: Current value of the upper Bollinger band. public decimal UPPER { get{ return Upper;} } //Public Result Access: Current value of the lower Bollinger band. public decimal LOWER { get{ return Lower;} } //Public Result Access: Current value of the Bollinger bandwidth. public decimal BANDWIDTH { get{ return Bandwidth;} } //Public Result Access: Track the number of samples: public int Samples { get { return samples; } } //Public Result Access: We've got sufficient data samples to get right values. public bool Ready { get { return sd.Ready; } } // Online implementation of simple moving average public void AddSample(decimal quote) { sd.AddSample(quote); Lower = sd.SMA - sd.SD*NumStdDevs; Upper = sd.SMA + sd.SD*NumStdDevs; Bandwidth = Upper - Lower; samples++; } } }
using System; using System.Collections; using System.Collections.Generic; using System.Collections.Concurrent; using System.Text; using System.Linq; using QuantConnect.Models; namespace QuantConnect { /* * Standard Deviation Indicator * * To use this indicator: * 1. Create an instance of it in your algorithm: * StandardDeviation sd10 = new StandardDeviation(10); * * 2. Push in data with AddSample: * decimal sd = sd10.AddSample(data["spy"].Close); * * 3. If you're sensitive to the precise sd values you push wait until the indicator is Ready. */ public class StandardDeviation { //Class Variables: private int period, samples; private decimal sd; private double sum, divisor; private SimpleMovingAverage sma; private FixedSizedQueue<double> sampleQueue; // Initialise the StandardDeviation public StandardDeviation(int period) { this.period = period; this.samples = 0; this.sampleQueue = new FixedSizedQueue<double>(period); this.sma = new SimpleMovingAverage(period); } //Public Result Access: Current value of the SD. public decimal SD { get{ return sd;} } //Public Result Access: Track the number of samples: public int Samples { get { return samples; } } //Public Result Access: Current value of the SMA. public decimal SMA { get { return sma.SMA; } } //Public Result Access: We've got sufficient data samples to get the correct value. // Note that the SMA needs to be ready before the SD accumulates correct values, so we wait // another period after the SMA is ready, hence period*2. public bool Ready { get { return samples >= period*2; } } // Online implementation of standard deviation public decimal AddSample(decimal quote) { samples++; sma.AddSample(quote); double DeviationFromMeanSquared = (double)((quote-sma.SMA)*(quote-sma.SMA)); sum += DeviationFromMeanSquared; //Add this sample to the SD, subtract the previous sampleQueue.Enqueue(DeviationFromMeanSquared); if (sampleQueue.Size == period) { //"last" is a dequeued item: subtract it from the SD. sum -= sampleQueue.LastDequeued; } //When less than period samples, only divide by the number of samples. if (samples < period) { divisor = samples; } else { divisor = period; } if (divisor>1) { sd = (decimal)Math.Sqrt(sum / (divisor-1)); } return sd; } //Fixed length queue that dumps things off when no more space in queue. private class FixedSizedQueue<T> : ConcurrentQueue<T> { public int Size { get; private set; } public T LastDequeued { get; private set; } public FixedSizedQueue(int size) { Size = size; } public new void Enqueue(T obj) { base.Enqueue(obj); lock (this) { if (base.Count > Size) { T outObj; base.TryDequeue(out outObj); LastDequeued = outObj; } } } } } }
using System; using System.Collections; using System.Collections.Generic; using System.Collections.Concurrent; using System.Text; using System.Linq; using QuantConnect.Models; namespace QuantConnect { public class ConnorsRSI { //Class Variables: private int samples = 0; private decimal mCRSI = 0; private RelativeStrengthIndex mRSI; private RelativeStrengthIndex mStreakRSI; private int mUpDownStreak = 0; private decimal mROC = 0; private int mROCPeriod = 0; private decimal mPreviousValue = 0; private FixedSizedQueue<decimal> mPriceChanges; // Initialise the Connors RSI public ConnorsRSI(int iRSIPeriod = 3, int iUpDownStreakRSIPeriod = 2, int iROCPeriod = 100) { this.mROCPeriod = iROCPeriod; this.mRSI = new RelativeStrengthIndex(iRSIPeriod); this.mStreakRSI = new RelativeStrengthIndex(iUpDownStreakRSIPeriod); mPriceChanges = new FixedSizedQueue<decimal>(mROCPeriod); } //Public Result Access: Current value of the Connors RSI. public decimal CRSI { get{ return mCRSI;} } //Public Result Access: Track the number of samples: public int Samples { get { return samples; } } //Public Result Access: We've got sufficient data samples to get right values. public bool Ready { get { return mRSI.Ready && mStreakRSI.Ready && samples >= mROCPeriod;} } // Online implementation of simple moving average public void AddSample(TradeBar quote) { mRSI.AddSample(quote); if (mUpDownStreak <= 0 && quote.Close < mPreviousValue) --mUpDownStreak; if (mUpDownStreak > 0 && quote.Close < mPreviousValue) mUpDownStreak = -1; if (mUpDownStreak >= 0 && quote.Close > mPreviousValue) ++mUpDownStreak; if (mUpDownStreak < 0 && quote.Close > mPreviousValue) mUpDownStreak = 1; if (quote.Close == mPreviousValue) mUpDownStreak = 0; mStreakRSI.AddSample(mUpDownStreak); decimal wPercentChange = (quote.Close - quote.Open) / quote.Open; mPriceChanges.Enqueue(wPercentChange); mCRSI = (mRSI.RSI + mStreakRSI.RSI + GetROC(wPercentChange))/3; mPreviousValue = quote.Close; samples++; } private decimal GetROC(decimal wRefChange) { int wNumberOfLowerChange = 0; foreach (decimal i in mPriceChanges) { if (i < wRefChange) ++wNumberOfLowerChange; } mROC = (wNumberOfLowerChange / mPriceChanges.Count) * 100; return mROC; } //Fixed length queue that dumps things off when no more space in queue. private class FixedSizedQueue<T> : ConcurrentQueue<T> { public int Size { get; private set; } public T LastDequeued { get; private set; } public T LastEnqueued {get; private set;} public bool Dequeued { get; private set; } public FixedSizedQueue(int size) { Size = size; } public new bool Enqueue(T obj) { base.Enqueue(obj); LastEnqueued = obj; Dequeued = false; lock (this) { if (base.Count > Size) { T outObj; Dequeued = base.TryDequeue(out outObj); LastDequeued = outObj; } } return Dequeued; } } } }
using System; using System.Collections; using System.Collections.Generic; using System.Collections.Concurrent; using QuantConnect.Securities; using QuantConnect.Models; namespace QuantConnect { /* * Relative Strength Index Indicator: * * 100 * RSI = 100 - ------------ * 1 + RS * * Where RS = Avg of X Period Close Up / Absolute(Avg) X of Period Close Down. * */ public class RelativeStrengthIndex { //Public Access to the RSI Output public decimal RSI { get { return (100 - (100 / (1 + _rs))); } } //Public Access to Know if RSI Indicator Ready public bool Ready { get { return (_upward.Count >= _period) && (_downward.Count >= _period); } } //Private Class Variables: private decimal _rs = 0; private bool _ema = false; private decimal _period = 14; private decimal _joinBars = 1; private Candle _superCandle = new Candle(); private Candle _previousCandle = new Candle(); private decimal _previousValue = 0; private FixedSizedQueue<decimal> _downward = new FixedSizedQueue<decimal>(0); private FixedSizedQueue<decimal> _upward = new FixedSizedQueue<decimal>(0); private decimal _upwardSum = 0, _avgUpward = 0; private decimal _downwardSum = 0, _avgDownward = 0; //Initialize the RSI with 'period' candles public RelativeStrengthIndex(int period, int joinBars = 1, bool useEMA = false) { //Range check variables: if (period < 2) period = 2; //Class settings: _period = (decimal)period; // How many samples is the RSI? _ema = useEMA; // Use the EMA average for RSI _joinBars = joinBars; // Join multiple tradebars together //Remember the upward and downward movements in a FIFO queue: _upward = new FixedSizedQueue<decimal>(period); _downward = new FixedSizedQueue<decimal>(period); //Online implementation of SMA - needs moving sum of all components: _upwardSum = 0; _downwardSum = 0; } //Add a new sample to build the RSI Indicator: public void AddSample(TradeBar bar) { //Build a multibar candle, until join reached return. _superCandle.Update(bar); if (_superCandle.Samples < _joinBars) return; //Initialize the first loop. if (_previousCandle.Samples == 0) { _previousCandle = _superCandle; _superCandle = new Candle(); return; } //Get the difference between this bar and previous bar: decimal difference = _superCandle.Close - _previousCandle.Close; //Update the Moving Average Calculations: if (difference >= 0) { if (_ema) { _avgUpward = UpdateDirectionalEMA(ref _upward, difference); _avgDownward = UpdateDirectionalEMA(ref _downward, 0); } else { _avgUpward = UpdateDirectionalSMA(ref _upward, ref _upwardSum, difference); _avgDownward = UpdateDirectionalSMA(ref _downward, ref _downwardSum, 0); } } if (difference <= 0) { difference = Math.Abs(difference); if (_ema) { _avgUpward = UpdateDirectionalEMA(ref _upward, 0); _avgDownward = UpdateDirectionalEMA(ref _downward, difference); } else { _avgUpward = UpdateDirectionalSMA(ref _upward, ref _upwardSum, 0); _avgDownward = UpdateDirectionalSMA(ref _downward, ref _downwardSum, difference); } } //Refresh RS Factor: //RS Index Automatically Updated in the Public Property Above: if (_avgDownward != 0) { _rs = _avgUpward / _avgDownward; } else { _rs = Decimal.MaxValue - 1; } //Reset for next loop: _previousCandle = _superCandle; _superCandle = new Candle(); } //Add a new sample to build the RSI Indicator: public void AddSample(decimal Value) { //Get the difference between this bar and previous bar: decimal difference = Value - _previousValue; //Update the Moving Average Calculations: if (difference >= 0) { if (_ema) { _avgUpward = UpdateDirectionalEMA(ref _upward, difference); _avgDownward = UpdateDirectionalEMA(ref _downward, 0); } else { _avgUpward = UpdateDirectionalSMA(ref _upward, ref _upwardSum, difference); _avgDownward = UpdateDirectionalSMA(ref _downward, ref _downwardSum, 0); } } if (difference <= 0) { difference = Math.Abs(difference); if (_ema) { _avgUpward = UpdateDirectionalEMA(ref _upward, 0); _avgDownward = UpdateDirectionalEMA(ref _downward, difference); } else { _avgUpward = UpdateDirectionalSMA(ref _upward, ref _upwardSum, 0); _avgDownward = UpdateDirectionalSMA(ref _downward, ref _downwardSum, difference); } } //Refresh RS Factor: //RS Index Automatically Updated in the Public Property Above: if (_avgDownward != 0) { _rs = _avgUpward / _avgDownward; } else { _rs = Decimal.MaxValue - 1; } //Reset for next loop: _previousValue = Value; } // Update the moving average and fixed length queue in a generic fashion to work for up and downward movement. // Return the average. private decimal UpdateDirectionalSMA(ref FixedSizedQueue<decimal> queue, ref decimal sum, decimal sample) { //Increment Sum sum += sample; //If we've shuffled off queue, remove from sum: if(queue.Enqueue(sample)) { sum -= queue.LastDequeued; } //When less than period samples, only divide by the number of samples. if (queue.Count < _period) { return (sum / (decimal)queue.Count); } else { return (sum / _period); } } // Update the moving average and fixed length queue in a generic fashion to work for up and downward movement. // Return the average. private decimal UpdateDirectionalEMA(ref FixedSizedQueue<decimal> queue, decimal sample) { queue.Enqueue(sample); if (queue.Count == 1) { return sample; } else { return (1m / _period) * sample + ((_period - 1m) / _period) * queue.LastEnqueued; } } //Fixed length queue that dumps things off when no more space in queue. private class FixedSizedQueue<T> : ConcurrentQueue<T> { public int Size { get; private set; } public T LastDequeued { get; private set; } public T LastEnqueued {get; private set;} public bool Dequeued { get; private set; } public FixedSizedQueue(int size) { Size = size; } public new bool Enqueue(T obj) { base.Enqueue(obj); LastEnqueued = obj; Dequeued = false; lock (this) { if (base.Count > Size) { T outObj; Dequeued = base.TryDequeue(out outObj); LastDequeued = outObj; } } return Dequeued; } } /// <summary> /// Simple online "super-tradebar" generator for making an OHLC from multiple bars. /// </summary> public class Candle { public decimal Open = 0; public decimal High = Decimal.MinValue; public decimal Low = Decimal.MaxValue; public decimal Close = 0; public int Samples = 0; public void Update(TradeBar bar) { if (Open == 0) Open = bar.Open; if (High < bar.High) High = bar.High; if (Low > bar.Low) Low = bar.Low; Close = bar.Close; Samples++; } } } }
using System; using System.Collections; using System.Collections.Generic; using QuantConnect.Securities; using QuantConnect.Models; namespace QuantConnect { /* ADX Indicator - Average Directional Index is a complicated but powerful indicator. * * 1. Calculate the Directional Movements: +DM_t, -DM_t. * 2. Find the Average Directional Movement: ADM_t * 3. Calculate the Average True Range: ATR_t * 4. Calculate the Directional Indexes: DI+_t, DI-_t, * Directional Movement Index: DX_t, * then Average Directional Movement Index! ADX_t * * Requires day to be divided into segments, periods and a running record kept of the previous value so the averages can be made. */ public class AverageDirectionalIndex { //Public Result Access: Primary ADX Indicator public decimal ADX { get; private set; } //Public Result Access: DMI Positive: public decimal DMI_Pos { get; private set; } //Public Result Access: DMI Negative: public decimal DMI_Neg { get; private set; } //Public Result Access: Direction of the ADX Indicator: public decimal Decision { get; private set; } //Public Result Access: When indicator has sufficient data flags as true. public bool Ready { get; private set; } //Initialize private ADXCacheItem indicatorMemory = new ADXCacheItem(); private decimal _expConst = 0; private int _samplePeriods = 0; private Candle _superCandle = new Candle(); // Constructor: Set the sample period: public AverageDirectionalIndex(int samplePeriods) { _expConst = (2m / (decimal)(samplePeriods + 1)); _samplePeriods = samplePeriods; Ready = false; } /// <summary> /// Calculate the ADX figure, return an indicator result. /// </summary> public decimal AddSample(TradeBar bar) { //0. Save these samples in running OHLC candle until requested samplePeriod reached: _superCandle.Update(bar); if (_superCandle.Samples < _samplePeriods) return Decision; //0. Define a result storage for this session. ADXCacheItem current = new ADXCacheItem(); current.OHLC = _superCandle; //0. If this is the first candle skip it and come back for second: to calc directional index can't be relative to 0. if (!indicatorMemory.set) { current.set = true; indicatorMemory = current; _superCandle = new Candle(); return Decision; } //1. Calculate the Directional Movements: store results back into current-variable class cache GetDirectionalMovements(ref current); //2. Find the Average Directional Movement. GetAverageDirectionalMovements(ref current); //3. Get the Average True Range: GetAverageTrueRange(ref current); //4. Get the Average Directional Movement Index ADX-t, and DI+, DI- GetADX(ref current); //Strong Trend is Present, and have at least X-min data Decision = 0; if (current.adx > 40) { //NOW! We have an ADX result, interpret it.. if (current.dmiPos > 40) { Decision = 1; } else if (current.dmiNeg > 40) { Decision = -1; } } //Save the results to publicly accessible properties. ADX = current.adx; DMI_Neg = current.dmiNeg; DMI_Pos = current.dmiPos; //Update the indicator cache - store previous result between calls. current.set = true; Ready = true; indicatorMemory = current; _superCandle = new Candle(); return Decision; } /// <summary> /// 1. Get the pure directional movements, in DM+, DM- Form. /// </summary> /// <param name="current">ADX Cache class for easy storing for next analysis session.</param> private void GetDirectionalMovements(ref ADXCacheItem current) { //Change from the previous period to now. decimal deltaHigh = current.OHLC.High - indicatorMemory.OHLC.High; decimal deltaLow = indicatorMemory.OHLC.Low - current.OHLC.Low; //Allocate the Delta Movement. if ((deltaHigh < 0 && deltaLow < 0) || (deltaHigh == deltaLow)) { current.dm_plus = 0; current.dm_neg = 0; } else if (deltaHigh > deltaLow) { current.dm_plus = deltaHigh; current.dm_neg = 0; } else if (deltaHigh < deltaLow) { current.dm_plus = 0; current.dm_neg = deltaLow; } } /// <summary> /// 2. Get the Exp Average of the directional movement indexs /// </summary> private void GetAverageDirectionalMovements(ref ADXCacheItem current) { if (!Ready) { //If this is the first run, current.adm_plus = current.dm_plus; current.adm_neg = current.dm_neg; } else { //This is not our first sample current.adm_plus = (current.dm_plus * _expConst) + (indicatorMemory.adm_plus * (1 - _expConst)); current.adm_neg = (current.dm_neg * _expConst) + (indicatorMemory.adm_neg * (1 - _expConst)); ; } } /// <summary> /// 3. Get the true range of the price. /// </summary> private void GetAverageTrueRange(ref ADXCacheItem current) { decimal yesterdayClose = indicatorMemory.OHLC.Close; decimal trueRange = System.Math.Max(Math.Abs(current.OHLC.High - current.OHLC.Low), System.Math.Max(Math.Abs(current.OHLC.High - yesterdayClose), Math.Abs(yesterdayClose - current.OHLC.Low))); //Get the current true range: if (indicatorMemory.atr == 0) { current.atr = trueRange; } else { current.atr = (trueRange * _expConst) + ((1 - _expConst) * indicatorMemory.atr); } } /// <summary> /// 4. Get the Directional Movement Index /// </summary> private void GetADX(ref ADXCacheItem current) { decimal dmi_plus = 0; decimal dmi_neg = 0; if (current.atr > 0) { dmi_plus = (current.adm_plus / current.atr) * 100; dmi_neg = (current.adm_neg / current.atr) * 100; } if ((dmi_plus + dmi_neg) != 0) { current.dx = (Math.Abs(dmi_plus - dmi_neg) / (dmi_plus + dmi_neg)) * 100; } else { current.dx = indicatorMemory.dx; } //Save the results. current.dmiPos = dmi_plus; current.dmiNeg = dmi_neg; current.adx = current.dx * _expConst + (1 - _expConst) * indicatorMemory.adx; } /// <summary> /// Provide a structure for caching the previous values of the ADX /// </summary> public class ADXCacheItem { public Candle OHLC = new Candle(); public bool set = false; public decimal atr = 0; public decimal dm_plus = 0; public decimal dm_neg = 0; public decimal adm_plus = 0; public decimal adm_neg = 0; public decimal dx = 0; public decimal dmiPos = 0; public decimal dmiNeg = 0; public decimal adx = 0; } /// <summary> /// Simple online "super-tradebar" generator for making an OHLC from multiple bars. /// </summary> public class Candle { public Candle() { } public decimal Open = 0; public decimal High = Decimal.MinValue; public decimal Low = Decimal.MaxValue; public decimal Close = 0; public int Samples = 0; public void Update(TradeBar bar) { if (Open == 0) Open = bar.Open; if (High < bar.High) High = bar.High; if (Low > bar.Low) Low = bar.Low; Close = bar.Close; Samples++; } } } // End ADX Indicator Class } // End Namespace