def liquidity_size_contract_order( self, contract_order_after_trade_limits: contractOrder) -> contractOrder: data_broker = dataBroker(self.data) log = contract_order_after_trade_limits.log_with_attributes(self.log) # check liquidity, and if neccessary carve up order # Note for spread orders we check liquidity in the component markets liquid_qty = ( data_broker. get_largest_offside_liquid_size_for_contract_order_by_leg( contract_order_after_trade_limits)) if liquid_qty != contract_order_after_trade_limits.trade: log.msg("Cut down order to size %s from %s because of liquidity" % (str(liquid_qty), str(contract_order_after_trade_limits.trade))) if liquid_qty.equals_zero(): return missing_order contract_order_to_trade = contract_order_after_trade_limits.replace_required_trade_size_only_use_for_unsubmitted_trades( liquid_qty) return contract_order_to_trade
def preprocess_contract_order( self, original_contract_order: contractOrder) -> contractOrder: if original_contract_order is missing_order: # weird race condition return missing_order if original_contract_order.fill_equals_desired_trade(): return missing_order if original_contract_order.is_order_controlled_by_algo(): # already being traded by an active algo return missing_order data_broker = dataBroker(self.data) # CHECK FOR LOCKS data_locks = dataLocks(self.data) instrument_locked = data_locks.is_instrument_locked( original_contract_order.instrument_code) market_closed = not (data_broker.is_contract_okay_to_trade( original_contract_order.futures_contract)) if instrument_locked or market_closed: # we don't log to avoid spamming #print("market is closed for order %s" % str(original_contract_order)) return missing_order # RESIZE contract_order_to_trade = self.size_contract_order( original_contract_order) return contract_order_to_trade
def update_contract_position_table_with_contract_order( self, contract_order_before_fills: contractOrder, fill_list: tradeQuantity): """ Alter the strategy position table according to contract order fill value :param contract_order_before_fills: :return: """ futures_contract_entire_order = contract_order_before_fills.futures_contract list_of_individual_contracts = ( futures_contract_entire_order.as_list_of_individual_contracts()) time_date = datetime.datetime.now() log = contract_order_before_fills.log_with_attributes(self.log) for contract, trade_done in zip(list_of_individual_contracts, fill_list): self._update_positions_for_individual_contract_leg( contract=contract, trade_done=trade_done, time_date=time_date) log.msg( "Updated position of %s because of trade %s ID:%d with fills %d" % ( str(contract), str(contract_order_before_fills), contract_order_before_fills.order_id, trade_done, ))
def send_to_algo( self, contract_order_to_trade: contractOrder ) -> (Algo, orderWithControls): log = contract_order_to_trade.log_with_attributes(self.log) instrument_order = self.get_parent_of_contract_order( contract_order_to_trade) contract_order_to_trade_with_algo_set = check_and_if_required_allocate_algo_to_single_contract_order( data=self.data, contract_order=contract_order_to_trade, instrument_order=instrument_order) log.msg("Sending order %s to algo %s" % (str(contract_order_to_trade_with_algo_set), contract_order_to_trade_with_algo_set.algo_to_use)) algo_class_to_call = self.add_controlling_algo_to_order( contract_order_to_trade_with_algo_set) algo_instance = algo_class_to_call( self.data, contract_order_to_trade_with_algo_set) # THIS LINE ACTUALLY SENDS THE ORDER TO THE ALGO placed_broker_order_with_controls = algo_instance.submit_trade() if placed_broker_order_with_controls is missing_order: # important we do this or order will never execute # if no issue here will be released once order filled self.contract_stack.release_order_from_algo_control( contract_order_to_trade_with_algo_set.order_id) return missing_order return algo_instance, placed_broker_order_with_controls
def allocate_for_best_execution_no_limit( data: dataBlob, contract_order: contractOrder) -> contractOrder: # in the future could be randomized... log = contract_order.log_with_attributes(data.log) data_broker = dataBroker(data) short_of_time = data_broker.less_than_one_hour_of_trading_leg_for_contract( contract_order.futures_contract) if short_of_time: log.warn("Short of time, so allocating to algo_market") contract_order.algo_to_use = MARKET_ALGO else: log.msg("'Best' order so allocating to original_best") contract_order.algo_to_use = ORIGINAL_BEST return contract_order
def put_balance_trades_on_stack( self, instrument_order: instrumentOrder, contract_order: contractOrder, broker_order: brokerOrder, ): log = instrument_order.log_with_attributes(self.log) log.msg("Putting balancing trades on stacks") try: instrument_order_id = ( self.instrument_stack. put_manual_order_on_stack_and_return_order_id(instrument_order) ) except Exception as e: log.error( "Couldn't add balancing instrument trade error condition %s" % str(e)) return failure, missing_order, missing_order, missing_order try: contract_order.parent = instrument_order_id contract_order_id = self.contract_stack.put_order_on_stack( contract_order) except Exception as e: log.error( "Couldn't add balancing contract trade error condition %s " % str(e)) return failure, instrument_order_id, missing_order, missing_order try: self.instrument_stack.add_children_to_order_without_existing_children( instrument_order_id, [contract_order_id]) except Exception as e: log.error("Couldn't add children to instrument order error %s" % str(e)) return failure, instrument_order_id, contract_order_id, missing_order broker_order.parent = contract_order_id try: broker_order_id = self.broker_stack.put_order_on_stack( broker_order) except Exception as e: log.error( "Couldn't add balancing broker trade error condition %s" % str(e)) return failure, instrument_order_id, contract_order_id, missing_order try: self.contract_stack.add_children_to_order_without_existing_children( contract_order_id, [broker_order_id]) except Exception as e: log.error("Couldn't add children to contract order exception %s" % str(e)) return failure, instrument_order_id, contract_order_id, broker_order_id log.msg("All balancing trades added to stacks") return success, instrument_order_id, contract_order_id, broker_order_id
def apply_trade_limits_to_contract_order( self, proposed_order: contractOrder) -> contractOrder: log = proposed_order.log_with_attributes(self.log) data_trade_limits = dataTradeLimits(self.data) instrument_strategy = proposed_order.instrument_strategy # proposed_order.trade.total_abs_qty() is a scalar, returns a scalar maximum_abs_qty = ( data_trade_limits.what_trade_is_possible_for_strategy_instrument( instrument_strategy, proposed_order.trade)) contract_order_after_trade_limits = ( proposed_order. change_trade_size_proportionally_to_meet_abs_qty_limit( maximum_abs_qty)) if contract_order_after_trade_limits.trade != proposed_order.trade: log.msg("%s trade change from %s to %s because of trade limits" % ( proposed_order.key, str(proposed_order.trade), str(contract_order_after_trade_limits.trade), )) return contract_order_after_trade_limits
def check_and_if_required_allocate_algo_to_single_contract_order( data: dataBlob, contract_order: contractOrder, instrument_order: instrumentOrder) -> contractOrder: """ :param data: dataBlog :param instrument_order: parent instrument order :param list_of_contract_orders: :return: list of contract orders with algo added """ log = contract_order.log_with_attributes(data.log) if contract_order.algo_to_use != "": # Already done return contract_order instrument_order_type = instrument_order.order_type # not used yet, but maybe in the future is_roll_order = instrument_order.roll_order if instrument_order_type == market_order_type: log.msg("Market order type, so allocating to algo_market") contract_order.algo_to_use = MARKET_ALGO elif (instrument_order_type == best_order_type or instrument_order_type == zero_roll_order_type): contract_order = allocate_for_best_execution_no_limit( data=data, contract_order=contract_order) elif instrument_order_type == limit_order_type: log.critical( "Don't have an algo for instrument level limit orders yet!") return missing_order elif instrument_order_type == balance_order_type: log.critical("Balance orders aren't executed, shouldn't even be here!") return missing_order else: log.warn( "Don't recognise order type %s so allocating to default algo_market" % instrument_order_type) contract_order.algo_to_use = DEFAULT_ALGO return contract_order
def round_limit_price_to_tick_size(self, contract_order: contractOrder, limit_price: float) -> float: contract = contract_order.futures_contract min_tick = self.data_broker.get_min_tick_size_for_contract(contract) if min_tick is missing_contract: log = contract_order.log_with_attributes(self.data.log) log.warn("Couldn't find min tick size for %s, not rounding limit price %f" % (str(contract), limit_price)) return limit_price rounded_limit_price = min_tick * round(limit_price / min_tick) return rounded_limit_price
def get_ticker_object_for_order(self, order: contractOrder) -> tickerObject: contract_object = order.futures_contract trade_list_for_multiple_legs = order.trade new_log = order.log_with_attributes(self.log) contract_object_with_ib_data = ( self.futures_contract_data.get_contract_object_with_IB_data(contract_object) ) if contract_object_with_ib_data is missing_contract: new_log.warn("Can't get data for %s" % str(contract_object)) return futuresContractPrices.create_empty() ticker_with_bs = self.ib_client.get_ticker_object( contract_object_with_ib_data, trade_list_for_multiple_legs=trade_list_for_multiple_legs, ) ticker_object = ibTickerObject(ticker_with_bs, self.ib_client) return ticker_object
def add_limit_price_to_a_direct_child_order( data: dataBlob, instrument_order: instrumentOrder, child_order: contractOrder ) -> contractOrder: """ :param data: dataBlob :param instrument_order: :param child_order: will be modified :return: float """ contract_to_match = instrument_order.limit_contract price_to_adjust = instrument_order.limit_price if contract_to_match is None or price_to_adjust is None: # No limit price so don't bother return child_order new_limit_price = calculate_adjusted_price_for_a_direct_child_order( data, child_order, contract_to_match, price_to_adjust ) if new_limit_price is missing_data: # This is a serious problem # We can't possibly execute any part of the parent order log = instrument_order.log_with_attributes(data.log) log.critical( "Couldn't adjust limit price for order %s child %s going from %s to %s" % ( str(instrument_order), str(child_order), contract_to_match, child_order.contract_date, ) ) return missing_order child_order.limit_price = new_limit_price return child_order
def size_contract_order( self, original_contract_order: contractOrder) -> contractOrder: # We can deal with partially filled contract orders: that's how hard we # are! remaining_contract_order = original_contract_order.create_order_with_unfilled_qty( ) # Check the order doesn't breach trade limits contract_order_after_trade_limits = self.apply_trade_limits_to_contract_order( remaining_contract_order) contract_order_to_trade = self.liquidity_size_contract_order( contract_order_after_trade_limits) if contract_order_to_trade is missing_order: return missing_order if contract_order_to_trade.fill_equals_desired_trade(): # Nothing left to trade return missing_order return contract_order_to_trade
def add_reference_price_to_a_direct_child_order( data: dataBlob, instrument_order: instrumentOrder, child_order: contractOrder ): """ :param data: dataBlob :param instrument_order: :param child_order: will be modified :return: child order """ contract_to_match = instrument_order.reference_contract price_to_adjust = instrument_order.reference_price if contract_to_match is None or price_to_adjust is None: # No reference price so don't bother return child_order new_reference_price = calculate_adjusted_price_for_a_direct_child_order( data, child_order, contract_to_match, price_to_adjust ) if new_reference_price is missing_data: log = instrument_order.log_with_attributes(data.log) log.warn( "Couldn't adjust reference price for order %s child %s going from %s to %s, can't do TCA" % ( str(instrument_order), str(child_order), contract_to_match, child_order.contract_date, ) ) return child_order child_order.reference_price = new_reference_price return child_order
def get_market_data_for_order_modifies_ticker_object(self, ticker_object: tickerObject, contract_order: contractOrder) -> benchmarkPriceCollection: # We use prices for a couple of reasons: # to provide a benchmark for execution purposes # (optionally) to set limit prices ## log = contract_order.log_with_attributes(self.data.log) # Get the first 'reference' tick reference_tick = ( ticker_object.wait_for_valid_bid_and_ask_and_return_current_tick( wait_time_seconds=10 ) ) tick_analysis = ticker_object.analyse_for_tick(reference_tick) if tick_analysis is missing_data: log.warn( "Can't get market data for %s so not trading with limit order %s" % (contract_order.instrument_code, str(contract_order))) return missing_data ticker_object.clear_and_add_reference_as_first_tick(reference_tick) # These prices will be used for limit price purposes # They are scalars benchmark_side_prices = tick_analysis.side_price offside_price = tick_analysis.offside_price mid_price = tick_analysis.mid_price collected_prices = benchmarkPriceCollection( offside_price=tick_analysis.offside_price, side_price=tick_analysis.side_price, mid_price = tick_analysis.mid_price) return collected_prices
def get_and_submit_broker_order_for_contract_order( self, contract_order: contractOrder, input_limit_price: float=None, order_type: brokerOrderType=market_order_type, limit_price_from: str = limit_price_from_input, ticker_object: tickerObject=None, broker_account: str = arg_not_supplied ): log = contract_order.log_with_attributes(self.data.log) broker = self.data_broker.get_broker_name() if broker_account is arg_not_supplied: broker_account = self.data_broker.get_broker_account() broker_clientid = self.data_broker.get_broker_clientid() if ticker_object is None: ticker_object = self.data_broker.get_ticker_object_for_order(contract_order) collected_prices = self.get_market_data_for_order_modifies_ticker_object( ticker_object, contract_order ) if collected_prices is missing_data: # no data available, no can do return missing_order ## We want to preserve these otherwise there is a danger they will dynamically change collected_prices = copy(collected_prices) if order_type == limit_order_type: limit_price = self.set_limit_price( contract_order=contract_order, collected_prices=collected_prices, limit_price_from=limit_price_from, input_limit_price=input_limit_price, ) elif order_type == market_order_type: limit_price = None else: error_msg = "Order type %s not valid for broker orders" % str (order_type) log.critical(error_msg) return missing_order broker_order = create_new_broker_order_from_contract_order( contract_order, order_type=order_type, side_price=collected_prices.side_price, mid_price=collected_prices.mid_price, offside_price = collected_prices.offside_price, broker=broker, broker_account=broker_account, broker_clientid=broker_clientid, limit_price=limit_price, ) log.msg( "Created a broker order %s (not yet submitted or written to local DB)" % str(broker_order)) placed_broker_order_with_controls = self.data_broker.submit_broker_order( broker_order) if placed_broker_order_with_controls is missing_order: log.warn("Order could not be submitted") return missing_order log = placed_broker_order_with_controls.order.log_with_attributes(log) log.msg("Submitted order to IB %s" % str(placed_broker_order_with_controls.order)) placed_broker_order_with_controls.add_or_replace_ticker(ticker_object) return placed_broker_order_with_controls