{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "fb3aacb0",
   "metadata": {},
   "source": [
    "# =============================================================================\n",
    "# Quantitative Trading — Strategy Development & Backtesting Demo\n",
    "# 量化交易 — 策略开发与回测演示\n",
    "\n",
    "# =============================================================================\n",
    "#\n",
    "# 本文件是数据管道 (quant_data_pipeline_demo.py) 的续集。\n",
    "# This file is the sequel to the data pipeline demo.\n",
    "#\n",
    "# Topics covered / 涵盖主题:\n",
    "#   1. Technical Indicators  技术指标  (MA, RSI, MACD, Bollinger Bands)\n",
    "#   2. Signal Generation     信号生成  (entry & exit rules)\n",
    "#   3. Two Demo Strategies   两个示范策略:\n",
    "#        A. Dual Moving Average Crossover  双均线金叉死叉策略\n",
    "#        B. RSI Mean Reversion             RSI 均值回归策略\n",
    "#   4. Vectorized Backtest Engine  向量化回测引擎\n",
    "#   5. Performance Metrics         绩效指标\n",
    "#        (Sharpe, Sortino, Max Drawdown, Win Rate …)\n",
    "#   6. Visualization               可视化\n",
    "#\n",
    "# Prerequisites / 前置条件:\n",
    "#   pip install numpy pandas matplotlib scipy\n",
    "#\n",
    "# Running / 运行方式:\n",
    "#   python quant_strategy_backtest_demo.py\n",
    "\n",
    "# ============================================================================="
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "156c36ec",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "import matplotlib.gridspec as gridspec\n",
    "from scipy import stats\n",
    "import warnings\n",
    "warnings.filterwarnings('ignore')\n",
    "\n",
    "# 中文字体配置 / Chinese font config\n",
    "plt.rcParams['font.sans-serif'] = ['WenQuanYi Zen Hei', 'Arial Unicode MS', 'SimHei', 'DejaVu Sans']\n",
    "plt.rcParams['axes.unicode_minus'] = False\n",
    "\n",
    "np.random.seed(42)\n",
    "print(\"=\" * 70)\n",
    "print(\"  量化交易策略开发与回测演示\")\n",
    "print(\"  Quantitative Trading: Strategy Development & Backtesting Demo\")\n",
    "print(\"=\" * 70)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "62cbe290",
   "metadata": {},
   "source": [
    "# =============================================================================\n",
    "# SECTION 0: Synthetic Price Data  合成价格数据\n",
    "\n",
    "# -----------------------------------------------------------------------------\n",
    "# We simulate a single stock using Geometric Brownian Motion (几何布朗运动),\n",
    "# the classical model that underlies the Black-Scholes formula.\n",
    "#\n",
    "# GBM formula:\n",
    "#   dS = μ·S·dt + σ·S·dW\n",
    "#\n",
    "# Discrete form (what we actually compute each day):\n",
    "#   S_t = S_{t-1} · exp( (μ - σ²/2)·dt + σ·√dt·ε )\n",
    "#\n",
    "# where:\n",
    "#   μ  = drift / 年化漂移率 (expected annual return)\n",
    "#   σ  = volatility / 年化波动率\n",
    "#   dt = 1/252   (one trading day as a fraction of a year)\n",
    "#   ε  ~ N(0,1) (standard normal random shock / 标准正态随机扰动)\n",
    "\n",
    "# ============================================================================="
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "04ed6429",
   "metadata": {},
   "outputs": [],
   "source": [
    "def generate_price_series(\n",
    "    n_days: int = 1500,\n",
    "    mu: float = 0.10,       # 年化预期收益率 / annual expected return\n",
    "    sigma: float = 0.25,    # 年化波动率 / annual volatility\n",
    "    s0: float = 100.0,      # 初始价格 / initial price\n",
    "    seed: int = 42,\n",
    ") -> pd.Series:\n",
    "    \"\"\"\n",
    "    Generate a synthetic daily price series via GBM.\n",
    "    用几何布朗运动生成合成日线价格序列。\n",
    "    \"\"\"\n",
    "    np.random.seed(seed)\n",
    "    dt = 1.0 / 252                                       # 每个交易日占一年的比例\n",
    "    epsilon = np.random.randn(n_days)                    # 每日随机冲击\n",
    "    log_returns = (mu - 0.5 * sigma ** 2) * dt + sigma * np.sqrt(dt) * epsilon\n",
    "    prices = s0 * np.exp(np.cumsum(log_returns))         # 累积乘积 → 价格路径\n",
    "\n",
    "    # 生成工作日日期序列 / generate business-day date index\n",
    "    dates = pd.bdate_range(start=\"2019-01-02\", periods=n_days)\n",
    "    return pd.Series(prices, index=dates, name=\"close\")\n",
    "\n",
    "\n",
    "price = generate_price_series()\n",
    "print(f\"\\n[数据] 生成模拟股票价格: {len(price)} 个交易日\")\n",
    "print(f\"       价格区间: {price.min():.2f} ~ {price.max():.2f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ccda8a1f",
   "metadata": {},
   "source": [
    "# =============================================================================\n",
    "# SECTION 1: Technical Indicators  技术指标\n",
    "\n",
    "# -----------------------------------------------------------------------------\n",
    "# Technical indicators transform raw price/volume data into signals.\n",
    "# 技术指标将原始价格/成交量数据转化为交易信号。\n",
    "#\n",
    "# They are divided into two broad families:\n",
    "# 主要分为两大类:\n",
    "#\n",
    "#   ① Trend-following indicators  趋势跟随指标\n",
    "#       → Moving Averages (MA), MACD\n",
    "#       → Work well in trending markets (趋势市中效果好)\n",
    "#\n",
    "#   ② Oscillators / Mean-reversion indicators  震荡/均值回归指标\n",
    "#       → RSI, Bollinger Bands\n",
    "#       → Work well in range-bound / choppy markets (震荡市中效果好)\n",
    "\n",
    "# =============================================================================\n",
    "\n",
    "# ── 1-A  Simple Moving Average  简单移动平均线 (SMA) ──────────────────────────\n",
    "#\n",
    "# SMA_n(t) = (P_{t} + P_{t-1} + … + P_{t-n+1}) / n\n",
    "#\n",
    "# The SMA smooths out daily noise to reveal the underlying trend.\n",
    "# SMA 平滑日内噪音，揭示潜在趋势。\n",
    "# A longer window → smoother, but lags more behind recent price action.\n",
    "# 窗口越长 → 越平滑，但对价格变化的反应越滞后。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8ec5acb4",
   "metadata": {},
   "outputs": [],
   "source": [
    "def sma(prices: pd.Series, window: int) -> pd.Series:\n",
    "    \"\"\"Simple Moving Average / 简单移动平均线\"\"\"\n",
    "    return prices.rolling(window=window).mean()\n",
    "\n",
    "\n",
    "# ── 1-B  Exponential Moving Average  指数移动平均线 (EMA) ───────────────────\n",
    "#\n",
    "# EMA gives MORE weight to recent prices (recent data matters more).\n",
    "# EMA 给予近期价格更高权重（近期数据更重要）。\n",
    "#\n",
    "# EMA_t = α · P_t + (1 - α) · EMA_{t-1}\n",
    "# where  α = 2 / (n + 1)   (smoothing factor / 平滑因子)\n",
    "#\n",
    "# EMA reacts faster than SMA to price changes.\n",
    "# EMA 对价格变动的反应比 SMA 更灵敏。\n",
    "\n",
    "def ema(prices: pd.Series, span: int) -> pd.Series:\n",
    "    \"\"\"Exponential Moving Average / 指数移动平均线\"\"\"\n",
    "    return prices.ewm(span=span, adjust=False).mean()\n",
    "\n",
    "\n",
    "# ── 1-C  RSI  相对强弱指数 (Relative Strength Index) ─────────────────────────\n",
    "#\n",
    "# RSI measures the speed and magnitude of recent price changes.\n",
    "# RSI 衡量近期价格变动的速度和幅度。\n",
    "#\n",
    "# Formula:\n",
    "#   RS  = average_gain / average_loss  (over last n days)\n",
    "#   RSI = 100 - 100 / (1 + RS)\n",
    "#\n",
    "# Interpretation / 指标解读:\n",
    "#   RSI > 70  →  Overbought  超买  (price may be due for a pullback / 价格可能回调)\n",
    "#   RSI < 30  →  Oversold   超卖  (price may be due for a bounce  / 价格可能反弹)\n",
    "#   RSI = 50  →  Neutral    中性\n",
    "\n",
    "def rsi(prices: pd.Series, window: int = 14) -> pd.Series:\n",
    "    \"\"\"\n",
    "    Compute Wilder's RSI.\n",
    "    计算 Wilder 平滑法 RSI。\n",
    "    \"\"\"\n",
    "    delta = prices.diff()                      # 每日价格变化 / daily price change\n",
    "    gain = delta.clip(lower=0)                 # 只保留上涨部分 / keep only up-days\n",
    "    loss = -delta.clip(upper=0)                # 只保留下跌部分 / keep only down-days\n",
    "\n",
    "    # Wilder uses EMA with span = 2*n - 1 (equivalent to 1/n smoothing)\n",
    "    avg_gain = gain.ewm(alpha=1.0 / window, adjust=False).mean()\n",
    "    avg_loss = loss.ewm(alpha=1.0 / window, adjust=False).mean()\n",
    "\n",
    "    rs = avg_gain / avg_loss                   # 相对强弱值 / relative strength\n",
    "    return 100 - (100 / (1 + rs))             # 转换为 0~100 范围\n",
    "\n",
    "\n",
    "# ── 1-D  MACD  指数平滑异同移动平均线 ────────────────────────────────────────\n",
    "#\n",
    "# MACD reveals the relationship between two EMAs.\n",
    "# MACD 揭示两条 EMA 之间的关系。\n",
    "#\n",
    "# Components / 构成:\n",
    "#   MACD Line  MACD线  = EMA(12) - EMA(26)   (fast minus slow / 快线减慢线)\n",
    "#   Signal Line 信号线 = EMA(9) of MACD Line  (trigger line / 触发线)\n",
    "#   Histogram  柱状图  = MACD Line - Signal Line\n",
    "#\n",
    "# Trading rules / 交易规则:\n",
    "#   MACD crosses above Signal  →  Bullish (金叉, buy signal  / 买入信号)\n",
    "#   MACD crosses below Signal  →  Bearish (死叉, sell signal / 卖出信号)\n",
    "\n",
    "def macd(prices: pd.Series,\n",
    "         fast: int = 12, slow: int = 26, signal: int = 9\n",
    "         ) -> pd.DataFrame:\n",
    "    \"\"\"\n",
    "    Compute MACD, Signal line, and Histogram.\n",
    "    计算 MACD线、信号线和柱状图。\n",
    "    \"\"\"\n",
    "    ema_fast   = ema(prices, fast)\n",
    "    ema_slow   = ema(prices, slow)\n",
    "    macd_line  = ema_fast - ema_slow           # MACD 线\n",
    "    signal_line = ema(macd_line, signal)       # 信号线 (DIF的EMA)\n",
    "    histogram  = macd_line - signal_line       # 柱状图 (MACD Bar)\n",
    "    return pd.DataFrame({\n",
    "        \"macd\": macd_line,\n",
    "        \"signal\": signal_line,\n",
    "        \"histogram\": histogram,\n",
    "    })\n",
    "\n",
    "\n",
    "# ── 1-E  Bollinger Bands  布林带 ─────────────────────────────────────────────\n",
    "#\n",
    "# Bollinger Bands place upper/lower envelopes around a moving average.\n",
    "# 布林带在移动平均线上下各画一条\"包络线\"。\n",
    "#\n",
    "# Formula:\n",
    "#   Middle Band  中轨  = SMA(n)\n",
    "#   Upper Band   上轨  = SMA(n) + k·σ_n     (k = 2 by default / 默认 k=2)\n",
    "#   Lower Band   下轨  = SMA(n) - k·σ_n\n",
    "#\n",
    "# where σ_n is the rolling standard deviation / 滚动标准差\n",
    "#\n",
    "# When price touches the lower band → oversold area (超卖区域)\n",
    "# When price touches the upper band → overbought area (超买区域)\n",
    "# Band width (带宽) contracts before explosive moves (波动收窄常预示突破)\n",
    "\n",
    "def bollinger_bands(prices: pd.Series, window: int = 20, k: float = 2.0\n",
    "                    ) -> pd.DataFrame:\n",
    "    \"\"\"\n",
    "    Compute Bollinger Bands.\n",
    "    计算布林带（上轨、中轨、下轨）。\n",
    "    \"\"\"\n",
    "    mid    = sma(prices, window)               # 中轨 (SMA)\n",
    "    std    = prices.rolling(window).std()      # 滚动标准差\n",
    "    upper  = mid + k * std                     # 上轨\n",
    "    lower  = mid - k * std                     # 下轨\n",
    "    # %B indicator: where is the current price within the band?\n",
    "    # %B 指标：当前价格在带宽中的位置 (0=下轨, 1=上轨)\n",
    "    pct_b  = (prices - lower) / (upper - lower)\n",
    "    return pd.DataFrame({\n",
    "        \"upper\": upper, \"mid\": mid, \"lower\": lower, \"pct_b\": pct_b\n",
    "    })\n",
    "\n",
    "\n",
    "# Compute all indicators on our simulated price series\n",
    "# 对模拟价格序列计算所有指标\n",
    "sma20  = sma(price, 20)       # 20日均线 / 20-day SMA\n",
    "sma60  = sma(price, 60)       # 60日均线 / 60-day SMA (longer trend)\n",
    "rsi14  = rsi(price, 14)       # 14日RSI  / 14-day RSI\n",
    "macd_df = macd(price)         # MACD (12/26/9)\n",
    "bb     = bollinger_bands(price, window=20, k=2.0)\n",
    "\n",
    "print(\"\\n[指标] 技术指标计算完成:\")\n",
    "print(f\"  SMA20   — 首个有效值日期: {sma20.first_valid_index().date()}\")\n",
    "print(f\"  SMA60   — 首个有效值日期: {sma60.first_valid_index().date()}\")\n",
    "print(f\"  RSI14   — 首个有效值日期: {rsi14.first_valid_index().date()}\")\n",
    "print(f\"  MACD    — 首个有效值日期: {macd_df['macd'].first_valid_index().date()}\")\n",
    "print(f\"  BollingerBands — 首个有效值日期: {bb['mid'].first_valid_index().date()}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "739084bb",
   "metadata": {},
   "source": [
    "# =============================================================================\n",
    "# SECTION 2: Strategy A — Dual Moving Average Crossover\n",
    "#            策略 A — 双均线金叉/死叉策略\n",
    "\n",
    "# -----------------------------------------------------------------------------\n",
    "# One of the oldest and most intuitive trend-following strategies.\n",
    "# 最古老也最直观的趋势跟随策略之一。\n",
    "#\n",
    "# Logic / 逻辑:\n",
    "#   Golden Cross (金叉): short MA crosses ABOVE long MA  → BUY  (做多)\n",
    "#   Death Cross  (死叉): short MA crosses BELOW long MA  → SELL (平仓)\n",
    "#\n",
    "# Rationale / 原理:\n",
    "#   When the short-term average rises above the long-term average, it signals\n",
    "#   that recent momentum is stronger than the historical trend → bullish.\n",
    "#   短期均线上穿长期均线，意味着近期动能强于历史趋势 → 看涨。\n",
    "#\n",
    "# Parameters / 参数:\n",
    "#   SHORT_WINDOW = 20  (fast line / 快线)\n",
    "#   LONG_WINDOW  = 60  (slow line / 慢线)\n",
    "\n",
    "# ============================================================================="
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a99d5899",
   "metadata": {},
   "outputs": [],
   "source": [
    "SHORT_WIN = 20   # 短期均线窗口 / short-term MA window\n",
    "LONG_WIN  = 60   # 长期均线窗口 / long-term MA window\n",
    "\n",
    "ma_short = sma(price, SHORT_WIN)\n",
    "ma_long  = sma(price, LONG_WIN)\n",
    "\n",
    "# ── Signal generation  信号生成 ───────────────────────────────────────────────\n",
    "#\n",
    "# Signal (信号) = +1 when we should be LONG (持多仓), 0 when out of market (空仓)\n",
    "#\n",
    "# Step 1: raw_signal = 1 whenever short MA > long MA (short MA above long MA)\n",
    "# Step 2: detect crossovers (cross = today's signal ≠ yesterday's signal)\n",
    "#\n",
    "# We use a \"position\" approach — hold the position until it reverses.\n",
    "# 使用\"持仓\"方式 — 持有直到信号翻转。\n",
    "\n",
    "# raw_signal: 1 = short above long (看多区域), 0 = short below long (看空区域)\n",
    "raw_signal = (ma_short > ma_long).astype(int)\n",
    "\n",
    "# Align signals: use yesterday's signal to trade today (avoid lookahead bias)\n",
    "# 用昨天的信号决定今天的仓位，避免\"未来数据偷窥\" (前视偏差 / lookahead bias)\n",
    "ma_signal = raw_signal.shift(1).fillna(0)\n",
    "\n",
    "print(\"\\n[策略A] 双均线信号生成完成\")\n",
    "print(f\"  多头持仓天数 (Signal=1): {int(ma_signal.sum())} 天\")\n",
    "print(f\"  空仓天数     (Signal=0): {int((ma_signal == 0).sum())} 天\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dd6312ac",
   "metadata": {},
   "source": [
    "# =============================================================================\n",
    "# SECTION 3: Strategy B — RSI Mean Reversion\n",
    "#            策略 B — RSI 均值回归策略\n",
    "\n",
    "# -----------------------------------------------------------------------------\n",
    "# This is a contrarian strategy: buy when the market seems \"too weak\",\n",
    "# sell when it seems \"too strong\".\n",
    "# 这是一个逆势策略：市场\"跌过头\"时买入，\"涨过头\"时卖出。\n",
    "#\n",
    "# Logic / 逻辑:\n",
    "#   RSI drops below oversold level (超卖线, default 30)  →  BUY  signal\n",
    "#   RSI rises above overbought level (超买线, default 70) →  SELL signal\n",
    "#\n",
    "# This exploits mean reversion (均值回归): extreme prices tend to revert.\n",
    "# 利用均值回归特性：极端价格倾向于回归均值。\n",
    "#\n",
    "# Risk / 风险:\n",
    "#   In a strong trend, RSI can stay oversold/overbought for long stretches.\n",
    "#   在强趋势中，RSI 可以长时间停留在超卖/超买区域，造成连续亏损。\n",
    "\n",
    "# ============================================================================="
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c428d624",
   "metadata": {},
   "outputs": [],
   "source": [
    "RSI_OVERSOLD  = 30   # 超卖线 / oversold threshold\n",
    "RSI_OVERBOUGHT = 70  # 超买线 / overbought threshold\n",
    "\n",
    "def rsi_signal(rsi_series: pd.Series,\n",
    "               oversold: float = 30,\n",
    "               overbought: float = 70) -> pd.Series:\n",
    "    \"\"\"\n",
    "    Generate long/short/flat signals from RSI.\n",
    "    根据 RSI 生成多空平信号。\n",
    "\n",
    "    Returns a Series of:\n",
    "      +1  →  Long  (做多)\n",
    "      -1  →  Short (做空)\n",
    "       0  →  Flat  (空仓, no position)\n",
    "    \"\"\"\n",
    "    position = pd.Series(0, index=rsi_series.index, dtype=float)\n",
    "    current_pos = 0   # 当前持仓状态 / current position state\n",
    "\n",
    "    for i in range(1, len(rsi_series)):\n",
    "        r = rsi_series.iloc[i]\n",
    "        if pd.isna(r):\n",
    "            position.iloc[i] = 0\n",
    "            continue\n",
    "\n",
    "        # Entry rules / 入场规则\n",
    "        if r < oversold and current_pos == 0:\n",
    "            current_pos = 1     # 超卖 → 做多 / oversold → go long\n",
    "\n",
    "        elif r > overbought and current_pos == 0:\n",
    "            current_pos = -1    # 超买 → 做空 / overbought → go short\n",
    "\n",
    "        # Exit rules / 出场规则\n",
    "        # Exit long when RSI recovers above 50 (回到中性区域 / back to neutral)\n",
    "        elif current_pos == 1 and r > 50:\n",
    "            current_pos = 0\n",
    "\n",
    "        # Exit short when RSI falls below 50\n",
    "        elif current_pos == -1 and r < 50:\n",
    "            current_pos = 0\n",
    "\n",
    "        position.iloc[i] = current_pos\n",
    "\n",
    "    return position\n",
    "\n",
    "\n",
    "rsi_pos = rsi_signal(rsi14, RSI_OVERSOLD, RSI_OVERBOUGHT)\n",
    "\n",
    "# Shift by 1 day to avoid lookahead bias / 前移一天避免前视偏差\n",
    "rsi_signal_shifted = rsi_pos.shift(1).fillna(0)\n",
    "\n",
    "print(\"\\n[策略B] RSI信号生成完成\")\n",
    "print(f\"  多头持仓天数 (Signal=+1): {int((rsi_signal_shifted == 1).sum())} 天\")\n",
    "print(f\"  空头持仓天数 (Signal=-1): {int((rsi_signal_shifted == -1).sum())} 天\")\n",
    "print(f\"  空仓天数     (Signal= 0): {int((rsi_signal_shifted == 0).sum())} 天\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3bb7e0d4",
   "metadata": {},
   "source": [
    "# =============================================================================\n",
    "# SECTION 4: Vectorized Backtest Engine  向量化回测引擎\n",
    "\n",
    "# -----------------------------------------------------------------------------\n",
    "# A backtest (回测) simulates how a strategy would have performed\n",
    "# on historical data. It is the primary tool for validating a strategy\n",
    "# before risking real money.\n",
    "# 回测是在历史数据上模拟策略表现的工具，是真实投资前验证策略的主要手段。\n",
    "#\n",
    "# Two main backtest styles / 两种主要回测方式:\n",
    "#\n",
    "#   ① Vectorized backtest  向量化回测\n",
    "#       - Compute all positions & P&L as array operations at once (numpy/pandas)\n",
    "#       - Very fast; good for strategy exploration\n",
    "#       - 所有仓位和盈亏一次性用数组运算计算，速度极快，适合策略探索\n",
    "#\n",
    "#   ② Event-driven backtest  事件驱动回测\n",
    "#       - Simulate time step-by-step, reacting to each market event\n",
    "#       - More realistic (handles fills, slippage, latency, order queuing)\n",
    "#       - 逐笔模拟市场事件，更真实（考虑成交、滑点、延迟等），速度较慢\n",
    "#\n",
    "# We use the vectorized approach here for clarity and speed.\n",
    "# 此处使用向量化方式，兼顾清晰度和速度。\n",
    "#\n",
    "# Cost model  交易成本模型:\n",
    "#   - Commission (佣金): charged each time you trade (per trade)\n",
    "#   - Slippage   (滑点): the difference between the expected fill price and\n",
    "#                        the actual fill price (price moves against you)\n",
    "#   We approximate both as a percentage of the trade value.\n",
    "#   两者合并近似为交易金额的固定比例。\n",
    "\n",
    "# ============================================================================="
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1d6b6a55",
   "metadata": {},
   "outputs": [],
   "source": [
    "class VectorizedBacktester:\n",
    "    \"\"\"\n",
    "    A simple vectorized backtesting engine.\n",
    "    简单的向量化回测引擎。\n",
    "\n",
    "    Assumptions / 假设:\n",
    "      • Long-only or long/short positions\n",
    "      • Trade at next-day's open (用下一天开盘价成交) — conservative assumption\n",
    "        We approximate this by using the same day's close shifted by 1 day.\n",
    "      • Round-trip cost (单次交易成本) = 2 × cost_per_trade\n",
    "        (pay cost on entry AND exit / 进出各收一次)\n",
    "      • No leverage (无杠杆), position size is 100% of capital when in trade\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(\n",
    "        self,\n",
    "        prices: pd.Series,\n",
    "        signal: pd.Series,\n",
    "        cost_per_trade: float = 0.001,  # 0.1% one-way / 单向 0.1% (含佣金+滑点)\n",
    "        initial_capital: float = 1_000_000.0,  # 初始资金 / initial capital\n",
    "        name: str = \"Strategy\",\n",
    "    ):\n",
    "        self.prices = prices\n",
    "        self.signal = signal.reindex(prices.index).fillna(0)\n",
    "        self.cost_per_trade = cost_per_trade\n",
    "        self.initial_capital = initial_capital\n",
    "        self.name = name\n",
    "        self._run()\n",
    "\n",
    "    def _run(self):\n",
    "        \"\"\"Core backtesting logic.  核心回测逻辑。\"\"\"\n",
    "        prices = self.prices\n",
    "        signal = self.signal\n",
    "\n",
    "        # ── Daily price return  日收益率 ────────────────────────────────────\n",
    "        daily_ret = prices.pct_change().fillna(0)\n",
    "\n",
    "        # ── Strategy return (before costs)  策略日收益率（扣除成本前）─────────\n",
    "        # Strategy return = signal × market return\n",
    "        # 策略当日收益率 = 持仓方向 × 市场当日收益率\n",
    "        strat_ret_gross = signal * daily_ret\n",
    "\n",
    "        # ── Transaction cost  交易成本 ──────────────────────────────────────\n",
    "        # Detect position changes (signal changes from one day to the next)\n",
    "        # 检测仓位变化（信号从一天到下一天发生变化）\n",
    "        position_change = signal.diff().fillna(0).abs()  # >0 means we traded\n",
    "        # Cost is charged each time position changes\n",
    "        # 每次仓位变化时扣除成本\n",
    "        cost = position_change * self.cost_per_trade\n",
    "\n",
    "        # ── Net strategy return  策略净收益率 ───────────────────────────────\n",
    "        strat_ret_net = strat_ret_gross - cost\n",
    "\n",
    "        # ── Equity curve  净值曲线 ───────────────────────────────────────────\n",
    "        # The equity curve tracks how 1 unit of capital grows over time.\n",
    "        # 净值曲线追踪单位资本随时间的增长。\n",
    "        # (1 + daily_net_return) compounded every day\n",
    "        equity = self.initial_capital * (1 + strat_ret_net).cumprod()\n",
    "        equity_bh = self.initial_capital * (1 + daily_ret).cumprod()  # Buy & Hold benchmark\n",
    "\n",
    "        # ── Drawdown  回撤 ──────────────────────────────────────────────────\n",
    "        # Drawdown measures how far we are from the peak at any point in time.\n",
    "        # 回撤衡量当前净值距离历史最高点的跌幅。\n",
    "        rolling_max = equity.cummax()\n",
    "        drawdown = (equity - rolling_max) / rolling_max  # always <= 0\n",
    "\n",
    "        # Store results for later analysis\n",
    "        self.daily_ret    = daily_ret\n",
    "        self.strat_ret    = strat_ret_net\n",
    "        self.equity       = equity\n",
    "        self.equity_bh    = equity_bh\n",
    "        self.drawdown     = drawdown\n",
    "        self.n_trades     = int((position_change > 0).sum())\n",
    "        self.total_cost   = cost.sum()\n",
    "\n",
    "    # ── Performance metrics  绩效指标 ──────────────────────────────────────────\n",
    "    #\n",
    "    # A well-rounded strategy evaluation uses multiple metrics, because\n",
    "    # no single number captures the full picture.\n",
    "    # 全面的策略评估需要多个指标，因为单一数字无法描述全貌。\n",
    "    #\n",
    "    # Key metrics / 关键指标:\n",
    "    #   Total Return    总收益率  — how much did we make in total?\n",
    "    #   CAGR            年化复合增长率 — annualized compounded growth rate\n",
    "    #   Sharpe Ratio    夏普比率  — return per unit of total risk (risk-adjusted)\n",
    "    #   Sortino Ratio   索提诺比率 — return per unit of DOWNSIDE risk only\n",
    "    #   Max Drawdown    最大回撤  — worst peak-to-trough decline\n",
    "    #   Calmar Ratio    卡玛比率  — CAGR / Max Drawdown (reward vs worst loss)\n",
    "    #   Win Rate        胜率     — fraction of days (or trades) with positive P&L\n",
    "    #   Profit Factor   盈亏比   — total profit / total loss\n",
    "\n",
    "    def metrics(self) -> dict:\n",
    "        \"\"\"Compute and return a dictionary of performance metrics.\n",
    "           计算并返回绩效指标字典。\"\"\"\n",
    "        r = self.strat_ret\n",
    "        eq = self.equity\n",
    "        n  = len(r)\n",
    "        years = n / 252.0          # approximate years in sample / 样本年数估算\n",
    "\n",
    "        # Total return / 总收益率\n",
    "        total_return = (eq.iloc[-1] / self.initial_capital) - 1\n",
    "\n",
    "        # CAGR  年化复合增长率\n",
    "        # CAGR = (EndValue / StartValue)^(1/years) - 1\n",
    "        cagr = (1 + total_return) ** (1 / years) - 1\n",
    "\n",
    "        # Annualized volatility  年化波动率\n",
    "        ann_vol = r.std() * np.sqrt(252)\n",
    "\n",
    "        # Sharpe Ratio  夏普比率\n",
    "        # Sharpe = (Mean excess return) / StdDev(return) × √252\n",
    "        # Excess return = strategy return - risk-free rate\n",
    "        # 超额收益率 = 策略收益率 - 无风险利率\n",
    "        # We use 0 as risk-free rate for simplicity (or assume it's netted out)\n",
    "        risk_free = 0.0\n",
    "        sharpe = (r.mean() - risk_free / 252) / r.std() * np.sqrt(252) if r.std() > 0 else 0\n",
    "\n",
    "        # Sortino Ratio  索提诺比率\n",
    "        # Like Sharpe but only penalizes DOWNSIDE volatility\n",
    "        # 类似夏普，但只惩罚下行波动率（亏损波动率）\n",
    "        downside = r[r < 0]\n",
    "        downside_std = downside.std() * np.sqrt(252) if len(downside) > 0 else 1e-9\n",
    "        sortino = (cagr - risk_free) / downside_std if downside_std > 0 else 0\n",
    "\n",
    "        # Maximum Drawdown  最大回撤\n",
    "        max_dd = self.drawdown.min()           # most negative value (最大负值)\n",
    "\n",
    "        # Calmar Ratio  卡玛比率\n",
    "        # Calmar = CAGR / |Max Drawdown|\n",
    "        calmar = cagr / abs(max_dd) if max_dd != 0 else 0\n",
    "\n",
    "        # Win rate  胜率 (fraction of trading days with positive return)\n",
    "        win_rate = (r > 0).mean()\n",
    "\n",
    "        # Profit factor  盈亏比\n",
    "        # = Sum of positive returns / |Sum of negative returns|\n",
    "        gross_profit = r[r > 0].sum()\n",
    "        gross_loss   = abs(r[r < 0].sum())\n",
    "        profit_factor = gross_profit / gross_loss if gross_loss > 0 else np.inf\n",
    "\n",
    "        return {\n",
    "            \"总收益率   Total Return\":    f\"{total_return:.2%}\",\n",
    "            \"年化收益率 CAGR\":            f\"{cagr:.2%}\",\n",
    "            \"年化波动率 Ann. Volatility\":  f\"{ann_vol:.2%}\",\n",
    "            \"夏普比率  Sharpe Ratio\":     f\"{sharpe:.3f}\",\n",
    "            \"索提诺比率 Sortino Ratio\":   f\"{sortino:.3f}\",\n",
    "            \"最大回撤  Max Drawdown\":     f\"{max_dd:.2%}\",\n",
    "            \"卡玛比率  Calmar Ratio\":     f\"{calmar:.3f}\",\n",
    "            \"胜率      Win Rate\":         f\"{win_rate:.2%}\",\n",
    "            \"盈亏比    Profit Factor\":    f\"{profit_factor:.3f}\",\n",
    "            \"交易次数  # Trades\":         str(self.n_trades),\n",
    "            \"总成本    Total Cost\":       f\"{self.total_cost:.4%}\",\n",
    "        }\n",
    "\n",
    "    def print_metrics(self):\n",
    "        \"\"\"Pretty-print the performance report.  格式化打印绩效报告。\"\"\"\n",
    "        print(f\"\\n{'=' * 55}\")\n",
    "        print(f\"  策略绩效报告 / Performance Report: {self.name}\")\n",
    "        print(f\"{'=' * 55}\")\n",
    "        for k, v in self.metrics().items():\n",
    "            print(f\"  {k:<35} {v}\")\n",
    "        print(f\"{'=' * 55}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ea411c63",
   "metadata": {},
   "source": [
    "# =============================================================================\n",
    "# SECTION 5: Run Backtests  执行回测\n",
    "\n",
    "# =============================================================================\n",
    "\n",
    "# ── Strategy A: MA Crossover  双均线策略 ──────────────────────────────────────"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e13a487c",
   "metadata": {},
   "outputs": [],
   "source": [
    "bt_ma = VectorizedBacktester(\n",
    "    prices=price,\n",
    "    signal=ma_signal,      # +1 = long, 0 = flat\n",
    "    cost_per_trade=0.001,  # 0.1% per trade (reasonable for liquid stocks)\n",
    "    name=\"双均线策略 (MA Crossover 20/60)\",\n",
    ")\n",
    "\n",
    "# ── Strategy B: RSI Mean Reversion  RSI均值回归策略 ──────────────────────────\n",
    "bt_rsi = VectorizedBacktester(\n",
    "    prices=price,\n",
    "    signal=rsi_signal_shifted,   # +1 = long, -1 = short, 0 = flat\n",
    "    cost_per_trade=0.001,\n",
    "    name=\"RSI均值回归策略 (RSI Mean Reversion 14)\",\n",
    ")\n",
    "\n",
    "# ── Benchmark: Buy & Hold  基准：买入并持有 ───────────────────────────────────\n",
    "# Buy & Hold (买入持有) is always our benchmark: simply hold the asset forever.\n",
    "# It requires zero skill and zero effort — any strategy must beat this to\n",
    "# justify the extra complexity and transaction costs.\n",
    "# 买入持有是永远的基准策略：无需技能、零成本。任何策略都必须超越它才有意义。\n",
    "bt_bh = VectorizedBacktester(\n",
    "    prices=price,\n",
    "    signal=pd.Series(1, index=price.index, dtype=float),  # always long / 始终做多\n",
    "    cost_per_trade=0.0,   # no trading costs / 无交易成本\n",
    "    name=\"Buy & Hold 基准 (买入持有)\",\n",
    ")\n",
    "\n",
    "bt_ma.print_metrics()\n",
    "bt_rsi.print_metrics()\n",
    "bt_bh.print_metrics()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d601da1",
   "metadata": {},
   "source": [
    "# =============================================================================\n",
    "# SECTION 6: Visualization  可视化\n",
    "\n",
    "# ============================================================================="
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e6b6bfc8",
   "metadata": {},
   "outputs": [],
   "source": [
    "fig = plt.figure(figsize=(16, 22))\n",
    "gs  = gridspec.GridSpec(6, 2, figure=fig, hspace=0.45, wspace=0.3)\n",
    "\n",
    "# ── Plot 1: Price + MA signals  价格 + 均线信号 ────────────────────────────────\n",
    "ax1 = fig.add_subplot(gs[0, :])   # span full width\n",
    "ax1.plot(price, color=\"#1f77b4\", linewidth=1, label=\"价格 Price\")\n",
    "ax1.plot(ma_short, color=\"orange\", linewidth=1.2, label=f\"SMA{SHORT_WIN} (快线)\")\n",
    "ax1.plot(ma_long,  color=\"red\",    linewidth=1.2, label=f\"SMA{LONG_WIN}  (慢线)\")\n",
    "\n",
    "# Shade long periods (持多仓的区间着色)\n",
    "ax1.fill_between(\n",
    "    price.index, price.min(), price.max(),\n",
    "    where=(ma_signal == 1).values,\n",
    "    alpha=0.12, color=\"green\", label=\"多头持仓区间 Long Period\"\n",
    ")\n",
    "ax1.set_title(\"策略A — 双均线信号  (MA Crossover Signals)\", fontsize=13, fontweight=\"bold\")\n",
    "ax1.legend(loc=\"upper left\", fontsize=8)\n",
    "ax1.set_ylabel(\"价格 Price\")\n",
    "ax1.grid(alpha=0.3)\n",
    "\n",
    "# ── Plot 2: RSI  RSI指标 ────────────────────────────────────────────────────\n",
    "ax2 = fig.add_subplot(gs[1, :])\n",
    "ax2.plot(rsi14, color=\"purple\", linewidth=1)\n",
    "ax2.axhline(RSI_OVERBOUGHT, color=\"red\",   linestyle=\"--\", linewidth=1, label=f\"超买线 {RSI_OVERBOUGHT}\")\n",
    "ax2.axhline(RSI_OVERSOLD,   color=\"green\", linestyle=\"--\", linewidth=1, label=f\"超卖线 {RSI_OVERSOLD}\")\n",
    "ax2.axhline(50,             color=\"gray\",  linestyle=\":\",  linewidth=0.8)\n",
    "ax2.fill_between(rsi14.index, RSI_OVERSOLD, rsi14,\n",
    "                  where=(rsi14 < RSI_OVERSOLD), alpha=0.25, color=\"green\",\n",
    "                  label=\"超卖区域 Oversold\")\n",
    "ax2.fill_between(rsi14.index, rsi14, RSI_OVERBOUGHT,\n",
    "                  where=(rsi14 > RSI_OVERBOUGHT), alpha=0.25, color=\"red\",\n",
    "                  label=\"超买区域 Overbought\")\n",
    "ax2.set_ylim(0, 100)\n",
    "ax2.set_title(f\"策略B指标 — RSI({14})  均值回归信号  (RSI Mean Reversion)\", fontsize=13, fontweight=\"bold\")\n",
    "ax2.set_ylabel(\"RSI\")\n",
    "ax2.legend(loc=\"upper left\", fontsize=8, ncol=2)\n",
    "ax2.grid(alpha=0.3)\n",
    "\n",
    "# ── Plot 3: Bollinger Bands  布林带 ────────────────────────────────────────────\n",
    "ax3 = fig.add_subplot(gs[2, :])\n",
    "ax3.plot(price,      color=\"#1f77b4\", linewidth=1,   label=\"价格 Price\")\n",
    "ax3.plot(bb[\"mid\"],  color=\"orange\",  linewidth=1.2, label=\"中轨 Middle (SMA20)\")\n",
    "ax3.plot(bb[\"upper\"],color=\"red\",     linewidth=1,   linestyle=\"--\", label=\"上轨 Upper (+2σ)\")\n",
    "ax3.plot(bb[\"lower\"],color=\"green\",   linewidth=1,   linestyle=\"--\", label=\"下轨 Lower (-2σ)\")\n",
    "ax3.fill_between(price.index, bb[\"upper\"], bb[\"lower\"], alpha=0.07, color=\"blue\")\n",
    "ax3.set_title(\"布林带 (Bollinger Bands 20, 2σ)\", fontsize=13, fontweight=\"bold\")\n",
    "ax3.set_ylabel(\"价格 Price\")\n",
    "ax3.legend(loc=\"upper left\", fontsize=8, ncol=2)\n",
    "ax3.grid(alpha=0.3)\n",
    "\n",
    "# ── Plot 4: MACD  MACD指标 ────────────────────────────────────────────────────\n",
    "ax4 = fig.add_subplot(gs[3, :])\n",
    "ax4.plot(macd_df[\"macd\"],   color=\"blue\",   linewidth=1,   label=\"MACD线 (DIF)\")\n",
    "ax4.plot(macd_df[\"signal\"], color=\"orange\", linewidth=1,   label=\"信号线 (DEA)\")\n",
    "colors = [\"green\" if v >= 0 else \"red\" for v in macd_df[\"histogram\"]]\n",
    "ax4.bar(macd_df.index, macd_df[\"histogram\"], color=colors, alpha=0.5, width=1, label=\"柱状图 Histogram\")\n",
    "ax4.axhline(0, color=\"black\", linewidth=0.8)\n",
    "ax4.set_title(\"MACD (12/26/9) — 趋势确认指标  (Trend Confirmation)\", fontsize=13, fontweight=\"bold\")\n",
    "ax4.set_ylabel(\"MACD\")\n",
    "ax4.legend(loc=\"upper left\", fontsize=8)\n",
    "ax4.grid(alpha=0.3)\n",
    "\n",
    "# ── Plot 5: Equity Curves  净值曲线 ────────────────────────────────────────────\n",
    "ax5 = fig.add_subplot(gs[4, :])\n",
    "ax5.plot(bt_ma.equity,   color=\"blue\",   linewidth=1.5, label=\"策略A: 双均线  MA Crossover\")\n",
    "ax5.plot(bt_rsi.equity,  color=\"purple\", linewidth=1.5, label=\"策略B: RSI 均值回归  RSI Reversion\")\n",
    "ax5.plot(bt_bh.equity,   color=\"gray\",   linewidth=1.2, linestyle=\"--\", label=\"基准: 买入持有  Buy & Hold\")\n",
    "ax5.set_title(\"净值曲线对比  (Equity Curve Comparison)\", fontsize=13, fontweight=\"bold\")\n",
    "ax5.set_ylabel(\"账户价值  Portfolio Value (元)\")\n",
    "ax5.legend(loc=\"upper left\", fontsize=9)\n",
    "ax5.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f\"¥{x/1e4:.0f}万\"))\n",
    "ax5.grid(alpha=0.3)\n",
    "\n",
    "# ── Plot 6: Drawdown  回撤曲线 ────────────────────────────────────────────────\n",
    "ax6 = fig.add_subplot(gs[5, :])\n",
    "ax6.fill_between(bt_ma.drawdown.index,  bt_ma.drawdown,  0, alpha=0.5, color=\"blue\",   label=\"策略A\")\n",
    "ax6.fill_between(bt_rsi.drawdown.index, bt_rsi.drawdown, 0, alpha=0.5, color=\"purple\", label=\"策略B\")\n",
    "ax6.fill_between(bt_bh.drawdown.index,  bt_bh.drawdown,  0, alpha=0.3, color=\"gray\",   label=\"Buy & Hold\")\n",
    "ax6.set_title(\"回撤曲线  (Drawdown Curves)\", fontsize=13, fontweight=\"bold\")\n",
    "ax6.set_ylabel(\"回撤幅度  Drawdown\")\n",
    "ax6.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f\"{x:.0%}\"))\n",
    "ax6.legend(loc=\"lower left\", fontsize=9)\n",
    "ax6.grid(alpha=0.3)\n",
    "\n",
    "plt.suptitle(\n",
    "    \"量化交易策略开发与回测演示\\nQuantitative Trading: Strategy Development & Backtesting\",\n",
    "    fontsize=15, fontweight=\"bold\", y=1.005,\n",
    ")\n",
    "plt.savefig(\"strategy_backtest_demo.png\", dpi=120, bbox_inches=\"tight\")\n",
    "plt.show()\n",
    "print(\"\\n[图表] 已保存至 strategy_backtest_demo.png\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ff8384a5",
   "metadata": {},
   "source": [
    "# =============================================================================\n",
    "# SECTION 7: Walk-Forward Validation  滚动前向验证\n",
    "\n",
    "# -----------------------------------------------------------------------------\n",
    "# A critical warning for all new quant traders / 对所有量化新手的重要警告:\n",
    "#\n",
    "# In-sample overfitting (样本内过拟合) is the #1 trap in backtesting.\n",
    "# 样本内过拟合是回测中最大的陷阱。\n",
    "#\n",
    "# If you test 100 different parameter sets on the same data and pick the best,\n",
    "# that \"best\" result will almost certainly NOT hold out of sample.\n",
    "# 如果在同一份数据上测试100组参数并选最好的，这个\"最优\"结果在样本外几乎必然失效。\n",
    "# This is called data snooping bias / 数据窥探偏差 or p-hacking.\n",
    "#\n",
    "# Walk-Forward Validation (滚动前向验证) helps guard against this:\n",
    "# ┌──────────────────────────────────────────────────────────────────────┐\n",
    "# │ Window 1:  [TRAIN period 1] → optimize params → TEST on period 1+   │\n",
    "# │ Window 2:  [TRAIN period 2] → optimize params → TEST on period 2+   │\n",
    "# │  …repeat, always training on past, testing on future                │\n",
    "# │  始终用过去数据训练，用未来数据测试                                          │\n",
    "# └──────────────────────────────────────────────────────────────────────┘\n",
    "# Only report the concatenated OUT-OF-SAMPLE test results.\n",
    "# 只汇报样本外（OOS）的测试结果。\n",
    "#\n",
    "# Below: a simplified version — we just split into train / test (80/20).\n",
    "# 下面是简化版：直接按 80/20 切分训练集和测试集。\n",
    "\n",
    "# ============================================================================="
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5e99677e",
   "metadata": {},
   "outputs": [],
   "source": [
    "TRAIN_RATIO = 0.8\n",
    "split_idx = int(len(price) * TRAIN_RATIO)\n",
    "split_date = price.index[split_idx]\n",
    "\n",
    "price_train = price.iloc[:split_idx]\n",
    "price_test  = price.iloc[split_idx:]\n",
    "\n",
    "print(f\"\\n{'=' * 55}\")\n",
    "print(f\"  滚动前向验证 / Walk-Forward Split\")\n",
    "print(f\"{'=' * 55}\")\n",
    "print(f\"  训练期 Train: {price_train.index[0].date()} → {price_train.index[-1].date()}  ({len(price_train)} 天)\")\n",
    "print(f\"  测试期 Test : {price_test.index[0].date()}  → {price_test.index[-1].date()}  ({len(price_test)} 天)\")\n",
    "\n",
    "# ── Optimize MA windows on TRAIN set  在训练集上优化均线参数 ────────────────────\n",
    "#\n",
    "# Grid search (网格搜索): try all combinations in the parameter space.\n",
    "# This is the simplest optimization method — good for small parameter spaces.\n",
    "# 网格搜索：遍历参数空间内的所有组合。适合参数空间小的情形。\n",
    "\n",
    "print(\"\\n[优化] 在训练集上搜索最优均线参数...\")\n",
    "print(\"  (搜索空间: short=[5,10,15,20,30], long=[30,40,50,60,80,100])\")\n",
    "\n",
    "best_sharpe = -np.inf\n",
    "best_short  = SHORT_WIN\n",
    "best_long   = LONG_WIN\n",
    "results_grid = []\n",
    "\n",
    "for sw in [5, 10, 15, 20, 30]:\n",
    "    for lw in [30, 40, 50, 60, 80, 100]:\n",
    "        if sw >= lw:\n",
    "            continue   # short must be shorter than long / 短期必须小于长期\n",
    "        ma_s = sma(price_train, sw)\n",
    "        ma_l = sma(price_train, lw)\n",
    "        sig  = (ma_s > ma_l).astype(int).shift(1).fillna(0)\n",
    "        bt   = VectorizedBacktester(price_train, sig, cost_per_trade=0.001, name=\"grid\")\n",
    "        m    = bt.metrics()\n",
    "        sharpe_val = float(m[\"夏普比率  Sharpe Ratio\"])\n",
    "        results_grid.append({\"short\": sw, \"long\": lw, \"sharpe\": sharpe_val})\n",
    "        if sharpe_val > best_sharpe:\n",
    "            best_sharpe = sharpe_val\n",
    "            best_short  = sw\n",
    "            best_long   = lw\n",
    "\n",
    "print(f\"\\n  最优参数 (训练集 in-sample): short={best_short}, long={best_long}\")\n",
    "print(f\"  训练集夏普比率 In-sample Sharpe: {best_sharpe:.3f}\")\n",
    "\n",
    "# ── Apply best params on TEST set  将最优参数应用于测试集 ──────────────────────\n",
    "ma_s_test = sma(price_test, best_short)\n",
    "ma_l_test = sma(price_test, best_long)\n",
    "sig_test  = (ma_s_test > ma_l_test).astype(int).shift(1).fillna(0)\n",
    "\n",
    "bt_test   = VectorizedBacktester(price_test, sig_test, cost_per_trade=0.001,\n",
    "                                  name=f\"MA({best_short}/{best_long}) — 测试集 OOS\")\n",
    "bt_test.print_metrics()\n",
    "\n",
    "print(\"\\n\" + \"=\" * 55)\n",
    "print(\"  ⚠️  注意 / WARNING:\")\n",
    "print(\"  训练集(in-sample)夏普 通常高于 测试集(out-of-sample)夏普\")\n",
    "print(\"  In-sample Sharpe is typically HIGHER than out-of-sample.\")\n",
    "print(\"  夏普衰减 (Sharpe decay) 是策略过拟合的典型信号。\")\n",
    "print(\"  Sharpe decay is a classic sign of overfitting.\")\n",
    "print(\"=\" * 55)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "95c33a90",
   "metadata": {},
   "source": [
    "# =============================================================================\n",
    "# SECTION 8: Return Distribution Analysis  收益率分布分析\n",
    "\n",
    "# -----------------------------------------------------------------------------\n",
    "# Before trusting your Sharpe ratio, check if the return distribution\n",
    "# violates the normality assumption.\n",
    "# 在相信夏普比率之前，检验收益率分布是否违背正态假设。\n",
    "#\n",
    "# Real returns typically show:\n",
    "# 真实收益率通常呈现:\n",
    "#   Fat tails (厚尾 / leptokurtosis):  extreme events more frequent than normal\n",
    "#   Negative skew (负偏态):            crashes are larger than rallies\n",
    "#\n",
    "# A high Sharpe ratio on a fat-tailed distribution can be misleading.\n",
    "# 厚尾分布下的高夏普比率可能具有误导性。\n",
    "\n",
    "# ============================================================================="
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e74d07b8",
   "metadata": {},
   "outputs": [],
   "source": [
    "fig2, axes = plt.subplots(1, 2, figsize=(14, 5))\n",
    "\n",
    "# ── Return distribution histogram  收益率直方图 ─────────────────────────────\n",
    "ax = axes[0]\n",
    "r_ma  = bt_ma.strat_ret.dropna()\n",
    "r_bh  = bt_bh.strat_ret.dropna()\n",
    "\n",
    "ax.hist(r_bh, bins=80, alpha=0.5, color=\"gray\",  density=True, label=\"Buy & Hold\")\n",
    "ax.hist(r_ma, bins=80, alpha=0.5, color=\"blue\",  density=True, label=\"策略A: MA Crossover\")\n",
    "\n",
    "# Overlay a normal distribution for comparison / 叠加正态分布对比\n",
    "x_range = np.linspace(r_bh.min(), r_bh.max(), 300)\n",
    "ax.plot(x_range, stats.norm.pdf(x_range, r_bh.mean(), r_bh.std()),\n",
    "        color=\"red\", linewidth=1.5, linestyle=\"--\", label=\"正态分布 Normal Dist.\")\n",
    "ax.set_title(\"收益率分布  (Return Distribution)\", fontsize=12)\n",
    "ax.set_xlabel(\"日收益率  Daily Return\")\n",
    "ax.set_ylabel(\"频率密度  Density\")\n",
    "ax.legend(fontsize=8)\n",
    "ax.grid(alpha=0.3)\n",
    "\n",
    "# Print distribution stats\n",
    "print(f\"\\n[分布] 策略A — 日收益率统计:\")\n",
    "print(f\"  偏度 Skewness : {r_ma.skew():.3f}  (负值=左尾更厚 fat left tail)\")\n",
    "print(f\"  峰度 Kurtosis : {r_ma.kurtosis():.3f}  (>0 表示厚尾 fat tails vs normal)\")\n",
    "\n",
    "# ── Monthly returns heatmap  月度收益热力图 ─────────────────────────────────\n",
    "ax = axes[1]\n",
    "monthly = bt_ma.strat_ret.resample(\"M\").apply(lambda x: (1 + x).prod() - 1)\n",
    "monthly_df = pd.DataFrame({\n",
    "    \"year\":  monthly.index.year,\n",
    "    \"month\": monthly.index.month,\n",
    "    \"ret\":   monthly.values,\n",
    "})\n",
    "pivot = monthly_df.pivot(index=\"year\", columns=\"month\", values=\"ret\")\n",
    "pivot.columns = [\"Jan\",\"Feb\",\"Mar\",\"Apr\",\"May\",\"Jun\",\"Jul\",\"Aug\",\"Sep\",\"Oct\",\"Nov\",\"Dec\"]\n",
    "\n",
    "import matplotlib.colors as mcolors\n",
    "cmap = mcolors.LinearSegmentedColormap.from_list(\"rg\", [\"#d73027\",\"#ffffff\",\"#1a9850\"])\n",
    "im = ax.imshow(pivot.values, cmap=cmap, aspect=\"auto\",\n",
    "                vmin=-0.15, vmax=0.15)\n",
    "ax.set_xticks(range(12))\n",
    "ax.set_xticklabels(pivot.columns, fontsize=8)\n",
    "ax.set_yticks(range(len(pivot.index)))\n",
    "ax.set_yticklabels(pivot.index, fontsize=9)\n",
    "for i in range(len(pivot.index)):\n",
    "    for j in range(12):\n",
    "        v = pivot.values[i, j]\n",
    "        if not np.isnan(v):\n",
    "            ax.text(j, i, f\"{v:.1%}\", ha=\"center\", va=\"center\", fontsize=6,\n",
    "                    color=\"black\" if abs(v) < 0.08 else \"white\")\n",
    "ax.set_title(\"策略A月度收益热力图\\n(Monthly Return Heatmap)\", fontsize=11)\n",
    "plt.colorbar(im, ax=ax, format=plt.FuncFormatter(lambda x, _: f\"{x:.0%}\"))\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.savefig(\"return_distribution.png\", dpi=120, bbox_inches=\"tight\")\n",
    "plt.show()\n",
    "print(\"[图表] 已保存至 return_distribution.png\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4dfb5f58",
   "metadata": {},
   "source": [
    "# =============================================================================\n",
    "# SECTION 9: Summary & Next Steps  总结与后续\n",
    "\n",
    "# ============================================================================="
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7bac6b3d",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(f\"\"\"\n",
    "{'=' * 70}\n",
    "  总结 Summary\n",
    "{'=' * 70}\n",
    "  本 Demo 演示了量化策略开发与回测的完整流程：\n",
    "\n",
    "  ① 技术指标计算  Technical Indicators\n",
    "     SMA / EMA / RSI / MACD / Bollinger Bands\n",
    "\n",
    "  ② 信号生成  Signal Generation\n",
    "     策略A: 双均线金叉死叉 (MA Crossover) — 趋势跟随\n",
    "     策略B: RSI 超买超卖  (RSI Reversion) — 均值回归\n",
    "\n",
    "  ③ 向量化回测引擎  Vectorized Backtester\n",
    "     考虑了交易成本(佣金+滑点)和前视偏差(lookahead bias)\n",
    "\n",
    "  ④ 绩效指标  Performance Metrics\n",
    "     Sharpe / Sortino / Max Drawdown / Calmar / Win Rate / Profit Factor\n",
    "\n",
    "  ⑤ 前向验证  Walk-Forward Validation\n",
    "     训练集优化参数 → 测试集验证 → 防止过拟合\n",
    "\n",
    "  ⑥ 收益率分布  Return Distribution\n",
    "     偏度/峰度检验，月度热力图\n",
    "\n",
    "  下一步学习方向  Next Steps:\n",
    "  ──────────────────────────────────────────────────────────────\n",
    "  • 因子选股策略 (Alpha Factor Models) — Fama-French, Momentum\n",
    "  • 组合优化     (Portfolio Optimization) — Mean-Variance, Risk Parity\n",
    "  • 事件驱动回测 (Event-Driven Backtesting) — more realistic execution\n",
    "  • 机器学习信号 (ML-based Signals) — XGBoost, LSTM for return prediction\n",
    "  • 风险管理     (Risk Management) — Position sizing, Stop-loss, VaR\n",
    "{'=' * 70}\n",
    "\"\"\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (trading)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.11.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
