{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "24762fa2",
   "metadata": {},
   "source": [
    "# 📚 PaperBroker Client Tutorial\n",
    "\n",
    "**Version:** 0.2.3  \n",
    "**Goal:** Learn to use `paperbroker-client` for trading via FIX protocol.\n",
    "\n",
    "---\n",
    "\n",
    "## 📋 Table of Contents\n",
    "\n",
    "| # | Topic | Description |\n",
    "|---|-------|-------------|\n",
    "| 1 | [Architecture](#1-architecture) | FIX Client ↔ Server overview |\n",
    "| 2 | [Configuration & Connection](#2-configuration--connection) | Setup connection parameters |\n",
    "| 3 | [Event-Driven Design](#3-event-driven-design) | Handle events without polling |\n",
    "| 4 | [Place & Cancel Orders](#4-place--cancel-orders) | Order management |\n",
    "| 5 | [Market Data](#5-market-data) | Real-time quotes |\n",
    "| 6 | [Account & Portfolio](#6-account--portfolio) | Balance and positions |\n",
    "\n",
    "---\n",
    "\n",
    "## 🎯 Quick Start\n",
    "\n",
    "```python\n",
    "from paperbroker import PaperBrokerClient\n",
    "\n",
    "client = PaperBrokerClient(\n",
    "    default_sub_account=\"D1\",\n",
    "    username=\"your_user\",\n",
    "    password=\"your_pass\",\n",
    "    rest_base_url=\"http://server:9090\",\n",
    "    socket_connect_host=\"server_ip\",\n",
    "    socket_connect_port=5001,\n",
    "    sender_comp_id=\"YOUR-FIX\",\n",
    "    target_comp_id=\"SERVER\",\n",
    ")\n",
    "\n",
    "client.connect()\n",
    "client.wait_until_logged_on(timeout=10)\n",
    "# Ready to trade!\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "839d24bc",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## 1. Architecture\n",
    "\n",
    "### 1.1. FIX Protocol Overview\n",
    "\n",
    "```\n",
    "┌─────────────────┐         FIX 4.4          ┌─────────────────┐\n",
    "│  FIX Client     │ ◀════════════════════▶  │  FIX Server     │\n",
    "│  (paperbroker)  │    TCP/Socket           │  (Exchange)     │\n",
    "└────────┬────────┘                         └─────────────────┘\n",
    "         │                                           │\n",
    "         │ REST API                                  │\n",
    "         ▼                                           ▼\n",
    "┌─────────────────┐                         ┌─────────────────┐\n",
    "│  Account Info   │                         │  Order Matching │\n",
    "│  Portfolio      │                         │  Market Data    │\n",
    "└─────────────────┘                         └─────────────────┘\n",
    "```\n",
    "\n",
    "### 1.2. Connection Parameters\n",
    "\n",
    "| Parameter | Description | Example |\n",
    "|-----------|-------------|----------|\n",
    "| `socket_connect_host` | FIX server IP/hostname | `100.97.83.110` |\n",
    "| `socket_connect_port` | FIX port (TCP) | `5001` |\n",
    "| `sender_comp_id` | Your client identifier | `BL-FIX` |\n",
    "| `target_comp_id` | Server identifier | `SERVER` |\n",
    "| `username` | FIX account username | `BL01` |\n",
    "| `password` | FIX account password | `***` |\n",
    "| `rest_base_url` | REST API endpoint | `http://100.97.83.110:9090` |\n",
    "\n",
    "### 1.3. FIX Message Flow\n",
    "\n",
    "```\n",
    "Client                              Server\n",
    "  │                                    │\n",
    "  │──── Logon (35=A) ─────────────────▶│\n",
    "  │◀─── Logon (35=A) ──────────────────│  ✅ Session established\n",
    "  │                                    │\n",
    "  │──── NewOrderSingle (35=D) ────────▶│\n",
    "  │◀─── ExecutionReport (35=8) ────────│  📊 Order status\n",
    "  │                                    │\n",
    "  │──── OrderCancelRequest (35=F) ────▶│\n",
    "  │◀─── ExecutionReport (35=8) ────────│  ❌ Canceled\n",
    "  │                                    │\n",
    "  │──── Logout (35=5) ─────────────────▶│\n",
    "  │◀─── Logout (35=5) ──────────────────│  👋 Session closed\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "71450c45",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## 2. Configuration & Connection"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "62552d09",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ Imports ready!\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# CELL 1: Import & Setup\n",
    "# ============================================================================\n",
    "# Required imports for PaperBroker client:\n",
    "#   - os: Environment variables for configuration\n",
    "#   - sys: Path manipulation for imports\n",
    "#   - time: Delays between operations\n",
    "#   - logging: Standard Python logging\n",
    "#   - pathlib: Cross-platform path handling\n",
    "#   - threading.Event: Synchronization between threads\n",
    "#   - dotenv: Load .env file for secrets\n",
    "# ============================================================================\n",
    "\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "import logging\n",
    "from pathlib import Path\n",
    "from threading import Event as ThreadEvent\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "# Add parent directory to path (only needed when running from examples/)\n",
    "# This allows importing paperbroker module from the parent directory\n",
    "sys.path.insert(0, str(Path.cwd().parent))\n",
    "\n",
    "# Import the main client class\n",
    "from paperbroker import PaperBrokerClient\n",
    "\n",
    "# Load environment variables from .env file (if exists)\n",
    "# .env file should contain: PAPER_USERNAME, PAPER_PASSWORD, etc.\n",
    "load_dotenv()\n",
    "\n",
    "# Setup logging - controls what messages appear in console\n",
    "# Levels: DEBUG < INFO < WARNING < ERROR < CRITICAL\n",
    "logging.basicConfig(\n",
    "    level=logging.INFO,\n",
    "    format='[%(asctime)s] %(levelname)s: %(message)s',\n",
    "    datefmt='%H:%M:%S'\n",
    ")\n",
    "logger = logging.getLogger(__name__)\n",
    "\n",
    "print(\"✅ Imports ready!\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "e87cf707",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "📋 Configuration:\n",
      "   socket_host: 100.97.83.110\n",
      "   socket_port: 5001\n",
      "   sender_comp_id: LOCAL_FIX\n",
      "   target_comp_id: SERVER\n",
      "   username: test2226\n",
      "   password: ***\n",
      "   rest_base_url: http://100.97.83.110:9090\n",
      "   account_d1: TEST2226A\n",
      "   account_d2: TEST2226B\n",
      "   vn30f: HNXDS:VN30F2602\n",
      "   mwg: HSX:MWG\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# CELL 2: Connection Configuration\n",
    "# ============================================================================\n",
    "# This dictionary contains all parameters needed for connection.\n",
    "# Values are loaded from environment variables with fallback defaults.\n",
    "#\n",
    "# FIX Connection:\n",
    "#   - socket_host/port: TCP connection to FIX server\n",
    "#   - sender_comp_id: Your unique client identifier in FIX session\n",
    "#   - target_comp_id: Server identifier (usually \"SERVER\")\n",
    "#\n",
    "# Authentication:\n",
    "#   - username/password: FIX account credentials\n",
    "#\n",
    "# REST API:\n",
    "#   - rest_base_url: HTTP endpoint for account/portfolio queries\n",
    "#\n",
    "# Trading:\n",
    "#   - account_d1/d2: Sub-account IDs for derivatives trading\n",
    "#   - Symbol format: \"{Exchange}:{Symbol}\" e.g., \"HNXDS:VN30F2602\"\n",
    "# ============================================================================\n",
    "\n",
    "CONFIG = {\n",
    "    # ---- FIX Connection (TCP Socket) ----\n",
    "    # IP/hostname and port of the FIX gateway\n",
    "    \"socket_host\": os.getenv(\"SOCKET_HOST\", \"100.97.83.110\"),\n",
    "    \"socket_port\": int(os.getenv(\"SOCKET_PORT\", \"5001\")),\n",
    "    \n",
    "    # ---- FIX Session Identifiers ----\n",
    "    # sender_comp_id: Must be unique per connection session\n",
    "    # target_comp_id: Usually \"SERVER\" - the exchange's identifier\n",
    "    \"sender_comp_id\": os.getenv(\"SENDER_COMP_ID\", \"BL-FIX\"),\n",
    "    \"target_comp_id\": os.getenv(\"TARGET_COMP_ID\", \"SERVER\"),\n",
    "    \n",
    "    # ---- Authentication ----\n",
    "    # Credentials for FIX logon message (Tag 553/554)\n",
    "    \"username\": os.getenv(\"PAPER_USERNAME\", \"BL01\"),\n",
    "    \"password\": os.getenv(\"PAPER_PASSWORD\", \"123\"),\n",
    "    \n",
    "    # ---- REST API ----\n",
    "    # HTTP endpoint for non-FIX operations (balance, portfolio)\n",
    "    \"rest_base_url\": os.getenv(\"PAPER_REST_BASE_URL\", \"http://100.97.83.110:9090\"),\n",
    "    \n",
    "    # ---- Sub-Accounts ----\n",
    "    # Separate accounts under same master account\n",
    "    # D1 and D2 can cross-match with each other\n",
    "    \"account_d1\": os.getenv(\"PAPER_ACCOUNT_ID_D1\", \"D1\"),\n",
    "    \"account_d2\": os.getenv(\"PAPER_ACCOUNT_ID_D2\", \"D2\"),\n",
    "    \n",
    "    # ---- Trading Symbols ----\n",
    "    # Format: {Exchange}:{Symbol}\n",
    "    # HNXDS = HNX Derivatives, HSX = Ho Chi Minh Stock Exchange\n",
    "    \"vn30f\": \"HNXDS:VN30F2602\",  # VN30 Futures Feb 2026\n",
    "    \"mwg\": \"HSX:MWG\",            # MWG Stock\n",
    "}\n",
    "\n",
    "# Display configuration (mask password for security)\n",
    "print(\"📋 Configuration:\")\n",
    "for key, value in CONFIG.items():\n",
    "    if key == \"password\":\n",
    "        print(f\"   {key}: ***\")\n",
    "    else:\n",
    "        print(f\"   {key}: {value}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "9bf65405",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "🔌 Connecting...\n",
      "✅ Connected to FIX server!\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# CELL 3: Create Client & Connect\n",
    "# ============================================================================\n",
    "# PaperBrokerClient wraps both FIX and REST connections in one object.\n",
    "#\n",
    "# Constructor parameters:\n",
    "#   - default_sub_account: Default account for operations\n",
    "#   - username/password: FIX authentication\n",
    "#   - rest_base_url: REST API endpoint\n",
    "#   - socket_connect_host/port: FIX TCP connection\n",
    "#   - sender_comp_id/target_comp_id: FIX session identifiers\n",
    "#   - console: True=show DEBUG in console, False=WARNING+ only\n",
    "#\n",
    "# Connection flow:\n",
    "#   1. client.connect() - Opens TCP socket, initiates FIX logon\n",
    "#   2. wait_until_logged_on() - Blocks until Logon response received\n",
    "#   3. If logon fails, last_logon_error() returns the reason\n",
    "# ============================================================================\n",
    "\n",
    "# Create the client with all configuration\n",
    "client = PaperBrokerClient(\n",
    "    # Default sub-account for operations when not using context manager\n",
    "    default_sub_account=CONFIG[\"account_d1\"],\n",
    "    \n",
    "    # FIX authentication (sent in Logon message)\n",
    "    username=CONFIG[\"username\"],\n",
    "    password=CONFIG[\"password\"],\n",
    "    \n",
    "    # REST API endpoint for balance/portfolio queries\n",
    "    rest_base_url=CONFIG[\"rest_base_url\"],\n",
    "    \n",
    "    # FIX TCP socket connection\n",
    "    socket_connect_host=CONFIG[\"socket_host\"],\n",
    "    socket_connect_port=CONFIG[\"socket_port\"],\n",
    "    \n",
    "    # FIX session identifiers (SenderCompID/TargetCompID)\n",
    "    sender_comp_id=CONFIG[\"sender_comp_id\"],\n",
    "    target_comp_id=CONFIG[\"target_comp_id\"],\n",
    "    \n",
    "    # Logging: False = only WARNING/ERROR to console\n",
    "    # Set True for debugging to see all FIX messages\n",
    "    console=False,\n",
    ")\n",
    "\n",
    "# Initiate connection (non-blocking, starts background threads)\n",
    "print(\"🔌 Connecting...\")\n",
    "client.connect()\n",
    "\n",
    "# Block until FIX Logon response received (with timeout)\n",
    "if client.wait_until_logged_on(timeout=10):\n",
    "    print(\"✅ Connected to FIX server!\")\n",
    "else:\n",
    "    # Logon failed - get the error message\n",
    "    error = client.last_logon_error()\n",
    "    print(f\"❌ Connection failed: {error}\")\n",
    "\n",
    "# Brief pause to let session stabilize\n",
    "time.sleep(1)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8ccacbc8",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## 3. Event System\n",
    "\n",
    "### Available Events\n",
    "\n",
    "| Event | When | Callback params |\n",
    "|-------|------|----------------|\n",
    "| `fix:logon` | Session established | `session_id` |\n",
    "| `fix:logout` | Session closed | `session_id, reason` |\n",
    "| `fix:order:accepted` | Order accepted | `cl_ord_id, status` |\n",
    "| `fix:order:filled` | Order filled | `cl_ord_id, last_px, last_qty` |\n",
    "| `fix:order:canceled` | Order canceled | `orig_cl_ord_id, status` |\n",
    "| `fix:order:rejected` | Order rejected | `cl_ord_id, reason` |\n",
    "\n",
    "### Usage\n",
    "\n",
    "```python\n",
    "# Subscribe to events with client.on(event_name, handler)\n",
    "client.on(\"fix:order:filled\", my_handler_function)\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "b98df675",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ Event handlers registered!\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# CELL 4: Event Handlers\n",
    "# ============================================================================\n",
    "# Event handlers receive data from FIX server responses in real-time.\n",
    "# Each handler is called when the corresponding event occurs.\n",
    "# \n",
    "# Parameters vary by event type - see event table above.\n",
    "# Always include **kwargs for forward compatibility.\n",
    "# ============================================================================\n",
    "\n",
    "def on_order_accepted(cl_ord_id, status, **kwargs):\n",
    "    \"\"\"\n",
    "    Called when order is accepted by server.\n",
    "    \n",
    "    Args:\n",
    "        cl_ord_id: Client order ID (UUID string)\n",
    "        status: Order status code (e.g., \"NEW\", \"PENDING\")\n",
    "    \"\"\"\n",
    "    print(f\"✅ Order ACCEPTED: {cl_ord_id[:8]}... | Status: {status}\")\n",
    "\n",
    "def on_order_filled(cl_ord_id, last_px, last_qty, status, **kwargs):\n",
    "    \"\"\"\n",
    "    Called when order is filled (full or partial).\n",
    "    \n",
    "    Args:\n",
    "        cl_ord_id: Client order ID\n",
    "        last_px: Fill price\n",
    "        last_qty: Fill quantity  \n",
    "        status: Order status (e.g., \"FILLED\", \"PARTIALLY_FILLED\")\n",
    "    \"\"\"\n",
    "    print(f\"🎯 Order FILLED: {cl_ord_id[:8]}... | {last_qty} @ {last_px:,.0f}\")\n",
    "\n",
    "def on_order_canceled(orig_cl_ord_id, status, **kwargs):\n",
    "    \"\"\"\n",
    "    Called when order is successfully canceled.\n",
    "    \n",
    "    Args:\n",
    "        orig_cl_ord_id: Original client order ID that was canceled\n",
    "        status: Cancellation status\n",
    "    \"\"\"\n",
    "    print(f\"❌ Order CANCELED: {orig_cl_ord_id[:8]}...\")\n",
    "\n",
    "def on_order_rejected(cl_ord_id, reason, **kwargs):\n",
    "    \"\"\"\n",
    "    Called when order is rejected by server.\n",
    "    \n",
    "    Args:\n",
    "        cl_ord_id: Client order ID\n",
    "        reason: Rejection reason (e.g., \"Insufficient balance\")\n",
    "    \"\"\"\n",
    "    print(f\"🚫 Order REJECTED: {cl_ord_id[:8]}... | Reason: {reason}\")\n",
    "\n",
    "# Register handlers with the client\n",
    "# Format: client.on(\"<event_name>\", handler_function)\n",
    "client.on(\"fix:order:accepted\", on_order_accepted)\n",
    "client.on(\"fix:order:filled\", on_order_filled)\n",
    "client.on(\"fix:order:canceled\", on_order_canceled)\n",
    "client.on(\"fix:order:rejected\", on_order_rejected)\n",
    "\n",
    "print(\"✅ Event handlers registered!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ae2dc947",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## 4. Place & Cancel Orders\n",
    "\n",
    "### 4.1. Order Parameters\n",
    "\n",
    "| Parameter | Type | Description |\n",
    "|-----------|------|-------------|\n",
    "| `full_symbol` | str | `\"HNXDS:VN30F2602\"` or `\"HSX:MWG\"` |\n",
    "| `side` | str | `\"BUY\"` or `\"SELL\"` |\n",
    "| `qty` | int | Quantity (contracts/shares) |\n",
    "| `price` | float | Price (VN30F = points, Stock = VND) |\n",
    "| `ord_type` | str | `\"LIMIT\"`, `\"MARKET\"` |"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "724b840c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "📤 Order placed: 7ed7e28e...\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# CELL 5: Place Order\n",
    "# ============================================================================\n",
    "# place_order() sends a NewOrderSingle (35=D) FIX message.\n",
    "#\n",
    "# Parameters:\n",
    "#   - full_symbol: Exchange:Symbol format (e.g., \"HNXDS:VN30F2602\")\n",
    "#   - side: \"BUY\" or \"SELL\"\n",
    "#   - qty: Number of contracts (derivatives) or shares (stocks)\n",
    "#   - price: Price in exchange units (points for VN30F, VND for stocks)\n",
    "#   - ord_type: \"LIMIT\" or \"MARKET\"\n",
    "#\n",
    "# Returns: cl_ord_id (Client Order ID) - UUID string to track the order\n",
    "#\n",
    "# use_sub_account() context manager:\n",
    "#   - Temporarily switches the active sub-account\n",
    "#   - Thread-safe - can use in multi-threaded code\n",
    "#   - Reverts to default when exiting the `with` block\n",
    "# ============================================================================\n",
    "\n",
    "# Switch to D1 sub-account for this order\n",
    "with client.use_sub_account(CONFIG[\"account_d1\"]):\n",
    "    \n",
    "    # Place a SELL order at a far price (unlikely to match immediately)\n",
    "    # This allows us to demonstrate cancellation\n",
    "    order_id = client.place_order(\n",
    "        full_symbol=CONFIG[\"vn30f\"],  # HNXDS:VN30F2602\n",
    "        side=\"SELL\",\n",
    "        qty=1,                         # 1 contract\n",
    "        price=1985.0,                  # Price in points\n",
    "        ord_type=\"LIMIT\"\n",
    "    )\n",
    "    \n",
    "    # Print truncated order ID (full ID is 36 chars)\n",
    "    print(f\"📤 Order placed: {order_id[:16]}...\")\n",
    "\n",
    "# Event handler (on_order_accepted) will print when server confirms"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "ce3ddb54",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "⏳ Waiting 2 seconds before cancel...\n",
      "✅ Order ACCEPTED: 7ed7e28e... | Status: NEW\n",
      "🗑️ Cancel request sent for: 7ed7e28e...\n",
      "❌ Order CANCELED: 7ed7e28e...\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# CELL 6: Cancel Order\n",
    "# ============================================================================\n",
    "# cancel_order() sends an OrderCancelRequest (35=F) FIX message.\n",
    "#\n",
    "# The order must still be active (not filled/canceled) to cancel.\n",
    "# Server responds with ExecutionReport confirming cancellation.\n",
    "# Event handler (on_order_canceled) will print the confirmation.\n",
    "# ============================================================================\n",
    "\n",
    "# Wait before canceling (simulate some time passing)\n",
    "print(\"⏳ Waiting 2 seconds before cancel...\")\n",
    "time.sleep(2)\n",
    "\n",
    "# Send cancel request\n",
    "# Uses the order_id returned from place_order()\n",
    "client.cancel_order(order_id)\n",
    "print(f\"🗑️ Cancel request sent for: {order_id[:16]}...\")\n",
    "\n",
    "# Wait for server confirmation via event handler\n",
    "time.sleep(1)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "35c07d8d",
   "metadata": {},
   "source": [
    "### 4.2. Cross-Matching (D1 ↔ D2)\n",
    "\n",
    "Cross-matching allows 2 sub-accounts to match orders with each other:\n",
    "\n",
    "```\n",
    "D1 SELL @ 1985 ──┐\n",
    "                 ├──▶ INSTANT MATCH\n",
    "D2 BUY  @ 1985 ──┘\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "29746c8a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "🔄 Cross-matching: D1 SELL vs D2 BUY @ 1,985\n",
      "   D1 SELL: 79e0bada...\n",
      "✅ Order ACCEPTED: 79e0bada... | Status: NEW\n",
      "   D2 BUY:  431bf1cc...\n",
      "\n",
      "⏳ Waiting for fills...\n",
      "✅ Order ACCEPTED: 431bf1cc... | Status: NEW\n",
      "🎯 Order FILLED: 431bf1cc... | 1.0 @ 1,985\n",
      "🎯 Order FILLED: 79e0bada... | 1.0 @ 1,985\n",
      "✅ Check event output above!\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# CELL 7: Cross-Matching Demo\n",
    "# ============================================================================\n",
    "# Cross-matching: Two sub-accounts under the same master can match orders.\n",
    "#\n",
    "# Flow:\n",
    "#   1. D1 places SELL order → sits in order book\n",
    "#   2. D2 places BUY order at same price → matches instantly with D1\n",
    "#   3. Both receive ExecutionReport with fill details\n",
    "#\n",
    "# Key points:\n",
    "#   - Same price required for matching\n",
    "#   - D1 order must be in book before D2 can match\n",
    "#   - Both sides receive fill events simultaneously\n",
    "# ============================================================================\n",
    "\n",
    "CROSS_PRICE = 1985.0  # Match price in points\n",
    "CROSS_QTY = 1         # 1 contract\n",
    "\n",
    "print(f\"🔄 Cross-matching: D1 SELL vs D2 BUY @ {CROSS_PRICE:,.0f}\")\n",
    "\n",
    "# Step 1: D1 places SELL order (enters the order book)\n",
    "with client.use_sub_account(CONFIG[\"account_d1\"]):\n",
    "    sell_order = client.place_order(\n",
    "        full_symbol=CONFIG[\"vn30f\"],\n",
    "        side=\"SELL\",\n",
    "        qty=CROSS_QTY,\n",
    "        price=CROSS_PRICE,\n",
    "        ord_type=\"LIMIT\"\n",
    "    )\n",
    "    print(f\"   D1 SELL: {sell_order[:12]}...\")\n",
    "\n",
    "# Wait to ensure D1 order is in the book before D2 matches\n",
    "time.sleep(0.5)\n",
    "\n",
    "# Step 2: D2 places BUY order → matches immediately with D1's SELL\n",
    "with client.use_sub_account(CONFIG[\"account_d2\"]):\n",
    "    buy_order = client.place_order(\n",
    "        full_symbol=CONFIG[\"vn30f\"],\n",
    "        side=\"BUY\",\n",
    "        qty=CROSS_QTY,\n",
    "        price=CROSS_PRICE,\n",
    "        ord_type=\"LIMIT\"\n",
    "    )\n",
    "    print(f\"   D2 BUY:  {buy_order[:12]}...\")\n",
    "\n",
    "# Wait for fill events from server\n",
    "print(\"\\n⏳ Waiting for fills...\")\n",
    "time.sleep(2)\n",
    "print(\"✅ Check event output above!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f5e2cd1f",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## 5. Market Data\n",
    "\n",
    "### Two ways to get data:\n",
    "\n",
    "| Mode | Use Case | API |\n",
    "|------|----------|-----|\n",
    "| **Query** | Get current price (one-time) | `await client.query(symbol)` |\n",
    "| **Subscribe** | Real-time streaming | `await client.subscribe(symbol, callback)` |\n",
    "\n",
    "### Data Sources:\n",
    "- **Redis**: Low latency, pub/sub\n",
    "- **Kafka**: High throughput, replay capability"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "49443540",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "⚠️ No data available (check Redis connection)\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# CELL 8: Market Data Query (Redis)\n",
    "# ============================================================================\n",
    "# RedisMarketDataClient connects to Redis server for market quotes.\n",
    "# Use query() for one-time snapshot, subscribe() for real-time stream.\n",
    "#\n",
    "# QuoteSnapshot attributes:\n",
    "#   - instrument: Symbol (e.g., \"HNXDS:VN30F2602\")\n",
    "#   - latest_matched_price: Last traded price\n",
    "#   - latest_matched_quantity: Last traded volume\n",
    "#   - bid_price_1, bid_quantity_1: Best bid\n",
    "#   - ask_price_1, ask_quantity_1: Best ask\n",
    "#   - bid_price_2/3, ask_price_2/3: Level 2/3 depth\n",
    "#   - ref_price, ceiling_price, floor_price: Reference prices\n",
    "#   - spread: Calculated bid-ask spread (property)\n",
    "#   - spread_bps: Spread in basis points (property)\n",
    "# ============================================================================\n",
    "\n",
    "import asyncio\n",
    "from paperbroker.market_data import RedisMarketDataClient\n",
    "\n",
    "async def query_market_data():\n",
    "    \"\"\"\n",
    "    Fetch current price snapshot from Redis.\n",
    "    This is a one-time query, not a subscription.\n",
    "    \"\"\"\n",
    "    \n",
    "    # Create Redis client with connection parameters\n",
    "    redis_client = RedisMarketDataClient(\n",
    "        host=os.getenv(\"MARKET_REDIS_HOST\", \"localhost\"),\n",
    "        port=int(os.getenv(\"MARKET_REDIS_PORT\", \"6379\")),\n",
    "        password=os.getenv(\"MARKET_REDIS_PASSWORD\"),\n",
    "    )\n",
    "    \n",
    "    try:\n",
    "        # Query returns a QuoteSnapshot object or None\n",
    "        quote = await redis_client.query(CONFIG[\"vn30f\"])\n",
    "        \n",
    "        if quote:\n",
    "            # Display quote information\n",
    "            print(f\"📊 {quote.instrument}\")\n",
    "            print(f\"   Last Price: {quote.latest_matched_price:,.2f}\")\n",
    "            \n",
    "            # bid_quantity_1 and ask_quantity_1 are the correct attribute names\n",
    "            print(f\"   Bid: {quote.bid_price_1:,.2f} x {quote.bid_quantity_1}\")\n",
    "            print(f\"   Ask: {quote.ask_price_1:,.2f} x {quote.ask_quantity_1}\")\n",
    "            \n",
    "            # spread_bps is a calculated property (basis points)\n",
    "            if quote.spread_bps:\n",
    "                print(f\"   Spread: {quote.spread_bps:.1f} bps\")\n",
    "        else:\n",
    "            print(\"⚠️ No data available (check Redis connection)\")\n",
    "    finally:\n",
    "        # Always close the client when done\n",
    "        await redis_client.close()\n",
    "\n",
    "# Run the async function in notebook\n",
    "await query_market_data()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e249239d",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## 6. Account & Portfolio\n",
    "\n",
    "REST API endpoints for account management:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "b80d1d20",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "💰 Account Balances\n",
      "==================================================\n",
      "\n",
      "📋 Sub-Account: TEST2226A\n",
      "   Available Cash:   8,598,675,140 VND\n",
      "   Total Balance:    9,243,800,140 VND\n",
      "\n",
      "📋 Sub-Account: TEST2226B\n",
      "   Available Cash:  10,105,674,860 VND\n",
      "   Total Balance:   10,750,799,860 VND\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# CELL 9: Account Balance\n",
    "# ============================================================================\n",
    "# REST API calls for account information.\n",
    "#\n",
    "# get_cash_balance():\n",
    "#   Returns: {\"remainCash\": float, \"totalCash\": float, ...}\n",
    "#   - remainCash: Available cash for trading\n",
    "#\n",
    "# get_account_balance():\n",
    "#   Returns: {\"totalBalance\": float, \"equity\": float, ...}\n",
    "#   - totalBalance: Total account value including positions\n",
    "# ============================================================================\n",
    "\n",
    "print(\"💰 Account Balances\")\n",
    "print(\"=\" * 50)\n",
    "\n",
    "# Query both sub-accounts\n",
    "for acc_id in [CONFIG[\"account_d1\"], CONFIG[\"account_d2\"]]:\n",
    "    # Context manager switches active sub-account for REST calls\n",
    "    with client.use_sub_account(acc_id):\n",
    "        cash = client.get_cash_balance()\n",
    "        total = client.get_account_balance()\n",
    "        \n",
    "        print(f\"\\n📋 Sub-Account: {acc_id}\")\n",
    "        print(f\"   Available Cash: {cash.get('remainCash', 0):>15,.0f} VND\")\n",
    "        print(f\"   Total Balance:  {total.get('totalBalance', 0):>15,.0f} VND\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "ece637bb",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "📊 Max Placeable Calculator\n",
      "==================================================\n",
      "\n",
      "Symbol: HNXDS:VN30F2602\n",
      "Price:  1985 points\n",
      "Side:   BUY\n",
      "\n",
      "   Max Quantity:           186 contracts\n",
      "   Per Unit Cost:   49,645,000 VND\n",
      "   Available Cash:  8,598,675,140 VND\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# CELL 10: Max Placeable Quantity\n",
    "# ============================================================================\n",
    "# get_max_placeable() calculates maximum order size.\n",
    "#\n",
    "# Parameters:\n",
    "#   - symbol: Trading symbol\n",
    "#   - price: Order price\n",
    "#   - side: \"BUY\" or \"SELL\"\n",
    "#\n",
    "# Returns: {\n",
    "#   \"success\": bool,\n",
    "#   \"maxQty\": int,       # Maximum quantity you can order\n",
    "#   \"perUnitCost\": float, # Cost per contract/share\n",
    "#   \"remainCash\": float,  # Available cash\n",
    "#   \"error\": str         # Error message if failed\n",
    "# }\n",
    "#\n",
    "# For derivatives: perUnitCost = margin requirement per contract\n",
    "# For stocks: perUnitCost = price × 1 share\n",
    "# ============================================================================\n",
    "\n",
    "print(\"📊 Max Placeable Calculator\")\n",
    "print(\"=\" * 50)\n",
    "\n",
    "# Calculate max BUY quantity for VN30F at 1985\n",
    "result = client.get_max_placeable(\n",
    "    symbol=CONFIG[\"vn30f\"],\n",
    "    price=1985,\n",
    "    side=\"BUY\"\n",
    ")\n",
    "\n",
    "if result.get(\"success\"):\n",
    "    print(f\"\\nSymbol: {CONFIG['vn30f']}\")\n",
    "    print(f\"Price:  1985 points\")\n",
    "    print(f\"Side:   BUY\")\n",
    "    print(f\"\")\n",
    "    print(f\"   Max Quantity:    {result.get('maxQty', 0):>10} contracts\")\n",
    "    print(f\"   Per Unit Cost:   {result.get('perUnitCost', 0):>10,.0f} VND\")\n",
    "    print(f\"   Available Cash:  {result.get('remainCash', 0):>10,.0f} VND\")\n",
    "else:\n",
    "    print(f\"⚠️ Error: {result.get('error', 'Unknown')}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "3d058798",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "📈 Portfolio Positions\n",
      "==========================================================================================\n",
      "Instrument                Qty    Avg Price      Total Cost             PnL\n",
      "------------------------------------------------------------------------------------------\n",
      "HNXDS:VN30F2602           -13     1,985.00     645,125,000               0\n",
      "------------------------------------------------------------------------------------------\n",
      "TOTAL                                          645,125,000               0\n",
      "\n",
      "📊 Margin: Derivative=25%, Equity=100%\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# CELL 11: Portfolio Positions\n",
    "# ============================================================================\n",
    "# get_portfolio_by_sub() returns current positions.\n",
    "#\n",
    "# Returns: {\n",
    "#   \"success\": bool,\n",
    "#   \"items\": [\n",
    "#     {\n",
    "#       \"instrument\": str,     # Trading symbol (e.g., \"HNXDS:VN30F2602\")\n",
    "#       \"quantity\": int,       # Position size (+ = long, - = short)\n",
    "#       \"avgPrice\": float,     # Average entry price (from server)\n",
    "#       \"totalCost\": float,    # Total cost basis\n",
    "#       \"currentPrice\": float, # Current market price (may be None)\n",
    "#       \"marketValue\": float,  # quantity × currentPrice (may be None)\n",
    "#       \"pnl\": float           # Unrealized P&L (may be None)\n",
    "#     },\n",
    "#     ...\n",
    "#   ],\n",
    "#   \"derivativeMargin\": float, # e.g., 0.25 (25% margin for derivatives)\n",
    "#   \"equityMargin\": float      # e.g., 1.0 (100% for equities)\n",
    "# }\n",
    "# ============================================================================\n",
    "\n",
    "print(\"📈 Portfolio Positions\")\n",
    "print(\"=\" * 90)\n",
    "\n",
    "portfolio = client.get_portfolio_by_sub()\n",
    "\n",
    "if portfolio.get(\"success\"):\n",
    "    items = portfolio.get(\"items\", [])\n",
    "    \n",
    "    if items:\n",
    "        # Print header\n",
    "        print(f\"{'Instrument':<20} {'Qty':>8} {'Avg Price':>12} {'Total Cost':>15} {'PnL':>15}\")\n",
    "        print(\"-\" * 90)\n",
    "        \n",
    "        # Print each position\n",
    "        for pos in items:\n",
    "            instrument = pos.get(\"instrument\", \"\")\n",
    "            qty = pos.get(\"quantity\", 0)\n",
    "            avg_px = pos.get(\"avgPrice\", 0)\n",
    "            cost = pos.get(\"totalCost\", 0)\n",
    "            pnl = pos.get(\"pnl\")\n",
    "            \n",
    "            # Format PnL with color\n",
    "            if pnl is not None:\n",
    "                pnl_str = f\"{pnl:>15,.0f}\"\n",
    "                if pnl > 0:\n",
    "                    pnl_str = f\"🟢 +{pnl:,.0f}\"\n",
    "                elif pnl < 0:\n",
    "                    pnl_str = f\"🔴 {pnl:,.0f}\"\n",
    "            else:\n",
    "                pnl_str = \"N/A\"\n",
    "            \n",
    "            print(f\"{instrument:<20} {qty:>8} {avg_px:>12,.2f} {cost:>15,.0f} {pnl_str:>15}\")\n",
    "        \n",
    "        # Summary\n",
    "        print(\"-\" * 90)\n",
    "        total_cost = sum(float(p.get(\"totalCost\", 0) or 0) for p in items)\n",
    "        total_pnl = sum(float(p.get(\"pnl\", 0) or 0) for p in items if p.get(\"pnl\") is not None)\n",
    "        print(f\"{'TOTAL':<20} {'':<8} {'':<12} {total_cost:>15,.0f} {total_pnl:>15,.0f}\")\n",
    "        \n",
    "        # Show margin info\n",
    "        if portfolio.get(\"derivativeMargin\"):\n",
    "            print(f\"\\n📊 Margin: Derivative={portfolio.get('derivativeMargin')*100:.0f}%, Equity={portfolio.get('equityMargin', 1)*100:.0f}%\")\n",
    "    else:\n",
    "        print(\"No open positions\")\n",
    "else:\n",
    "    print(\"No portfolio data available\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7f53da04",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## 🎉 Next Steps\n",
    "\n",
    "1. **More examples**: `examples/01_*.py` → `examples/11_*.py`\n",
    "2. **Market Data**: See `examples/05-07` for Redis/Kafka streaming\n",
    "3. **Advanced Orders**: See `examples/04` for cross-matching patterns\n",
    "4. **Documentation**: See `docs/` folder\n",
    "\n",
    "---\n",
    "\n",
    "*Happy Trading!* 🚀📈"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "d8dd3da3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "🔌 Closing session...\n",
      "✅ Done!\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# CELL 12: Cleanup (Optional)\n",
    "# ============================================================================\n",
    "# Run this cell when done to properly close the connection.\n",
    "#\n",
    "# Note: QuickFIX (underlying FIX engine) cleanup can sometimes cause\n",
    "# segmentation faults on Python exit. This is a known issue.\n",
    "# In production, you may need to use os._exit(0) for clean exit.\n",
    "# ============================================================================\n",
    "\n",
    "print(\"🔌 Closing session...\")\n",
    "# client.disconnect()  # Uncomment to explicitly disconnect\n",
    "print(\"✅ Done!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1fae16fe",
   "metadata": {},
   "source": [
    "# PaperBroker Client - Complete Tutorial\n",
    "\n",
    "**Comprehensive guide** covering all trading scenarios with a single persistent connection.\n",
    "\n",
    "---\n",
    "\n",
    "## 📚 Table of Contents\n",
    "\n",
    "1. [Setup & Configuration](#1-setup--configuration) - Verify existing connection\n",
    "2. [Example 1: Simple Login & Account Info](#2-example-1-simple-login--account-info)\n",
    "3. [Example 2: Order Cancellation](#3-example-2-order-cancellation)\n",
    "4. [Example 3: Cross-Matching Derivative](#4-example-3-cross-matching-derivative)\n",
    "5. [Example 4: Advanced Cross-Matching](#5-example-4-advanced-cross-matching)\n",
    "6. [Summary & Best Practices](#6-summary--best-practices)\n",
    "\n",
    "---\n",
    "\n",
    "## 🎯 Learning Path\n",
    "\n",
    "- **Beginner**: Examples 1-2 (Login, Cancellation)\n",
    "- **Intermediate**: Example 3 (Simple Cross-Matching)\n",
    "- **Advanced**: Example 4 (Multiple Orders, FIFO)\n",
    "\n",
    "---\n",
    "\n",
    "## ⚠️ Important: Single Connection Strategy\n",
    "\n",
    "This part **reuses the FIX connection** established in Part 1 (Cell 3).\n",
    "\n",
    "**Do NOT** create a new client or call `connect()` again - this will cause:\n",
    "- Sequence number mismatches\n",
    "- Session conflicts\n",
    "- Logon failures\n",
    "\n",
    "**Instead:** Just verify the connection is active and continue using the existing `client` variable."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "abdd64f7",
   "metadata": {},
   "source": [
    "## 1. Setup & Configuration\n",
    "\n",
    "### Prerequisites\n",
    "\n",
    "1. Python 3.10+\n",
    "2. PaperBroker account with FIX access\n",
    "3. **Cell 1-3 already executed** (connection established)\n",
    "\n",
    "### ⚠️ Connection Strategy\n",
    "\n",
    "**This part reuses the FIX connection from Part 1!**\n",
    "\n",
    "```\n",
    "Part 1 (Cell 1-3):     Part 2 (This section):\n",
    "┌─────────────┐        ┌─────────────────────┐\n",
    "│ Create      │        │ Verify connection   │\n",
    "│ client &    │───────▶│ is still active     │\n",
    "│ connect     │        │ (NO new connection) │\n",
    "└─────────────┘        └─────────────────────┘\n",
    "```\n",
    "\n",
    "**Why?** Logging in/out repeatedly can cause FIX session bugs due to sequence number mismatches."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "b47cc38f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ Client already connected from Part 1!\n",
      "✅ Ready to continue with Part 2 examples!\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# PART 2: Complete Tutorial - Verify Connection\n",
    "# ============================================================================\n",
    "# This part reuses the client from Part 1 (Cell 3).\n",
    "# NO new connection is created - we just verify the existing one.\n",
    "#\n",
    "# ⚠️ IMPORTANT: You must run Cell 1-3 first to establish the connection!\n",
    "# ============================================================================\n",
    "\n",
    "# Verify that client exists and is connected\n",
    "try:\n",
    "    # Check if client variable exists from Part 1\n",
    "    if 'client' not in dir() or client is None:\n",
    "        raise NameError(\"Client not found\")\n",
    "    \n",
    "    # Verify connection is still active\n",
    "    if not client.is_logged_on():\n",
    "        print(\"⚠️ Client exists but not logged on. Reconnecting...\")\n",
    "        client.connect()\n",
    "        if not client.wait_until_logged_on(timeout=10):\n",
    "            error = client.last_logon_error()\n",
    "            raise Exception(f\"Reconnection failed: {error}\")\n",
    "        print(\"✅ Reconnected successfully!\")\n",
    "    else:\n",
    "        print(\"✅ Client already connected from Part 1!\")\n",
    "        \n",
    "except NameError:\n",
    "    print(\"❌ ERROR: Client not found!\")\n",
    "    print(\"   Please run Cell 1-3 first to create and connect the client.\")\n",
    "    print(\"   This part reuses the existing connection.\")\n",
    "    raise Exception(\"Run Cell 1-3 first to establish FIX connection\")\n",
    "\n",
    "print(\"✅ Ready to continue with Part 2 examples!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "89518079",
   "metadata": {},
   "source": [
    "### Verify Connection (Reuse from Part 1)\n",
    "\n",
    "**⚠️ Important:** This section reuses the client created in **Cell 3**. No new connection is created!\n",
    "\n",
    "If you haven't run Cell 1-3 yet, go back and run them first."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "7a0ea288",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "📝 Configuration for Part 2:\n",
      "==================================================\n",
      "  socket_host: 100.97.83.110\n",
      "  socket_port: 5001\n",
      "  sender_comp_id: LOCAL_FIX\n",
      "  target_comp_id: SERVER\n",
      "  username: test2226\n",
      "  password: ***\n",
      "  rest_base_url: http://100.97.83.110:9090\n",
      "  account_d1: TEST2226A\n",
      "  account_d2: TEST2226B\n",
      "  vn30f: HNXDS:VN30F2602\n",
      "  mwg: HSX:MWG\n",
      "  symbol_derivative: HNXDS:VN30F2602\n",
      "\n",
      "==================================================\n",
      "🔌 CLIENT STATUS\n",
      "==================================================\n",
      "✅ FIX session is ACTIVE\n",
      "   Sender: LOCAL_FIX\n",
      "   Target: SERVER\n",
      "\n",
      "✅ Ready for cross-matching examples!\n",
      "==================================================\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# PART 2: Extended Configuration for Cross-Matching\n",
    "# ============================================================================\n",
    "# This cell extends the CONFIG from Part 1 with additional symbols.\n",
    "# Uses the SAME client - no new connection created!\n",
    "#\n",
    "# Key difference from Part 1 CONFIG:\n",
    "#   - Adds symbol_derivative for cross-matching examples\n",
    "#   - Reuses existing connection parameters\n",
    "# ============================================================================\n",
    "\n",
    "# Extend CONFIG with derivative symbol for cross-matching examples\n",
    "# (CONFIG was defined in Cell 2, client was created in Cell 3)\n",
    "if \"symbol_derivative\" not in CONFIG:\n",
    "    CONFIG[\"symbol_derivative\"] = os.getenv(\"VN30F1M\", \"HNXDS:VN30F2511\")\n",
    "\n",
    "# Display current configuration\n",
    "print(\"📝 Configuration for Part 2:\")\n",
    "print(\"=\" * 50)\n",
    "for key, value in CONFIG.items():\n",
    "    if key == \"password\":\n",
    "        print(f\"  {key}: ***\")\n",
    "    else:\n",
    "        print(f\"  {key}: {value}\")\n",
    "\n",
    "print(\"\\n\" + \"=\" * 50)\n",
    "print(\"🔌 CLIENT STATUS\")\n",
    "print(\"=\" * 50)\n",
    "\n",
    "# Verify client is still connected (from Cell 3)\n",
    "if client.is_logged_on():\n",
    "    print(\"✅ FIX session is ACTIVE\")\n",
    "    print(f\"   Sender: {CONFIG.get('sender_comp_id', 'N/A')}\")\n",
    "    print(f\"   Target: {CONFIG.get('target_comp_id', 'N/A')}\")\n",
    "else:\n",
    "    print(\"⚠️ FIX session is NOT active\")\n",
    "    print(\"   Run Cell 3 to connect, or check if connection was lost.\")\n",
    "\n",
    "print(\"\\n✅ Ready for cross-matching examples!\")\n",
    "print(\"=\" * 50)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "be421469",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## 2. Example 1: Simple Login & Account Info\n",
    "\n",
    "**Goal:** Query account information using the connected client.\n",
    "\n",
    "**What you'll learn:**\n",
    "- Balance queries via REST API\n",
    "- Using persistent connections\n",
    "- Working with sub-accounts"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "8d97f36a",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[07:12:22] INFO: 📊 Querying account information...\n",
      "[07:12:22] INFO: \n",
      "💰 Account D1:\n",
      "[07:12:22] INFO:    Available cash: 8,598,675,140 VND\n",
      "[07:12:22] INFO:    Total balance: 9,243,800,140 VND\n",
      "[07:12:22] INFO: \n",
      "💰 Account D2:\n",
      "[07:12:22] INFO:    Available cash: 10,105,674,860 VND\n",
      "[07:12:22] INFO:    Total balance: 10,750,799,860 VND\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================================================================\n",
      "EXAMPLE 1: SIMPLE LOGIN & ACCOUNT INFO\n",
      "================================================================================\n",
      "\n",
      "✅ Example 1 completed!\n",
      "================================================================================\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# EXAMPLE 1: Simple Login & Account Info\n",
    "# ============================================================================\n",
    "# Demonstrates querying account information via REST API.\n",
    "# The FIX connection is already established - just query balances.\n",
    "# ============================================================================\n",
    "\n",
    "print(\"=\" * 80)\n",
    "print(\"EXAMPLE 1: SIMPLE LOGIN & ACCOUNT INFO\")\n",
    "print(\"=\" * 80)\n",
    "\n",
    "logger.info(\"📊 Querying account information...\")\n",
    "\n",
    "# Query D1 sub-account\n",
    "with client.use_sub_account(CONFIG[\"account_d1\"]):\n",
    "    cash_d1 = client.get_cash_balance()      # Available cash\n",
    "    total_d1 = client.get_account_balance()  # Total balance\n",
    "\n",
    "# Query D2 sub-account\n",
    "with client.use_sub_account(CONFIG[\"account_d2\"]):\n",
    "    cash_d2 = client.get_cash_balance()\n",
    "    total_d2 = client.get_account_balance()\n",
    "\n",
    "# Display results\n",
    "logger.info(\"\\n💰 Account D1:\")\n",
    "logger.info(f\"   Available cash: {cash_d1.get('remainCash', 0):,.0f} VND\")\n",
    "logger.info(f\"   Total balance: {total_d1.get('totalBalance', 0):,.0f} VND\")\n",
    "\n",
    "logger.info(\"\\n💰 Account D2:\")\n",
    "logger.info(f\"   Available cash: {cash_d2.get('remainCash', 0):,.0f} VND\")\n",
    "logger.info(f\"   Total balance: {total_d2.get('totalBalance', 0):,.0f} VND\")\n",
    "\n",
    "print(\"\\n✅ Example 1 completed!\")\n",
    "print(\"=\" * 80)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "be32104b",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## 3. Example 2: Order Cancellation\n",
    "\n",
    "**Goal:** Place a limit order and cancel it before execution.\n",
    "\n",
    "**What you'll learn:**\n",
    "- Place limit orders\n",
    "- Track order status with events\n",
    "- Cancel pending orders"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d4a1fe25",
   "metadata": {},
   "source": [
    "### Define Order Tracker"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "b9a82638",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ OrderTracker class defined\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# EXAMPLE 2: Order Tracker Class\n",
    "# ============================================================================\n",
    "# This class tracks a single order using ThreadEvent for synchronization.\n",
    "# ThreadEvent.set() signals the event occurred.\n",
    "# ThreadEvent.wait(timeout) blocks until the event is set.\n",
    "# ============================================================================\n",
    "\n",
    "class OrderTracker:\n",
    "    \"\"\"\n",
    "    Track order lifecycle using events.\n",
    "    \n",
    "    Usage:\n",
    "        tracker = OrderTracker()\n",
    "        client.on(\"fix:order:accepted\", tracker.on_order_accepted)\n",
    "        tracker.order_id = client.place_order(...)\n",
    "        tracker.order_accepted.wait(timeout=10)  # Blocks until accepted\n",
    "    \"\"\"\n",
    "    def __init__(self):\n",
    "        self.order_accepted = ThreadEvent()  # Set when order is accepted\n",
    "        self.order_canceled = ThreadEvent()  # Set when order is canceled\n",
    "        self.order_id = None                 # Order ID to track\n",
    "        self.status = None                   # Current status\n",
    "        \n",
    "    def on_order_accepted(self, cl_ord_id, status, **kwargs):\n",
    "        \"\"\"Event handler: Order accepted by server.\"\"\"\n",
    "        if cl_ord_id == self.order_id:\n",
    "            self.status = status\n",
    "            logger.info(f\"✅ Order accepted: {cl_ord_id[:8]}... (Status: {status})\")\n",
    "            self.order_accepted.set()  # Unblock wait()\n",
    "    \n",
    "    def on_order_canceled(self, orig_cl_ord_id, status, **kwargs):\n",
    "        \"\"\"Event handler: Order canceled.\"\"\"\n",
    "        if orig_cl_ord_id == self.order_id:\n",
    "            self.status = status\n",
    "            logger.info(f\"✅ Order canceled: {orig_cl_ord_id[:8]}... (Status: {status})\")\n",
    "            self.order_canceled.set()\n",
    "    \n",
    "    def on_order_rejected(self, cl_ord_id, reason, **kwargs):\n",
    "        \"\"\"Event handler: Order rejected.\"\"\"\n",
    "        if cl_ord_id == self.order_id:\n",
    "            logger.error(f\"❌ Order rejected: {cl_ord_id[:8]}... - {reason}\")\n",
    "\n",
    "print(\"✅ OrderTracker class defined\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2dcc0566",
   "metadata": {},
   "source": [
    "### Execute Cancellation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "de410207",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[07:12:22] INFO: Symbol: HNXDS:VN30F2602\n",
      "[07:12:22] INFO: Account: TEST2226A\n",
      "[07:12:22] INFO: Action: Place order → Wait 2s → Cancel\n",
      "[07:12:22] INFO: \n",
      "📤 Placing SELL order...\n",
      "[07:12:22] INFO: Order ID: ade7db5f...\n",
      "[07:12:22] INFO: ✅ Order accepted: ade7db5f... (Status: NEW)\n",
      "[07:12:22] INFO: \n",
      "⏳ Waiting 2 seconds...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================================================================\n",
      "EXAMPLE 2: ORDER CANCELLATION\n",
      "================================================================================\n",
      "✅ Order ACCEPTED: ade7db5f... | Status: NEW\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[07:12:24] INFO: \n",
      "🗑️ Canceling order...\n",
      "[07:12:24] INFO: ✅ Order canceled: ade7db5f... (Status: CANCELED)\n",
      "[07:12:24] INFO: \n",
      "📊 Summary:\n",
      "[07:12:24] INFO:    Order ID: ade7db5f...\n",
      "[07:12:24] INFO:    Final Status: CANCELED\n",
      "[07:12:24] INFO:    Symbol: HNXDS:VN30F2602\n",
      "[07:12:24] INFO:    Quantity: 1\n",
      "[07:12:24] INFO:    Price: 1,985 VND\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "❌ Order CANCELED: ade7db5f...\n",
      "\n",
      "✅ Example 2 completed!\n",
      "================================================================================\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# EXAMPLE 2: Execute Order Cancellation\n",
    "# ============================================================================\n",
    "# Flow: Place order → Wait for acceptance → Wait N seconds → Cancel\n",
    "# Uses OrderTracker to synchronize with FIX responses.\n",
    "# ============================================================================\n",
    "\n",
    "print(\"=\" * 80)\n",
    "print(\"EXAMPLE 2: ORDER CANCELLATION\")\n",
    "print(\"=\" * 80)\n",
    "\n",
    "# Order parameters\n",
    "symbol = CONFIG[\"symbol_derivative\"]\n",
    "price = 1985.0          # Far price - won't match immediately\n",
    "qty = 1                 # 1 contract\n",
    "delay_seconds = 2       # Wait before canceling\n",
    "\n",
    "logger.info(f\"Symbol: {symbol}\")\n",
    "logger.info(f\"Account: {CONFIG['account_d1']}\")\n",
    "logger.info(f\"Action: Place order → Wait {delay_seconds}s → Cancel\")\n",
    "\n",
    "# Create tracker and subscribe to events\n",
    "tracker = OrderTracker()\n",
    "client.on(\"fix:order:accepted\", tracker.on_order_accepted)\n",
    "client.on(\"fix:order:canceled\", tracker.on_order_canceled)\n",
    "client.on(\"fix:order:rejected\", tracker.on_order_rejected)\n",
    "\n",
    "# Step 1: Place order\n",
    "logger.info(\"\\n📤 Placing SELL order...\")\n",
    "with client.use_sub_account(CONFIG[\"account_d1\"]):\n",
    "    order_id = client.place_order(\n",
    "        full_symbol=symbol,\n",
    "        side=\"SELL\",\n",
    "        qty=qty,\n",
    "        price=price,\n",
    "        ord_type=\"LIMIT\"\n",
    "    )\n",
    "\n",
    "# Store order ID in tracker for event matching\n",
    "tracker.order_id = order_id\n",
    "logger.info(f\"Order ID: {order_id[:16]}...\")\n",
    "\n",
    "# Step 2: Wait for acceptance confirmation\n",
    "if not tracker.order_accepted.wait(timeout=10):\n",
    "    logger.error(\"❌ Timeout waiting for order acceptance\")\n",
    "else:\n",
    "    # Step 3: Wait before canceling\n",
    "    logger.info(f\"\\n⏳ Waiting {delay_seconds} seconds...\")\n",
    "    time.sleep(delay_seconds)\n",
    "    \n",
    "    # Step 4: Send cancel request\n",
    "    logger.info(f\"\\n🗑️ Canceling order...\")\n",
    "    client.cancel_order(order_id)\n",
    "    \n",
    "    # Step 5: Wait for cancel confirmation\n",
    "    if not tracker.order_canceled.wait(timeout=10):\n",
    "        logger.warning(\"⚠️ Timeout waiting for cancellation\")\n",
    "    \n",
    "    # Summary\n",
    "    logger.info(\"\\n📊 Summary:\")\n",
    "    logger.info(f\"   Order ID: {order_id[:16]}...\")\n",
    "    logger.info(f\"   Final Status: {tracker.status or 'UNKNOWN'}\")\n",
    "    logger.info(f\"   Symbol: {symbol}\")\n",
    "    logger.info(f\"   Quantity: {qty}\")\n",
    "    logger.info(f\"   Price: {price:,.0f} VND\")\n",
    "\n",
    "print(\"\\n✅ Example 2 completed!\")\n",
    "print(\"=\" * 80)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "47ee6c8a",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## 4. Example 3: Cross-Matching Derivative\n",
    "\n",
    "**Goal:** Demonstrate instant order matching between two sub-accounts.\n",
    "\n",
    "**What you'll learn:**\n",
    "- Multi-account trading (D1 ↔ D2)\n",
    "- Cross-matching mechanism\n",
    "- Event-driven fill tracking"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "168793ac",
   "metadata": {},
   "source": [
    "### Define Cross-Match Tracker"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "bac86139",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ CrossMatchTracker class defined\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# EXAMPLE 3: Cross-Match Tracker Class\n",
    "# ============================================================================\n",
    "# Tracks fills from both D1 and D2 orders.\n",
    "# fill_count increments for each fill event.\n",
    "# When both orders are filled (count >= 2), signals completion.\n",
    "# ============================================================================\n",
    "\n",
    "class CrossMatchTracker:\n",
    "    \"\"\"\n",
    "    Track cross-matching fill events.\n",
    "    \n",
    "    Cross-matching: D1 SELL + D2 BUY → 2 fill events\n",
    "    \"\"\"\n",
    "    \n",
    "    def __init__(self):\n",
    "        self.orders = {}                  # {order_id: order_info}\n",
    "        self.filled_event = ThreadEvent() # Set when both orders filled\n",
    "        self.fill_count = 0               # Number of fills received\n",
    "    \n",
    "    def on_order_filled(self, cl_ord_id, status, last_px, last_qty, **kw):\n",
    "        \"\"\"Event handler: Order filled.\"\"\"\n",
    "        logger.info(f\"🔔 ORDER FILLED: {cl_ord_id[:8]}...\")\n",
    "        \n",
    "        # Track order info\n",
    "        if cl_ord_id not in self.orders:\n",
    "            self.orders[cl_ord_id] = {\"status\": status, \"qty_filled\": 0, \"avg_price\": 0}\n",
    "        \n",
    "        order = self.orders[cl_ord_id]\n",
    "        order[\"status\"] = status\n",
    "        order[\"qty_filled\"] = last_qty\n",
    "        order[\"avg_price\"] = last_px\n",
    "        \n",
    "        self.fill_count += 1\n",
    "        logger.info(f\"✅ Filled: {last_qty} @ {last_px:,.0f} VND\")\n",
    "        \n",
    "        # Cross-match complete when both sides filled\n",
    "        if self.fill_count >= 2:\n",
    "            logger.info(\"🎯 Both orders filled - cross-match complete!\")\n",
    "            self.filled_event.set()\n",
    "    \n",
    "    def on_order_accepted(self, cl_ord_id, status, **kw):\n",
    "        \"\"\"Event handler: Order accepted.\"\"\"\n",
    "        logger.info(f\"✓ Order ACCEPTED: {cl_ord_id[:8]}...\")\n",
    "    \n",
    "    def on_order_rejected(self, cl_ord_id, reason, **kw):\n",
    "        \"\"\"Event handler: Order rejected.\"\"\"\n",
    "        logger.error(f\"❌ Order REJECTED: {cl_ord_id[:8]}... | Reason: {reason}\")\n",
    "\n",
    "print(\"✅ CrossMatchTracker class defined\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3b13ba8b",
   "metadata": {},
   "source": [
    "### Execute Cross-Matching"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "f081f19e",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[07:12:24] INFO: Symbol: HNXDS:VN30F2602\n",
      "[07:12:24] INFO: Sub-Accounts: TEST2226A (seller) and TEST2226B (buyer)\n",
      "[07:12:24] INFO: Mechanism: D2 can match unconditionally with D1\n",
      "[07:12:24] INFO: \n",
      "💰 Initial Balances:\n",
      "[07:12:24] INFO:    D1: 8,598,675,140 VND\n",
      "[07:12:24] INFO:    D2: 10,105,674,860 VND\n",
      "[07:12:24] INFO: \n",
      "📤 Placing Cross-Match Orders:\n",
      "[07:12:24] INFO:    Symbol: HNXDS:VN30F2602\n",
      "[07:12:24] INFO:    Price: 1,985\n",
      "[07:12:24] INFO:    Quantity: 1 contract(s)\n",
      "[07:12:24] INFO: \n",
      "   Strategy: D1 SELLS → D2 BUYS → INSTANT MATCH\n",
      "[07:12:24] INFO: \n",
      "   [1/2] D1 placing SELL order...\n",
      "[07:12:24] INFO:          Order ID: 71909016...\n",
      "[07:12:24] INFO: ✓ Order ACCEPTED: 71909016...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================================================================\n",
      "EXAMPLE 3: CROSS-MATCHING DERIVATIVE (D1 vs D2)\n",
      "================================================================================\n",
      "✅ Order ACCEPTED: 71909016... | Status: NEW\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[07:12:25] INFO: \n",
      "   [2/2] D2 placing BUY order...\n",
      "[07:12:25] INFO:          Order ID: 18b46e61...\n",
      "[07:12:25] INFO: \n",
      "⏳ Waiting for cross-match...\n",
      "[07:12:25] INFO: ✓ Order ACCEPTED: 18b46e61...\n",
      "[07:12:25] INFO: 🔔 ORDER FILLED: 18b46e61...\n",
      "[07:12:25] INFO: ✅ Filled: 1.0 @ 1,985 VND\n",
      "[07:12:25] INFO: 🔔 ORDER FILLED: 71909016...\n",
      "[07:12:25] INFO: ✅ Filled: 1.0 @ 1,985 VND\n",
      "[07:12:25] INFO: 🎯 Both orders filled - cross-match complete!\n",
      "[07:12:25] INFO: \n",
      "🎉 CROSS-MATCH COMPLETED!\n",
      "[07:12:25] INFO: \n",
      "📊 Trade Summary:\n",
      "[07:12:25] INFO:    Order 18b46e61... | Status: FILLED   | Filled: 1.0 @ 1,985 VND\n",
      "[07:12:25] INFO:    Order 71909016... | Status: FILLED   | Filled: 1.0 @ 1,985 VND\n",
      "[07:12:25] INFO: \n",
      "💰 Final Balances:\n",
      "[07:12:25] INFO:    D1: 8,549,030,140 VND\n",
      "[07:12:25] INFO:    D2: 10,056,029,860 VND\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ Order ACCEPTED: 18b46e61... | Status: NEW\n",
      "🎯 Order FILLED: 18b46e61... | 1.0 @ 1,985\n",
      "🎯 Order FILLED: 71909016... | 1.0 @ 1,985\n",
      "\n",
      "✅ Example 3 completed!\n",
      "================================================================================\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# EXAMPLE 3: Execute Cross-Matching\n",
    "# ============================================================================\n",
    "# Cross-match flow:\n",
    "#   1. D1 places SELL → order sits in book\n",
    "#   2. D2 places BUY at same price → instant match with D1\n",
    "#   3. Both accounts receive fill events\n",
    "#\n",
    "# Key: D1 must be in book BEFORE D2 can match\n",
    "# ============================================================================\n",
    "\n",
    "print(\"=\" * 80)\n",
    "print(\"EXAMPLE 3: CROSS-MATCHING DERIVATIVE (D1 vs D2)\")\n",
    "print(\"=\" * 80)\n",
    "\n",
    "# Trade parameters\n",
    "symbol = CONFIG[\"symbol_derivative\"]\n",
    "price = 1985.0  # Match price\n",
    "qty = 1         # 1 contract\n",
    "\n",
    "logger.info(f\"Symbol: {symbol}\")\n",
    "logger.info(f\"Sub-Accounts: {CONFIG['account_d1']} (seller) and {CONFIG['account_d2']} (buyer)\")\n",
    "logger.info(\"Mechanism: D2 can match unconditionally with D1\")\n",
    "\n",
    "# Create tracker and subscribe to fill events\n",
    "tracker_ex3 = CrossMatchTracker()\n",
    "client.on(\"fix:order:filled\", tracker_ex3.on_order_filled)\n",
    "client.on(\"fix:order:accepted\", tracker_ex3.on_order_accepted)\n",
    "client.on(\"fix:order:rejected\", tracker_ex3.on_order_rejected)\n",
    "\n",
    "# Query initial balances\n",
    "with client.use_sub_account(CONFIG[\"account_d1\"]):\n",
    "    balance_d1 = client.get_cash_balance()\n",
    "with client.use_sub_account(CONFIG[\"account_d2\"]):\n",
    "    balance_d2 = client.get_cash_balance()\n",
    "\n",
    "logger.info(\"\\n💰 Initial Balances:\")\n",
    "logger.info(f\"   D1: {balance_d1.get('remainCash', 0):,.0f} VND\")\n",
    "logger.info(f\"   D2: {balance_d2.get('remainCash', 0):,.0f} VND\")\n",
    "\n",
    "# --- Cross-Match Execution ---\n",
    "logger.info(\"\\n📤 Placing Cross-Match Orders:\")\n",
    "logger.info(f\"   Symbol: {symbol}\")\n",
    "logger.info(f\"   Price: {price:,.0f}\")\n",
    "logger.info(f\"   Quantity: {qty} contract(s)\")\n",
    "logger.info(\"\\n   Strategy: D1 SELLS → D2 BUYS → INSTANT MATCH\")\n",
    "\n",
    "# Step 1: D1 places SELL order (enters order book)\n",
    "logger.info(\"\\n   [1/2] D1 placing SELL order...\")\n",
    "with client.use_sub_account(CONFIG[\"account_d1\"]):\n",
    "    order_d1 = client.place_order(\n",
    "        full_symbol=symbol,\n",
    "        side=\"SELL\",\n",
    "        qty=qty,\n",
    "        price=price,\n",
    "        ord_type=\"LIMIT\"\n",
    "    )\n",
    "logger.info(f\"         Order ID: {order_d1[:8]}...\")\n",
    "\n",
    "# Wait to ensure D1 order is in the book\n",
    "time.sleep(1.0)\n",
    "\n",
    "# Step 2: D2 places BUY order → matches immediately with D1\n",
    "logger.info(\"\\n   [2/2] D2 placing BUY order...\")\n",
    "with client.use_sub_account(CONFIG[\"account_d2\"]):\n",
    "    order_d2 = client.place_order(\n",
    "        full_symbol=symbol,\n",
    "        side=\"BUY\",\n",
    "        qty=qty,\n",
    "        price=price,\n",
    "        ord_type=\"LIMIT\"\n",
    "    )\n",
    "logger.info(f\"         Order ID: {order_d2[:8]}...\")\n",
    "\n",
    "# Wait for both fill events\n",
    "logger.info(\"\\n⏳ Waiting for cross-match...\")\n",
    "\n",
    "if tracker_ex3.filled_event.wait(timeout=10):\n",
    "    logger.info(\"\\n🎉 CROSS-MATCH COMPLETED!\")\n",
    "    \n",
    "    # Display trade results\n",
    "    logger.info(\"\\n📊 Trade Summary:\")\n",
    "    for cl_ord_id, order in tracker_ex3.orders.items():\n",
    "        logger.info(\n",
    "            f\"   Order {cl_ord_id[:8]}... | \"\n",
    "            f\"Status: {order['status']:8s} | \"\n",
    "            f\"Filled: {order['qty_filled']} @ {order['avg_price']:,.0f} VND\"\n",
    "        )\n",
    "    \n",
    "    # Query updated balances\n",
    "    with client.use_sub_account(CONFIG[\"account_d1\"]):\n",
    "        balance_d1_after = client.get_cash_balance()\n",
    "    with client.use_sub_account(CONFIG[\"account_d2\"]):\n",
    "        balance_d2_after = client.get_cash_balance()\n",
    "    \n",
    "    logger.info(\"\\n💰 Final Balances:\")\n",
    "    logger.info(f\"   D1: {balance_d1_after.get('remainCash', 0):,.0f} VND\")\n",
    "    logger.info(f\"   D2: {balance_d2_after.get('remainCash', 0):,.0f} VND\")\n",
    "else:\n",
    "    logger.warning(\"\\n⚠️ Timeout waiting for fills (10 seconds)\")\n",
    "\n",
    "print(\"\\n✅ Example 3 completed!\")\n",
    "print(\"=\" * 80)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cf327ddb",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## 5. Example 4: Advanced Cross-Matching\n",
    "\n",
    "**Goal:** Demonstrate full fills vs multiple orders patterns.\n",
    "\n",
    "**What you'll learn:**\n",
    "- Full fill (1 order → 1 fill event)\n",
    "- Multiple orders (3 orders → 3 fill events)\n",
    "- FIFO matching at same price\n",
    "- Detailed fill tracking"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a4d7eca6",
   "metadata": {},
   "source": [
    "### Define Advanced Order Tracker"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "df1d6571",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ D1OrderTracker class defined\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# EXAMPLE 4: D1 Order Tracker Class\n",
    "# ============================================================================\n",
    "# Advanced tracker that monitors multiple D1 orders and their fill events.\n",
    "# \n",
    "# Features:\n",
    "#   - register_order(): Add an order ID to track\n",
    "#   - on_order_filled(): Accumulate fills, detect full vs partial fills\n",
    "#   - print_summary(): Display detailed execution report\n",
    "#\n",
    "# This demonstrates the difference between:\n",
    "#   - 1 order of 5 contracts → 1 fill event\n",
    "#   - 3 orders of varying sizes → 3 fill events (FIFO)\n",
    "# ============================================================================\n",
    "\n",
    "class D1OrderTracker:\n",
    "    \"\"\"\n",
    "    Advanced tracker for multiple D1 orders.\n",
    "    Tracks fill events with cumulative quantity and fill history.\n",
    "    \"\"\"\n",
    "    \n",
    "    def __init__(self):\n",
    "        self.orders = {}              # {cl_ord_id: order_info}\n",
    "        self.filled_event = ThreadEvent()\n",
    "        self.target_orders = []       # List of order IDs we're tracking\n",
    "    \n",
    "    def register_order(self, cl_ord_id, qty):\n",
    "        \"\"\"Add an order to track. Call after place_order().\"\"\"\n",
    "        self.orders[cl_ord_id] = {\n",
    "            \"status\": \"NEW\",\n",
    "            \"qty_ordered\": qty,       # Original quantity\n",
    "            \"cum_qty\": 0,             # Cumulative filled quantity\n",
    "            \"avg_price\": 0,\n",
    "            \"fills\": []               # List of fill events\n",
    "        }\n",
    "        self.target_orders.append(cl_ord_id)\n",
    "        logger.info(f\"   📋 Registered: {cl_ord_id[:8]}... ({qty} contracts)\")\n",
    "    \n",
    "    def on_order_filled(self, cl_ord_id, status, last_px, last_qty, cum_qty=None, **kw):\n",
    "        \"\"\"\n",
    "        Event handler: Order filled.\n",
    "        \n",
    "        Args:\n",
    "            last_qty: Quantity filled in this event\n",
    "            cum_qty: Total cumulative filled quantity (if provided)\n",
    "        \"\"\"\n",
    "        # Only track orders we registered\n",
    "        if cl_ord_id not in self.target_orders:\n",
    "            return\n",
    "        \n",
    "        order = self.orders[cl_ord_id]\n",
    "        order[\"status\"] = status\n",
    "        order[\"avg_price\"] = last_px\n",
    "        \n",
    "        # Update cumulative quantity\n",
    "        if cum_qty is not None:\n",
    "            order[\"cum_qty\"] = cum_qty\n",
    "        else:\n",
    "            order[\"cum_qty\"] += last_qty\n",
    "        \n",
    "        # Record fill event\n",
    "        fill_num = len(order[\"fills\"]) + 1\n",
    "        order[\"fills\"].append({\"fill_num\": fill_num, \"qty\": last_qty, \"price\": last_px})\n",
    "        \n",
    "        # Determine fill type\n",
    "        is_fully_filled = order[\"cum_qty\"] >= order[\"qty_ordered\"]\n",
    "        fill_type = \"✅ FULL FILL\" if is_fully_filled else \"🔸 PARTIAL FILL\"\n",
    "        \n",
    "        logger.info(\n",
    "            f\"      {fill_type} #{fill_num}: {last_qty} @ {last_px:,.0f} VND | \"\n",
    "            f\"Progress: {order['cum_qty']}/{order['qty_ordered']}\"\n",
    "        )\n",
    "        \n",
    "        # Signal if all orders complete\n",
    "        if self._all_orders_complete():\n",
    "            self.filled_event.set()\n",
    "    \n",
    "    def _all_orders_complete(self):\n",
    "        \"\"\"Check if all registered orders are fully filled.\"\"\"\n",
    "        for cl_ord_id in self.target_orders:\n",
    "            order = self.orders[cl_ord_id]\n",
    "            if order[\"cum_qty\"] < order[\"qty_ordered\"]:\n",
    "                return False\n",
    "        return True\n",
    "    \n",
    "    def on_order_accepted(self, cl_ord_id, status, **kw):\n",
    "        if cl_ord_id in self.target_orders:\n",
    "            logger.info(f\"   ✓ Order accepted: {cl_ord_id[:8]}...\")\n",
    "    \n",
    "    def on_order_rejected(self, cl_ord_id, reason, **kw):\n",
    "        if cl_ord_id in self.target_orders:\n",
    "            logger.error(f\"   ✗ Order rejected: {cl_ord_id[:8]}... - {reason}\")\n",
    "    \n",
    "    def print_summary(self):\n",
    "        \"\"\"Print detailed execution summary.\"\"\"\n",
    "        logger.info(\"\\n📊 D1 ORDER EXECUTION SUMMARY\")\n",
    "        logger.info(\"=\" * 80)\n",
    "        \n",
    "        total_orders = len(self.target_orders)\n",
    "        total_fills = sum(len(self.orders[oid][\"fills\"]) for oid in self.target_orders)\n",
    "        \n",
    "        logger.info(f\"Total D1 Orders: {total_orders}\")\n",
    "        logger.info(f\"Total Fill Events: {total_fills}\\n\")\n",
    "        \n",
    "        for idx, cl_ord_id in enumerate(self.target_orders, 1):\n",
    "            order = self.orders[cl_ord_id]\n",
    "            logger.info(f\"Order #{idx}: {cl_ord_id[:8]}...\")\n",
    "            logger.info(f\"  Status: {order['status']}\")\n",
    "            logger.info(f\"  Ordered: {order['qty_ordered']} contracts\")\n",
    "            logger.info(f\"  Filled: {order['cum_qty']} contracts\")\n",
    "            logger.info(f\"  Fills: {len(order['fills'])}\")\n",
    "            logger.info(f\"  Avg Price: {order['avg_price']:,.0f} VND\")\n",
    "            \n",
    "            if order['fills']:\n",
    "                logger.info(f\"  Fill History:\")\n",
    "                for fill in order['fills']:\n",
    "                    logger.info(\n",
    "                        f\"    Fill #{fill['fill_num']}: \"\n",
    "                        f\"{fill['qty']} @ {fill['price']:,.0f} VND\"\n",
    "                    )\n",
    "            logger.info(\"\")\n",
    "        logger.info(\"=\" * 80)\n",
    "\n",
    "print(\"✅ D1OrderTracker class defined\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "89f3e020",
   "metadata": {},
   "source": [
    "### Scenario A: Full Fill"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "c1be09c3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ Scenario A function defined\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# EXAMPLE 4 - Scenario A: Full Fill\n",
    "# ============================================================================\n",
    "# Single order of 5 contracts → 1 fill event\n",
    "# D2 matches the entire order in one trade.\n",
    "# ============================================================================\n",
    "\n",
    "def run_scenario_a(tracker, symbol, price):\n",
    "    \"\"\"\n",
    "    Scenario A: Full Fill\n",
    "    \n",
    "    1 order of 5 contracts → 1 fill event\n",
    "    \"\"\"\n",
    "    logger.info(\"\\n\" + \"🔷\" * 40)\n",
    "    logger.info(\"SCENARIO A: FULL FILL\")\n",
    "    logger.info(\"🔷\" * 40)\n",
    "    \n",
    "    qty = 5  # 5 contracts in single order\n",
    "    \n",
    "    # D1 places SELL order\n",
    "    logger.info(f\"\\n1️⃣ D1 places SELL: {qty} @ {price:,.0f}\")\n",
    "    with client.use_sub_account(CONFIG[\"account_d1\"]):\n",
    "        order_d1 = client.place_order(\n",
    "            full_symbol=symbol, side=\"SELL\", qty=qty, price=price, ord_type=\"LIMIT\"\n",
    "        )\n",
    "    tracker.register_order(order_d1, qty)\n",
    "    \n",
    "    time.sleep(0.5)  # Ensure order is in book\n",
    "    \n",
    "    # D2 matches entire order\n",
    "    logger.info(f\"\\n2️⃣ D2 matches BUY: {qty} @ {price:,.0f}\")\n",
    "    with client.use_sub_account(CONFIG[\"account_d2\"]):\n",
    "        client.place_order(\n",
    "            full_symbol=symbol, side=\"BUY\", qty=qty, price=price, ord_type=\"LIMIT\"\n",
    "        )\n",
    "    \n",
    "    # Wait for fill event\n",
    "    logger.info(\"\\n⏳ Waiting for fill...\")\n",
    "    if tracker.filled_event.wait(timeout=5):\n",
    "        logger.info(\"\\n✅ Scenario A completed!\")\n",
    "        return True\n",
    "    else:\n",
    "        logger.warning(\"⚠️ Timeout!\")\n",
    "        return False\n",
    "\n",
    "print(\"✅ Scenario A function defined\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4683795f",
   "metadata": {},
   "source": [
    "### Scenario B: Multiple Orders"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "83a16441",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ Scenario B function defined\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# EXAMPLE 4 - Scenario B: Multiple Orders\n",
    "# ============================================================================\n",
    "# 3 separate orders → 3 fill events\n",
    "# Demonstrates FIFO matching: orders matched in order they were placed.\n",
    "# ============================================================================\n",
    "\n",
    "def run_scenario_b(tracker, symbol, price):\n",
    "    \"\"\"\n",
    "    Scenario B: Multiple Orders\n",
    "    \n",
    "    3 orders (2, 2, 1 contracts) → 3 fill events\n",
    "    Orders are matched in FIFO order at the same price.\n",
    "    \"\"\"\n",
    "    logger.info(\"\\n\" + \"🔶\" * 40)\n",
    "    logger.info(\"SCENARIO B: MULTIPLE ORDERS\")\n",
    "    logger.info(\"🔶\" * 40)\n",
    "    \n",
    "    # 3 separate orders with varying quantities\n",
    "    orders = [\n",
    "        {\"qty\": 2, \"price\": price},  # Order 1\n",
    "        {\"qty\": 2, \"price\": price},  # Order 2\n",
    "        {\"qty\": 1, \"price\": price},  # Order 3\n",
    "    ]\n",
    "    \n",
    "    # Reset the filled event for new tracking\n",
    "    tracker.filled_event.clear()\n",
    "    \n",
    "    # Step 1: D1 places multiple SELL orders\n",
    "    logger.info(f\"\\n1️⃣ D1 places {len(orders)} SELL orders:\")\n",
    "    d1_orders = []\n",
    "    for i, spec in enumerate(orders, 1):\n",
    "        qty = spec[\"qty\"]\n",
    "        px = spec[\"price\"]\n",
    "        logger.info(f\"   Order {i}: {qty} @ {px:,.0f}\")\n",
    "        with client.use_sub_account(CONFIG[\"account_d1\"]):\n",
    "            order_id = client.place_order(\n",
    "                full_symbol=symbol, side=\"SELL\", qty=qty, price=px, ord_type=\"LIMIT\"\n",
    "            )\n",
    "        tracker.register_order(order_id, qty)\n",
    "        d1_orders.append((order_id, qty, px))\n",
    "        time.sleep(0.5)  # Space out orders\n",
    "    \n",
    "    time.sleep(1.0)  # Ensure all orders are in book\n",
    "    \n",
    "    # Step 2: D2 matches each order in FIFO order\n",
    "    logger.info(\"\\n2️⃣ D2 matches each order:\")\n",
    "    for i, (order_id, qty, px) in enumerate(d1_orders, 1):\n",
    "        logger.info(f\"\\n   Match {i}/{len(d1_orders)}: BUY {qty} @ {px:,.0f}\")\n",
    "        with client.use_sub_account(CONFIG[\"account_d2\"]):\n",
    "            client.place_order(\n",
    "                full_symbol=symbol, side=\"BUY\", qty=qty, price=px, ord_type=\"LIMIT\"\n",
    "            )\n",
    "        time.sleep(0.8)  # Allow fill to process\n",
    "    \n",
    "    # Wait for all fills\n",
    "    logger.info(\"\\n⏳ Waiting for all fills...\")\n",
    "    if tracker.filled_event.wait(timeout=15):\n",
    "        logger.info(\"\\n✅ Scenario B completed!\")\n",
    "        return True\n",
    "    else:\n",
    "        logger.warning(\"⚠️ Timeout!\")\n",
    "        return False\n",
    "\n",
    "print(\"✅ Scenario B function defined\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "df1eff42",
   "metadata": {},
   "source": [
    "### Execute Advanced Scenarios"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "6b8e40d6",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[07:12:25] INFO: Symbol: HNXDS:VN30F2602\n",
      "[07:12:25] INFO: Price: 1,985\n",
      "[07:12:25] INFO: Focus: Full fills vs Multiple orders\n",
      "[07:12:25] INFO: \n",
      "💰 D1 Balance: 8,549,030,140 VND\n",
      "\n",
      "[07:12:25] INFO: \n",
      "🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷\n",
      "[07:12:25] INFO: SCENARIO A: FULL FILL\n",
      "[07:12:25] INFO: 🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷\n",
      "[07:12:25] INFO: \n",
      "1️⃣ D1 places SELL: 5 @ 1,985\n",
      "[07:12:26] INFO:    📋 Registered: cf41edf5... (5 contracts)\n",
      "[07:12:26] INFO: ✓ Order ACCEPTED: cf41edf5...\n",
      "[07:12:26] INFO:    ✓ Order accepted: cf41edf5...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================================================================\n",
      "EXAMPLE 4: ADVANCED CROSS-MATCHING\n",
      "================================================================================\n",
      "✅ Order ACCEPTED: cf41edf5... | Status: NEW\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[07:12:26] INFO: \n",
      "2️⃣ D2 matches BUY: 5 @ 1,985\n",
      "[07:12:26] INFO: \n",
      "⏳ Waiting for fill...\n",
      "[07:12:26] INFO: ✓ Order ACCEPTED: c03b001c...\n",
      "[07:12:26] INFO: 🔔 ORDER FILLED: c03b001c...\n",
      "[07:12:26] INFO: ✅ Filled: 5.0 @ 1,985 VND\n",
      "[07:12:26] INFO: 🎯 Both orders filled - cross-match complete!\n",
      "[07:12:26] INFO: 🔔 ORDER FILLED: cf41edf5...\n",
      "[07:12:26] INFO: ✅ Filled: 5.0 @ 1,985 VND\n",
      "[07:12:26] INFO: 🎯 Both orders filled - cross-match complete!\n",
      "[07:12:26] INFO:       ✅ FULL FILL #1: 5.0 @ 1,985 VND | Progress: 5.0/5\n",
      "[07:12:26] INFO: \n",
      "✅ Scenario A completed!\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ Order ACCEPTED: c03b001c... | Status: NEW\n",
      "🎯 Order FILLED: c03b001c... | 5.0 @ 1,985\n",
      "🎯 Order FILLED: cf41edf5... | 5.0 @ 1,985\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[07:12:28] INFO: \n",
      "🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶\n",
      "[07:12:28] INFO: SCENARIO B: MULTIPLE ORDERS\n",
      "[07:12:28] INFO: 🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶\n",
      "[07:12:28] INFO: \n",
      "1️⃣ D1 places 3 SELL orders:\n",
      "[07:12:28] INFO:    Order 1: 2 @ 1,985\n",
      "[07:12:28] INFO:    📋 Registered: ce45d8e6... (2 contracts)\n",
      "[07:12:28] INFO: ✓ Order ACCEPTED: ce45d8e6...\n",
      "[07:12:28] INFO:    ✓ Order accepted: ce45d8e6...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ Order ACCEPTED: ce45d8e6... | Status: NEW\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[07:12:29] INFO:    Order 2: 2 @ 1,985\n",
      "[07:12:29] INFO:    📋 Registered: 6dc36178... (2 contracts)\n",
      "[07:12:29] INFO: ✓ Order ACCEPTED: 6dc36178...\n",
      "[07:12:29] INFO:    ✓ Order accepted: 6dc36178...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ Order ACCEPTED: 6dc36178... | Status: NEW\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[07:12:29] INFO:    Order 3: 1 @ 1,985\n",
      "[07:12:29] INFO:    📋 Registered: 6e0874f2... (1 contracts)\n",
      "[07:12:29] INFO: ✓ Order ACCEPTED: 6e0874f2...\n",
      "[07:12:29] INFO:    ✓ Order accepted: 6e0874f2...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ Order ACCEPTED: 6e0874f2... | Status: NEW\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[07:12:31] INFO: \n",
      "2️⃣ D2 matches each order:\n",
      "[07:12:31] INFO: \n",
      "   Match 1/3: BUY 2 @ 1,985\n",
      "[07:12:31] INFO: ✓ Order ACCEPTED: bcc94b02...\n",
      "[07:12:31] INFO: 🔔 ORDER FILLED: 6e0874f2...\n",
      "[07:12:31] INFO: ✅ Filled: 1.0 @ 1,985 VND\n",
      "[07:12:31] INFO: 🎯 Both orders filled - cross-match complete!\n",
      "[07:12:31] INFO:       ✅ FULL FILL #1: 1.0 @ 1,985 VND | Progress: 1.0/1\n",
      "[07:12:31] INFO: 🔔 ORDER FILLED: bcc94b02...\n",
      "[07:12:31] INFO: ✅ Filled: 1.0 @ 1,985 VND\n",
      "[07:12:31] INFO: 🎯 Both orders filled - cross-match complete!\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ Order ACCEPTED: bcc94b02... | Status: NEW\n",
      "🎯 Order FILLED: 6e0874f2... | 1.0 @ 1,985\n",
      "🎯 Order FILLED: bcc94b02... | 1.0 @ 1,985\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[07:12:31] INFO: \n",
      "   Match 2/3: BUY 2 @ 1,985\n",
      "[07:12:32] INFO: ✓ Order ACCEPTED: 2f8cda43...\n",
      "[07:12:32] INFO: 🔔 ORDER FILLED: 6dc36178...\n",
      "[07:12:32] INFO: ✅ Filled: 1.0 @ 1,985 VND\n",
      "[07:12:32] INFO: 🎯 Both orders filled - cross-match complete!\n",
      "[07:12:32] INFO:       ✅ FULL FILL #1: 1.0 @ 1,985 VND | Progress: 2.0/2\n",
      "[07:12:32] INFO: 🔔 ORDER FILLED: 2f8cda43...\n",
      "[07:12:32] INFO: ✅ Filled: 1.0 @ 1,985 VND\n",
      "[07:12:32] INFO: 🎯 Both orders filled - cross-match complete!\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ Order ACCEPTED: 2f8cda43... | Status: NEW\n",
      "🎯 Order FILLED: 6dc36178... | 1.0 @ 1,985\n",
      "🎯 Order FILLED: 2f8cda43... | 1.0 @ 1,985\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[07:12:32] INFO: \n",
      "   Match 3/3: BUY 1 @ 1,985\n",
      "[07:12:32] INFO: ✓ Order ACCEPTED: 41666518...\n",
      "[07:12:32] INFO: 🔔 ORDER FILLED: 41666518...\n",
      "[07:12:32] INFO: ✅ Filled: 1.0 @ 1,985 VND\n",
      "[07:12:32] INFO: 🎯 Both orders filled - cross-match complete!\n",
      "[07:12:32] INFO: 🔔 ORDER FILLED: ce45d8e6...\n",
      "[07:12:32] INFO: ✅ Filled: 1.0 @ 1,985 VND\n",
      "[07:12:32] INFO: 🎯 Both orders filled - cross-match complete!\n",
      "[07:12:32] INFO:       ✅ FULL FILL #1: 1.0 @ 1,985 VND | Progress: 2.0/2\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ Order ACCEPTED: 41666518... | Status: NEW\n",
      "🎯 Order FILLED: 41666518... | 1.0 @ 1,985\n",
      "🎯 Order FILLED: ce45d8e6... | 1.0 @ 1,985\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[07:12:33] INFO: \n",
      "⏳ Waiting for all fills...\n",
      "[07:12:33] INFO: \n",
      "✅ Scenario B completed!\n",
      "[07:12:33] INFO: \n",
      "📊 D1 ORDER EXECUTION SUMMARY\n",
      "[07:12:33] INFO: ================================================================================\n",
      "[07:12:33] INFO: Total D1 Orders: 4\n",
      "[07:12:33] INFO: Total Fill Events: 4\n",
      "\n",
      "[07:12:33] INFO: Order #1: cf41edf5...\n",
      "[07:12:33] INFO:   Status: FILLED\n",
      "[07:12:33] INFO:   Ordered: 5 contracts\n",
      "[07:12:33] INFO:   Filled: 5.0 contracts\n",
      "[07:12:33] INFO:   Fills: 1\n",
      "[07:12:33] INFO:   Avg Price: 1,985 VND\n",
      "[07:12:33] INFO:   Fill History:\n",
      "[07:12:33] INFO:     Fill #1: 5.0 @ 1,985 VND\n",
      "[07:12:33] INFO: \n",
      "[07:12:33] INFO: Order #2: ce45d8e6...\n",
      "[07:12:33] INFO:   Status: FILLED\n",
      "[07:12:33] INFO:   Ordered: 2 contracts\n",
      "[07:12:33] INFO:   Filled: 2.0 contracts\n",
      "[07:12:33] INFO:   Fills: 1\n",
      "[07:12:33] INFO:   Avg Price: 1,985 VND\n",
      "[07:12:33] INFO:   Fill History:\n",
      "[07:12:33] INFO:     Fill #1: 1.0 @ 1,985 VND\n",
      "[07:12:33] INFO: \n",
      "[07:12:33] INFO: Order #3: 6dc36178...\n",
      "[07:12:33] INFO:   Status: FILLED\n",
      "[07:12:33] INFO:   Ordered: 2 contracts\n",
      "[07:12:33] INFO:   Filled: 2.0 contracts\n",
      "[07:12:33] INFO:   Fills: 1\n",
      "[07:12:33] INFO:   Avg Price: 1,985 VND\n",
      "[07:12:33] INFO:   Fill History:\n",
      "[07:12:33] INFO:     Fill #1: 1.0 @ 1,985 VND\n",
      "[07:12:33] INFO: \n",
      "[07:12:33] INFO: Order #4: 6e0874f2...\n",
      "[07:12:33] INFO:   Status: FILLED\n",
      "[07:12:33] INFO:   Ordered: 1 contracts\n",
      "[07:12:33] INFO:   Filled: 1.0 contracts\n",
      "[07:12:33] INFO:   Fills: 1\n",
      "[07:12:33] INFO:   Avg Price: 1,985 VND\n",
      "[07:12:33] INFO:   Fill History:\n",
      "[07:12:33] INFO:     Fill #1: 1.0 @ 1,985 VND\n",
      "[07:12:33] INFO: \n",
      "[07:12:33] INFO: ================================================================================\n",
      "[07:12:33] INFO: \n",
      "💰 D1 Final: 8,052,580,140 VND\n",
      "[07:12:33] INFO: \n",
      "🎉 ALL SCENARIOS COMPLETED!\n",
      "[07:12:33] INFO: \n",
      "Key Observations:\n",
      "[07:12:33] INFO:   • Scenario A: 1 order → 1 fill (full execution)\n",
      "[07:12:33] INFO:   • Scenario B: 3 orders → 3 fills (FIFO)\n",
      "[07:12:33] INFO:   • Same price → time-priority matching\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "✅ Example 4 completed!\n",
      "================================================================================\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# EXAMPLE 4: Execute Both Scenarios\n",
    "# ============================================================================\n",
    "# Runs Scenario A (full fill) then Scenario B (multiple orders).\n",
    "# Demonstrates different fill patterns with the same cross-matching mechanism.\n",
    "# ============================================================================\n",
    "\n",
    "print(\"=\" * 80)\n",
    "print(\"EXAMPLE 4: ADVANCED CROSS-MATCHING\")\n",
    "print(\"=\" * 80)\n",
    "\n",
    "symbol = CONFIG[\"symbol_derivative\"]\n",
    "price = 1985.0\n",
    "\n",
    "logger.info(f\"Symbol: {symbol}\")\n",
    "logger.info(f\"Price: {price:,.0f}\")\n",
    "logger.info(\"Focus: Full fills vs Multiple orders\")\n",
    "\n",
    "# Create tracker and subscribe to events\n",
    "tracker_ex4 = D1OrderTracker()\n",
    "client.on(\"fix:order:filled\", tracker_ex4.on_order_filled)\n",
    "client.on(\"fix:order:accepted\", tracker_ex4.on_order_accepted)\n",
    "client.on(\"fix:order:rejected\", tracker_ex4.on_order_rejected)\n",
    "\n",
    "# Show initial balance\n",
    "with client.use_sub_account(CONFIG[\"account_d1\"]):\n",
    "    balance = client.get_cash_balance()\n",
    "logger.info(f\"\\n💰 D1 Balance: {balance.get('remainCash', 0):,.0f} VND\\n\")\n",
    "\n",
    "# --- Scenario A: 1 order → 1 fill ---\n",
    "if not run_scenario_a(tracker_ex4, symbol, price):\n",
    "    raise Exception(\"Scenario A failed\")\n",
    "\n",
    "time.sleep(2)  # Pause between scenarios\n",
    "\n",
    "# --- Scenario B: 3 orders → 3 fills ---\n",
    "if not run_scenario_b(tracker_ex4, symbol, price):\n",
    "    raise Exception(\"Scenario B failed\")\n",
    "\n",
    "# Print execution summary\n",
    "tracker_ex4.print_summary()\n",
    "\n",
    "# Show final balance\n",
    "with client.use_sub_account(CONFIG[\"account_d1\"]):\n",
    "    balance_after = client.get_cash_balance()\n",
    "logger.info(f\"\\n💰 D1 Final: {balance_after.get('remainCash', 0):,.0f} VND\")\n",
    "\n",
    "# Summary of observations\n",
    "logger.info(\"\\n🎉 ALL SCENARIOS COMPLETED!\")\n",
    "logger.info(\"\\nKey Observations:\")\n",
    "logger.info(\"  • Scenario A: 1 order → 1 fill (full execution)\")\n",
    "logger.info(\"  • Scenario B: 3 orders → 3 fills (FIFO)\")\n",
    "logger.info(\"  • Same price → time-priority matching\")\n",
    "\n",
    "print(\"\\n✅ Example 4 completed!\")\n",
    "print(\"=\" * 80)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f9abdc50",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## 6. Summary\n",
    "\n",
    "### 🎓 What You Learned\n",
    "\n",
    "1. **Single Persistent Connection**\n",
    "   - One client for all examples\n",
    "   - No disconnect/reconnect overhead\n",
    "\n",
    "2. **Sub-Account Management**\n",
    "   - Use `client.use_sub_account(id)` context manager\n",
    "   - Thread-safe switching\n",
    "\n",
    "3. **Event System**\n",
    "   - Subscribe with `client.on(\"event_name\", handler)`\n",
    "   - Handlers receive real-time FIX responses\n",
    "\n",
    "4. **Order Management**\n",
    "   - Place limit orders with `client.place_order()`\n",
    "   - Cancel orders with `client.cancel_order()`\n",
    "\n",
    "5. **Cross-Matching**\n",
    "   - D1 ↔ D2 can match unconditionally\n",
    "   - FIFO order at same price\n",
    "\n",
    "---\n",
    "\n",
    "### 📚 Next Steps\n",
    "\n",
    "1. **Read Guides**: `event-guide.md`, `config-guide.md`, `market-data-guide.md`\n",
    "2. **Try Examples**: `examples/01_*.py` → `examples/11_*.py`\n",
    "3. **Build Your Strategy**: Start with paper trading\n",
    "\n",
    "---\n",
    "\n",
    "*Happy Trading!* 🎯"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d2481a97",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## 7. Cleanup (Optional)\n",
    "\n",
    "Run this cell when you're completely done to disconnect the client.\n",
    "\n",
    "**Note:** Throughout the tutorial, we kept the connection alive for efficiency. Only disconnect when you're done with ALL trading activities."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "38db16f7",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[07:12:33] INFO: Disconnecting...\n",
      "[07:12:33] INFO: ✅ Session ended\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================================================================\n",
      "🔌 DISCONNECTING CLIENT\n",
      "================================================================================\n",
      "================================================================================\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# PART 2: Cleanup\n",
    "# ============================================================================\n",
    "# Disconnect the client when done with all trading.\n",
    "# Note: QuickFIX cleanup may cause segfault - known issue.\n",
    "# ============================================================================\n",
    "\n",
    "print(\"=\" * 80)\n",
    "print(\"🔌 DISCONNECTING CLIENT\")\n",
    "print(\"=\" * 80)\n",
    "\n",
    "logger.info(\"Disconnecting...\")\n",
    "# client.disconnect()  # Uncomment to explicitly disconnect\n",
    "\n",
    "logger.info(\"✅ Session ended\")\n",
    "print(\"=\" * 80)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "venv (3.10.12)",
   "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.10.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
