Skip to content

Commit

Permalink
Fixes Market On Open Fill of Equity Fill Model (QuantConnect#5679)
Browse files Browse the repository at this point in the history
* Fixes Market On Open Fill of Equity Fill Model

Only use trade data (Tick with Trade type or TradeBar) to get the open price, since MOO is filled with the opening action price. Ensure that this method doesn't use trade data from before the market opens for high-resolution data case.

Fix unit tests to show that the new implementation only fills with trade data from the current open market.

Change regression tests to reflect the bug fix.
In the `ExtendedMarketHoursHistoryRegressionAlgorithm`, MOO was filled with extended market hours.

* Fix Bug for Tick Resolution Case

For tick susbcription, the tick with the open price information is the the first valid (non-zero) tick of trade type from an open market.
Addresses peer-review by moving the if-condition for data belonging to the open market where the subscriscribed types are checked.

* Fix Bug for Low Resolution Edge Case

For the edge case where the order is placed after the trade bar is open, for example, order places at 1 pm with daily-resolution data. The fill model will not use the open of the bar that will close at midnight, since this value is prior to the order.

Adds unit test.

Change regression tests to reflect the bug fix.
In the `RegressionAlgorithm`, MOO was filled with open prior to the order. The algorithm now has one order less, since the last MOO would need to wait another day to be filled.

* Implements SaleCondition and Exchange Check For Tick

- Adds additional unit tests for MOO

* Fixes Regression Test in DataConsolidatorPythonWrapperTests

* Addresses Peer-Review

- Adds new unit test cases.
  • Loading branch information
AlexCatarino authored Aug 5, 2021
1 parent 1a02fba commit 9b7af08
Show file tree
Hide file tree
Showing 8 changed files with 354 additions and 111 deletions.
26 changes: 13 additions & 13 deletions Algorithm.CSharp/ExtendedMarketHoursHistoryRegressionAlgorithm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,33 +127,33 @@ public override void OnEndOfAlgorithm()
/// </summary>
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
{
{"Total Trades", "18"},
{"Total Trades", "19"},
{"Average Win", "0%"},
{"Average Loss", "0.00%"},
{"Compounding Annual Return", "-74.117%"},
{"Compounding Annual Return", "-73.997%"},
{"Drawdown", "2.500%"},
{"Expectancy", "-1"},
{"Net Profit", "-1.044%"},
{"Sharpe Ratio", "-8.269"},
{"Net Profit", "-1.040%"},
{"Sharpe Ratio", "-8.265"},
{"Probabilistic Sharpe Ratio", "0%"},
{"Loss Rate", "100%"},
{"Win Rate", "0%"},
{"Profit-Loss Ratio", "0"},
{"Alpha", "-0.187"},
{"Beta", "0.584"},
{"Beta", "0.585"},
{"Annual Standard Deviation", "0.065"},
{"Annual Variance", "0.004"},
{"Information Ratio", "1.354"},
{"Information Ratio", "1.345"},
{"Tracking Error", "0.048"},
{"Treynor Ratio", "-0.926"},
{"Total Fees", "$20.45"},
{"Estimated Strategy Capacity", "$350000.00"},
{"Treynor Ratio", "-0.925"},
{"Total Fees", "$21.45"},
{"Estimated Strategy Capacity", "$830000.00"},
{"Lowest Capacity Asset", "SPY R735QTJ8XC9X"},
{"Fitness Score", "0.002"},
{"Fitness Score", "0.003"},
{"Kelly Criterion Estimate", "0"},
{"Kelly Criterion Probability Value", "0"},
{"Sortino Ratio", "-11.829"},
{"Return Over Maximum Drawdown", "-71.014"},
{"Sortino Ratio", "-11.746"},
{"Return Over Maximum Drawdown", "-71.142"},
{"Portfolio Turnover", "0.341"},
{"Total Insights Generated", "0"},
{"Total Insights Closed", "0"},
Expand All @@ -168,7 +168,7 @@ public override void OnEndOfAlgorithm()
{"Mean Population Magnitude", "0%"},
{"Rolling Averaged Population Direction", "0%"},
{"Rolling Averaged Population Magnitude", "0%"},
{"OrderListHash", "fbefbed1d94294a14bc563a71b336056"}
{"OrderListHash", "6ee62edf1ac883882b0fcef8cb3e9bae"}
};
}
}
22 changes: 11 additions & 11 deletions Algorithm.CSharp/OrderTicketDemoAlgorithm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -491,30 +491,30 @@ private bool TimeIs(int day, int hour, int minute)
{"Total Trades", "8"},
{"Average Win", "0%"},
{"Average Loss", "-0.01%"},
{"Compounding Annual Return", "91.903%"},
{"Compounding Annual Return", "91.836%"},
{"Drawdown", "0.100%"},
{"Expectancy", "-1"},
{"Net Profit", "0.837%"},
{"Sharpe Ratio", "12.937"},
{"Probabilistic Sharpe Ratio", "99.060%"},
{"Net Profit", "0.836%"},
{"Sharpe Ratio", "12.924"},
{"Probabilistic Sharpe Ratio", "99.044%"},
{"Loss Rate", "100%"},
{"Win Rate", "0%"},
{"Profit-Loss Ratio", "0"},
{"Alpha", "0.248"},
{"Beta", "0.229"},
{"Alpha", "0.247"},
{"Beta", "0.23"},
{"Annual Standard Deviation", "0.054"},
{"Annual Variance", "0.003"},
{"Information Ratio", "-7.423"},
{"Information Ratio", "-7.426"},
{"Tracking Error", "0.172"},
{"Treynor Ratio", "3.062"},
{"Treynor Ratio", "3.059"},
{"Total Fees", "$8.00"},
{"Estimated Strategy Capacity", "$48000000.00"},
{"Lowest Capacity Asset", "SPY R735QTJ8XC9X"},
{"Fitness Score", "0.093"},
{"Kelly Criterion Estimate", "0"},
{"Kelly Criterion Probability Value", "0"},
{"Sortino Ratio", "152.568"},
{"Return Over Maximum Drawdown", "1192.069"},
{"Sortino Ratio", "150.447"},
{"Return Over Maximum Drawdown", "1180.131"},
{"Portfolio Turnover", "0.093"},
{"Total Insights Generated", "0"},
{"Total Insights Closed", "0"},
Expand All @@ -529,7 +529,7 @@ private bool TimeIs(int day, int hour, int minute)
{"Mean Population Magnitude", "0%"},
{"Rolling Averaged Population Direction", "0%"},
{"Rolling Averaged Population Magnitude", "0%"},
{"OrderListHash", "4c82126e7ba029738d8bd2ad39cc5ed9"}
{"OrderListHash", "11d64052951ca2d3abf586c88d41a97a"}
};
}
}
28 changes: 14 additions & 14 deletions Algorithm.CSharp/RegressionAlgorithm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,33 +85,33 @@ public override void OnData(Slice data)
/// </summary>
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
{
{"Total Trades", "1590"},
{"Total Trades", "1589"},
{"Average Win", "0.00%"},
{"Average Loss", "0.00%"},
{"Compounding Annual Return", "-1.167%"},
{"Compounding Annual Return", "-1.158%"},
{"Drawdown", "0.000%"},
{"Expectancy", "-0.974"},
{"Expectancy", "-0.989"},
{"Net Profit", "-0.016%"},
{"Sharpe Ratio", "-8.696"},
{"Probabilistic Sharpe Ratio", "0.002%"},
{"Loss Rate", "99%"},
{"Win Rate", "1%"},
{"Profit-Loss Ratio", "3.17"},
{"Sharpe Ratio", "-8.353"},
{"Probabilistic Sharpe Ratio", "0.025%"},
{"Loss Rate", "100%"},
{"Win Rate", "0%"},
{"Profit-Loss Ratio", "1.81"},
{"Alpha", "-0.006"},
{"Beta", "-0.001"},
{"Annual Standard Deviation", "0.001"},
{"Annual Variance", "0"},
{"Information Ratio", "-7.193"},
{"Information Ratio", "-7.192"},
{"Tracking Error", "0.195"},
{"Treynor Ratio", "8.201"},
{"Total Fees", "$1590.00"},
{"Treynor Ratio", "8.322"},
{"Total Fees", "$1589.00"},
{"Estimated Strategy Capacity", "$67000000.00"},
{"Lowest Capacity Asset", "AIG R735QTJ8XC9X"},
{"Fitness Score", "0"},
{"Kelly Criterion Estimate", "0"},
{"Kelly Criterion Probability Value", "0"},
{"Sortino Ratio", "-14.995"},
{"Return Over Maximum Drawdown", "-72.578"},
{"Sortino Ratio", "-15.241"},
{"Return Over Maximum Drawdown", "-72.582"},
{"Portfolio Turnover", "0.018"},
{"Total Insights Generated", "0"},
{"Total Insights Closed", "0"},
Expand All @@ -126,7 +126,7 @@ public override void OnData(Slice data)
{"Mean Population Magnitude", "0%"},
{"Rolling Averaged Population Direction", "0%"},
{"Rolling Averaged Population Magnitude", "0%"},
{"OrderListHash", "98388f6232375a64b6858a6e3e82c321"}
{"OrderListHash", "121beea4033002e611c01dad24956f88"}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,30 +75,30 @@ public override void OnOrderEvent(OrderEvent orderEvent)
{"Total Trades", "199"},
{"Average Win", "0.04%"},
{"Average Loss", "0.00%"},
{"Compounding Annual Return", "1331.036%"},
{"Compounding Annual Return", "1331.360%"},
{"Drawdown", "0.600%"},
{"Expectancy", "132.053"},
{"Expectancy", "132.065"},
{"Net Profit", "3.461%"},
{"Sharpe Ratio", "38.696"},
{"Sharpe Ratio", "38.704"},
{"Probabilistic Sharpe Ratio", "99.757%"},
{"Loss Rate", "1%"},
{"Win Rate", "99%"},
{"Profit-Loss Ratio", "133.60"},
{"Alpha", "6.068"},
{"Profit-Loss Ratio", "133.61"},
{"Alpha", "6.07"},
{"Beta", "0.798"},
{"Annual Standard Deviation", "0.198"},
{"Annual Variance", "0.039"},
{"Information Ratio", "57.987"},
{"Information Ratio", "57.997"},
{"Tracking Error", "0.098"},
{"Treynor Ratio", "9.585"},
{"Treynor Ratio", "9.587"},
{"Total Fees", "$260.38"},
{"Estimated Strategy Capacity", "$400000.00"},
{"Lowest Capacity Asset", "AIG R735QTJ8XC9X"},
{"Fitness Score", "0.621"},
{"Kelly Criterion Estimate", "34.359"},
{"Kelly Criterion Probability Value", "0.442"},
{"Sortino Ratio", "79228162514264337593543950335"},
{"Return Over Maximum Drawdown", "30069.961"},
{"Return Over Maximum Drawdown", "30277.012"},
{"Portfolio Turnover", "0.621"},
{"Total Insights Generated", "5"},
{"Total Insights Closed", "3"},
Expand All @@ -113,7 +113,7 @@ public override void OnOrderEvent(OrderEvent orderEvent)
{"Mean Population Magnitude", "0%"},
{"Rolling Averaged Population Direction", "100%"},
{"Rolling Averaged Population Magnitude", "0%"},
{"OrderListHash", "0d7ee294394cc50ff154627b3f64b766"}
{"OrderListHash", "1cb6986aa4193a8722b0a9d502776ebb"}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,30 +77,30 @@ public override void OnOrderEvent(OrderEvent orderEvent)
{"Total Trades", "239"},
{"Average Win", "0.05%"},
{"Average Loss", "-0.01%"},
{"Compounding Annual Return", "434.839%"},
{"Compounding Annual Return", "434.243%"},
{"Drawdown", "1.300%"},
{"Expectancy", "1.939"},
{"Net Profit", "2.167%"},
{"Sharpe Ratio", "11.682"},
{"Probabilistic Sharpe Ratio", "70.330%"},
{"Expectancy", "1.938"},
{"Net Profit", "2.166%"},
{"Sharpe Ratio", "11.667"},
{"Probabilistic Sharpe Ratio", "70.318%"},
{"Loss Rate", "31%"},
{"Win Rate", "69%"},
{"Profit-Loss Ratio", "3.27"},
{"Alpha", "0.853"},
{"Profit-Loss Ratio", "3.26"},
{"Alpha", "0.849"},
{"Beta", "1.059"},
{"Annual Standard Deviation", "0.253"},
{"Annual Variance", "0.064"},
{"Information Ratio", "10.492"},
{"Information Ratio", "10.465"},
{"Tracking Error", "0.092"},
{"Treynor Ratio", "2.789"},
{"Treynor Ratio", "2.785"},
{"Total Fees", "$399.15"},
{"Estimated Strategy Capacity", "$470000.00"},
{"Lowest Capacity Asset", "AIG R735QTJ8XC9X"},
{"Fitness Score", "0.938"},
{"Kelly Criterion Estimate", "34.359"},
{"Kelly Criterion Probability Value", "0.442"},
{"Sortino Ratio", "79228162514264337593543950335"},
{"Return Over Maximum Drawdown", "484.157"},
{"Return Over Maximum Drawdown", "482.497"},
{"Portfolio Turnover", "0.938"},
{"Total Insights Generated", "5"},
{"Total Insights Closed", "3"},
Expand All @@ -115,7 +115,7 @@ public override void OnOrderEvent(OrderEvent orderEvent)
{"Mean Population Magnitude", "0%"},
{"Rolling Averaged Population Direction", "100%"},
{"Rolling Averaged Population Magnitude", "0%"},
{"OrderListHash", "e5836f3f083d102f1b862c8cb4d5f220"}
{"OrderListHash", "d661c77833be41c963b94944b00889ff"}
};
}
}
62 changes: 53 additions & 9 deletions Common/Orders/Fills/EquityFillModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@
using System.Linq;
using QLNet;
using QuantConnect.Data;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Data.Market;
using QuantConnect.Python;
using QuantConnect.Orders.Fees;
using QuantConnect.Securities;
using QuantConnect.Securities.Equity;
using QuantConnect.Util;

namespace QuantConnect.Orders.Fills
Expand Down Expand Up @@ -493,12 +495,56 @@ public virtual OrderEvent MarketOnOpenFill(Security asset, MarketOnOpenOrder ord
// have large gaps, in which case the currentBar.EndTime will be in the past
// ASUR | | | [order] | | | | | | |
// SPY | | | | | | | | | | | | | | | | | | | |
var currentBar = asset.GetLastData();
var localOrderTime = order.Time.ConvertFromUtc(asset.Exchange.TimeZone);
if (currentBar == null || localOrderTime >= currentBar.EndTime) return fill;
var endTime = DateTime.MinValue;

var subscribedTypes = GetSubscribedTypes(asset);

if (subscribedTypes.Contains(typeof(Tick)))
{
var primaryExchange = (byte) ((Equity) asset).PrimaryExchange;
var officialOpen = (uint) (TradeConditionFlags.Regular | TradeConditionFlags.OfficialOpen);
var openingPrints = (uint) (TradeConditionFlags.Regular | TradeConditionFlags.OpeningPrints);

// Get the first valid (non-zero) tick of trade type from an open market
var trade = asset.Cache.GetAll<Tick>()
.Where(x => !string.IsNullOrWhiteSpace(x.SaleCondition))
.FirstOrDefault(x =>
x.TickType == TickType.Trade && x.Price > 0 && x.ExchangeCode == primaryExchange &&
(x.ParsedSaleCondition == officialOpen || x.ParsedSaleCondition == openingPrints) &&
asset.Exchange.DateTimeIsOpen(x.Time));

if (trade != null)
{
endTime = trade.EndTime;
fill.FillPrice = trade.Price;
}
}
else if (subscribedTypes.Contains(typeof(TradeBar)))
{
var tradeBar = asset.Cache.GetData<TradeBar>();
if (tradeBar != null)
{
// If the order was placed during the bar aggregation, we cannot use its open price
if (tradeBar.Time < localOrderTime) return fill;

// We need to verify whether the trade data is from the open market.
if (tradeBar.Period < Resolution.Hour.ToTimeSpan() && !asset.Exchange.DateTimeIsOpen(tradeBar.Time))
{
return fill;
}

endTime = tradeBar.EndTime;
fill.FillPrice = tradeBar.Open;
}
}

if (localOrderTime >= endTime) return fill;

// if the MOO was submitted during market the previous day, wait for a day to turn over
if (asset.Exchange.DateTimeIsOpen(localOrderTime) && localOrderTime.Date == asset.LocalTime.Date)
// The date of the order and the trade data end time cannot be the same.
// Note that the security local time can be ahead of the data end time.
if (asset.Exchange.DateTimeIsOpen(localOrderTime) && localOrderTime.Date == endTime.Date)
{
return fill;
}
Expand All @@ -507,8 +553,10 @@ public virtual OrderEvent MarketOnOpenFill(Security asset, MarketOnOpenOrder ord
// make sure the exchange is open/normal market hours before filling
if (!IsExchangeOpen(asset, false)) return fill;

fill.FillPrice = GetPricesCheckingPythonWrapper(asset, order.Direction).Open;
// assume the order completely filled
fill.FillQuantity = order.Quantity;
fill.Status = OrderStatus.Filled;

//Calculate the model slippage: e.g. 0.01c
var slip = asset.SlippageModel.GetSlippageApproximation(asset, order);

Expand All @@ -517,13 +565,9 @@ public virtual OrderEvent MarketOnOpenFill(Security asset, MarketOnOpenOrder ord
{
case OrderDirection.Buy:
fill.FillPrice += slip;
// assume the order completely filled
fill.FillQuantity = order.Quantity;
break;
case OrderDirection.Sell:
fill.FillPrice -= slip;
// assume the order completely filled
fill.FillQuantity = order.Quantity;
break;
}

Expand Down Expand Up @@ -809,4 +853,4 @@ protected static bool IsExchangeOpen(Security asset, bool isExtendedMarketHours)
return true;
}
}
}
}
Loading

0 comments on commit 9b7af08

Please sign in to comment.