Skip to content

Commit 6f057f6

Browse files
committed
Refine IB adapter
1 parent 6a62838 commit 6f057f6

File tree

29 files changed

+1403
-788
lines changed

29 files changed

+1403
-788
lines changed

examples/live/interactive_brokers/connect_with_tws.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@
5252
"SPY.ARCA",
5353
"AAPL.NASDAQ",
5454
"V.NYSE",
55-
"CLZ28.NYMEX",
56-
"ESZ28.CME",
55+
"CLZ8.NYMEX",
56+
"ESZ8.CME",
5757
],
5858
),
5959
)

examples/live/interactive_brokers/historical_download.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ async def main(
7474
)
7575

7676
trade_ticks = await client.request_ticks(
77-
"TRADES",
77+
tick_type="TRADES",
7878
start_date_time=datetime.datetime(2023, 11, 6, 10, 0),
7979
end_date_time=datetime.datetime(2023, 11, 6, 10, 1),
8080
tz_name="America/New_York",
@@ -83,7 +83,7 @@ async def main(
8383
)
8484

8585
quote_ticks = await client.request_ticks(
86-
"BID_ASK",
86+
tick_type="BID_ASK",
8787
start_date_time=datetime.datetime(2023, 11, 6, 10, 0),
8888
end_date_time=datetime.datetime(2023, 11, 6, 10, 1),
8989
tz_name="America/New_York",

examples/live/interactive_brokers/with_databento_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
ibg_client_id=1,
7474
account_id="DU123456", # This must match with the IB Gateway/TWS node is connecting to
7575
instrument_provider=InteractiveBrokersInstrumentProviderConfig(
76-
symbology_method=SymbologyMethod.DATABENTO,
76+
symbology_method=SymbologyMethod.IB_SIMPLIFIED,
7777
load_ids=frozenset(instrument_ids),
7878
),
7979
routing=RoutingConfig(
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
# ---
2+
# jupyter:
3+
# jupytext:
4+
# formats: ipynb,py:percent
5+
# text_representation:
6+
# extension: .py
7+
# format_name: percent
8+
# format_version: '1.3'
9+
# jupytext_version: 1.17.1
10+
# kernelspec:
11+
# display_name: Python 3 (ipykernel)
12+
# language: python
13+
# name: python3
14+
# ---
15+
16+
# %%
17+
18+
# fmt: off
19+
from nautilus_trader.adapters.interactive_brokers.common import IB
20+
from nautilus_trader.adapters.interactive_brokers.common import IB_VENUE
21+
from nautilus_trader.adapters.interactive_brokers.config import InteractiveBrokersDataClientConfig
22+
from nautilus_trader.adapters.interactive_brokers.config import InteractiveBrokersExecClientConfig
23+
from nautilus_trader.adapters.interactive_brokers.config import InteractiveBrokersInstrumentProviderConfig
24+
from nautilus_trader.adapters.interactive_brokers.config import SymbologyMethod
25+
from nautilus_trader.adapters.interactive_brokers.factories import InteractiveBrokersLiveDataClientFactory
26+
from nautilus_trader.adapters.interactive_brokers.factories import InteractiveBrokersLiveExecClientFactory
27+
from nautilus_trader.common.enums import LogColor
28+
from nautilus_trader.config import LiveDataEngineConfig
29+
from nautilus_trader.config import LoggingConfig
30+
from nautilus_trader.config import RoutingConfig
31+
from nautilus_trader.config import TradingNodeConfig
32+
from nautilus_trader.live.node import TradingNode
33+
from nautilus_trader.model.data import Bar
34+
from nautilus_trader.model.data import BarType
35+
from nautilus_trader.model.enums import OrderSide
36+
from nautilus_trader.model.enums import TimeInForce
37+
from nautilus_trader.model.events import PositionOpened
38+
from nautilus_trader.model.identifiers import InstrumentId
39+
from nautilus_trader.trading.config import StrategyConfig
40+
from nautilus_trader.trading.strategy import Strategy
41+
42+
43+
# fmt: on
44+
45+
46+
class DemoStrategyConfig(StrategyConfig, frozen=True):
47+
bar_type: BarType
48+
instrument_id: InstrumentId
49+
50+
51+
class DemoStrategy(Strategy):
52+
def __init__(self, config: DemoStrategyConfig):
53+
super().__init__(config=config)
54+
55+
# Track if we've already placed an order
56+
self.order_placed = False
57+
58+
# Track total bars seen
59+
self.count_of_bars: int = 0
60+
self.show_portfolio_at_bar: int | None = 0
61+
62+
def on_start(self):
63+
"""
64+
Handle strategy start event.
65+
"""
66+
self.request_instrument(self.config.instrument_id)
67+
self.instrument = self.cache.instrument(self.config.instrument_id)
68+
69+
self.request_bars(BarType.from_str(f"{self.config.instrument_id}-1-MINUTE-LAST-EXTERNAL"))
70+
71+
# Subscribe to market data
72+
self.subscribe_bars(self.config.bar_type)
73+
74+
# Show initial portfolio state
75+
self.show_portfolio_info("Portfolio state (Before trade)")
76+
77+
def on_bar(self, bar: Bar):
78+
"""
79+
Handle new bar event.
80+
"""
81+
# Increment total bars seen
82+
self.count_of_bars += 1
83+
84+
# Show portfolio state if we reached target bar
85+
if self.show_portfolio_at_bar == self.count_of_bars:
86+
self.show_portfolio_info("Portfolio state (2 minutes after position opened)")
87+
88+
# Only place one order for demonstration
89+
if not self.order_placed:
90+
# Prepare values for order
91+
last_price = bar.close
92+
tick_size = self.instrument.price_increment
93+
profit_price = self.instrument.make_price(last_price + (10 * tick_size))
94+
stoploss_price = self.instrument.make_price(last_price - (10 * tick_size))
95+
96+
# Create BUY MARKET order with PT and SL (both 10 ticks)
97+
bracket_order_list = self.order_factory.bracket(
98+
instrument_id=self.config.instrument_id,
99+
order_side=OrderSide.BUY,
100+
quantity=self.instrument.make_qty(1), # Trade size: 1 contract
101+
time_in_force=TimeInForce.GTC,
102+
tp_price=profit_price,
103+
sl_trigger_price=stoploss_price,
104+
entry_post_only=False,
105+
tp_post_only=False,
106+
)
107+
108+
# Submit order and remember it
109+
self.submit_order_list(bracket_order_list)
110+
self.order_placed = True
111+
self.log.info(f"Submitted bracket order: {bracket_order_list}", color=LogColor.GREEN)
112+
113+
def on_position_opened(self, event: PositionOpened):
114+
"""
115+
Handle position opened event.
116+
"""
117+
# Log position details
118+
self.log.info(f"Position opened: {event}", color=LogColor.GREEN)
119+
120+
# Show portfolio state when position is opened
121+
self.show_portfolio_info("Portfolio state (In position):")
122+
123+
# Set target bar number for next portfolio display
124+
self.show_portfolio_at_bar = self.count_of_bars + 2 # Show after 2 bars
125+
126+
def on_stop(self):
127+
"""
128+
Handle strategy stop event.
129+
"""
130+
# Show final portfolio state
131+
self.show_portfolio_info("Portfolio state (After trade)")
132+
133+
def show_portfolio_info(self, intro_message: str = ""):
134+
"""
135+
Display current portfolio information.
136+
"""
137+
if intro_message:
138+
self.log.info(f"====== {intro_message} ======")
139+
140+
# POSITION information
141+
self.log.info("Portfolio -> Position information:", color=LogColor.BLUE)
142+
is_flat = self.portfolio.is_flat(self.config.instrument_id)
143+
self.log.info(f"Is flat: {is_flat}", color=LogColor.BLUE)
144+
145+
net_position = self.portfolio.net_position(self.config.instrument_id)
146+
self.log.info(f"Net position: {net_position} contract(s)", color=LogColor.BLUE)
147+
148+
net_exposure = self.portfolio.net_exposure(self.config.instrument_id)
149+
self.log.info(f"Net exposure: {net_exposure}", color=LogColor.BLUE)
150+
151+
# -----------------------------------------------------
152+
153+
# P&L information
154+
self.log.info("Portfolio -> P&L information:", color=LogColor.YELLOW)
155+
156+
realized_pnl = self.portfolio.realized_pnl(self.config.instrument_id)
157+
self.log.info(f"Realized P&L: {realized_pnl}", color=LogColor.YELLOW)
158+
159+
unrealized_pnl = self.portfolio.unrealized_pnl(self.config.instrument_id)
160+
self.log.info(f"Unrealized P&L: {unrealized_pnl}", color=LogColor.YELLOW)
161+
162+
# -----------------------------------------------------
163+
164+
self.log.info("Portfolio -> Account information:", color=LogColor.CYAN)
165+
margins_init = self.portfolio.margins_init(IB_VENUE)
166+
self.log.info(f"Initial margin: {margins_init}", color=LogColor.CYAN)
167+
168+
margins_maint = self.portfolio.margins_maint(IB_VENUE)
169+
self.log.info(f"Maintenance margin: {margins_maint}", color=LogColor.CYAN)
170+
171+
balances_locked = self.portfolio.balances_locked(IB_VENUE)
172+
self.log.info(f"Locked balance: {balances_locked}", color=LogColor.CYAN)
173+
174+
175+
# %%
176+
instrument_provider = InteractiveBrokersInstrumentProviderConfig(
177+
symbology_method=SymbologyMethod.IB_SIMPLIFIED,
178+
convert_exchange_to_mic_venue=True,
179+
load_ids=frozenset(
180+
[
181+
"ES.XCME",
182+
],
183+
),
184+
)
185+
186+
# Configure the trading node
187+
# IMPORTANT: you must use the imported IB string so this client works properly
188+
config_node = TradingNodeConfig(
189+
trader_id="TESTER-001",
190+
logging=LoggingConfig(log_level="INFO"),
191+
data_clients={
192+
IB: InteractiveBrokersDataClientConfig(
193+
ibg_port=7497,
194+
handle_revised_bars=False,
195+
use_regular_trading_hours=False,
196+
instrument_provider=instrument_provider,
197+
),
198+
},
199+
exec_clients={
200+
IB: InteractiveBrokersExecClientConfig(
201+
ibg_port=7497,
202+
instrument_provider=instrument_provider,
203+
routing=RoutingConfig(default=True),
204+
account_id="DU187075",
205+
),
206+
},
207+
data_engine=LiveDataEngineConfig(
208+
time_bars_timestamp_on_close=False, # Will use opening time as `ts_event` (same as IB)
209+
validate_data_sequence=True, # Will make sure DataEngine discards any Bars received out of sequence
210+
),
211+
timeout_connection=90.0,
212+
timeout_reconciliation=5.0,
213+
timeout_portfolio=5.0,
214+
timeout_disconnection=5.0,
215+
timeout_post_stop=2.0,
216+
)
217+
218+
219+
# Instantiate the node with a configuration
220+
node = TradingNode(config=config_node)
221+
222+
# Instantiate your strategy
223+
instrument_id = "ES.XCME" # AAPL.XNAS, "YMM5.XCBT"
224+
225+
strategy_config = DemoStrategyConfig(
226+
bar_type=BarType.from_str(f"{instrument_id}-1-MINUTE-LAST-EXTERNAL"),
227+
instrument_id=InstrumentId.from_str(instrument_id),
228+
)
229+
strategy = DemoStrategy(config=strategy_config)
230+
231+
# Add your strategies and modules
232+
node.trader.add_strategy(strategy)
233+
234+
# Register your client factories with the node (can take user-defined factories)
235+
node.add_data_client_factory(IB, InteractiveBrokersLiveDataClientFactory)
236+
node.add_exec_client_factory(IB, InteractiveBrokersLiveExecClientFactory)
237+
node.build()
238+
239+
# %%
240+
node.run()
241+
242+
# %%
243+
node.dispose()

nautilus_trader/adapters/interactive_brokers/client/client.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ def __init__(
150150
self._order_id_to_order_ref: dict[int, AccountOrderRef] = {}
151151
self._next_valid_order_id: int = -1
152152

153+
# Instrument provider (set by data/execution clients during connection)
154+
self._instrument_provider = None
155+
153156
# Start client
154157
self._request_id_seq: int = 10000
155158

nautilus_trader/adapters/interactive_brokers/client/common.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,9 @@ class BaseMixin:
498498
_port: int
499499
_client_id: int
500500
_requests: Requests
501+
_instrument_provider: (
502+
Any # InteractiveBrokersInstrumentProvider | None - Will be set by data/execution client
503+
)
501504
_subscriptions: Subscriptions
502505
_event_subscriptions: dict[str, Callable]
503506
_eclient: EClient

0 commit comments

Comments
 (0)