Can't attach IBKR orders in zipline live

Hi Brian,

I've been trying to bracket two orders together (one to open the position, the other a stop loss) in zipline live trading. I've tried using the 'OrderId' and 'ParentId' parameters as follows:

# Parent order

params = {  'Tif' : 'DAY',
            'AlgoStrategy' : 'Adaptive',
            'FaGroup' : context.alloc_group,
            'FaMethod' : context.alloc_method,
            'OrderId' : some_id }

market_style = MarketOrder(exchange='SMART', order_params=params)
new_orderId = order_target_percent(asset, order_perc, style=market_style)


# Child order

params = {  'Tif' : 'GTC',
            'FaGroup' : context.alloc_group,
            'FaMethod' : context.alloc_method,
            'ParentId' : some_id }

stop_style = StopOrder(stop_price, asset=asset, exchange='SMART', order_params=params)
new_orderId = order_target_percent(asset, -order_perc, style=stop_style)

The parent order is executed as expected. When submitting the child order, I get an exception: 'no parent order found for order'.

I thought maybe the parent order was executed before the child was received, so I tried using the 'Transmit' parameter to prevent the execution of the parent until both are submitted. But the same exception is thrown.

Going through the logs, it seems like maybe the 'OrderId' field for the parent is not being passed to IBKR correctly. Is there something in the zipline order execution code that would remove or alter this field?

Worth noting that both orders execute as expected when I remove the 'OrderId' and 'ParentId' fields from order_params, i.e. the rest of the code works fine.

Thanks,
Paul.

That looks like an IBKR error message. Parent-child orders don’t work for every combination of order type, asset, exchange, and time in force. You should first verify that what you’re trying to do is possible by submitting the exact order parameters manually through TWS.

I'm able to place this exact bracket combo in TWS without issue.

I created a quick test outside my main algo to replicate and get the same error. I checked the IB API logs and can find transmission of the parent order but not the child. The error seems to occur when houston makes the order request to the quantrocket blotter, per the last line of the traceback below.

Can you try to replicate?

Full test code:

from zipline.api import sid, get_environment, cancel_policy, set_cancel_policy, order_target_percent, set_realtime_db
from zipline.finance.execution import StopOrder, MarketOrder
from quantrocket.realtime import collect_market_data

def initialize(context):

    context.dayCount = 0

    # Zipline settings
    set_cancel_policy(cancel_policy.NeverCancel())

   
def before_trading_start(context, data):

    context.dayCount += 1
    context.minCount = 0

    # Request tick data for SPY 
    sids = ['FIBBG000BDTBL9']

    collect_market_data('all-stk-etf-tick',
                        sids=sids,
                        until='16:01:00 America/New_York')

        

    # Assign fields to zipline
    set_realtime_db('usstock-tick-1min',
                    fields={'close': 'LastPriceClose',
                            'open': 'LastPriceOpen',
                            'high': 'LastPriceHigh',
                            'low': 'LastPriceLow',
                            'volume': 'VolumeClose' })

    return

def handle_data(context, data):

    context.minCount += 1

    spy_asset = sid('FIBBG000BDTBL9')

    # Perform test on first minute only
    if (context.dayCount == 1) and (context.minCount == 1):

        # Place parent order
        params = { 'Tif' : 'DAY',
                   'AlgoStrategy' : 'Adaptive',
                   'FaGroup' : 'Group0',
                   'FaMethod' : 'AvailableEquity',
                   'OrderId' : 1000 }

        market_style = MarketOrder(exchange='SMART', order_params=params)
        parent_orderId = order_target_percent(spy_asset, -0.5, style=market_style)

        print('Placing PARENT order {} with params: {}'.format(parent_orderId, params))

        # Place child order
        params = { 'Tif' : 'DAY',
                   'FaGroup' : 'Group0',
                   'FaMethod' : 'AvailableEquity',
                   'ParentId' : 1000 }

        stop_style = StopOrder(500, asset=spy_asset, exchange='SMART', order_params=params)
        child_orderId = order_target_percent(spy_asset, 0.5, style=stop_style)

        print('Placing CHILD order {} with params: {}'.format(child_orderId, params))

    return

The full traceback:

quantrocket_zipline_1|Placing PARENT order 6001:44 with params: {'Tif': 'DAY', 'AlgoStrategy': 'Adaptive', 'FaGroup': 'Group0', 'FaMethod': 'AvailableEquity', 'OrderId': 1000}
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR Traceback (most recent call last):
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR   File "sym://qrocket_log_py", line 34, in wrapped
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR   File "sym://qrocket_qrzipline_trade_trade_py", line 63, in mule_trade_algo
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR   File "sym://qrocket_qrzipline_trade_trade_py", line 187, in trade_algo
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR   File "/opt/conda/lib/python3.6/site-packages/zipline/algorithm.py", line 675, in run
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR     for perf in self.get_generator():
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR   File "/opt/conda/lib/python3.6/site-packages/zipline/gens/tradesimulation.py", line 205, in transform
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR     for capital_change_packet in every_bar(dt):
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR   File "/opt/conda/lib/python3.6/site-packages/zipline/gens/tradesimulation.py", line 133, in every_bar
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR     handle_data(algo, current_data, dt_to_use)
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR   File "/opt/conda/lib/python3.6/site-packages/zipline/utils/events.py", line 218, in handle_data
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR     dt,
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR   File "/opt/conda/lib/python3.6/site-packages/zipline/utils/events.py", line 237, in handle_data
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR     self.callback(context, data)
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR   File "sym://qrocket_qrzipline_trade_algorithm_py", line 161, in handle_data
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR   File "/opt/conda/lib/python3.6/site-packages/zipline/algorithm.py", line 485, in handle_data
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR     self._handle_data(self, data)
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR   File "bracketTest", line 84, in handle_data
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR   File "/opt/conda/lib/python3.6/site-packages/zipline/utils/api_support.py", line 56, in wrapped
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR     return getattr(algo_instance, f.__name__)(*args, **kwargs)
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR   File "/opt/conda/lib/python3.6/site-packages/zipline/utils/api_support.py", line 125, in wrapped_method
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR     return method(self, *args, **kwargs)
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR   File "/opt/conda/lib/python3.6/site-packages/zipline/algorithm.py", line 2032, in order_target_percent
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR     style=style)
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR   File "/opt/conda/lib/python3.6/site-packages/zipline/utils/api_support.py", line 125, in wrapped_method
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR     return method(self, *args, **kwargs)
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR   File "/opt/conda/lib/python3.6/site-packages/zipline/algorithm.py", line 1405, in order
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR     return self.blotter.order(asset, amount, style)
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR   File "sym://qrocket_qrzipline_trade_blotter_py", line 94, in order
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR   File "sym://qrocket_qrzipline_trade_order_py", line 62, in place
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR   File "/opt/conda/lib/python3.6/site-packages/quantrocket/blotter.py", line 85, in place_orders
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR     houston.raise_for_status_with_json(response)
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR   File "/opt/conda/lib/python3.6/site-packages/quantrocket/houston.py", line 206, in raise_for_status_with_json
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR     raise e
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR   File "/opt/conda/lib/python3.6/site-packages/quantrocket/houston.py", line 198, in raise_for_status_with_json
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR     response.raise_for_status()
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR   File "/opt/conda/lib/python3.6/site-packages/requests/models.py", line 941, in raise_for_status
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR     raise HTTPError(http_error_msg, response=self)
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR requests.exceptions.HTTPError: ('400 Client Error: BAD REQUEST for url: http://houston/blotter/orders', {'status': 'error', 'msg': 'no parent order found for order: {"TotalQuantity": 118, "Sid": "FIBBG000BDTBL9", "OrderRef": "bracketTest", "Account": "DF00000000", "Tif": "GTC", "Action": "BUY", "OrderType": "STP", "AuxPrice": 500.0, "Exchange": "SMART", "FaGroup": "Group0", "FaMethod": "AvailableEquity", "ParentId": 1000}'})
  quantrocket_flightlog_1|2021-06-16 10:41:02 quantrocket.zipline: ERROR

Per the docs, the parent and child orders have to be sent to the blotter in the same batch but Zipline sends each order individually, so this is not supported.

Perhaps you could consider removing this blotter constraint, given that support for sending orders in sequence (not necessarily concurrently) already exists on the IBKR side? Matching positions with stop losses is pretty standard, and this would make it much easier.