Skip to content

Commit d686362

Browse files
authored
Example market maker (#100)
* Example market making strategy. * Configure what exchange market making orders are executed on. * Fixed a NPE in the contract registry * Rename account column to be consistent with other tables. * Market making script udpate * Market making script update * Market making script update
1 parent 0727bcf commit d686362

File tree

3 files changed

+214
-2
lines changed

3 files changed

+214
-2
lines changed

examples/example_market_maker.py

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
2+
from ibapi.contract import Contract
3+
from ibapi.order import Order
4+
5+
import deephaven_ib as dhib
6+
from deephaven.updateby import ema_time_decay
7+
from deephaven import time_table
8+
from deephaven.plot import Figure
9+
from deephaven.plot.selectable_dataset import one_click
10+
from deephaven.plot import PlotStyle
11+
12+
13+
###########################################################################
14+
# WARNING: THIS SCRIPT EXECUTES TRADES!! ONLY USE ON PAPER TRADING ACCOUNTS
15+
###########################################################################
16+
17+
print("==============================================================================================================")
18+
print("==== Create a client and connect.")
19+
print("==== ** Accept the connection in TWS **")
20+
print("==============================================================================================================")
21+
22+
client = dhib.IbSessionTws(host="host.docker.internal", port=7497, client_id=0, download_short_rates=False, read_only=False)
23+
print(f"IsConnected: {client.is_connected()}")
24+
25+
client.connect()
26+
print(f"IsConnected: {client.is_connected()}")
27+
28+
## Setup
29+
30+
account = "DU4943848"
31+
max_position_dollars = 10000.0
32+
ema_time = "00:02:00"
33+
34+
errors = client.tables["errors"]
35+
requests = client.tables["requests"]
36+
positions = client.tables["accounts_positions"].where("Account = account")
37+
ticks_bid_ask = client.tables["ticks_bid_ask"]
38+
orders_submitted = client.tables["orders_submitted"].where("Account = account")
39+
orders_status = client.tables["orders_status"]
40+
orders_exec_details = client.tables["orders_exec_details"].where("Account = account")
41+
42+
print("==============================================================================================================")
43+
print("==== Request data.")
44+
print("==============================================================================================================")
45+
46+
registered_contracts_data = {}
47+
registred_contracts_orders = {}
48+
49+
def add_contract(symbol: str, exchange: str="SMART") -> None:
50+
"""
51+
Configure a contract for trading.
52+
:param symbol: Symbol to trade.
53+
:param exchange: exchange where orders get routed.
54+
:return: None
55+
"""
56+
57+
contract = Contract()
58+
contract.symbol = symbol
59+
contract.secType = "STK"
60+
contract.currency = "USD"
61+
contract.exchange = "SMART"
62+
63+
rc = client.get_registered_contract(contract)
64+
id = rc.contract_details[0].contract.conId
65+
registered_contracts_data[id] = rc
66+
client.request_tick_data_realtime(rc, dhib.TickDataType.BID_ASK)
67+
print(f"Registered contract: id={id} rc={rc}")
68+
69+
if exchange != "SMART":
70+
contract.exchange = "NYSE"
71+
rc = client.get_registered_contract(contract)
72+
73+
registred_contracts_orders[id] = rc
74+
print(f"Registered contract: id={id} rc={rc}")
75+
76+
77+
add_contract("GOOG")
78+
add_contract("BAC")
79+
add_contract("AAPL", exchange="NYSE")
80+
81+
print("==============================================================================================================")
82+
print("==== Compute predictions.")
83+
print("==============================================================================================================")
84+
85+
preds = ticks_bid_ask \
86+
.update_view(["MidPrice=0.5*(BidPrice+AskPrice)", "MidPrice2=MidPrice*MidPrice"]) \
87+
.update_by([ema_time_decay("Timestamp", ema_time, ["PredPrice=MidPrice","MidPrice2Bar=MidPrice2"])], by="Symbol") \
88+
.view([
89+
"ReceiveTime",
90+
"Timestamp",
91+
"ContractId",
92+
"Symbol",
93+
"BidPrice",
94+
"AskPrice",
95+
"MidPrice",
96+
"PredPrice",
97+
"PredSD = sqrt(MidPrice2Bar-PredPrice*PredPrice)",
98+
"PredLow=PredPrice-PredSD",
99+
"PredHigh=PredPrice+PredSD",
100+
])
101+
102+
preds_start = preds.first_by("Symbol").view(["Symbol", "Timestamp"])
103+
preds = preds.natural_join(preds_start, on="Symbol", joins="TimestampFirst=Timestamp")
104+
105+
preds_one_click = one_click(preds, by=["Symbol"], require_all_filters=True)
106+
107+
preds_plot = Figure() \
108+
.plot_xy("BidPrice", t=preds_one_click, x="Timestamp", y="BidPrice") \
109+
.plot_xy("AskPrice", t=preds_one_click, x="Timestamp", y="AskPrice") \
110+
.plot_xy("MidPrice", t=preds_one_click, x="Timestamp", y="MidPrice") \
111+
.plot_xy("PredPrice", t=preds_one_click, x="Timestamp", y="PredPrice") \
112+
.plot_xy("PredLow", t=preds_one_click, x="Timestamp", y="PredLow") \
113+
.plot_xy("PredHigh", t=preds_one_click, x="Timestamp", y="PredHigh") \
114+
.show()
115+
116+
print("==============================================================================================================")
117+
print("==== Generate orders.")
118+
print("==============================================================================================================")
119+
120+
open_orders = {}
121+
122+
def update_orders(contract_id: int, pred_low: float, pred_high: float, buy_order: bool, sell_order:bool) -> int:
123+
"""
124+
Update orders on a contract. First existing orders are canceled. Then new buy/sell limit orders are placed.
125+
126+
:param contract_id: Contract id.
127+
:param pred_low: Price for buy limit orders.
128+
:param pred_high: Price for sell limit orders.
129+
:param buy_order: True to post a buy order; False to not post a buy order.
130+
:param sell_order: True to post a sell order; False to not post a sell order.
131+
:return: Number of orders submitted.
132+
"""
133+
134+
if contract_id in open_orders:
135+
for order in open_orders[contract_id]:
136+
# print(f"Canceling order: contract_id={contract_id} order_id={order.request_id}")
137+
order.cancel()
138+
139+
new_orders = []
140+
rc = registred_contracts_orders[contract_id]
141+
142+
if sell_order:
143+
order_sell = Order()
144+
order_sell.account = account
145+
order_sell.action = "SELL"
146+
order_sell.orderType = "LIMIT"
147+
order_sell.totalQuantity = 100
148+
order_sell.lmtPrice = round( pred_high, 2)
149+
order_sell.transmit = True
150+
151+
order = client.order_place(rc, order_sell)
152+
new_orders.append(order)
153+
154+
if buy_order:
155+
order_buy = Order()
156+
order_buy.account = account
157+
order_buy.action = "BUY"
158+
order_buy.orderType = "LIMIT"
159+
order_buy.totalQuantity = 100
160+
order_buy.lmtPrice = round( pred_low, 2)
161+
order_buy.transmit = True
162+
163+
order = client.order_place(rc, order_buy)
164+
new_orders.append(order)
165+
166+
open_orders[contract_id] = new_orders
167+
return len(new_orders)
168+
169+
170+
orders = time_table("00:01:00") \
171+
.rename_columns("SnapTime=Timestamp") \
172+
.snapshot(preds.last_by(["Symbol"])) \
173+
.where(f"Timestamp > TimestampFirst + '{ema_time}'") \
174+
.natural_join(positions, on="ContractId", joins="Position") \
175+
.update_view([
176+
"Position = replaceIfNull(Position, 0.0)",
177+
"PositionDollars = Position * MidPrice",
178+
"MaxPositionDollars = max_position_dollars",
179+
"BuyOrder = PositionDollars < MaxPositionDollars",
180+
"SellOrder = PositionDollars > -MaxPositionDollars",
181+
]) \
182+
.update("NumNewOrders = (long)update_orders(ContractId, PredLow, PredHigh, BuyOrder, SellOrder)")
183+
184+
185+
print("==============================================================================================================")
186+
print("==== Plot trade executions.")
187+
print("==============================================================================================================")
188+
189+
190+
trades = orders_exec_details \
191+
.natural_join(preds_start, on="Symbol", joins="TimestampFirst=Timestamp") \
192+
.where("ReceiveTime >= TimestampFirst") \
193+
.view(["Timestamp=ReceiveTime", "ContractId", "Symbol", "ExecutionExchange", "Side", "Shares", "Price"])
194+
195+
buys_one_click = one_click(trades.where("Side=`BOT`"), by=["Symbol"], require_all_filters=True)
196+
sells_one_click = one_click(trades.where("Side=`SLD`"), by=["Symbol"], require_all_filters=True)
197+
198+
execution_plot = Figure() \
199+
.plot_xy("BidPrice", t=preds_one_click, x="Timestamp", y="BidPrice") \
200+
.plot_xy("AskPrice", t=preds_one_click, x="Timestamp", y="AskPrice") \
201+
.plot_xy("MidPrice", t=preds_one_click, x="Timestamp", y="MidPrice") \
202+
.plot_xy("PredPrice", t=preds_one_click, x="Timestamp", y="PredPrice") \
203+
.plot_xy("PredLow", t=preds_one_click, x="Timestamp", y="PredLow") \
204+
.plot_xy("PredHigh", t=preds_one_click, x="Timestamp", y="PredHigh") \
205+
.twin() \
206+
.axes(plot_style=PlotStyle.SCATTER) \
207+
.plot_xy("Buys", t=buys_one_click, x="Timestamp", y="Price") \
208+
.plot_xy("Sells", t=sells_one_click, x="Timestamp", y="Price") \
209+
.show()

src/deephaven_ib/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,8 @@ def deephaven_ib_parse_note(note:str, key:str) -> Optional[str]:
623623
.move_columns_up(["ReceiveTime", "OrderId", "ClientId", "PermId", "ParentId"]),
624624
"orders_exec_commission_report": tables_raw["raw_orders_exec_commission_report"],
625625
"orders_exec_details": tables_raw["raw_orders_exec_details"] \
626-
.move_columns_up(["RequestId", "ReceiveTime", "Timestamp", "ExecId", "AcctNumber"]),
626+
.move_columns_up(["RequestId", "ReceiveTime", "Timestamp", "ExecId", "AcctNumber"]) \
627+
.rename_columns("Account=AcctNumber"),
627628
# The status on raw_orders_submitted is buggy, so using the status from raw_orders_status
628629
"orders_submitted": tables_raw["raw_orders_submitted"] \
629630
.last_by("PermId") \

src/deephaven_ib/_tws/contract_registry.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,9 @@ def add_error_data(self, req_id: int, error_string: str) -> None:
107107

108108
contract, event = self._requests_by_id[req_id]
109109
self._update_error(contract, req_id, error_string)
110-
event.set()
110+
111+
if event:
112+
event.set()
111113

112114
def request_end(self, req_id: int) -> None:
113115
"""Indicate that the request is over and all data has been received."""

0 commit comments

Comments
 (0)