1059 lines
46 KiB
Text
1059 lines
46 KiB
Text
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Session 5: Event-Driven Trading Bot\n",
|
|
"\n",
|
|
"## Notebook 2: Order Management System (OMS)\n",
|
|
"\n",
|
|
"**Learning Objectives:**\n",
|
|
"- Build a professional Order Management System\n",
|
|
"- Implement Market, Limit, and Stop order types\n",
|
|
"- Handle order lifecycle (pending → filled → complete)\n",
|
|
"- Integrate OMS with our event system\n",
|
|
"- Simulate exchange interactions\n",
|
|
"\n",
|
|
"**Why This Matters:**\n",
|
|
"The OMS is the heart of any trading system. It's responsible for creating, tracking, and managing all orders. A robust OMS ensures your trades execute correctly and your bot stays in sync with the exchange.\n",
|
|
"\n",
|
|
"---"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Quick Setup: Import Event System\n",
|
|
"\n",
|
|
"Let's import the event system we built in Notebook 1:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"🏗️ Event system ready for OMS integration!\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"# Import our event system from Notebook 1\n",
|
|
"from datetime import datetime\n",
|
|
"from typing import Dict, Any, List, Callable, Optional\n",
|
|
"from dataclasses import dataclass, field\n",
|
|
"from collections import deque\n",
|
|
"from threading import Lock\n",
|
|
"from enum import Enum\n",
|
|
"import json\n",
|
|
"import uuid\n",
|
|
"import time\n",
|
|
"\n",
|
|
"# Re-create our basic Event system (copy from Notebook 1)\n",
|
|
"@dataclass\n",
|
|
"class Event:\n",
|
|
" \"\"\"Base class for all events in our trading system\"\"\"\n",
|
|
" event_type: str\n",
|
|
" data: Dict[str, Any]\n",
|
|
" timestamp: datetime = None\n",
|
|
" source: str = \"unknown\"\n",
|
|
" \n",
|
|
" def __post_init__(self):\n",
|
|
" if self.timestamp is None:\n",
|
|
" self.timestamp = datetime.now()\n",
|
|
"\n",
|
|
"class EventQueue:\n",
|
|
" \"\"\"Central event queue from Notebook 1\"\"\"\n",
|
|
" def __init__(self):\n",
|
|
" self.events = deque()\n",
|
|
" self.subscribers = {}\n",
|
|
" self.lock = Lock()\n",
|
|
" self.event_history = []\n",
|
|
" \n",
|
|
" def subscribe(self, event_type: str, callback: Callable[[Event], None]):\n",
|
|
" with self.lock:\n",
|
|
" if event_type not in self.subscribers:\n",
|
|
" self.subscribers[event_type] = []\n",
|
|
" self.subscribers[event_type].append(callback)\n",
|
|
" print(f\"✅ Subscribed to '{event_type}' events\")\n",
|
|
" \n",
|
|
" def publish(self, event: Event):\n",
|
|
" with self.lock:\n",
|
|
" self.events.append(event)\n",
|
|
" self.event_history.append(event)\n",
|
|
" \n",
|
|
" if event.event_type in self.subscribers:\n",
|
|
" for callback in self.subscribers[event.event_type]:\n",
|
|
" try:\n",
|
|
" callback(event)\n",
|
|
" except Exception as e:\n",
|
|
" print(f\"❌ Error in callback for {event.event_type}: {e}\")\n",
|
|
" \n",
|
|
" print(f\"📤 Published: {event.event_type} from {event.source}\")\n",
|
|
"\n",
|
|
"# Create our event queue\n",
|
|
"event_queue = EventQueue()\n",
|
|
"print(\"🏗️ Event system ready for OMS integration!\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Part 1: Order States and Types\n",
|
|
"\n",
|
|
"First, let's define the different order states and types our OMS will handle:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"📊 Order enums defined:\n",
|
|
"States: ['PENDING', 'SUBMITTED', 'OPEN', 'PARTIAL', 'FILLED', 'CANCELLED', 'REJECTED', 'EXPIRED']\n",
|
|
"Types: ['MARKET', 'LIMIT', 'STOP', 'STOP_LIMIT']\n",
|
|
"Sides: ['BUY', 'SELL']\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"# Order States - lifecycle of an order\n",
|
|
"class OrderState(Enum):\n",
|
|
" PENDING = \"PENDING\" # Order created, waiting to be sent\n",
|
|
" SUBMITTED = \"SUBMITTED\" # Order sent to exchange\n",
|
|
" OPEN = \"OPEN\" # Order active on exchange\n",
|
|
" PARTIALLY_FILLED = \"PARTIAL\" # Order partially executed\n",
|
|
" FILLED = \"FILLED\" # Order completely executed\n",
|
|
" CANCELLED = \"CANCELLED\" # Order cancelled\n",
|
|
" REJECTED = \"REJECTED\" # Order rejected by exchange\n",
|
|
" EXPIRED = \"EXPIRED\" # Order expired (for time-limited orders)\n",
|
|
"\n",
|
|
"# Order Types\n",
|
|
"class OrderType(Enum):\n",
|
|
" MARKET = \"MARKET\" # Execute immediately at current market price\n",
|
|
" LIMIT = \"LIMIT\" # Execute only at specified price or better\n",
|
|
" STOP = \"STOP\" # Convert to market order when stop price is hit\n",
|
|
" STOP_LIMIT = \"STOP_LIMIT\" # Convert to limit order when stop price is hit\n",
|
|
"\n",
|
|
"# Order Side\n",
|
|
"class OrderSide(Enum):\n",
|
|
" BUY = \"BUY\"\n",
|
|
" SELL = \"SELL\"\n",
|
|
"\n",
|
|
"print(\"📊 Order enums defined:\")\n",
|
|
"print(f\"States: {[state.value for state in OrderState]}\")\n",
|
|
"print(f\"Types: {[order_type.value for order_type in OrderType]}\")\n",
|
|
"print(f\"Sides: {[side.value for side in OrderSide]}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Part 2: Order Class\n",
|
|
"\n",
|
|
"Now let's create a comprehensive Order class that tracks all order information:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"🧪 Testing Order class:\n",
|
|
"\n",
|
|
"Market Order: BUY 0.1 BTC/USDT @MARKET (PENDING)\n",
|
|
"Limit Order: SELL 0.05 BTC/USDT @$45000.00 (PENDING)\n",
|
|
"Stop Order: SELL 0.08 BTC/USDT @MARKET (PENDING)\n",
|
|
"\n",
|
|
"📊 Market order details:\n",
|
|
"{\n",
|
|
" \"order_id\": \"aab41f17-faa2-4d5e-ad50-2689e3c8b689\",\n",
|
|
" \"client_order_id\": \"client_1750871237729\",\n",
|
|
" \"symbol\": \"BTC/USDT\",\n",
|
|
" \"side\": \"BUY\",\n",
|
|
" \"type\": \"MARKET\",\n",
|
|
" \"quantity\": 0.1,\n",
|
|
" \"price\": null,\n",
|
|
" \"stop_price\": null,\n",
|
|
" \"state\": \"PENDING\",\n",
|
|
" \"filled_quantity\": 0.0,\n",
|
|
" \"remaining_quantity\": 0.1,\n",
|
|
" \"average_fill_price\": 0.0,\n",
|
|
" \"fill_percentage\": \"0.0%\",\n",
|
|
" \"created_at\": \"2025-06-25T19:07:17.729210\",\n",
|
|
" \"fees_paid\": 0.0\n",
|
|
"}\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"@dataclass\n",
|
|
"class Order:\n",
|
|
" \"\"\"Comprehensive order class for our trading system\"\"\"\n",
|
|
" \n",
|
|
" # Core order information\n",
|
|
" symbol: str\n",
|
|
" side: OrderSide\n",
|
|
" order_type: OrderType\n",
|
|
" quantity: float\n",
|
|
" \n",
|
|
" # Optional price information\n",
|
|
" price: Optional[float] = None # For limit orders\n",
|
|
" stop_price: Optional[float] = None # For stop orders\n",
|
|
" \n",
|
|
" # Order tracking\n",
|
|
" order_id: str = field(default_factory=lambda: str(uuid.uuid4()))\n",
|
|
" client_order_id: str = field(default_factory=lambda: f\"client_{int(time.time() * 1000)}\")\n",
|
|
" exchange_order_id: Optional[str] = None\n",
|
|
" \n",
|
|
" # Order state\n",
|
|
" state: OrderState = OrderState.PENDING\n",
|
|
" filled_quantity: float = 0.0\n",
|
|
" remaining_quantity: Optional[float] = None\n",
|
|
" average_fill_price: float = 0.0\n",
|
|
" \n",
|
|
" # Timestamps\n",
|
|
" created_at: datetime = field(default_factory=datetime.now)\n",
|
|
" submitted_at: Optional[datetime] = None\n",
|
|
" filled_at: Optional[datetime] = None\n",
|
|
" \n",
|
|
" # Additional metadata\n",
|
|
" fees_paid: float = 0.0\n",
|
|
" notes: str = \"\"\n",
|
|
" \n",
|
|
" def __post_init__(self):\n",
|
|
" \"\"\"Initialize remaining quantity and validate order\"\"\"\n",
|
|
" if self.remaining_quantity is None:\n",
|
|
" self.remaining_quantity = self.quantity\n",
|
|
" \n",
|
|
" # Validate order\n",
|
|
" self._validate()\n",
|
|
" \n",
|
|
" def _validate(self):\n",
|
|
" \"\"\"Validate order parameters\"\"\"\n",
|
|
" if self.quantity <= 0:\n",
|
|
" raise ValueError(\"Order quantity must be positive\")\n",
|
|
" \n",
|
|
" if self.order_type in [OrderType.LIMIT, OrderType.STOP_LIMIT] and self.price is None:\n",
|
|
" raise ValueError(f\"{self.order_type.value} orders require a price\")\n",
|
|
" \n",
|
|
" if self.order_type in [OrderType.STOP, OrderType.STOP_LIMIT] and self.stop_price is None:\n",
|
|
" raise ValueError(f\"{self.order_type.value} orders require a stop price\")\n",
|
|
" \n",
|
|
" @property\n",
|
|
" def is_buy(self) -> bool:\n",
|
|
" return self.side == OrderSide.BUY\n",
|
|
" \n",
|
|
" @property\n",
|
|
" def is_sell(self) -> bool:\n",
|
|
" return self.side == OrderSide.SELL\n",
|
|
" \n",
|
|
" @property\n",
|
|
" def is_filled(self) -> bool:\n",
|
|
" return self.state == OrderState.FILLED\n",
|
|
" \n",
|
|
" @property\n",
|
|
" def is_open(self) -> bool:\n",
|
|
" return self.state in [OrderState.OPEN, OrderState.PARTIALLY_FILLED]\n",
|
|
" \n",
|
|
" @property\n",
|
|
" def fill_percentage(self) -> float:\n",
|
|
" \"\"\"Percentage of order that has been filled\"\"\"\n",
|
|
" return (self.filled_quantity / self.quantity) * 100 if self.quantity > 0 else 0\n",
|
|
" \n",
|
|
" def update_fill(self, fill_quantity: float, fill_price: float, fees: float = 0.0):\n",
|
|
" \"\"\"Update order with a partial or complete fill\"\"\"\n",
|
|
" # Update quantities\n",
|
|
" self.filled_quantity += fill_quantity\n",
|
|
" self.remaining_quantity -= fill_quantity\n",
|
|
" self.fees_paid += fees\n",
|
|
" \n",
|
|
" # Update average fill price (weighted average)\n",
|
|
" if self.filled_quantity > 0:\n",
|
|
" total_cost = (self.average_fill_price * (self.filled_quantity - fill_quantity)) + (fill_price * fill_quantity)\n",
|
|
" self.average_fill_price = total_cost / self.filled_quantity\n",
|
|
" \n",
|
|
" # Update state\n",
|
|
" if self.remaining_quantity <= 0:\n",
|
|
" self.state = OrderState.FILLED\n",
|
|
" self.filled_at = datetime.now()\n",
|
|
" else:\n",
|
|
" self.state = OrderState.PARTIALLY_FILLED\n",
|
|
" \n",
|
|
" def cancel(self):\n",
|
|
" \"\"\"Cancel the order\"\"\"\n",
|
|
" if self.is_open:\n",
|
|
" self.state = OrderState.CANCELLED\n",
|
|
" \n",
|
|
" def to_dict(self) -> Dict[str, Any]:\n",
|
|
" \"\"\"Convert order to dictionary for logging/API calls\"\"\"\n",
|
|
" return {\n",
|
|
" 'order_id': self.order_id,\n",
|
|
" 'client_order_id': self.client_order_id,\n",
|
|
" 'symbol': self.symbol,\n",
|
|
" 'side': self.side.value,\n",
|
|
" 'type': self.order_type.value,\n",
|
|
" 'quantity': self.quantity,\n",
|
|
" 'price': self.price,\n",
|
|
" 'stop_price': self.stop_price,\n",
|
|
" 'state': self.state.value,\n",
|
|
" 'filled_quantity': self.filled_quantity,\n",
|
|
" 'remaining_quantity': self.remaining_quantity,\n",
|
|
" 'average_fill_price': self.average_fill_price,\n",
|
|
" 'fill_percentage': f\"{self.fill_percentage:.1f}%\",\n",
|
|
" 'created_at': self.created_at.isoformat(),\n",
|
|
" 'fees_paid': self.fees_paid\n",
|
|
" }\n",
|
|
" \n",
|
|
" def __str__(self):\n",
|
|
" price_str = f\"@${self.price:.2f}\" if self.price else \"@MARKET\"\n",
|
|
" return f\"{self.side.value} {self.quantity} {self.symbol} {price_str} ({self.state.value})\"\n",
|
|
"\n",
|
|
"\n",
|
|
"# Test our Order class\n",
|
|
"print(\"🧪 Testing Order class:\\n\")\n",
|
|
"\n",
|
|
"# Market order\n",
|
|
"market_order = Order(\"BTC/USDT\", OrderSide.BUY, OrderType.MARKET, 0.1)\n",
|
|
"print(f\"Market Order: {market_order}\")\n",
|
|
"\n",
|
|
"# Limit order\n",
|
|
"limit_order = Order(\"BTC/USDT\", OrderSide.SELL, OrderType.LIMIT, 0.05, price=45000)\n",
|
|
"print(f\"Limit Order: {limit_order}\")\n",
|
|
"\n",
|
|
"# Stop order\n",
|
|
"stop_order = Order(\"BTC/USDT\", OrderSide.SELL, OrderType.STOP, 0.08, stop_price=42000)\n",
|
|
"print(f\"Stop Order: {stop_order}\")\n",
|
|
"\n",
|
|
"print(f\"\\n📊 Market order details:\")\n",
|
|
"print(json.dumps(market_order.to_dict(), indent=2, default=str))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Part 3: Order Events\n",
|
|
"\n",
|
|
"Let's create specific events for order management that integrate with our event system:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"📧 Testing Order Events:\n",
|
|
"\n",
|
|
"Request Event: order_request from algorithm\n",
|
|
"Status Event: order_status - submitted\n",
|
|
"Fill Event: order_fill - 0.05 @ $42950\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"class OrderRequestEvent(Event):\n",
|
|
" \"\"\"Event to request creation of a new order\"\"\"\n",
|
|
" def __init__(self, order: Order, source: str = \"strategy\"):\n",
|
|
" super().__init__(\n",
|
|
" event_type=\"order_request\",\n",
|
|
" data={\n",
|
|
" \"order\": order,\n",
|
|
" \"order_dict\": order.to_dict()\n",
|
|
" },\n",
|
|
" source=source\n",
|
|
" )\n",
|
|
"\n",
|
|
"class OrderStatusEvent(Event):\n",
|
|
" \"\"\"Event for order status updates\"\"\"\n",
|
|
" def __init__(self, order: Order, status_type: str, message: str = \"\", source: str = \"oms\"):\n",
|
|
" super().__init__(\n",
|
|
" event_type=\"order_status\",\n",
|
|
" data={\n",
|
|
" \"order_id\": order.order_id,\n",
|
|
" \"status_type\": status_type, # 'submitted', 'filled', 'cancelled', etc.\n",
|
|
" \"message\": message,\n",
|
|
" \"order_state\": order.state.value,\n",
|
|
" \"order\": order\n",
|
|
" },\n",
|
|
" source=source\n",
|
|
" )\n",
|
|
"\n",
|
|
"class OrderFillEvent(Event):\n",
|
|
" \"\"\"Event for order fills (partial or complete)\"\"\"\n",
|
|
" def __init__(self, order: Order, fill_quantity: float, fill_price: float, fees: float = 0.0, source: str = \"exchange\"):\n",
|
|
" super().__init__(\n",
|
|
" event_type=\"order_fill\",\n",
|
|
" data={\n",
|
|
" \"order_id\": order.order_id,\n",
|
|
" \"symbol\": order.symbol,\n",
|
|
" \"side\": order.side.value,\n",
|
|
" \"fill_quantity\": fill_quantity,\n",
|
|
" \"fill_price\": fill_price,\n",
|
|
" \"fees\": fees,\n",
|
|
" \"order\": order,\n",
|
|
" \"is_complete_fill\": (order.remaining_quantity - fill_quantity) <= 0\n",
|
|
" },\n",
|
|
" source=source\n",
|
|
" )\n",
|
|
"\n",
|
|
"class OrderErrorEvent(Event):\n",
|
|
" \"\"\"Event for order errors and rejections\"\"\"\n",
|
|
" def __init__(self, order: Order, error_type: str, error_message: str, source: str = \"oms\"):\n",
|
|
" super().__init__(\n",
|
|
" event_type=\"order_error\",\n",
|
|
" data={\n",
|
|
" \"order_id\": order.order_id,\n",
|
|
" \"error_type\": error_type,\n",
|
|
" \"error_message\": error_message,\n",
|
|
" \"order\": order\n",
|
|
" },\n",
|
|
" source=source\n",
|
|
" )\n",
|
|
"\n",
|
|
"# Test order events\n",
|
|
"print(\"📧 Testing Order Events:\\n\")\n",
|
|
"\n",
|
|
"test_order = Order(\"BTC/USDT\", OrderSide.BUY, OrderType.LIMIT, 0.1, price=43000)\n",
|
|
"\n",
|
|
"# Order request event\n",
|
|
"request_event = OrderRequestEvent(test_order, \"algorithm\")\n",
|
|
"print(f\"Request Event: {request_event.event_type} from {request_event.source}\")\n",
|
|
"\n",
|
|
"# Order status event\n",
|
|
"status_event = OrderStatusEvent(test_order, \"submitted\", \"Order sent to exchange\")\n",
|
|
"print(f\"Status Event: {status_event.event_type} - {status_event.data['status_type']}\")\n",
|
|
"\n",
|
|
"# Order fill event\n",
|
|
"fill_event = OrderFillEvent(test_order, 0.05, 42950, 2.50)\n",
|
|
"print(f\"Fill Event: {fill_event.event_type} - {fill_event.data['fill_quantity']} @ ${fill_event.data['fill_price']}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Part 4: Order Management System\n",
|
|
"\n",
|
|
"Now let's build the core OMS that manages all orders and integrates with our event system:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"✅ Subscribed to 'order_request' events\n",
|
|
"✅ Subscribed to 'order_fill' events\n",
|
|
"🏢 order_manager: Order Management System initialized\n",
|
|
"\n",
|
|
"📊 OMS Stats: {\n",
|
|
" \"total_orders_created\": 0,\n",
|
|
" \"total_orders_filled\": 0,\n",
|
|
" \"total_orders_cancelled\": 0,\n",
|
|
" \"open_orders_count\": 0,\n",
|
|
" \"symbols_traded\": [],\n",
|
|
" \"orders_by_state\": {\n",
|
|
" \"PENDING\": 0,\n",
|
|
" \"SUBMITTED\": 0,\n",
|
|
" \"OPEN\": 0,\n",
|
|
" \"PARTIAL\": 0,\n",
|
|
" \"FILLED\": 0,\n",
|
|
" \"CANCELLED\": 0,\n",
|
|
" \"REJECTED\": 0,\n",
|
|
" \"EXPIRED\": 0\n",
|
|
" }\n",
|
|
"}\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"class OrderManager:\n",
|
|
" \"\"\"Professional Order Management System\"\"\"\n",
|
|
" \n",
|
|
" def __init__(self, event_queue: EventQueue):\n",
|
|
" self.event_queue = event_queue\n",
|
|
" self.name = \"order_manager\"\n",
|
|
" \n",
|
|
" # Order storage\n",
|
|
" self.orders: Dict[str, Order] = {} # order_id -> Order\n",
|
|
" self.orders_by_symbol: Dict[str, List[str]] = {} # symbol -> [order_ids]\n",
|
|
" self.open_orders: List[str] = [] # Active order IDs\n",
|
|
" \n",
|
|
" # Statistics\n",
|
|
" self.total_orders_created = 0\n",
|
|
" self.total_orders_filled = 0\n",
|
|
" self.total_orders_cancelled = 0\n",
|
|
" \n",
|
|
" # Subscribe to order-related events\n",
|
|
" self.event_queue.subscribe(\"order_request\", self.on_order_request)\n",
|
|
" self.event_queue.subscribe(\"order_fill\", self.on_order_fill)\n",
|
|
" \n",
|
|
" print(f\"🏢 {self.name}: Order Management System initialized\")\n",
|
|
" \n",
|
|
" def create_order(self, symbol: str, side: OrderSide, order_type: OrderType, \n",
|
|
" quantity: float, price: float = None, stop_price: float = None) -> Order:\n",
|
|
" \"\"\"Create a new order\"\"\"\n",
|
|
" try:\n",
|
|
" order = Order(\n",
|
|
" symbol=symbol,\n",
|
|
" side=side,\n",
|
|
" order_type=order_type,\n",
|
|
" quantity=quantity,\n",
|
|
" price=price,\n",
|
|
" stop_price=stop_price\n",
|
|
" )\n",
|
|
" \n",
|
|
" # Store order\n",
|
|
" self.orders[order.order_id] = order\n",
|
|
" \n",
|
|
" # Track by symbol\n",
|
|
" if symbol not in self.orders_by_symbol:\n",
|
|
" self.orders_by_symbol[symbol] = []\n",
|
|
" self.orders_by_symbol[symbol].append(order.order_id)\n",
|
|
" \n",
|
|
" # Add to open orders\n",
|
|
" self.open_orders.append(order.order_id)\n",
|
|
" \n",
|
|
" # Update stats\n",
|
|
" self.total_orders_created += 1\n",
|
|
" \n",
|
|
" print(f\"📝 {self.name}: Created order {order.order_id[:8]}... - {order}\")\n",
|
|
" \n",
|
|
" # Publish order status event\n",
|
|
" status_event = OrderStatusEvent(order, \"created\", \"Order created successfully\", self.name)\n",
|
|
" self.event_queue.publish(status_event)\n",
|
|
" \n",
|
|
" return order\n",
|
|
" \n",
|
|
" except Exception as e:\n",
|
|
" print(f\"❌ {self.name}: Failed to create order - {e}\")\n",
|
|
" return None\n",
|
|
" \n",
|
|
" def on_order_request(self, event: Event):\n",
|
|
" \"\"\"Handle order request events\"\"\"\n",
|
|
" order_data = event.data[\"order\"]\n",
|
|
" print(f\"📥 {self.name}: Received order request from {event.source}\")\n",
|
|
" \n",
|
|
" # For this demo, we'll directly process the order\n",
|
|
" # In real system, this would validate and send to exchange\n",
|
|
" order = order_data\n",
|
|
" \n",
|
|
" # Store the order\n",
|
|
" self.orders[order.order_id] = order\n",
|
|
" if order.symbol not in self.orders_by_symbol:\n",
|
|
" self.orders_by_symbol[order.symbol] = []\n",
|
|
" self.orders_by_symbol[order.symbol].append(order.order_id)\n",
|
|
" self.open_orders.append(order.order_id)\n",
|
|
" self.total_orders_created += 1\n",
|
|
" \n",
|
|
" # Update order state\n",
|
|
" order.state = OrderState.SUBMITTED\n",
|
|
" order.submitted_at = datetime.now()\n",
|
|
" \n",
|
|
" # Publish status update\n",
|
|
" status_event = OrderStatusEvent(order, \"submitted\", \"Order submitted to exchange\", self.name)\n",
|
|
" self.event_queue.publish(status_event)\n",
|
|
" \n",
|
|
" def on_order_fill(self, event: Event):\n",
|
|
" \"\"\"Handle order fill events from exchange\"\"\"\n",
|
|
" order_id = event.data[\"order_id\"]\n",
|
|
" fill_quantity = event.data[\"fill_quantity\"]\n",
|
|
" fill_price = event.data[\"fill_price\"]\n",
|
|
" fees = event.data.get(\"fees\", 0.0)\n",
|
|
" \n",
|
|
" if order_id not in self.orders:\n",
|
|
" print(f\"⚠️ {self.name}: Received fill for unknown order {order_id}\")\n",
|
|
" return\n",
|
|
" \n",
|
|
" order = self.orders[order_id]\n",
|
|
" print(f\"💰 {self.name}: Processing fill for {order_id[:8]}... - {fill_quantity} @ ${fill_price}\")\n",
|
|
" \n",
|
|
" # Update order with fill\n",
|
|
" order.update_fill(fill_quantity, fill_price, fees)\n",
|
|
" \n",
|
|
" # If order is completely filled, remove from open orders\n",
|
|
" if order.is_filled and order_id in self.open_orders:\n",
|
|
" self.open_orders.remove(order_id)\n",
|
|
" self.total_orders_filled += 1\n",
|
|
" print(f\"✅ {self.name}: Order {order_id[:8]}... completely filled!\")\n",
|
|
" \n",
|
|
" # Publish status update\n",
|
|
" status_type = \"filled\" if order.is_filled else \"partially_filled\"\n",
|
|
" message = f\"Fill: {fill_quantity} @ ${fill_price} (Total: {order.filled_quantity}/{order.quantity})\"\n",
|
|
" status_event = OrderStatusEvent(order, status_type, message, self.name)\n",
|
|
" self.event_queue.publish(status_event)\n",
|
|
" \n",
|
|
" def cancel_order(self, order_id: str) -> bool:\n",
|
|
" \"\"\"Cancel an order\"\"\"\n",
|
|
" if order_id not in self.orders:\n",
|
|
" print(f\"⚠️ {self.name}: Cannot cancel unknown order {order_id}\")\n",
|
|
" return False\n",
|
|
" \n",
|
|
" order = self.orders[order_id]\n",
|
|
" if not order.is_open:\n",
|
|
" print(f\"⚠️ {self.name}: Cannot cancel order {order_id[:8]}... - not open ({order.state.value})\")\n",
|
|
" return False\n",
|
|
" \n",
|
|
" order.cancel()\n",
|
|
" if order_id in self.open_orders:\n",
|
|
" self.open_orders.remove(order_id)\n",
|
|
" \n",
|
|
" self.total_orders_cancelled += 1\n",
|
|
" print(f\"🚫 {self.name}: Cancelled order {order_id[:8]}...\")\n",
|
|
" \n",
|
|
" # Publish status update\n",
|
|
" status_event = OrderStatusEvent(order, \"cancelled\", \"Order cancelled by user\", self.name)\n",
|
|
" self.event_queue.publish(status_event)\n",
|
|
" \n",
|
|
" return True\n",
|
|
" \n",
|
|
" def get_orders(self, symbol: str = None, state: OrderState = None) -> List[Order]:\n",
|
|
" \"\"\"Get orders with optional filtering\"\"\"\n",
|
|
" orders = list(self.orders.values())\n",
|
|
" \n",
|
|
" if symbol:\n",
|
|
" orders = [o for o in orders if o.symbol == symbol]\n",
|
|
" \n",
|
|
" if state:\n",
|
|
" orders = [o for o in orders if o.state == state]\n",
|
|
" \n",
|
|
" return orders\n",
|
|
" \n",
|
|
" def get_stats(self) -> Dict[str, Any]:\n",
|
|
" \"\"\"Get OMS statistics\"\"\"\n",
|
|
" return {\n",
|
|
" \"total_orders_created\": self.total_orders_created,\n",
|
|
" \"total_orders_filled\": self.total_orders_filled,\n",
|
|
" \"total_orders_cancelled\": self.total_orders_cancelled,\n",
|
|
" \"open_orders_count\": len(self.open_orders),\n",
|
|
" \"symbols_traded\": list(self.orders_by_symbol.keys()),\n",
|
|
" \"orders_by_state\": {\n",
|
|
" state.value: len([o for o in self.orders.values() if o.state == state])\n",
|
|
" for state in OrderState\n",
|
|
" }\n",
|
|
" }\n",
|
|
"\n",
|
|
"# Create our Order Management System\n",
|
|
"oms = OrderManager(event_queue)\n",
|
|
"print(f\"\\n📊 OMS Stats: {json.dumps(oms.get_stats(), indent=2)}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Part 5: Mock Exchange Simulator\n",
|
|
"\n",
|
|
"Let's create a simple exchange simulator to test our OMS:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"✅ Subscribed to 'order_status' events\n",
|
|
"🏪 mock_exchange: Mock Exchange initialized\n",
|
|
" Current prices: {'BTC/USDT': 43250.0, 'ETH/USDT': 2650.0, 'BNB/USDT': 310.0}\n",
|
|
"\n",
|
|
"🏪 Mock Exchange ready for testing!\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"import random\n",
|
|
"\n",
|
|
"class MockExchange:\n",
|
|
" \"\"\"Simple exchange simulator for testing our OMS\"\"\"\n",
|
|
" \n",
|
|
" def __init__(self, event_queue: EventQueue):\n",
|
|
" self.event_queue = event_queue\n",
|
|
" self.name = \"mock_exchange\"\n",
|
|
" \n",
|
|
" # Mock market prices\n",
|
|
" self.market_prices = {\n",
|
|
" \"BTC/USDT\": 43250.00,\n",
|
|
" \"ETH/USDT\": 2650.00,\n",
|
|
" \"BNB/USDT\": 310.00\n",
|
|
" }\n",
|
|
" \n",
|
|
" # Exchange settings\n",
|
|
" self.trading_fee = 0.001 # 0.1% trading fee\n",
|
|
" self.fill_probability = 0.8 # 80% chance orders get filled\n",
|
|
" \n",
|
|
" # Subscribe to order status events to simulate fills\n",
|
|
" self.event_queue.subscribe(\"order_status\", self.on_order_status)\n",
|
|
" \n",
|
|
" print(f\"🏪 {self.name}: Mock Exchange initialized\")\n",
|
|
" print(f\" Current prices: {self.market_prices}\")\n",
|
|
" \n",
|
|
" def update_price(self, symbol: str, new_price: float):\n",
|
|
" \"\"\"Update market price for a symbol\"\"\"\n",
|
|
" old_price = self.market_prices.get(symbol, 0)\n",
|
|
" self.market_prices[symbol] = new_price\n",
|
|
" change = ((new_price - old_price) / old_price * 100) if old_price > 0 else 0\n",
|
|
" print(f\"📈 {self.name}: {symbol} price updated: ${old_price:,.2f} → ${new_price:,.2f} ({change:+.2f}%)\")\n",
|
|
" \n",
|
|
" def on_order_status(self, event: Event):\n",
|
|
" \"\"\"React to order status events and simulate fills\"\"\"\n",
|
|
" if event.data[\"status_type\"] != \"submitted\":\n",
|
|
" return # Only process newly submitted orders\n",
|
|
" \n",
|
|
" order = event.data[\"order\"]\n",
|
|
" \n",
|
|
" # Simulate some processing delay\n",
|
|
" time.sleep(0.1)\n",
|
|
" \n",
|
|
" print(f\"🏪 {self.name}: Processing order {order.order_id[:8]}... - {order}\")\n",
|
|
" \n",
|
|
" # Simulate order execution based on type\n",
|
|
" if order.order_type == OrderType.MARKET:\n",
|
|
" self._fill_market_order(order)\n",
|
|
" elif order.order_type == OrderType.LIMIT:\n",
|
|
" self._process_limit_order(order)\n",
|
|
" elif order.order_type == OrderType.STOP:\n",
|
|
" self._process_stop_order(order)\n",
|
|
" \n",
|
|
" def _fill_market_order(self, order: Order):\n",
|
|
" \"\"\"Fill market order immediately at current price\"\"\"\n",
|
|
" if order.symbol not in self.market_prices:\n",
|
|
" error_event = OrderErrorEvent(order, \"invalid_symbol\", f\"Unknown symbol: {order.symbol}\", self.name)\n",
|
|
" self.event_queue.publish(error_event)\n",
|
|
" return\n",
|
|
" \n",
|
|
" # Market orders fill immediately at current market price with some slippage\n",
|
|
" market_price = self.market_prices[order.symbol]\n",
|
|
" slippage = random.uniform(-0.005, 0.005) # ±0.5% slippage\n",
|
|
" fill_price = market_price * (1 + slippage)\n",
|
|
" \n",
|
|
" # Calculate fees\n",
|
|
" fees = order.quantity * fill_price * self.trading_fee\n",
|
|
" \n",
|
|
" # Create fill event\n",
|
|
" fill_event = OrderFillEvent(order, order.quantity, fill_price, fees, self.name)\n",
|
|
" self.event_queue.publish(fill_event)\n",
|
|
" \n",
|
|
" print(f\" ✅ Market order filled: {order.quantity} @ ${fill_price:.2f} (fees: ${fees:.2f})\")\n",
|
|
" \n",
|
|
" def _process_limit_order(self, order: Order):\n",
|
|
" \"\"\"Process limit order - fill if price is favorable\"\"\"\n",
|
|
" if order.symbol not in self.market_prices:\n",
|
|
" error_event = OrderErrorEvent(order, \"invalid_symbol\", f\"Unknown symbol: {order.symbol}\", self.name)\n",
|
|
" self.event_queue.publish(error_event)\n",
|
|
" return\n",
|
|
" \n",
|
|
" market_price = self.market_prices[order.symbol]\n",
|
|
" \n",
|
|
" # Check if limit order can be filled immediately\n",
|
|
" can_fill = False\n",
|
|
" if order.is_buy and market_price <= order.price: # Buy limit can fill if market price is at or below limit\n",
|
|
" can_fill = True\n",
|
|
" elif order.is_sell and market_price >= order.price: # Sell limit can fill if market price is at or above limit\n",
|
|
" can_fill = True\n",
|
|
" \n",
|
|
" if can_fill and random.random() < self.fill_probability:\n",
|
|
" # Fill at the limit price (or better)\n",
|
|
" fill_price = min(order.price, market_price) if order.is_buy else max(order.price, market_price)\n",
|
|
" fees = order.quantity * fill_price * self.trading_fee\n",
|
|
" \n",
|
|
" fill_event = OrderFillEvent(order, order.quantity, fill_price, fees, self.name)\n",
|
|
" self.event_queue.publish(fill_event)\n",
|
|
" \n",
|
|
" print(f\" ✅ Limit order filled: {order.quantity} @ ${fill_price:.2f} (limit: ${order.price:.2f})\")\n",
|
|
" else:\n",
|
|
" # Order remains open on the book\n",
|
|
" order.state = OrderState.OPEN\n",
|
|
" print(f\" 📋 Limit order placed on book: {order.quantity} @ ${order.price:.2f} (market: ${market_price:.2f})\")\n",
|
|
" \n",
|
|
" def _process_stop_order(self, order: Order):\n",
|
|
" \"\"\"Process stop order - becomes market order when triggered\"\"\"\n",
|
|
" if order.symbol not in self.market_prices:\n",
|
|
" error_event = OrderErrorEvent(order, \"invalid_symbol\", f\"Unknown symbol: {order.symbol}\", self.name)\n",
|
|
" self.event_queue.publish(error_event)\n",
|
|
" return\n",
|
|
" \n",
|
|
" market_price = self.market_prices[order.symbol]\n",
|
|
" \n",
|
|
" # Check if stop is triggered\n",
|
|
" triggered = False\n",
|
|
" if order.is_buy and market_price >= order.stop_price: # Buy stop triggered when price goes up\n",
|
|
" triggered = True\n",
|
|
" elif order.is_sell and market_price <= order.stop_price: # Sell stop triggered when price goes down\n",
|
|
" triggered = True\n",
|
|
" \n",
|
|
" if triggered:\n",
|
|
" print(f\" 🚨 Stop order triggered at ${market_price:.2f} (stop: ${order.stop_price:.2f})\")\n",
|
|
" # Convert to market order and fill\n",
|
|
" slippage = random.uniform(-0.005, 0.005)\n",
|
|
" fill_price = market_price * (1 + slippage)\n",
|
|
" fees = order.quantity * fill_price * self.trading_fee\n",
|
|
" \n",
|
|
" fill_event = OrderFillEvent(order, order.quantity, fill_price, fees, self.name)\n",
|
|
" self.event_queue.publish(fill_event)\n",
|
|
" \n",
|
|
" print(f\" ✅ Stop order filled as market: {order.quantity} @ ${fill_price:.2f}\")\n",
|
|
" else:\n",
|
|
" order.state = OrderState.OPEN\n",
|
|
" print(f\" 📋 Stop order waiting: trigger @ ${order.stop_price:.2f} (market: ${market_price:.2f})\")\n",
|
|
"\n",
|
|
"# Create mock exchange\n",
|
|
"exchange = MockExchange(event_queue)\n",
|
|
"print(\"\\n🏪 Mock Exchange ready for testing!\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Part 6: Complete OMS Demo\n",
|
|
"\n",
|
|
"Let's test our complete Order Management System with different order types!"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"🎬 DEMO: Complete Order Management System\n",
|
|
"\n",
|
|
"======================================================================\n",
|
|
"\n",
|
|
"📊 Test 1: Market Order\n",
|
|
"------------------------------\n",
|
|
"📝 order_manager: Created order 5c53cc3e... - BUY 0.1 BTC/USDT @MARKET (PENDING)\n",
|
|
"📤 Published: order_status from order_manager\n",
|
|
"📥 order_manager: Received order request from demo\n"
|
|
]
|
|
},
|
|
{
|
|
"ename": "KeyboardInterrupt",
|
|
"evalue": "",
|
|
"output_type": "error",
|
|
"traceback": [
|
|
"\u001b[31m---------------------------------------------------------------------------\u001b[39m",
|
|
"\u001b[31mKeyboardInterrupt\u001b[39m Traceback (most recent call last)",
|
|
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[7]\u001b[39m\u001b[32m, line 11\u001b[39m\n\u001b[32m 8\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m market_order:\n\u001b[32m 9\u001b[39m \u001b[38;5;66;03m# Publish order request to trigger exchange processing\u001b[39;00m\n\u001b[32m 10\u001b[39m request_event = OrderRequestEvent(market_order, \u001b[33m\"\u001b[39m\u001b[33mdemo\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m---> \u001b[39m\u001b[32m11\u001b[39m \u001b[43mevent_queue\u001b[49m\u001b[43m.\u001b[49m\u001b[43mpublish\u001b[49m\u001b[43m(\u001b[49m\u001b[43mrequest_event\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 13\u001b[39m time.sleep(\u001b[32m1\u001b[39m) \u001b[38;5;66;03m# Allow processing\u001b[39;00m\n\u001b[32m 15\u001b[39m \u001b[38;5;66;03m# Test 2: Limit Order (should fill immediately)\u001b[39;00m\n",
|
|
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[1]\u001b[39m\u001b[32m, line 48\u001b[39m, in \u001b[36mEventQueue.publish\u001b[39m\u001b[34m(self, event)\u001b[39m\n\u001b[32m 46\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m callback \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m.subscribers[event.event_type]:\n\u001b[32m 47\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m---> \u001b[39m\u001b[32m48\u001b[39m \u001b[43mcallback\u001b[49m\u001b[43m(\u001b[49m\u001b[43mevent\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 49\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[32m 50\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33m❌ Error in callback for \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mevent.event_type\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00me\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m)\n",
|
|
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[5]\u001b[39m\u001b[32m, line 86\u001b[39m, in \u001b[36mOrderManager.on_order_request\u001b[39m\u001b[34m(self, event)\u001b[39m\n\u001b[32m 84\u001b[39m \u001b[38;5;66;03m# Publish status update\u001b[39;00m\n\u001b[32m 85\u001b[39m status_event = OrderStatusEvent(order, \u001b[33m\"\u001b[39m\u001b[33msubmitted\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33mOrder submitted to exchange\u001b[39m\u001b[33m\"\u001b[39m, \u001b[38;5;28mself\u001b[39m.name)\n\u001b[32m---> \u001b[39m\u001b[32m86\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mevent_queue\u001b[49m\u001b[43m.\u001b[49m\u001b[43mpublish\u001b[49m\u001b[43m(\u001b[49m\u001b[43mstatus_event\u001b[49m\u001b[43m)\u001b[49m\n",
|
|
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[1]\u001b[39m\u001b[32m, line 41\u001b[39m, in \u001b[36mEventQueue.publish\u001b[39m\u001b[34m(self, event)\u001b[39m\n\u001b[32m 40\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mpublish\u001b[39m(\u001b[38;5;28mself\u001b[39m, event: Event):\n\u001b[32m---> \u001b[39m\u001b[32m41\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mwith\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mlock\u001b[49m\u001b[43m:\u001b[49m\n\u001b[32m 42\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mevents\u001b[49m\u001b[43m.\u001b[49m\u001b[43mappend\u001b[49m\u001b[43m(\u001b[49m\u001b[43mevent\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 43\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mevent_history\u001b[49m\u001b[43m.\u001b[49m\u001b[43mappend\u001b[49m\u001b[43m(\u001b[49m\u001b[43mevent\u001b[49m\u001b[43m)\u001b[49m\n",
|
|
"\u001b[31mKeyboardInterrupt\u001b[39m: "
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"print(\"🎬 DEMO: Complete Order Management System\\n\")\n",
|
|
"print(\"=\" * 70)\n",
|
|
"\n",
|
|
"# Test 1: Market Order\n",
|
|
"print(\"\\n📊 Test 1: Market Order\")\n",
|
|
"print(\"-\" * 30)\n",
|
|
"market_order = oms.create_order(\"BTC/USDT\", OrderSide.BUY, OrderType.MARKET, 0.1)\n",
|
|
"if market_order:\n",
|
|
" # Publish order request to trigger exchange processing\n",
|
|
" request_event = OrderRequestEvent(market_order, \"demo\")\n",
|
|
" event_queue.publish(request_event)\n",
|
|
"\n",
|
|
"time.sleep(1) # Allow processing\n",
|
|
"\n",
|
|
"# Test 2: Limit Order (should fill immediately)\n",
|
|
"print(\"\\n📊 Test 2: Limit Order (Favorable Price)\")\n",
|
|
"print(\"-\" * 40)\n",
|
|
"current_btc_price = exchange.market_prices[\"BTC/USDT\"]\n",
|
|
"limit_order = oms.create_order(\"BTC/USDT\", OrderSide.BUY, OrderType.LIMIT, 0.05, price=current_btc_price + 100) # Above market\n",
|
|
"if limit_order:\n",
|
|
" request_event = OrderRequestEvent(limit_order, \"demo\")\n",
|
|
" event_queue.publish(request_event)\n",
|
|
"\n",
|
|
"time.sleep(1)\n",
|
|
"\n",
|
|
"# Test 3: Limit Order (won't fill)\n",
|
|
"print(\"\\n📊 Test 3: Limit Order (Too Low)\")\n",
|
|
"print(\"-\" * 30)\n",
|
|
"low_limit_order = oms.create_order(\"BTC/USDT\", OrderSide.BUY, OrderType.LIMIT, 0.08, price=current_btc_price - 1000) # Well below market\n",
|
|
"if low_limit_order:\n",
|
|
" request_event = OrderRequestEvent(low_limit_order, \"demo\")\n",
|
|
" event_queue.publish(request_event)\n",
|
|
"\n",
|
|
"time.sleep(1)\n",
|
|
"\n",
|
|
"# Test 4: Stop Order\n",
|
|
"print(\"\\n📊 Test 4: Stop Order\")\n",
|
|
"print(\"-\" * 20)\n",
|
|
"stop_order = oms.create_order(\"BTC/USDT\", OrderSide.SELL, OrderType.STOP, 0.03, stop_price=current_btc_price - 500)\n",
|
|
"if stop_order:\n",
|
|
" request_event = OrderRequestEvent(stop_order, \"demo\")\n",
|
|
" event_queue.publish(request_event)\n",
|
|
"\n",
|
|
"time.sleep(1)\n",
|
|
"\n",
|
|
"# Test 5: Price Movement to Trigger Stop\n",
|
|
"print(\"\\n📊 Test 5: Price Movement (Trigger Stop)\")\n",
|
|
"print(\"-\" * 35)\n",
|
|
"new_price = current_btc_price - 600 # Drop price to trigger stop\n",
|
|
"exchange.update_price(\"BTC/USDT\", new_price)\n",
|
|
"\n",
|
|
"# Re-process stop order with new price\n",
|
|
"if stop_order and stop_order.state == OrderState.OPEN:\n",
|
|
" print(f\"🔄 Re-checking stop order against new price...\")\n",
|
|
" exchange._process_stop_order(stop_order)\n",
|
|
"\n",
|
|
"time.sleep(1)\n",
|
|
"\n",
|
|
"# Test 6: Cancel an order\n",
|
|
"print(\"\\n📊 Test 6: Cancel Order\")\n",
|
|
"print(\"-\" * 20)\n",
|
|
"if low_limit_order and low_limit_order.is_open:\n",
|
|
" oms.cancel_order(low_limit_order.order_id)\n",
|
|
"\n",
|
|
"print(\"\\n\" + \"=\" * 70)\n",
|
|
"print(\"📈 Final OMS Statistics:\")\n",
|
|
"print(json.dumps(oms.get_stats(), indent=2))\n",
|
|
"\n",
|
|
"print(\"\\n📋 All Orders Summary:\")\n",
|
|
"for order in oms.orders.values():\n",
|
|
" print(f\" • {order.order_id[:8]}... - {order} - {order.fill_percentage:.1f}% filled\")\n",
|
|
"\n",
|
|
"print(f\"\\n📊 Total Events Processed: {len(event_queue.event_history)}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Part 7: Your Turn - Advanced Exercise\n",
|
|
"\n",
|
|
"**Challenge:** Extend the OMS with advanced features!\n",
|
|
"\n",
|
|
"Choose one or more of these challenges:\n",
|
|
"\n",
|
|
"1. **Order Modification**: Add ability to modify price/quantity of open orders\n",
|
|
"2. **Order Expiration**: Add time-based order expiration\n",
|
|
"3. **Bracket Orders**: Create orders with both profit target and stop loss\n",
|
|
"4. **Order Validation**: Add more sophisticated validation (min/max quantities, price bands)\n",
|
|
"5. **Partial Fill Handling**: Better handling of multiple partial fills"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Challenge 1: Order Modification\n",
|
|
"def modify_order_price(oms: OrderManager, order_id: str, new_price: float):\n",
|
|
" \"\"\"Modify the price of an open order\"\"\"\n",
|
|
" # YOUR CODE HERE\n",
|
|
" pass\n",
|
|
"\n",
|
|
"# Challenge 2: Order Expiration\n",
|
|
"from datetime import timedelta\n",
|
|
"\n",
|
|
"def add_expiration_to_order(order: Order, minutes: int):\n",
|
|
" \"\"\"Add expiration time to an order\"\"\"\n",
|
|
" # YOUR CODE HERE\n",
|
|
" pass\n",
|
|
"\n",
|
|
"def check_expired_orders(oms: OrderManager):\n",
|
|
" \"\"\"Check for and cancel expired orders\"\"\"\n",
|
|
" # YOUR CODE HERE\n",
|
|
" pass\n",
|
|
"\n",
|
|
"# Challenge 3: Bracket Order\n",
|
|
"def create_bracket_order(oms: OrderManager, symbol: str, side: OrderSide, quantity: float, \n",
|
|
" entry_price: float, profit_target: float, stop_loss: float):\n",
|
|
" \"\"\"Create a bracket order (entry + profit target + stop loss)\"\"\"\n",
|
|
" # YOUR CODE HERE\n",
|
|
" pass\n",
|
|
"\n",
|
|
"# Test your implementations here\n",
|
|
"print(\"🧪 Test your advanced features here!\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Congratulations!\n",
|
|
"\n",
|
|
"You've built a professional-grade Order Management System! Here's what you accomplished:\n",
|
|
"\n",
|
|
"✅ **Complete Order Types**: Market, Limit, and Stop orders with full lifecycle management \n",
|
|
"✅ **Event Integration**: OMS communicates through events for loose coupling \n",
|
|
"✅ **Order Tracking**: Complete state management from creation to completion \n",
|
|
"✅ **Exchange Simulation**: Realistic order processing and fills \n",
|
|
"✅ **Error Handling**: Proper validation and error events \n",
|
|
"✅ **Statistics & Monitoring**: Complete order tracking and reporting \n",
|
|
"\n",
|
|
"## Key Professional Features:\n",
|
|
"\n",
|
|
"1. **Order States**: Proper lifecycle management (PENDING → SUBMITTED → FILLED)\n",
|
|
"2. **Event-Driven**: Loose coupling through event system\n",
|
|
"3. **Fill Handling**: Partial and complete fills with average price calculation\n",
|
|
"4. **Order Validation**: Input validation and error handling\n",
|
|
"5. **Statistics**: Comprehensive tracking and reporting\n",
|
|
"\n",
|
|
"## Next Steps:\n",
|
|
"\n",
|
|
"In the next notebook, we'll build the **Portfolio Manager** and **Risk Controller** that work with this OMS to create a complete trading system!\n",
|
|
"\n",
|
|
"---\n",
|
|
"*\"Professional trading systems are built on robust order management.\"* 📋💰"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "venv",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.13.3"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
}
|