From 3de9fab5e2f7d6c63fa28c38d94526a4a7e4c36e Mon Sep 17 00:00:00 2001 From: Jakub Polec Date: Thu, 26 Jun 2025 15:46:49 +0200 Subject: [PATCH] Session_05 files --- Session_05/01_event_foundation.ipynb | 610 ++++++++ Session_05/02_order_management_system.ipynb | 1059 +++++++++++++ Session_05/03_portfolio_and_risk.ipynb | 990 ++++++++++++ Session_05/04_market_data_and_algorithm.ipynb | 1358 +++++++++++++++++ Session_05/Functions/funcs.py | 226 +++ Session_05/Functions/functions.py | 144 ++ Session_05/_files_ | 0 Session_05/bots/.env | 2 + Session_05/bots/Breakout.py | 174 +++ Session_05/bots/Consollidation_Pop.py | 137 ++ Session_05/bots/Correlation.py | 233 +++ Session_05/bots/Engulfing_Candle.py | 119 ++ Session_05/bots/HyperLiquid/BBands.py | 205 +++ .../bots/HyperLiquid/Supply_Demand_Zone.py | 191 +++ Session_05/bots/HyperLiquid/VWAP.py | 173 +++ Session_05/bots/Market_Maker.py | 341 +++++ .../bots/Multi_Ticker_Mean_Reversion.py | 261 ++++ Session_05/bots/SMA_Order_book.py | 300 ++++ Session_05/bots/Stochastic_RSI_Nadarya.py | 72 + Session_05/bots/Turtle.py | 84 + 20 files changed, 6679 insertions(+) create mode 100644 Session_05/01_event_foundation.ipynb create mode 100644 Session_05/02_order_management_system.ipynb create mode 100644 Session_05/03_portfolio_and_risk.ipynb create mode 100644 Session_05/04_market_data_and_algorithm.ipynb create mode 100644 Session_05/Functions/funcs.py create mode 100644 Session_05/Functions/functions.py delete mode 100644 Session_05/_files_ create mode 100644 Session_05/bots/.env create mode 100644 Session_05/bots/Breakout.py create mode 100644 Session_05/bots/Consollidation_Pop.py create mode 100644 Session_05/bots/Correlation.py create mode 100644 Session_05/bots/Engulfing_Candle.py create mode 100644 Session_05/bots/HyperLiquid/BBands.py create mode 100644 Session_05/bots/HyperLiquid/Supply_Demand_Zone.py create mode 100644 Session_05/bots/HyperLiquid/VWAP.py create mode 100644 Session_05/bots/Market_Maker.py create mode 100644 Session_05/bots/Multi_Ticker_Mean_Reversion.py create mode 100644 Session_05/bots/SMA_Order_book.py create mode 100644 Session_05/bots/Stochastic_RSI_Nadarya.py create mode 100644 Session_05/bots/Turtle.py diff --git a/Session_05/01_event_foundation.ipynb b/Session_05/01_event_foundation.ipynb new file mode 100644 index 0000000..14c7284 --- /dev/null +++ b/Session_05/01_event_foundation.ipynb @@ -0,0 +1,610 @@ +{ + "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 +} diff --git a/Session_05/02_order_management_system.ipynb b/Session_05/02_order_management_system.ipynb new file mode 100644 index 0000000..92d90cc --- /dev/null +++ b/Session_05/02_order_management_system.ipynb @@ -0,0 +1,1059 @@ +{ + "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 +} diff --git a/Session_05/03_portfolio_and_risk.ipynb b/Session_05/03_portfolio_and_risk.ipynb new file mode 100644 index 0000000..26bed6a --- /dev/null +++ b/Session_05/03_portfolio_and_risk.ipynb @@ -0,0 +1,990 @@ +{ + "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 +} diff --git a/Session_05/04_market_data_and_algorithm.ipynb b/Session_05/04_market_data_and_algorithm.ipynb new file mode 100644 index 0000000..1c1e8a1 --- /dev/null +++ b/Session_05/04_market_data_and_algorithm.ipynb @@ -0,0 +1,1358 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Session 5: Event-Driven Trading Bot\n", + "\n", + "## Notebook 4: Market Data Handler & Algorithm Engine\n", + "\n", + "**Learning Objectives:**\n", + "- Build a professional Market Data Handler\n", + "- Implement Algorithm Engine with technical indicators\n", + "- Create complete trading strategies that respond to market events\n", + "- Integrate with all MARKET components (complete system)\n", + "- Add system monitoring and performance tracking\n", + "\n", + "**Why This Matters:**\n", + "The Market Data Handler is your \"eyes\" - it processes all incoming market information and feeds it to your trading strategy. The Algorithm Engine is your \"brain\" - it analyzes data, makes decisions, and generates trading signals. Together, they complete our MARKET architecture and create a fully autonomous trading bot.\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Quick Setup: Import Complete MARKET System\n", + "\n", + "Let's import all our systems from previous notebooks:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Import all our systems from previous notebooks\n", + "from datetime import datetime, timedelta\n", + "from typing import Dict, Any, List, Callable, Optional, Deque\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", + "import random\n", + "import statistics\n", + "import pandas as pd\n", + "import numpy as np\n", + "\n", + "# Event System (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 (Notebook 2) - simplified\n", + "class OrderState(Enum):\n", + " PENDING = \"PENDING\"\n", + " SUBMITTED = \"SUBMITTED\"\n", + " FILLED = \"FILLED\"\n", + " CANCELLED = \"CANCELLED\"\n", + "\n", + "class OrderSide(Enum):\n", + " BUY = \"BUY\"\n", + " SELL = \"SELL\"\n", + "\n", + "class OrderType(Enum):\n", + " MARKET = \"MARKET\"\n", + " LIMIT = \"LIMIT\"\n", + "\n", + "@dataclass\n", + "class Order:\n", + " symbol: str\n", + " side: OrderSide\n", + " order_type: OrderType\n", + " quantity: float\n", + " price: Optional[float] = None\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", + " 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}\"\n", + "\n", + "# Position & Portfolio (Notebook 3) - simplified\n", + "@dataclass\n", + "class Position:\n", + " symbol: str\n", + " quantity: float = 0.0\n", + " average_price: float = 0.0\n", + " market_price: float = 0.0\n", + " realized_pnl: float = 0.0\n", + " fees_paid: float = 0.0\n", + " \n", + " @property\n", + " def market_value(self) -> float:\n", + " return abs(self.quantity) * self.market_price\n", + " \n", + " @property\n", + " def unrealized_pnl(self) -> float:\n", + " if self.quantity == 0:\n", + " return 0.0\n", + " return (self.market_price - self.average_price) * self.quantity\n", + " \n", + " def update_market_price(self, new_price: float):\n", + " self.market_price = new_price\n", + " \n", + " def add_trade(self, quantity: float, price: float, fees: float = 0.0):\n", + " if self.quantity == 0:\n", + " self.quantity = quantity\n", + " self.average_price = price\n", + " else:\n", + " if (self.quantity > 0 and quantity > 0) or (self.quantity < 0 and quantity < 0):\n", + " total_value = (self.quantity * self.average_price) + (quantity * price)\n", + " self.quantity += quantity\n", + " self.average_price = total_value / self.quantity if self.quantity != 0 else 0\n", + " else:\n", + " if abs(quantity) >= abs(self.quantity):\n", + " self.realized_pnl += (price - self.average_price) * self.quantity\n", + " self.quantity = 0\n", + " self.average_price = 0\n", + " else:\n", + " close_pnl = (price - self.average_price) * abs(quantity)\n", + " self.realized_pnl += close_pnl\n", + " self.quantity += quantity\n", + " \n", + " self.fees_paid += fees\n", + "\n", + "# Create our event queue\n", + "event_queue = EventQueue()\n", + "print(\"๐Ÿ—๏ธ Complete MARKET system ready for Market Data & Algorithm integration!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Part 1: Advanced Market Data Handler\n", + "\n", + "Let's build a sophisticated Market Data Handler that processes real-time data and calculates technical indicators:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "@dataclass\n", + "class OHLCV:\n", + " \"\"\"OHLCV candlestick data\"\"\"\n", + " timestamp: datetime\n", + " open: float\n", + " high: float\n", + " low: float\n", + " close: float\n", + " volume: float\n", + " \n", + " def to_dict(self) -> Dict[str, Any]:\n", + " return {\n", + " 'timestamp': self.timestamp.isoformat(),\n", + " 'open': self.open,\n", + " 'high': self.high,\n", + " 'low': self.low,\n", + " 'close': self.close,\n", + " 'volume': self.volume\n", + " }\n", + "\n", + "class TechnicalIndicators:\n", + " \"\"\"Calculate technical indicators from price data\"\"\"\n", + " \n", + " @staticmethod\n", + " def sma(prices: List[float], period: int) -> Optional[float]:\n", + " \"\"\"Simple Moving Average\"\"\"\n", + " if len(prices) < period:\n", + " return None\n", + " return sum(prices[-period:]) / period\n", + " \n", + " @staticmethod\n", + " def ema(prices: List[float], period: int) -> Optional[float]:\n", + " \"\"\"Exponential Moving Average\"\"\"\n", + " if len(prices) < period:\n", + " return None\n", + " \n", + " multiplier = 2 / (period + 1)\n", + " ema = prices[0]\n", + " \n", + " for price in prices[1:]:\n", + " ema = (price * multiplier) + (ema * (1 - multiplier))\n", + " \n", + " return ema\n", + " \n", + " @staticmethod\n", + " def rsi(prices: List[float], period: int = 14) -> Optional[float]:\n", + " \"\"\"Relative Strength Index\"\"\"\n", + " if len(prices) < period + 1:\n", + " return None\n", + " \n", + " gains = []\n", + " losses = []\n", + " \n", + " for i in range(1, len(prices)):\n", + " change = prices[i] - prices[i-1]\n", + " if change > 0:\n", + " gains.append(change)\n", + " losses.append(0)\n", + " else:\n", + " gains.append(0)\n", + " losses.append(-change)\n", + " \n", + " if len(gains) < period:\n", + " return None\n", + " \n", + " avg_gain = sum(gains[-period:]) / period\n", + " avg_loss = sum(losses[-period:]) / period\n", + " \n", + " if avg_loss == 0:\n", + " return 100\n", + " \n", + " rs = avg_gain / avg_loss\n", + " rsi = 100 - (100 / (1 + rs))\n", + " return rsi\n", + " \n", + " @staticmethod\n", + " def bollinger_bands(prices: List[float], period: int = 20, std_dev: float = 2) -> Optional[Dict[str, float]]:\n", + " \"\"\"Bollinger Bands\"\"\"\n", + " if len(prices) < period:\n", + " return None\n", + " \n", + " recent_prices = prices[-period:]\n", + " sma = sum(recent_prices) / period\n", + " variance = sum((p - sma) ** 2 for p in recent_prices) / period\n", + " std = variance ** 0.5\n", + " \n", + " return {\n", + " 'upper': sma + (std * std_dev),\n", + " 'middle': sma,\n", + " 'lower': sma - (std * std_dev)\n", + " }\n", + "\n", + "class MarketDataHandler:\n", + " \"\"\"Professional Market Data Handler - the 'M' in MARKET\"\"\"\n", + " \n", + " def __init__(self, event_queue: EventQueue):\n", + " self.event_queue = event_queue\n", + " self.name = \"market_data_handler\"\n", + " \n", + " # Price history storage\n", + " self.price_history: Dict[str, List[float]] = defaultdict(list)\n", + " self.ohlcv_history: Dict[str, List[OHLCV]] = defaultdict(list)\n", + " self.current_prices: Dict[str, float] = {}\n", + " \n", + " # Technical indicators cache\n", + " self.indicators: Dict[str, Dict[str, Any]] = defaultdict(dict)\n", + " \n", + " # Data statistics\n", + " self.total_price_updates = 0\n", + " self.symbols_tracked = set()\n", + " self.last_update_time = {}\n", + " \n", + " print(f\"๐Ÿ“Š {self.name}: Market Data Handler initialized\")\n", + " \n", + " def process_price_update(self, symbol: str, price: float, volume: float = 0.0):\n", + " \"\"\"Process incoming price update\"\"\"\n", + " timestamp = datetime.now()\n", + " \n", + " # Store price data\n", + " self.price_history[symbol].append(price)\n", + " self.current_prices[symbol] = price\n", + " self.symbols_tracked.add(symbol)\n", + " self.last_update_time[symbol] = timestamp\n", + " self.total_price_updates += 1\n", + " \n", + " # Limit history size for memory management\n", + " if len(self.price_history[symbol]) > 200:\n", + " self.price_history[symbol] = self.price_history[symbol][-200:]\n", + " \n", + " # Calculate technical indicators\n", + " self._update_indicators(symbol)\n", + " \n", + " # Publish enhanced market data event\n", + " self._publish_market_data_event(symbol, price, volume, timestamp)\n", + " \n", + " print(f\"๐Ÿ“Š {self.name}: {symbol} ${price:,.2f} (Volume: {volume:.2f})\")\n", + " \n", + " def _update_indicators(self, symbol: str):\n", + " \"\"\"Update technical indicators for a symbol\"\"\"\n", + " prices = self.price_history[symbol]\n", + " \n", + " if len(prices) < 2:\n", + " return\n", + " \n", + " indicators = {}\n", + " \n", + " # Moving averages\n", + " indicators['sma_5'] = TechnicalIndicators.sma(prices, 5)\n", + " indicators['sma_10'] = TechnicalIndicators.sma(prices, 10)\n", + " indicators['sma_20'] = TechnicalIndicators.sma(prices, 20)\n", + " indicators['ema_12'] = TechnicalIndicators.ema(prices, 12)\n", + " indicators['ema_26'] = TechnicalIndicators.ema(prices, 26)\n", + " \n", + " # RSI\n", + " indicators['rsi'] = TechnicalIndicators.rsi(prices, 14)\n", + " \n", + " # Bollinger Bands\n", + " bb = TechnicalIndicators.bollinger_bands(prices, 20)\n", + " if bb:\n", + " indicators['bb_upper'] = bb['upper']\n", + " indicators['bb_middle'] = bb['middle']\n", + " indicators['bb_lower'] = bb['lower']\n", + " \n", + " # MACD\n", + " if indicators['ema_12'] and indicators['ema_26']:\n", + " indicators['macd'] = indicators['ema_12'] - indicators['ema_26']\n", + " \n", + " # Price change percentage\n", + " if len(prices) >= 2:\n", + " indicators['price_change_pct'] = ((prices[-1] - prices[-2]) / prices[-2]) * 100\n", + " \n", + " # Volatility (standard deviation of last 20 prices)\n", + " if len(prices) >= 20:\n", + " recent_prices = prices[-20:]\n", + " mean_price = sum(recent_prices) / len(recent_prices)\n", + " variance = sum((p - mean_price) ** 2 for p in recent_prices) / len(recent_prices)\n", + " indicators['volatility'] = (variance ** 0.5) / mean_price * 100 # As percentage\n", + " \n", + " self.indicators[symbol] = indicators\n", + " \n", + " def _publish_market_data_event(self, symbol: str, price: float, volume: float, timestamp: datetime):\n", + " \"\"\"Publish enhanced market data event with indicators\"\"\"\n", + " event_data = {\n", + " 'symbol': symbol,\n", + " 'price': price,\n", + " 'volume': volume,\n", + " 'timestamp': timestamp.isoformat(),\n", + " 'indicators': self.indicators[symbol].copy()\n", + " }\n", + " \n", + " market_event = Event(\n", + " event_type=\"market_data\",\n", + " data=event_data,\n", + " source=self.name,\n", + " timestamp=timestamp\n", + " )\n", + " \n", + " self.event_queue.publish(market_event)\n", + " \n", + " def get_current_price(self, symbol: str) -> Optional[float]:\n", + " \"\"\"Get current price for a symbol\"\"\"\n", + " return self.current_prices.get(symbol)\n", + " \n", + " def get_indicators(self, symbol: str) -> Dict[str, Any]:\n", + " \"\"\"Get current indicators for a symbol\"\"\"\n", + " return self.indicators.get(symbol, {})\n", + " \n", + " def get_price_history(self, symbol: str, length: int = 50) -> List[float]:\n", + " \"\"\"Get recent price history\"\"\"\n", + " return self.price_history.get(symbol, [])[-length:]\n", + " \n", + " def get_stats(self) -> Dict[str, Any]:\n", + " \"\"\"Get market data handler statistics\"\"\"\n", + " return {\n", + " 'total_price_updates': self.total_price_updates,\n", + " 'symbols_tracked': len(self.symbols_tracked),\n", + " 'symbols_list': list(self.symbols_tracked),\n", + " 'current_prices': self.current_prices.copy(),\n", + " 'data_points_per_symbol': {symbol: len(prices) for symbol, prices in self.price_history.items()}\n", + " }\n", + "\n", + "# Test Market Data Handler\n", + "market_data = MarketDataHandler(event_queue)\n", + "\n", + "print(\"\\n๐Ÿงช Testing Market Data Handler:\")\n", + "market_data.process_price_update(\"BTC/USDT\", 43000, 1.5)\n", + "market_data.process_price_update(\"BTC/USDT\", 43150, 2.1)\n", + "market_data.process_price_update(\"BTC/USDT\", 43080, 1.8)\n", + "\n", + "print(f\"\\nCurrent BTC indicators: {market_data.get_indicators('BTC/USDT')}\")\n", + "print(f\"Market Data Stats: {json.dumps(market_data.get_stats(), indent=2, default=str)}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Part 2: Algorithm Engine with Trading Strategies\n", + "\n", + "Now let's build the Algorithm Engine that implements sophisticated trading strategies:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from abc import ABC, abstractmethod\n", + "\n", + "class TradingStrategy(ABC):\n", + " \"\"\"Base class for all trading strategies\"\"\"\n", + " \n", + " def __init__(self, name: str):\n", + " self.name = name\n", + " self.signals_generated = 0\n", + " self.last_signal_time = None\n", + " \n", + " @abstractmethod\n", + " def analyze(self, symbol: str, price: float, indicators: Dict[str, Any]) -> Optional[Dict[str, Any]]:\n", + " \"\"\"Analyze market data and return trading signal if any\"\"\"\n", + " pass\n", + " \n", + " def _generate_signal(self, symbol: str, signal_type: str, strength: float, \n", + " reason: str, suggested_quantity: float = None) -> Dict[str, Any]:\n", + " \"\"\"Generate a trading signal\"\"\"\n", + " self.signals_generated += 1\n", + " self.last_signal_time = datetime.now()\n", + " \n", + " return {\n", + " 'strategy': self.name,\n", + " 'symbol': symbol,\n", + " 'signal_type': signal_type, # 'BUY', 'SELL', 'HOLD'\n", + " 'strength': strength, # 0.0 to 1.0\n", + " 'reason': reason,\n", + " 'suggested_quantity': suggested_quantity,\n", + " 'timestamp': self.last_signal_time\n", + " }\n", + "\n", + "class MovingAverageCrossoverStrategy(TradingStrategy):\n", + " \"\"\"Simple moving average crossover strategy\"\"\"\n", + " \n", + " def __init__(self, fast_period: int = 5, slow_period: int = 20):\n", + " super().__init__(f\"MA_Crossover_{fast_period}_{slow_period}\")\n", + " self.fast_period = fast_period\n", + " self.slow_period = slow_period\n", + " self.last_signal = {} # Track last signal per symbol\n", + " \n", + " def analyze(self, symbol: str, price: float, indicators: Dict[str, Any]) -> Optional[Dict[str, Any]]:\n", + " fast_ma = indicators.get(f'sma_{self.fast_period}')\n", + " slow_ma = indicators.get(f'sma_{self.slow_period}')\n", + " \n", + " if not fast_ma or not slow_ma:\n", + " return None\n", + " \n", + " # Determine current position of moving averages\n", + " if fast_ma > slow_ma:\n", + " current_trend = \"BULLISH\"\n", + " else:\n", + " current_trend = \"BEARISH\"\n", + " \n", + " # Check for crossover\n", + " last_trend = self.last_signal.get(symbol, {}).get('trend')\n", + " \n", + " if last_trend and last_trend != current_trend:\n", + " # Crossover detected!\n", + " if current_trend == \"BULLISH\":\n", + " signal = self._generate_signal(\n", + " symbol, \"BUY\", 0.7,\n", + " f\"Golden cross: {fast_ma:.2f} > {slow_ma:.2f}\",\n", + " 0.1 # Suggest 0.1 units\n", + " )\n", + " else:\n", + " signal = self._generate_signal(\n", + " symbol, \"SELL\", 0.7,\n", + " f\"Death cross: {fast_ma:.2f} < {slow_ma:.2f}\",\n", + " 0.1\n", + " )\n", + " \n", + " self.last_signal[symbol] = {'trend': current_trend, 'time': datetime.now()}\n", + " return signal\n", + " \n", + " # Update trend without signal\n", + " self.last_signal[symbol] = {'trend': current_trend, 'time': datetime.now()}\n", + " return None\n", + "\n", + "class RSIStrategy(TradingStrategy):\n", + " \"\"\"RSI-based mean reversion strategy\"\"\"\n", + " \n", + " def __init__(self, oversold_threshold: float = 30, overbought_threshold: float = 70):\n", + " super().__init__(f\"RSI_{oversold_threshold}_{overbought_threshold}\")\n", + " self.oversold_threshold = oversold_threshold\n", + " self.overbought_threshold = overbought_threshold\n", + " self.last_rsi = {} # Track RSI history per symbol\n", + " \n", + " def analyze(self, symbol: str, price: float, indicators: Dict[str, Any]) -> Optional[Dict[str, Any]]:\n", + " rsi = indicators.get('rsi')\n", + " \n", + " if not rsi:\n", + " return None\n", + " \n", + " last_rsi = self.last_rsi.get(symbol)\n", + " self.last_rsi[symbol] = rsi\n", + " \n", + " # Look for RSI crossing thresholds\n", + " if last_rsi:\n", + " # RSI moving from oversold to normal (buy signal)\n", + " if last_rsi <= self.oversold_threshold and rsi > self.oversold_threshold:\n", + " strength = min(0.9, (self.oversold_threshold - last_rsi) / 10 + 0.5)\n", + " return self._generate_signal(\n", + " symbol, \"BUY\", strength,\n", + " f\"RSI bounce from oversold: {last_rsi:.1f} โ†’ {rsi:.1f}\",\n", + " 0.05\n", + " )\n", + " \n", + " # RSI moving from overbought to normal (sell signal)\n", + " elif last_rsi >= self.overbought_threshold and rsi < self.overbought_threshold:\n", + " strength = min(0.9, (last_rsi - self.overbought_threshold) / 10 + 0.5)\n", + " return self._generate_signal(\n", + " symbol, \"SELL\", strength,\n", + " f\"RSI drop from overbought: {last_rsi:.1f} โ†’ {rsi:.1f}\",\n", + " 0.05\n", + " )\n", + " \n", + " return None\n", + "\n", + "class BollingerBandsStrategy(TradingStrategy):\n", + " \"\"\"Bollinger Bands mean reversion strategy\"\"\"\n", + " \n", + " def __init__(self):\n", + " super().__init__(\"Bollinger_Bands\")\n", + " self.last_position = {} # Track position relative to bands\n", + " \n", + " def analyze(self, symbol: str, price: float, indicators: Dict[str, Any]) -> Optional[Dict[str, Any]]:\n", + " bb_upper = indicators.get('bb_upper')\n", + " bb_lower = indicators.get('bb_lower')\n", + " bb_middle = indicators.get('bb_middle')\n", + " \n", + " if not all([bb_upper, bb_lower, bb_middle]):\n", + " return None\n", + " \n", + " # Determine position relative to bands\n", + " if price <= bb_lower:\n", + " current_position = \"BELOW_LOWER\"\n", + " elif price >= bb_upper:\n", + " current_position = \"ABOVE_UPPER\"\n", + " else:\n", + " current_position = \"MIDDLE\"\n", + " \n", + " last_position = self.last_position.get(symbol)\n", + " self.last_position[symbol] = current_position\n", + " \n", + " if last_position:\n", + " # Price bouncing off lower band (buy signal)\n", + " if last_position == \"BELOW_LOWER\" and current_position == \"MIDDLE\":\n", + " distance_pct = ((price - bb_lower) / bb_lower) * 100\n", + " strength = min(0.8, distance_pct / 2 + 0.4)\n", + " return self._generate_signal(\n", + " symbol, \"BUY\", strength,\n", + " f\"Bounce off lower BB: ${bb_lower:.2f} โ†’ ${price:.2f}\",\n", + " 0.08\n", + " )\n", + " \n", + " # Price falling from upper band (sell signal)\n", + " elif last_position == \"ABOVE_UPPER\" and current_position == \"MIDDLE\":\n", + " distance_pct = ((bb_upper - price) / bb_upper) * 100\n", + " strength = min(0.8, distance_pct / 2 + 0.4)\n", + " return self._generate_signal(\n", + " symbol, \"SELL\", strength,\n", + " f\"Rejection at upper BB: ${bb_upper:.2f} โ†’ ${price:.2f}\",\n", + " 0.08\n", + " )\n", + " \n", + " return None\n", + "\n", + "class AlgorithmEngine:\n", + " \"\"\"Algorithm Engine - the 'A' in MARKET\"\"\"\n", + " \n", + " def __init__(self, event_queue: EventQueue):\n", + " self.event_queue = event_queue\n", + " self.name = \"algorithm_engine\"\n", + " \n", + " # Trading strategies\n", + " self.strategies: List[TradingStrategy] = [\n", + " MovingAverageCrossoverStrategy(5, 20),\n", + " RSIStrategy(30, 70),\n", + " BollingerBandsStrategy()\n", + " ]\n", + " \n", + " # Algorithm state\n", + " self.total_signals_generated = 0\n", + " self.signals_by_symbol = defaultdict(int)\n", + " self.last_analysis_time = {}\n", + " \n", + " # Subscribe to market data\n", + " self.event_queue.subscribe(\"market_data\", self.on_market_data)\n", + " \n", + " print(f\"๐Ÿง  {self.name}: Algorithm Engine initialized with {len(self.strategies)} strategies\")\n", + " for strategy in self.strategies:\n", + " print(f\" โ€ข {strategy.name}\")\n", + " \n", + " def on_market_data(self, event: Event):\n", + " \"\"\"Process market data and generate trading signals\"\"\"\n", + " symbol = event.data['symbol']\n", + " price = event.data['price']\n", + " indicators = event.data['indicators']\n", + " \n", + " self.last_analysis_time[symbol] = datetime.now()\n", + " \n", + " print(f\"๐Ÿง  {self.name}: Analyzing {symbol} @ ${price:,.2f}\")\n", + " \n", + " # Run all strategies\n", + " signals = []\n", + " for strategy in self.strategies:\n", + " try:\n", + " signal = strategy.analyze(symbol, price, indicators)\n", + " if signal:\n", + " signals.append(signal)\n", + " print(f\" ๐Ÿ“ถ {strategy.name}: {signal['signal_type']} signal (strength: {signal['strength']:.2f})\")\n", + " print(f\" Reason: {signal['reason']}\")\n", + " except Exception as e:\n", + " print(f\" โŒ Error in {strategy.name}: {e}\")\n", + " \n", + " # Process signals\n", + " if signals:\n", + " # Combine signals using weighted voting\n", + " combined_signal = self._combine_signals(signals)\n", + " if combined_signal:\n", + " self._publish_trading_signal(combined_signal)\n", + " else:\n", + " print(f\" โž– No signals generated for {symbol}\")\n", + " \n", + " def _combine_signals(self, signals: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]:\n", + " \"\"\"Combine multiple signals into one using weighted voting\"\"\"\n", + " if not signals:\n", + " return None\n", + " \n", + " # Group signals by type\n", + " buy_signals = [s for s in signals if s['signal_type'] == 'BUY']\n", + " sell_signals = [s for s in signals if s['signal_type'] == 'SELL']\n", + " \n", + " # Calculate weighted strength for each side\n", + " buy_strength = sum(s['strength'] for s in buy_signals)\n", + " sell_strength = sum(s['strength'] for s in sell_signals)\n", + " \n", + " # Determine final signal\n", + " if buy_strength > sell_strength and buy_strength > 0.5:\n", + " # Combine buy signals\n", + " combined_strength = min(1.0, buy_strength / len(buy_signals))\n", + " reasons = [s['reason'] for s in buy_signals]\n", + " avg_quantity = sum(s.get('suggested_quantity', 0.1) for s in buy_signals) / len(buy_signals)\n", + " \n", + " return {\n", + " 'strategy': 'COMBINED',\n", + " 'symbol': signals[0]['symbol'],\n", + " 'signal_type': 'BUY',\n", + " 'strength': combined_strength,\n", + " 'reason': f\"Combined: {'; '.join(reasons)}\",\n", + " 'suggested_quantity': avg_quantity,\n", + " 'contributing_strategies': [s['strategy'] for s in buy_signals],\n", + " 'timestamp': datetime.now()\n", + " }\n", + " \n", + " elif sell_strength > buy_strength and sell_strength > 0.5:\n", + " # Combine sell signals\n", + " combined_strength = min(1.0, sell_strength / len(sell_signals))\n", + " reasons = [s['reason'] for s in sell_signals]\n", + " avg_quantity = sum(s.get('suggested_quantity', 0.1) for s in sell_signals) / len(sell_signals)\n", + " \n", + " return {\n", + " 'strategy': 'COMBINED',\n", + " 'symbol': signals[0]['symbol'],\n", + " 'signal_type': 'SELL',\n", + " 'strength': combined_strength,\n", + " 'reason': f\"Combined: {'; '.join(reasons)}\",\n", + " 'suggested_quantity': avg_quantity,\n", + " 'contributing_strategies': [s['strategy'] for s in sell_signals],\n", + " 'timestamp': datetime.now()\n", + " }\n", + " \n", + " return None # Conflicting or weak signals\n", + " \n", + " def _publish_trading_signal(self, signal: Dict[str, Any]):\n", + " \"\"\"Publish trading signal event\"\"\"\n", + " self.total_signals_generated += 1\n", + " self.signals_by_symbol[signal['symbol']] += 1\n", + " \n", + " signal_event = Event(\n", + " event_type=\"trading_signal\",\n", + " data=signal,\n", + " source=self.name\n", + " )\n", + " \n", + " self.event_queue.publish(signal_event)\n", + " \n", + " print(f\"\\n๐ŸŽฏ {self.name}: TRADING SIGNAL GENERATED!\")\n", + " print(f\" Symbol: {signal['symbol']}\")\n", + " print(f\" Type: {signal['signal_type']}\")\n", + " print(f\" Strength: {signal['strength']:.2f}\")\n", + " print(f\" Quantity: {signal['suggested_quantity']:.4f}\")\n", + " print(f\" Reason: {signal['reason']}\")\n", + " if 'contributing_strategies' in signal:\n", + " print(f\" Strategies: {', '.join(signal['contributing_strategies'])}\")\n", + " print()\n", + " \n", + " def get_stats(self) -> Dict[str, Any]:\n", + " \"\"\"Get algorithm engine statistics\"\"\"\n", + " strategy_stats = {}\n", + " for strategy in self.strategies:\n", + " strategy_stats[strategy.name] = {\n", + " 'signals_generated': strategy.signals_generated,\n", + " 'last_signal_time': strategy.last_signal_time.isoformat() if strategy.last_signal_time else None\n", + " }\n", + " \n", + " return {\n", + " 'total_signals_generated': self.total_signals_generated,\n", + " 'signals_by_symbol': dict(self.signals_by_symbol),\n", + " 'strategies_count': len(self.strategies),\n", + " 'strategy_stats': strategy_stats\n", + " }\n", + "\n", + "# Create Algorithm Engine\n", + "algorithm = AlgorithmEngine(event_queue)\n", + "print(f\"\\n๐Ÿ“Š Algorithm Engine Stats: {json.dumps(algorithm.get_stats(), indent=2, default=str)}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Part 3: System Tracker (Monitoring & Logging)\n", + "\n", + "Let's build the final component - the Tracker that monitors our entire system:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import logging\n", + "from typing import Counter\n", + "\n", + "class SystemTracker:\n", + " \"\"\"System Tracker - the 'T' in MARKET\"\"\"\n", + " \n", + " def __init__(self, event_queue: EventQueue):\n", + " self.event_queue = event_queue\n", + " self.name = \"system_tracker\"\n", + " \n", + " # Performance metrics\n", + " self.system_start_time = datetime.now()\n", + " self.total_events_processed = 0\n", + " self.events_by_type = defaultdict(int)\n", + " self.events_by_source = defaultdict(int)\n", + " \n", + " # System health monitoring\n", + " self.error_count = 0\n", + " self.warning_count = 0\n", + " self.last_error_time = None\n", + " \n", + " # Trading performance\n", + " self.signals_tracked = 0\n", + " self.orders_tracked = 0\n", + " self.fills_tracked = 0\n", + " self.risk_violations_tracked = 0\n", + " \n", + " # Component health\n", + " self.component_last_seen = {}\n", + " self.component_event_counts = defaultdict(int)\n", + " \n", + " # Subscribe to all events for monitoring\n", + " self.event_queue.subscribe(\"market_data\", self.on_market_data)\n", + " self.event_queue.subscribe(\"trading_signal\", self.on_trading_signal)\n", + " self.event_queue.subscribe(\"order_fill\", self.on_order_fill)\n", + " self.event_queue.subscribe(\"risk_violation\", self.on_risk_violation)\n", + " self.event_queue.subscribe(\"risk_warning\", self.on_risk_warning)\n", + " self.event_queue.subscribe(\"portfolio_update\", self.on_portfolio_update)\n", + " \n", + " print(f\"๐Ÿ“Š {self.name}: System Tracker initialized\")\n", + " print(f\" Monitoring started at: {self.system_start_time.strftime('%Y-%m-%d %H:%M:%S')}\")\n", + " \n", + " def on_market_data(self, event: Event):\n", + " \"\"\"Track market data events\"\"\"\n", + " self._track_event(event)\n", + " symbol = event.data['symbol']\n", + " price = event.data['price']\n", + " \n", + " # Log significant price movements\n", + " change_pct = event.data['indicators'].get('price_change_pct', 0)\n", + " if abs(change_pct) > 2.0: # > 2% change\n", + " print(f\"๐Ÿ“Š {self.name}: Significant price move - {symbol}: {change_pct:+.2f}%\")\n", + " \n", + " def on_trading_signal(self, event: Event):\n", + " \"\"\"Track trading signals\"\"\"\n", + " self._track_event(event)\n", + " self.signals_tracked += 1\n", + " \n", + " signal_data = event.data\n", + " print(f\"๐Ÿ“Š {self.name}: Signal #{self.signals_tracked} - {signal_data['symbol']} {signal_data['signal_type']} (strength: {signal_data['strength']:.2f})\")\n", + " \n", + " def on_order_fill(self, event: Event):\n", + " \"\"\"Track order fills\"\"\"\n", + " self._track_event(event)\n", + " self.fills_tracked += 1\n", + " \n", + " fill_data = event.data\n", + " fill_value = fill_data['fill_quantity'] * fill_data['fill_price']\n", + " print(f\"๐Ÿ“Š {self.name}: Fill #{self.fills_tracked} - {fill_data['symbol']} ${fill_value:,.2f}\")\n", + " \n", + " def on_risk_violation(self, event: Event):\n", + " \"\"\"Track risk violations\"\"\"\n", + " self._track_event(event)\n", + " self.risk_violations_tracked += 1\n", + " self.error_count += 1\n", + " self.last_error_time = datetime.now()\n", + " \n", + " print(f\"๐Ÿšจ {self.name}: RISK VIOLATION #{self.risk_violations_tracked} - {event.data.get('violations', ['Unknown violation'])}\")\n", + " \n", + " def on_risk_warning(self, event: Event):\n", + " \"\"\"Track risk warnings\"\"\"\n", + " self._track_event(event)\n", + " self.warning_count += 1\n", + " \n", + " print(f\"โš ๏ธ {self.name}: Risk warning - {event.data.get('warnings', ['Unknown warning'])}\")\n", + " \n", + " def on_portfolio_update(self, event: Event):\n", + " \"\"\"Track portfolio updates\"\"\"\n", + " self._track_event(event)\n", + " \n", + " update_type = event.data.get('update_type')\n", + " if update_type == 'trade_executed':\n", + " portfolio_value = event.data.get('portfolio_value', 0)\n", + " total_pnl = event.data.get('total_pnl', 0)\n", + " print(f\"๐Ÿ“Š {self.name}: Portfolio update - Value: ${portfolio_value:,.2f}, P&L: ${total_pnl:+,.2f}\")\n", + " \n", + " def _track_event(self, event: Event):\n", + " \"\"\"Common event tracking logic\"\"\"\n", + " self.total_events_processed += 1\n", + " self.events_by_type[event.event_type] += 1\n", + " self.events_by_source[event.source] += 1\n", + " self.component_last_seen[event.source] = datetime.now()\n", + " self.component_event_counts[event.source] += 1\n", + " \n", + " def check_system_health(self) -> Dict[str, Any]:\n", + " \"\"\"Check overall system health\"\"\"\n", + " current_time = datetime.now()\n", + " uptime = current_time - self.system_start_time\n", + " \n", + " # Check component health\n", + " component_health = {}\n", + " for component, last_seen in self.component_last_seen.items():\n", + " time_since_last_seen = (current_time - last_seen).total_seconds()\n", + " \n", + " if time_since_last_seen < 60: # Active if seen in last minute\n", + " status = \"HEALTHY\"\n", + " elif time_since_last_seen < 300: # Warning if not seen in 5 minutes\n", + " status = \"WARNING\"\n", + " else:\n", + " status = \"CRITICAL\"\n", + " \n", + " component_health[component] = {\n", + " 'status': status,\n", + " 'last_seen': last_seen.isoformat(),\n", + " 'seconds_ago': int(time_since_last_seen),\n", + " 'event_count': self.component_event_counts[component]\n", + " }\n", + " \n", + " # Calculate system health score\n", + " healthy_components = sum(1 for h in component_health.values() if h['status'] == 'HEALTHY')\n", + " total_components = len(component_health)\n", + " health_score = (healthy_components / total_components * 100) if total_components > 0 else 0\n", + " \n", + " return {\n", + " 'system_uptime_seconds': int(uptime.total_seconds()),\n", + " 'system_uptime_formatted': str(uptime).split('.')[0],\n", + " 'health_score': health_score,\n", + " 'total_events_processed': self.total_events_processed,\n", + " 'events_per_minute': (self.total_events_processed / (uptime.total_seconds() / 60)) if uptime.total_seconds() > 0 else 0,\n", + " 'error_count': self.error_count,\n", + " 'warning_count': self.warning_count,\n", + " 'last_error_time': self.last_error_time.isoformat() if self.last_error_time else None,\n", + " 'component_health': component_health\n", + " }\n", + " \n", + " def get_performance_summary(self) -> Dict[str, Any]:\n", + " \"\"\"Get comprehensive performance summary\"\"\"\n", + " return {\n", + " 'system_health': self.check_system_health(),\n", + " 'trading_metrics': {\n", + " 'signals_generated': self.signals_tracked,\n", + " 'orders_filled': self.fills_tracked,\n", + " 'risk_violations': self.risk_violations_tracked,\n", + " 'conversion_rate': (self.fills_tracked / self.signals_tracked * 100) if self.signals_tracked > 0 else 0\n", + " },\n", + " 'event_statistics': {\n", + " 'total_events': self.total_events_processed,\n", + " 'events_by_type': dict(self.events_by_type),\n", + " 'events_by_source': dict(self.events_by_source)\n", + " },\n", + " 'component_activity': dict(self.component_event_counts)\n", + " }\n", + " \n", + " def print_real_time_dashboard(self):\n", + " \"\"\"Print a real-time system dashboard\"\"\"\n", + " health = self.check_system_health()\n", + " \n", + " print(\"\\n\" + \"=\" * 80)\n", + " print(f\"๐Ÿ“Š SYSTEM DASHBOARD - {datetime.now().strftime('%H:%M:%S')}\")\n", + " print(\"=\" * 80)\n", + " \n", + " print(f\"๐ŸŸข System Health: {health['health_score']:.1f}% | Uptime: {health['system_uptime_formatted']}\")\n", + " print(f\"๐Ÿ“ˆ Events: {health['total_events_processed']} ({health['events_per_minute']:.1f}/min)\")\n", + " print(f\"๐ŸŽฏ Trading: {self.signals_tracked} signals โ†’ {self.fills_tracked} fills\")\n", + " print(f\"๐Ÿšจ Issues: {health['error_count']} errors, {health['warning_count']} warnings\")\n", + " \n", + " print(\"\\n๐Ÿ“ก Component Status:\")\n", + " for component, status in health['component_health'].items():\n", + " status_icon = \"๐ŸŸข\" if status['status'] == 'HEALTHY' else \"๐ŸŸก\" if status['status'] == 'WARNING' else \"๐Ÿ”ด\"\n", + " print(f\" {status_icon} {component}: {status['event_count']} events ({status['seconds_ago']}s ago)\")\n", + " \n", + " print(\"=\" * 80)\n", + "\n", + "# Create System Tracker\n", + "tracker = SystemTracker(event_queue)\n", + "print(f\"\\n๐Ÿ“Š System Tracker initialized and monitoring all components!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Part 4: Complete MARKET Trading Bot Demo\n", + "\n", + "Now let's put it all together and run our complete event-driven trading bot!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# First, let's create simplified versions of our other components for this demo\n", + "class SimplePortfolioManager:\n", + " \"\"\"Simplified portfolio manager for demo\"\"\"\n", + " def __init__(self, event_queue: EventQueue, initial_balance: float = 100000):\n", + " self.event_queue = event_queue\n", + " self.positions = {}\n", + " self.cash_balance = initial_balance\n", + " self.initial_balance = initial_balance\n", + " event_queue.subscribe(\"order_fill\", self.on_order_fill)\n", + " print(f\"๐Ÿ’ผ Portfolio initialized with ${initial_balance:,.2f}\")\n", + " \n", + " def on_order_fill(self, event: Event):\n", + " symbol = event.data['symbol']\n", + " side = event.data['side']\n", + " quantity = event.data['fill_quantity']\n", + " price = event.data['fill_price']\n", + " fees = event.data.get('fees', 0)\n", + " \n", + " if symbol not in self.positions:\n", + " self.positions[symbol] = Position(symbol)\n", + " \n", + " trade_quantity = quantity if side == \"BUY\" else -quantity\n", + " trade_value = quantity * price\n", + " \n", + " if side == \"BUY\":\n", + " self.cash_balance -= (trade_value + fees)\n", + " else:\n", + " self.cash_balance += (trade_value - fees)\n", + " \n", + " self.positions[symbol].add_trade(trade_quantity, price, fees)\n", + " \n", + " # Update market price\n", + " self.positions[symbol].update_market_price(price)\n", + " \n", + " # Publish portfolio update\n", + " portfolio_event = Event(\n", + " event_type=\"portfolio_update\",\n", + " data={\n", + " \"update_type\": \"trade_executed\",\n", + " \"portfolio_value\": self.total_portfolio_value,\n", + " \"total_pnl\": self.total_pnl,\n", + " \"cash_balance\": self.cash_balance\n", + " },\n", + " source=\"portfolio_manager\"\n", + " )\n", + " self.event_queue.publish(portfolio_event)\n", + " \n", + " @property\n", + " def total_portfolio_value(self):\n", + " return self.cash_balance + sum(pos.market_value for pos in self.positions.values())\n", + " \n", + " @property\n", + " def total_pnl(self):\n", + " return self.total_portfolio_value - self.initial_balance\n", + "\n", + "class SimpleRiskController:\n", + " \"\"\"Simplified risk controller for demo\"\"\"\n", + " def __init__(self, event_queue: EventQueue, portfolio: SimplePortfolioManager):\n", + " self.event_queue = event_queue\n", + " self.portfolio = portfolio\n", + " self.max_position_size_pct = 20.0\n", + " self.violations = 0\n", + " event_queue.subscribe(\"trading_signal\", self.on_trading_signal)\n", + " print(f\"๐Ÿ›ก๏ธ Risk Controller initialized (max position: {self.max_position_size_pct}%)\")\n", + " \n", + " def on_trading_signal(self, event: Event):\n", + " signal = event.data\n", + " symbol = signal['symbol']\n", + " signal_type = signal['signal_type']\n", + " suggested_quantity = signal.get('suggested_quantity', 0.1)\n", + " \n", + " # Get current market price (simplified)\n", + " current_price = market_data.get_current_price(symbol) or 43000\n", + " order_value = suggested_quantity * current_price\n", + " position_pct = (order_value / self.portfolio.total_portfolio_value) * 100\n", + " \n", + " if signal_type == \"BUY\" and position_pct > self.max_position_size_pct:\n", + " # Risk violation\n", + " self.violations += 1\n", + " risk_event = Event(\n", + " event_type=\"risk_violation\",\n", + " data={\n", + " \"violations\": [f\"Position size too large: {position_pct:.1f}% > {self.max_position_size_pct}%\"],\n", + " \"action\": \"ORDER_BLOCKED\"\n", + " },\n", + " source=\"risk_controller\"\n", + " )\n", + " self.event_queue.publish(risk_event)\n", + " print(f\"๐Ÿ›ก๏ธ Risk Controller: BLOCKED {signal_type} signal for {symbol} (position too large)\")\n", + " else:\n", + " # Approve and create order\n", + " print(f\"๐Ÿ›ก๏ธ Risk Controller: APPROVED {signal_type} signal for {symbol}\")\n", + " self._create_order(signal, current_price)\n", + " \n", + " def _create_order(self, signal: Dict[str, Any], current_price: float):\n", + " \"\"\"Create and simulate order execution\"\"\"\n", + " symbol = signal['symbol']\n", + " signal_type = signal['signal_type']\n", + " quantity = signal.get('suggested_quantity', 0.1)\n", + " \n", + " # Simulate order creation and immediate fill\n", + " order = Order(\n", + " symbol=symbol,\n", + " side=OrderSide.BUY if signal_type == \"BUY\" else OrderSide.SELL,\n", + " order_type=OrderType.MARKET,\n", + " quantity=quantity\n", + " )\n", + " \n", + " # Simulate fill with slight slippage\n", + " slippage = random.uniform(-0.002, 0.002) # ยฑ0.2% slippage\n", + " fill_price = current_price * (1 + slippage)\n", + " fees = quantity * fill_price * 0.001 # 0.1% fee\n", + " \n", + " # Publish order fill event\n", + " fill_event = Event(\n", + " event_type=\"order_fill\",\n", + " data={\n", + " \"order_id\": order.order_id,\n", + " \"symbol\": symbol,\n", + " \"side\": signal_type,\n", + " \"fill_quantity\": quantity,\n", + " \"fill_price\": fill_price,\n", + " \"fees\": fees\n", + " },\n", + " source=\"exchange_simulator\"\n", + " )\n", + " \n", + " self.event_queue.publish(fill_event)\n", + " print(f\"๐Ÿ’ฐ Order executed: {signal_type} {quantity} {symbol} @ ${fill_price:.2f}\")\n", + "\n", + "# Create simplified portfolio and risk management\n", + "simple_portfolio = SimplePortfolioManager(event_queue, 100000)\n", + "simple_risk = SimpleRiskController(event_queue, simple_portfolio)\n", + "\n", + "print(\"\\n๐ŸŽฌ COMPLETE MARKET TRADING BOT - LIVE DEMO!\")\n", + "print(\"=\" * 70)\n", + "print(\"All components initialized and connected through events:\")\n", + "print(\"๐Ÿ“Š Market Data Handler โ†’ ๐Ÿง  Algorithm Engine โ†’ ๐Ÿ›ก๏ธ Risk Controller โ†’ ๐Ÿ’ผ Portfolio Manager\")\n", + "print(\"With ๐Ÿ“ˆ System Tracker monitoring everything!\")\n", + "print(\"=\" * 70)\n", + "\n", + "# Simulate realistic market data and watch the complete system work\n", + "btc_prices = [\n", + " 43000, 43050, 43120, 43200, 43150, 43280, 43350, 43300, # Gradual uptrend\n", + " 43450, 43520, 43480, 43600, 43550, 43700, 43650, 43750, # Continued rise\n", + " 43800, 43720, 43650, 43580, 43500, 43420, 43350, 43280, # Reversal\n", + " 43200, 43150, 43080, 43000, 42950, 42880, 42820, 42750 # Decline\n", + "]\n", + "\n", + "print(\"\\n๐ŸŽฏ Starting market simulation...\\n\")\n", + "\n", + "for i, price in enumerate(btc_prices):\n", + " print(f\"\\n๐Ÿ“ˆ Market Update #{i+1}:\")\n", + " print(\"-\" * 40)\n", + " \n", + " # Process market data (this triggers the entire chain!)\n", + " market_data.process_price_update(\"BTC/USDT\", price, random.uniform(0.5, 2.0))\n", + " \n", + " # Small delay to make it readable\n", + " time.sleep(0.3)\n", + " \n", + " # Show system dashboard every 10 updates\n", + " if (i + 1) % 10 == 0:\n", + " tracker.print_real_time_dashboard()\n", + " time.sleep(1)\n", + "\n", + "# Final system summary\n", + "print(\"\\n\" + \"=\" * 70)\n", + "print(\"๐ŸŽ‰ COMPLETE TRADING BOT DEMO FINISHED!\")\n", + "print(\"=\" * 70)\n", + "\n", + "# System performance\n", + "performance = tracker.get_performance_summary()\n", + "print(f\"\\n๐Ÿ“Š Final Performance Summary:\")\n", + "print(json.dumps(performance, indent=2, default=str))\n", + "\n", + "# Portfolio results\n", + "print(f\"\\n๐Ÿ’ฐ Portfolio Results:\")\n", + "print(f\" Initial Balance: ${simple_portfolio.initial_balance:,.2f}\")\n", + "print(f\" Final Value: ${simple_portfolio.total_portfolio_value:,.2f}\")\n", + "print(f\" Total P&L: ${simple_portfolio.total_pnl:+,.2f}\")\n", + "print(f\" Cash Balance: ${simple_portfolio.cash_balance:,.2f}\")\n", + "\n", + "if simple_portfolio.positions:\n", + " print(f\"\\n๐Ÿ“Š Final Positions:\")\n", + " for symbol, position in simple_portfolio.positions.items():\n", + " if position.quantity != 0:\n", + " print(f\" {symbol}: {position.quantity:+.4f} @ ${position.average_price:.2f} (P&L: ${position.unrealized_pnl:+,.2f})\")\n", + "\n", + "print(f\"\\n๐ŸŽฏ Key Achievements:\")\n", + "print(f\" โ€ข Processed {performance['event_statistics']['total_events']} events\")\n", + "print(f\" โ€ข Generated {performance['trading_metrics']['signals_generated']} trading signals\")\n", + "print(f\" โ€ข Executed {performance['trading_metrics']['orders_filled']} orders\")\n", + "print(f\" โ€ข Signal-to-fill conversion: {performance['trading_metrics']['conversion_rate']:.1f}%\")\n", + "print(f\" โ€ข Risk violations prevented: {performance['trading_metrics']['risk_violations']}\")\n", + "print(f\" โ€ข System uptime: {performance['system_health']['system_uptime_formatted']}\")\n", + "print(f\" โ€ข System health: {performance['system_health']['health_score']:.1f}%\")\n", + "\n", + "print(\"\\nโœจ Your complete MARKET trading bot is fully operational!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Part 5: Your Turn - Advanced Features\n", + "\n", + "**Challenge:** Extend the complete trading system with advanced features!\n", + "\n", + "Choose one or more of these challenges:\n", + "\n", + "1. **Multi-Symbol Trading**: Add support for trading multiple cryptocurrencies simultaneously\n", + "2. **Advanced Strategies**: Implement MACD, Stochastic, or custom momentum strategies \n", + "3. **Machine Learning**: Add a simple ML model for price prediction\n", + "4. **Backtesting Integration**: Connect with historical data for strategy validation\n", + "5. **Real Exchange Integration**: Connect to a real crypto exchange API (testnet)\n", + "6. **Advanced Risk Management**: Implement correlation-based position sizing\n", + "7. **Performance Attribution**: Track which strategies are most profitable" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Challenge 1: Multi-Symbol Trading\n", + "def add_multi_symbol_support(market_data: MarketDataHandler, symbols: List[str]):\n", + " \"\"\"Add support for multiple trading symbols\"\"\"\n", + " # YOUR CODE HERE\n", + " # Hint: Process multiple price feeds simultaneously\n", + " pass\n", + "\n", + "# Challenge 2: MACD Strategy\n", + "class MACDStrategy(TradingStrategy):\n", + " \"\"\"MACD crossover strategy\"\"\"\n", + " def __init__(self, fast_period: int = 12, slow_period: int = 26, signal_period: int = 9):\n", + " super().__init__(f\"MACD_{fast_period}_{slow_period}_{signal_period}\")\n", + " # YOUR CODE HERE\n", + " pass\n", + " \n", + " def analyze(self, symbol: str, price: float, indicators: Dict[str, Any]) -> Optional[Dict[str, Any]]:\n", + " # YOUR CODE HERE\n", + " # Hint: Look for MACD line crossing signal line\n", + " pass\n", + "\n", + "# Challenge 3: Simple ML Price Predictor\n", + "class MLPricePredictionStrategy(TradingStrategy):\n", + " \"\"\"Machine learning based price prediction\"\"\"\n", + " def __init__(self):\n", + " super().__init__(\"ML_Price_Prediction\")\n", + " # YOUR CODE HERE\n", + " # Hint: Use sklearn's LinearRegression or simple moving average prediction\n", + " pass\n", + " \n", + " def analyze(self, symbol: str, price: float, indicators: Dict[str, Any]) -> Optional[Dict[str, Any]]:\n", + " # YOUR CODE HERE\n", + " pass\n", + "\n", + "# Challenge 4: Strategy Performance Tracker\n", + "class StrategyPerformanceTracker:\n", + " \"\"\"Track performance of individual strategies\"\"\"\n", + " def __init__(self, event_queue: EventQueue):\n", + " self.event_queue = event_queue\n", + " self.strategy_performance = defaultdict(lambda: {'signals': 0, 'profits': 0, 'losses': 0})\n", + " # YOUR CODE HERE\n", + " pass\n", + " \n", + " def track_strategy_result(self, strategy_name: str, profit_loss: float):\n", + " # YOUR CODE HERE\n", + " # Hint: Track which strategies generate the most profitable signals\n", + " pass\n", + "\n", + "# Challenge 5: Real Exchange Connection (Paper Trading)\n", + "class PaperTradingExchange:\n", + " \"\"\"Paper trading exchange for testing with real market data\"\"\"\n", + " def __init__(self, event_queue: EventQueue):\n", + " self.event_queue = event_queue\n", + " # YOUR CODE HERE\n", + " # Hint: Use ccxt library to get real prices but simulate trades\n", + " pass\n", + " \n", + " def get_real_market_data(self, symbol: str):\n", + " # YOUR CODE HERE\n", + " # Hint: Fetch real prices from exchanges like Binance\n", + " pass\n", + "\n", + "# Test your implementations here\n", + "print(\"๐Ÿงช Test your advanced features here!\")\n", + "\n", + "# Example tests:\n", + "# Add MACD strategy to algorithm engine\n", + "# macd_strategy = MACDStrategy()\n", + "# algorithm.strategies.append(macd_strategy)\n", + "\n", + "# Test multi-symbol trading\n", + "# add_multi_symbol_support(market_data, [\"BTC/USDT\", \"ETH/USDT\", \"BNB/USDT\"])\n", + "\n", + "# Initialize performance tracker\n", + "# perf_tracker = StrategyPerformanceTracker(event_queue)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Congratulations!\n", + "\n", + "You've built a complete, professional-grade event-driven trading bot! Here's what you accomplished:\n", + "\n", + "โœ… **Complete MARKET Architecture**: All 6 components working together seamlessly \n", + "โœ… **Advanced Market Data Processing**: Real-time indicators (SMA, EMA, RSI, Bollinger Bands) \n", + "โœ… **Sophisticated Algorithm Engine**: Multiple strategies with signal combination \n", + "โœ… **Professional System Monitoring**: Real-time dashboards and performance tracking \n", + "โœ… **Full Event-Driven Integration**: Loose coupling with robust error handling \n", + "โœ… **Realistic Trading Simulation**: Complete market simulation with slippage and fees \n", + "\n", + "## Your Trading Bot Features:\n", + "\n", + "### ๐Ÿ“Š **Market Data Handler**\n", + "- Real-time price processing with technical indicators\n", + "- SMA, EMA, RSI, Bollinger Bands, MACD calculation\n", + "- Price history management and volatility tracking\n", + "\n", + "### ๐Ÿง  **Algorithm Engine** \n", + "- Multiple trading strategies (MA Crossover, RSI, Bollinger Bands)\n", + "- Signal combination with weighted voting\n", + "- Strategy performance tracking\n", + "\n", + "### ๐Ÿ›ก๏ธ **Risk Management**\n", + "- Position size limits and cash requirements\n", + "- Real-time risk monitoring and violation prevention\n", + "- Automatic order blocking for dangerous trades\n", + "\n", + "### ๐Ÿ’ผ **Portfolio Management**\n", + "- Real-time position tracking and P&L calculation\n", + "- Multi-asset portfolio management\n", + "- Performance attribution and reporting\n", + "\n", + "### ๐Ÿ“ˆ **System Monitoring**\n", + "- Real-time system health dashboards\n", + "- Component status monitoring\n", + "- Performance metrics and error tracking\n", + "\n", + "## What You Can Do Next:\n", + "\n", + "1. **Connect to Real Data**: Use APIs like Binance or Coinbase for live market data\n", + "2. **Add More Strategies**: Implement advanced strategies like mean reversion, momentum, arbitrage\n", + "3. **Machine Learning**: Add ML models for price prediction and signal generation\n", + "4. **Backtesting**: Test your strategies against historical data\n", + "5. **Paper Trading**: Connect to real exchanges in paper trading mode\n", + "6. **Multi-Asset**: Expand to trade stocks, forex, commodities\n", + "7. **Cloud Deployment**: Deploy your bot to AWS/Azure for 24/7 operation\n", + "\n", + "## Achievement Unlocked:\n", + "\n", + "**\"Professional Trading Bot Architect\"** ๐Ÿ—๏ธ๐Ÿ’ฐ\n", + "\n", + "You've mastered event-driven architecture, real-time data processing, algorithmic trading, risk management, and system monitoring. Your bot follows industry best practices and could serve as the foundation for a production trading system!\n", + "\n", + "---\n", + "*\"The best trading systems are not just profitable - they're reliable, scalable, and built to last.\"* ๐Ÿ—๏ธ๐Ÿ“ˆ๐Ÿ’Ž" + ] + } + ], + "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 +} diff --git a/Session_05/Functions/funcs.py b/Session_05/Functions/funcs.py new file mode 100644 index 0000000..ed1f858 --- /dev/null +++ b/Session_05/Functions/funcs.py @@ -0,0 +1,226 @@ +import ccxt +import pandas as pd +import time +import os +from dotenv import load_dotenv + +load_dotenv() +api_key = os.getenv('BINANCE_API_KEY') +api_secret = os.getenv('BINANCE_SECRET_KEY') + +exchange = ccxt.binance({ + 'apiKey': api_key, + 'secret': api_secret, + 'enableRateLimit': True, + 'options': { + 'defaultType': 'future', + } +}) + +symbol = 'APEUSDT' +pause_time = 60 +vol_repeat = 11 +vol_time = 5 +pos_size = 100 +params = {'timeInForce': 'GTC'} +target = 35 +max_loss = -55 +vol_decimal = 0.4 +timeframe = '4h' +limit = 100 +sma = 20 + +def ask_bid(symbol=symbol): + orderbook = exchange.fetch_order_book(symbol) + bid = orderbook['bids'][0][0] + ask = orderbook['asks'][0][0] + print(f'Ask price for {symbol}: {ask}') + return ask, bid + +def df_sma(symbol=symbol, timeframe=timeframe, limit=limit, sma=sma): + print('Starting technical indicators...') + bars = exchange.fetch_ohlcv(symbol, timeframe=timeframe, limit=limit) + df = pd.DataFrame(bars, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume']) + df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms') + + df[f'sma{sma}_{timeframe}'] = df['close'].rolling(sma).mean() + _, bid = ask_bid(symbol) + + df.loc[df[f'sma{sma}_{timeframe}'] > bid, 'sig'] = 'SELL' + df.loc[df[f'sma{sma}_{timeframe}'] < bid, 'sig'] = 'BUY' + + df['support'] = df[:-2]['close'].min() + df['resistance'] = df[:-2]['close'].max() + df['prev_close'] = df['close'].shift(1) + df['bullish_confirmation'] = df['close'] > df['prev_close'] + + return df + +def open_positions(symbol=symbol): + positions = exchange.fetch_positions([symbol]) + + for pos in positions: + if pos['symbol'] == symbol and float(pos['contracts']) != 0: + side = pos['side'] + size = pos['contracts'] + is_long = side == 'long' + print(f'Open position: {side} {size} contracts') + return positions, True, size, is_long, 0, {} + + return [], False, 0, None, 0, {} + +def kill_switch(symbol=symbol): + print(f'Starting kill switch for {symbol}') + + while True: + _, has_position, position_size, is_long, _, _ = open_positions(symbol) + + if not has_position: + break + + print(f'Closing position: {position_size} contracts, long: {is_long}') + + exchange.cancel_all_orders(symbol) + ask, bid = ask_bid(symbol) + position_size = float(position_size) + + if is_long: + exchange.create_limit_sell_order(symbol, position_size, ask, params) + print(f'SELL to close: {position_size} {symbol} at {ask}') + else: + exchange.create_limit_buy_order(symbol, position_size, bid, params) + print(f'BUY to close: {position_size} {symbol} at {bid}') + + print('Waiting 30 seconds for fill...') + time.sleep(30) + +def sleep_on_close(symbol=symbol, pause_time=pause_time): + closed_orders = exchange.fetch_closed_orders(symbol) + + for order in reversed(closed_orders): + if order['status'] == 'closed': + order_time = int(order['timestamp'] / 1000) + current_time = int(exchange.fetch_order_book(symbol)['timestamp'] / 1000) + time_since_trade = (current_time - order_time) / 60 + + print(f'Last trade was {time_since_trade:.1f} minutes ago') + + if time_since_trade < pause_time - 1: + print(f'Sleeping 60 seconds (recent trade)') + time.sleep(60) + else: + print('No sleep needed') + break + + print(f'Sleep check complete for {symbol}') + +def ob(symbol=symbol, vol_repeat=vol_repeat, vol_time=vol_time): + print(f'Analyzing order book volume for {symbol}...') + + volume_data = [] + + for i in range(vol_repeat): + orderbook = exchange.fetch_order_book(symbol) + + bid_volume = sum([bid[1] for bid in orderbook['bids']]) + ask_volume = sum([ask[1] for ask in orderbook['asks']]) + + volume_data.append({'bid_vol': bid_volume, 'ask_vol': ask_volume}) + + if i < vol_repeat - 1: + time.sleep(vol_time) + + print(f'Sample {i+1}: Bid Vol: {bid_volume}, Ask Vol: {ask_volume}') + + df = pd.DataFrame(volume_data) + total_bid_vol = df['bid_vol'].sum() + total_ask_vol = df['ask_vol'].sum() + + minutes = (vol_time * vol_repeat) / 60 + print(f'Total volume over {minutes:.1f} minutes:') + print(f'Bid: {total_bid_vol}, Ask: {total_ask_vol}') + + if total_bid_vol > total_ask_vol: + control_ratio = total_ask_vol / total_bid_vol + print(f'Bulls in control: {control_ratio:.3f}') + else: + control_ratio = total_bid_vol / total_ask_vol + print(f'Bears in control: {control_ratio:.3f}') + + _, has_position, _, is_long, _, _ = open_positions(symbol) + + if has_position: + volume_under_threshold = control_ratio < vol_decimal + position_type = 'long' if is_long else 'short' + print(f'In {position_type} position. Volume under threshold: {volume_under_threshold}') + return volume_under_threshold + else: + print('Not in position') + return None + +def pnl_close(symbol=symbol, target=target, max_loss=max_loss): + print(f'Checking PnL for {symbol}...') + + positions = exchange.fetch_positions([symbol]) + + position = None + for pos in positions: + if pos['symbol'] == symbol and float(pos['contracts']) != 0: + position = pos + break + + if position is None: + print('No position open') + return False, False, 0, None + + side = position['side'] + size = position['contracts'] + entry_price = float(position['entryPrice']) + leverage = float(position['leverage']) + is_long = side == 'long' + + _, current_price = ask_bid(symbol) + + if is_long: + price_diff = current_price - entry_price + else: + price_diff = entry_price - current_price + + try: + pnl_percent = ((price_diff / entry_price) * leverage) * 100 + except: + pnl_percent = 0 + + print(f'PnL: {pnl_percent:.2f}% (Entry: {entry_price}, Current: {current_price})') + + if pnl_percent > target: + print(f'Target hit! Checking volume before closing...') + volume_under_threshold = ob(symbol) + + if volume_under_threshold: + print(f'Volume too low, waiting 30s...') + time.sleep(30) + else: + print(f'Closing profitable position!') + kill_switch(symbol) + return True, True, size, is_long + + elif pnl_percent <= max_loss: + print(f'Max loss hit! Closing position immediately.') + kill_switch(symbol) + return True, True, size, is_long + + elif pnl_percent > 0: + print(f'In profit but target not reached') + else: + print(f'In loss but within acceptable range') + + # Stop loss check using 15m SMA + if True: # Position exists + df_15m = df_sma(symbol, '15m', 100, 20) + sma_15m = df_15m.iloc[-1][f'sma{sma}_15m'] + stop_loss_level = sma_15m * 1.008 + print(f'SMA stop loss level: {stop_loss_level}') + + print(f'PnL check complete for {symbol}') + return False, True, size, is_long \ No newline at end of file diff --git a/Session_05/Functions/functions.py b/Session_05/Functions/functions.py new file mode 100644 index 0000000..2dd0d1b --- /dev/null +++ b/Session_05/Functions/functions.py @@ -0,0 +1,144 @@ +import pandas as pd +from datetime import datetime, time +from pytz import timezone +from time import sleep +import math + +def in_timeframe(): + now = datetime.now(timezone('US/Eastern')).time() + day = datetime.today().weekday() + return (now >= time(9,30) and now < time(16)) and (day < 5 and day >= 0) + +def get_position(exchange, symbol): + positions = exchange.fetch_positions([symbol]) + + for pos in positions: + if pos['symbol'] == symbol and float(pos['contracts']) != 0: + position_info = pos + in_position = True + long = pos['side'] == 'long' + return position_info, in_position, long + + return {}, False, None + +def get_candle_df(exchange, symbol, timeframe, limit=55): + ohlcv = pd.DataFrame( + exchange.fetch_ohlcv(symbol, timeframe, limit=limit), + columns=['time', 'open', 'high', 'low', 'close', 'volume'] + ).set_index('time') + return ohlcv + +def calc_tr(df): + df['True_Range'] = df.ta.atr() + return df['True_Range'].iloc[-1] + +def calc_atr(df, length=None): + df['ATR'] = df.ta.atr(length=length) + +def calc_sup_res(df, length=20): + df['support'] = df['low'].rolling(window=length).min() + df['resistance'] = df['high'].rolling(window=length).max() + return df['support'].iloc[-1], df['resistance'].iloc[-1] + +def hit_target(price, tp, sl, long: bool): + if long: + if price >= tp: + print('TAKE PROFIT REACHED, CLOSING POSITION') + return True + elif price <= sl: + print('STOP LOSS REACHED, CLOSING POSITION') + return True + else: + if price <= tp: + print('TAKE PROFIT REACHED, CLOSING POSITION') + return True + elif price >= sl: + print('STOP LOSS REACHED, CLOSING POSITION') + return True + return False + +def close_position(exchange, symbol): + exchange.cancel_all_orders(symbol) + position, in_position, long = get_position(exchange, symbol) + + while in_position: + ticker = exchange.fetch_ticker(symbol) + + if long: + bid = ticker['bid'] + exchange.create_limit_sell_order( + symbol, + position['contracts'], + bid, + {'timeInForce': 'GTC', 'reduceOnly': True} + ) + print(f'SELL to close: {position["contracts"]} {symbol} at {bid}') + else: + ask = ticker['ask'] + exchange.create_limit_buy_order( + symbol, + position['contracts'], + ask, + {'timeInForce': 'GTC', 'reduceOnly': True} + ) + print(f'BUY to close: {position["contracts"]} {symbol} at {ask}') + + sleep(30) + position, in_position, long = get_position(exchange, symbol) + + exchange.cancel_all_orders(symbol) + sleep(60) + +def end_of_trading_week(): + now = datetime.now(timezone('US/Eastern')).time() + day = datetime.today().weekday() + return day == 4 and now >= time(16) and now < time(16,1) + +def get_extreme_of_consolidation(df, percent): + for index, row in df.iloc[::-1].iterrows(): + if (row['True_Range'] / row['close']) * 100 > percent: + consolidation_data = df[df.index > index] + return consolidation_data.low.min(), consolidation_data.high.max() + return df.low.min(), df.high.max() + +def calc_stoch_rsi(df, lookback=14): + rsi = df.ta.rsi(length=lookback) + min_rsi = rsi.tail(lookback).min() + max_rsi = rsi.tail(lookback).max() + df['stoch_rsi'] = (rsi.iloc[-1] - min_rsi) / (max_rsi - min_rsi) + +def calc_nadarya(df, bandwidth=8, source='close'): + src = df[source] + out = [] + + for i, v1 in src.items(): + tsum = 0 + sumw = 0 + for j, v2 in src.items(): + w = math.exp(-(math.pow(i-j, 2) / (bandwidth * bandwidth * 2))) + tsum += v2 * w + sumw += w + out.append(tsum / sumw) + + df['nadarya'] = out + d = df['nadarya'].rolling(window=2).apply(lambda x: x.iloc[1] - x.iloc[0]) + df['nadarya_buy'] = (d > 0) & (d.shift(1) < 0) + df['nadarya_sell'] = (d < 0) & (d.shift(1) > 0) + + return df['nadarya_buy'].iloc[-1], df['nadarya_sell'].iloc[-1] + +def is_oversold(rsi, window=14, times=1, target=10): + rsi_window = rsi.tail(window) + rsi_crossed = [ + v for ind, v in enumerate(rsi_window.values) + if v <= target and rsi_window.values[ind-1] >= target and ind > 0 + ] + return len(rsi_crossed) >= times + +def is_overbought(rsi, window=14, times=1, target=90): + rsi_window = rsi.tail(window) + rsi_crossed = [ + v for ind, v in enumerate(rsi_window.values) + if v >= target and rsi_window.values[ind-1] <= target and ind > 0 + ] + return len(rsi_crossed) >= times \ No newline at end of file diff --git a/Session_05/_files_ b/Session_05/_files_ deleted file mode 100644 index e69de29..0000000 diff --git a/Session_05/bots/.env b/Session_05/bots/.env new file mode 100644 index 0000000..5699bf6 --- /dev/null +++ b/Session_05/bots/.env @@ -0,0 +1,2 @@ +BINANCE_API_KEY = "isV8Eo73qCBkrySBWXg4OcHlqp3BxVH9K6p5K5Qv63Bk9DLkwuUNRmSfh6Fq9YCH" +BINANCE_SECRET_KEY = "LB6T8AEuD210xM6Cx3MuOckDbZb4iLu5JiXhrJiuxOOPy9Q1hf0vVjsnlNbvtXO3" \ No newline at end of file diff --git a/Session_05/bots/Breakout.py b/Session_05/bots/Breakout.py new file mode 100644 index 0000000..b0ee437 --- /dev/null +++ b/Session_05/bots/Breakout.py @@ -0,0 +1,174 @@ +''' +Breakout Trading Strategy Overview: + +Support/Resistance Calculation: +- Uses 3 days of 15-minute data (289 periods) for range analysis +- Calculates support (lowest close) and resistance (highest close) levels +- Establishes key levels based on recent price action + +Breakout Detection: +- BULLISH breakout: Current bid > recent resistance level +- BEARISH breakdown: Current bid < recent support level +- Confirms momentum before placing trades + +Entry Strategy: +- LONG: Place buy order 0.1% above broken resistance (retest entry) +- SHORT: Place sell order 0.1% below broken support (retest entry) +- Waits for price to break key levels before entering + +Retest Logic: +- Enters on pullbacks to previously broken levels +- Resistance becomes support after bullish breakout +- Support becomes resistance after bearish breakdown +- Higher probability entries on retests vs initial breakouts + +Risk Management: +- Profit target: 9% gain +- Stop loss: 8% loss +- Position size management with limits +- Trade pause system to avoid overtrading + +Technical Setup: +- Timeframe: 15 minutes +- Lookback: 3 days (289 periods) +- Uses close-based support/resistance (more reliable than wicks) +- Breakout confirmation with 0.1% threshold for noise filtering + +WARNING: Do not run without thorough backtesting and understanding! +''' + +import ccxt +import time, schedule +import Functions.funcs as func +import os +from dotenv import load_dotenv + +load_dotenv() +api_key = os.getenv('BINANCE_API_KEY') +api_secret = os.getenv('BINANCE_SECRET_KEY') + +exchange = ccxt.binance({ + 'apiKey': api_key, + 'secret': api_secret, + 'enableRateLimit': True, + 'options': { + 'defaultType': 'future', + } +}) + +# Configuration +symbol = 'BTCUSDT' +pos_size = 30 +target = 9 +max_loss = -8 +pause_time = 10 +vol_repeat = 11 +vol_time = 5 +vol_decimal = 0.4 +params = {'timeInForce': 'GTC'} + +def get_support_resistance(symbol): + """Calculate support and resistance from 3 days of 15m data""" + df = func.df_sma(symbol, '15m', 289, 20) # 3 days of 15m data + + support = df['close'].min() + resistance = df['close'].max() + + # Also get recent support/resistance for comparison + recent_support = df['support'].iloc[-1] + recent_resistance = df['resis'].iloc[-1] + + print(f'Levels - Support: {support:.2f} | Resistance: {resistance:.2f}') + print(f'Recent - Support: {recent_support:.2f} | Resistance: {recent_resistance:.2f}') + + return support, resistance, recent_support, recent_resistance, df + +def detect_breakout(symbol): + """Detect if price is breaking above resistance or below support""" + support, resistance, recent_support, recent_resistance, df = get_support_resistance(symbol) + + ask, bid = func.ask_bid(symbol) + + buy_breakout = False + sell_breakdown = False + breakout_price = False + breakdown_price = False + + # Check for breakout above recent resistance + if bid > recent_resistance: + print(f'BULLISH BREAKOUT detected - Bid {bid:.2f} > Resistance {recent_resistance:.2f}') + buy_breakout = True + breakout_price = recent_resistance * 1.001 # 0.1% above broken resistance + + # Check for breakdown below recent support + elif bid < recent_support: + print(f'BEARISH BREAKDOWN detected - Bid {bid:.2f} < Support {recent_support:.2f}') + sell_breakdown = True + breakdown_price = recent_support * 0.999 # 0.1% below broken support + + else: + print(f'Price {bid:.2f} within range [{recent_support:.2f} - {recent_resistance:.2f}]') + + return buy_breakout, sell_breakdown, breakout_price, breakdown_price + +def bot(): + try: + # Check PnL and manage existing positions + func.pnl_close(symbol, target, max_loss) + + # Check sleep on close (pause between trades) + func.sleep_on_close(symbol, pause_time) + + # Get current prices + ask, bid = func.ask_bid(symbol) + + # Detect breakout signals + buy_breakout, sell_breakdown, breakout_price, breakdown_price = detect_breakout(symbol) + + # Get position info + _, in_position, position_size, _ = func.open_positions(symbol) + current_size = int(position_size) if position_size else 0 + + print(f'Status - In position: {in_position} | Size: {current_size} | Price: {bid:.2f}') + print(f'Signals - Breakout: {buy_breakout} | Breakdown: {sell_breakdown}') + + # Only trade if not in position and size within limits + if not in_position and current_size < pos_size: + exchange.cancel_all_orders(symbol) + + # Refresh prices after canceling orders + ask, bid = func.ask_bid(symbol) + + if buy_breakout and breakout_price: + print(f'Placing LONG order - Size: {pos_size} at {breakout_price:.2f}') + exchange.create_limit_buy_order(symbol, pos_size, breakout_price, params) + print('BUY order placed - Sleeping 2 minutes...') + time.sleep(120) + + elif sell_breakdown and breakdown_price: + print(f'Placing SHORT order - Size: {pos_size} at {breakdown_price:.2f}') + exchange.create_limit_sell_order(symbol, pos_size, breakdown_price, params) + print('SELL order placed - Sleeping 2 minutes...') + time.sleep(120) + + else: + print('No breakout signal - Sleeping 1 minute...') + time.sleep(60) + + else: + if in_position: + print('Already in position - Monitoring...') + else: + print('Position size at limit - No new trades') + + except Exception as e: + print(f'Bot error: {e}') + +schedule.every(28).seconds.do(bot) + +while True: + try: + schedule.run_pending() + except Exception as e: + print(f'Schedule error: {e} - Sleeping 30 seconds') + time.sleep(30) \ No newline at end of file diff --git a/Session_05/bots/Consollidation_Pop.py b/Session_05/bots/Consollidation_Pop.py new file mode 100644 index 0000000..e3a0da6 --- /dev/null +++ b/Session_05/bots/Consollidation_Pop.py @@ -0,0 +1,137 @@ +''' +Consolidation Pop Strategy Overview: + +Consolidation Detection: +- Identifies sideways price movement using True Range analysis +- Consolidation confirmed when TR deviation < 0.7% of current price +- Uses recent 20-candle period to establish consolidation range + +Range Trading Logic: +- LONG: Buy in lower 1/3 of consolidation range (near support) +- SHORT: Sell in upper 1/3 of consolidation range (near resistance) +- Anticipates breakouts in either direction from established ranges + +Entry Strategy: +- Waits for price to enter specific zones within consolidation +- Lower 1/3: Expects bounce from support with breakout potential +- Upper 1/3: Expects rejection from resistance with breakdown potential +- Contrarian approach: Buy weakness, sell strength within range + +Risk Management: +- Take profit: 0.3% gain from entry +- Stop loss: 0.25% loss from entry +- Quick scalping approach for range-bound markets + +Technical Setup: +- Timeframe: 5 minutes (adjustable for 1m, 3m, 15m, 1h) +- Lookback: 20 candles for range establishment +- Consolidation threshold: 0.7% TR deviation from price +- Targets small, quick moves within established ranges + +Market Conditions: +- Best suited for sideways, range-bound markets +- Avoids trending markets where consolidation patterns break down +- Designed for short-term momentum bursts within ranges + +WARNING: Do not run without thorough backtesting and understanding! +''' + +import ccxt, time, schedule +from Functions.functions import * +import os +from dotenv import load_dotenv + +load_dotenv() +api_key = os.getenv('BINANCE_API_KEY') +api_secret = os.getenv('BINANCE_SECRET_KEY') + +exchange = ccxt.binance({ + 'apiKey': api_key, + 'secret': api_secret, + 'enableRateLimit': True, + 'options': { + 'defaultType': 'future', + } +}) + +# Configuration +timeframe = '5m' +limit = 20 +symbol = 'ETHUSDT' +size = 1 +tp_percent = 0.3 +sl_percent = 0.25 +consolidation_percent = 0.7 +params = {'timeInForce': 'GTC'} + +def bot(): + try: + # Check current position status + position_info, in_position, is_long = get_position(exchange, symbol) + + if in_position: + print('Already in position - monitoring...') + return + + # Get market data + candles = get_candle_df(exchange, symbol, timeframe, limit) + tr = calc_tr(candles) + current_price = exchange.fetch_ticker(symbol)['bid'] + + # Calculate TR deviation from current price + tr_deviation = (tr / candles['close'].iloc[-1]) * 100 + + print(f'Price: {current_price:.2f} | TR: {tr:.2f} | TR Deviation: {tr_deviation:.2f}%') + + # Only trade during consolidation periods + if tr_deviation < consolidation_percent: + print('CONSOLIDATION detected - Analyzing range...') + + # Get consolidation range extremes + low, high = get_extreme_of_consolidation(candles, consolidation_percent) + range_size = high - low + lower_third = low + (range_size / 3) + upper_third = high - (range_size / 3) + + print(f'Range: {low:.2f} - {high:.2f} | Size: {range_size:.2f}') + print(f'Lower 1/3: {lower_third:.2f} | Upper 1/3: {upper_third:.2f}') + + # LONG setup: Buy in lower 1/3 of consolidation (near support) + if current_price <= lower_third: + stop_loss = current_price * (1 - (sl_percent / 100)) + take_profit = current_price * (1 + (tp_percent / 100)) + + print(f'LONG setup - Price in lower 1/3 of range') + print(f'Entry: {current_price:.2f} | SL: {stop_loss:.2f} | TP: {take_profit:.2f}') + + exchange.create_limit_buy_order(symbol, size, current_price, params) + print('BUY order placed - Expecting consolidation pop upward') + + # SHORT setup: Sell in upper 1/3 of consolidation (near resistance) + elif current_price >= upper_third: + stop_loss = current_price * (1 + (sl_percent / 100)) + take_profit = current_price * (1 - (tp_percent / 100)) + + print(f'SHORT setup - Price in upper 1/3 of range') + print(f'Entry: {current_price:.2f} | SL: {stop_loss:.2f} | TP: {take_profit:.2f}') + + exchange.create_limit_sell_order(symbol, size, current_price, params) + print('SELL order placed - Expecting consolidation pop downward') + + else: + print(f'Price in middle zone ({current_price:.2f}) - No trade') + + else: + print(f'TR deviation too high ({tr_deviation:.2f}%) - Not consolidating') + + except Exception as e: + print(f'Bot error: {e}') + +schedule.every(20).seconds.do(bot) + +while True: + try: + schedule.run_pending() + except Exception as e: + print(f'Schedule error: {e} - Sleeping 30 seconds') + time.sleep(30) \ No newline at end of file diff --git a/Session_05/bots/Correlation.py b/Session_05/bots/Correlation.py new file mode 100644 index 0000000..f709d0a --- /dev/null +++ b/Session_05/bots/Correlation.py @@ -0,0 +1,233 @@ +''' +Correlation Trading Strategy Overview: + +Lead Asset Analysis: +- Monitors ETH price movements for breakouts +- Uses True Range and support/resistance levels as breakout signals +- Detects when ETH moves outside recent trading range + +Correlation Logic: +- When ETH breaks above resistance/ATR: Look for LONG opportunities in lagging altcoins +- When ETH breaks below support/ATR: Look for SHORT opportunities in lagging altcoins +- Identifies the most "lagging" altcoin (least price movement) to trade + +Entry Strategy: +- Calculates percentage change from last candle for each altcoin +- Trades the altcoin with minimal movement (highest correlation lag) +- Assumes lagging altcoins will catch up to ETH's direction + +Risk Management: +- Stop loss: 0.2% against position +- Take profit: 0.25% in favor of position +- Quick execution to capture correlation momentum + +Altcoin Universe: +- ADAUSDT, DOTUSDT, MANAUSDT, XRPUSDT, UNIUSDT, SOLUSDT +- All major altcoins that typically correlate with ETH movements + +Technical Setup: +- Timeframe: 15 minutes (900 seconds) +- Lookback period: 20 candles +- Uses ATR and support/resistance for breakout confirmation + +WARNING: Do not run without thorough backtesting and understanding! +''' + +import ccxt, time, schedule +from Functions.functions import * +import os +from dotenv import load_dotenv + +load_dotenv() +api_key = os.getenv('BINANCE_API_KEY') +api_secret = os.getenv('BINANCE_SECRET_KEY') + +exchange = ccxt.binance({ + 'apiKey': api_key, + 'secret': api_secret, + 'enableRateLimit': True, + 'options': { + 'defaultType': 'future', + } +}) + +# Configuration +timeframe = '15m' +data_range = 20 +sl_percent = 0.2 +tp_percent = 0.25 +size = 1 +params = {'timeInForce': 'GTC'} + +# Lead symbol to monitor +lead_symbol = 'ETHUSDT' + +# Altcoins to trade based on correlation +alt_coins = ['ADAUSDT', 'DOTUSDT', 'MANAUSDT', 'XRPUSDT', 'UNIUSDT', 'SOLUSDT'] + +def get_eth_signal(): + """Get ETH breakout signal and current price""" + try: + # Get ETH candle data + candles = get_candle_df(exchange, lead_symbol, timeframe, data_range) + + # Calculate technical indicators + calc_atr(candles) + support, resistance = calc_sup_res(candles, data_range) + + # Get current price + ticker = exchange.fetch_ticker(lead_symbol) + current_price = ticker['bid'] + last_close = candles['close'].iloc[-1] + atr = candles['ATR'].iloc[-1] + + print(f'ETH - Price: {current_price:.2f} | Last Close: {last_close:.2f} | ATR: {atr:.2f}') + print(f'Support: {support:.2f} | Resistance: {resistance:.2f}') + + # Check for breakout signals + if current_price > last_close + atr or current_price > resistance: + return 'BULLISH', current_price + elif current_price < last_close - atr or current_price < support: + return 'BEARISH', current_price + else: + return 'NEUTRAL', current_price + + except Exception as e: + print(f'Error getting ETH signal: {e}') + return 'NEUTRAL', 0 + +def find_most_lagging_altcoin(eth_price): + """Find the altcoin with the least movement (most lagging)""" + try: + coin_data = {} + + for coin in alt_coins: + try: + # Get current price and recent candle data + ticker = exchange.fetch_ticker(coin) + current_price = ticker['bid'] + + candles = get_candle_df(exchange, coin, timeframe, 5) # Last 5 candles + recent_close = candles['close'].iloc[-1] + + # Calculate percentage change from recent close + pct_change = abs((current_price - recent_close) / recent_close) * 100 + coin_data[coin] = pct_change + + print(f'{coin}: Current: {current_price:.4f} | Recent Close: {recent_close:.4f} | Change: {pct_change:.2f}%') + + except Exception as e: + print(f'Error getting data for {coin}: {e}') + continue + + if coin_data: + # Find coin with minimum movement (most lagging) + most_lagging = min(coin_data, key=coin_data.get) + lag_amount = coin_data[most_lagging] + print(f'Most lagging coin: {most_lagging} (Change: {lag_amount:.2f}%)') + return most_lagging + else: + return None + + except Exception as e: + print(f'Error finding lagging altcoin: {e}') + return None + +def place_correlation_trade(signal, target_coin): + """Place trade on the most lagging altcoin""" + try: + if not target_coin: + print('No target coin identified') + return + + # Check if already in position for this coin + _, in_position, _, _ = get_position(exchange, target_coin) + + if in_position: + print(f'Already in position for {target_coin}') + return + + # Get current price for target coin + ticker = exchange.fetch_ticker(target_coin) + current_price = ticker['bid'] if signal == 'BULLISH' else ticker['ask'] + + # Calculate stop loss and take profit + if signal == 'BULLISH': + stop_loss = current_price * (1 - (sl_percent / 100)) + take_profit = current_price * (1 + (tp_percent / 100)) + + print(f'Placing LONG on {target_coin} at {current_price:.4f}') + print(f'Stop Loss: {stop_loss:.4f} | Take Profit: {take_profit:.4f}') + + exchange.create_limit_buy_order(target_coin, size, current_price, params) + + elif signal == 'BEARISH': + stop_loss = current_price * (1 + (sl_percent / 100)) + take_profit = current_price * (1 - (tp_percent / 100)) + + print(f'Placing SHORT on {target_coin} at {current_price:.4f}') + print(f'Stop Loss: {stop_loss:.4f} | Take Profit: {take_profit:.4f}') + + exchange.create_limit_sell_order(target_coin, size, current_price, params) + + # Note: In a full implementation, you'd want to place actual stop loss and take profit orders + # This would require additional order management logic + + except Exception as e: + print(f'Error placing trade: {e}') + +def manage_existing_positions(): + """Check and manage existing positions""" + for coin in alt_coins: + try: + position_info, in_position, _ = get_position(exchange, coin) + + if in_position: + # Here you would implement PnL checking and position management + # For now, just log that we have a position + print(f'Managing position in {coin}') + + # You could call a PnL management function here + # hit_target() or similar logic from other strategies + + except Exception as e: + print(f'Error managing position for {coin}: {e}') + +def bot(): + try: + print('\n---- CORRELATION STRATEGY ACTIVE ----') + + # Step 1: Manage existing positions + manage_existing_positions() + + # Step 2: Check ETH for breakout signal + eth_signal, eth_price = get_eth_signal() + + if eth_signal == 'NEUTRAL': + print('ETH: No breakout signal detected') + return + + print(f'ETH BREAKOUT: {eth_signal} signal at {eth_price:.2f}') + + # Step 3: Find most lagging altcoin + target_coin = find_most_lagging_altcoin(eth_price) + + if target_coin: + # Step 4: Place correlation trade + place_correlation_trade(eth_signal, target_coin) + else: + print('No suitable target coin found') + + print('=' * 50) + + except Exception as e: + print(f'Bot error: {e}') + +schedule.every(20).seconds.do(bot) + +while True: + try: + schedule.run_pending() + except Exception as e: + print(f'Schedule error: {e} - Sleeping 30 seconds') + time.sleep(30) \ No newline at end of file diff --git a/Session_05/bots/Engulfing_Candle.py b/Session_05/bots/Engulfing_Candle.py new file mode 100644 index 0000000..bc63635 --- /dev/null +++ b/Session_05/bots/Engulfing_Candle.py @@ -0,0 +1,119 @@ +''' +Engulfing Candle Strategy Overview: + +Pattern Recognition: +- Identifies engulfing candle patterns on 15-minute timeframe +- BULLISH engulfing: Current bid > previous candle close +- BEARISH engulfing: Current bid < previous candle close + +Entry Conditions: +- LONG: Bid > last close AND bid > 20-period SMA (trend confirmation) +- SHORT: Bid < last close AND bid < 20-period SMA (trend confirmation) +- Uses dual confirmation: price action + trend direction + +Order Management: +- Entry orders placed at 0.1% offset from current bid/ask +- Cancels all existing orders before placing new ones +- 2-minute pause after order placement + +Exit Conditions: +- Profit target: 9% gain +- Stop loss: 8% loss +- Managed through external PnL function + +Technical Setup: +- Timeframe: 15 minutes +- SMA period: 20 +- Combines reversal pattern with trend following for higher probability trades + +WARNING: Do not run without thorough backtesting and understanding! +''' + +import ccxt +import time, schedule +import Functions.funcs as func +import os +from dotenv import load_dotenv + +load_dotenv() +api_key = os.getenv('BINANCE_API_KEY') +api_secret = os.getenv('BINANCE_SECRET_KEY') + +exchange = ccxt.binance({ + 'apiKey': api_key, + 'secret': api_secret, + 'enableRateLimit': True, + 'options': { + 'defaultType': 'future', + } +}) + +symbol = 'BTCUSDT' +pos_size = 1 +target = 9 +max_loss = -8 +params = {'timeInForce': 'GTC'} + +def bot(): + # Check PnL and manage existing positions + func.pnl_close(symbol, target, max_loss) + + # Get market data and indicators + timeframe = '15m' + limit = 97 + sma = 20 + df = func.df_sma(symbol, timeframe, limit, sma) + + # Get current prices + ask, bid = func.ask_bid(symbol) + + # Get position info + _, in_position, current_size, _ = func.open_positions(symbol) + current_size = int(current_size) if current_size else 0 + + # Get technical levels + sma_20_15m = df.iloc[-1][f'sma{sma}_{timeframe}'] + last_close = df.iloc[-1]['close'] + + print(f'Position: {in_position} | Size: {current_size} | Bid: {bid:.2f} | SMA: {sma_20_15m:.2f} | Last Close: {last_close:.2f}') + + if not in_position: + # BULLISH engulfing: bid above last close + above SMA (uptrend) + if bid > last_close and bid > sma_20_15m: + print('BULLISH engulfing pattern detected - Opening LONG') + exchange.cancel_all_orders(symbol) + + # Refresh prices and place order + ask, bid = func.ask_bid(symbol) + order_price = bid * 0.999 # 0.1% below bid + exchange.create_limit_buy_order(symbol, pos_size, order_price, params) + + print(f'BUY order placed at {order_price:.2f} - Sleeping 2 minutes') + time.sleep(120) + + # BEARISH engulfing: bid below last close + below SMA (downtrend) + elif bid < last_close and bid < sma_20_15m: + print('BEARISH engulfing pattern detected - Opening SHORT') + exchange.cancel_all_orders(symbol) + + # Refresh prices and place order + ask, bid = func.ask_bid(symbol) + order_price = ask * 1.001 # 0.1% above ask + exchange.create_limit_sell_order(symbol, pos_size, order_price, params) + + print(f'SELL order placed at {order_price:.2f} - Sleeping 2 minutes') + time.sleep(120) + + else: + print('No engulfing pattern or trend confirmation - No trade') + else: + print('Already in position - Monitoring...') + +schedule.every(28).seconds.do(bot) + +while True: + try: + schedule.run_pending() + except Exception as e: + print(f'Error: {e} - Sleeping 30 seconds') + time.sleep(30) \ No newline at end of file diff --git a/Session_05/bots/HyperLiquid/BBands.py b/Session_05/bots/HyperLiquid/BBands.py new file mode 100644 index 0000000..a0cc351 --- /dev/null +++ b/Session_05/bots/HyperLiquid/BBands.py @@ -0,0 +1,205 @@ +''' +Bollinger Bands Volatility Breakout Strategy Overview: + +Volatility Detection: +- Uses BTC Bollinger Bands on 1-minute timeframe to gauge market volatility +- "Tight bands" indicate low volatility periods (compression before expansion) +- Strategy activates only during low volatility conditions + +Breakout Anticipation: +- Places BOTH buy and sell orders simultaneously when bands are tight +- Anticipates volatility expansion (breakout) in either direction +- Uses 11th level bid/ask for better execution probability + +Dual Order Logic: +- BUY order: Positioned to catch upward breakout +- SELL order: Positioned to catch downward breakout +- One order gets filled, the other gets canceled when volatility returns +- Position size split in half to accommodate dual orders + +Risk Management: +- Profit target: 5% gain +- Stop loss: 10% loss +- Automatic position closure when volatility returns (bands widen) +- Maximum 1 position with controlled exposure + +Technical Setup: +- Reference: BTC Bollinger Bands (market volatility proxy) +- Trading Symbol: WIF (or configurable) +- Timeframe: 1 minute (500 periods for BB calculation) +- Position sizing: 50% of normal size per order (dual orders) +- Leverage: 3x (adjustable) + +Strategy Logic: +- Low volatility (tight bands): Place dual orders expecting breakout +- High volatility (wide bands): Close all positions and orders +- Uses BTC as volatility gauge but trades altcoins for better moves + +WARNING: Do not run without thorough backtesting and understanding! +''' + +import Functions.funcs as func +import eth_account +import time +import schedule +import os +from dotenv import load_dotenv + +load_dotenv() + +# Configuration +symbol = 'WIF' +timeframe = '15m' +sma_window = 20 +lookback_days = 1 +size = 1 +target = 5 +max_loss = -10 +leverage = 3 +max_positions = 1 + +def get_account(): + """Initialize trading account from private key""" + secret = os.getenv('PRIVATE_KEY') + return eth_account.Account.from_key(secret) + +def get_position_info(symbol, account): + """Get current position and setup information""" + try: + positions, in_position, position_size, pos_symbol, entry_price, pnl_percent, is_long, num_positions = func.get_position_andmaxpos( + symbol, account, max_positions + ) + + print(f'Position Status - In position: {in_position} | Size: {position_size} | PnL: {pnl_percent}%') + + # Setup leverage and get adjusted position size + leverage_set, pos_size = func.adjust_leverage_size_signal(symbol, leverage, account) + + # Split position size for dual orders + dual_order_size = pos_size / 2 + + return in_position, dual_order_size, positions + + except Exception as e: + print(f'Error getting position info: {e}') + return False, size / 2, [] + +def check_bollinger_volatility(): + """Check if Bollinger Bands are tight (low volatility condition)""" + try: + # Get BTC data for volatility analysis + snapshot_data = func.get_ohlcv2('BTC', '1m', 500) + df = func.process_data_to_df(snapshot_data) + + # Calculate Bollinger Bands + bb_data = func.calculate_bollinger_bands(df) + bands_tight = bb_data[1] # Boolean indicating if bands are tight + + print(f'BTC Bollinger Bands Analysis - Tight (Low Volatility): {bands_tight}') + + return bands_tight + + except Exception as e: + print(f'Error checking Bollinger volatility: {e}') + return False + +def get_market_levels(symbol): + """Get current market levels for order placement""" + try: + ask, bid, l2_data = func.ask_bid(symbol) + + # Get 11th level for better execution + bid_11 = float(l2_data[0][10]['px']) + ask_11 = float(l2_data[1][10]['px']) + + print(f'Market Levels - Bid: {bid:.4f} | Ask: {ask:.4f}') + print(f'11th Level - Bid: {bid_11:.4f} | Ask: {ask_11:.4f}') + + return bid_11, ask_11 + + except Exception as e: + print(f'Error getting market levels: {e}') + return 0, 0 + +def place_dual_orders(symbol, bid_11, ask_11, position_size, account): + """Place both buy and sell orders for breakout capture""" + try: + func.cancel_all_orders(account) + print('Canceled all existing orders') + + # Place buy order (upward breakout) + func.limit_order(symbol, True, position_size, bid_11, False, account) + print(f'BUY order placed: {position_size} at {bid_11:.4f}') + + # Place sell order (downward breakout) + func.limit_order(symbol, False, position_size, ask_11, False, account) + print(f'SELL order placed: {position_size} at {ask_11:.4f}') + + print('Dual orders active - Ready for volatility breakout') + + except Exception as e: + print(f'Error placing dual orders: {e}') + +def manage_volatility_exit(account): + """Close all positions and orders when volatility returns""" + try: + print('High volatility detected - Closing all positions and orders') + func.cancel_all_orders(account) + func.close_all_positions(account) + print('All positions and orders closed') + + except Exception as e: + print(f'Error managing volatility exit: {e}') + +def bot(): + try: + print(f'\n---- Bollinger Bands Volatility Strategy for {symbol} ----') + + # Initialize account + account = get_account() + + # Get position information + in_position, dual_order_size, positions = get_position_info(symbol, account) + + # Check existing positions first + if in_position: + func.cancel_all_orders(account) + print('Managing existing position - checking PnL') + func.pnl_close(symbol, target, max_loss, account) + return + + # Check Bollinger Bands volatility condition + bands_tight = check_bollinger_volatility() + + if not in_position and bands_tight: + print('LOW VOLATILITY detected - Placing dual breakout orders') + + # Get market levels + bid_11, ask_11 = get_market_levels(symbol) + + if bid_11 > 0 and ask_11 > 0: + place_dual_orders(symbol, bid_11, ask_11, dual_order_size, account) + else: + print('Invalid market data - skipping dual orders') + + elif not bands_tight: + print('HIGH VOLATILITY detected - Managing exits') + manage_volatility_exit(account) + + else: + print(f'Position status: {in_position} | Volatility condition: {"Low" if bands_tight else "High"}') + + print('=' * 60) + + except Exception as e: + print(f'Bot error: {e}') + +schedule.every(30).seconds.do(bot) + +while True: + try: + schedule.run_pending() + time.sleep(10) + except Exception as e: + print(f'Schedule error: {e} - Sleeping 30 seconds') + time.sleep(30) \ No newline at end of file diff --git a/Session_05/bots/HyperLiquid/Supply_Demand_Zone.py b/Session_05/bots/HyperLiquid/Supply_Demand_Zone.py new file mode 100644 index 0000000..7a9676d --- /dev/null +++ b/Session_05/bots/HyperLiquid/Supply_Demand_Zone.py @@ -0,0 +1,191 @@ +''' +Supply & Demand Zone Trading Strategy Overview: + +Zone Identification: +- Calculates supply zones (resistance areas where selling pressure exists) +- Calculates demand zones (support areas where buying pressure exists) +- Uses historical price action analysis to identify key levels +- Zones represent areas where institutional orders likely cluster + +Entry Strategy: +- BUY: Places orders at demand zone levels (support areas) +- SELL: Places orders at supply zone levels (resistance areas) +- Chooses the zone closest to current price for higher probability fills +- Uses average of multiple zone levels for more reliable entries + +Position Management: +- Supports partial position building (adds to positions under target size) +- Maximum 1 full position at a time with partial fills allowed +- Automatic position sizing adjustments based on existing exposure +- Leverage: 3x (adjustable) + +Risk Management: +- Profit target: 5% gain +- Stop loss: 10% loss +- Continuous PnL monitoring for existing positions +- Order cancellation and replacement system + +Technical Setup: +- Symbol: WIF (configurable) +- Timeframe: 15 minutes for zone calculation +- SMA confirmation: 20-period moving average +- Lookback: 1 day for zone identification +- Platform: Hyper Liquid (on-chain execution) + +Zone Logic: +- Supply zones: Areas where price previously dropped (selling pressure) +- Demand zones: Areas where price previously bounced (buying pressure) +- Strategy assumes these levels will act as support/resistance again + +WARNING: Do not run without thorough backtesting and understanding! +''' + +import Functions.funcs as func +import eth_account +import time +import pandas as pd +import schedule +import os +from dotenv import load_dotenv + +load_dotenv() + +# Configuration +symbol = 'WIF' +timeframe = '15m' +sma_window = 20 +lookback_days = 1 +size = 1 +target = 5 +max_loss = -10 +leverage = 3 +max_positions = 1 + +def get_account(): + """Initialize trading account from private key""" + secret = os.getenv('PRIVATE_KEY') + return eth_account.Account.from_key(secret) + +def setup_account(): + """Setup account with leverage""" + account = get_account() + func.adjust_leverage_size_signal(symbol, leverage, account) + return account + +def get_position_info(symbol, account): + """Get current position information and calculate adjusted size""" + try: + positions, in_position, position_size, pos_symbol, entry_price, pnl_percent, is_long = func.get_position(symbol, account) + + print(f'Position Status - In position: {in_position} | Size: {position_size} | PnL: {pnl_percent}%') + + # Calculate adjusted position size for partial fills + if 0 < position_size < size: + adjusted_size = size - position_size + print(f'Partial position detected - Current: {position_size} | Needed: {adjusted_size}') + return False, adjusted_size, positions # Treat as not in position for adding + else: + return in_position, size, positions + + except Exception as e: + print(f'Error getting position info: {e}') + return False, size, [] + +def get_supply_demand_zones(symbol): + """Calculate supply and demand zones and determine best entry""" + try: + # Get supply and demand zones + sd_df = func.supply_demand_zones_hl(symbol, timeframe, lookback_days) + print('Supply & Demand Zones:') + print(sd_df) + + # Convert to numeric + sd_df[f'{timeframe}_dz'] = pd.to_numeric(sd_df[f'{timeframe}_dz'], errors='coerce') + sd_df[f'{timeframe}_sz'] = pd.to_numeric(sd_df[f'{timeframe}_sz'], errors='coerce') + + # Calculate average zone levels + demand_zone_avg = float(sd_df[f'{timeframe}_dz'].mean()) + supply_zone_avg = float(sd_df[f'{timeframe}_sz'].mean()) + + # Get current price and SMA confirmation + current_price = func.ask_bid(symbol)[0] + latest_sma = func.get_latest_sma(symbol, timeframe, sma_window, 2) + + print(f'Market Data - Price: {current_price:.4f} | SMA: {latest_sma:.4f}') + print(f'Zones - Demand: {demand_zone_avg:.4f} | Supply: {supply_zone_avg:.4f}') + + # Calculate distance to each zone + distance_to_demand = abs(current_price - demand_zone_avg) + distance_to_supply = abs(current_price - supply_zone_avg) + + print(f'Distances - To Demand: {distance_to_demand:.4f} | To Supply: {distance_to_supply:.4f}') + + # Choose closest zone for entry + if distance_to_demand < distance_to_supply: + return 'BUY', demand_zone_avg, 'demand' + else: + return 'SELL', supply_zone_avg, 'supply' + + except Exception as e: + print(f'Error calculating supply/demand zones: {e}') + return None, None, None + +def place_zone_trade(signal, entry_price, zone_type, position_size, account): + """Place trade order at supply or demand zone""" + try: + func.cancel_all_orders(account) + print('Canceled all existing orders') + + if signal == 'BUY': + func.limit_order(symbol, True, position_size, entry_price, False, account) + print(f'LONG order placed at {zone_type} zone: {position_size} at {entry_price:.4f}') + + elif signal == 'SELL': + func.limit_order(symbol, False, position_size, entry_price, False, account) + print(f'SHORT order placed at {zone_type} zone: {position_size} at {entry_price:.4f}') + + except Exception as e: + print(f'Error placing zone trade: {e}') + +def bot(): + try: + print(f'\n---- Supply & Demand Zone Strategy for {symbol} ----') + + # Setup account + account = get_account() + + # Get position information + in_position, position_size, positions = get_position_info(symbol, account) + + # Check PnL for existing positions + if in_position: + print('Managing existing position') + func.pnl_close(symbol, target, max_loss, account) + return + + # Look for new trade opportunities + print('Analyzing supply & demand zones...') + signal, entry_price, zone_type = get_supply_demand_zones(symbol) + + if signal and entry_price: + place_zone_trade(signal, entry_price, zone_type, position_size, account) + else: + print('No valid zone signals found') + + print('=' * 60) + + except Exception as e: + print(f'Bot error: {e}') + +# Initial setup +account = setup_account() + +schedule.every(30).seconds.do(bot) + +while True: + try: + schedule.run_pending() + time.sleep(10) + except Exception as e: + print(f'Schedule error: {e} - Sleeping 30 seconds') + time.sleep(30) \ No newline at end of file diff --git a/Session_05/bots/HyperLiquid/VWAP.py b/Session_05/bots/HyperLiquid/VWAP.py new file mode 100644 index 0000000..cbc075a --- /dev/null +++ b/Session_05/bots/HyperLiquid/VWAP.py @@ -0,0 +1,173 @@ +''' +VWAP Trading Strategy Overview: + +VWAP Signal Logic: +- Uses Volume Weighted Average Price as primary trend indicator +- Price > VWAP: Bullish bias (70% chance to go long) +- Price < VWAP: Bearish bias (30% chance to go long) +- Incorporates randomization to avoid predictable patterns + +Order Placement Strategy: +- Uses 11th level bid/ask for better fill probability +- Avoids competing at best bid/offer for improved execution +- Places limit orders to capture spread while getting fills + +Position Management: +- Maximum 1 position at a time +- Leverage: 3x (adjustable) +- Profit target: 5% gain +- Stop loss: 10% loss +- Dynamic position sizing based on leverage + +Risk Management: +- Automatic position and PnL monitoring +- Order cancellation and re-placement system +- Maximum position limits to control exposure +- Ethereum account-based execution with private key security + +Technical Setup: +- Symbol: LINK (configurable) +- Timeframe: 1 minute +- VWAP calculation: Real-time volume weighted average +- Execution: DeFi/on-chain trading platform + +WARNING: Do not run without thorough backtesting and understanding! +''' + +import Functions.funcs as func +import eth_account +import time, random +import schedule +import os +from dotenv import load_dotenv + +load_dotenv() + +# Configuration +symbol = 'LINK' +timeframe = '1m' +sma_window = 20 +lookback_days = 1 +size = 1 +target = 5 +max_loss = -10 +leverage = 3 +max_positions = 1 + +def get_account(): + """Initialize trading account from private key""" + secret = os.getenv('PRIVATE_KEY') + return eth_account.Account.from_key(secret) + +def get_vwap_signal(symbol): + """Calculate VWAP signal and determine trade direction""" + try: + ask, bid, l2_data = func.ask_bid(symbol) + + # Get 11th level bid and ask for better execution + bid_11 = float(l2_data[0][10]['px']) + ask_11 = float(l2_data[1][10]['px']) + + # Get current VWAP + latest_vwap = func.calculate_vwap_with_symbol(symbol)[1] + + print(f'Market Data - Bid: {bid:.4f} | Ask: {ask:.4f} | VWAP: {latest_vwap:.4f}') + print(f'11th Level - Bid: {bid_11:.4f} | Ask: {ask_11:.4f}') + + # Randomization factor for signal variation + random_chance = random.random() + + # VWAP-based signal logic + if bid > latest_vwap: + # Price above VWAP - bullish bias + go_long = random_chance <= 0.7 # 70% chance + signal_strength = "Strong Bullish" if go_long else "Weak Bullish" + print(f'Price > VWAP: {signal_strength} (Random: {random_chance:.2f})') + else: + # Price below VWAP - bearish bias + go_long = random_chance <= 0.3 # 30% chance + signal_strength = "Contrarian Long" if go_long else "Bearish" + print(f'Price < VWAP: {signal_strength} (Random: {random_chance:.2f})') + + return go_long, bid_11, ask_11, latest_vwap + + except Exception as e: + print(f'Error getting VWAP signal: {e}') + return False, 0, 0, 0 + +def manage_position(symbol, account): + """Check and manage existing positions""" + try: + positions, in_position, position_size, pos_symbol, entry_price, pnl_percent, is_long, num_positions = func.get_position_andmaxpos( + symbol, account, max_positions + ) + + print(f'Position Status - In position: {in_position} | Size: {position_size} | PnL: {pnl_percent}%') + + if in_position: + func.cancel_all_orders(account) + print('Managing existing position - checking PnL') + func.pnl_close(symbol, target, max_loss, account) + return True + else: + print('No position - ready for new trade') + return False + + except Exception as e: + print(f'Error managing position: {e}') + return False + +def place_trade(symbol, go_long, bid_11, ask_11, account): + """Place trade order based on signal""" + try: + # Adjust leverage and position size + leverage_set, position_size = func.adjust_leverage_size_signal(symbol, leverage, account) + + func.cancel_all_orders(account) + print('Canceled all existing orders') + + if go_long: + # Place buy order at 11th bid level + func.limit_order(symbol, True, position_size, bid_11, False, account) + print(f'LONG order placed: {position_size} at {bid_11:.4f}') + else: + # Place sell order at 11th ask level + func.limit_order(symbol, False, position_size, ask_11, False, account) + print(f'SHORT order placed: {position_size} at {ask_11:.4f}') + + except Exception as e: + print(f'Error placing trade: {e}') + +def bot(): + try: + print(f'\n---- VWAP Strategy Running for {symbol} ----') + + # Initialize account + account = get_account() + + # Check existing positions first + in_position = manage_position(symbol, account) + + if not in_position: + # Get VWAP signal and market data + go_long, bid_11, ask_11, vwap = get_vwap_signal(symbol) + + if bid_11 > 0 and ask_11 > 0: # Valid market data + place_trade(symbol, go_long, bid_11, ask_11, account) + else: + print('Invalid market data - skipping trade') + + print('=' * 50) + + except Exception as e: + print(f'Bot error: {e}') + +schedule.every(3).seconds.do(bot) + +while True: + try: + schedule.run_pending() + time.sleep(10) + except Exception as e: + print(f'Schedule error: {e} - Sleeping 30 seconds') + time.sleep(30) \ No newline at end of file diff --git a/Session_05/bots/Market_Maker.py b/Session_05/bots/Market_Maker.py new file mode 100644 index 0000000..23c2d59 --- /dev/null +++ b/Session_05/bots/Market_Maker.py @@ -0,0 +1,341 @@ +''' +Market Maker Strategy Overview: + +Market Making Logic: +- Places buy orders near recent lows, sell orders near recent highs +- Uses dynamic range based on recent high-low spread +- Only trades within configured percentage of recent range extremes + +Risk Management: +- ATR-based volatility filtering (no trading if volatility too high) +- Emergency position size limits with automatic closure +- Time-based position limits (auto-close after time limit) +- Stop losses with trailing functionality + +Entry Conditions: +- BUY: When bid drops below (low + range_percentage) +- SELL: When ask rises above (high - range_percentage) +- Range calculated from recent N-period high/low +- Filtered out during high volatility periods + +Exit Conditions: +- Profit target: 0.4% gain from entry +- Stop loss: 0.1% loss from entry +- Time limit: 2 hours maximum hold time +- Emergency size limit: $1100 maximum risk + +Technical Filters: +- Uses 5-minute timeframe with 180-period lookback (15 hours) +- ATR volatility filter prevents trading in choppy markets +- Recent high/low trend analysis prevents bad entries + +WARNING: This strategy is complex and has not been fully backtested. +Use only for educational purposes without proper testing. + +WARNING: Do not run without thorough backtesting and understanding! +''' + +import ccxt +import pandas as pd +import time +from datetime import datetime, timedelta +import schedule +import warnings +import os +from dotenv import load_dotenv + +warnings.filterwarnings("ignore") + +load_dotenv() +api_key = os.getenv('BINANCE_API_KEY') +api_secret = os.getenv('BINANCE_SECRET_KEY') + +exchange = ccxt.binance({ + 'apiKey': api_key, + 'secret': api_secret, + 'enableRateLimit': True, + 'options': { + 'defaultType': 'future', + } +}) + +# Configuration +size = 4200 +symbol = 'DYDXUSDT' +perc_from_lh = 0.35 +close_seconds = 60 * 47 # 47 minutes +trade_pause_mins = 15 +max_lh = 1250 +timeframe = '5m' +num_bars = 180 +max_risk = 1100 +sl_perc = 0.1 +exit_perc = 0.004 +max_tr = 550 +quartile = 0.33 +time_limit = 120 # minutes +sleep_time = 30 +params = {'timeInForce': 'GTC'} + +def get_bid_ask(symbol=symbol): + orderbook = exchange.fetch_order_book(symbol) + bid = orderbook['bids'][0][0] + ask = orderbook['asks'][0][0] + bid_vol = orderbook['bids'][0][1] + ask_vol = orderbook['asks'][0][1] + return ask, bid, ask_vol, bid_vol + +def get_open_positions(symbol=symbol): + positions = exchange.fetch_positions([symbol]) + + for pos in positions: + if pos['symbol'] == symbol and float(pos['contracts']) != 0: + return { + 'side': pos['side'], + 'size': float(pos['contracts']), + 'entry_price': float(pos['entryPrice']), + 'is_long': pos['side'] == 'long', + 'has_position': True + } + + return { + 'side': None, + 'size': 0, + 'entry_price': 0, + 'is_long': None, + 'has_position': False + } + +def size_kill(symbol=symbol): + position = get_open_positions(symbol) + + if position['has_position']: + position_cost = abs(position['size'] * position['entry_price']) + + if position_cost > max_risk: + print(f'EMERGENCY: Position size ${position_cost} exceeds max risk ${max_risk}') + kill_switch(symbol) + print('Emergency closure complete. Sleeping 72 hours...') + time.sleep(260000) + +def kill_switch(symbol=symbol): + print('KILL SWITCH ACTIVATED') + + while True: + position = get_open_positions(symbol) + + if not position['has_position']: + break + + exchange.cancel_all_orders(symbol) + ask, bid, _, _ = get_bid_ask(symbol) + + if position['is_long']: + exchange.create_limit_sell_order(symbol, position['size'], ask, params) + print(f'SELL to close: {position["size"]} at {ask}') + else: + exchange.create_limit_buy_order(symbol, position['size'], bid, params) + print(f'BUY to close: {position["size"]} at {bid}') + + time.sleep(30) + +def get_order_status(symbol=symbol): + open_orders = exchange.fetch_open_orders(symbol) + + has_stop_loss = any(order['type'] == 'stop_market' for order in open_orders) + has_close_order = any(order['type'] == 'limit' for order in open_orders) + has_entry_order = len(open_orders) > 0 and not get_open_positions(symbol)['has_position'] + + return { + 'has_both': has_stop_loss and has_close_order, + 'needs_stop_loss': not has_stop_loss, + 'needs_close_order': not has_close_order, + 'has_entry_pending': has_entry_order + } + +def calculate_atr(df, period=7): + df['prev_close'] = df['close'].shift(1) + df['high_low'] = abs(df['high'] - df['low']) + df['high_prev_close'] = abs(df['high'] - df['prev_close']) + df['low_prev_close'] = abs(df['low'] - df['prev_close']) + + df['tr'] = df[['high_low', 'high_prev_close', 'low_prev_close']].max(axis=1) + df['atr'] = df['tr'].rolling(period).mean() + + return df + +def get_pnl(symbol=symbol): + position = get_open_positions(symbol) + + if not position['has_position']: + return "No position", 0, 0 + + ask, bid, _, _ = get_bid_ask(symbol) + current_price = bid + entry_price = position['entry_price'] + + if position['is_long']: + price_diff = current_price - entry_price + else: + price_diff = entry_price - current_price + + try: + pnl_percent = (price_diff / entry_price) * 100 + except: + pnl_percent = 0 + + pnl_text = f'PnL: {pnl_percent:.2f}%' + print(pnl_text) + + return pnl_text, ask, bid + +def create_stop_order(symbol, reference_price, direction): + position = get_open_positions(symbol) + position_size = position['size'] if position['has_position'] else size + + if direction == 'SELL': # Stop for long position + stop_price = reference_price * 1.001 + exchange.create_order( + symbol, 'stop_market', 'sell', position_size, None, + params={'stopPrice': stop_price} + ) + print(f'Stop loss created: SELL at {stop_price}') + + elif direction == 'BUY': # Stop for short position + stop_price = reference_price * 0.999 + exchange.create_order( + symbol, 'stop_market', 'buy', position_size, None, + params={'stopPrice': stop_price} + ) + print(f'Stop loss created: BUY at {stop_price}') + +def bot(): + print('\n---- MARKET MAKER ACTIVE ----\n') + + # Check PnL and get current prices + pnl_info = get_pnl(symbol) + ask = pnl_info[1] + bid = pnl_info[2] + + # Emergency size check + size_kill() + + # Get market data + bars = exchange.fetch_ohlcv(symbol, timeframe=timeframe, limit=num_bars) + df = pd.DataFrame(bars[:-1], columns=['timestamp', 'open', 'high', 'low', 'close', 'volume']) + df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms') + + # Calculate technical indicators + df = calculate_atr(df) + + # Get range data + low = df['low'].min() + high = df['high'].max() + range_size = high - low + avg_price = (high + low) / 2 + + print(f'Range: {low:.2f} - {high:.2f} | Size: {range_size:.2f} | Avg: {avg_price:.2f}') + + # Check if range is too large (high volatility) + if range_size > max_lh: + print('RANGE TOO LARGE - No trading') + kill_switch() + return + + # Check recent trend (no new highs/lows in last 17 bars) + recent_lows = df['low'].tail(17).tolist() + no_trading = any(low >= recent_low for recent_low in recent_lows) + + if no_trading: + print('Recent trend unfavorable - No trading') + return + + # Get position and order status + position = get_open_positions(symbol) + order_status = get_order_status(symbol) + + # Calculate entry levels + open_range = range_size * perc_from_lh + sell_to_open_limit = high - open_range + buy_to_open_limit = low + open_range + + print(f'Entry levels - Buy below: {buy_to_open_limit:.2f}, Sell above: {sell_to_open_limit:.2f}') + + # Calculate exit price for existing position + if position['has_position']: + if position['is_long']: + exit_price = position['entry_price'] * (1 + exit_perc) + else: + exit_price = position['entry_price'] * (1 - exit_perc) + + # Check time limit for existing positions + current_time = int(time.time()) + + # Main trading logic + if not position['has_position'] and not order_status['has_entry_pending']: + # Look for entry opportunities + + if ask > sell_to_open_limit: + print('SELLING to open - Price above sell level') + exchange.cancel_all_orders(symbol) + + # Create sell order + sell_price = ask * 1.0009 + exchange.create_limit_sell_order(symbol, size, sell_price, params) + print(f'SELL order placed at {sell_price}') + + # Create stop loss + create_stop_order(symbol, high, 'SELL') + time.sleep(300) # 5 minute pause + + elif bid < buy_to_open_limit: + print('BUYING to open - Price below buy level') + exchange.cancel_all_orders(symbol) + + # Create buy order + buy_price = bid * 0.9991 + exchange.create_limit_buy_order(symbol, size, buy_price, params) + print(f'BUY order placed at {buy_price}') + + # Create stop loss + create_stop_order(symbol, low, 'BUY') + time.sleep(300) # 5 minute pause + + else: + print('Price in middle of range - No entry') + + elif position['has_position']: + # Manage existing position + + if order_status['needs_close_order']: + # Create close order at profit target + if position['is_long']: + exchange.create_limit_sell_order(symbol, position['size'], exit_price, params) + print(f'Close order: SELL at {exit_price}') + else: + exchange.create_limit_buy_order(symbol, position['size'], exit_price, params) + print(f'Close order: BUY at {exit_price}') + + if order_status['needs_stop_loss']: + # Create missing stop loss + if position['is_long']: + create_stop_order(symbol, low, 'BUY') + else: + create_stop_order(symbol, high, 'SELL') + + else: + print('All orders in place - Monitoring...') + time.sleep(12) + + print('=' * 40) + +schedule.every(25).seconds.do(bot) + +while True: + try: + schedule.run_pending() + time.sleep(15) + except Exception as e: + print(f'Bot error: {e}') + print('Sleeping 75 seconds...') + time.sleep(75) \ No newline at end of file diff --git a/Session_05/bots/Multi_Ticker_Mean_Reversion.py b/Session_05/bots/Multi_Ticker_Mean_Reversion.py new file mode 100644 index 0000000..787bc2b --- /dev/null +++ b/Session_05/bots/Multi_Ticker_Mean_Reversion.py @@ -0,0 +1,261 @@ +''' +Multi-Ticker Mean Reversion Strategy Overview: + +Strategy Logic: +- Trades multiple cryptocurrencies using mean reversion around 15m SMA +- Uses 4h SMA for trend direction (only trade with the trend) +- BULLISH trend (4h): Only LONG when price drops below 15m SMA +- BEARISH trend (4h): Only SHORT when price rises above 15m SMA +- 5m confirmation: Requires bullish/bearish candle confirmation + +Entry Conditions: +- LONG: 4h bullish + price < 15m SMA + 5m bullish confirmation +- SHORT: 4h bearish + price > 15m SMA + 5m bearish confirmation +- Entry at 0.8% deviation from 15m SMA + +Exit Conditions: +- Profit target: 9% gain +- Stop loss: 8% loss +- Automatic order cancellation every 30 minutes + +Risk Management: +- Position sizing based on account balance +- Multiple symbol exposure with individual PnL tracking +- Leverage: 10x (configurable) + +WARNING: This strategy has not been backtested. Do not run live without proper testing. +''' + +import ccxt +import pandas as pd +import numpy as np +from datetime import datetime +import time, schedule +import random +import os +from dotenv import load_dotenv + +load_dotenv() +api_key = os.getenv('BINANCE_API_KEY') +api_secret = os.getenv('BINANCE_SECRET_KEY') + +exchange = ccxt.binance({ + 'apiKey': api_key, + 'secret': api_secret, + 'enableRateLimit': True, + 'options': { + 'defaultType': 'future', + } +}) + +pos_size = 30 +target = 9 +max_loss = -8 +leverage = 10 +timeframe = '15m' +limit = 97 +sma = 20 +params = {'timeInForce': 'GTC'} + +# Popular crypto symbols for Binance futures +SYMBOLS = [ + 'BTCUSDT', 'ETHUSDT', 'ADAUSDT', 'DOTUSDT', 'LINKUSDT', + 'LTCUSDT', 'BCHUSDT', 'XLMUSDT', 'EOSUSDT', 'TRXUSDT', + 'ETCUSDT', 'DASHUSDT', 'XMRUSDT', 'ZECUSDT', 'XRPUSDT', + 'BNBUSDT', 'SOLUSDT', 'AVAXUSDT', 'MATICUSDT', 'UNIUSDT', + 'SUSHIUSDT', 'AAVEUSDT', 'COMPUSDT', 'MKRUSDT', 'YFIUSDT' +] + +def ask_bid(symbol): + orderbook = exchange.fetch_order_book(symbol) + bid = orderbook['bids'][0][0] + ask = orderbook['asks'][0][0] + return ask, bid + +def get_open_positions(): + positions = exchange.fetch_positions() + active_positions = [] + + for pos in positions: + if pos['symbol'] in SYMBOLS and float(pos['contracts']) != 0: + active_positions.append({ + 'symbol': pos['symbol'], + 'side': pos['side'], + 'size': float(pos['contracts']), + 'entry_price': float(pos['entryPrice']), + 'is_long': pos['side'] == 'long' + }) + + return active_positions + +def kill_switch(symbol): + print(f'Closing position for {symbol}') + + positions = exchange.fetch_positions([symbol]) + for pos in positions: + if pos['symbol'] == symbol and float(pos['contracts']) != 0: + size = float(pos['contracts']) + is_long = pos['side'] == 'long' + + exchange.cancel_all_orders(symbol) + ask, bid = ask_bid(symbol) + + if is_long: + exchange.create_limit_sell_order(symbol, size, ask, params) + print(f'SELL to close: {symbol}') + else: + exchange.create_limit_buy_order(symbol, size, bid, params) + print(f'BUY to close: {symbol}') + + time.sleep(5) + break + +def kill_switch_all(): + print('Closing all positions...') + active_positions = get_open_positions() + + for pos in active_positions: + kill_switch(pos['symbol']) + +def get_sma_data(symbol, timeframe, limit, sma_period): + try: + bars = exchange.fetch_ohlcv(symbol, timeframe=timeframe, limit=limit) + df = pd.DataFrame(bars, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume']) + df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms') + df[f'sma{sma_period}_{timeframe}'] = df['close'].rolling(sma_period).mean() + + # Add signal based on price vs SMA + _, bid = ask_bid(symbol) + df.loc[df[f'sma{sma_period}_{timeframe}'] > bid, 'sig'] = 'SELL' + df.loc[df[f'sma{sma_period}_{timeframe}'] < bid, 'sig'] = 'BUY' + + # Add bullish confirmation (current close > previous close) + df['prev_close'] = df['close'].shift(1) + df['bullish_confirmation'] = df['close'] > df['prev_close'] + + return df + except Exception as e: + print(f'Error getting SMA data for {symbol}: {e}') + return pd.DataFrame() + +def pnl_close(symbol): + positions = exchange.fetch_positions([symbol]) + + for pos in positions: + if pos['symbol'] == symbol and float(pos['contracts']) != 0: + entry_price = float(pos['entryPrice']) + leverage_used = float(pos['leverage']) + is_long = pos['side'] == 'long' + + _, current_price = ask_bid(symbol) + + if is_long: + price_diff = current_price - entry_price + else: + price_diff = entry_price - current_price + + try: + pnl_percent = ((price_diff / entry_price) * leverage_used) * 100 + except: + pnl_percent = 0 + + print(f'{symbol} PnL: {pnl_percent:.2f}%') + + if pnl_percent >= target: + print(f'{symbol} hit profit target: {target}%') + kill_switch(symbol) + elif pnl_percent <= max_loss: + print(f'{symbol} hit stop loss: {max_loss}%') + kill_switch(symbol) + + break + +def get_current_time_info(): + now = datetime.now() + comp24time = int(now.strftime('%H%M')) + return comp24time + +def cancel_all_orders_scheduled(): + comp24time = get_current_time_info() + + # Cancel orders every 30 minutes + cancel_times = [0, 30, 100, 130, 200, 230, 300, 330, 400, 430, 500, 530, + 600, 630, 700, 730, 800, 830, 900, 930, 1000, 1030, 1100, + 1130, 1200, 1230, 1300, 1330, 1400, 1430, 1500, 1530, 1600, + 1630, 1700, 1730, 1800, 1830, 1900, 1930, 2000, 2030, 2100, + 2130, 2200, 2230, 2300, 2330] + + if comp24time in cancel_times: + print('Cancelling all pending orders...') + for symbol in SYMBOLS: + try: + exchange.cancel_all_orders(symbol) + except: + pass + +def bot(): + # Check and close positions first + active_positions = get_open_positions() + for pos in active_positions: + pnl_close(pos['symbol']) + + # Cancel orders if scheduled + cancel_all_orders_scheduled() + + # Select random symbol to analyze + symbol = random.choice(SYMBOLS) + + try: + # Set leverage + exchange.set_leverage(leverage, symbol) + + # Get SMA data for different timeframes + df_4h = get_sma_data(symbol, '4h', 31, sma) + df_15m = get_sma_data(symbol, '15m', 97, sma) + df_5m = get_sma_data(symbol, '5m', 100, sma) + + if df_4h.empty or df_15m.empty or df_5m.empty: + return + + # Get current market data + ask, bid = ask_bid(symbol) + + # Get signals + sig_4h = df_4h['sig'].iloc[-1] + sma_15m = df_15m[f'sma{sma}_15m'].iloc[-1] + bullish_confirmation_5m = df_5m['bullish_confirmation'].iloc[-1] + + # Calculate entry levels + sma_minus_008 = sma_15m * 0.992 # 0.8% below SMA + sma_plus_008 = sma_15m * 1.008 # 0.8% above SMA + + # Check if already in position + in_position = any(pos['symbol'] == symbol for pos in active_positions) + + # Mean reversion strategy + if not in_position: + if sig_4h == 'BUY': # Bullish 4h trend + # Look for longs when price drops below 15m SMA + if bid <= sma_15m and bullish_confirmation_5m: + exchange.cancel_all_orders(symbol) + exchange.create_limit_buy_order(symbol, pos_size, sma_minus_008, params) + print(f'BUY to open: {symbol} at {sma_minus_008}') + + elif sig_4h == 'SELL': # Bearish 4h trend + # Look for shorts when price rises above 15m SMA + if bid >= sma_15m and not bullish_confirmation_5m: + exchange.cancel_all_orders(symbol) + exchange.create_limit_sell_order(symbol, pos_size, sma_plus_008, params) + print(f'SELL to open: {symbol} at {sma_plus_008}') + + except Exception as e: + print(f'Error processing {symbol}: {e}') + +schedule.every(10).seconds.do(bot) + +while True: + try: + schedule.run_pending() + except Exception as e: + print(f'Bot error: {e}. Sleeping 7 seconds...') + time.sleep(7) \ No newline at end of file diff --git a/Session_05/bots/SMA_Order_book.py b/Session_05/bots/SMA_Order_book.py new file mode 100644 index 0000000..516d911 --- /dev/null +++ b/Session_05/bots/SMA_Order_book.py @@ -0,0 +1,300 @@ +''' +SMA + Order Book Strategy Overview: + +Direction Signal: +- Uses 20-day SMA on daily timeframe to determine market bias +- BUY when price > daily SMA, SELL when price < daily SMA + +Entry Logic: +- Uses 15-minute SMA with offset levels for precise entries +- Places dual limit orders around 15m SMA (ยฑ0.1% to ยฑ0.3%) +- Only enters when price aligns with daily SMA direction + +Risk Management: +- Profit target: 8% gain +- Stop loss: 9% loss +- Order book volume analysis confirms exits +- Position size management with incremental entries + +Volume Confirmation: +- Analyzes bid/ask volume ratio over 55 seconds +- Delays exits if volume ratio < 0.4 (insufficient confirmation) + +WARNING: Do not run without thorough backtesting and understanding! +''' + +import ccxt +import pandas as pd +import time, schedule +import os +from dotenv import load_dotenv + +load_dotenv() +api_key = os.getenv('BINANCE_API_KEY') +api_secret = os.getenv('BINANCE_SECRET_KEY') + +exchange = ccxt.binance({ + 'apiKey': api_key, + 'secret': api_secret, + 'enableRateLimit': True, + 'options': { + 'defaultType': 'future', + } +}) + +symbol = 'BTCUSDT' +pos_size = 30 +params = {'timeInForce': 'GTC'} +target = 8 +max_loss = -9 +vol_decimal = 0.4 + +def ask_bid(): + orderbook = exchange.fetch_order_book(symbol) + bid = orderbook['bids'][0][0] + ask = orderbook['asks'][0][0] + return ask, bid + +def daily_sma(): + print('Calculating daily SMA...') + bars = exchange.fetch_ohlcv(symbol, timeframe='1d', limit=100) + df = pd.DataFrame(bars, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume']) + df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms') + df['sma20_d'] = df['close'].rolling(20).mean() + + _, bid = ask_bid() + df.loc[df['sma20_d'] > bid, 'sig'] = 'SELL' + df.loc[df['sma20_d'] < bid, 'sig'] = 'BUY' + + return df + +def f15_sma(): + print('Calculating 15m SMA...') + bars = exchange.fetch_ohlcv(symbol, timeframe='15m', limit=100) + df = pd.DataFrame(bars, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume']) + df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms') + df['sma20_15'] = df['close'].rolling(20).mean() + + df['bp_1'] = df['sma20_15'] * 1.001 + df['bp_2'] = df['sma20_15'] * 0.997 + df['sp_1'] = df['sma20_15'] * 0.999 + df['sp_2'] = df['sma20_15'] * 1.003 + + return df + +def open_positions(): + positions = exchange.fetch_positions([symbol]) + + for pos in positions: + if pos['symbol'] == symbol and float(pos['contracts']) != 0: + side = pos['side'] + size = pos['contracts'] + is_long = side == 'long' + return positions, True, size, is_long + + return [], False, 0, None + +def kill_switch(): + print('Starting kill switch...') + + while True: + _, has_position, position_size, is_long = open_positions() + + if not has_position: + break + + exchange.cancel_all_orders(symbol) + ask, bid = ask_bid() + position_size = float(position_size) + + if is_long: + exchange.create_limit_sell_order(symbol, position_size, ask, params) + print(f'SELL to close: {position_size} {symbol} at {ask}') + else: + exchange.create_limit_buy_order(symbol, position_size, bid, params) + print(f'BUY to close: {position_size} {symbol} at {bid}') + + time.sleep(30) + +def sleep_on_close(): + closed_orders = exchange.fetch_closed_orders(symbol) + + for order in reversed(closed_orders): + if order['status'] == 'closed': + order_time = int(order['timestamp'] / 1000) + current_time = int(exchange.fetch_order_book(symbol)['timestamp'] / 1000) + time_since_trade = (current_time - order_time) / 60 + + if time_since_trade < 59: + print(f'Recent trade {time_since_trade:.1f}m ago, sleeping 60s...') + time.sleep(60) + else: + print(f'Last trade {time_since_trade:.1f}m ago, no sleep needed') + break + + print('Sleep check complete') + +def ob(): + print('Analyzing order book volume...') + + volume_data = [] + + for i in range(11): + orderbook = exchange.fetch_order_book(symbol) + + bid_volume = sum([bid[1] for bid in orderbook['bids']]) + ask_volume = sum([ask[1] for ask in orderbook['asks']]) + + volume_data.append({'bid_vol': bid_volume, 'ask_vol': ask_volume}) + + if i < 10: + time.sleep(5) + + print(f'Sample {i+1}: Bid: {bid_volume}, Ask: {ask_volume}') + + df = pd.DataFrame(volume_data) + total_bid_vol = df['bid_vol'].sum() + total_ask_vol = df['ask_vol'].sum() + + print(f'Total volumes - Bid: {total_bid_vol}, Ask: {total_ask_vol}') + + if total_bid_vol > total_ask_vol: + control_ratio = total_ask_vol / total_bid_vol + print(f'Bulls in control: {control_ratio:.3f}') + else: + control_ratio = total_bid_vol / total_ask_vol + print(f'Bears in control: {control_ratio:.3f}') + + _, has_position, _, is_long = open_positions() + + if has_position: + volume_under_threshold = control_ratio < vol_decimal + position_type = 'long' if is_long else 'short' + print(f'In {position_type} position. Volume under threshold: {volume_under_threshold}') + return volume_under_threshold + else: + print('Not in position') + return None + +def pnl_close(): + print('Checking PnL...') + + positions = exchange.fetch_positions([symbol]) + + position = None + for pos in positions: + if pos['symbol'] == symbol and float(pos['contracts']) != 0: + position = pos + break + + if position is None: + print('No position open') + return False, False, 0, None + + side = position['side'] + size = position['contracts'] + entry_price = float(position['entryPrice']) + leverage = float(position['leverage']) + is_long = side == 'long' + + _, current_price = ask_bid() + + if is_long: + price_diff = current_price - entry_price + else: + price_diff = entry_price - current_price + + try: + pnl_percent = ((price_diff / entry_price) * leverage) * 100 + except: + pnl_percent = 0 + + print(f'PnL: {pnl_percent:.2f}% (Entry: {entry_price}, Current: {current_price})') + + if pnl_percent > target: + print('Target hit! Checking volume...') + volume_under_threshold = ob() + + if volume_under_threshold: + print(f'Volume too low, waiting 30s...') + time.sleep(30) + else: + print('Closing profitable position!') + kill_switch() + return True, True, size, is_long + + elif pnl_percent <= max_loss: + print('Max loss hit! Closing position.') + kill_switch() + return True, True, size, is_long + + elif pnl_percent > 0: + print('In profit but target not reached') + else: + print('In loss but within acceptable range') + + # SMA stop loss check + if True: + df_15m = f15_sma() + sma_15m = df_15m.iloc[-1]['sma20_15'] + stop_loss_level = sma_15m * 1.008 + print(f'SMA stop level: {stop_loss_level}') + + return False, True, size, is_long + +def bot(): + pnl_close() + sleep_on_close() + + df_daily = daily_sma() + df_15m = f15_sma() + ask, bid = ask_bid() + + signal = df_daily.iloc[-1]['sig'] + open_size = pos_size / 2 + + _, in_position, current_size, _ = open_positions() + current_size = float(current_size) if current_size else 0 + current_price = bid + sma_15m = df_15m.iloc[-1]['sma20_15'] + + if not in_position and current_size < pos_size: + exchange.cancel_all_orders(symbol) + + if signal == 'BUY' and current_price > sma_15m: + print('Making BUY orders') + bp_1 = df_15m.iloc[-1]['bp_1'] + bp_2 = df_15m.iloc[-1]['bp_2'] + print(f'Buy prices: {bp_1}, {bp_2}') + + exchange.create_limit_buy_order(symbol, open_size, bp_1, params) + exchange.create_limit_buy_order(symbol, open_size, bp_2, params) + + print('Orders placed, sleeping 2 minutes...') + time.sleep(120) + + elif signal == 'SELL' and current_price < sma_15m: + print('Making SELL orders') + sp_1 = df_15m.iloc[-1]['sp_1'] + sp_2 = df_15m.iloc[-1]['sp_2'] + print(f'Sell prices: {sp_1}, {sp_2}') + + exchange.create_limit_sell_order(symbol, open_size, sp_1, params) + exchange.create_limit_sell_order(symbol, open_size, sp_2, params) + + print('Orders placed, sleeping 2 minutes...') + time.sleep(120) + else: + print('Price not aligned with SMA, sleeping 10 minutes...') + time.sleep(600) + else: + print('Already in position or at size limit') + +schedule.every(28).seconds.do(bot) + +while True: + try: + schedule.run_pending() + except Exception as e: + print(f'ERROR: {e}. Sleeping 30 seconds...') + time.sleep(30) \ No newline at end of file diff --git a/Session_05/bots/Stochastic_RSI_Nadarya.py b/Session_05/bots/Stochastic_RSI_Nadarya.py new file mode 100644 index 0000000..dae26cb --- /dev/null +++ b/Session_05/bots/Stochastic_RSI_Nadarya.py @@ -0,0 +1,72 @@ +''' +Stochastic RSI + Nadarya Strategy Overview: + +Entry Signals: +- LONG: Nadarya buy signal OR Stochastic RSI shows oversold (below 10) +- SHORT: Nadarya sell signal OR Stochastic RSI shows overbought (above 90) + +Exit Signals: +- Close LONG: Nadarya sell signal OR Stochastic RSI overbought 2+ times +- Close SHORT: Nadarya buy signal OR Stochastic RSI oversold 2+ times + +Uses dual confirmation system combining trend-following (Nadarya) with +momentum oscillator (Stochastic RSI) for improved entry/exit timing. + +WARNING: Do not run without thorough backtesting and understanding! +''' + +import ccxt, schedule +from Functions.functions import * +import time +import os +from dotenv import load_dotenv + +load_dotenv() +api_key = os.getenv('BINANCE_API_KEY') +api_secret = os.getenv('BINANCE_SECRET_KEY') + +exchange = ccxt.binance({ + 'apiKey': api_key, + 'secret': api_secret, + 'enableRateLimit': True, + 'options': { + 'defaultType': 'future', + } +}) + +rsi_targets = [10, 90] +rsi_window = 14 +timeframe = '1h' +symbol = 'ETHUSDT' +size = 1000 +params = {'timeInForce': 'GTC'} + +def bot(): + position_info, in_position, long = get_position(exchange, symbol) + candles = get_candle_df(exchange, symbol, timeframe) + nadarya_buy_signal, nadarya_sell_signal = calc_nadarya(candles) + calc_stoch_rsi(candles) + bid = exchange.fetch_ticker(symbol)['bid'] + + if not in_position: + if nadarya_buy_signal or is_oversold(candles['stoch_rsi'], rsi_window, 1, rsi_targets[0]): + exchange.create_limit_buy_order(symbol, size, bid, params) + elif nadarya_sell_signal or is_overbought(candles['stoch_rsi'], rsi_window, 1, rsi_targets[1]): + exchange.create_limit_sell_order(symbol, size, bid, params) + + elif in_position: + if long: + if nadarya_sell_signal or is_overbought(candles['stoch_rsi'], rsi_window, times=2, target=rsi_targets[1]): + close_position(exchange, symbol) + else: + if nadarya_buy_signal or is_oversold(candles['stoch_rsi'], rsi_window, times=2, target=rsi_targets[0]): + close_position(exchange, symbol) + +schedule.every(1).seconds.do(bot) + +while True: + try: + schedule.run_pending() + except Exception as e: + print(f'ERROR RUNNING BOT: {e}. Sleeping 30 seconds...') + time.sleep(30) \ No newline at end of file diff --git a/Session_05/bots/Turtle.py b/Session_05/bots/Turtle.py new file mode 100644 index 0000000..521b1a2 --- /dev/null +++ b/Session_05/bots/Turtle.py @@ -0,0 +1,84 @@ +''' +Turtle Strategy Overview: + +- Works on multiple timeframes: 1m, 5m, 15m, 1h, 4h +- Enter LONG when 55-bar high is broken with upward momentum (limit order at bid) +- Enter SHORT when 55-bar low is broken with downward momentum (limit order at bid) +- Only trades during market hours: 9:30 AM - 4:00 PM EST, Monday-Friday +- Exit before 4:00 PM on Fridays if still in position + +Exit Conditions: +- Take Profit: 0.2% gain from entry price +- Stop Loss: 2x ATR (Average True Range) from entry price + +WARNING: Do not run without thorough backtesting and understanding! +''' + +import ccxt, time, schedule +from Functions.functions import * +import os +from dotenv import load_dotenv + +load_dotenv() +api_key = os.getenv('BINANCE_API_KEY') +api_secret = os.getenv('BINANCE_SECRET_KEY') + +exchange = ccxt.binance({ + 'apiKey': api_key, + 'secret': api_secret, + 'enableRateLimit': True, + 'options': { + 'defaultType': 'future', + } +}) + +timeframe = '1m' +symbol = 'ETHUSDT' +size = 1 +params = {'timeInForce': 'GTC'} +take_profit_percent = 0.2 + +def bot(): + if in_timeframe(): + position_info, in_position, long = get_position(exchange, symbol) + candles = get_candle_df(exchange, symbol, timeframe, limit=55) + ticker = exchange.fetch_ticker(symbol) + bid = ticker['bid'] + open_price = ticker['open'] + + if not in_position: + min_price = candles['low'].min() + max_price = candles['high'].max() + + if bid <= min_price and open_price > min_price: + exchange.create_limit_sell_order(symbol, size, bid, params) + elif bid >= max_price and open_price < max_price: + exchange.create_limit_buy_order(symbol, size, bid, params) + + elif in_position: + calc_atr(candles) + + entry_price = float(position_info['entryPrice']) + atr = candles['ATR'].iloc[-1] + + if long: + take_profit_price = entry_price * (1 + (take_profit_percent / 100)) + stop_loss_price = entry_price - (atr * 2) + else: + take_profit_price = entry_price * (1 - (take_profit_percent / 100)) + stop_loss_price = entry_price + (atr * 2) + + if hit_target(bid, take_profit_price, stop_loss_price, long): + close_position(exchange, symbol) + + elif end_of_trading_week(): + close_position(exchange, symbol) + +schedule.every(60).seconds.do(bot) + +while True: + try: + schedule.run_pending() + except Exception as e: + print(f'ERROR RUNNING BOT: {e}. Sleeping 30 seconds...') + time.sleep(30) \ No newline at end of file