1
Fork 0
crypto_bot_training/Session_05/02_order_management_system.ipynb
2025-06-26 15:46:49 +02:00

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
}