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