610 lines
27 KiB
Text
610 lines
27 KiB
Text
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Session 5: Event-Driven Trading Bot Foundation\n",
|
|
"\n",
|
|
"## Notebook 1: Event System Foundation\n",
|
|
"\n",
|
|
"**Learning Objectives:**\n",
|
|
"- Understand the Publisher-Subscriber pattern\n",
|
|
"- Build a basic event queue system\n",
|
|
"- Create event types for trading bots\n",
|
|
"- Test component communication through events\n",
|
|
"\n",
|
|
"**Why This Matters:**\n",
|
|
"Instead of components calling each other directly (tight coupling), they communicate through events (loose coupling). This makes your trading bot more professional, testable, and scalable.\n",
|
|
"\n",
|
|
"---"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Part 1: Basic Event System\n",
|
|
"\n",
|
|
"Let's start with the simplest possible event system - a basic Event class that carries information between components."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Basic Event:\n",
|
|
"Event(price_update from market_data_handler at 19:06:23)\n",
|
|
"\n",
|
|
"Event Data:\n",
|
|
"{\n",
|
|
" \"event_type\": \"price_update\",\n",
|
|
" \"data\": {\n",
|
|
" \"symbol\": \"BTC/USDT\",\n",
|
|
" \"price\": 43250.5\n",
|
|
" },\n",
|
|
" \"timestamp\": \"2025-06-25T19:06:23.130343\",\n",
|
|
" \"source\": \"market_data_handler\"\n",
|
|
"}\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"from datetime import datetime\n",
|
|
"from typing import Dict, Any, List, Callable\n",
|
|
"from dataclasses import dataclass\n",
|
|
"import json\n",
|
|
"\n",
|
|
"# Basic Event class - every message in our system\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",
|
|
" def to_dict(self) -> Dict[str, Any]:\n",
|
|
" \"\"\"Convert event to dictionary for logging/debugging\"\"\"\n",
|
|
" return {\n",
|
|
" 'event_type': self.event_type,\n",
|
|
" 'data': self.data,\n",
|
|
" 'timestamp': self.timestamp.isoformat(),\n",
|
|
" 'source': self.source\n",
|
|
" }\n",
|
|
" \n",
|
|
" def __str__(self):\n",
|
|
" return f\"Event({self.event_type} from {self.source} at {self.timestamp.strftime('%H:%M:%S')})\"\n",
|
|
"\n",
|
|
"# Test our basic Event\n",
|
|
"test_event = Event(\n",
|
|
" event_type=\"price_update\",\n",
|
|
" data={\"symbol\": \"BTC/USDT\", \"price\": 43250.50},\n",
|
|
" source=\"market_data_handler\"\n",
|
|
")\n",
|
|
"\n",
|
|
"print(\"Basic Event:\")\n",
|
|
"print(test_event)\n",
|
|
"print(\"\\nEvent Data:\")\n",
|
|
"print(json.dumps(test_event.to_dict(), indent=2, default=str))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Part 2: Event Queue (Message Bus)\n",
|
|
"\n",
|
|
"Now we need a central place where all events go - the Event Queue. This is the \"post office\" of our trading bot."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"🏗️ Event Queue created!\n",
|
|
"Initial stats: {'events_in_queue': 0, 'total_events_processed': 0, 'event_types_subscribed': [], 'subscribers_count': {}}\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"from collections import deque\n",
|
|
"from threading import Lock\n",
|
|
"\n",
|
|
"class EventQueue:\n",
|
|
" \"\"\"Central event queue - the heart of our event-driven system\"\"\"\n",
|
|
" \n",
|
|
" def __init__(self):\n",
|
|
" self.events = deque() # Fast append/pop from both ends\n",
|
|
" self.subscribers = {} # {event_type: [callback_functions]}\n",
|
|
" self.lock = Lock() # Thread safety for production\n",
|
|
" self.event_history = [] # Keep history for debugging\n",
|
|
" \n",
|
|
" def subscribe(self, event_type: str, callback: Callable[[Event], None]):\n",
|
|
" \"\"\"Subscribe to specific event types\"\"\"\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",
|
|
" \"\"\"Publish an event to all subscribers\"\"\"\n",
|
|
" with self.lock:\n",
|
|
" # Add to queue\n",
|
|
" self.events.append(event)\n",
|
|
" self.event_history.append(event)\n",
|
|
" \n",
|
|
" # Notify subscribers immediately\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}\")\n",
|
|
" \n",
|
|
" def get_next_event(self) -> Event:\n",
|
|
" \"\"\"Get next event from queue (useful for batch processing)\"\"\"\n",
|
|
" with self.lock:\n",
|
|
" if self.events:\n",
|
|
" return self.events.popleft()\n",
|
|
" return None\n",
|
|
" \n",
|
|
" def get_stats(self) -> Dict[str, Any]:\n",
|
|
" \"\"\"Get queue statistics\"\"\"\n",
|
|
" with self.lock:\n",
|
|
" return {\n",
|
|
" 'events_in_queue': len(self.events),\n",
|
|
" 'total_events_processed': len(self.event_history),\n",
|
|
" 'event_types_subscribed': list(self.subscribers.keys()),\n",
|
|
" 'subscribers_count': {k: len(v) for k, v in self.subscribers.items()}\n",
|
|
" }\n",
|
|
"\n",
|
|
"# Create our central event queue\n",
|
|
"event_queue = EventQueue()\n",
|
|
"print(\"🏗️ Event Queue created!\")\n",
|
|
"print(f\"Initial stats: {event_queue.get_stats()}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Part 3: Trading Event Types\n",
|
|
"\n",
|
|
"Let's create specific event types that our MARKET components will use to communicate."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"🧪 Testing Trading Event Types:\n",
|
|
"\n",
|
|
"Market Event: Event(market_data from market_data at 19:06:23)\n",
|
|
"Signal Event: Event(signal from algorithm at 19:06:23)\n",
|
|
"Order Event: Event(order from order_manager at 19:06:23)\n",
|
|
"Risk Event: Event(risk from risk_controller at 19:06:23)\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"# Specific event types for our trading system\n",
|
|
"\n",
|
|
"class MarketDataEvent(Event):\n",
|
|
" \"\"\"Market data events - price updates, order book changes\"\"\"\n",
|
|
" def __init__(self, symbol: str, price: float, volume: float = 0, source: str = \"market_data\"):\n",
|
|
" super().__init__(\n",
|
|
" event_type=\"market_data\",\n",
|
|
" data={\n",
|
|
" \"symbol\": symbol,\n",
|
|
" \"price\": price,\n",
|
|
" \"volume\": volume\n",
|
|
" },\n",
|
|
" source=source\n",
|
|
" )\n",
|
|
"\n",
|
|
"class SignalEvent(Event):\n",
|
|
" \"\"\"Trading signal events - buy/sell signals from algorithm\"\"\"\n",
|
|
" def __init__(self, symbol: str, signal_type: str, strength: float, source: str = \"algorithm\"):\n",
|
|
" super().__init__(\n",
|
|
" event_type=\"signal\",\n",
|
|
" data={\n",
|
|
" \"symbol\": symbol,\n",
|
|
" \"signal_type\": signal_type, # 'BUY', 'SELL', 'HOLD'\n",
|
|
" \"strength\": strength # 0.0 to 1.0\n",
|
|
" },\n",
|
|
" source=source\n",
|
|
" )\n",
|
|
"\n",
|
|
"class OrderEvent(Event):\n",
|
|
" \"\"\"Order events - order requests, fills, cancellations\"\"\"\n",
|
|
" def __init__(self, symbol: str, order_type: str, quantity: float, price: float = None, source: str = \"order_manager\"):\n",
|
|
" super().__init__(\n",
|
|
" event_type=\"order\",\n",
|
|
" data={\n",
|
|
" \"symbol\": symbol,\n",
|
|
" \"order_type\": order_type, # 'MARKET', 'LIMIT', 'STOP'\n",
|
|
" \"quantity\": quantity,\n",
|
|
" \"price\": price\n",
|
|
" },\n",
|
|
" source=source\n",
|
|
" )\n",
|
|
"\n",
|
|
"class RiskEvent(Event):\n",
|
|
" \"\"\"Risk management events - warnings, violations, limits\"\"\"\n",
|
|
" def __init__(self, risk_type: str, message: str, severity: str = \"WARNING\", source: str = \"risk_controller\"):\n",
|
|
" super().__init__(\n",
|
|
" event_type=\"risk\",\n",
|
|
" data={\n",
|
|
" \"risk_type\": risk_type,\n",
|
|
" \"message\": message,\n",
|
|
" \"severity\": severity # 'INFO', 'WARNING', 'ERROR', 'CRITICAL'\n",
|
|
" },\n",
|
|
" source=source\n",
|
|
" )\n",
|
|
"\n",
|
|
"# Test our trading event types\n",
|
|
"print(\"🧪 Testing Trading Event Types:\\n\")\n",
|
|
"\n",
|
|
"# Market data event\n",
|
|
"market_event = MarketDataEvent(\"BTC/USDT\", 43250.50, 1.25)\n",
|
|
"print(f\"Market Event: {market_event}\")\n",
|
|
"\n",
|
|
"# Signal event\n",
|
|
"signal_event = SignalEvent(\"BTC/USDT\", \"BUY\", 0.8)\n",
|
|
"print(f\"Signal Event: {signal_event}\")\n",
|
|
"\n",
|
|
"# Order event\n",
|
|
"order_event = OrderEvent(\"BTC/USDT\", \"MARKET\", 0.1)\n",
|
|
"print(f\"Order Event: {order_event}\")\n",
|
|
"\n",
|
|
"# Risk event\n",
|
|
"risk_event = RiskEvent(\"position_limit\", \"Position size exceeds 10% of portfolio\", \"WARNING\")\n",
|
|
"print(f\"Risk Event: {risk_event}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Part 4: Simple Component Example\n",
|
|
"\n",
|
|
"Let's create two simple components that communicate through events - this demonstrates the publisher-subscriber pattern."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"🏗️ Creating Components...\n",
|
|
"\n",
|
|
"✅ Subscribed to 'market_data' events\n",
|
|
"✅ Subscribed to 'signal' events\n",
|
|
"\n",
|
|
"📊 Current Event Queue Stats:\n",
|
|
"{\n",
|
|
" \"events_in_queue\": 0,\n",
|
|
" \"total_events_processed\": 0,\n",
|
|
" \"event_types_subscribed\": [\n",
|
|
" \"market_data\",\n",
|
|
" \"signal\"\n",
|
|
" ],\n",
|
|
" \"subscribers_count\": {\n",
|
|
" \"market_data\": 1,\n",
|
|
" \"signal\": 1\n",
|
|
" }\n",
|
|
"}\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"class SimpleMarketDataHandler:\n",
|
|
" \"\"\"Simulates receiving market data and publishing events\"\"\"\n",
|
|
" \n",
|
|
" def __init__(self, event_queue: EventQueue):\n",
|
|
" self.event_queue = event_queue\n",
|
|
" self.name = \"market_data_handler\"\n",
|
|
" \n",
|
|
" def simulate_price_update(self, symbol: str, price: float):\n",
|
|
" \"\"\"Simulate receiving a price update from exchange\"\"\"\n",
|
|
" print(f\"📊 {self.name}: Received price update for {symbol}: ${price:,.2f}\")\n",
|
|
" \n",
|
|
" # Create and publish market data event\n",
|
|
" event = MarketDataEvent(symbol, price, source=self.name)\n",
|
|
" self.event_queue.publish(event)\n",
|
|
"\n",
|
|
"\n",
|
|
"class SimpleAlgorithm:\n",
|
|
" \"\"\"Simple algorithm that listens to market data and generates signals\"\"\"\n",
|
|
" \n",
|
|
" def __init__(self, event_queue: EventQueue):\n",
|
|
" self.event_queue = event_queue\n",
|
|
" self.name = \"simple_algorithm\"\n",
|
|
" self.last_price = None\n",
|
|
" \n",
|
|
" # Subscribe to market data events\n",
|
|
" self.event_queue.subscribe(\"market_data\", self.on_market_data)\n",
|
|
" \n",
|
|
" def on_market_data(self, event: Event):\n",
|
|
" \"\"\"Handle market data events\"\"\"\n",
|
|
" symbol = event.data[\"symbol\"]\n",
|
|
" price = event.data[\"price\"]\n",
|
|
" \n",
|
|
" print(f\"🤖 {self.name}: Processing {symbol} price: ${price:,.2f}\")\n",
|
|
" \n",
|
|
" # Simple momentum strategy\n",
|
|
" if self.last_price is not None:\n",
|
|
" price_change = (price - self.last_price) / self.last_price\n",
|
|
" \n",
|
|
" if price_change > 0.01: # 1% increase\n",
|
|
" signal = SignalEvent(symbol, \"BUY\", 0.7, source=self.name)\n",
|
|
" self.event_queue.publish(signal)\n",
|
|
" print(f\" 📈 Generated BUY signal (price up {price_change:.2%})\")\n",
|
|
" \n",
|
|
" elif price_change < -0.01: # 1% decrease\n",
|
|
" signal = SignalEvent(symbol, \"SELL\", 0.7, source=self.name)\n",
|
|
" self.event_queue.publish(signal)\n",
|
|
" print(f\" 📉 Generated SELL signal (price down {price_change:.2%})\")\n",
|
|
" \n",
|
|
" self.last_price = price\n",
|
|
"\n",
|
|
"\n",
|
|
"class SimpleRiskController:\n",
|
|
" \"\"\"Simple risk controller that monitors signals\"\"\"\n",
|
|
" \n",
|
|
" def __init__(self, event_queue: EventQueue):\n",
|
|
" self.event_queue = event_queue\n",
|
|
" self.name = \"risk_controller\"\n",
|
|
" \n",
|
|
" # Subscribe to signal events\n",
|
|
" self.event_queue.subscribe(\"signal\", self.on_signal)\n",
|
|
" \n",
|
|
" def on_signal(self, event: Event):\n",
|
|
" \"\"\"Validate trading signals\"\"\"\n",
|
|
" symbol = event.data[\"symbol\"]\n",
|
|
" signal_type = event.data[\"signal_type\"]\n",
|
|
" strength = event.data[\"strength\"]\n",
|
|
" \n",
|
|
" print(f\"🛡️ {self.name}: Validating {signal_type} signal for {symbol} (strength: {strength})\")\n",
|
|
" \n",
|
|
" # Simple validation - only allow strong signals\n",
|
|
" if strength >= 0.6:\n",
|
|
" print(f\" ✅ Signal APPROVED - strength sufficient ({strength})\")\n",
|
|
" # In real system, would publish order_request event here\n",
|
|
" else:\n",
|
|
" risk_event = RiskEvent(\n",
|
|
" \"weak_signal\", \n",
|
|
" f\"Signal strength too low: {strength} < 0.6\", \n",
|
|
" \"WARNING\",\n",
|
|
" source=self.name\n",
|
|
" )\n",
|
|
" self.event_queue.publish(risk_event)\n",
|
|
" print(f\" ❌ Signal REJECTED - strength too low\")\n",
|
|
"\n",
|
|
"\n",
|
|
"# Create our components\n",
|
|
"print(\"🏗️ Creating Components...\\n\")\n",
|
|
"\n",
|
|
"market_handler = SimpleMarketDataHandler(event_queue)\n",
|
|
"algorithm = SimpleAlgorithm(event_queue)\n",
|
|
"risk_controller = SimpleRiskController(event_queue)\n",
|
|
"\n",
|
|
"print(\"\\n📊 Current Event Queue Stats:\")\n",
|
|
"print(json.dumps(event_queue.get_stats(), indent=2))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Part 5: Event System in Action\n",
|
|
"\n",
|
|
"Now let's test our event-driven system by simulating market data updates!"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"🎬 DEMO: Event-Driven Trading Bot in Action!\n",
|
|
"\n",
|
|
"============================================================\n",
|
|
"\n",
|
|
"📡 Market Update #1:\n",
|
|
"----------------------------------------\n",
|
|
"📊 market_data_handler: Received price update for BTC/USDT: $43,000.00\n",
|
|
"🤖 simple_algorithm: Processing BTC/USDT price: $43,000.00\n",
|
|
"📤 Published: Event(market_data from market_data_handler at 19:06:23)\n",
|
|
"\n",
|
|
"📡 Market Update #2:\n",
|
|
"----------------------------------------\n",
|
|
"📊 market_data_handler: Received price update for BTC/USDT: $43,450.00\n",
|
|
"🤖 simple_algorithm: Processing BTC/USDT price: $43,450.00\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[5]\u001b[39m\u001b[32m, line 20\u001b[39m\n\u001b[32m 17\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33m\"\u001b[39m\u001b[33m-\u001b[39m\u001b[33m\"\u001b[39m * \u001b[32m40\u001b[39m)\n\u001b[32m 19\u001b[39m \u001b[38;5;66;03m# This will trigger the entire event chain!\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m20\u001b[39m \u001b[43mmarket_handler\u001b[49m\u001b[43m.\u001b[49m\u001b[43msimulate_price_update\u001b[49m\u001b[43m(\u001b[49m\u001b[43msymbol\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mprice\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 22\u001b[39m \u001b[38;5;66;03m# Small delay to make it readable\u001b[39;00m\n\u001b[32m 23\u001b[39m time.sleep(\u001b[32m0.5\u001b[39m)\n",
|
|
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[4]\u001b[39m\u001b[32m, line 14\u001b[39m, in \u001b[36mSimpleMarketDataHandler.simulate_price_update\u001b[39m\u001b[34m(self, symbol, price)\u001b[39m\n\u001b[32m 12\u001b[39m \u001b[38;5;66;03m# Create and publish market data event\u001b[39;00m\n\u001b[32m 13\u001b[39m event = MarketDataEvent(symbol, price, source=\u001b[38;5;28mself\u001b[39m.name)\n\u001b[32m---> \u001b[39m\u001b[32m14\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[43mevent\u001b[49m\u001b[43m)\u001b[49m\n",
|
|
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[2]\u001b[39m\u001b[32m, line 32\u001b[39m, in \u001b[36mEventQueue.publish\u001b[39m\u001b[34m(self, event)\u001b[39m\n\u001b[32m 30\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 31\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m---> \u001b[39m\u001b[32m32\u001b[39m \u001b[43mcallback\u001b[49m\u001b[43m(\u001b[49m\u001b[43mevent\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 33\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 34\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[4]\u001b[39m\u001b[32m, line 41\u001b[39m, in \u001b[36mSimpleAlgorithm.on_market_data\u001b[39m\u001b[34m(self, event)\u001b[39m\n\u001b[32m 39\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m price_change > \u001b[32m0.01\u001b[39m: \u001b[38;5;66;03m# 1% increase\u001b[39;00m\n\u001b[32m 40\u001b[39m signal = SignalEvent(symbol, \u001b[33m\"\u001b[39m\u001b[33mBUY\u001b[39m\u001b[33m\"\u001b[39m, \u001b[32m0.7\u001b[39m, source=\u001b[38;5;28mself\u001b[39m.name)\n\u001b[32m---> \u001b[39m\u001b[32m41\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[43msignal\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 42\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33m 📈 Generated BUY signal (price up \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mprice_change\u001b[38;5;132;01m:\u001b[39;00m\u001b[33m.2%\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m)\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m 44\u001b[39m \u001b[38;5;28;01melif\u001b[39;00m price_change < -\u001b[32m0.01\u001b[39m: \u001b[38;5;66;03m# 1% decrease\u001b[39;00m\n",
|
|
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[2]\u001b[39m\u001b[32m, line 23\u001b[39m, in \u001b[36mEventQueue.publish\u001b[39m\u001b[34m(self, event)\u001b[39m\n\u001b[32m 21\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 22\u001b[39m \u001b[38;5;250m \u001b[39m\u001b[33;03m\"\"\"Publish an event to all subscribers\"\"\"\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m23\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 24\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# Add to queue\u001b[39;49;00m\n\u001b[32m 25\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 26\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": [
|
|
"import time\n",
|
|
"\n",
|
|
"print(\"🎬 DEMO: Event-Driven Trading Bot in Action!\\n\")\n",
|
|
"print(\"=\" * 60)\n",
|
|
"\n",
|
|
"# Simulate market data updates\n",
|
|
"test_prices = [\n",
|
|
" (\"BTC/USDT\", 43000.00), # Starting price\n",
|
|
" (\"BTC/USDT\", 43450.00), # 1.05% increase -> should trigger BUY\n",
|
|
" (\"BTC/USDT\", 43200.00), # -0.58% change -> no signal\n",
|
|
" (\"BTC/USDT\", 42750.00), # -1.04% decrease -> should trigger SELL\n",
|
|
" (\"BTC/USDT\", 43100.00), # 0.82% increase -> no signal\n",
|
|
"]\n",
|
|
"\n",
|
|
"for i, (symbol, price) in enumerate(test_prices, 1):\n",
|
|
" print(f\"\\n📡 Market Update #{i}:\")\n",
|
|
" print(\"-\" * 40)\n",
|
|
" \n",
|
|
" # This will trigger the entire event chain!\n",
|
|
" market_handler.simulate_price_update(symbol, price)\n",
|
|
" \n",
|
|
" # Small delay to make it readable\n",
|
|
" time.sleep(0.5)\n",
|
|
"\n",
|
|
"print(\"\\n\" + \"=\" * 60)\n",
|
|
"print(\"📈 Final Event Queue Statistics:\")\n",
|
|
"print(json.dumps(event_queue.get_stats(), indent=2))\n",
|
|
"\n",
|
|
"print(f\"\\n📋 Total Events Processed: {len(event_queue.event_history)}\")\n",
|
|
"print(\"Event Types:\")\n",
|
|
"event_types = {}\n",
|
|
"for event in event_queue.event_history:\n",
|
|
" event_types[event.event_type] = event_types.get(event.event_type, 0) + 1\n",
|
|
"\n",
|
|
"for event_type, count in event_types.items():\n",
|
|
" print(f\" • {event_type}: {count} events\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Part 6: Your Turn - Practice Exercise\n",
|
|
"\n",
|
|
"**Challenge:** Create a simple `PortfolioTracker` component that:\n",
|
|
"1. Subscribes to `order` events\n",
|
|
"2. Keeps track of positions for each symbol\n",
|
|
"3. Publishes `portfolio_update` events when positions change\n",
|
|
"\n",
|
|
"**Hint:** You'll need to create a `PortfolioEvent` class first!"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# TODO: Create PortfolioEvent class\n",
|
|
"class PortfolioEvent(Event):\n",
|
|
" \"\"\"Portfolio update events\"\"\"\n",
|
|
" def __init__(self, symbol: str, position: float, avg_price: float, unrealized_pnl: float, source: str = \"portfolio_tracker\"):\n",
|
|
" # YOUR CODE HERE\n",
|
|
" pass\n",
|
|
"\n",
|
|
"# TODO: Create PortfolioTracker class\n",
|
|
"class PortfolioTracker:\n",
|
|
" \"\"\"Tracks portfolio positions and P&L\"\"\"\n",
|
|
" \n",
|
|
" def __init__(self, event_queue: EventQueue):\n",
|
|
" # YOUR CODE HERE\n",
|
|
" pass\n",
|
|
" \n",
|
|
" def on_order(self, event: Event):\n",
|
|
" \"\"\"Handle order events and update positions\"\"\"\n",
|
|
" # YOUR CODE HERE\n",
|
|
" pass\n",
|
|
"\n",
|
|
"# Test your implementation\n",
|
|
"# portfolio_tracker = PortfolioTracker(event_queue)\n",
|
|
"\n",
|
|
"# # Simulate some orders\n",
|
|
"# test_order = OrderEvent(\"BTC/USDT\", \"MARKET\", 0.1, 43000, source=\"test\")\n",
|
|
"# event_queue.publish(test_order)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Congratulations!\n",
|
|
"\n",
|
|
"You've just built the foundation of a professional event-driven trading system! Here's what you accomplished:\n",
|
|
"\n",
|
|
"✅ **Event System**: Created a robust event queue with publisher-subscriber pattern \n",
|
|
"✅ **Trading Events**: Built specific event types for market data, signals, orders, and risk \n",
|
|
"✅ **Component Communication**: Made components talk through events (loose coupling) \n",
|
|
"✅ **Real Demo**: Saw the entire chain work from market data → algorithm → risk control \n",
|
|
"\n",
|
|
"## Key Takeaways:\n",
|
|
"\n",
|
|
"1. **Loose Coupling**: Components don't know about each other - they only know about events\n",
|
|
"2. **Scalability**: Easy to add new components by subscribing to events\n",
|
|
"3. **Testability**: Each component can be tested independently\n",
|
|
"4. **Professional**: This is how real trading firms build their systems\n",
|
|
"\n",
|
|
"## Next Steps:\n",
|
|
"\n",
|
|
"In the next notebook, we'll build a complete **Order Management System** that uses this event foundation to handle market, limit, and stop orders professionally!\n",
|
|
"\n",
|
|
"---\n",
|
|
"*\"The best trading bots are not just profitable - they're architecturally sound.\"* 🏗️💰"
|
|
]
|
|
}
|
|
],
|
|
"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
|
|
}
|