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 } }