Why didn't my MOC order exit.
"WHY DIDN'T MY MARKET ON CLOSE ORDER EXECUTE?"
We often get this question from people who want write a model that executes "at the close price".
They enter a Market On Close (MOC) order with plenty of time before the close but the symbol is stuck in the position at the end of the day as if the MOC order did not execute.
So, there are two main reasons for failure...
- Timing : You did not submit your order early enough, there is a cutoff for each exchange and these cutoff times have varied through the years. Place the order in plenty of time and see if it executes. Also, be careful on half trading days!
- Thickness : MOC is an end of day auction, this assumes there are enough traders in the auction to merit an auction. If the symbol has low volume or low trades then the likelihood is there was not a closing auction and no '6' print for the symbol.
To find a '6' print make a script for your symbol, run it through the close (so you can just run 15:59 to 16:20)
def on_trade(self, event, md, order, service, account):
if '6' in md.L1.trade_condition:
print service.time_to_string(event.timestamp,"%H:%M:%S.%f"),self.symbol, md.L1.trade_condition
On a typical day you will see around 8800 symbols available to you in the backtest.
I ran a random date and got 8778 symbols.
Of these 32% (2731) failed a MOC order.
Of these 2731...
- 91.6% (2502) were under 100k avol
- 95.5% (2609) were under 200k avol
- 98% (2676) were under 0.5m avol
99% (2704) were under 1m avol
80% (2175) had <100 average count for a trading day
- 89% (2431) had <200 average count for a trading day
- 98% (2677) had <1000 average count for a trading day
So, by simply disqualifying symbols that are below certain average volume or average count levels, you can significantly reduce the number of symbols that you will be stuck in after the close.
In is_symbol_qualified add something like the following...
AVOL : avol is easy, it is a static variable (one that does not change through the day), so is available all the time..
return md.stat.avol > 100000
Count : for the count do something like this...
countbars = md.bar.daily(start = -20).count
if len(countbars)>0:
count = sum(countbars)/len(countbars)
else:
count = 0
Comments
This is great!
I've noticed that these techniques also lead to better fills with Lime MidPoint Peg, and the custom orders like TWAP and VWAP.
If you can post more articles like this, I for one get a great deal of benefit from them.
Thanks!