{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "fcb72cd8",
   "metadata": {},
   "source": [
    "# 量化交易工作坊 · 任务五：AI 交易引擎——机器学习算法与场景应用\n",
    "\n",
    "**主题：基础分类型机器学习算法（逻辑回归 / 决策树 / 随机森林）**\n",
    "\n",
    "本 Notebook 完成以下工作：\n",
    "1. 解释**分类型机器学习算法**：逻辑回归、决策树、随机森林的思想与特点；\n",
    "2. 解释**模型评价指标**：混淆矩阵、AUC、ROC 曲线的概念与含义；\n",
    "3. Python 实现：加载分类数据（乳腺癌数据集 / 股票次日涨跌数据集）→ 划分训练集与测试集 → 训练多模型 → 评估并计算 AUC → 绘制 ROC 曲线与混淆矩阵；\n",
    "4. 对比不同数据集、不同模型的表现，体会机器学习在量化场景中的应用与局限。\n",
    "\n",
    "> 说明：本 Notebook 复用 `app/ml_lib.py` 中的机器学习引擎，仅需 `pandas / numpy / scikit-learn / matplotlib`。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "30353501",
   "metadata": {},
   "source": [
    "## 一、分类型机器学习算法\n",
    "\n",
    "分类（Classification）是监督学习的核心任务之一：给定一组带标签的样本 $(x, y)$，其中标签 $y$ 取自有限个离散类别（如 0/1、阴性/阳性），目标是训练一个模型 $f$，使它能对**未见过的样本** $x'$ 预测出正确的类别 $y'$。\n",
    "\n",
    "在量化交易中，许多问题都可建模为分类：明天涨还是跌（二分类）、某笔交易是否违约、某只股票是否属于某一风格板块等。下面介绍三种最基础、也最常被用做\"基线模型\"的分类算法。\n",
    "\n",
    "### 1. 逻辑回归（Logistic Regression）\n",
    "尽管名字带\"回归\"，逻辑回归其实是一个**线性分类器**。它先对特征做线性组合 $z = w_0 + w_1x_1 + \\dots + w_nx_n$，再通过 **Sigmoid 函数** 把结果压缩到 (0,1) 区间，解释为\"样本属于正类的概率\"：\n",
    "\n",
    "$$P(y=1 \\mid x) = \\sigma(z) = \\frac{1}{1+e^{-z}}$$\n",
    "\n",
    "- 训练时用**对数损失（Log Loss）** 做极大似然估计，得到参数 $w$；\n",
    "- 预测时以 0.5 为阈值（或可调节）：概率 ≥0.5 判为正类，否则为负类；\n",
    "- **优点**：结构简单、可解释性强（系数 $w$ 直接反映特征对\"涨/跌\"方向的影响）、训练快、作为基线极好；\n",
    "- **局限**：只能刻画特征与标签之间的**线性关系**，对非线性边界无能为力。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cce9a76d",
   "metadata": {},
   "source": [
    "### 2. 决策树（Decision Tree）\n",
    "决策树通过一系列\"if-then\"规则把特征空间递归切分，每到一个叶子节点就给出一个类别判断。其构建过程是**贪心**的：在每个节点选择能最大程度\"提纯\"样本类别的某一特征及其切分点（常用指标为基尼不纯度或信息增益）。\n",
    "\n",
    "- 例如判断\"次日是否上涨\"时，树可能先问\"近 5 日波动率是否大于某阈值？\"，再问\"量比是否高于 1.2？\"，层层递进；\n",
    "- **优点**：天然处理非线性、无需特征缩放、结果直观（可直接画出树）、能自动捕捉特征交互；\n",
    "- **局限**：单棵树**容易过拟合**（在训练集表现好但测试集差）、对数据微小扰动敏感、稳定性差。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7bc11185",
   "metadata": {},
   "source": [
    "### 3. 随机森林（Random Forest）\n",
    "随机森林是**集成学习（Ensemble）** 的代表：它训练大量（如 100 棵）决策树，每棵树只在**随机抽样的样本子集**与**随机选取的特征子集**上训练（双重随机性），最后用**多数投票**（分类）得到最终结果。\n",
    "\n",
    "- 这种\"集体决策\"显著降低了单棵树的方差与过拟合风险，通常比单棵决策树更稳健、泛化更好；\n",
    "- **优点**：精度高、对异常值与缺失值不敏感、能输出**特征重要性**（哪些特征对预测贡献最大）、几乎不需要调参；\n",
    "- **局限**：模型较大、可解释性弱于单棵树（但可用特征重要性部分弥补）、训练比单棵树慢。\n",
    "\n",
    "> 三者关系：**逻辑回归**是线性基线，**决策树**引入非线性但易过拟合，**随机森林**用集成把决策树的优点放大、缺点抑制——在结构化表格数据上，随机森林常常是最先尝试的强力模型。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "30a36b85",
   "metadata": {},
   "source": [
    "## 二、机器学习模型评价指标\n",
    "\n",
    "光看\"准确率\"远远不够。下面三个指标能更全面地刻画一个分类器，尤其是**类别不平衡**或**关注某类识别能力**时。\n",
    "\n",
    "### 1. 混淆矩阵（Confusion Matrix）\n",
    "以二分类为例，把预测结果与真实标签交叉统计，得到 2×2 矩阵：\n",
    "\n",
    "|  | 预测为正(1) | 预测为负(0) |\n",
    "|---|---|---|\n",
    "| **实际为正(1)** | TP（真正例） | FN（假负例，漏报） |\n",
    "| **实际为负(0)** | FP（假正例，误报） | TN（真负例） |\n",
    "\n",
    "由它可派生出：\n",
    "- **准确率 Accuracy** = (TP+TN) / 总数（整体正确率）；\n",
    "- **精确率 Precision** = TP / (TP+FP)（预测为阳性者中，真阳性的比例，衡量\"误报\"多少）；\n",
    "- **召回率 Recall** = TP / (TP+FN)（真实阳性者中，被正确找出多少，衡量\"漏报\"多少）；\n",
    "- **F1** = 2·Precision·Recall / (Precision+Recall)（二者的调和平均，综合考量）。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f71c1177",
   "metadata": {},
   "source": [
    "### 2. ROC 曲线与 AUC\n",
    "- **ROC（Receiver Operating Characteristic）曲线**以**假正率 FPR = FP/(FP+TN)** 为横轴、**真正率 TPR = TP/(TP+FN) = 召回率** 为纵轴。通过改变判定阈值（0.5 只是其中一个），把每一阈值对应的 (FPR, TPR) 点连成曲线。\n",
    "- 曲线越向**左上角**凸出，说明模型在\"尽量找出正例\"的同时\"尽量少误报\"，判别力越强；\n",
    "- 从左下到右上对角线的**随机猜测**基准（AUC=0.5）代表毫无判别力。\n",
    "- **AUC（Area Under Curve）** 即 ROC 曲线下的面积，取值范围 [0,1]：\n",
    "  - AUC = 0.5 → 与随机相当（无预测力）；\n",
    "  - 0.7~0.8 → 有可接受判别力；\n",
    "  - >0.9 → 判别力很强。\n",
    "- AUC 的**一大优点**是对类别不平衡不敏感、且综合了所有阈值下的表现，因此比单一阈值的准确率更适合衡量排序型/概率型分类器——在金融风控与涨跌预测中尤为常用。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "76c258e6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 环境准备：引入机器学习引擎与绘图\n",
    "import os, sys\n",
    "sys.path.insert(0, os.path.abspath(\"../app\"))   # ml_lib.py 位于 app/ 目录\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "from ml_lib import (load_dataset, prepare, train_one, evaluate_model, run_pipeline,\n",
    "                   make_roc_figure, make_cm_figure, make_importance_figure,\n",
    "                   MODEL_REGISTRY)\n",
    "\n",
    "print(\"可用模型:\", {k: v[0] for k, v in MODEL_REGISTRY.items()})\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "03d3f07b",
   "metadata": {},
   "source": [
    "## 三、Python 实现：从加载数据到绘制 ROC\n",
    "\n",
    "### 3.1 加载分类数据\n",
    "本任务提供两类数据：\n",
    "- **乳腺癌诊断数据集**（scikit-learn 内置）：569 个样本、30 个细胞核影像特征，标签 `target=1` 为良性、`0` 为恶性；\n",
    "- **股票次日涨跌数据集**（由资料区日线工程化）：以量价技术指标为特征，标签为\"次日收益率是否为正\"（1=涨，0=跌）。\n",
    "\n",
    "下面分别加载并查看基本信息。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3e6fd340",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 1) 加载乳腺癌数据集\n",
    "X_c, y_c, meta_c = load_dataset(kind=\"cancer\")\n",
    "print(\"数据集:\", meta_c[\"name\"])\n",
    "print(\"样本数:\", meta_c[\"n_samples\"], \" 特征数:\", meta_c[\"n_features\"])\n",
    "print(\"正类(良性/涨)数:\", meta_c[\"pos_count\"], \" 负类(恶性/跌)数:\", meta_c[\"neg_count\"])\n",
    "print(\"前 3 个特征名:\", meta_c[\"feature_names\"][:3])\n",
    "X_c.head(3)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5555f5c9",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 2) 加载股票次日涨跌数据集（以芯动联科为例）\n",
    "X_s, y_s, meta_s = load_dataset(kind=\"stock\", code=\"688582.SH\")\n",
    "print(\"数据集:\", meta_s[\"name\"])\n",
    "print(\"样本数:\", meta_s[\"n_samples\"], \" 特征数:\", meta_s[\"n_features\"])\n",
    "print(\"区间:\", meta_s.get(\"start_date\"), \"->\", meta_s.get(\"end_date\"))\n",
    "print(\"次日上涨数:\", meta_s[\"pos_count\"], \" 次日下跌数:\", meta_s[\"neg_count\"])\n",
    "print(\"特征:\", meta_s[\"feature_names\"])\n",
    "X_s.head(3)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "27c6779e",
   "metadata": {},
   "source": [
    "### 3.2 设定训练集 / 测试集划分\n",
    "用 `train_test_split` 按比例（默认 30% 作测试集）切分，并通过 `stratify=y` 保持正负样本比例一致；逻辑回归对特征尺度敏感，因此对特征做 **StandardScaler 标准化**（在训练集上拟合、再变换测试集，杜绝未来函数）。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b2e5fb31",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 3) 划分训练集与测试集（乳腺癌）\n",
    "models = (\"logistic\", \"tree\", \"forest\")\n",
    "split_c = prepare(X_c, y_c, test_size=0.3, random_state=42, scale=True)\n",
    "print(\"训练集样本:\", len(split_c[\"y_train\"]), \" 测试集样本:\", len(split_c[\"y_test\"]))\n",
    "print(\"训练集正类占比: %.3f\" % split_c[\"y_train\"].mean())\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6fec35a6",
   "metadata": {},
   "source": [
    "### 3.3 构建并训练分类模型\n",
    "分别训练逻辑回归、决策树、随机森林三个模型，并对测试集做预测与评估。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d0d18d4c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 4) 训练三个模型并评估（乳腺癌）\n",
    "results_c = []\n",
    "for mk in models:\n",
    "    model = train_one(mk, split_c[\"X_train\"], split_c[\"y_train\"])\n",
    "    ev = evaluate_model(model, split_c[\"X_test\"], split_c[\"y_test\"])\n",
    "    ev[\"name\"] = MODEL_REGISTRY[mk][0]\n",
    "    results_c.append(ev)\n",
    "\n",
    "print(\"========== 乳腺癌数据集 · 测试集评估结果 ==========\")\n",
    "for r in results_c:\n",
    "    print(f\"{r['name']:6s}  AUC={r['auc']:.3f}  准确率={r['accuracy']:.3f}  \"\n",
    "          f\"精确率={r['precision']:.3f}  召回率={r['recall']:.3f}  F1={r['f1']:.3f}\")\n",
    "    print(f\"        混淆矩阵 TP={r['cm'][1][1]} FP={r['cm'][0][1]} FN={r['cm'][1][0]} TN={r['cm'][0][0]}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "eab05313",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 5) 绘制 ROC 曲线（图1，多模型对比，乳腺癌）\n",
    "fig = make_roc_figure(results_c, title=\"图1  乳腺癌数据集 · 多模型 ROC 曲线\")\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "29969572",
   "metadata": {},
   "source": [
    "**图1 解读**：图中三条 ROC 曲线均明显位于随机基准（对角线）左上方，说明三个模型都能有效区分良/恶性肿瘤。其中逻辑回归与随机森林的 AUC 高达 0.99，决策树稍低（约 0.94，单棵树略有过拟合）。在结构化医学数据上，线性模型与树模型都能取得极佳判别力——这也说明乳腺癌数据的特征与标签之间存在很强的可学习结构。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "be22030f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 6) 绘制混淆矩阵（图2，以随机森林为例）\n",
    "best = max(results_c, key=lambda r: r[\"auc\"])\n",
    "fig = make_cm_figure(best[\"cm\"], best[\"name\"],\n",
    "    title=f\"图2  {best['name']} 混淆矩阵（乳腺癌·测试集）\")\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "257d680c",
   "metadata": {},
   "source": [
    "**图2 解读**：以表现最好的随机森林为例，测试集 170 例样本中，真正例 TP=102、真负例 TN=58，仅有 5 例假负例（FN，把恶性误判为良性）与 5 例假正例（FP）。在医疗诊断场景里，FN（漏诊恶性肿瘤）的代价极高，因此实践中往往通过**调低判定阈值**来抬高召回率、压低 FN——这正是 ROC 曲线能帮我们\"按代价选阈值\"的价值所在。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0e0bfd00",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 7) 绘制特征重要性（图3，随机森林）\n",
    "# 直接复用 run_pipeline 返回的特征重要性（随机森林）\n",
    "_, _, _, imp_c, _ = run_pipeline(kind=\"cancer\", models=models, test_size=0.3)\n",
    "importances = imp_c.get(\"forest\", [])\n",
    "fig = make_importance_figure(importances, \"随机森林\",\n",
    "    title=\"图3  乳腺癌数据集 · 随机森林特征重要性(Top)\")\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ddae385a",
   "metadata": {},
   "source": [
    "**图3 解读**：随机森林给出的特征重要性清晰地指向 `worst concave points`（最差凹点）、`worst area`（最差面积）、`worst perimeter`（最差周长）等\"worst 类\"几何特征——这些描述细胞核极端形态的指标，正是判别良/恶性肿瘤最关键的线索。特征重要性不仅帮助解释模型，也能反过来指导**特征工程与降维**。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4525d396",
   "metadata": {},
   "source": [
    "### 3.4 在股票数据上验证：涨跌方向的\"可预测性\"\n",
    "把同样的流程套用到\"股票次日涨跌\"任务上，能直观看到金融数据的另一面。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2fa7a35b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 8) 股票次日涨跌：训练 + 评估 + ROC（图4）\n",
    "split_s = prepare(X_s, y_s, test_size=0.3, random_state=42, scale=True)\n",
    "results_s = []\n",
    "for mk in models:\n",
    "    model = train_one(mk, split_s[\"X_train\"], split_s[\"y_train\"])\n",
    "    ev = evaluate_model(model, split_s[\"X_test\"], split_s[\"y_test\"])\n",
    "    ev[\"name\"] = MODEL_REGISTRY[mk][0]\n",
    "    results_s.append(ev)\n",
    "\n",
    "print(\"========== 股票次日涨跌 · 测试集评估结果 ==========\")\n",
    "for r in results_s:\n",
    "    print(f\"{r['name']:6s}  AUC={r['auc']:.3f}  准确率={r['accuracy']:.3f}  F1={r['f1']:.3f}\")\n",
    "\n",
    "fig = make_roc_figure(results_s, title=\"图4  股票次日涨跌 · 多模型 ROC 曲线\")\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "29aba569",
   "metadata": {},
   "source": [
    "**图4 解读**：在股票次日涨跌任务上，三个模型的 AUC 都**接近甚至低于 0.5**（随机森林仅约 0.39），ROC 曲线几乎贴着对角线甚至下凹。这揭示了一个重要事实：**用简单的量价技术指标去预测\"明天涨跌\"，几乎不具备可学习的预测力**。金融时间序列噪声大、有效信息被快速套利，使得\"方向预测\"成为业界公认的难题。这也正是机器学习在量化中更常见的用法——不是直接押注涨跌，而是做**概率化风控、截面排序、特征降维、异常检测**等辅助决策。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0935f0c9",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 9) 参数探索：不同测试集比例对乳腺癌模型 AUC 的影响\n",
    "import pandas as pd\n",
    "rows = []\n",
    "for ts in [0.2, 0.3, 0.4]:\n",
    "    sp = prepare(X_c, y_c, test_size=ts, random_state=42, scale=True)\n",
    "    for mk in models:\n",
    "        mdl = train_one(mk, sp[\"X_train\"], sp[\"y_train\"])\n",
    "        ev = evaluate_model(mdl, sp[\"X_test\"], sp[\"y_test\"])\n",
    "        rows.append({\"测试集比例\": ts, \"模型\": MODEL_REGISTRY[mk][0],\n",
    "                     \"AUC\": round(ev[\"auc\"],3), \"准确率\": round(ev[\"accuracy\"],3)})\n",
    "pd.DataFrame(rows)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ac00a465",
   "metadata": {},
   "source": [
    "## 四、总结与心得\n",
    "\n",
    "1. **算法选型**：逻辑回归是线性基线、可解释；决策树引入非线性但易过拟合；随机森林以集成换取稳健与特征重要性，是表格数据上的强力默认选择。三者 AUC 在结构化医学数据上均表现出色。\n",
    "2. **评价指标**：准确率在高不平衡场景会\"失真\"（全判阴性也能拿高准确率），必须结合**混淆矩阵、精确率/召回率、ROC 与 AUC** 综合判断；AUC 因对阈值与不平衡不敏感，是排序/概率型分类器的首选度量。\n",
    "3. **数据决定上限**：同样的算法，在乳腺癌数据上 AUC≈0.99，在股票涨跌数据上 AUC≈0.4——模型能力再强也受限于**特征中蕴含的信息量**。这提示量化中\"特征工程 > 调模型\"，也点明了机器学习在金融领域的现实边界。\n",
    "4. **量化应用方向**：与其执着于\"预测涨跌\"，不如把分类/排序模型用于**风险评分、违约预警、风格聚类、因子有效性筛选**等更稳健的场景，并始终以 AUC/回测/样本外检验严格把关，避免过拟合与幸存者偏差。\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
