990 lines
43 KiB
Text
990 lines
43 KiB
Text
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"# Session 5: Event-Driven Trading Bot\n",
|
||
"\n",
|
||
"## Notebook 3: Portfolio Management & Risk Controls\n",
|
||
"\n",
|
||
"**Learning Objectives:**\n",
|
||
"- Build a real-time Portfolio Manager (the \"Keeper\")\n",
|
||
"- Implement comprehensive Risk Controls\n",
|
||
"- Track positions, balances, and P&L in real-time\n",
|
||
"- Apply risk management rules (position sizing, daily limits)\n",
|
||
"- Integrate portfolio and risk systems with OMS\n",
|
||
"\n",
|
||
"**Why This Matters:**\n",
|
||
"The Portfolio Manager is your \"financial brain\" - it knows exactly what you own, what it's worth, and how much you've made or lost. Risk Controls are your \"safety net\" - they prevent catastrophic losses by enforcing rules. Together, they keep your trading bot profitable and safe.\n",
|
||
"\n",
|
||
"---"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Quick Setup: Import Previous Systems\n",
|
||
"\n",
|
||
"Let's import the event system and OMS from our previous notebooks:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"# Import our systems from previous notebooks\n",
|
||
"from datetime import datetime, timedelta\n",
|
||
"from typing import Dict, Any, List, Callable, Optional\n",
|
||
"from dataclasses import dataclass, field\n",
|
||
"from collections import deque, defaultdict\n",
|
||
"from threading import Lock\n",
|
||
"from enum import Enum\n",
|
||
"import json\n",
|
||
"import uuid\n",
|
||
"import time\n",
|
||
"\n",
|
||
"# Event System (from Notebook 1)\n",
|
||
"@dataclass\n",
|
||
"class Event:\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",
|
||
" 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",
|
||
" \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",
|
||
"# Order System (from Notebook 2) - simplified\n",
|
||
"class OrderState(Enum):\n",
|
||
" PENDING = \"PENDING\"\n",
|
||
" FILLED = \"FILLED\"\n",
|
||
" CANCELLED = \"CANCELLED\"\n",
|
||
"\n",
|
||
"class OrderSide(Enum):\n",
|
||
" BUY = \"BUY\"\n",
|
||
" SELL = \"SELL\"\n",
|
||
"\n",
|
||
"@dataclass\n",
|
||
"class Order:\n",
|
||
" symbol: str\n",
|
||
" side: OrderSide\n",
|
||
" quantity: float\n",
|
||
" price: float\n",
|
||
" order_id: str = field(default_factory=lambda: str(uuid.uuid4()))\n",
|
||
" state: OrderState = OrderState.PENDING\n",
|
||
" filled_quantity: float = 0.0\n",
|
||
" average_fill_price: float = 0.0\n",
|
||
" fees_paid: float = 0.0\n",
|
||
" created_at: datetime = field(default_factory=datetime.now)\n",
|
||
"\n",
|
||
"# Create our event queue\n",
|
||
"event_queue = EventQueue()\n",
|
||
"print(\"🏗️ Event system and Order classes ready for Portfolio & Risk integration!\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Part 1: Position and Portfolio Data Structures\n",
|
||
"\n",
|
||
"Let's create the core data structures to track positions and portfolio state:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@dataclass\n",
|
||
"class Position:\n",
|
||
" \"\"\"Represents a position in a specific symbol\"\"\"\n",
|
||
" symbol: str\n",
|
||
" quantity: float = 0.0 # Current position size (+ for long, - for short)\n",
|
||
" average_price: float = 0.0 # Average cost basis\n",
|
||
" market_price: float = 0.0 # Current market price\n",
|
||
" total_cost: float = 0.0 # Total amount invested\n",
|
||
" realized_pnl: float = 0.0 # Profit/Loss from closed positions\n",
|
||
" fees_paid: float = 0.0 # Total fees paid\n",
|
||
" last_updated: datetime = field(default_factory=datetime.now)\n",
|
||
" \n",
|
||
" @property\n",
|
||
" def market_value(self) -> float:\n",
|
||
" \"\"\"Current market value of position\"\"\"\n",
|
||
" return abs(self.quantity) * self.market_price\n",
|
||
" \n",
|
||
" @property\n",
|
||
" def unrealized_pnl(self) -> float:\n",
|
||
" \"\"\"Unrealized profit/loss\"\"\"\n",
|
||
" if self.quantity == 0:\n",
|
||
" return 0.0\n",
|
||
" \n",
|
||
" if self.quantity > 0: # Long position\n",
|
||
" return (self.market_price - self.average_price) * self.quantity\n",
|
||
" else: # Short position\n",
|
||
" return (self.average_price - self.market_price) * abs(self.quantity)\n",
|
||
" \n",
|
||
" @property\n",
|
||
" def total_pnl(self) -> float:\n",
|
||
" \"\"\"Total P&L (realized + unrealized)\"\"\"\n",
|
||
" return self.realized_pnl + self.unrealized_pnl\n",
|
||
" \n",
|
||
" @property\n",
|
||
" def pnl_percentage(self) -> float:\n",
|
||
" \"\"\"P&L as percentage of invested capital\"\"\"\n",
|
||
" if self.total_cost == 0:\n",
|
||
" return 0.0\n",
|
||
" return (self.total_pnl / self.total_cost) * 100\n",
|
||
" \n",
|
||
" def update_market_price(self, new_price: float):\n",
|
||
" \"\"\"Update current market price\"\"\"\n",
|
||
" self.market_price = new_price\n",
|
||
" self.last_updated = datetime.now()\n",
|
||
" \n",
|
||
" def add_trade(self, quantity: float, price: float, fees: float = 0.0):\n",
|
||
" \"\"\"Add a trade to this position\"\"\"\n",
|
||
" trade_value = quantity * price\n",
|
||
" \n",
|
||
" if self.quantity == 0:\n",
|
||
" # Opening new position\n",
|
||
" self.quantity = quantity\n",
|
||
" self.average_price = price\n",
|
||
" self.total_cost = abs(trade_value)\n",
|
||
" else:\n",
|
||
" # Modifying existing position\n",
|
||
" if (self.quantity > 0 and quantity > 0) or (self.quantity < 0 and quantity < 0):\n",
|
||
" # Adding to position\n",
|
||
" total_value = (self.quantity * self.average_price) + trade_value\n",
|
||
" self.quantity += quantity\n",
|
||
" self.average_price = total_value / self.quantity if self.quantity != 0 else 0\n",
|
||
" self.total_cost += abs(trade_value)\n",
|
||
" else:\n",
|
||
" # Reducing or closing position\n",
|
||
" if abs(quantity) >= abs(self.quantity):\n",
|
||
" # Closing position completely\n",
|
||
" self.realized_pnl += (price - self.average_price) * self.quantity\n",
|
||
" self.quantity = 0\n",
|
||
" self.average_price = 0\n",
|
||
" else:\n",
|
||
" # Partial close\n",
|
||
" close_pnl = (price - self.average_price) * abs(quantity)\n",
|
||
" self.realized_pnl += close_pnl\n",
|
||
" self.quantity += quantity # quantity is negative for close\n",
|
||
" \n",
|
||
" self.fees_paid += fees\n",
|
||
" self.last_updated = datetime.now()\n",
|
||
" \n",
|
||
" def to_dict(self) -> Dict[str, Any]:\n",
|
||
" return {\n",
|
||
" 'symbol': self.symbol,\n",
|
||
" 'quantity': self.quantity,\n",
|
||
" 'average_price': self.average_price,\n",
|
||
" 'market_price': self.market_price,\n",
|
||
" 'market_value': self.market_value,\n",
|
||
" 'total_cost': self.total_cost,\n",
|
||
" 'unrealized_pnl': self.unrealized_pnl,\n",
|
||
" 'realized_pnl': self.realized_pnl,\n",
|
||
" 'total_pnl': self.total_pnl,\n",
|
||
" 'pnl_percentage': f\"{self.pnl_percentage:.2f}%\",\n",
|
||
" 'fees_paid': self.fees_paid\n",
|
||
" }\n",
|
||
" \n",
|
||
" def __str__(self):\n",
|
||
" direction = \"LONG\" if self.quantity > 0 else \"SHORT\" if self.quantity < 0 else \"FLAT\"\n",
|
||
" return f\"{direction} {abs(self.quantity):.4f} {self.symbol} @ ${self.average_price:.2f} (P&L: ${self.total_pnl:.2f})\"\n",
|
||
"\n",
|
||
"\n",
|
||
"# Test Position class\n",
|
||
"print(\"🧪 Testing Position class:\\n\")\n",
|
||
"\n",
|
||
"btc_position = Position(\"BTC/USDT\")\n",
|
||
"print(f\"New position: {btc_position}\")\n",
|
||
"\n",
|
||
"# Add a buy trade\n",
|
||
"btc_position.add_trade(0.1, 43000, 4.30) # Buy 0.1 BTC at $43,000\n",
|
||
"btc_position.update_market_price(43500) # Market moves up\n",
|
||
"print(f\"After buy: {btc_position}\")\n",
|
||
"print(f\"Position details: {json.dumps(btc_position.to_dict(), indent=2, default=str)}\")\n",
|
||
"\n",
|
||
"# Partial sell\n",
|
||
"btc_position.add_trade(-0.03, 43600, 1.31) # Sell 0.03 BTC at $43,600\n",
|
||
"print(f\"\\nAfter partial sell: {btc_position}\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Part 2: Portfolio Manager (The \"Keeper\")\n",
|
||
"\n",
|
||
"Now let's build the Portfolio Manager that tracks all positions and overall account health:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"class PortfolioManager:\n",
|
||
" \"\"\"The Keeper - manages all positions and portfolio state\"\"\"\n",
|
||
" \n",
|
||
" def __init__(self, event_queue: EventQueue, initial_balance: float = 100000.0):\n",
|
||
" self.event_queue = event_queue\n",
|
||
" self.name = \"portfolio_manager\"\n",
|
||
" \n",
|
||
" # Portfolio state\n",
|
||
" self.positions: Dict[str, Position] = {} # symbol -> Position\n",
|
||
" self.cash_balance = initial_balance\n",
|
||
" self.initial_balance = initial_balance\n",
|
||
" \n",
|
||
" # Portfolio tracking\n",
|
||
" self.total_trades = 0\n",
|
||
" self.total_fees_paid = 0.0\n",
|
||
" self.daily_pnl = 0.0\n",
|
||
" self.max_drawdown = 0.0\n",
|
||
" self.peak_value = initial_balance\n",
|
||
" \n",
|
||
" # Market prices (for position valuation)\n",
|
||
" self.market_prices: Dict[str, float] = {}\n",
|
||
" \n",
|
||
" # Subscribe to relevant events\n",
|
||
" self.event_queue.subscribe(\"order_fill\", self.on_order_fill)\n",
|
||
" self.event_queue.subscribe(\"market_data\", self.on_market_data)\n",
|
||
" \n",
|
||
" print(f\"💼 {self.name}: Portfolio initialized with ${initial_balance:,.2f}\")\n",
|
||
" \n",
|
||
" def on_market_data(self, event: Event):\n",
|
||
" \"\"\"Update market prices for position valuation\"\"\"\n",
|
||
" symbol = event.data[\"symbol\"]\n",
|
||
" price = event.data[\"price\"]\n",
|
||
" \n",
|
||
" self.market_prices[symbol] = price\n",
|
||
" \n",
|
||
" # Update position market prices\n",
|
||
" if symbol in self.positions:\n",
|
||
" old_pnl = self.positions[symbol].total_pnl\n",
|
||
" self.positions[symbol].update_market_price(price)\n",
|
||
" new_pnl = self.positions[symbol].total_pnl\n",
|
||
" \n",
|
||
" # Update daily P&L\n",
|
||
" self.daily_pnl += (new_pnl - old_pnl)\n",
|
||
" \n",
|
||
" # Check for new peak/drawdown\n",
|
||
" current_value = self.total_portfolio_value\n",
|
||
" if current_value > self.peak_value:\n",
|
||
" self.peak_value = current_value\n",
|
||
" else:\n",
|
||
" drawdown = (self.peak_value - current_value) / self.peak_value * 100\n",
|
||
" self.max_drawdown = max(self.max_drawdown, drawdown)\n",
|
||
" \n",
|
||
" # Publish portfolio update event\n",
|
||
" self._publish_portfolio_update(\"price_update\", f\"{symbol} price updated to ${price:,.2f}\")\n",
|
||
" \n",
|
||
" def on_order_fill(self, event: Event):\n",
|
||
" \"\"\"Process order fills and update positions\"\"\"\n",
|
||
" symbol = event.data[\"symbol\"]\n",
|
||
" side = event.data[\"side\"]\n",
|
||
" fill_quantity = event.data[\"fill_quantity\"]\n",
|
||
" fill_price = event.data[\"fill_price\"]\n",
|
||
" fees = event.data.get(\"fees\", 0.0)\n",
|
||
" \n",
|
||
" print(f\"💼 {self.name}: Processing fill - {side} {fill_quantity} {symbol} @ ${fill_price:,.2f}\")\n",
|
||
" \n",
|
||
" # Calculate trade quantity (negative for sells)\n",
|
||
" trade_quantity = fill_quantity if side == \"BUY\" else -fill_quantity\n",
|
||
" trade_value = fill_quantity * fill_price\n",
|
||
" \n",
|
||
" # Update cash balance\n",
|
||
" if side == \"BUY\":\n",
|
||
" self.cash_balance -= (trade_value + fees)\n",
|
||
" else:\n",
|
||
" self.cash_balance += (trade_value - fees)\n",
|
||
" \n",
|
||
" # Update or create position\n",
|
||
" if symbol not in self.positions:\n",
|
||
" self.positions[symbol] = Position(symbol)\n",
|
||
" # Set initial market price if we have it\n",
|
||
" if symbol in self.market_prices:\n",
|
||
" self.positions[symbol].update_market_price(self.market_prices[symbol])\n",
|
||
" \n",
|
||
" # Add trade to position\n",
|
||
" self.positions[symbol].add_trade(trade_quantity, fill_price, fees)\n",
|
||
" \n",
|
||
" # Update statistics\n",
|
||
" self.total_trades += 1\n",
|
||
" self.total_fees_paid += fees\n",
|
||
" \n",
|
||
" # Publish portfolio update\n",
|
||
" message = f\"Position updated: {self.positions[symbol]}\"\n",
|
||
" self._publish_portfolio_update(\"trade_executed\", message)\n",
|
||
" \n",
|
||
" print(f\" 💰 Cash balance: ${self.cash_balance:,.2f}\")\n",
|
||
" print(f\" 📊 Position: {self.positions[symbol]}\")\n",
|
||
" \n",
|
||
" def _publish_portfolio_update(self, update_type: str, message: str):\n",
|
||
" \"\"\"Publish portfolio update event\"\"\"\n",
|
||
" portfolio_event = Event(\n",
|
||
" event_type=\"portfolio_update\",\n",
|
||
" data={\n",
|
||
" \"update_type\": update_type,\n",
|
||
" \"message\": message,\n",
|
||
" \"portfolio_value\": self.total_portfolio_value,\n",
|
||
" \"cash_balance\": self.cash_balance,\n",
|
||
" \"total_pnl\": self.total_pnl,\n",
|
||
" \"daily_pnl\": self.daily_pnl,\n",
|
||
" \"positions\": {symbol: pos.to_dict() for symbol, pos in self.positions.items()}\n",
|
||
" },\n",
|
||
" source=self.name\n",
|
||
" )\n",
|
||
" self.event_queue.publish(portfolio_event)\n",
|
||
" \n",
|
||
" @property\n",
|
||
" def total_portfolio_value(self) -> float:\n",
|
||
" \"\"\"Total portfolio value (cash + positions)\"\"\"\n",
|
||
" positions_value = sum(pos.market_value for pos in self.positions.values())\n",
|
||
" return self.cash_balance + positions_value\n",
|
||
" \n",
|
||
" @property\n",
|
||
" def total_pnl(self) -> float:\n",
|
||
" \"\"\"Total profit/loss\"\"\"\n",
|
||
" return self.total_portfolio_value - self.initial_balance\n",
|
||
" \n",
|
||
" @property\n",
|
||
" def total_pnl_percentage(self) -> float:\n",
|
||
" \"\"\"Total P&L as percentage\"\"\"\n",
|
||
" return (self.total_pnl / self.initial_balance) * 100\n",
|
||
" \n",
|
||
" def get_position(self, symbol: str) -> Optional[Position]:\n",
|
||
" \"\"\"Get position for a specific symbol\"\"\"\n",
|
||
" return self.positions.get(symbol)\n",
|
||
" \n",
|
||
" def get_portfolio_summary(self) -> Dict[str, Any]:\n",
|
||
" \"\"\"Get comprehensive portfolio summary\"\"\"\n",
|
||
" return {\n",
|
||
" \"cash_balance\": self.cash_balance,\n",
|
||
" \"total_portfolio_value\": self.total_portfolio_value,\n",
|
||
" \"total_pnl\": self.total_pnl,\n",
|
||
" \"total_pnl_percentage\": f\"{self.total_pnl_percentage:.2f}%\",\n",
|
||
" \"daily_pnl\": self.daily_pnl,\n",
|
||
" \"max_drawdown\": f\"{self.max_drawdown:.2f}%\",\n",
|
||
" \"total_trades\": self.total_trades,\n",
|
||
" \"total_fees_paid\": self.total_fees_paid,\n",
|
||
" \"open_positions\": len([p for p in self.positions.values() if p.quantity != 0]),\n",
|
||
" \"positions\": {symbol: pos.to_dict() for symbol, pos in self.positions.items() if pos.quantity != 0}\n",
|
||
" }\n",
|
||
"\n",
|
||
"# Create portfolio manager\n",
|
||
"portfolio = PortfolioManager(event_queue, initial_balance=100000.0)\n",
|
||
"print(f\"\\n📊 Initial Portfolio Summary:\")\n",
|
||
"print(json.dumps(portfolio.get_portfolio_summary(), indent=2, default=str))"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Part 3: Risk Controller\n",
|
||
"\n",
|
||
"Now let's build the Risk Controller that enforces trading rules and protects our capital:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"class RiskController:\n",
|
||
" \"\"\"Risk management system - the safety net for our trading bot\"\"\"\n",
|
||
" \n",
|
||
" def __init__(self, event_queue: EventQueue, portfolio: PortfolioManager):\n",
|
||
" self.event_queue = event_queue\n",
|
||
" self.portfolio = portfolio\n",
|
||
" self.name = \"risk_controller\"\n",
|
||
" \n",
|
||
" # Risk limits\n",
|
||
" self.max_position_size_pct = 25.0 # Max 25% of portfolio in one position\n",
|
||
" self.max_daily_loss_pct = 5.0 # Max 5% daily loss\n",
|
||
" self.max_total_drawdown_pct = 15.0 # Max 15% total drawdown\n",
|
||
" self.min_cash_reserve_pct = 10.0 # Keep at least 10% in cash\n",
|
||
" self.max_leverage = 1.0 # No leverage for now\n",
|
||
" \n",
|
||
" # Risk tracking\n",
|
||
" self.daily_start_value = portfolio.total_portfolio_value\n",
|
||
" self.risk_violations = []\n",
|
||
" self.blocked_orders = 0\n",
|
||
" self.risk_warnings = 0\n",
|
||
" \n",
|
||
" # Subscribe to events that need risk checking\n",
|
||
" self.event_queue.subscribe(\"order_request\", self.on_order_request)\n",
|
||
" self.event_queue.subscribe(\"portfolio_update\", self.on_portfolio_update)\n",
|
||
" \n",
|
||
" print(f\"🛡️ {self.name}: Risk management initialized\")\n",
|
||
" print(f\" Max position size: {self.max_position_size_pct}%\")\n",
|
||
" print(f\" Max daily loss: {self.max_daily_loss_pct}%\")\n",
|
||
" print(f\" Max drawdown: {self.max_total_drawdown_pct}%\")\n",
|
||
" \n",
|
||
" def on_order_request(self, event: Event):\n",
|
||
" \"\"\"Check order requests against risk rules\"\"\"\n",
|
||
" order = event.data[\"order\"]\n",
|
||
" \n",
|
||
" print(f\"🛡️ {self.name}: Evaluating order risk - {order}\")\n",
|
||
" \n",
|
||
" # Run all risk checks\n",
|
||
" risk_checks = [\n",
|
||
" self._check_position_size_limit(order),\n",
|
||
" self._check_cash_availability(order),\n",
|
||
" self._check_daily_loss_limit(),\n",
|
||
" self._check_total_drawdown_limit(),\n",
|
||
" self._check_minimum_cash_reserve(order)\n",
|
||
" ]\n",
|
||
" \n",
|
||
" # Evaluate results\n",
|
||
" violations = [check for check in risk_checks if not check[\"approved\"]]\n",
|
||
" warnings = [check for check in risk_checks if check[\"approved\"] and check.get(\"warning\")]\n",
|
||
" \n",
|
||
" if violations:\n",
|
||
" # Block the order\n",
|
||
" self.blocked_orders += 1\n",
|
||
" violation_messages = [v[\"message\"] for v in violations]\n",
|
||
" \n",
|
||
" risk_event = Event(\n",
|
||
" event_type=\"risk_violation\",\n",
|
||
" data={\n",
|
||
" \"order_id\": order.order_id,\n",
|
||
" \"violations\": violation_messages,\n",
|
||
" \"severity\": \"CRITICAL\",\n",
|
||
" \"action\": \"ORDER_BLOCKED\"\n",
|
||
" },\n",
|
||
" source=self.name\n",
|
||
" )\n",
|
||
" self.event_queue.publish(risk_event)\n",
|
||
" \n",
|
||
" print(f\" ❌ ORDER BLOCKED - Risk violations:\")\n",
|
||
" for msg in violation_messages:\n",
|
||
" print(f\" • {msg}\")\n",
|
||
" \n",
|
||
" self.risk_violations.extend(violation_messages)\n",
|
||
" \n",
|
||
" elif warnings:\n",
|
||
" # Allow order but issue warnings\n",
|
||
" self.risk_warnings += len(warnings)\n",
|
||
" warning_messages = [w[\"message\"] for w in warnings]\n",
|
||
" \n",
|
||
" risk_event = Event(\n",
|
||
" event_type=\"risk_warning\",\n",
|
||
" data={\n",
|
||
" \"order_id\": order.order_id,\n",
|
||
" \"warnings\": warning_messages,\n",
|
||
" \"severity\": \"WARNING\",\n",
|
||
" \"action\": \"ORDER_APPROVED_WITH_WARNINGS\"\n",
|
||
" },\n",
|
||
" source=self.name\n",
|
||
" )\n",
|
||
" self.event_queue.publish(risk_event)\n",
|
||
" \n",
|
||
" print(f\" ⚠️ ORDER APPROVED (with warnings):\")\n",
|
||
" for msg in warning_messages:\n",
|
||
" print(f\" • {msg}\")\n",
|
||
" \n",
|
||
" else:\n",
|
||
" # Approve order\n",
|
||
" risk_event = Event(\n",
|
||
" event_type=\"risk_approved\",\n",
|
||
" data={\n",
|
||
" \"order_id\": order.order_id,\n",
|
||
" \"severity\": \"INFO\",\n",
|
||
" \"action\": \"ORDER_APPROVED\"\n",
|
||
" },\n",
|
||
" source=self.name\n",
|
||
" )\n",
|
||
" self.event_queue.publish(risk_event)\n",
|
||
" \n",
|
||
" print(f\" ✅ ORDER APPROVED - All risk checks passed\")\n",
|
||
" \n",
|
||
" def on_portfolio_update(self, event: Event):\n",
|
||
" \"\"\"Monitor portfolio for ongoing risk issues\"\"\"\n",
|
||
" update_type = event.data[\"update_type\"]\n",
|
||
" \n",
|
||
" if update_type == \"price_update\":\n",
|
||
" # Check if we're approaching risk limits\n",
|
||
" current_drawdown = self.portfolio.max_drawdown\n",
|
||
" daily_loss_pct = self._calculate_daily_loss_percentage()\n",
|
||
" \n",
|
||
" if current_drawdown > self.max_total_drawdown_pct * 0.8: # 80% of limit\n",
|
||
" print(f\"⚠️ {self.name}: Approaching drawdown limit ({current_drawdown:.1f}% / {self.max_total_drawdown_pct}%)\")\n",
|
||
" \n",
|
||
" if daily_loss_pct > self.max_daily_loss_pct * 0.8: # 80% of limit\n",
|
||
" print(f\"⚠️ {self.name}: Approaching daily loss limit ({daily_loss_pct:.1f}% / {self.max_daily_loss_pct}%)\")\n",
|
||
" \n",
|
||
" def _check_position_size_limit(self, order: Order) -> Dict[str, Any]:\n",
|
||
" \"\"\"Check if order would exceed position size limits\"\"\"\n",
|
||
" symbol = order.symbol\n",
|
||
" order_value = order.quantity * order.price\n",
|
||
" portfolio_value = self.portfolio.total_portfolio_value\n",
|
||
" \n",
|
||
" # Calculate position value after this order\n",
|
||
" current_position = self.portfolio.get_position(symbol)\n",
|
||
" current_value = current_position.market_value if current_position else 0\n",
|
||
" \n",
|
||
" if order.side == OrderSide.BUY:\n",
|
||
" new_position_value = current_value + order_value\n",
|
||
" else:\n",
|
||
" new_position_value = max(0, current_value - order_value)\n",
|
||
" \n",
|
||
" position_pct = (new_position_value / portfolio_value) * 100\n",
|
||
" \n",
|
||
" if position_pct > self.max_position_size_pct:\n",
|
||
" return {\n",
|
||
" \"approved\": False,\n",
|
||
" \"message\": f\"Position size limit exceeded: {position_pct:.1f}% > {self.max_position_size_pct}%\"\n",
|
||
" }\n",
|
||
" elif position_pct > self.max_position_size_pct * 0.8:\n",
|
||
" return {\n",
|
||
" \"approved\": True,\n",
|
||
" \"warning\": True,\n",
|
||
" \"message\": f\"Large position warning: {position_pct:.1f}% of portfolio\"\n",
|
||
" }\n",
|
||
" \n",
|
||
" return {\"approved\": True, \"message\": f\"Position size OK: {position_pct:.1f}%\"}\n",
|
||
" \n",
|
||
" def _check_cash_availability(self, order: Order) -> Dict[str, Any]:\n",
|
||
" \"\"\"Check if we have enough cash for the order\"\"\"\n",
|
||
" if order.side == OrderSide.SELL:\n",
|
||
" return {\"approved\": True, \"message\": \"Sell order - no cash required\"}\n",
|
||
" \n",
|
||
" order_value = order.quantity * order.price\n",
|
||
" estimated_fees = order_value * 0.001 # Estimate 0.1% fees\n",
|
||
" total_needed = order_value + estimated_fees\n",
|
||
" \n",
|
||
" if total_needed > self.portfolio.cash_balance:\n",
|
||
" return {\n",
|
||
" \"approved\": False,\n",
|
||
" \"message\": f\"Insufficient cash: need ${total_needed:,.2f}, have ${self.portfolio.cash_balance:,.2f}\"\n",
|
||
" }\n",
|
||
" \n",
|
||
" return {\"approved\": True, \"message\": \"Cash availability OK\"}\n",
|
||
" \n",
|
||
" def _check_daily_loss_limit(self) -> Dict[str, Any]:\n",
|
||
" \"\"\"Check daily loss limits\"\"\"\n",
|
||
" daily_loss_pct = self._calculate_daily_loss_percentage()\n",
|
||
" \n",
|
||
" if daily_loss_pct > self.max_daily_loss_pct:\n",
|
||
" return {\n",
|
||
" \"approved\": False,\n",
|
||
" \"message\": f\"Daily loss limit exceeded: {daily_loss_pct:.1f}% > {self.max_daily_loss_pct}%\"\n",
|
||
" }\n",
|
||
" elif daily_loss_pct > self.max_daily_loss_pct * 0.8:\n",
|
||
" return {\n",
|
||
" \"approved\": True,\n",
|
||
" \"warning\": True,\n",
|
||
" \"message\": f\"Approaching daily loss limit: {daily_loss_pct:.1f}%\"\n",
|
||
" }\n",
|
||
" \n",
|
||
" return {\"approved\": True, \"message\": \"Daily loss within limits\"}\n",
|
||
" \n",
|
||
" def _check_total_drawdown_limit(self) -> Dict[str, Any]:\n",
|
||
" \"\"\"Check total drawdown limits\"\"\"\n",
|
||
" current_drawdown = self.portfolio.max_drawdown\n",
|
||
" \n",
|
||
" if current_drawdown > self.max_total_drawdown_pct:\n",
|
||
" return {\n",
|
||
" \"approved\": False,\n",
|
||
" \"message\": f\"Maximum drawdown exceeded: {current_drawdown:.1f}% > {self.max_total_drawdown_pct}%\"\n",
|
||
" }\n",
|
||
" \n",
|
||
" return {\"approved\": True, \"message\": \"Drawdown within limits\"}\n",
|
||
" \n",
|
||
" def _check_minimum_cash_reserve(self, order: Order) -> Dict[str, Any]:\n",
|
||
" \"\"\"Check minimum cash reserve requirements\"\"\"\n",
|
||
" if order.side == OrderSide.SELL:\n",
|
||
" return {\"approved\": True, \"message\": \"Sell order - increases cash\"}\n",
|
||
" \n",
|
||
" order_value = order.quantity * order.price\n",
|
||
" remaining_cash = self.portfolio.cash_balance - order_value\n",
|
||
" portfolio_value = self.portfolio.total_portfolio_value\n",
|
||
" cash_pct = (remaining_cash / portfolio_value) * 100\n",
|
||
" \n",
|
||
" if cash_pct < self.min_cash_reserve_pct:\n",
|
||
" return {\n",
|
||
" \"approved\": False,\n",
|
||
" \"message\": f\"Minimum cash reserve violated: {cash_pct:.1f}% < {self.min_cash_reserve_pct}%\"\n",
|
||
" }\n",
|
||
" \n",
|
||
" return {\"approved\": True, \"message\": \"Cash reserve OK\"}\n",
|
||
" \n",
|
||
" def _calculate_daily_loss_percentage(self) -> float:\n",
|
||
" \"\"\"Calculate daily loss as percentage\"\"\"\n",
|
||
" current_value = self.portfolio.total_portfolio_value\n",
|
||
" daily_change = current_value - self.daily_start_value\n",
|
||
" \n",
|
||
" if daily_change >= 0:\n",
|
||
" return 0.0 # No loss\n",
|
||
" \n",
|
||
" return abs(daily_change / self.daily_start_value) * 100\n",
|
||
" \n",
|
||
" def get_risk_summary(self) -> Dict[str, Any]:\n",
|
||
" \"\"\"Get risk management summary\"\"\"\n",
|
||
" return {\n",
|
||
" \"risk_limits\": {\n",
|
||
" \"max_position_size_pct\": self.max_position_size_pct,\n",
|
||
" \"max_daily_loss_pct\": self.max_daily_loss_pct,\n",
|
||
" \"max_total_drawdown_pct\": self.max_total_drawdown_pct,\n",
|
||
" \"min_cash_reserve_pct\": self.min_cash_reserve_pct\n",
|
||
" },\n",
|
||
" \"current_status\": {\n",
|
||
" \"daily_loss_pct\": self._calculate_daily_loss_percentage(),\n",
|
||
" \"total_drawdown_pct\": self.portfolio.max_drawdown,\n",
|
||
" \"cash_reserve_pct\": (self.portfolio.cash_balance / self.portfolio.total_portfolio_value) * 100\n",
|
||
" },\n",
|
||
" \"statistics\": {\n",
|
||
" \"blocked_orders\": self.blocked_orders,\n",
|
||
" \"risk_warnings\": self.risk_warnings,\n",
|
||
" \"total_violations\": len(self.risk_violations)\n",
|
||
" }\n",
|
||
" }\n",
|
||
"\n",
|
||
"# Create risk controller\n",
|
||
"risk_controller = RiskController(event_queue, portfolio)\n",
|
||
"print(f\"\\n🛡️ Risk Controller Summary:\")\n",
|
||
"print(json.dumps(risk_controller.get_risk_summary(), indent=2, default=str))"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Part 4: Market Data Simulator\n",
|
||
"\n",
|
||
"Let's create a simple market data simulator to test our portfolio and risk systems:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"class MarketDataSimulator:\n",
|
||
" \"\"\"Simple market data simulator for testing\"\"\"\n",
|
||
" \n",
|
||
" def __init__(self, event_queue: EventQueue):\n",
|
||
" self.event_queue = event_queue\n",
|
||
" self.name = \"market_simulator\"\n",
|
||
" \n",
|
||
" # Initial prices\n",
|
||
" self.prices = {\n",
|
||
" \"BTC/USDT\": 43000.0,\n",
|
||
" \"ETH/USDT\": 2650.0,\n",
|
||
" \"BNB/USDT\": 310.0\n",
|
||
" }\n",
|
||
" \n",
|
||
" def update_price(self, symbol: str, new_price: float):\n",
|
||
" \"\"\"Update price and publish market data event\"\"\"\n",
|
||
" old_price = self.prices.get(symbol, 0)\n",
|
||
" self.prices[symbol] = new_price\n",
|
||
" change_pct = ((new_price - old_price) / old_price * 100) if old_price > 0 else 0\n",
|
||
" \n",
|
||
" market_event = Event(\n",
|
||
" event_type=\"market_data\",\n",
|
||
" data={\n",
|
||
" \"symbol\": symbol,\n",
|
||
" \"price\": new_price,\n",
|
||
" \"volume\": 100.0, # Mock volume\n",
|
||
" \"change_pct\": change_pct\n",
|
||
" },\n",
|
||
" source=self.name\n",
|
||
" )\n",
|
||
" \n",
|
||
" self.event_queue.publish(market_event)\n",
|
||
" print(f\"📈 {symbol}: ${old_price:,.2f} → ${new_price:,.2f} ({change_pct:+.2f}%)\")\n",
|
||
" \n",
|
||
" def simulate_order_fill(self, symbol: str, side: str, quantity: float, price: float, fees: float = None):\n",
|
||
" \"\"\"Simulate an order fill\"\"\"\n",
|
||
" if fees is None:\n",
|
||
" fees = quantity * price * 0.001 # 0.1% default fee\n",
|
||
" \n",
|
||
" fill_event = Event(\n",
|
||
" event_type=\"order_fill\",\n",
|
||
" data={\n",
|
||
" \"order_id\": str(uuid.uuid4()),\n",
|
||
" \"symbol\": symbol,\n",
|
||
" \"side\": side,\n",
|
||
" \"fill_quantity\": quantity,\n",
|
||
" \"fill_price\": price,\n",
|
||
" \"fees\": fees\n",
|
||
" },\n",
|
||
" source=self.name\n",
|
||
" )\n",
|
||
" \n",
|
||
" self.event_queue.publish(fill_event)\n",
|
||
"\n",
|
||
"# Create market simulator\n",
|
||
"market_sim = MarketDataSimulator(event_queue)\n",
|
||
"print(f\"📊 Market simulator ready with initial prices: {market_sim.prices}\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Part 5: Complete Demo - Portfolio & Risk in Action\n",
|
||
"\n",
|
||
"Let's test our complete system with various trading scenarios!"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"print(\"🎬 DEMO: Portfolio & Risk Management System\\n\")\n",
|
||
"print(\"=\" * 70)\n",
|
||
"\n",
|
||
"# Initial state\n",
|
||
"print(\"\\n📊 Initial State:\")\n",
|
||
"print(f\"Portfolio Value: ${portfolio.total_portfolio_value:,.2f}\")\n",
|
||
"print(f\"Cash Balance: ${portfolio.cash_balance:,.2f}\")\n",
|
||
"\n",
|
||
"# Test 1: Normal trade (should pass all risk checks)\n",
|
||
"print(\"\\n📊 Test 1: Normal BTC Purchase\")\n",
|
||
"print(\"-\" * 40)\n",
|
||
"btc_order = Order(\"BTC/USDT\", OrderSide.BUY, 0.5, 43000) # $21,500 order (21.5% of portfolio)\n",
|
||
"request_event = Event(\n",
|
||
" event_type=\"order_request\",\n",
|
||
" data={\"order\": btc_order},\n",
|
||
" source=\"demo\"\n",
|
||
")\n",
|
||
"event_queue.publish(request_event)\n",
|
||
"\n",
|
||
"# Simulate the fill\n",
|
||
"time.sleep(0.5)\n",
|
||
"market_sim.simulate_order_fill(\"BTC/USDT\", \"BUY\", 0.5, 43000)\n",
|
||
"\n",
|
||
"time.sleep(0.5)\n",
|
||
"\n",
|
||
"# Test 2: Price update (profit scenario)\n",
|
||
"print(\"\\n📊 Test 2: BTC Price Increase\")\n",
|
||
"print(\"-\" * 30)\n",
|
||
"market_sim.update_price(\"BTC/USDT\", 44500) # +3.49% increase\n",
|
||
"\n",
|
||
"time.sleep(0.5)\n",
|
||
"\n",
|
||
"# Test 3: Large order (should trigger position size warning)\n",
|
||
"print(\"\\n📊 Test 3: Large ETH Order (Position Size Warning)\")\n",
|
||
"print(\"-\" * 50)\n",
|
||
"eth_order = Order(\"ETH/USDT\", OrderSide.BUY, 8.0, 2650) # $21,200 order\n",
|
||
"request_event = Event(\n",
|
||
" event_type=\"order_request\",\n",
|
||
" data={\"order\": eth_order},\n",
|
||
" source=\"demo\"\n",
|
||
")\n",
|
||
"event_queue.publish(request_event)\n",
|
||
"\n",
|
||
"time.sleep(0.5)\n",
|
||
"market_sim.simulate_order_fill(\"ETH/USDT\", \"BUY\", 8.0, 2650)\n",
|
||
"\n",
|
||
"time.sleep(0.5)\n",
|
||
"\n",
|
||
"# Test 4: Excessive order (should be blocked)\n",
|
||
"print(\"\\n📊 Test 4: Excessive Order (Should be BLOCKED)\")\n",
|
||
"print(\"-\" * 45)\n",
|
||
"huge_order = Order(\"BTC/USDT\", OrderSide.BUY, 1.0, 44500) # $44,500 order (too large)\n",
|
||
"request_event = Event(\n",
|
||
" event_type=\"order_request\",\n",
|
||
" data={\"order\": huge_order},\n",
|
||
" source=\"demo\"\n",
|
||
")\n",
|
||
"event_queue.publish(request_event)\n",
|
||
"\n",
|
||
"time.sleep(0.5)\n",
|
||
"\n",
|
||
"# Test 5: Market crash (trigger risk warnings)\n",
|
||
"print(\"\\n📊 Test 5: Market Crash Simulation\")\n",
|
||
"print(\"-\" * 35)\n",
|
||
"market_sim.update_price(\"BTC/USDT\", 39000) # -12.4% crash\n",
|
||
"market_sim.update_price(\"ETH/USDT\", 2300) # -13.2% crash\n",
|
||
"\n",
|
||
"time.sleep(0.5)\n",
|
||
"\n",
|
||
"# Test 6: Try to trade during high losses (should be blocked)\n",
|
||
"print(\"\\n📊 Test 6: Order During High Losses (Should be BLOCKED)\")\n",
|
||
"print(\"-\" * 55)\n",
|
||
"risky_order = Order(\"BNB/USDT\", OrderSide.BUY, 50, 310) # $15,500 order\n",
|
||
"request_event = Event(\n",
|
||
" event_type=\"order_request\",\n",
|
||
" data={\"order\": risky_order},\n",
|
||
" source=\"demo\"\n",
|
||
")\n",
|
||
"event_queue.publish(request_event)\n",
|
||
"\n",
|
||
"time.sleep(0.5)\n",
|
||
"\n",
|
||
"print(\"\\n\" + \"=\" * 70)\n",
|
||
"print(\"📈 Final Portfolio Summary:\")\n",
|
||
"print(json.dumps(portfolio.get_portfolio_summary(), indent=2, default=str))\n",
|
||
"\n",
|
||
"print(\"\\n🛡️ Final Risk Summary:\")\n",
|
||
"print(json.dumps(risk_controller.get_risk_summary(), indent=2, default=str))\n",
|
||
"\n",
|
||
"print(f\"\\n📊 Total Events Processed: {len(event_queue.event_history)}\")\n",
|
||
"print(\"\\n🎯 Key Achievements:\")\n",
|
||
"print(f\" • Tracked {len(portfolio.positions)} positions in real-time\")\n",
|
||
"print(f\" • Processed {portfolio.total_trades} trades\")\n",
|
||
"print(f\" • Blocked {risk_controller.blocked_orders} risky orders\")\n",
|
||
"print(f\" • Issued {risk_controller.risk_warnings} risk warnings\")\n",
|
||
"print(f\" • Current P&L: ${portfolio.total_pnl:,.2f} ({portfolio.total_pnl_percentage:+.2f}%)\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Part 6: Your Turn - Advanced Portfolio Features\n",
|
||
"\n",
|
||
"**Challenge:** Extend the system with advanced portfolio and risk features!\n",
|
||
"\n",
|
||
"Choose one or more of these challenges:\n",
|
||
"\n",
|
||
"1. **Performance Metrics**: Add Sharpe ratio, Sortino ratio, maximum drawdown duration\n",
|
||
"2. **Advanced Risk Controls**: Position correlation limits, sector exposure limits\n",
|
||
"3. **Dynamic Risk Adjustment**: Adjust risk limits based on market volatility\n",
|
||
"4. **Portfolio Rebalancing**: Automatic rebalancing when positions drift too far\n",
|
||
"5. **Risk Reports**: Generate detailed risk reports with recommendations"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"# Challenge 1: Performance Metrics\n",
|
||
"def calculate_sharpe_ratio(portfolio: PortfolioManager, risk_free_rate: float = 0.02) -> float:\n",
|
||
" \"\"\"Calculate Sharpe ratio for the portfolio\"\"\"\n",
|
||
" # YOUR CODE HERE\n",
|
||
" # Hint: You'll need to track daily returns over time\n",
|
||
" pass\n",
|
||
"\n",
|
||
"# Challenge 2: Correlation-based Risk Control\n",
|
||
"def check_position_correlation(portfolio: PortfolioManager, new_symbol: str, max_correlation: float = 0.7) -> bool:\n",
|
||
" \"\"\"Check if new position would create excessive correlation risk\"\"\"\n",
|
||
" # YOUR CODE HERE\n",
|
||
" # Hint: You could use simple sector groupings (BTC/ETH = crypto, etc.)\n",
|
||
" pass\n",
|
||
"\n",
|
||
"# Challenge 3: Dynamic Risk Adjustment\n",
|
||
"def adjust_risk_limits_for_volatility(risk_controller: RiskController, market_volatility: float):\n",
|
||
" \"\"\"Adjust risk limits based on market conditions\"\"\"\n",
|
||
" # YOUR CODE HERE\n",
|
||
" # Hint: Reduce position limits when volatility is high\n",
|
||
" pass\n",
|
||
"\n",
|
||
"# Challenge 4: Portfolio Rebalancing\n",
|
||
"def suggest_rebalancing_trades(portfolio: PortfolioManager, target_weights: Dict[str, float]) -> List[Order]:\n",
|
||
" \"\"\"Suggest trades to rebalance portfolio to target weights\"\"\"\n",
|
||
" # YOUR CODE HERE\n",
|
||
" pass\n",
|
||
"\n",
|
||
"# Challenge 5: Risk Report Generation\n",
|
||
"def generate_risk_report(portfolio: PortfolioManager, risk_controller: RiskController) -> Dict[str, Any]:\n",
|
||
" \"\"\"Generate comprehensive risk report\"\"\"\n",
|
||
" # YOUR CODE HERE\n",
|
||
" pass\n",
|
||
"\n",
|
||
"# Test your implementations here\n",
|
||
"print(\"🧪 Test your advanced features here!\")\n",
|
||
"\n",
|
||
"# Example tests:\n",
|
||
"# sharpe = calculate_sharpe_ratio(portfolio)\n",
|
||
"# rebalance_trades = suggest_rebalancing_trades(portfolio, {\"BTC/USDT\": 0.6, \"ETH/USDT\": 0.3, \"CASH\": 0.1})\n",
|
||
"# risk_report = generate_risk_report(portfolio, risk_controller)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Congratulations!\n",
|
||
"\n",
|
||
"You've built a professional-grade Portfolio Management and Risk Control system! Here's what you accomplished:\n",
|
||
"\n",
|
||
"✅ **Real-time Portfolio Tracking**: Positions, P&L, balances updated instantly \n",
|
||
"✅ **Comprehensive Risk Controls**: Position size, daily loss, drawdown, cash reserve limits \n",
|
||
"✅ **Event-driven Integration**: Seamless communication with order management system \n",
|
||
"✅ **Professional Risk Management**: Order blocking, warnings, violation tracking \n",
|
||
"✅ **Market Simulation**: Realistic testing environment with price movements \n",
|
||
"✅ **Complete Statistics**: Detailed reporting and monitoring capabilities \n",
|
||
"\n",
|
||
"## Key Professional Features:\n",
|
||
"\n",
|
||
"1. **Position Management**: Real-time tracking with average cost basis and P&L calculation\n",
|
||
"2. **Risk Controls**: Multi-layered protection against excessive losses\n",
|
||
"3. **Event Integration**: Loose coupling through event system\n",
|
||
"4. **Real-time Updates**: Instant portfolio updates as market prices change\n",
|
||
"5. **Comprehensive Monitoring**: Statistics, warnings, and violation tracking\n",
|
||
"\n",
|
||
"## Next Steps:\n",
|
||
"\n",
|
||
"In the next notebook, we'll add the **Market Data Handler** and **Algorithm Engine** to complete our MARKET architecture and create a fully functional trading bot!\n",
|
||
"\n",
|
||
"---\n",
|
||
"*\"Risk management is not about avoiding risk - it's about taking the right risks at the right size.\"* 🛡️💰"
|
||
]
|
||
}
|
||
],
|
||
"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
|
||
}
|