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
using System;
using System.Collections;
using System.Collections.Generic;
using QuantConnect.Securities;
using QuantConnect.Models;
namespace QuantConnect
{
// Name your algorithm class anything, as long as it inherits QCAlgorithm
public class BasicTemplateAlgorithm : QCAlgorithm
{
AverageDirectionalIndex adx = new AverageDirectionalIndex(3);
//Initialize the data and resolution you require for your strategy:
public override void Initialize()
{
SetStartDate(2014, 12, 01);
SetEndDate(DateTime.Now.Date.AddDays(-1));
SetCash(25000);
AddSecurity(SecurityType.Equity, "SPY", 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)
{
TradeBar SPY = data["SPY"];
adx.AddSample(SPY);
Log("READY? " + adx.Ready + " VALUE: " + Math.Round(adx.ADX,2).ToString());
}
}
}