| Overall Statistics |
|
Total Trades 204 Average Win 1.98% Average Loss -0.97% Compounding Annual Return -84.304% Drawdown 47.800% Expectancy -0.643 Net Profit -47.774% Sharpe Ratio -2.512 Probabilistic Sharpe Ratio 0.000% Loss Rate 88% Win Rate 12% Profit-Loss Ratio 2.03 Alpha -0.706 Beta -0.171 Annual Standard Deviation 0.304 Annual Variance 0.092 Information Ratio -3.09 Tracking Error 0.355 Treynor Ratio 4.466 Total Fees $377.40 |
namespace QuantConnect {
//
// Make sure to change "BasicTemplateAlgorithm" to your algorithm class name, and that all
// files use "public partial class" if you want to split up your algorithm namespace into multiple files.
//
//public partial class BasicTemplateAlgorithm : QCAlgorithm, IAlgorithm
//{
// Extension functions can go here...(ones that need access to QCAlgorithm functions e.g. Debug, Log etc.)
//}
public static class ExtensionMethods
{
public static bool In(this int num, int[] numbers)
{
bool containsNumber = false;
foreach (int n in numbers) // go over every number in the list
{
if (n == num) // check if it matches
{
containsNumber = true;
break; // no need to check any further
}
}
return containsNumber;
}
}
}namespace QuantConnect
{
public class POP_BBTrendAlgorithm_NoFramework : QCAlgorithm
{
#region Private Variables
private Symbol _symbol;
private BollingerBands _bBands;
private Identity _self;
private AverageTrueRange _atr;
private TradeBarConsolidator _consolidator;
private decimal _entryPrice = 0;
private decimal _stopPrice = 0;
private decimal _targetPrice = 0;
private decimal _stopAtrFactor = 1.6m;
private decimal _targetAtrFactor = 3.0m;
private OrderTicket _stopOrder = null;
private OrderTicket _targetOrder = null;
//0 is the previous bar, 1 is the bar before last
private decimal[] _previousClose = new decimal[2] { 0, 0 };
private decimal[] _previousBBUpper = new decimal[2] { 0, 0 };
private decimal[] _previousBBLower = new decimal[2] { 0, 0 };
#endregion
public override void Initialize()
{
SetStartDate(2020, 6, 15);
SetEndDate(2020, 10, 20);
SetCash(10000);
var future = AddFuture(Futures.Currencies.EUR);
/***
Note 1: Need to filter this for quarterly expiration dates (not front month)
Take only March, June, Sept, Dec
***/
//Initialize Indicators
_bBands = new BollingerBands(200, 1.5m, MovingAverageType.Exponential);
_atr = new AverageTrueRange(14, MovingAverageType.Wilders);
_self = new Identity("/6E");
// Add a custom chart to track the EMA cross
var chart = new Chart("EMA Cross");
chart.AddSeries(new Series("BollingerBand", SeriesType.Line, 0));
chart.AddSeries(new Series("Close", SeriesType.Line, 0));
AddChart(chart);
var atrChart = new Chart("ATR");
atrChart.AddSeries(new Series("ATR", SeriesType.Line, 0));
}
#region On Data Arrival Events
public override void OnData(Slice slice)
{
/*****
NOTE 2: Since I can't figure out how to do this using SetFilter, taking approach similar
to the lab
*****/
//Don't check all day long, only when the day changes
if (slice.Time.Hour == 0 && slice.Time.Minute < 1)
{
foreach (var chain in slice.FutureChains)
{
var allContracts = chain.Value.ToList();
if (allContracts.Count == 0) continue;
var filteredContracts = allContracts.Where(x => x.Symbol.ID.Date.Month.In(new int[4] { 3, 6, 9, 12 }));
if (filteredContracts.Count() == 0) continue;
var quarterlyContract = filteredContracts.FirstOrDefault();
if (_symbol != quarterlyContract.Symbol)
{
//remove _symbol
RemoveSymbol();
//add liquidcontract as the new _symbol
AddSymbol(quarterlyContract.Symbol);
}
}
}
}
private void OnDataConsolidated(object sender, TradeBar consolidated)
{
/*******
NOTE 3: Even with warming up indicators, the indicator values were differing
from ThinkOrSwim values, so giving it 5 more days to catch up before making
decision
********/
if (Time < new DateTime(2020, 6, 20))
{
_previousClose[1] = _previousClose[0];
_previousBBLower[1] = _previousBBLower[0];
_previousBBUpper[1] = _previousBBUpper[0];
_previousBBLower[0] = _bBands.LowerBand;
_previousBBUpper[0] = _bBands.UpperBand;
_previousClose[0] = consolidated.Close;
return;
}
try
{
SecurityHolding holding;
if (Portfolio.TryGetValue(_symbol, out holding))
{
//When the close crosses 200 EMA in wrong direction liquidate
if (holding.Invested &&
((consolidated.Close < _bBands.MiddleBand && holding.Quantity > 0)
|| (consolidated.Close > _bBands.MiddleBand && holding.Quantity < 0)
)
)
{
SetHoldings(_symbol, 0.0m, tag: "Liquidate");
}
// Buy future when close crosses above upper bollinger band
if (_previousClose[0] > _previousBBUpper[0]
&& ((consolidated.Close + consolidated.Open) / 2 > _bBands.UpperBand)
&& _previousClose[1] < _previousBBUpper[1] && !holding.Invested)
{
SetHoldings(_symbol, 0.7m, tag: "Go Long");
}
//Sell future contract when close crosses below lower bollinger band
else if (_previousClose[0] < _previousBBLower[0]
&& ((consolidated.Close + consolidated.Open) / 2 < _bBands.UpperBand)
&& _previousClose[1] > _previousBBLower[1] && !holding.Invested)
{
SetHoldings(_symbol, -0.7m, tag: "Go Short");
}
}
//Keep a rolling window of last two values
_previousClose[1] = _previousClose[0];
_previousBBLower[1] = _previousBBLower[0];
_previousBBUpper[1] = _previousBBUpper[0];
_previousBBLower[0] = _bBands.LowerBand;
_previousBBUpper[0] = _bBands.UpperBand;
_previousClose[0] = consolidated.Close;
PlotIndicators();
}
catch (Exception ex)
{
//Console.WriteLine(ex.Message + "\r\n" + ex.StackTrace);
}
}
#endregion
public override void OnOrderEvent(OrderEvent orderEvent)
{
/*******
NOTE 4: This whole complicated thing is to manage stop and limit orders
The logic is very simple: Entry price (when close crosses BB) is market order,
with 3.0 * ATR target and 1.5 * ATR Stop.
As one more way to limit risk, if the close is in wrong
direction of 200 EMA, close at market.
This seems to be working, but will appreciate if someone suggests a simpler way
*******/
var order = Transactions.GetOrderById(orderEvent.OrderId);
//if (orderEvent.Status == OrderStatus.Filled)
//Console.WriteLine("{0};{1};{2};{3};{4};{5};{6};{7}", order.Time, order.Type, order.Id, order.Status, order.Quantity, order.Price, order.Tag, Portfolio[_symbol].Quantity);
if (order.Type == OrderType.Market && orderEvent.Status == OrderStatus.Filled && order.Tag.ToUpper(CultureInfo.InvariantCulture) != "LIQUIDATE")
{
if (order.Direction == OrderDirection.Buy)
{
_targetPrice = order.Price + _targetAtrFactor * _atr;
_stopPrice = order.Price - _stopAtrFactor * _atr;
}
else if (order.Direction == OrderDirection.Sell)
{
_targetPrice = order.Price - _targetAtrFactor * _atr;
_stopPrice = order.Price + _stopAtrFactor * _atr;
}
_stopOrder = StopMarketOrder(_symbol, -1 * order.Quantity, _stopPrice);
_targetOrder = LimitOrder(_symbol, -1 * order.Quantity, _targetPrice);
}
if (_targetOrder != null && _stopOrder != null && orderEvent.OrderId == _stopOrder.OrderId && orderEvent.Status == OrderStatus.Filled)
{
_targetOrder.Cancel();
_stopOrder = null;
_targetOrder = null;
}
if (_targetOrder != null && _stopOrder != null && orderEvent.OrderId == _targetOrder.OrderId && orderEvent.Status == OrderStatus.Filled)
{
_stopOrder.Cancel();
_stopOrder = null;
_targetOrder = null;
}
if (order.Tag.ToUpper(CultureInfo.InvariantCulture) == "LIQUIDATE")
{
if (_stopOrder != null) _stopOrder.Cancel();
if (_targetOrder != null) _targetOrder.Cancel();
_stopOrder = null;
_targetOrder = null;
}
}
public override void OnSecuritiesChanged(SecurityChanges changes)
{
if (changes.RemovedSecurities.Count > 0)
{
// We don't need to call Liquidate(_symbol),
// since its positions are liquidated because the contract has expired.
RemoveSymbol();
}
if (changes.AddedSecurities.Count > 0)
AddSymbol(changes.AddedSecurities.FirstOrDefault().Symbol);
else
{
/****
NOTE 5: For some reason 6/16/20, 7/21/20, 8/18, 9/15 and 10/20 it hits this point,
so there are no securities working in algorithm at this point. These are all third
Tuesdays of the month
****/
Debug("Added Secuirty count 0 on " + Time.ToShortDateString());
}
}
//public override void OnEndOfAlgorithm()
//{
//}
#region Private Methods
private void RemoveSymbol()
{
if (_symbol != null && _consolidator != null)
{
// Remove the consolidator for the previous contract
// and reset the indicators
SubscriptionManager.RemoveConsolidator(_symbol, _consolidator);
_bBands.Reset();
_atr.Reset();
_self.Reset();
Liquidate();
}
}
private void AddSymbol(Symbol sym)
{
_symbol = sym;
// Create a new consolidator and register the indicators to it
_consolidator = new TradeBarConsolidator(TimeSpan.FromMinutes(5));
_consolidator.DataConsolidated += OnDataConsolidated;
SubscriptionManager.AddConsolidator(_symbol, _consolidator);
RegisterIndicator(_symbol, _bBands, _consolidator);
RegisterIndicator(_symbol, _atr, _consolidator);
RegisterIndicator(_symbol, _self, _consolidator);
// Warm up the indicators
WarmUpIndicator(_symbol, _bBands, TimeSpan.FromMinutes(5));
WarmUpIndicator(_symbol, _atr, TimeSpan.FromMinutes(5));
PlotIndicators();
}
private void PlotIndicators()
{
Plot("EMA Cross", "UpperBol", _bBands.UpperBand);
Plot("EMA Cross", "LowerBol", _bBands.LowerBand);
Plot("EMA Cross", "Close", _self);
Plot("ATR", "ATR", _atr);
}
#endregion
}
}