|
53 | 53 | from nautilus_trader.model.data import DataType |
54 | 54 | from nautilus_trader.model.data import QuoteTick |
55 | 55 | from nautilus_trader.model.enums import OrderSide |
| 56 | +from nautilus_trader.model.enums import PriceType |
56 | 57 | from nautilus_trader.model.greeks_data import GreeksData |
57 | 58 | from nautilus_trader.model.identifiers import InstrumentId |
58 | 59 | from nautilus_trader.model.identifiers import Venue |
59 | 60 | from nautilus_trader.model.identifiers import new_generic_spread_id |
60 | 61 | from nautilus_trader.model.instruments import FuturesContract |
61 | | -from nautilus_trader.model.objects import Price |
62 | 62 | from nautilus_trader.model.objects import Quantity |
63 | 63 | from nautilus_trader.model.tick_scheme import TieredTickScheme |
64 | 64 | from nautilus_trader.model.tick_scheme import register_tick_scheme |
|
156 | 156 | ], |
157 | 157 | ) |
158 | 158 |
|
| 159 | + |
| 160 | +def get_mid_price_rounded(tick, instrument, round_up=True, num_ticks=0): |
| 161 | + mid_price = tick.extract_price(PriceType.MID) |
| 162 | + if round_up: |
| 163 | + return instrument.next_ask_price(mid_price.as_double(), num_ticks=num_ticks) |
| 164 | + else: |
| 165 | + return instrument.next_bid_price(mid_price.as_double(), num_ticks=num_ticks) |
| 166 | + |
| 167 | + |
159 | 168 | # %% [markdown] |
160 | 169 | # ## strategy |
161 | 170 |
|
@@ -303,13 +312,31 @@ def on_quote_tick(self, tick): |
303 | 312 | ) |
304 | 313 |
|
305 | 314 | # Submit spread order when we have spread quotes available |
306 | | - if tick.instrument_id == self.config.spread_id and not self.spread_order_submitted: |
| 315 | + if ( |
| 316 | + tick.instrument_id == self.config.spread_id |
| 317 | + and not self.spread_order_submitted |
| 318 | + and tick.ts_init == 1715248980000000000 |
| 319 | + ): |
307 | 320 | # Try submitting order immediately - the exchange should have processed the quote by now |
308 | | - self.submit_market_order(instrument_id=self.config.spread_id, quantity=5) |
| 321 | + instrument = self.cache.instrument(tick.instrument_id) |
| 322 | + mid_price_rounded = get_mid_price_rounded( |
| 323 | + tick=tick, |
| 324 | + instrument=instrument, |
| 325 | + round_up=True, # Round up for buy orders (more aggressive) |
| 326 | + ) |
| 327 | + self.submit_limit_order( |
| 328 | + instrument_id=self.config.spread_id, |
| 329 | + price=mid_price_rounded, |
| 330 | + quantity=5, |
| 331 | + ) |
309 | 332 | self.spread_order_submitted = True |
310 | 333 |
|
311 | 334 | if tick.instrument_id == self.config.spread_id2 and not self.spread_order_submitted2: |
312 | | - self.submit_market_order(instrument_id=self.config.spread_id2, quantity=5) |
| 335 | + self.submit_limit_order( |
| 336 | + instrument_id=self.config.spread_id2, |
| 337 | + price=tick.ask_price, |
| 338 | + quantity=5, |
| 339 | + ) |
313 | 340 | self.spread_order_submitted2 = True |
314 | 341 |
|
315 | 342 | def on_order_filled(self, event): |
@@ -367,18 +394,18 @@ def submit_market_order(self, instrument_id, quantity): |
367 | 394 | order_side=(OrderSide.BUY if quantity > 0 else OrderSide.SELL), |
368 | 395 | quantity=Quantity.from_int(abs(quantity)), |
369 | 396 | ) |
| 397 | + self.user_log(f"Submitting order: {order}") |
370 | 398 | self.submit_order(order) |
371 | | - self.user_log(f"Order submitted: {order}") |
372 | 399 |
|
373 | 400 | def submit_limit_order(self, instrument_id, price, quantity): |
374 | 401 | order = self.order_factory.limit( |
375 | 402 | instrument_id=instrument_id, |
376 | 403 | order_side=(OrderSide.BUY if quantity > 0 else OrderSide.SELL), |
377 | 404 | quantity=Quantity.from_int(abs(quantity)), |
378 | | - price=Price.from_str(f"{price:.2f}"), |
| 405 | + price=price, |
379 | 406 | ) |
| 407 | + self.user_log(f"Submitting order: {order}") |
380 | 408 | self.submit_order(order) |
381 | | - self.user_log(f"Order submitted: {order}") |
382 | 409 |
|
383 | 410 | def user_log(self, msg, color=LogColor.GREEN): |
384 | 411 | self.log.warning(f"{msg}", color=color) |
@@ -463,6 +490,7 @@ def on_stop(self): |
463 | 490 | strategies=strategies, |
464 | 491 | streaming=(streaming if not load_greeks else None), |
465 | 492 | catalogs=catalogs, |
| 493 | + # allow_immediate_quote_execution=True, |
466 | 494 | ) |
467 | 495 |
|
468 | 496 | # BacktestRunConfig |
@@ -498,12 +526,9 @@ def on_stop(self): |
498 | 526 | *data, |
499 | 527 | ] |
500 | 528 |
|
501 | | -# Configure venue with enhanced SizeAwareFillModel for realistic option execution |
502 | | -# This fill model provides different execution behavior based on order size: |
503 | | -# - Small orders (<=10 contracts): Good liquidity at best prices |
504 | | -# - Large orders: Experience price impact with partial fills at worse prices |
| 529 | +# Configure venue with enhanced BestPriceFillModel for being able to execute limit orders anywhere between a bid ask |
505 | 530 | fill_model = ImportableFillModelConfig( |
506 | | - fill_model_path="nautilus_trader.backtest.models:SizeAwareFillModel", |
| 531 | + fill_model_path="nautilus_trader.backtest.models:BestPriceFillModel", |
507 | 532 | config_path="nautilus_trader.backtest.config:FillModelConfig", |
508 | 533 | config={}, |
509 | 534 | ) |
|
0 commit comments