|
| 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 | + # Subscribe to market data |
| 70 | + self.subscribe_bars(self.config.bar_type) |
| 71 | + |
| 72 | + # Show initial portfolio state |
| 73 | + self.show_portfolio_info("Portfolio state (Before trade)") |
| 74 | + |
| 75 | + def on_bar(self, bar: Bar): |
| 76 | + """ |
| 77 | + Handle new bar event. |
| 78 | + """ |
| 79 | + # Increment total bars seen |
| 80 | + self.count_of_bars += 1 |
| 81 | + |
| 82 | + # Show portfolio state if we reached target bar |
| 83 | + if self.show_portfolio_at_bar == self.count_of_bars: |
| 84 | + self.show_portfolio_info("Portfolio state (2 minutes after position opened)") |
| 85 | + |
| 86 | + # Only place one order for demonstration |
| 87 | + if not self.order_placed: |
| 88 | + # Prepare values for order |
| 89 | + last_price = bar.close |
| 90 | + tick_size = self.instrument.price_increment |
| 91 | + profit_price = self.instrument.make_price(last_price + (10 * tick_size)) |
| 92 | + stoploss_price = self.instrument.make_price(last_price - (10 * tick_size)) |
| 93 | + |
| 94 | + # Create BUY MARKET order with PT and SL (both 10 ticks) |
| 95 | + bracket_order_list = self.order_factory.bracket( |
| 96 | + instrument_id=self.config.instrument_id, |
| 97 | + order_side=OrderSide.BUY, |
| 98 | + quantity=self.instrument.make_qty(1), # Trade size: 1 contract |
| 99 | + time_in_force=TimeInForce.GTC, |
| 100 | + tp_price=profit_price, |
| 101 | + sl_trigger_price=stoploss_price, |
| 102 | + entry_post_only=False, |
| 103 | + tp_post_only=False, |
| 104 | + ) |
| 105 | + |
| 106 | + # Submit order and remember it |
| 107 | + self.submit_order_list(bracket_order_list) |
| 108 | + self.order_placed = True |
| 109 | + self.log.info(f"Submitted bracket order: {bracket_order_list}", color=LogColor.GREEN) |
| 110 | + |
| 111 | + def on_position_opened(self, event: PositionOpened): |
| 112 | + """ |
| 113 | + Handle position opened event. |
| 114 | + """ |
| 115 | + # Log position details |
| 116 | + self.log.info(f"Position opened: {event}", color=LogColor.GREEN) |
| 117 | + |
| 118 | + # Show portfolio state when position is opened |
| 119 | + self.show_portfolio_info("Portfolio state (In position):") |
| 120 | + |
| 121 | + # Set target bar number for next portfolio display |
| 122 | + self.show_portfolio_at_bar = self.count_of_bars + 2 # Show after 2 bars |
| 123 | + |
| 124 | + def on_stop(self): |
| 125 | + """ |
| 126 | + Handle strategy stop event. |
| 127 | + """ |
| 128 | + # Show final portfolio state |
| 129 | + self.show_portfolio_info("Portfolio state (After trade)") |
| 130 | + |
| 131 | + def show_portfolio_info(self, intro_message: str = ""): |
| 132 | + """ |
| 133 | + Display current portfolio information. |
| 134 | + """ |
| 135 | + if intro_message: |
| 136 | + self.log.info(f"====== {intro_message} ======") |
| 137 | + |
| 138 | + # POSITION information |
| 139 | + self.log.info("Portfolio -> Position information:", color=LogColor.BLUE) |
| 140 | + is_flat = self.portfolio.is_flat(self.config.instrument_id) |
| 141 | + self.log.info(f"Is flat: {is_flat}", color=LogColor.BLUE) |
| 142 | + |
| 143 | + net_position = self.portfolio.net_position(self.config.instrument_id) |
| 144 | + self.log.info(f"Net position: {net_position} contract(s)", color=LogColor.BLUE) |
| 145 | + |
| 146 | + net_exposure = self.portfolio.net_exposure(self.config.instrument_id) |
| 147 | + self.log.info(f"Net exposure: {net_exposure}", color=LogColor.BLUE) |
| 148 | + |
| 149 | + # ----------------------------------------------------- |
| 150 | + |
| 151 | + # P&L information |
| 152 | + self.log.info("Portfolio -> P&L information:", color=LogColor.YELLOW) |
| 153 | + |
| 154 | + realized_pnl = self.portfolio.realized_pnl(self.config.instrument_id) |
| 155 | + self.log.info(f"Realized P&L: {realized_pnl}", color=LogColor.YELLOW) |
| 156 | + |
| 157 | + unrealized_pnl = self.portfolio.unrealized_pnl(self.config.instrument_id) |
| 158 | + self.log.info(f"Unrealized P&L: {unrealized_pnl}", color=LogColor.YELLOW) |
| 159 | + |
| 160 | + # ----------------------------------------------------- |
| 161 | + |
| 162 | + self.log.info("Portfolio -> Account information:", color=LogColor.CYAN) |
| 163 | + margins_init = self.portfolio.margins_init(IB_VENUE) |
| 164 | + self.log.info(f"Initial margin: {margins_init}", color=LogColor.CYAN) |
| 165 | + |
| 166 | + margins_maint = self.portfolio.margins_maint(IB_VENUE) |
| 167 | + self.log.info(f"Maintenance margin: {margins_maint}", color=LogColor.CYAN) |
| 168 | + |
| 169 | + balances_locked = self.portfolio.balances_locked(IB_VENUE) |
| 170 | + self.log.info(f"Locked balance: {balances_locked}", color=LogColor.CYAN) |
| 171 | + |
| 172 | + |
| 173 | +# %% |
| 174 | +instrument_provider = InteractiveBrokersInstrumentProviderConfig( |
| 175 | + symbology_method=SymbologyMethod.IB_SIMPLIFIED, |
| 176 | + convert_exchange_to_mic_venue=True, |
| 177 | + # load_ids=frozenset( |
| 178 | + # [ |
| 179 | + # "YMM5.XCBT", |
| 180 | + # ], |
| 181 | + # ), |
| 182 | +) |
| 183 | + |
| 184 | +# Configure the trading node |
| 185 | +# IMPORTANT: you must use the imported IB string so this client works properly |
| 186 | +config_node = TradingNodeConfig( |
| 187 | + trader_id="TESTER-001", |
| 188 | + logging=LoggingConfig(log_level="INFO"), |
| 189 | + data_clients={ |
| 190 | + IB: InteractiveBrokersDataClientConfig( |
| 191 | + ibg_port=7497, |
| 192 | + handle_revised_bars=False, |
| 193 | + use_regular_trading_hours=False, |
| 194 | + instrument_provider=instrument_provider, |
| 195 | + ), |
| 196 | + }, |
| 197 | + exec_clients={ |
| 198 | + IB: InteractiveBrokersExecClientConfig( |
| 199 | + ibg_port=7497, |
| 200 | + instrument_provider=instrument_provider, |
| 201 | + routing=RoutingConfig(default=True), |
| 202 | + account_id="DU187075", |
| 203 | + ), |
| 204 | + }, |
| 205 | + data_engine=LiveDataEngineConfig( |
| 206 | + time_bars_timestamp_on_close=False, # Will use opening time as `ts_event` (same as IB) |
| 207 | + validate_data_sequence=True, # Will make sure DataEngine discards any Bars received out of sequence |
| 208 | + ), |
| 209 | + timeout_connection=90.0, |
| 210 | + timeout_reconciliation=5.0, |
| 211 | + timeout_portfolio=5.0, |
| 212 | + timeout_disconnection=5.0, |
| 213 | + timeout_post_stop=2.0, |
| 214 | +) |
| 215 | + |
| 216 | + |
| 217 | +# Instantiate the node with a configuration |
| 218 | +node = TradingNode(config=config_node) |
| 219 | + |
| 220 | +# Instantiate your strategy |
| 221 | +instrument_id = "YMM5.XCBT" # AAPL.XNAS |
| 222 | + |
| 223 | +strategy_config = DemoStrategyConfig( |
| 224 | + bar_type=BarType.from_str(f"{instrument_id}-1-MINUTE-LAST-EXTERNAL"), |
| 225 | + instrument_id=InstrumentId.from_str(instrument_id), |
| 226 | +) |
| 227 | +strategy = DemoStrategy(config=strategy_config) |
| 228 | + |
| 229 | +# Add your strategies and modules |
| 230 | +node.trader.add_strategy(strategy) |
| 231 | + |
| 232 | +# Register your client factories with the node (can take user-defined factories) |
| 233 | +node.add_data_client_factory(IB, InteractiveBrokersLiveDataClientFactory) |
| 234 | +node.add_exec_client_factory(IB, InteractiveBrokersLiveExecClientFactory) |
| 235 | +node.build() |
| 236 | + |
| 237 | +# %% |
| 238 | +node.run() |
| 239 | + |
| 240 | +# %% |
| 241 | +node.dispose() |
0 commit comments