|
| 1 | +# ------------------------------------------------------------------------------------------------- |
| 2 | +# Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved. |
| 3 | +# https://nautechsystems.io |
| 4 | +# |
| 5 | +# Licensed under the GNU Lesser General Public License Version 3.0 (the "License"); |
| 6 | +# You may not use this file except in compliance with the License. |
| 7 | +# You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html |
| 8 | +# |
| 9 | +# Unless required by applicable law or agreed to in writing, software |
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +# See the License for the specific language governing permissions and |
| 13 | +# limitations under the License. |
| 14 | +# ------------------------------------------------------------------------------------------------- |
| 15 | + |
| 16 | + |
| 17 | +from nautilus_trader.common.enums import LogColor |
| 18 | +from nautilus_trader.model.data import Bar |
| 19 | +from nautilus_trader.model.data import BarType |
| 20 | +from nautilus_trader.model.enums import OrderSide |
| 21 | +from nautilus_trader.model.enums import TimeInForce |
| 22 | +from nautilus_trader.model.events import PositionOpened |
| 23 | +from nautilus_trader.model.instruments.base import Instrument |
| 24 | +from nautilus_trader.trading.config import StrategyConfig |
| 25 | +from nautilus_trader.trading.strategy import Strategy |
| 26 | + |
| 27 | + |
| 28 | +class DemoStrategyConfig(StrategyConfig, frozen=True): |
| 29 | + bar_type: BarType |
| 30 | + instrument: Instrument |
| 31 | + |
| 32 | + |
| 33 | +class DemoStrategy(Strategy): |
| 34 | + |
| 35 | + def __init__(self, config: DemoStrategyConfig): |
| 36 | + super().__init__(config=config) |
| 37 | + |
| 38 | + # Track if we've already placed an order |
| 39 | + self.order_placed = False |
| 40 | + |
| 41 | + # Track total bars seen |
| 42 | + self.count_of_bars: int = 0 |
| 43 | + self.show_portfolio_at_bar: int | None = 0 |
| 44 | + |
| 45 | + def on_start(self): |
| 46 | + """ |
| 47 | + Handle strategy start event. |
| 48 | + """ |
| 49 | + # Subscribe to market data |
| 50 | + self.subscribe_bars(self.config.bar_type) |
| 51 | + |
| 52 | + # Show initial portfolio state |
| 53 | + self.show_portfolio_info("Portfolio state (Before trade)") |
| 54 | + |
| 55 | + def on_bar(self, bar: Bar): |
| 56 | + """ |
| 57 | + Handle new bar event. |
| 58 | + """ |
| 59 | + # Increment total bars seen |
| 60 | + self.count_of_bars += 1 |
| 61 | + |
| 62 | + # Show portfolio state if we reached target bar |
| 63 | + if self.show_portfolio_at_bar == self.count_of_bars: |
| 64 | + self.show_portfolio_info("Portfolio state (2 minutes after position opened)") |
| 65 | + |
| 66 | + # Only place one order for demonstration |
| 67 | + if not self.order_placed: |
| 68 | + # Prepare values for order |
| 69 | + last_price = bar.close |
| 70 | + tick_size = self.config.instrument.price_increment |
| 71 | + profit_price = self.config.instrument.make_price(last_price + (10 * tick_size)) |
| 72 | + stoploss_price = self.config.instrument.make_price(last_price - (10 * tick_size)) |
| 73 | + |
| 74 | + # Create BUY MARKET order with PT and SL (both 10 ticks) |
| 75 | + bracket_order_list = self.order_factory.bracket( |
| 76 | + instrument_id=self.config.instrument.id, |
| 77 | + order_side=OrderSide.BUY, |
| 78 | + quantity=self.config.instrument.make_qty(1), # Trade size: 1 contract |
| 79 | + time_in_force=TimeInForce.GTC, |
| 80 | + tp_price=profit_price, |
| 81 | + sl_trigger_price=stoploss_price, |
| 82 | + ) |
| 83 | + |
| 84 | + # Submit order and remember it |
| 85 | + self.submit_order_list(bracket_order_list) |
| 86 | + self.order_placed = True |
| 87 | + self.log.info(f"Submitted bracket order: {bracket_order_list}", color=LogColor.GREEN) |
| 88 | + |
| 89 | + def on_position_opened(self, event: PositionOpened): |
| 90 | + """ |
| 91 | + Handle position opened event. |
| 92 | + """ |
| 93 | + # Log position details |
| 94 | + self.log.info(f"Position opened: {event}", color=LogColor.GREEN) |
| 95 | + |
| 96 | + # Show portfolio state when position is opened |
| 97 | + self.show_portfolio_info("Portfolio state (In position):") |
| 98 | + |
| 99 | + # Set target bar number for next portfolio display |
| 100 | + self.show_portfolio_at_bar = self.count_of_bars + 2 # Show after 2 bars |
| 101 | + |
| 102 | + def on_stop(self): |
| 103 | + """ |
| 104 | + Handle strategy stop event. |
| 105 | + """ |
| 106 | + # Show final portfolio state |
| 107 | + self.show_portfolio_info("Portfolio state (After trade)") |
| 108 | + |
| 109 | + def show_portfolio_info(self, intro_message: str = ""): |
| 110 | + """ |
| 111 | + Display current portfolio information. |
| 112 | + """ |
| 113 | + if intro_message: |
| 114 | + self.log.info(f"====== {intro_message} ======") |
| 115 | + |
| 116 | + # POSITION information |
| 117 | + self.log.info("Portfolio -> Position information:", color=LogColor.BLUE) |
| 118 | + is_flat = self.portfolio.is_flat(self.config.instrument.id) |
| 119 | + self.log.info(f"Is flat: {is_flat}", color=LogColor.BLUE) |
| 120 | + |
| 121 | + net_position = self.portfolio.net_position(self.config.instrument.id) |
| 122 | + self.log.info(f"Net position: {net_position} contract(s)", color=LogColor.BLUE) |
| 123 | + |
| 124 | + net_exposure = self.portfolio.net_exposure(self.config.instrument.id) |
| 125 | + self.log.info(f"Net exposure: {net_exposure}", color=LogColor.BLUE) |
| 126 | + |
| 127 | + # ----------------------------------------------------- |
| 128 | + |
| 129 | + # P&L information |
| 130 | + self.log.info("Portfolio -> P&L information:", color=LogColor.YELLOW) |
| 131 | + |
| 132 | + realized_pnl = self.portfolio.realized_pnl(self.config.instrument.id) |
| 133 | + self.log.info(f"Realized P&L: {realized_pnl}", color=LogColor.YELLOW) |
| 134 | + |
| 135 | + unrealized_pnl = self.portfolio.unrealized_pnl(self.config.instrument.id) |
| 136 | + self.log.info(f"Unrealized P&L: {unrealized_pnl}", color=LogColor.YELLOW) |
| 137 | + |
| 138 | + # ----------------------------------------------------- |
| 139 | + |
| 140 | + self.log.info("Portfolio -> Account information:", color=LogColor.CYAN) |
| 141 | + margins_init = self.portfolio.margins_init(self.config.instrument.venue) |
| 142 | + self.log.info(f"Initial margin: {margins_init}", color=LogColor.CYAN) |
| 143 | + |
| 144 | + margins_maint = self.portfolio.margins_maint(self.config.instrument.venue) |
| 145 | + self.log.info(f"Maintenance margin: {margins_maint}", color=LogColor.CYAN) |
| 146 | + |
| 147 | + balances_locked = self.portfolio.balances_locked(self.config.instrument.venue) |
| 148 | + self.log.info(f"Locked balance: {balances_locked}", color=LogColor.CYAN) |
0 commit comments