Skip to content

Commit

Permalink
finished rough version of order management code. new production p&l r…
Browse files Browse the repository at this point in the history
…eporting code
  • Loading branch information
robcarver17 committed Jun 13, 2020
1 parent 3291c01 commit 3f4fba9
Show file tree
Hide file tree
Showing 38 changed files with 2,407 additions and 588 deletions.
140 changes: 137 additions & 3 deletions sysbrokers/IB/ibClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
import pandas as pd
import re

from collections import namedtuple

from ib_insync import Forex, Future, util
from sysdata.fx.spotfx import currencyValue
from ib_insync.order import MarketOrder

from sysdata.fx.spotfx import currencyValue
from sysbrokers.baseClient import brokerClient
from syscore.genutils import NOT_REQUIRED
from syscore.objects import missing_contract, arg_not_supplied
from syscore.objects import missing_contract, arg_not_supplied, missing_order
from syscore.dateutils import adjust_timestamp
from syslogdiag.log import logtoscreen

Expand All @@ -32,8 +35,43 @@ def __init__(self, log=logtoscreen("ibClient")):
## means our first call won't be throttled for pacing
self.last_historic_price_calltime = datetime.datetime.now()- datetime.timedelta(seconds=_PACING_PERIOD_SECONDS)


def broker_get_orders(self):
"""
Get all trades, orders and return them with the information needed
:return: list
"""
trades_in_broker_format = self.ib.trades()
order_list = [extract_trade_info(trade_to_process) for trade_to_process in trades_in_broker_format]

return order_list

# Methods in parent class overriden here
# These methods should abstract the broker completely
def broker_submit_single_leg_order(self, contract_object_with_ib_data, trade, account,
order_type = "market",
limit_price = None):
"""
:param ibcontract: contract_object_with_ib_broker_config: contract where instrument has ib metadata
:param trade: int
:param account: str
:param order_type: str, market or limit
:param limit_price: None or float
:return: brokers trade object
"""

if order_type=="market":
trade_object = self.ib_submit_single_leg_market_order(contract_object_with_ib_data, trade, account)
else:
self.log.critical("Order type %s is not supported for order on %s" % (order_type, str(contract_object_with_ib_data)))
return missing_order

return trade_object

def broker_get_positions(self):
## Get all the positions
## We return these as a dict of pd DataFrame
Expand Down Expand Up @@ -166,10 +204,22 @@ def broker_get_contract_expiry_date(self, contract_object_with_ib_broker_config)
return expiry_date



# IB specific methods
# Called by parent class generics

def ib_submit_single_leg_market_order(self, contract_object_with_ib_data, trade, account):
ibcontract = self.ib_futures_contract(contract_object_with_ib_data)
if ibcontract is missing_contract:
return missing_order
## account?!s
ib_BS_str, ib_qty = resolveBS(trade)
ib_order = MarketOrder(ib_BS_str, ib_qty)
if account!='':
ib_order.account = account
trade = self.ib.placeOrder(ibcontract, ib_order)

return trade

def _get_generic_data_for_contract(self, ibcontract, log=None, bar_freq="D", whatToShow='TRADES'):
"""
Get historical daily data
Expand Down Expand Up @@ -562,3 +612,87 @@ def resolve_ib_cash_position(position):
expiry = "", multiplier = 1.0,
currency = position.contract.currency, position = position.position)

def resolveBS(trade):
if trade<0:
return 'SELL', abs(trade)
return 'BUY', abs(trade)


def sign_from_BS(action):
if action=="SELL":
return -1
return 1


def extract_trade_info(trade_to_process):
order_info = extract_order_info(trade_to_process)
contract_info = extract_contract_info(trade_to_process)
fill_info = extract_fill_info(trade_to_process)

algo_msg = " ".join([str(log_entry) for log_entry in trade_to_process.log])
total_filled = trade_to_process.filled()
active = trade_to_process.isActive()

tradeInfo = namedtuple("tradeInfo", ['order', 'contract', 'fills','algo_msg', 'total_filled', 'active'])
trade_info = tradeInfo(order_info, contract_info, fill_info, algo_msg, total_filled, active)

return trade_info

def extract_order_info(trade_to_process):
order = trade_to_process.order

account = order.account
perm_id = order.permId
limit_price = order.lmtPrice
order_sign = sign_from_BS(order.action)
order_type = resolve_order_type(order.orderType)
remain_qty = order.totalQuantity

orderInfo = namedtuple('orderInfo', ['account', 'perm_id', 'limit_price', 'order_sign', 'type',
'remain_qty'])
order_info = orderInfo(account=account, perm_id=perm_id, limit_price=limit_price,
order_sign=order_sign, type = order_type, remain_qty=remain_qty)

return order_info

def extract_contract_info(trade_to_process):
contract = trade_to_process.contract
ib_instrument_code = contract.symbol
ib_contract_id = contract.lastTradeDateOrContractMonth
ib_sectype = contract.secType

contractInfo = namedtuple("contractInfo", ['ib_instrument_code', 'ib_contract_id', 'ib_sectype'])
contract_info = contractInfo(ib_instrument_code=ib_instrument_code, ib_contract_id=ib_contract_id,
ib_sectype=ib_sectype)

return contract_info

def extract_fill_info(trade_to_process):
all_fills = trade_to_process.fills
fill_info = [extract_single_fill(single_fill) for single_fill in all_fills]

return fill_info

def extract_single_fill(single_fill):
commission = single_fill.commissionReport.commission
commission_ccy = single_fill.commissionReport.currency
cum_qty = single_fill.execution.cumQty
price = single_fill.execution.price
avg_price = single_fill.execution.avgPrice
time = single_fill.execution.time
temp_id = single_fill.execution.orderId
client_id = single_fill.execution.clientId

singleFill = namedtuple("singleFill", ['commission','commission_ccy', 'cum_qty', 'price', 'avg_price', 'time',
'temp_id', 'client_id'])

single_fill = singleFill(commission, commission_ccy, cum_qty, price, avg_price, time, temp_id, client_id)

return single_fill

def resolve_order_type(ib_order_type):
lookup_dict = dict(MKT='market')
my_order_type = lookup_dict.get(ib_order_type, "")

return my_order_type

24 changes: 24 additions & 0 deletions sysbrokers/IB/ibMiscData.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from syscore.objects import missing_data
from syslogdiag.log import logtoscreen
from sysdata.private_config import get_private_then_default_key_value

class ibMiscData(object):
def __init__(self, ibconnection, log=logtoscreen("ibFuturesContractPriceData")):
setattr(self, "ibconnection", ibconnection)
setattr(self, "log", log)

def __repr__(self):
return "IB misc data %s" % str(self.ibconnection)

def get_broker_clientid(self):
return self.ibconnection.ib.client.clientId

def get_broker_account(self):
account_id = get_private_then_default_key_value("broker_account", raise_error=False)
if account_id is missing_data:
return ''
else:
return account_id

def get_broker_name(self):
return "IB"
169 changes: 169 additions & 0 deletions sysbrokers/IB/ibOrders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
from syslogdiag.log import logtoscreen
from syscore.objects import missing_order, no_order_id
from sysdata.futures.contracts import futuresContract
from sysdata.fx.spotfx import currencyValue
from sysexecution.broker_orders import brokerOrderStackData, brokerOrder
from sysbrokers.IB.ibFuturesContracts import ibFuturesContractData

class ibOrdersData(brokerOrderStackData):
def __init__(self, ibconnection, log=logtoscreen("ibFuturesContractPriceData")):
setattr(self, "ibconnection", ibconnection)
setattr(self, "log", log)

def __repr__(self):
return "IB orders %s" % str(self.ibconnection)

@property
def futures_contract_data(self):
return ibFuturesContractData(self.ibconnection)

def get_list_of_broker_orders(self):
"""
Get list of broker orders from IB, and return as my broker_order objects
:return: list of brokerOrder objects
"""

list_of_raw_orders = self.ibconnection.broker_get_orders()
order_list = [self.create_broker_order_object(raw_order) for raw_order in list_of_raw_orders]
order_list = [order for order in order_list if order is not missing_order]

return order_list

def get_order_with_id_from_stack(self, order_id):
pass

def get_list_of_order_ids(self):
return self.get_list_of_temp_ids()

def get_list_of_temp_ids(self):

all_active_orders = self.get_list_of_broker_orders()
ib_broker_ids = [broker_order.broker_tempid for broker_order in all_active_orders]

return ib_broker_ids

def cancel_order(self, temp_order_id):
pass

def modify_order_on_stack(self, temp_order_id, new_trade):
pass

def put_order_on_stack(self, broker_order):
"""
:param broker_order: key properties are instrument_code, contract_id, quantity
:return: int with order ID or missing_order
"""
if len(broker_order.trade)>1:
# only single legs!
return missing_order

order_id = self.put_single_leg_order_on_stack(broker_order)

return order_id

def put_single_leg_order_on_stack(self, broker_order):
"""
:param broker_order: key properties are instrument_code, contract_id, quantity
:return: int with order ID or missing_order
"""
instrument_code = broker_order.instrument_code

## Next two are because we are a single leg order, but both are lists
contract_id = broker_order.contract_id[0]
trade = broker_order.trade[0]

order_type = broker_order.order_type
limit_price = broker_order.limit_price
account = broker_order.broker_account

contract_object = futuresContract(instrument_code, contract_id)
contract_object_with_ib_data = self.futures_contract_data.get_contract_object_with_IB_metadata(contract_object)

trade_object = self.ibconnection.broker_submit_single_leg_order(contract_object_with_ib_data, trade, account,
order_type = order_type,
limit_price = limit_price)


return trade_object

def broker_id_from_trade_object(self, trade_object):
permid = trade_object.order.permId
orderid = trade_object.order.orderId
clientid = trade_object.order.clientId

return orderid, permid, clientid


def create_broker_order_object(self, raw_order):
"""
Map from the data IB gives us to my broker order object
:param raw_order: named tuple with fields defined in ibClient
:return: brokerOrder
"""

sec_type = raw_order.contract.ib_sectype
if sec_type!="FUT":
## Doesn't handle non futures trades, just ignores them
return missing_order

strategy_name="NOT_MATCHED"


instrument_code=self.futures_contract_data.get_instrument_code_from_broker_code(raw_order.contract.ib_instrument_code)
contract_id=raw_order.contract.ib_contract_id

# NOT A SPREAD ORDER

order_sign = raw_order.order.order_sign
remain_qty = raw_order.order.remain_qty * order_sign
fill=raw_order.total_filled*order_sign
trade_size = fill + remain_qty


algo_comment = raw_order.algo_msg
order_type=raw_order.order.type
limit_price = raw_order.order.limit_price
broker_account=raw_order.order.account
broker_permid=raw_order.order.perm_id


broker_clientid, broker_tempid, filled_price, fill_datetime, commission = self.extract_totals_from_fill_data(raw_order.fills)

broker_order = brokerOrder(strategy_name, instrument_code, [contract_id], [trade_size], fill=[fill],
order_type=order_type, limit_price=limit_price, filled_price=filled_price,
algo_comment=algo_comment,
fill_datetime=fill_datetime,
broker_account=broker_account,
commission=commission,
broker_permid=broker_permid, broker_tempid=broker_tempid,
broker_clientid=broker_clientid)

return broker_order

def extract_totals_from_fill_data(self, list_of_fills):
"""
Sum up info over fills
:param list_of_fills: list of named tuples
:return: average_filled_price, commission (as list of tuples), total quantity filled
"""
if len(list_of_fills)==0:
return no_order_id, no_order_id, None, None, 0.0

qty_and_price_and_datetime_and_id = [(fill.cum_qty, fill.avg_price, fill.time,
fill.temp_id, fill.client_id) for fill in list_of_fills]

## sort by total quantity
qty_and_price_and_datetime_and_id.sort(key = lambda x:x[0])

final_fill = qty_and_price_and_datetime_and_id[-1]
_, filled_price, fill_datetime, broker_tempid, broker_clientid = final_fill

commission = [currencyValue(fill.commission_ccy, fill.commission) for fill in list_of_fills]

return broker_clientid, broker_tempid, filled_price, fill_datetime, commission
7 changes: 7 additions & 0 deletions sysbrokers/baseClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,10 @@ def close_connection(self):
def broker_get_positions(self):
raise NotImplementedError

def broker_submit_single_leg_order(self, instrument_code, contract_id, account,
order_type = "market",
limit_price = None):
raise NotImplementedError

def broker_get_orders(self):
raise NotImplementedError
Loading

0 comments on commit 3f4fba9

Please sign in to comment.