| 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