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