Overall Statistics |
Total Trades 32 Average Win 0.25% Average Loss -0.52% Compounding Annual Return -66.722% Drawdown 4.500% Expectancy -0.351 Net Profit -2.872% Sharpe Ratio -4.948 Loss Rate 56% Win Rate 44% Profit-Loss Ratio 0.48 Alpha -1.348 Beta 28.78 Annual Standard Deviation 0.182 Annual Variance 0.033 Information Ratio -5.035 Tracking Error 0.182 Treynor Ratio -0.031 Total Fees $484.75 |
using QuantConnect.Data; using QuantConnect.Indicators; using QuantConnect.Indicators.CandlestickPatterns; using QuantConnect.Orders; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace QuantConnect.Algorithm.CSharp { class _DKOpeningRangeReversalWithConfirmation : QCAlgorithm { #region private variables decimal _lod = 9999999; decimal _hod = 0; // calculations decimal _avgBodyLength; // signals bool _withinLowRange = false; bool _withinHighRange = false; bool _bullishTrend = false; bool _bearishTrend = false; // candlestick Engulfing _engulfingCs; // thresholds and length decimal _percentWithinRange = 0.03m; //decimal _pctCandleBodyForEngulfing = 1.5m; int _lengthForTrendCalc = 6; // collection limits int _lengthToCalcAvgBodyHeight = 20; int _lengthCandlePeriod = 20; // collections RollingWindow<decimal> _rwBodyLength; RollingWindow<decimal> _rwOpen; RollingWindow<decimal> _rwClose; RollingWindow<decimal> _rwHigh; RollingWindow<decimal> _rwLow; RollingWindow<decimal> _rwTrend; // order settings public class OneCancelsOtherTicketSet { public OneCancelsOtherTicketSet(params OrderTicket[] orderTickets) { this.OrderTickets = orderTickets; } private OrderTicket[] OrderTickets { get; set; } public void Filled() { // Cancel all the outstanding tickets. foreach (var orderTicket in this.OrderTickets) { if (orderTicket.Status == OrderStatus.Submitted) { orderTicket.Cancel(); } } } } public class OrderChain { public OneCancelsOtherTicketSet oco; public OrderTicket limitEntryTicket; public OrderTicket stopLossTicket; public OrderTicket exitTicket; public DateTime limitDttm; public decimal stopLossPrice; public decimal profitPrice; public OrderDirection direction; public int numOfShares; } //TimeZoneInfo easternZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time"); OrderDirection _benchOrderDirection = OrderDirection.Hold; private OrderChain _orderChain; private decimal _limitPrice = 0; private decimal _stopLoss = 0; private decimal _exitPrice = 0; private int _numOfShares = 0; // portfolio settings private decimal _startingCash = 30000; // application settings private Symbol _symbol = QuantConnect.Symbol.Create("AMD", SecurityType.Equity, Market.USA); DateTime startDttm = new DateTime(2018, 4, 1); DateTime endDttm = new DateTime(2018, 4, 10); int startHour = 9; int startMinute = 50; int stopHour = 15; int stopMinute = 55; bool _debugTran = true; bool _debugSignals = true; bool _isDailyResultReported = false; // statistics private decimal _dailyPL = 0; private decimal _dailyLow = 0; private decimal _dailyHigh = 0; private int _dailyTrades = 0; private decimal _grandTotalPL = 0; private int _grandTotalTrades = 0; #endregion /// <summary> /// Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized. /// </summary> public override void Initialize() { // initialize rolling windows with length _rwBodyLength = new RollingWindow<decimal>(_lengthToCalcAvgBodyHeight); _rwOpen = new RollingWindow<decimal>(_lengthCandlePeriod); _rwClose = new RollingWindow<decimal>(_lengthCandlePeriod); _rwHigh = new RollingWindow<decimal>(_lengthCandlePeriod); _rwLow = new RollingWindow<decimal>(_lengthCandlePeriod); _rwTrend = new RollingWindow<decimal>(_lengthForTrendCalc); SetStartDate(startDttm.Year, startDttm.Month, startDttm.Day); //Set Start Date SetEndDate(endDttm.Year, endDttm.Month, endDttm.Day); //Set End Date SetCash(_startingCash); //Set Strategy Cash if ((EndDate - StartDate).Days <= 3) _debugTran = true; // Find more symbols here: http://quantconnect.com/data // Forex, CFD, Equities Resolutions: Tick, Second, Minute, Hour, Daily. // Futures Resolution: Tick, Second, Minute // Options Resolution: Minute Only. AddEquity(_symbol.Value, Resolution.Minute); // signals _engulfingCs = CandlestickPatterns.Engulfing(_symbol); } /// <summary> /// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here. /// </summary> /// <param name="data">Slice object keyed by symbol containing the stock data</param> public override void OnData(Slice data) { // if new day then reset all trackers if (data.Time.Hour == 9 && data.Time.Minute == 31) { _isDailyResultReported = false; // reset all signals and variables _rwBodyLength.Reset(); _rwOpen.Reset(); _rwClose.Reset(); _rwHigh.Reset(); _rwLow.Reset(); _rwTrend.Reset(); _withinHighRange = false; _withinLowRange = false; _bullishTrend = false; _bearishTrend = false; _benchOrderDirection = OrderDirection.Hold; } try { // when end of trading reached.... if (data.Time >= new DateTime(data.Time.Year, data.Time.Month, data.Time.Day, stopHour, stopMinute, 0)) { // if portfolio is invested then set top order to current close if (Portfolio[_symbol].Invested) { // ideally we should be converting this into market order to know for sure it will execute if (_orderChain != null) { if (_orderChain.stopLossTicket != null) { Debug($"******************************* Time: {data.Time.ToString()}, end of trading reached, updating stop loss to close out portfolio "); _orderChain.stopLossTicket.Update(new UpdateOrderFields { StopPrice = data.Bars[_symbol].Close }); } } } else { // portfolio is not invested. cancel any pending limit entry order if (_orderChain != null) { if (_orderChain.limitEntryTicket != null) { Debug($"******************************* Time: {data.Time.ToString()}, end of trading reached, cancelling open limit orders"); _orderChain.limitEntryTicket.Cancel(); _orderChain.stopLossTicket.Cancel(); _orderChain.exitTicket.Cancel(); } } else { if (!_isDailyResultReported) { _grandTotalPL += _dailyPL; _grandTotalTrades += _dailyTrades; _isDailyResultReported = true; Debug($"******************************* Time: {data.Time.ToString()}, end of trading reached, Benchmark Direction: {_benchOrderDirection.ToString()}, Trades: {_dailyTrades}, Low: {_dailyLow}, High: {_dailyHigh}, PL: { _dailyPL}"); if (this.EndDate.Year == data.Time.Year && this.EndDate.Month == data.Time.Month && this.EndDate.Day == data.Time.Day) Debug($"******************************* Time: {data.Time.ToString()}, end of algorithm, Grand Total Trades: {_grandTotalTrades}, Grand Total PL: { _grandTotalPL}"); } } } } else { // capture changes to high/low _lod = Math.Min(_lod, Math.Min(data.Bars[_symbol].Open, data.Bars[_symbol].Close)); _hod = Math.Max(_hod, Math.Max(data.Bars[_symbol].Open, data.Bars[_symbol].Close)); // CAPTURE KEY ATTRIBUTES // capture candle body attributes _rwBodyLength.Add(Math.Abs(data.Bars[_symbol].Open - data.Bars[_symbol].Close)); _rwOpen.Add(data.Bars[_symbol].Open); _rwClose.Add(data.Bars[_symbol].Close); _rwHigh.Add(data.Bars[_symbol].High); _rwLow.Add(data.Bars[_symbol].Low); // CAPTURE QUICK CALCUATIONS _avgBodyLength = _rwBodyLength.Average(); CalculateTrend(); // START TRACKING SIGNALS ONLY DURING INDICATED TRADING TIMES if (data.Time >= new DateTime(data.Time.Year, data.Time.Month, data.Time.Day, startHour, startMinute, 0) && data.Time <= new DateTime(data.Time.Year, data.Time.Month, data.Time.Day, stopHour, stopMinute, 0)) { if (!Portfolio[_symbol].Invested) { // TREND SIGNALS // capture signal - within opening range threshold SignalWithinOpeningRange(data.Time); // bullish or bearish trend confirmation signal SignalBullishTrend(data.Time); SignalBearishTrend(data.Time); // CANDLESTICK SIGNALS // SignalEngulfing(data.Time); // ALGO // if UpperRAnge && BullishConfirmation && Bearish Engulfing if (_bullishTrend && _engulfingCs == -1) { //Debug($"******************************* Time: {data.Time.ToString()} - ENTRY SHORT"); // if we already have an orderChain then cancel it first if(_orderChain != null) { if(_orderChain.limitEntryTicket != null) _orderChain.limitEntryTicket.Cancel(); if(_orderChain.stopLossTicket != null) _orderChain.stopLossTicket.Cancel(); if(_orderChain.exitTicket != null) _orderChain.exitTicket.Cancel(); } // prepare order _limitPrice = _rwClose[0]; //_stopLoss = Math.Max(_rwOpen.Skip(1).Take(_lengthForTrendCalc).Max(), _rwClose.Skip(1).Take(_lengthForTrendCalc).Max()); _stopLoss = _rwHigh.Skip(1).Take(_lengthForTrendCalc).Max(); _exitPrice = _limitPrice - (_stopLoss - _limitPrice) * 2; // improve later - how long was trend? if trend was long then may be a longer exit? Can we find support _numOfShares = -1 * Convert.ToInt32(Math.Min(Portfolio.Cash / _limitPrice, (Portfolio.Cash * 0.01m) / (_stopLoss - _limitPrice))); // place the order OrderTicket ticket = LimitOrder(_symbol, _numOfShares, _limitPrice); _orderChain = new OrderChain() { limitEntryTicket = ticket, stopLossPrice = _stopLoss, profitPrice = _exitPrice, direction = OrderDirection.Sell, numOfShares = _numOfShares }; } else if (_bearishTrend && _engulfingCs == 1) { // Debug($"******************************* Time: {data.Time.ToString()} - ENTRY LONG"); // if we already have an orderChain then cancel it first if(_orderChain != null) { if(_orderChain.limitEntryTicket != null) _orderChain.limitEntryTicket.Cancel(); if(_orderChain.stopLossTicket != null) _orderChain.stopLossTicket.Cancel(); if(_orderChain.exitTicket != null) _orderChain.exitTicket.Cancel(); } // prepare order _limitPrice = _rwClose[0]; //_stopLoss = Math.Min(_rwOpen.Skip(1).Take(_lengthForTrendCalc).Min(), _rwClose.Skip(1).Take(_lengthForTrendCalc).Min()); _stopLoss = _rwLow.Skip(1).Take(_lengthForTrendCalc).Min(); _exitPrice = _limitPrice + (_limitPrice - _stopLoss) * 2; // improve later - how long was trend? if trend was long then may be a longer exit? Can we find support _numOfShares = Convert.ToInt32(Math.Min(Portfolio.Cash / _limitPrice, (Portfolio.Cash * 0.01m) / (_limitPrice - _stopLoss))); // place the order OrderTicket ticket = LimitOrder(_symbol, _numOfShares, _limitPrice); _orderChain = new OrderChain() { limitEntryTicket = ticket, stopLossPrice = _stopLoss, profitPrice = _exitPrice, direction = OrderDirection.Buy, numOfShares = _numOfShares }; } } } } } catch (Exception e) { Debug($"******************************* Time: {data.Time.ToString()}, Exception: {e.ToString()} "); } } // Override the base class event handler for order events public override void OnOrderEvent(OrderEvent orderEvent) { // if orderStatus == "Cancelled" then print Debug if (orderEvent.Status.ToString() == "Canceled") { if (_debugTran) Debug($"******************************* Time: {orderEvent.UtcTime.AddHours(-4).ToString()}, OrderID: {orderEvent.OrderId}, CANCELLED"); } else { if (_orderChain != null) { // if orderEvent direction matches the orderChain direction if (_orderChain.direction == orderEvent.Direction) { // ...if and status is Filled then create stop order if (orderEvent.Status.IsFill()) { if (_debugTran) Debug($"******************************* Time: {orderEvent.UtcTime.AddHours(-4).ToString()}, OrderID: {orderEvent.OrderId}, Direction: {orderEvent.Direction}, Shares(Order/Fill): {_orderChain.numOfShares}/{orderEvent.FillQuantity}, FillPrice: {orderEvent.FillPrice}, StopLoss: {_orderChain.stopLossPrice}"); // record datetime of limit ordr in ET _orderChain.limitDttm = orderEvent.UtcTime.AddHours(-5); // crate stop loss order and exit ticket _orderChain.stopLossTicket = StopMarketOrder(orderEvent.Symbol, orderEvent.FillQuantity * -1, _orderChain.stopLossPrice, orderEvent.OrderId.ToString()); _orderChain.exitTicket = LimitOrder(orderEvent.Symbol, orderEvent.FillQuantity * -1, _orderChain.profitPrice, orderEvent.OrderId.ToString()); _orderChain.oco = new OneCancelsOtherTicketSet(_orderChain.stopLossTicket, _orderChain.exitTicket); } } else { // ... if status is Filled then we NULL then order chain if (orderEvent.Status.IsFill()) { decimal profitLoss = 0; string winLose = "WIN"; if (_orderChain.direction == OrderDirection.Buy) profitLoss = orderEvent.FillPrice * Math.Abs(orderEvent.FillQuantity) - _orderChain.limitEntryTicket.AverageFillPrice * _orderChain.limitEntryTicket.QuantityFilled; else profitLoss = _orderChain.limitEntryTicket.AverageFillPrice * Math.Abs(_orderChain.limitEntryTicket.QuantityFilled) - orderEvent.FillPrice * orderEvent.FillQuantity; if (profitLoss < 0) winLose = "LOSS"; _dailyTrades += 1; _dailyPL += profitLoss; if (_dailyPL < _dailyLow) _dailyLow = _dailyPL; if (_dailyPL > _dailyHigh) _dailyHigh = _dailyPL; if (_debugTran) Debug($"******************************* Time: {orderEvent.UtcTime.AddHours(-4).ToString()}, OrderID: {orderEvent.OrderId}, Direction: {orderEvent.Direction}, Shares(Order/Fill): {_orderChain.numOfShares}/{orderEvent.FillQuantity}, FillPrice: {orderEvent.FillPrice}, Profit/Loss: {profitLoss}, Win/Lose: {winLose}"); _orderChain.oco.Filled(); _orderChain = null; } } } } } #region helpers private void CalculateTrend() { //_rwOpen.Add(280.81m); _rwOpen.Add(281.56m); _rwOpen.Add(279.39m); _rwOpen.Add(282.53m); _rwOpen.Add(283.64m); _rwOpen.Add(285.39m); _rwOpen.Add(285.39m); _rwOpen.Add(285.53m); _rwOpen.Add(283.45m); //_rwClose.Add(281.33m); _rwClose.Add(280.86m); _rwClose.Add(282.39m); _rwClose.Add(283.60m); _rwClose.Add(284.64m); _rwClose.Add(285.58m); _rwClose.Add(285.46m); _rwClose.Add(285.07m); _rwClose.Add(283.16m); if (_rwOpen.Count >= _lengthForTrendCalc) { decimal min = Math.Min(_rwOpen.Skip(1).Take(_lengthForTrendCalc).Min(), _rwClose.Skip(1).Take(_lengthForTrendCalc).Min()); decimal max = Math.Max(_rwOpen.Skip(1).Take(_lengthForTrendCalc).Max(), _rwClose.Skip(1).Take(_lengthForTrendCalc).Max()); decimal mid = Math.Min(min, max) + Math.Abs(min - max) / 2; // if candle continues in directon of established trend then just continue the trend if (_bullishTrend && _rwClose[0] > _rwOpen[0]) _rwTrend.Add(1); else if (_bearishTrend && _rwClose[0] < _rwOpen[0]) _rwTrend.Add(0); else _rwTrend.Add(_rwClose[0] > mid ? 1 : 0); } } private bool SignalWithinOpeningRange(DateTime time) { bool lowRng = false; bool highRng = false; // if close < open (down trend) if (_rwClose[0] < _rwOpen[0]) { // if close is within lower range threshold if (_rwClose[0] < _lod + (_hod - _lod) * _percentWithinRange) { lowRng = true; // if first occurance of hitting lower range threshold if (lowRng && !_withinLowRange) { _withinLowRange = true; if (_debugSignals) Debug($"******************************* Time: {time.ToString()} - within opening range LOW of the day"); } } } if (!lowRng && _withinLowRange) _withinLowRange = false; // high range // if close > open (up trend) if (_rwClose[0] > _rwOpen[0]) { if (_rwClose[0] > _hod - (_hod - _lod) * _percentWithinRange) { highRng = true; if (highRng && !_withinHighRange) { _withinHighRange = true; if (_debugSignals) Debug($"******************************* Time: {time.ToString()} - within opening range LOW of the day"); } } } if (!highRng && _withinHighRange) _withinHighRange = false; return (_withinLowRange || _withinHighRange); } private bool SignalBullishTrend(DateTime time) { bool bull = false; if (_rwTrend.Average() >= 0.8m) { bull = true; if (bull && !_bullishTrend) { _bullishTrend = true; if (_debugSignals) Debug($"******************************* Time: {time.ToString()} - bullish trend confirmed"); } } if (!bull && _bullishTrend) { _bullishTrend = false; if (_debugSignals) Debug($"******************************* Time: {time.ToString()} - bullish trend ended"); } return _bullishTrend; } private bool SignalBearishTrend(DateTime time) { bool bear = false; if (_rwTrend.Average() <= 0.35m) { bear = true; if (bear && !_bearishTrend) { _bearishTrend = true; if (_debugSignals) Debug($"******************************* Time: {time.ToString()} - bearish trend confirmed"); } } if (!bear && _bearishTrend) { _bearishTrend = false; if (_debugSignals) Debug($"******************************* Time: {time.ToString()} - bearish trend ended"); } return _bearishTrend; } #endregion /// <summary> /// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm. /// </summary> public bool CanRunLocally { get; } = true; /// <summary> /// This is used by the regression test system to indicate which languages this algorithm is written in. /// </summary> public Language[] Languages { get; } = { Language.CSharp }; /// <summary> /// This is used by the regression test system to indicate what the expected statistics are from running the algorithm /// </summary> public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string> { {"Total Trades", "1"}, {"Average Win", "0%"}, {"Average Loss", "0%"}, {"Compounding Annual Return", "263.153%"}, {"Drawdown", "2.200%"}, {"Expectancy", "0"}, {"Net Profit", "1.663%"}, {"Sharpe Ratio", "4.41"}, {"Loss Rate", "0%"}, {"Win Rate", "0%"}, {"Profit-Loss Ratio", "0"}, {"Alpha", "0.007"}, {"Beta", "76.118"}, {"Annual Standard Deviation", "0.192"}, {"Annual Variance", "0.037"}, {"Information Ratio", "4.354"}, {"Tracking Error", "0.192"}, {"Treynor Ratio", "0.011"}, {"Total Fees", "$3.26"} }; } }