# =============================================================================
# Quantitative Trading — Alpha Factor Research Demo
# 量化交易 — Alpha 因子研究演示
# =============================================================================
#
# Alpha 因子 (Alpha Factor) 是量化选股的核心工具。
# 它是一个数学公式，从市场数据中提取信号，预测哪些股票未来会跑赢大盘。
#
# Alpha factors are the core tool of quantitative stock selection.
# Each factor is a mathematical formula that extracts a signal from market data
# to predict which stocks will outperform in the future.
#
# 研究流程 / Research Workflow:
#   §0  合成股票池       Synthetic Universe     (50只股票 × 3年日线数据)
#   §1  因子构建         Factor Construction    (5个经典因子)
#   §2  因子预处理       Factor Preprocessing   (去极值、截面标准化、市值中性化)
#   §3  IC 分析          IC Analysis            (信息系数 / 因子预测能力量化)
#   §4  分层回测         Quantile Analysis      (五分位收益分层验证)
#   §5  因子合成         Factor Combination     (等权 & IC加权合成因子)
#   §6  多空组合         Long-Short Portfolio   (Top/Bottom 20% 多空策略)
#   §7  因子衰减         Factor Decay           (IC 随持有期延长的衰减曲线)
#   §8  可视化           Visualization          (9面板汇总图)
#
# 前置条件 / Prerequisites:
#   pip install numpy pandas matplotlib scipy
#
# 运行方式 / Run:
#   python quant_alpha_factor_demo.py
# =============================================================================

import numpy as np
import pandas as pd
import matplotlib
matplotlib.use('Agg')   # 非交互模式，避免 GUI 阻塞 / non-interactive, prevents GUI block
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from matplotlib.ticker import FuncFormatter
from scipy import stats
import warnings
warnings.filterwarnings('ignore')

# 中文字体配置 / Chinese font configuration
plt.rcParams['font.sans-serif'] = ['WenQuanYi Zen Hei', 'Arial Unicode MS', 'SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

np.random.seed(42)
print("=" * 70)
print("  量化交易 Alpha 因子研究演示")
print("  Quantitative Trading: Alpha Factor Research Demo")
print("=" * 70)


# =============================================================================
# §0  合成股票池  Synthetic Stock Universe
# -----------------------------------------------------------------------------
# 真实因子研究需要几百到几千只股票的截面数据 (cross-sectional data)。
# 我们用"隐藏因子生成模型" (Hidden Factor Generative Model) 合成数据，
# 确保我们构建的因子确实具有预测能力：
#
# Real factor research requires cross-sectional data of hundreds of stocks.
# We use a Hidden Factor Generative Model to synthesize data, ensuring the
# factors we build actually have genuine predictive power.
#
# 每只股票的日收益 = 市场分量 + 质量Alpha + 动量Alpha + 特质噪音
# stock_daily_return = market_component + quality_alpha + momentum_alpha + noise
#
# 其中:
#   market_component = β_i × r_market               (系统性风险 / systematic risk)
#   quality_alpha    = λ_q × quality_score_{i,t}    (质量因子加载, 季度持续)
#   momentum_alpha   = λ_m × momentum_score_{i,t}   (动量因子加载, 月度持续)
#   noise            ~ N(0, σ_idio)                  (特质风险 / idiosyncratic risk)
#
# 关键: quality_score 和 momentum_score 是"隐藏"的，我们无法直接观察。
#       但当我们从价格数据中计算因子时，会自然捕捉到这些隐藏信号。
# Key: these hidden scores are unobservable, but our computed factors will
#      naturally capture them — this is exactly what alpha factors do!
# =============================================================================

N_STOCKS   = 50          # 股票数量 / number of stocks
N_DAYS     = 756         # 交易日 ≈ 3 年 / trading days ≈ 3 years
N_QUARTERS = N_DAYS // 63 + 2   # 季度数 / number of quarters
N_MONTHS   = N_DAYS // 21 + 2   # 月度数 / number of months

# 交易日期序列 / Business-day date index
dates = pd.bdate_range(start="2021-01-04", periods=N_DAYS)

# 股票代码 (模拟A股格式) / Stock symbols (simulated A-share format)
symbols = [f"{str(i).zfill(6)}.{'SH' if i % 2 == 0 else 'SZ'}" for i in range(1, N_STOCKS + 1)]

# ── 0-A  隐藏质量因子（季度持续）/ Hidden Quality Factor (Quarterly Persistence) ──
#
# 质量因子 AR(1): quality_t = 0.90 × quality_{t-1} + shock
# 季度自回归系数 0.90 → 年度持续性 ≈ 0.90^4 = 0.66（持续性更强）
# Quarterly AR(1) = 0.90 → annual persistence ≈ 0.90^4 = 0.66 (more persistent)
quality_quarterly = np.zeros((N_STOCKS, N_QUARTERS))
quality_quarterly[:, 0] = np.random.randn(N_STOCKS)
for q in range(1, N_QUARTERS):
    # sqrt(1 - 0.90^2) ≈ 0.436 保持方差为 1 / keeps variance = 1
    quality_quarterly[:, q] = (0.90 * quality_quarterly[:, q - 1]
                                + 0.436 * np.random.randn(N_STOCKS))

# 季度 → 每日映射 / Map quarterly to daily
quality_daily = np.zeros((N_DAYS, N_STOCKS))
for d in range(N_DAYS):
    q_idx = min(d // 63, N_QUARTERS - 1)
    quality_daily[d] = quality_quarterly[:, q_idx]

# ── 0-B  隐藏动量因子（月度持续）/ Hidden Momentum Factor (Monthly Persistence) ──
#
# 动量因子 AR(1): momentum_t = 0.60 × momentum_{t-1} + shock
# 月度自回归系数 0.60，意味着 3 个月后的持续性 ≈ 0.60^3 = 0.22
# Monthly AR(1) coefficient 0.60 → 3-month persistence ≈ 0.22
momentum_monthly = np.zeros((N_STOCKS, N_MONTHS))
momentum_monthly[:, 0] = np.random.randn(N_STOCKS)
for m in range(1, N_MONTHS):
    # 月度 AR(1) = 0.80，年度持续性 ≈ 0.80^12 = 0.069（合理的动量衰减）
    # Monthly AR(1) = 0.80 → annual persistence ≈ 0.80^12 = 0.069
    momentum_monthly[:, m] = (0.80 * momentum_monthly[:, m - 1]
                               + 0.60 * np.random.randn(N_STOCKS))

momentum_daily = np.zeros((N_DAYS, N_STOCKS))
for d in range(N_DAYS):
    m_idx = min(d // 21, N_MONTHS - 1)
    momentum_daily[d] = momentum_monthly[:, m_idx]

# ── 0-C  日收益率生成 / Daily Return Generation ────────────────────────────────
#
# 日市场收益 (Market Daily Return):  年化μ=8%, σ=20%
mkt_returns = np.random.normal(0.0003, 0.0126, N_DAYS)  # 0.20/√252 ≈ 0.0126

# 每只股票的市场贝塔 β: 均匀分布在 [0.5, 1.5]
# Each stock's market beta β: uniformly distributed in [0.5, 1.5]
stock_betas = np.random.uniform(0.5, 1.5, N_STOCKS)

# (N_DAYS, N_STOCKS): 每列是一只股票，每行是一个交易日
market_component   = mkt_returns[:, np.newaxis] * stock_betas[np.newaxis, :]  # 市场分量
quality_component  = 0.00080 * quality_daily    # 质量贡献：约 ±20% 年化 alpha
momentum_component = 0.00050 * momentum_daily   # 动量贡献：约 ±12% 年化 alpha
idio_noise         = np.random.normal(0, 0.0075, (N_DAYS, N_STOCKS))          # 特质噪音

# 日对数收益率 / Daily log returns
log_returns = market_component + quality_component + momentum_component + idio_noise

# ── 0-D  价格路径 / Price Paths ────────────────────────────────────────────────
# P_t = P_0 × exp( Σ log_return_s, s=1..t )   (价格路径 / price path)
initial_prices = np.random.uniform(5.0, 100.0, N_STOCKS)  # A股初始价格
prices_arr = initial_prices * np.exp(np.cumsum(log_returns, axis=0))

prices_df    = pd.DataFrame(prices_arr,  index=dates, columns=symbols)
log_ret_df   = pd.DataFrame(log_returns, index=dates, columns=symbols)
simple_ret_df = prices_df.pct_change()   # 简单收益率: r_t = P_t/P_{t-1} - 1

# ── 0-E  成交量生成 / Volume Generation ──────────────────────────────────────
# 真实市场中，成交量与绝对收益率正相关（大涨大跌时换手更活跃）。
# In real markets, volume correlates with |return| (more trading on big moves).
#
# log(volume) = log(base) + 3×|log_return| + noise
base_volumes = np.random.uniform(1e6, 5e7, N_STOCKS)   # 基础日均成交量（股）
vol_multiplier = np.exp(3.0 * np.abs(log_returns) + 0.3 * np.random.randn(N_DAYS, N_STOCKS))
volume_df = pd.DataFrame(base_volumes * vol_multiplier, index=dates, columns=symbols)

# ── 0-F  模拟市值 / Simulated Market Capitalization ───────────────────────────
# 市值 (Market Cap) = 价格 × 流通股数  / Market Cap = Price × Float Shares
float_shares  = np.random.uniform(1e8, 1e10, N_STOCKS)  # 流通股数（股）
mktcap_df     = prices_df * float_shares                  # 市值（元）
log_mktcap_df = np.log(mktcap_df)                         # 对数市值（因子分析常用）

market_series = pd.Series(mkt_returns, index=dates)       # 市场收益率序列

print(f"\n[§0] 股票池生成完成 / Universe Generated")
print(f"     股票数量 (Stocks):    {N_STOCKS} 只")
print(f"     交易日数 (Days):      {N_DAYS} 天  {dates[0].date()} ~ {dates[-1].date()}")
print(f"     价格区间 (Price):     {prices_df.min().min():.2f} ~ {prices_df.max().max():.2f} 元")


# =============================================================================
# §1  因子构建  Factor Construction
# -----------------------------------------------------------------------------
# 我们构建 5 个来自学术文献的经典因子，涵盖不同的 Alpha 来源：
# We build 5 classic factors from academic literature, covering different alpha sources:
#
# ┌──────┬────────────────────────────┬──────────────────────────────────────────┐
# │ 因子 │ 名称                       │ 公式 & 来源                              │
# ├──────┼────────────────────────────┼──────────────────────────────────────────┤
# │ MOM  │ 动量 Momentum              │ r(t-252, t-21)  Jegadeesh & Titman 1993  │
# │ REV  │ 短期反转 Reversal          │ -r(t-21, t)     Jegadeesh 1990           │
# │ LVOL │ 低波动 Low Volatility      │ -std(r, 60D)    Baker et al. 2011        │
# │ BAB  │ 低贝塔 Betting vs Beta     │ -β(60D)         Frazzini & Pedersen 2014 │
# │ ILLIQ│ 非流动性 Amihud Illiquidity│ mean(|r|/V,20D) Amihud 2002             │
# └──────┴────────────────────────────┴──────────────────────────────────────────┘
#
# 重要约定: 所有因子都以"因子值越高 → 预期未来收益越高"为正方向。
# Convention: higher factor value → expected higher future return (unified sign).
# =============================================================================

print("\n[§1] 构建因子 / Constructing factors...")

# ── 1-A  动量因子 (MOM)  Momentum Factor ──────────────────────────────────────
#
# 来源 / Source: Jegadeesh & Titman (1993)
# "Returns to Buying Winners and Selling Losers"
#
# 动量效应 (Momentum Effect): 过去 12 个月（跳过最近 1 个月）涨幅最大的股票，
#   未来 3-12 个月通常继续跑赢市场。这是学术上记录最多的股票市场异象之一。
#
# Momentum effect: stocks with highest 12M-1M returns tend to outperform
#   over the next 3-12 months. One of the most documented stock market anomalies.
#
# 为什么要跳过最近 1 个月？/ Why skip the last month?
#   最近 1 个月的收益存在短期反转效应（买卖价差、做市商库存调整等微观结构原因），
#   会污染中期动量信号，所以必须跳过。
#   The last month exhibits short-term reversal due to bid-ask spread and
#   market-maker inventory rebalancing, contaminating medium-term momentum.
#
# 公式 / Formula:
#   MOM_t = (P_{t-21} / P_{t-252}) - 1   (从12M前 到 1M前 的累积收益)
#   MOM_t = (P_{t-21} / P_{t-252}) - 1   (cumulative return from 12M to 1M ago)

MOM_LONG  = 252   # 长回看窗口: 12个月 / lookback long: 12 months
MOM_SHORT = 21    # 跳过窗口: 1个月   / skip window:  1 month

factor_mom = prices_df.shift(MOM_SHORT) / prices_df.shift(MOM_LONG) - 1.0
print(f"     ✓ MOM  动量因子: 有效值 {factor_mom.notna().mean().mean():.1%}")

# ── 1-B  短期反转因子 (REV)  Short-Term Reversal Factor ───────────────────────
#
# 来源 / Source: Jegadeesh (1990)
# "Evidence of Predictable Behavior of Security Returns"
#
# 反转效应 (Reversal Effect): 上个月大涨的股票，下个月往往小幅回调；
#   大跌的股票往往小幅反弹。这是对短期"过度反应"的修正。
#
# Reversal effect: last month's winners tend to reverse; losers tend to bounce.
#   This is correction of short-term overreaction.
#
# 注意: 取负号是因为月涨 → 因子值高 → 预期下跌（反转），我们统一正方向。
# Note: negative sign because high past return → expected to reverse → lower future return,
#       we flip to maintain convention: higher factor → higher expected return.
#
# 公式 / Formula:
#   REV_t = -(P_t / P_{t-21} - 1)    (负1月收益 / negative 1-month return)

factor_rev = -(prices_df / prices_df.shift(MOM_SHORT) - 1.0)
print(f"     ✓ REV  反转因子: 有效值 {factor_rev.notna().mean().mean():.1%}")

# ── 1-C  低波动因子 (LVOL)  Low Volatility Factor ────────────────────────────
#
# 来源 / Source: Ang et al.(2006); Baker, Bradley & Wurgler (2011)
# "Benchmarks as Limits to Arbitrage: Understanding the Low-Volatility Anomaly"
#
# 低波动异象 (Low Volatility Anomaly): 经典金融理论 CAPM 预测高风险 = 高收益。
#   但实证研究发现，低波动率的股票反而有更高的原始收益和风险调整后收益！
#
# Low-vol anomaly: contrary to CAPM, low-volatility stocks earn HIGHER
#   risk-adjusted and even raw returns than high-volatility stocks.
#
# 可能的解释 / Possible explanations:
#   ① 机构投资者受基准约束，被迫持有高贝塔股票 (benchmark constraints)
#   ② 投资者对"彩票式"高波动股票有偏好，导致其被高估 (lottery preference)
#   ③ 杠杆限制阻止套利者纠正定价偏差 (leverage constraints)
#
# 公式 / Formula:
#   LVOL_t = -σ(r_{t-60:t})    (负60日已实现波动率 / negative 60-day realized vol)

VOL_WINDOW = 60   # 60 个交易日 ≈ 3 个月 / 60 trading days ≈ 3 months
factor_lvol = -(simple_ret_df.rolling(VOL_WINDOW).std())
print(f"     ✓ LVOL 低波动因子: 有效值 {factor_lvol.notna().mean().mean():.1%}")

# ── 1-D  低贝塔因子 (BAB)  Betting Against Beta ──────────────────────────────
#
# 来源 / Source: Frazzini & Pedersen (2014) "Betting Against Beta"
#
# BAB 异象: 做多低贝塔股票、做空高贝塔股票，可以获得持续的超额收益。
#   与低波动因子类似，但专注于对"市场系统性风险"的暴露，而非总波动率。
#
# BAB anomaly: long low-beta, short high-beta → persistent excess return.
#   Similar to low-vol but focuses on market systematic risk exposure.
#
# 贝塔计算 / Beta computation:
#   β_i = Cov(r_i, r_market) / Var(r_market)    (60日滚动窗口 / 60-day rolling)
#
# 向量化技巧: 利用 pandas rolling 方法计算所有股票的滚动协方差和市场方差
# Vectorized trick: use pandas rolling to compute rolling covariance and variance

BETA_WINDOW = 60   # 60日滚动贝塔 / 60-day rolling beta

# pandas rolling().cov(other) 计算每个时间点的滚动协方差
# pandas rolling().cov(other) computes rolling covariance at each time point
rolling_var_mkt = market_series.rolling(BETA_WINDOW).var()    # 市场方差 / market variance
rolling_cov     = log_ret_df.apply(
    lambda col: col.rolling(BETA_WINDOW).cov(market_series)    # 逐列协方差 / per-stock cov
)
rolling_beta_df = rolling_cov.div(rolling_var_mkt, axis=0)     # β = cov / var
factor_bab = -rolling_beta_df   # 取负：低贝塔 → 高因子值 / flip: low-beta → high score
print(f"     ✓ BAB  低贝塔因子: 有效值 {factor_bab.notna().mean().mean():.1%}")

# ── 1-E  Amihud 非流动性因子 (ILLIQ)  Amihud Illiquidity Factor ───────────────
#
# 来源 / Source: Amihud (2002) "Illiquidity and Stock Returns"
#
# Amihud 非流动性指标 (Amihud Illiquidity Measure):
#   ILLIQ_{i,t} = (1/D) × Σ_{d=t-D+1}^{t} |r_{i,d}| / Volume_{i,d}
#
# 含义 (Interpretation):
#   "每单位成交量能引起多大的价格变动？"
#   "How much price movement per unit of trading volume?"
#
#   值越高 → 流动性越差 (higher → less liquid)
#   流动性差的股票风险更高，投资者要求额外的"流动性溢价"(liquidity premium)
#   Illiquid stocks carry higher risk, investors demand extra liquidity premium
#
# 注: 本因子中，高 ILLIQ（流动性差）→ 预期高收益（溢价补偿）→ 符合正方向约定
# Note: high ILLIQ (illiquid) → expected high return (premium) → matches positive convention

ILLIQ_WINDOW = 20   # 20日均值 / 20-day average

# |日收益率| / 日成交量，再取20日滚动均值
# |daily return| / daily volume, then 20-day rolling mean
price_impact  = simple_ret_df.abs() / volume_df    # 每日价格冲击 / daily price impact
factor_illiq  = price_impact.rolling(ILLIQ_WINDOW).mean() * 1e8   # 缩放 / scaling
print(f"     ✓ ILLIQ 非流动性因子: 有效值 {factor_illiq.notna().mean().mean():.1%}")

# ── 1-F  汇总 / Consolidate ───────────────────────────────────────────────────
FACTORS = {
    "MOM"   : factor_mom,   # 动量
    "REV"   : factor_rev,   # 短期反转
    "LVOL"  : factor_lvol,  # 低波动
    "BAB"   : factor_bab,   # 低贝塔 / 贝塔
    "ILLIQ" : factor_illiq, # Amihud 非流动性
}
print(f"\n     共构建 {len(FACTORS)} 个因子: {list(FACTORS.keys())}")


# =============================================================================
# §2  因子预处理  Factor Preprocessing
# -----------------------------------------------------------------------------
# 原始因子值不能直接用于分析，需要经过三步标准化预处理流程：
# Raw factor values must go through a 3-step preprocessing pipeline:
#
# Step 1 ── 截面去极值 (Cross-Sectional Winsorization)
#   在每个日期截面，将超出 [μ - 3σ, μ + 3σ] 范围的值截断。
#   At each date, clip values outside [mean - 3σ, mean + 3σ].
#   为什么？极端异常值会主导相关系数计算，掩盖真实的因子信号。
#   Why? Outliers dominate correlation calculations and mask the true signal.
#
# Step 2 ── 截面 Z-score 标准化 (Cross-Sectional Z-score)
#   z_{i,t} = (x_{i,t} - μ_t) / σ_t    (在每个时间截面 t 上计算)
#   z_{i,t} = (x_{i,t} - μ_t) / σ_t    (computed cross-sectionally at each date t)
#   为什么？不同因子量纲不同（动量是%，波动率也是%，ILLIQ 是极小数），
#          标准化后可以直接比较和合成。
#   Why? Factors have different scales; Z-score enables fair comparison and combination.
#
# Step 3 ── 市值中性化 (Market Cap Neutralization)
#   在每个截面，对 log(市值) 做 OLS 回归，用残差替换原因子值：
#   At each date, regress on log(mktcap) via OLS; use residuals as factor:
#   factor_neutral_i = factor_i - (α + β × log_mktcap_i)
#   为什么？小市值效应 (Size Effect) 会污染其他因子。例如小市值股票往往同时具有
#          高动量、高波动率、低流动性，如果不剔除市值效应，因子实际上只是
#          在选小市值股票，而不是真正的动量/波动/流动性信号。
#   Why? Size effect contaminates other factors — small caps often have high momentum,
#        high vol, and low liquidity simultaneously. Without neutralization,
#        factors simply pick small caps rather than true alpha signals.
# =============================================================================

print("\n[§2] 因子预处理 / Factor Preprocessing...")


def winsorize_cross_section(factor_df: pd.DataFrame, n_std: float = 3.0) -> pd.DataFrame:
    """
    截面去极值 / Cross-sectional winsorization.

    在每个日期（行），将超出 n_std 个标准差的值截断到边界。
    At each date (row), clip values that are more than n_std std devs from the mean.

    向量化实现，逐行 clip / Vectorized via per-row clip applied with apply.
    """
    mu  = factor_df.mean(axis=1)    # 每日截面均值 / daily cross-sectional mean
    sig = factor_df.std(axis=1)     # 每日截面标准差 / daily cross-sectional std
    lower = (mu - n_std * sig).values[:, np.newaxis]   # 广播形状 / broadcast shape
    upper = (mu + n_std * sig).values[:, np.newaxis]
    clipped = np.clip(factor_df.values, lower, upper)
    return pd.DataFrame(clipped, index=factor_df.index, columns=factor_df.columns)


def zscore_cross_section(factor_df: pd.DataFrame) -> pd.DataFrame:
    """
    截面 Z-score 标准化 / Cross-sectional Z-score normalization.

    使每个时间截面的因子均值=0, 标准差=1。
    Makes each time cross-section have mean=0 and std=1.
    """
    mu  = factor_df.mean(axis=1)    # 每日截面均值 (N_DAYS,)
    sig = factor_df.std(axis=1)     # 每日截面标准差 (N_DAYS,)
    # sub/div 沿行方向广播 / broadcast along rows
    return factor_df.sub(mu, axis=0).div(sig.replace(0, np.nan), axis=0)


def neutralize_mktcap(factor_df: pd.DataFrame, log_mktcap: pd.DataFrame) -> pd.DataFrame:
    """
    市值中性化 / Market cap neutralization via cross-sectional OLS.

    在每个截面，用 OLS 将因子对 log(市值) 回归，取残差作为中性化因子。
    At each cross-section, regress factor on log(mktcap) via OLS; keep residuals.

    residual_i = factor_i - (intercept + slope × log_mktcap_i)
    """
    # 对齐列顺序 / Align columns
    y_arr = factor_df.values.copy()        # (N_DAYS, N_STOCKS)
    x_arr = log_mktcap.reindex(columns=factor_df.columns).values  # (N_DAYS, N_STOCKS)

    for t in range(len(factor_df)):
        y = y_arr[t]
        x = x_arr[t]
        mask = ~(np.isnan(y) | np.isnan(x))
        if mask.sum() < 10:
            continue
        y_c, x_c = y[mask], x[mask]
        # 手动 OLS / manual OLS (faster than scipy on small arrays)
        xm, ym = x_c.mean(), y_c.mean()
        denom  = np.dot(x_c - xm, x_c - xm)
        if denom < 1e-12:
            continue
        slope     = np.dot(x_c - xm, y_c - ym) / denom
        intercept = ym - slope * xm
        y_arr[t][mask] = y_c - (intercept + slope * x_c)   # 残差 / residuals

    return pd.DataFrame(y_arr, index=factor_df.index, columns=factor_df.columns)


# 对所有因子应用预处理流水线 / Apply preprocessing pipeline to all factors
FACTORS_PROCESSED = {}
for name, factor in FACTORS.items():
    step1 = winsorize_cross_section(factor)          # ① 去极值
    step2 = zscore_cross_section(step1)              # ② Z-score
    step3 = neutralize_mktcap(step2, log_mktcap_df) # ③ 市值中性化
    FACTORS_PROCESSED[name] = step3
    print(f"     ✓ {name:5s}: 去极值 → Z-score → 市值中性化 完成")

print("     预处理完成 / Preprocessing complete.")


# =============================================================================
# §3  IC 分析  Information Coefficient Analysis
# -----------------------------------------------------------------------------
# IC (Information Coefficient 信息系数) 是衡量因子预测能力的黄金标准。
# IC is the gold standard for measuring a factor's predictive power.
#
# 定义 / Definition:
#   IC_t = Spearman_Rank_Correlation( factor_{t}, forward_return_{t+H} )
#
# 其中:
#   factor_{t}          = 第 t 日截面因子值（50只股票，每只一个数）
#   forward_return_{t+H} = 从第 t 日起持有 H 日的未来收益（未来数据！）
#   H = 21 交易日 ≈ 1 个月（最常用的持有期）
#
# 用 Spearman 秩相关而非 Pearson 线性相关的原因:
#   ① 对极端值鲁棒 (robust to outliers)
#   ② 衡量单调关系（不需要线性）(measures monotonic, not necessarily linear, relationship)
#
# 为什么用 Spearman rank correlation instead of Pearson?
#   ① Robust to outliers
#   ② Measures monotonic relationship (no linearity assumption needed)
#
# IC 评价标准 / IC benchmark:
#   |IC均值|  > 0.05  → 因子有效 / factor is effective
#   |IC均值|  > 0.10  → 因子强 / factor is strong
#   ICIR      > 0.50  → 因子稳定 / factor is stable
#   IC>0 比率 > 55%   → 方向一致性好 / directionally consistent
#
# ICIR (IC Information Ratio):
#   ICIR = IC均值 / IC标准差 = IC_mean / IC_std
#   类似夏普比率，衡量"每单位波动贡献多少稳定的预测能力"
#   Similar to Sharpe ratio — measures how much stable predictive power per unit of volatility
# =============================================================================

print("\n[§3] IC 分析 / IC Analysis...")

FORWARD_PERIOD = 21    # 持有期 H = 21 日 ≈ 1 个月 / holding period ≈ 1 month


def compute_ic_series(
    factor_df: pd.DataFrame,
    forward_ret_df: pd.DataFrame,
    holding_period: int = 21,
) -> pd.Series:
    """
    计算因子的 IC 时间序列。
    Compute the IC time series for a factor.

    在每个日期 t，计算截面 Spearman 相关系数：
    At each date t, compute cross-sectional Spearman rank correlation:
      IC_t = Spearmanr( factor[t, all_stocks], forward_return[t+H, all_stocks] )

    Parameters:
        factor_df      : 预处理后的因子值 (date × stock)
        forward_ret_df : 收益率 DataFrame (date × stock)
        holding_period : 持有期（交易日）/ holding period in days
    Returns:
        ic_series : IC 值时间序列 / IC time series indexed by date
    """
    # shift(-H): 把 t+H 的收益值移到第 t 行，使得 fwd_returns.loc[t] = return from t to t+H
    # shift(-H) aligns so that fwd_returns.loc[t] = the return earned from t to t+H
    fwd_returns = forward_ret_df.shift(-holding_period)

    ic_values, ic_dates = [], []
    for date in factor_df.index[:-holding_period]:  # 最后 H 行无未来收益 / no future return
        f_row = factor_df.loc[date].dropna()
        r_row = fwd_returns.loc[date, f_row.index].dropna()
        common = f_row.index.intersection(r_row.index)
        if len(common) < 10:   # 至少 10 只股票 / need at least 10 stocks
            continue
        ic, _ = stats.spearmanr(f_row[common].values, r_row[common].values)
        ic_values.append(ic)
        ic_dates.append(date)

    return pd.Series(ic_values, index=ic_dates, name="IC")


# 逐因子计算 IC 序列 / Compute IC series for each factor
ic_series_dict = {}
for name, factor in FACTORS_PROCESSED.items():
    ic_series_dict[name] = compute_ic_series(factor, simple_ret_df, FORWARD_PERIOD)

# ── IC 汇总统计表 / IC Summary Statistics Table ───────────────────────────────
print(f"\n     {'因子':8s}  {'IC均值':>8s}  {'IC标准差':>9s}  {'ICIR':>8s}  {'IC>0比率':>9s}  评级")
print(f"     " + "─" * 64)

ic_stats = {}
for name, ic_s in ic_series_dict.items():
    ic_mean       = ic_s.mean()
    ic_std        = ic_s.std()
    icir          = ic_mean / ic_std if ic_std > 0 else 0.0
    positive_rate = (ic_s > 0).mean()
    ic_stats[name] = {
        "IC Mean"      : ic_mean,
        "IC Std"       : ic_std,
        "ICIR"         : icir,
        "Positive Rate": positive_rate,
    }
    # 评级: ★★=强, ★=有效, ○=弱 / rating
    if abs(ic_mean) > 0.08:
        rating = "★★ 强 Strong"
    elif abs(ic_mean) > 0.04:
        rating = "★  有效 Effective"
    else:
        rating = "○  弱 Weak"
    print(f"     {name:8s}  {ic_mean:+8.4f}  {ic_std:9.4f}  {icir:+8.4f}  {positive_rate:9.1%}  {rating}")

print(f"     " + "─" * 64)
print(f"     评级标准: |IC均值|>0.08 ★★强  |IC均值|>0.04 ★有效  ICIR>0.5 稳定")


# =============================================================================
# §4  分层回测  Quantile Return Analysis
# -----------------------------------------------------------------------------
# 分层回测 (Quantile Analysis / Bucket Test) 是验证因子有效性的经典直观方法。
# Quantile analysis is the classic, intuitive way to validate a factor.
#
# 操作步骤 / Procedure:
# ① 在每个调仓日 T，按因子值将全部股票从小到大排序
# ② 等分为 Q 组（常用 Q=5 五分位）
# ③ 每组各构建等权组合 (equal-weight portfolio)，持有到下次调仓日 T+21
# ④ 记录每组收益，重复直到回测结束
#
# ① On each rebalance date T, rank all stocks by factor value
# ② Split into Q equal-sized buckets (usually Q=5 quintiles)
# ③ Each bucket forms an equal-weight portfolio, held until next rebalance T+21
# ④ Record each bucket's return, repeat until end of backtest
#
# 期望结果 / Expected result:
#   Q5 累积收益 > Q4 > Q3 > Q2 > Q1
#   即因子值越高，未来收益越高 —— 这是因子有效的最直观证明
#   Higher factor value → higher future return = most intuitive proof of efficacy
# =============================================================================

print("\n[§4] 分层回测 / Quantile Return Analysis...")

N_QUANTILES  = 5    # 五分位 / quintiles
REBAL_PERIOD = 21   # 月度调仓 / monthly rebalancing

# 选用 ICIR 最高的因子做分层演示 / Use factor with highest |ICIR| for demo
best_factor_name = max(ic_stats, key=lambda n: abs(ic_stats[n]["ICIR"]))
best_factor      = FACTORS_PROCESSED[best_factor_name]
print(f"     最强因子 (Best Factor): {best_factor_name}  ICIR={ic_stats[best_factor_name]['ICIR']:+.3f}")


def compute_quantile_returns(
    factor_df: pd.DataFrame,
    price_df: pd.DataFrame,
    n_quantiles: int = 5,
    rebal_period: int = 21,
) -> pd.DataFrame:
    """
    分层回测：每月调仓，计算各五分位等权组合的期间收益。
    Quintile backtest: monthly rebalancing, equal-weight portfolio returns per bucket.

    Returns:
        DataFrame, columns=[Q1..Q5], index=rebalance dates,
        values=equal-weight return for that holding period
    """
    # 调仓日序列 / Rebalance date sequence
    rebal_dates = price_df.index[::rebal_period]

    bucket_returns = {f"Q{q}": [] for q in range(1, n_quantiles + 1)}
    period_dates   = []

    for i in range(len(rebal_dates) - 1):
        t0 = rebal_dates[i]       # 建仓日 / entry date
        t1 = rebal_dates[i + 1]   # 平仓日 / exit date

        f_today = factor_df.loc[t0].dropna()
        if len(f_today) < n_quantiles * 3:   # 股票太少则跳过 / skip if too few stocks
            continue

        # pd.qcut 按因子值等分为 Q 组 / split into Q equal-sized bins
        labels = pd.qcut(
            f_today.rank(method='first'),   # 先按秩排名（避免重复值问题）
            n_quantiles,
            labels=[f"Q{q}" for q in range(1, n_quantiles + 1)]
        )

        # 每组的持有期收益（等权平均）/ equal-weight return per group
        p0       = price_df.loc[t0]
        p1       = price_df.loc[t1]
        hold_ret = p1 / p0 - 1.0

        for q in range(1, n_quantiles + 1):
            stocks_in_q = labels[labels == f"Q{q}"].index
            bucket_returns[f"Q{q}"].append(hold_ret[stocks_in_q].mean())

        period_dates.append(t0)

    return pd.DataFrame(bucket_returns, index=period_dates)


quantile_rets   = compute_quantile_returns(best_factor, prices_df, N_QUANTILES, REBAL_PERIOD)
quantile_cumret = (1 + quantile_rets).cumprod()   # 累积净值 / cumulative NAV

# 多空价差 (Long-Short Spread): Q5（多头）- Q1（空头）
ls_spread      = quantile_rets["Q5"] - quantile_rets["Q1"]
ls_cumret      = (1 + ls_spread).cumprod()

periods_per_year = 252.0 / REBAL_PERIOD

print(f"\n     {'分组':8s}  {'年化收益':>10s}  {'累积收益':>10s}")
print(f"     " + "─" * 40)
for q in range(1, N_QUANTILES + 1):
    ret_s   = quantile_rets[f"Q{q}"]
    ann_ret = (1 + ret_s.mean()) ** periods_per_year - 1
    cum_ret = quantile_cumret[f"Q{q}"].iloc[-1] - 1
    print(f"     Q{q}       {ann_ret:+10.2%}  {cum_ret:+10.2%}")
ann_ls = (1 + ls_spread.mean()) ** periods_per_year - 1
print(f"     L-S(Q5-Q1) {ann_ls:+9.2%}  {ls_cumret.iloc[-1]-1:+10.2%}")
print(f"     " + "─" * 40)


# =============================================================================
# §5  因子合成  Factor Combination
# -----------------------------------------------------------------------------
# 单个因子的预测能力有限（IC 通常只有 0.05~0.10），因子合成可以：
# Individual factors have limited predictive power (IC ≈ 0.05~0.10).
# Factor combination can:
#
#   ① 提升综合 ICIR（多个信号互补，减少因子特定噪音）
#      Improve composite ICIR (signals complement each other, reduce factor noise)
#   ② 覆盖更多 Alpha 来源（动量+质量+流动性的综合）
#      Cover more alpha sources (momentum + quality + liquidity combined)
#   ③ 降低单因子的"失效期"风险（某个风格因子可能在某段时间失效）
#      Reduce regime risk (individual factors can fail in certain market regimes)
#
# 方法1: 等权合成 (Equal-Weight Composite)
#   composite_EQ = (z_1 + z_2 + … + z_n) / n
#   优点: 简单、稳健，不依赖历史 IC 估计  缺点: 不区分因子强弱
#   Pro: simple, robust  Con: treats all factors equally regardless of efficacy
#
# 方法2: IC 加权合成 (IC-Weighted Composite)
#   w_i = max(IC_mean_i, 0) / Σ max(IC_mean_j, 0)    (只给正 IC 因子分配权重)
#   composite_ICW = Σ w_i × z_i
#   优点: 给更强的因子更高权重  缺点: 历史 IC 可能不稳定（过拟合风险）
#   Pro: higher weight to stronger factors  Con: historical IC may be unstable (overfitting)
# =============================================================================

print("\n[§5] 因子合成 / Factor Combination...")

# 只合成正 IC 的因子（负 IC 因子方向混乱，不宜纳入）
# Only combine factors with positive IC (negative IC factors are directionally inconsistent)
positive_ic_factors = {
    name: f for name, f in FACTORS_PROCESSED.items()
    if ic_stats[name]["IC Mean"] > 0
}
print(f"     IC均值为正的因子: {list(positive_ic_factors.keys())}")

# ── 等权合成 / Equal-Weight Composite ─────────────────────────────────────────
composite_eq = sum(
    f.reindex(columns=symbols).fillna(0)
    for f in positive_ic_factors.values()
) / len(positive_ic_factors)

# ── IC 加权合成 / IC-Weighted Composite ───────────────────────────────────────
ic_weights_raw = {n: max(ic_stats[n]["IC Mean"], 0) for n in positive_ic_factors}
total_w        = sum(ic_weights_raw.values())
ic_weights     = {n: w / total_w for n, w in ic_weights_raw.items()}

composite_icw = sum(
    ic_weights[n] * f.reindex(columns=symbols).fillna(0)
    for n, f in positive_ic_factors.items()
)

# 计算合成因子的 IC / Evaluate composite factor IC
ic_eq  = compute_ic_series(composite_eq,  simple_ret_df, FORWARD_PERIOD)
ic_icw = compute_ic_series(composite_icw, simple_ret_df, FORWARD_PERIOD)

print(f"\n     IC 权重分配 / IC Weights:")
for name, w in ic_weights.items():
    print(f"       {name}: {w:.3f}")

print(f"\n     {'合成方法':22s}  {'IC均值':>8s}  {'ICIR':>8s}")
print(f"     " + "─" * 45)
print(f"     {'等权 Equal-Weight':22s}  {ic_eq.mean():+8.4f}  {ic_eq.mean()/ic_eq.std():+8.4f}")
print(f"     {'IC加权 IC-Weighted':22s}  {ic_icw.mean():+8.4f}  {ic_icw.mean()/ic_icw.std():+8.4f}")


# =============================================================================
# §6  多空组合  Long-Short Portfolio
# -----------------------------------------------------------------------------
# 多空组合 (Long-Short Portfolio) 是因子策略的实际收益实现形式。
# A long-short portfolio is how a factor strategy generates actual P&L.
#
# 构建逻辑 / Construction logic:
#   做多 (Long)  = 买入因子分最高的 Top 20% 股票（预期跑赢，做多享受上涨）
#   做空 (Short) = 卖空因子分最低的 Bottom 20% 股票（预期跑输，做空赚下跌）
#   多空价差     = 多头组合收益 - 空头组合收益（无论市场涨跌都能赚钱）
#
#   Long  = Buy top 20% stocks by factor score (expected to outperform)
#   Short = Sell short bottom 20% stocks (expected to underperform)
#   L-S   = Long return − Short return (market-neutral, profits in any market)
#
# 注意 A 股限制 / A-share restriction:
#   A 股融券做空受到严格限制，本演示仅作为量化研究的理论演示。
#   在港股、美股等市场中，做空非常普遍。
#   Short selling in A-shares is heavily restricted. This demo is theoretical.
#   Short selling is common and practical in HK/US markets.
# =============================================================================

print("\n[§6] 多空组合 / Long-Short Portfolio...")

TOP_PCTILE = 0.20   # 前后 20% / top & bottom 20%


def compute_long_short_portfolio(
    factor_df: pd.DataFrame,
    price_df: pd.DataFrame,
    top_pct: float = 0.20,
    rebal_period: int = 21,
) -> dict:
    """
    构建多空组合，计算每个持仓期的多头、空头、多空价差收益。
    Build a long-short portfolio; compute per-period long, short, L-S returns.

    Returns: dict with 'long', 'short', 'long_short' keys, each a pd.Series.
    """
    rebal_dates = price_df.index[::rebal_period]
    long_rets, short_rets, ls_rets, ret_dates = [], [], [], []

    for i in range(len(rebal_dates) - 1):
        t0, t1 = rebal_dates[i], rebal_dates[i + 1]
        f_today = factor_df.loc[t0].dropna()
        if len(f_today) < 10:
            continue
        n_top = max(1, int(len(f_today) * top_pct))

        long_stocks  = f_today.nlargest(n_top).index    # Top 20% → 买入
        short_stocks = f_today.nsmallest(n_top).index   # Bottom 20% → 卖空

        p0  = price_df.loc[t0]
        p1  = price_df.loc[t1]
        ret = p1 / p0 - 1.0

        lr = ret[long_stocks].mean()
        sr = ret[short_stocks].mean()
        long_rets.append(lr)
        short_rets.append(sr)
        ls_rets.append(lr - sr)   # 多空价差 / long-short spread
        ret_dates.append(t0)

    return {
        "long"       : pd.Series(long_rets,  index=ret_dates, name="Long"),
        "short"      : pd.Series(short_rets, index=ret_dates, name="Short"),
        "long_short" : pd.Series(ls_rets,    index=ret_dates, name="L-S"),
    }


def compute_max_drawdown(returns: pd.Series) -> float:
    """最大回撤 / Maximum Drawdown"""
    cum  = (1 + returns).cumprod()
    peak = cum.cummax()
    return ((cum - peak) / peak).min()


def compute_sharpe(returns: pd.Series, ann_factor: float) -> float:
    """夏普比率 / Annualized Sharpe Ratio"""
    return returns.mean() / returns.std() * np.sqrt(ann_factor) if returns.std() > 0 else 0.0


ls_result = compute_long_short_portfolio(composite_icw, prices_df, TOP_PCTILE, REBAL_PERIOD)
ls_equity  = {k: (1 + v).cumprod() for k, v in ls_result.items()}

ann_factor = periods_per_year
print(f"\n     {'组合':14s}  {'年化收益':>10s}  {'夏普比率':>10s}  {'最大回撤':>10s}")
print(f"     " + "─" * 54)
for name, rets in ls_result.items():
    ann_ret = (1 + rets.mean()) ** ann_factor - 1
    sharpe  = compute_sharpe(rets, ann_factor)
    mdd     = compute_max_drawdown(rets)
    print(f"     {name:14s}  {ann_ret:+10.2%}  {sharpe:+10.3f}  {mdd:+10.2%}")
print(f"     " + "─" * 54)


# =============================================================================
# §7  因子衰减分析  Factor Decay Analysis
# -----------------------------------------------------------------------------
# 因子衰减 (Factor Decay) 描述了因子预测能力随持有期增加而减弱的规律：
# Factor decay describes how predictive power weakens as holding period grows:
#
#   短持有期 H=1D:   IC 最高（信号最新鲜，预测力最强）
#   H 增大:          IC 逐渐下降（信号被市场消化，噪音积累）
#   H=63D（1季度）: IC 接近 0（信号已基本失效）
#
# 实践意义 / Practical implications:
#   ┌──────────────────┬──────────────────────────────────────────────┐
#   │ 衰减类型         │ 适合持仓频率                                   │
#   ├──────────────────┼──────────────────────────────────────────────┤
#   │ 快速衰减 (Fast)  │ 日频或周频换仓（换手率高，成本高！）          │
#   │ 慢速衰减 (Slow)  │ 月频或季频换仓（成本效率更高，适合实盘）     │
#   └──────────────────┴──────────────────────────────────────────────┘
# =============================================================================

print("\n[§7] 因子衰减分析 / Factor Decay Analysis...")

DECAY_HORIZONS = [1, 5, 10, 21, 42, 63]    # 持有期（交易日）/ holding periods (days)

decay_results = {}
for fname in [best_factor_name, "REV"]:     # 对比 "慢衰减" vs "快衰减" 因子
    horizon_ics = []
    for h in DECAY_HORIZONS:
        ic_h = compute_ic_series(FACTORS_PROCESSED[fname], simple_ret_df, h)
        horizon_ics.append(ic_h.mean())
    decay_results[fname] = horizon_ics
    ic_str = "  ".join([f"H={h}D: {v:+.4f}" for h, v in zip(DECAY_HORIZONS, horizon_ics)])
    print(f"     {fname}: {ic_str}")


# =============================================================================
# §8  可视化  Visualization
# =============================================================================

print("\n[§8] 生成可视化图表 / Generating visualization...")

pct_fmt = FuncFormatter(lambda x, _: f"{x:.0%}")

fig = plt.figure(figsize=(20, 24))
fig.suptitle(
    "Alpha 因子研究演示  |  Alpha Factor Research Demo\n"
    "50只合成股票 × 3年日线数据  |  50 Synthetic Stocks × 3-Year Daily Data",
    fontsize=14, fontweight='bold', y=0.99
)
gs = gridspec.GridSpec(4, 3, figure=fig, hspace=0.50, wspace=0.35)

# ── Panel 1: 股票池价格（归一化）/ Universe Prices (Normalized) ──────────────
ax1 = fig.add_subplot(gs[0, 0])
norm_prices = prices_df / prices_df.iloc[0]   # 归一化 / normalize to 1
for sym in symbols[:12]:
    ax1.plot(dates, norm_prices[sym], alpha=0.35, linewidth=0.7, color='#1f77b4')
eq_index = norm_prices.mean(axis=1)           # 等权指数 / equal-weight index
ax1.plot(dates, eq_index, color='black', linewidth=2, label='等权指数 EW Index', zorder=5)
ax1.axhline(1, color='gray', linewidth=0.6, linestyle='--')
ax1.set_title("股票池价格（归一化）\nUniverse Prices (Normalized)", fontsize=10)
ax1.set_ylabel("归一化价格")
ax1.legend(fontsize=8)
ax1.tick_params(axis='x', rotation=25, labelsize=8)
ax1.xaxis.set_major_locator(plt.MaxNLocator(4))

# ── Panel 2: IC 时间序列（5因子）/ IC Time Series ─────────────────────────────
ax2 = fig.add_subplot(gs[0, 1:])
ic_colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd']
for (name, ic_s), color in zip(ic_series_dict.items(), ic_colors):
    rolling_ic = ic_s.rolling(20, min_periods=5).mean()   # 20日滚动均值平滑
    ax2.plot(ic_s.index, rolling_ic, label=name, color=color, linewidth=1.4)
ax2.axhline(0,    color='black', linewidth=0.8, linestyle='--')
ax2.axhline(0.05, color='green', linewidth=0.8, linestyle=':', alpha=0.7)
ax2.axhline(-0.05, color='red',  linewidth=0.8, linestyle=':', alpha=0.7)
ax2.set_title("因子 IC 时间序列（20日滚动均值）\nFactor IC Time Series (20D Rolling Mean)", fontsize=10)
ax2.set_ylabel("IC")
ax2.legend(loc='upper right', ncol=5, fontsize=8)
ax2.tick_params(axis='x', rotation=25, labelsize=8)
ax2.xaxis.set_major_locator(plt.MaxNLocator(5))

# ── Panel 3: IC 均值 & 误差棒 / IC Mean Bar Chart ─────────────────────────────
ax3 = fig.add_subplot(gs[1, 0])
names_list = list(ic_stats.keys())
ic_means   = [ic_stats[n]["IC Mean"] for n in names_list]
ic_stds    = [ic_stats[n]["IC Std"]  for n in names_list]
bar_colors3 = ['#2ca02c' if v > 0 else '#d62728' for v in ic_means]
ax3.bar(names_list, ic_means, yerr=ic_stds, capsize=5, color=bar_colors3,
        alpha=0.8, error_kw={'linewidth': 1.5, 'ecolor': 'black'})
ax3.axhline(0,    color='black', linewidth=0.8)
ax3.axhline(0.05, color='green', linewidth=1.2, linestyle='--', alpha=0.7)
ax3.axhline(-0.05, color='red', linewidth=1.2, linestyle='--', alpha=0.7)
ax3.set_title("因子 IC 均值（误差棒=±1σ）\nIC Mean ± 1σ", fontsize=10)
ax3.set_ylabel("IC 均值")
ax3.tick_params(axis='x', labelsize=9)

# ── Panel 4: ICIR 柱状图 / ICIR Bar Chart ──────────────────────────────────────
ax4 = fig.add_subplot(gs[1, 1])
icirs      = [ic_stats[n]["ICIR"] for n in names_list]
bar_colors4 = ['#2ca02c' if v > 0 else '#d62728' for v in icirs]
ax4.bar(names_list, icirs, color=bar_colors4, alpha=0.8)
ax4.axhline(0,   color='black', linewidth=0.8)
ax4.axhline(0.5, color='green', linewidth=1.2, linestyle='--', alpha=0.7, label='ICIR=0.5')
ax4.axhline(-0.5, color='red', linewidth=1.2, linestyle='--', alpha=0.7)
ax4.set_title("因子 ICIR\nFactor ICIR (Mean IC / Std IC)", fontsize=10)
ax4.set_ylabel("ICIR")
ax4.legend(fontsize=8)
ax4.tick_params(axis='x', labelsize=9)

# ── Panel 5: 因子相关矩阵 / Factor Correlation Matrix ─────────────────────────
ax5 = fig.add_subplot(gs[1, 2])
# 用各因子的截面均值时序来衡量因子间相关性 / Use cross-sectional mean timeseries
factor_ts   = pd.DataFrame({n: f.mean(axis=1) for n, f in FACTORS_PROCESSED.items()})
corr_matrix = factor_ts.corr()
im5 = ax5.imshow(corr_matrix.values, cmap='RdYlGn', vmin=-1, vmax=1, aspect='auto')
ax5.set_xticks(range(len(names_list)))
ax5.set_yticks(range(len(names_list)))
ax5.set_xticklabels(names_list, fontsize=9)
ax5.set_yticklabels(names_list, fontsize=9)
for i in range(len(names_list)):
    for j in range(len(names_list)):
        ax5.text(j, i, f"{corr_matrix.values[i, j]:.2f}",
                 ha='center', va='center', fontsize=8,
                 color='black' if abs(corr_matrix.values[i, j]) < 0.6 else 'white')
plt.colorbar(im5, ax=ax5)
ax5.set_title("因子相关矩阵\nFactor Correlation Matrix", fontsize=10)

# ── Panel 6: 分层回测累积净值 / Quantile Cumulative Returns ───────────────────
ax6 = fig.add_subplot(gs[2, 0])
q_colors = plt.cm.RdYlGn(np.linspace(0.05, 0.95, N_QUANTILES))
for q in range(1, N_QUANTILES + 1):
    ax6.plot(quantile_cumret.index, quantile_cumret[f"Q{q}"],
             label=f"Q{q}", color=q_colors[q - 1], linewidth=1.5)
ax6.plot(ls_cumret.index, ls_cumret, label='L-S', color='black', linewidth=2, linestyle='--')
ax6.axhline(1, color='gray', linewidth=0.6, linestyle=':')
ax6.set_title(f"分层回测（{best_factor_name}）\nQuantile Returns ({best_factor_name})", fontsize=10)
ax6.set_ylabel("累积净值")
ax6.legend(ncol=3, fontsize=8)
ax6.yaxis.set_major_formatter(FuncFormatter(lambda x, _: f"{x:.1f}x"))
ax6.tick_params(axis='x', rotation=25, labelsize=8)
ax6.xaxis.set_major_locator(plt.MaxNLocator(4))

# ── Panel 7: 分位年化收益柱状图 / Quintile Annualized Return Bar ───────────────
ax7 = fig.add_subplot(gs[2, 1])
ann_q_rets = [(1 + quantile_rets[f"Q{q}"].mean()) ** periods_per_year - 1
              for q in range(1, N_QUANTILES + 1)]
bar_colors7 = plt.cm.RdYlGn(np.linspace(0.05, 0.95, N_QUANTILES))
bars7 = ax7.bar([f"Q{q}" for q in range(1, N_QUANTILES + 1)], ann_q_rets, color=bar_colors7)
ax7.axhline(0, color='black', linewidth=0.8)
ax7.set_title("各分位组年化收益\nAnnualized Return per Quintile", fontsize=10)
ax7.set_ylabel("年化收益 / Ann. Return")
ax7.yaxis.set_major_formatter(pct_fmt)
ax7.tick_params(axis='x', labelsize=9)
for bar, val in zip(bars7, ann_q_rets):
    ax7.text(bar.get_x() + bar.get_width() / 2, val,
             f"{val:.1%}", ha='center',
             va='bottom' if val >= 0 else 'top', fontsize=8)

# ── Panel 8: 多空组合净值曲线 / Long-Short Equity Curve ───────────────────────
ax8 = fig.add_subplot(gs[2, 2])
ax8.plot(ls_equity['long_short'].index, ls_equity['long_short'].values,
         color='purple', linewidth=2, label='多空 L-S')
ax8.plot(ls_equity['long'].index, ls_equity['long'].values,
         color='#2ca02c', linewidth=1.5, linestyle='--', label='多头 Long', alpha=0.8)
ax8.plot(ls_equity['short'].index, ls_equity['short'].values,
         color='#d62728', linewidth=1.5, linestyle='--', label='空头 Short', alpha=0.8)
ax8.axhline(1, color='gray', linewidth=0.6, linestyle=':')
ax8.set_title("多空组合净值曲线\nLong-Short Portfolio NAV", fontsize=10)
ax8.set_ylabel("净值 / NAV")
ax8.legend(fontsize=8)
ax8.yaxis.set_major_formatter(FuncFormatter(lambda x, _: f"{x:.1f}x"))
ax8.tick_params(axis='x', rotation=25, labelsize=8)
ax8.xaxis.set_major_locator(plt.MaxNLocator(4))

# ── Panel 9: 因子衰减曲线 / Factor Decay Curve ────────────────────────────────
ax9 = fig.add_subplot(gs[3, :])
decay_colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728']
horizon_labels = [f"{h}D" for h in DECAY_HORIZONS]
for (fname, decay_ics), color in zip(decay_results.items(), decay_colors):
    ax9.plot(range(len(DECAY_HORIZONS)), decay_ics,
             'o-', label=fname, color=color, linewidth=2, markersize=7)
    for i, (h, v) in enumerate(zip(DECAY_HORIZONS, decay_ics)):
        ax9.annotate(f"{v:+.3f}", (i, v), textcoords="offset points",
                     xytext=(0, 8), ha='center', fontsize=8, color=color)
ax9.axhline(0,    color='black', linewidth=0.8, linestyle='--')
ax9.axhline(0.05, color='green', linewidth=0.8, linestyle=':', alpha=0.7, label='IC=0.05 参考线')
ax9.set_title(
    "因子衰减曲线  Factor Decay Curve\n"
    "IC 均值随持有期延长的衰减  |  How Mean IC Decays with Longer Holding Period",
    fontsize=10
)
ax9.set_xlabel("持有期 / Holding Period")
ax9.set_ylabel("IC 均值 / Mean IC")
ax9.set_xticks(range(len(DECAY_HORIZONS)))
ax9.set_xticklabels(horizon_labels)
ax9.legend(fontsize=9, ncol=4)
ax9.grid(True, alpha=0.3)
ax9.set_xlim(-0.3, len(DECAY_HORIZONS) - 0.7)

plt.savefig("alpha_factor_demo.png", dpi=150, bbox_inches='tight')
print(f"     图表已保存: alpha_factor_demo.png / Chart saved: alpha_factor_demo.png")

print("\n" + "=" * 70)
print("  演示完成！ Demo Complete!")
print("  输出: alpha_factor_demo.png")
print("=" * 70)
