Quantopian's community platform is shutting down. Please read this post for more information and download your code.
Back to Community
Getting nonsensical results on minutized trading with bracketed trades

HI guys,

I've got a complicated algo that is set up to work on 5 minute/ 15 minute or 30 minute bars. I've been confounded by Quantopian repeatedly filling my target or breach orders before the original order is filled. Of course that can happen now and then, due to gaps in pricing, but it should not happen in the following documented situation on SPY, which suggests that the data reported by Q on limit orders may be flawed:

for the date 2015/10/15 at 13:43 when close price is registered at 200.14 with minute bar high of 200.14 and low of 199.18
we place following bracket orders:




get_order(context.target_order_id): Order for -4200 shares of (Limit $NaN.N, Open)  
amount: -4200  
commission: None  
created: 2015-10-15 13:43:00+00:00  
dt: 2015-10-15 13:43:00+00:00  
filled: 0  
id: 696e15035e1c40febd97d7f92329b07c  
keys: <instancemethod>  
limit: 200.24  
limit_reached: False  
reason: None  
sid: Equity(8554, symbol=u'SPY', asset_name=u'SPDR S&P 500 ETF TRUST', exchange=u'NYSE ARCA EXCHANGE', start_date=Timestamp('1993-01-29 00:00:00+0000', tz='UTC'), end_date=Timestamp('2015-10-19 00:00:00+0000', tz='UTC'), first_traded=None)  
status: 0  
stop: None  
stop_reached: False  
to_series: <instancemethod>  
pNl: NameError: name 'pNl' is not defined  
get_datetime().minute % context.interval: 3  
get_order(context.entry_order_id): Order for 4200 shares of (Limit $NaN.N, Open)  
amount: 4200  
commission: None  
created: 2015-10-15 13:43:00+00:00  
dt: 2015-10-15 13:43:00+00:00  
filled: 0  
id: d3057efecb164810a59bb61546274363  
keys: <instancemethod>  
limit: 200.22  
limit_reached: False  
reason: None  
sid: Equity(8554, symbol=u'SPY', asset_name=u'SPDR S&P 500 ETF TRUST', exchange=u'NYSE ARCA EXCHANGE', start_date=Timestamp('1993-01-29 00:00:00+0000', tz='UTC'), end_date=Timestamp('2015-10-19 00:00:00+0000', tz='UTC'), first_traded=None)  
status: 0  
stop: None  
stop_reached: False  
to_series: <instancemethod>

in the next minute the context variables show



get_order(context.target_order_id): Order for -4200 shares of (Limit $NaN.N, Filled)  
amount: -4200  
commission: 25.2  
created: 2015-10-15 13:43:00+00:00  
dt: 2015-10-15 13:44:00+00:00  
filled: -4200  
id: 696e15035e1c40febd97d7f92329b07c  
keys: <instancemethod>  
limit: 200.24  
limit_reached: True  
reason: None  
sid: Equity(8554, symbol=u'SPY', asset_name=u'SPDR S&P 500 ETF TRUST', exchange=u'NYSE ARCA EXCHANGE', start_date=Timestamp('1993-01-29 00:00:00+0000', tz='UTC'), end_date=Timestamp('2015-10-19 00:00:00+0000', tz='UTC'), first_traded=None)  
status: 1  
stop: None  
stop_reached: False  
to_series: <instancemethod>  
pNl: NameError: name 'pNl' is not defined  
get_datetime().minute % context.interval: 4  
get_order(context.entry_order_id): Order for 4200 shares of (Limit $NaN.N, Open)  
amount: 4200  
commission: None  
created: 2015-10-15 13:43:00+00:00  
dt: 2015-10-15 13:43:00+00:00  
filled: 0  
id: d3057efecb164810a59bb61546274363  
keys: <instancemethod>  
limit: 200.22  
limit_reached: False  
reason: None  
sid: Equity(8554, symbol=u'SPY', asset_name=u'SPDR S&P 500 ETF TRUST', exchange=u'NYSE ARCA EXCHANGE', start_date=Timestamp('1993-01-29 00:00:00+0000', tz='UTC'), end_date=Timestamp('2015-10-19 00:00:00+0000', tz='UTC'), first_traded=None)  
status: 0  
stop: None  
stop_reached: False  
to_series: <instancemethod>

This is nonsensical: for the target order to have filled, the price must have attained at least 200.24. Yet the entry order is saying it never got filled, although this only required a price of at least 200.22. In theory, that could occur if we had a gap up, with sell order filling before the entry order (a buy). However, there was no gap.

In fact both orders would have been filled, as price opened at 200.18 and went up to 200.34 before closing at 200.33

But that's just the beginning. My algo traps for situations where my closing order fills before my entry order can fill. When it encounters such situations I simply close out all positions, take my lumps, and start the algo afresh. The problem is, no matter what I try, I cannot cancel the original entry orders, so the system incorrectly shows my to be short X number of shares ( my sell order above target price filled but not my buy order below it).

Just in case I am doing something wrong, this is how I track my position:

 context.current_position = context.portfolio.positions[context.stock].amount

Here's how I close any open orders:



def closeAnyOpenOrders(context):  
    orders = get_open_orders(context.stock)  
    if orders:  
        for order in orders:  
             message = 'Canceling order for {amount} shares in {stock}'  
             message = message.format(amount=order.amount, stock=context.stock)  
             #log.debug(message)  
             cancel_order(order)  
    context.current_position =0  
    context.bracketOrdersPlaced = False  

and here's how I simultaneously place my bracket orders:


def placeBracketOrders(shares,context):  
        context.bracketOrdersPlaced = False  
        if context.entryLimit is None or context.target is None or context.stopLimit is None or context.breachpoint is None:  
            if context.debug2: log.info("ERROR: time: {} MISSING context variables entry:{} target: {} breach: {} stoplimit" . format(context.strtime,context.entryLimit,context.breachpoint,context.stopLimit) )  
            return  context.bracketOrdersPlaced       

        context.entry_order_id=order_target(context.stock, shares, style=LimitOrder(context.entryLimit))  
        context.target_order_id=order_target(context.stock, -shares, style=LimitOrder(context.target))  
        context.breach_order_id=order_target(context.stock, -shares, style=StopLimitOrder(context.stopLimit,context.breachpoint))  
        if shares >0 :  
            context.openPosition = 1  
            direction = 'long'  
        elif shares<0:  
            context.openPosition = -1  
            direction='short'  
        else:  
            context.openPosition = 0  
        if context.entry_order_id is None or context.target_order_id is None or context.breach_order_id is None :  
            context.bracketOrdersPlaced =False  
            if context.debug2: log.info("time: {} INCOMPLETE ATTEMPT going {}.limit {} target {} PROTECTION  stop {} stoplimit {}"   . format(context.strtime,direction,context.entryLimit,context.target,context.breachpoint,context.stopLimit) )            

        else:  
            context.bracketOrdersPlaced =True  
            if context.debug2: log.info("time: {}SUCCESSFUL ATTEMPT going {} limit {} target {} PROTECTION  stop {} stoplimit {}"   . format(context.strtime,direction,context.entryLimit,context.target,context.breachpoint,context.stopLimit) )              

        return context.bracketOrdersPlaced  


I realize one work around would be to place my original order, and then only place my bracketing (target/breach) orders once I've got
confirmation that the original order filled. But this can take up to 2 minutes for Q to detect, which is far too long in any day trading strategy.

Am I beating a dead horse trying to make Q work for day trading?

Thanks

2 responses

Hmm I wonder if zipline only checks the closing price of the minute to execute limit orders? That would explain what you saw, right - it "gapped" up to 200.33 leaving the limit buy stranded, even though it was marketable from the open price of the minute...

I suspect something like that. I'm going to test it on longer intervals (30 minutes) and I'll post my results to this queue.