From 45a554585b62786f1a79da6e209fd86e342b9e94 Mon Sep 17 00:00:00 2001 From: shellway-pc <413209390@qq.com> Date: Tue, 4 Aug 2026 21:38:46 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=B7=BB=E5=8A=A0=E4=BA=86=E5=9F=BA?= =?UTF-8?q?=E6=9C=AC=E7=AD=96=E7=95=A5=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- alpha/__pycache__/data_loader.cpython-310.pyc | Bin 0 -> 10108 bytes alpha/data_loader.py | 283 ++++++++++++++++ alpha/价值投资策略.ipynb | 296 ++++++++++++++++ alpha/低波动率策略.ipynb | 277 +++++++++++++++ alpha/动量策略.ipynb | 307 +++++++++++++++++ alpha/均线趋势策略.ipynb | 274 +++++++++++++++ alpha/多因子选股策略.ipynb | 320 ++++++++++++++++++ alpha/质量因子策略.ipynb | 298 ++++++++++++++++ 8 files changed, 2055 insertions(+) create mode 100644 alpha/__pycache__/data_loader.cpython-310.pyc create mode 100644 alpha/data_loader.py create mode 100644 alpha/价值投资策略.ipynb create mode 100644 alpha/低波动率策略.ipynb create mode 100644 alpha/动量策略.ipynb create mode 100644 alpha/均线趋势策略.ipynb create mode 100644 alpha/多因子选股策略.ipynb create mode 100644 alpha/质量因子策略.ipynb diff --git a/alpha/__pycache__/data_loader.cpython-310.pyc b/alpha/__pycache__/data_loader.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..80c1bf951e98aa2c7ae0ffb9e2cbe1bd133e6427 GIT binary patch literal 10108 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 literal 0 HcmV?d00001 diff --git a/alpha/data_loader.py b/alpha/data_loader.py new file mode 100644 index 0000000..3afe936 --- /dev/null +++ b/alpha/data_loader.py @@ -0,0 +1,283 @@ +""" +数据加载辅助模块 — 从量化数据库加载回测所需数据 + +支持的数据源: + - 日线行情 (daily + adj_factor → 后复权价格) + - 每日指标 (daily_basic → PE / PB / 市值 / 换手率) + - 财务指标 (fina_indicator → ROE / ROA / 毛利率) + - 指数行情 (index_daily → 基准收益) + +若数据库连接失败,自动回退到随机模拟数据,便于 Notebook 演示完整研究流程。 + +用法: + from data_loader import DataLoader + + loader = DataLoader() + price_data = loader.load_prices('2020-01-01', '2024-12-31') + pe_data = loader.load_factor('pe', '2020-01-01', '2024-12-31') + roe_data = loader.load_factor('roe', '2020-01-01', '2024-12-31') + bench = loader.load_benchmark('000300.SH', '2020-01-01', '2024-12-31') +""" +import sys +from pathlib import Path +from typing import Dict, List, Optional + +import numpy as np +import pandas as pd + +# 项目根目录 (quanxiel/) +_PROJECT_ROOT = Path(__file__).resolve().parent.parent + + +class DataLoader: + """量化数据库数据加载器(数据库不可用时自动回退到模拟数据)""" + + # 默认股票池(沪深300中代表性的蓝筹成分股) + DEFAULT_STOCKS: List[str] = [ + # 银行 / 非银 + "600000.SH", "600015.SH", "600016.SH", "600036.SH", "601166.SH", + "601288.SH", "601398.SH", "601318.SH", "601601.SH", "601628.SH", + # 消费 / 医药 + "600519.SH", "000858.SZ", "000651.SZ", "000333.SZ", "600887.SH", + "600276.SH", "601888.SH", + # 工业 / 制造 + "600031.SH", "600048.SH", "600104.SH", "600309.SH", "600585.SH", + "601668.SH", "601088.SH", "600028.SH", "601857.SH", + # 科技 / 成长 + "000063.SZ", "002415.SZ", "300750.SZ", "600009.SH", "600050.SH", + ] + + # daily_basic 表可用字段 + DAILY_BASIC_FIELDS = { + "pe", "pe_ttm", "pb", "ps", "ps_ttm", "dv_ratio", "dv_ttm", + "total_mv", "circ_mv", "turnover_rate", "volume_ratio", + } + + # fina_indicator 表可用字段 + FINA_FIELDS = { + "roe", "roe_dt", "roa", "roa2", "roic", "gross_margin", + "eps", "dt_eps", "current_ratio", "quick_ratio", "debt_to_assets", + "assets_turn", "cfo_to_or", "ocfps", + } + + def __init__(self, stocks: Optional[List[str]] = None, seed: int = 42): + self.stocks = stocks or self.DEFAULT_STOCKS + self.seed = seed + self._conn = None + self._db_ok: Optional[bool] = None + + # ================================================================== + # 公开接口 + # ================================================================== + + def load_prices(self, start_date: str, end_date: str) -> pd.DataFrame: + """ + 加载后复权收盘价。 + 返回 DataFrame: index=date, columns=ts_code + """ + if self._ensure_db(): + try: + return self._load_prices_db(start_date, end_date) + except Exception as e: + print(f"[DataLoader] 价格数据加载失败,回退到模拟数据: {e}") + return self._simulate_prices(start_date, end_date) + + def load_factor(self, name: str, start_date: str, end_date: str) -> pd.DataFrame: + """ + 加载指定因子/指标截面数据。 + 返回 DataFrame: index=date, columns=ts_code + """ + if name in self.DAILY_BASIC_FIELDS and self._ensure_db(): + try: + return self._load_daily_basic_field(name, start_date, end_date) + except Exception as e: + print(f"[DataLoader] 指标 {name} 加载失败,回退到模拟数据: {e}") + elif name in self.FINA_FIELDS and self._ensure_db(): + try: + return self._load_fina_field(name, start_date, end_date) + except Exception as e: + print(f"[DataLoader] 财务指标 {name} 加载失败,回退到模拟数据: {e}") + return self._simulate_factor(name, start_date, end_date) + + def load_factors(self, names: List[str], start_date: str, end_date: str) -> Dict[str, pd.DataFrame]: + """批量加载多个因子,返回 {name: DataFrame}""" + return {n: self.load_factor(n, start_date, end_date) for n in names} + + def load_benchmark( + self, code: str = "000300.SH", start_date: str = "", end_date: str = "" + ) -> pd.Series: + """ + 加载基准指数收盘价。 + 返回 Series: index=date, values=close + """ + if start_date and end_date and self._ensure_db(): + try: + return self._load_index_db(code, start_date, end_date) + except Exception as e: + print(f"[DataLoader] 指数 {code} 加载失败,回退到模拟数据: {e}") + # 模拟基准:用股票池均值价格代替 + prices = self._simulate_prices(start_date or "2020-01-01", end_date or "2024-12-31") + return prices.mean(axis=1).rename(code) + + def get_trade_dates(self, start_date: str, end_date: str) -> pd.DatetimeIndex: + """获取交易日列表(数据库不可用时使用工作日)""" + if self._ensure_db(): + try: + conn = self._get_conn() + query = """ + SELECT DISTINCT cal_date FROM trade_cal + WHERE is_open = 1 AND cal_date BETWEEN %s AND %s + ORDER BY cal_date + """ + dates = pd.read_sql(query, conn, params=[start_date, end_date]) + return pd.to_datetime(dates["cal_date"]) + except Exception: + pass + return pd.bdate_range(start_date, end_date) + + # ================================================================== + # 数据库连接 + # ================================================================== + + def _get_conn(self): + if self._conn is None: + sys.path.insert(0, str(_PROJECT_ROOT / "quantitative_data")) + from config import DB_CONFIG + import psycopg2 + self._conn = psycopg2.connect( + host=DB_CONFIG["host"], + port=DB_CONFIG["port"], + database=DB_CONFIG["database"], + user=DB_CONFIG["user"], + password=DB_CONFIG["password"], + connect_timeout=5, + ) + return self._conn + + def _ensure_db(self) -> bool: + """检测数据库是否可用(结果缓存)""" + if self._db_ok is not None: + return self._db_ok + try: + self._get_conn() + self._db_ok = True + except Exception: + self._conn = None + self._db_ok = False + print("[DataLoader] 数据库连接失败,使用模拟数据。" + "请检查 quantitative_data/.env 配置。") + return self._db_ok + + # ================================================================== + # 数据库加载实现 + # ================================================================== + + def _stock_sql_placeholders(self) -> str: + return ",".join(["%s"] * len(self.stocks)) + + def _load_prices_db(self, start_date: str, end_date: str) -> pd.DataFrame: + conn = self._get_conn() + placeholders = self._stock_sql_placeholders() + query = f""" + SELECT d.ts_code, d.trade_date, + d.close * a.adj_factor AS adj_close + FROM daily d + JOIN adj_factor a + ON d.ts_code = a.ts_code AND d.trade_date = a.trade_date + WHERE d.ts_code IN ({placeholders}) + AND d.trade_date BETWEEN %s AND %s + ORDER BY d.trade_date, d.ts_code + """ + df = pd.read_sql(query, conn, params=[*self.stocks, start_date, end_date]) + if df.empty: + raise ValueError("查询无数据") + pivot = df.pivot(index="trade_date", columns="ts_code", values="adj_close") + pivot.index = pd.to_datetime(pivot.index) + pivot = pivot.sort_index() + # 只保留有数据的列 + return pivot.dropna(how="all", axis=1) + + def _load_daily_basic_field(self, field: str, start_date: str, end_date: str) -> pd.DataFrame: + conn = self._get_conn() + placeholders = self._stock_sql_placeholders() + query = f""" + SELECT ts_code, trade_date, {field} + FROM daily_basic + WHERE ts_code IN ({placeholders}) + AND trade_date BETWEEN %s AND %s + ORDER BY trade_date, ts_code + """ + df = pd.read_sql(query, conn, params=[*self.stocks, start_date, end_date]) + if df.empty: + raise ValueError(f"查询 {field} 无数据") + pivot = df.pivot(index="trade_date", columns="ts_code", values=field) + pivot.index = pd.to_datetime(pivot.index) + pivot = pivot.sort_index() + return pivot.replace([np.inf, -np.inf], np.nan) + + def _load_fina_field(self, field: str, start_date: str, end_date: str) -> pd.DataFrame: + conn = self._get_conn() + placeholders = self._stock_sql_placeholders() + query = f""" + SELECT ts_code, end_date, {field} + FROM fina_indicator + WHERE ts_code IN ({placeholders}) + AND end_date BETWEEN %s AND %s + ORDER BY end_date, ts_code + """ + df = pd.read_sql(query, conn, params=[*self.stocks, start_date, end_date]) + if df.empty: + raise ValueError(f"查询财务指标 {field} 无数据") + pivot = df.pivot(index="end_date", columns="ts_code", values=field) + pivot.index = pd.to_datetime(pivot.index) + # 重采样到交易日并向前填充 + trade_dates = self.get_trade_dates(start_date, end_date) + pivot = pivot.sort_index().reindex(trade_dates).ffill() + return pivot.replace([np.inf, -np.inf], np.nan) + + def _load_index_db(self, code: str, start_date: str, end_date: str) -> pd.Series: + conn = self._get_conn() + query = """ + SELECT trade_date, close + FROM index_daily + WHERE ts_code = %s AND trade_date BETWEEN %s AND %s + ORDER BY trade_date + """ + df = pd.read_sql(query, conn, params=[code, start_date, end_date]) + if df.empty: + raise ValueError(f"指数 {code} 无数据") + s = pd.Series(df["close"].values, index=pd.to_datetime(df["trade_date"])) + return s.sort_index().rename(code) + + # ================================================================== + # 模拟数据(数据库不可用时的回退) + # ================================================================== + + def _simulate_prices(self, start_date: str, end_date: str) -> pd.DataFrame: + np.random.seed(self.seed) + dates = pd.bdate_range(start_date, end_date) + n_dates, n_stocks = len(dates), len(self.stocks) + # 几何布朗运动模拟股价 + rets = np.random.randn(n_dates, n_stocks) * 0.02 + rets[:, 0] *= 0.5 # 让第一只股票波动小(制造多样化) + rets[:, 1] *= 1.5 # 让第二只股票波动大 + prices = 100 * np.exp(np.cumsum(rets, axis=0)) + df = pd.DataFrame(prices, index=dates, columns=self.stocks) + return df + + def _simulate_factor(self, name: str, start_date: str, end_date: str) -> pd.DataFrame: + np.random.seed(self.seed) + dates = pd.bdate_range(start_date, end_date) + n = len(self.stocks) + data = np.random.randn(len(dates), n) + # 部分指标需要为正(估值/基本面) + positive_fields = { + "pe", "pe_ttm", "pb", "ps", "ps_ttm", "dv_ratio", "dv_ttm", + "total_mv", "circ_mv", "turnover_rate", "volume_ratio", + "roe", "roe_dt", "roa", "roa2", "roic", "gross_margin", + "eps", "dt_eps", "current_ratio", "quick_ratio", "debt_to_assets", + "assets_turn", "cfo_to_or", "ocfps", + } + if name in positive_fields: + data = np.abs(data) + 0.5 + return pd.DataFrame(data, index=dates, columns=self.stocks) \ No newline at end of file diff --git a/alpha/价值投资策略.ipynb b/alpha/价值投资策略.ipynb new file mode 100644 index 0000000..76e4de5 --- /dev/null +++ b/alpha/价值投资策略.ipynb @@ -0,0 +1,296 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 价值投资策略 — 构建与回测\n", + "\n", + "## 策略思路\n", + "价值投资(Value Investing):买入相对于基本面被低估的股票。\n", + "本策略使用 **市盈率 PE 倒数(EP)** 与 **市净率 PB 倒数(BP)** 合成价值因子,\n", + "每月调仓,持有估值最低(价值因子最高)的一批股票。\n", + "\n", + "## 研究流程\n", + "1. 加载数据(价格 + PE/PB 估值指标)\n", + "2. 价值因子计算与 IC 分析\n", + "3. 分层回测验证因子有效性\n", + "4. 构建策略并回测\n", + "5. 绩效评估" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "sys.path.insert(0, '..')\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from alpha.config import AlphaConfig\n", + "from alpha.factors import FactorAnalyzer\n", + "from alpha.strategy import Strategy, QuantileSignal, EqualWeightAllocator\n", + "from alpha.backtest import BacktestEngine\n", + "from alpha.evaluation import PerformanceEvaluator\n", + "from alpha.data_loader import DataLoader\n", + "\n", + "%matplotlib inline\n", + "plt.rcParams['font.sans-serif'] = ['SimHei']\n", + "plt.rcParams['axes.unicode_minus'] = False\n", + "\n", + "print('模块导入成功')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. 加载数据\n", + "\n", + "价值因子需要估值数据(PE / PB),通过 `DataLoader.load_factor` 加载。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "START_DATE = '2020-01-01'\n", + "END_DATE = '2024-12-31'\n", + "\n", + "loader = DataLoader()\n", + "\n", + "# 价格数据 + 估值数据\n", + "price_data = loader.load_prices(START_DATE, END_DATE)\n", + "pe_data = loader.load_factor('pe_ttm', START_DATE, END_DATE) # 市盈率 TTM\n", + "pb_data = loader.load_factor('pb', START_DATE, END_DATE) # 市净率\n", + "\n", + "print(f'价格数据: {price_data.shape}')\n", + "print(f'PE(TTM)数据: {pe_data.shape}')\n", + "print(f'PB数据: {pb_data.shape}')\n", + "\n", + "# 展示某天的估值截面\n", + "sample = pd.DataFrame({\n", + " 'PE_TTM': pe_data.iloc[-1],\n", + " 'PB': pb_data.iloc[-1],\n", + "}).dropna()\n", + "sample.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. 价值因子计算与 IC 分析\n", + "\n", + "价值因子 = 标准化后的 (1/PE + 1/PB):\n", + "$$\n", + "Value = Z\\left(\\frac{1}{PE}\\right) + Z\\left(\\frac{1}{PB}\\right)\n", + "$$\n", + "PE/PB 越低 → 价值因子越高 → 越被低估。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 剔除 PE/PB 非正值(亏损或净资产为负)\n", + "ep = 1.0 / pe_data.where(pe_data > 0)\n", + "bp = 1.0 / pb_data.where(pb_data > 0)\n", + "\n", + "# 截面标准化后合成\n", + "def zscore_cross_section(df):\n", + " return (df - df.mean(axis=1)) / (df.std(axis=1) + 1e-12)\n", + "\n", + "value_factor = zscore_cross_section(ep) + zscore_cross_section(bp)\n", + "\n", + "# 未来 20 日收益率(价值因子预测周期较长)\n", + "fwd_20d = price_data.pct_change(20).shift(-20)\n", + "\n", + "factor_stacked = value_factor.stack()\n", + "fwd_stacked = fwd_20d.stack()\n", + "\n", + "analyzer = FactorAnalyzer(factor_stacked, fwd_stacked)\n", + "ic = analyzer.compute_ic(method='rank')\n", + "\n", + "print('价值因子 IC 汇总:')\n", + "for k, v in analyzer.ic_summary().items():\n", + " print(f' {k}: {v:.4f}')\n", + "\n", + "ic.plot(figsize=(12, 3), title='价值因子 Rank IC 时间序列')\n", + "plt.axhline(y=0, color='r', linestyle='--')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. 分层回测" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "quantile_ret = analyzer.quantile_returns(n_quantiles=5)\n", + "print('各分位组平均收益 (Q1=最贵, Q5=最便宜):')\n", + "print(quantile_ret)\n", + "\n", + "quantile_ret['avg_return'].plot(kind='bar', figsize=(8, 4), color='steelblue')\n", + "plt.title('价值因子分层收益 (未来20日)')\n", + "plt.ylabel('平均收益率')\n", + "plt.grid(True, alpha=0.3)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. 构建策略并回测" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 回测配置\n", + "config = AlphaConfig(\n", + " initial_cash=1_000_000,\n", + " commission_rate=0.0003,\n", + " slippage=0.001,\n", + " stamp_tax=0.001,\n", + ")\n", + "\n", + "# 价值策略:持有价值因子最高的 20% 股票(最被低估)\n", + "strategy = Strategy(\n", + " name='价值投资策略-EP+BP',\n", + " factors=[],\n", + " signal_generator=QuantileSignal(\n", + " n_quantiles=5, long_quantile=5, short_quantile=0,\n", + " ),\n", + " weight_allocator=EqualWeightAllocator(max_positions=10),\n", + " description='每月持有EP+BP合成的价值因子最高的20%股票'\n", + ")\n", + "\n", + "engine = BacktestEngine(config)\n", + "equity_curve = engine.run(\n", + " strategy=strategy,\n", + " price_data=price_data,\n", + " factor_data={'value': value_factor},\n", + " rebalance_freq='M',\n", + ")\n", + "\n", + "print(f'回测完成, 共 {len(equity_curve)} 个交易日')\n", + "print(f'累计收益率: {(equity_curve[\"nav\"].iloc[-1] - 1) * 100:.2f}%')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. 净值曲线与回撤" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, axes = plt.subplots(2, 1, figsize=(14, 8))\n", + "\n", + "# 净值曲线\n", + "axes[0].plot(equity_curve.index, equity_curve['nav'], label='策略净值', color='purple')\n", + "axes[0].axhline(y=1.0, color='gray', linestyle='--')\n", + "axes[0].set_title('价值投资策略净值曲线')\n", + "axes[0].legend()\n", + "axes[0].grid(True, alpha=0.3)\n", + "\n", + "# 回撤曲线\n", + "nav = equity_curve['nav']\n", + "running_max = nav.cummax()\n", + "drawdown = (nav - running_max) / running_max\n", + "axes[1].fill_between(equity_curve.index, 0, drawdown.values, color='red', alpha=0.3)\n", + "axes[1].set_title('回撤曲线')\n", + "axes[1].grid(True, alpha=0.3)\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. 绩效评估" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 基准:股票池等权组合\n", + "bench_ret = price_data.pct_change().mean(axis=1)\n", + "\n", + "evaluator = PerformanceEvaluator(equity_curve, benchmark_returns=bench_ret, risk_free_rate=0.03)\n", + "print(evaluator.summary())\n", + "\n", + "report_df = evaluator.full_report()\n", + "pd.DataFrame(list(report_df.items()), columns=['指标', '数值']).set_index('指标')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## 单独估值指标对比\n", + "\n", + "对比单独使用 EP、BP 与合成价值因子的效果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 单独因子的 IC 对比\n", + "for name, fac in [('EP', ep), ('BP', bp), ('EP+BP', value_factor)]:\n", + " a = FactorAnalyzer(fac.stack(), fwd_stacked)\n", + " s = a.ic_summary()\n", + " print(f'{name}: IC均值={s[\"IC_Mean\"]:.4f}, ICIR={s[\"IR\"]:.4f}, '\n", + " f'IC>0占比={s[\"IC>0_Ratio\"]:.2%}')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/alpha/低波动率策略.ipynb b/alpha/低波动率策略.ipynb new file mode 100644 index 0000000..0c7756c --- /dev/null +++ b/alpha/低波动率策略.ipynb @@ -0,0 +1,277 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 低波动率策略 — 构建与回测\n", + "\n", + "## 策略思路\n", + "低波动率异常(Low Volatility Anomaly)指波动率较低的股票,长期来看风险调整后收益往往优于高波动股票。\n", + "本策略使用 **20 日波动率倒数** 作为低波因子,每月调仓,买入波动率最低的一批股票。\n", + "\n", + "## 研究流程\n", + "1. 加载数据\n", + "2. 低波因子计算与 IC 分析\n", + "3. 分层回测验证因子有效性\n", + "4. 构建策略并回测\n", + "5. 绩效评估" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "sys.path.insert(0, '..')\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from alpha.config import AlphaConfig\n", + "from alpha.factors import FactorAnalyzer\n", + "from alpha.strategy import Strategy, QuantileSignal, EqualWeightAllocator\n", + "from alpha.backtest import BacktestEngine\n", + "from alpha.evaluation import PerformanceEvaluator\n", + "from alpha.data_loader import DataLoader\n", + "\n", + "%matplotlib inline\n", + "plt.rcParams['font.sans-serif'] = ['SimHei']\n", + "plt.rcParams['axes.unicode_minus'] = False\n", + "\n", + "print('模块导入成功')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. 加载数据" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 回测区间\n", + "START_DATE = '2020-01-01'\n", + "END_DATE = '2024-12-31'\n", + "\n", + "loader = DataLoader()\n", + "\n", + "# 后复权收盘价\n", + "price_data = loader.load_prices(START_DATE, END_DATE)\n", + "print(f'价格数据: {price_data.shape}, 日期: {price_data.index[0]} ~ {price_data.index[-1]}')\n", + "price_data.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. 低波因子计算与 IC 分析" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 低波因子: 20日波动率的倒数(波动越小 → 因子值越大)\n", + "VOL_WINDOW = 20\n", + "daily_ret = price_data.pct_change()\n", + "volatility = daily_ret.rolling(VOL_WINDOW).std()\n", + "low_vol_factor = -volatility # 取负号使低波动为正\n", + "\n", + "# 未来 10 日收益率(验证预测能力,低波策略通常持有期较长)\n", + "fwd_10d = price_data.pct_change(10).shift(-10)\n", + "\n", + "factor_stacked = low_vol_factor.stack()\n", + "fwd_stacked = fwd_10d.stack()\n", + "\n", + "analyzer = FactorAnalyzer(factor_stacked, fwd_stacked)\n", + "ic = analyzer.compute_ic(method='rank')\n", + "\n", + "print('低波因子 IC 汇总:')\n", + "for k, v in analyzer.ic_summary().items():\n", + " print(f' {k}: {v:.4f}')\n", + "\n", + "# IC 曲线\n", + "ic.plot(figsize=(12, 3), title='低波因子 Rank IC 时间序列')\n", + "plt.axhline(y=0, color='r', linestyle='--')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. 分层回测" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 分 5 组,观察单调性(Q1=高波动, Q5=低波动)\n", + "quantile_ret = analyzer.quantile_returns(n_quantiles=5)\n", + "print('各分位组平均收益 (Q1=最高波动, Q5=最低波动):')\n", + "print(quantile_ret)\n", + "\n", + "quantile_ret['avg_return'].plot(kind='bar', figsize=(8, 4), color='steelblue')\n", + "plt.title('低波因子分层收益 (未来10日)')\n", + "plt.ylabel('平均收益率')\n", + "plt.grid(True, alpha=0.3)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. 构建策略并回测" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 回测配置\n", + "config = AlphaConfig(\n", + " initial_cash=1_000_000,\n", + " commission_rate=0.0003,\n", + " slippage=0.001,\n", + " stamp_tax=0.001,\n", + ")\n", + "\n", + "# 低波策略:持有波动率最低的 20% 股票,等权\n", + "strategy = Strategy(\n", + " name='低波动率策略-20日',\n", + " factors=[],\n", + " signal_generator=QuantileSignal(\n", + " n_quantiles=5, long_quantile=5, short_quantile=0, # 只做多\n", + " ),\n", + " weight_allocator=EqualWeightAllocator(max_positions=10),\n", + " description='每月持有过去20日波动率最低的20%股票,等权配置'\n", + ")\n", + "\n", + "engine = BacktestEngine(config)\n", + "equity_curve = engine.run(\n", + " strategy=strategy,\n", + " price_data=price_data,\n", + " factor_data={'low_vol': low_vol_factor},\n", + " rebalance_freq='M',\n", + ")\n", + "\n", + "print(f'回测完成, 共 {len(equity_curve)} 个交易日')\n", + "print(f'累计收益率: {(equity_curve[\"nav\"].iloc[-1] - 1) * 100:.2f}%')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. 净值曲线与回撤" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, axes = plt.subplots(2, 1, figsize=(14, 8))\n", + "\n", + "# 净值曲线\n", + "axes[0].plot(equity_curve.index, equity_curve['nav'], label='策略净值', color='green')\n", + "axes[0].axhline(y=1.0, color='gray', linestyle='--')\n", + "axes[0].set_title('低波动率策略净值曲线')\n", + "axes[0].legend()\n", + "axes[0].grid(True, alpha=0.3)\n", + "\n", + "# 回撤曲线\n", + "nav = equity_curve['nav']\n", + "running_max = nav.cummax()\n", + "drawdown = (nav - running_max) / running_max\n", + "axes[1].fill_between(equity_curve.index, 0, drawdown.values, color='red', alpha=0.3)\n", + "axes[1].set_title('回撤曲线')\n", + "axes[1].grid(True, alpha=0.3)\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. 绩效评估" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 基准:股票池等权组合\n", + "bench_ret = price_data.pct_change().mean(axis=1)\n", + "\n", + "evaluator = PerformanceEvaluator(equity_curve, benchmark_returns=bench_ret, risk_free_rate=0.03)\n", + "print(evaluator.summary())\n", + "\n", + "report_df = evaluator.full_report()\n", + "pd.DataFrame(list(report_df.items()), columns=['指标', '数值']).set_index('指标')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## 波动率窗口敏感性分析\n", + "\n", + "不同波动率计算窗口对因子表现的影响:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 不同波动率窗口对比\n", + "for win in [5, 10, 20, 40]:\n", + " vol = daily_ret.rolling(win).std()\n", + " fac = -vol\n", + " a = FactorAnalyzer(fac.stack(), fwd_stacked)\n", + " summary = a.ic_summary()\n", + " print(f'窗口 {win}日: IC均值={summary[\"IC_Mean\"]:.4f}, ICIR={summary[\"IR\"]:.4f}, '\n", + " f'IC>0占比={summary[\"IC>0_Ratio\"]:.2%}')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/alpha/动量策略.ipynb b/alpha/动量策略.ipynb new file mode 100644 index 0000000..857625c --- /dev/null +++ b/alpha/动量策略.ipynb @@ -0,0 +1,307 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 动量策略 — 构建与回测\n", + "\n", + "## 策略思路\n", + "动量效应(Momentum Effect)指过去一段时间表现良好的股票,未来一段时间内往往继续表现良好。\n", + "本策略使用 **20 日收益率** 作为动量因子,每月调仓,买入过去表现最好的一个分位组股票。\n", + "\n", + "## 研究流程\n", + "1. 加载数据\n", + "2. 动量因子计算与 IC 分析\n", + "3. 分层回测验证因子有效性\n", + "4. 构建策略并回测\n", + "5. 绩效评估" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "sys.path.insert(0, '..')\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from alpha.config import AlphaConfig\n", + "from alpha.factors import FactorAnalyzer\n", + "from alpha.strategy import Strategy, QuantileSignal, EqualWeightAllocator\n", + "from alpha.backtest import BacktestEngine\n", + "from alpha.evaluation import PerformanceEvaluator, ReportGenerator\n", + "from alpha.data_loader import DataLoader\n", + "\n", + "%matplotlib inline\n", + "plt.rcParams['font.sans-serif'] = ['SimHei']\n", + "plt.rcParams['axes.unicode_minus'] = False\n", + "\n", + "print('模块导入成功')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. 加载数据" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 回测区间\n", + "START_DATE = '2020-01-01'\n", + "END_DATE = '2024-12-31'\n", + "\n", + "loader = DataLoader()\n", + "\n", + "# 后复权收盘价\n", + "price_data = loader.load_prices(START_DATE, END_DATE)\n", + "print(f'价格数据: {price_data.shape}, 日期: {price_data.index[0]} ~ {price_data.index[-1]}')\n", + "print(f'股票数量: {price_data.shape[1]}')\n", + "price_data.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. 动量因子计算与 IC 分析" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 动量因子: 20日收益率(过去20个交易日涨跌幅)\n", + "MOM_WINDOW = 20\n", + "momentum = price_data.pct_change(MOM_WINDOW)\n", + "\n", + "# 未来 5 日收益率(验证短期预测能力)\n", + "fwd_5d = price_data.pct_change(5).shift(-5)\n", + "\n", + "# 堆叠为长表 (MultiIndex: date, stock)\n", + "factor_stacked = momentum.stack()\n", + "fwd_stacked = fwd_5d.stack()\n", + "\n", + "analyzer = FactorAnalyzer(factor_stacked, fwd_stacked)\n", + "ic = analyzer.compute_ic(method='rank')\n", + "\n", + "print('动量因子 IC 汇总:')\n", + "for k, v in analyzer.ic_summary().items():\n", + " print(f' {k}: {v:.4f}')\n", + "\n", + "# IC 曲线\n", + "ic.plot(figsize=(12, 3), title='动量因子 Rank IC 时间序列')\n", + "plt.axhline(y=0, color='r', linestyle='--')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. 分层回测" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 分 5 组,观察单调性\n", + "quantile_ret = analyzer.quantile_returns(n_quantiles=5)\n", + "print('各分位组平均收益 (Q1=最低动量, Q5=最高动量):')\n", + "print(quantile_ret)\n", + "\n", + "# 绘制分组收益条形图\n", + "quantile_ret['avg_return'].plot(kind='bar', figsize=(8, 4), color='steelblue')\n", + "plt.title('动量因子分层收益 (未来5日)')\n", + "plt.ylabel('平均收益率')\n", + "plt.grid(True, alpha=0.3)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. 构建策略并回测" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 回测配置\n", + "config = AlphaConfig(\n", + " initial_cash=1_000_000,\n", + " commission_rate=0.0003,\n", + " slippage=0.001,\n", + " stamp_tax=0.001,\n", + ")\n", + "\n", + "# 动量策略:Top 20% (Q5) 长期持有,等权\n", + "strategy = Strategy(\n", + " name='动量策略-20日Top20%',\n", + " factors=[],\n", + " signal_generator=QuantileSignal(\n", + " n_quantiles=5, long_quantile=5, short_quantile=0, # 只做多\n", + " ),\n", + " weight_allocator=EqualWeightAllocator(max_positions=10),\n", + " description='每月持有过去20日涨幅最高的20%股票,等权配置'\n", + ")\n", + "\n", + "# 回测引擎\n", + "engine = BacktestEngine(config)\n", + "equity_curve = engine.run(\n", + " strategy=strategy,\n", + " price_data=price_data,\n", + " factor_data={'momentum': momentum},\n", + " rebalance_freq='M', # 月频调仓\n", + ")\n", + "\n", + "print(f'回测完成, 共 {len(equity_curve)} 个交易日')\n", + "print(f'累计收益率: {(equity_curve[\"nav\"].iloc[-1] - 1) * 100:.2f}%')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. 净值曲线与回撤" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, axes = plt.subplots(2, 1, figsize=(14, 8))\n", + "\n", + "# 净值曲线\n", + "axes[0].plot(equity_curve.index, equity_curve['nav'], label='策略净值', color='steelblue')\n", + "axes[0].axhline(y=1.0, color='gray', linestyle='--')\n", + "axes[0].set_title('动量策略净值曲线')\n", + "axes[0].legend()\n", + "axes[0].grid(True, alpha=0.3)\n", + "\n", + "# 回撤曲线\n", + "nav = equity_curve['nav']\n", + "running_max = nav.cummax()\n", + "drawdown = (nav - running_max) / running_max\n", + "axes[1].fill_between(equity_curve.index, 0, drawdown.values, color='red', alpha=0.3)\n", + "axes[1].set_title('回撤曲线')\n", + "axes[1].grid(True, alpha=0.3)\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. 绩效评估" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 基准:股票池等权组合\n", + "bench_ret = price_data.pct_change().mean(axis=1)\n", + "\n", + "evaluator = PerformanceEvaluator(equity_curve, benchmark_returns=bench_ret, risk_free_rate=0.03)\n", + "print(evaluator.summary())\n", + "\n", + "# 导出报告\n", + "report_df = evaluator.full_report()\n", + "pd.DataFrame(list(report_df.items()), columns=['指标', '数值']).set_index('指标')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. 交易记录分析" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "trades = engine.portfolio.trades\n", + "if trades:\n", + " trade_df = pd.DataFrame([\n", + " {'日期': t.date, '股票': t.stock, '方向': t.side,\n", + " '数量': t.quantity, '价格': t.price, '佣金': t.commission}\n", + " for t in trades\n", + " ])\n", + " print(f'总交易数: {len(trade_df)}')\n", + " print(f'买入: {(trade_df[\"方向\"]==\"buy\").sum()}, 卖出: {(trade_df[\"方向\"]==\"sell\").sum()}')\n", + " display(trade_df.head(20))\n", + "else:\n", + " print('无交易记录(可能数据量不足)')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## 参数敏感性分析\n", + "\n", + "动量窗口是影响策略表现的关键参数,可尝试不同窗口:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 不同动量窗口对比\n", + "for win in [10, 20, 40, 60]:\n", + " mom = price_data.pct_change(win)\n", + " fwd = price_data.pct_change(5).shift(-5)\n", + " a = FactorAnalyzer(mom.stack(), fwd.stack())\n", + " summary = a.ic_summary()\n", + " print(f'窗口 {win}日: IC均值={summary[\"IC_Mean\"]:.4f}, ICIR={summary[\"IR\"]:.4f}, '\n", + " f'IC>0占比={summary[\"IC>0_Ratio\"]:.2%}')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/alpha/均线趋势策略.ipynb b/alpha/均线趋势策略.ipynb new file mode 100644 index 0000000..7502503 --- /dev/null +++ b/alpha/均线趋势策略.ipynb @@ -0,0 +1,274 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 均线趋势策略 — 构建与回测\n", + "\n", + "## 策略思路\n", + "趋势跟踪(Trend Following):价格位于均线上方且均线向上时做多,反之做空/空仓。\n", + "本策略使用 **股价偏离 20 日均线的 Z-Score** 作为趋势强度因子,\n", + "每月调仓,买入趋势强度排名靠前的股票。\n", + "\n", + "## 研究流程\n", + "1. 加载数据\n", + "2. 趋势因子计算与 IC 分析\n", + "3. 分层回测验证因子有效性\n", + "4. 构建策略并回测\n", + "5. 绩效评估" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "sys.path.insert(0, '..')\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from alpha.config import AlphaConfig\n", + "from alpha.factors import FactorAnalyzer\n", + "from alpha.strategy import Strategy, QuantileSignal, EqualWeightAllocator\n", + "from alpha.backtest import BacktestEngine\n", + "from alpha.evaluation import PerformanceEvaluator\n", + "from alpha.data_loader import DataLoader\n", + "\n", + "%matplotlib inline\n", + "plt.rcParams['font.sans-serif'] = ['SimHei']\n", + "plt.rcParams['axes.unicode_minus'] = False\n", + "\n", + "print('模块导入成功')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. 加载数据" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "START_DATE = '2020-01-01'\n", + "END_DATE = '2024-12-31'\n", + "\n", + "loader = DataLoader()\n", + "price_data = loader.load_prices(START_DATE, END_DATE)\n", + "print(f'价格数据: {price_data.shape}, 日期: {price_data.index[0]} ~ {price_data.index[-1]}')\n", + "price_data.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. 趋势因子计算与 IC 分析\n", + "\n", + "趋势因子 = 价格偏离 20 日均线的幅度(百分比):\n", + "$$Trend = \\frac{Close}{MA_{20}} - 1$$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "MA_WINDOW = 20\n", + "ma = price_data.rolling(MA_WINDOW).mean()\n", + "trend_factor = price_data / ma - 1 # 正偏离 = 上升趋势\n", + "\n", + "# 未来 5 日收益率\n", + "fwd_5d = price_data.pct_change(5).shift(-5)\n", + "\n", + "factor_stacked = trend_factor.stack()\n", + "fwd_stacked = fwd_5d.stack()\n", + "\n", + "analyzer = FactorAnalyzer(factor_stacked, fwd_stacked)\n", + "ic = analyzer.compute_ic(method='rank')\n", + "\n", + "print('趋势因子 IC 汇总:')\n", + "for k, v in analyzer.ic_summary().items():\n", + " print(f' {k}: {v:.4f}')\n", + "\n", + "ic.plot(figsize=(12, 3), title='趋势因子 Rank IC 时间序列')\n", + "plt.axhline(y=0, color='r', linestyle='--')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. 分层回测" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "quantile_ret = analyzer.quantile_returns(n_quantiles=5)\n", + "print('各分位组平均收益 (Q1=最强下跌趋势, Q5=最强上升趋势):')\n", + "print(quantile_ret)\n", + "\n", + "quantile_ret['avg_return'].plot(kind='bar', figsize=(8, 4), color='steelblue')\n", + "plt.title('趋势因子分层收益 (未来5日)')\n", + "plt.ylabel('平均收益率')\n", + "plt.grid(True, alpha=0.3)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. 构建策略并回测" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 回测配置\n", + "config = AlphaConfig(\n", + " initial_cash=1_000_000,\n", + " commission_rate=0.0003,\n", + " slippage=0.001,\n", + " stamp_tax=0.001,\n", + ")\n", + "\n", + "# 趋势策略:持有趋势最强(均线偏离最大)的 20% 股票\n", + "strategy = Strategy(\n", + " name='均线趋势策略-MA20',\n", + " factors=[],\n", + " signal_generator=QuantileSignal(\n", + " n_quantiles=5, long_quantile=5, short_quantile=0,\n", + " ),\n", + " weight_allocator=EqualWeightAllocator(max_positions=10),\n", + " description='每月持有价格高于20日均线幅度最大的20%股票'\n", + ")\n", + "\n", + "engine = BacktestEngine(config)\n", + "equity_curve = engine.run(\n", + " strategy=strategy,\n", + " price_data=price_data,\n", + " factor_data={'trend': trend_factor},\n", + " rebalance_freq='M',\n", + ")\n", + "\n", + "print(f'回测完成, 共 {len(equity_curve)} 个交易日')\n", + "print(f'累计收益率: {(equity_curve[\"nav\"].iloc[-1] - 1) * 100:.2f}%')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. 净值曲线与回撤" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, axes = plt.subplots(2, 1, figsize=(14, 8))\n", + "\n", + "# 净值曲线\n", + "axes[0].plot(equity_curve.index, equity_curve['nav'], label='策略净值', color='orange')\n", + "axes[0].axhline(y=1.0, color='gray', linestyle='--')\n", + "axes[0].set_title('均线趋势策略净值曲线')\n", + "axes[0].legend()\n", + "axes[0].grid(True, alpha=0.3)\n", + "\n", + "# 回撤曲线\n", + "nav = equity_curve['nav']\n", + "running_max = nav.cummax()\n", + "drawdown = (nav - running_max) / running_max\n", + "axes[1].fill_between(equity_curve.index, 0, drawdown.values, color='red', alpha=0.3)\n", + "axes[1].set_title('回撤曲线')\n", + "axes[1].grid(True, alpha=0.3)\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. 绩效评估" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 基准:股票池等权组合\n", + "bench_ret = price_data.pct_change().mean(axis=1)\n", + "\n", + "evaluator = PerformanceEvaluator(equity_curve, benchmark_returns=bench_ret, risk_free_rate=0.03)\n", + "print(evaluator.summary())\n", + "\n", + "report_df = evaluator.full_report()\n", + "pd.DataFrame(list(report_df.items()), columns=['指标', '数值']).set_index('指标')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## 均线周期敏感性分析\n", + "\n", + "不同均线周期对趋势因子的影响:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 不同均线周期对比\n", + "for win in [5, 10, 20, 60]:\n", + " ma_tmp = price_data.rolling(win).mean()\n", + " fac = price_data / ma_tmp - 1\n", + " a = FactorAnalyzer(fac.stack(), fwd_stacked)\n", + " summary = a.ic_summary()\n", + " print(f'MA{win}: IC均值={summary[\"IC_Mean\"]:.4f}, ICIR={summary[\"IR\"]:.4f}, '\n", + " f'IC>0占比={summary[\"IC>0_Ratio\"]:.2%}')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/alpha/多因子选股策略.ipynb b/alpha/多因子选股策略.ipynb new file mode 100644 index 0000000..8d73fe7 --- /dev/null +++ b/alpha/多因子选股策略.ipynb @@ -0,0 +1,320 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 多因子选股策略 — 构建与回测\n", + "\n", + "## 策略思路\n", + "多因子模型(Multi-Factor Model)综合多个维度的因子,分散单一因子的风险,提升选股稳健性。\n", + "本策略合成 **四大类因子**:\n", + "\n", + "| 因子类别 | 具体因子 | 代表逻辑 |\n", + "|---------|---------|---------|\n", + "| 动量 | 20日收益率 | 强者恒强 |\n", + "| 低波 | 20日波动率倒数 | 低波动溢价 |\n", + "| 价值 | EP (PE倒数) | 低估值 |\n", + "| 质量 | ROE | 高盈利能力 |\n", + "\n", + "每月调仓,综合得分最高的股票入选。\n", + "\n", + "## 研究流程\n", + "1. 加载数据\n", + "2. 各因子计算与 IC 分析\n", + "3. 等权合成多因子 + 分层回测\n", + "4. 构建策略并回测\n", + "5. 绩效评估" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "sys.path.insert(0, '..')\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from alpha.config import AlphaConfig\n", + "from alpha.factors import FactorAnalyzer\n", + "from alpha.strategy import Strategy, QuantileSignal, EqualWeightAllocator\n", + "from alpha.backtest import BacktestEngine\n", + "from alpha.evaluation import PerformanceEvaluator\n", + "from alpha.data_loader import DataLoader\n", + "\n", + "%matplotlib inline\n", + "plt.rcParams['font.sans-serif'] = ['SimHei']\n", + "plt.rcParams['axes.unicode_minus'] = False\n", + "\n", + "print('模块导入成功')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. 加载数据" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "START_DATE = '2020-01-01'\n", + "END_DATE = '2024-12-31'\n", + "\n", + "loader = DataLoader()\n", + "\n", + "# 价格 + 估值 + 财务\n", + "price_data = loader.load_prices(START_DATE, END_DATE)\n", + "pe_data = loader.load_factor('pe_ttm', START_DATE, END_DATE)\n", + "roe_data = loader.load_factor('roe', START_DATE, END_DATE)\n", + "\n", + "print(f'价格数据: {price_data.shape}')\n", + "print(f'PE(TTM)数据: {pe_data.shape}')\n", + "print(f'ROE数据: {roe_data.shape}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. 各因子计算与 IC 分析" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# ---- 截面标准化函数 ----\n", + "def zscore_cross_section(df):\n", + " return (df - df.mean(axis=1)) / (df.std(axis=1) + 1e-12)\n", + "\n", + "# ---- 1. 动量因子 (20日收益率) ----\n", + "momentum = price_data.pct_change(20)\n", + "z_mom = zscore_cross_section(momentum)\n", + "\n", + "# ---- 2. 低波因子 (20日波动率倒数) ----\n", + "daily_ret = price_data.pct_change()\n", + "volatility = daily_ret.rolling(20).std()\n", + "z_lowvol = -zscore_cross_section(volatility) # 波动越小越好\n", + "\n", + "# ---- 3. 价值因子 (EP = 1/PE) ----\n", + "ep = 1.0 / pe_data.where(pe_data > 0)\n", + "z_value = zscore_cross_section(ep)\n", + "\n", + "# ---- 4. 质量因子 (ROE) ----\n", + "z_quality = zscore_cross_section(roe_data)\n", + "\n", + "# ---- 汇总各因子 ----\n", + "factors = {\n", + " '动量': momentum,\n", + " '低波': -volatility,\n", + " '价值': ep,\n", + " '质量': roe_data,\n", + "}\n", + "\n", + "# 未来 10 日收益率\n", + "fwd_10d = price_data.pct_change(10).shift(-10)\n", + "\n", + "# 各因子 IC 对比\n", + "print('各因子 IC 汇总 (未来10日):')\n", + "ic_results = {}\n", + "for name, fac in factors.items():\n", + " a = FactorAnalyzer(fac.stack(), fwd_10d.stack())\n", + " s = a.ic_summary()\n", + " ic_results[name] = s\n", + " print(f' {name}: IC均值={s[\"IC_Mean\"]:.4f}, ICIR={s[\"IR\"]:.4f}, '\n", + " f'IC>0占比={s[\"IC>0_Ratio\"]:.2%}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. 合成多因子与分层回测\n", + "\n", + "等权合成综合得分:\n", + "$$Score = \\frac{1}{4}(Z_{Momentum} + Z_{LowVol} + Z_{Value} + Z_{Quality})$$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 等权合成综合得分\n", + "multi_factor = (z_mom + z_lowvol + z_value + z_quality) / 4\n", + "\n", + "# 分层回测\n", + "analyzer = FactorAnalyzer(multi_factor.stack(), fwd_10d.stack())\n", + "quantile_ret = analyzer.quantile_returns(n_quantiles=5)\n", + "print('多因子分层收益 (Q1=综合得分最低, Q5=最高):')\n", + "print(quantile_ret)\n", + "\n", + "quantile_ret['avg_return'].plot(kind='bar', figsize=(8, 4), color='steelblue')\n", + "plt.title('多因子合成分层收益 (未来10日)')\n", + "plt.ylabel('平均收益率')\n", + "plt.grid(True, alpha=0.3)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. 构建策略并回测" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 回测配置\n", + "config = AlphaConfig(\n", + " initial_cash=1_000_000,\n", + " commission_rate=0.0003,\n", + " slippage=0.001,\n", + " stamp_tax=0.001,\n", + ")\n", + "\n", + "# 多因子策略:持有综合得分最高的 20% 股票\n", + "strategy = Strategy(\n", + " name='多因子选股策略-动量+低波+价值+质量',\n", + " factors=[],\n", + " signal_generator=QuantileSignal(\n", + " n_quantiles=5, long_quantile=5, short_quantile=0,\n", + " ),\n", + " weight_allocator=EqualWeightAllocator(max_positions=10),\n", + " description='每月持有动量/低波/价值/质量四因子综合得分最高的20%股票'\n", + ")\n", + "\n", + "engine = BacktestEngine(config)\n", + "equity_curve = engine.run(\n", + " strategy=strategy,\n", + " price_data=price_data,\n", + " factor_data={'multi': multi_factor},\n", + " rebalance_freq='M',\n", + ")\n", + "\n", + "print(f'回测完成, 共 {len(equity_curve)} 个交易日')\n", + "print(f'累计收益率: {(equity_curve[\"nav\"].iloc[-1] - 1) * 100:.2f}%')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. 净值曲线与回撤" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, axes = plt.subplots(2, 1, figsize=(14, 8))\n", + "\n", + "# 净值曲线\n", + "axes[0].plot(equity_curve.index, equity_curve['nav'], label='策略净值', color='darkred')\n", + "axes[0].axhline(y=1.0, color='gray', linestyle='--')\n", + "axes[0].set_title('多因子选股策略净值曲线')\n", + "axes[0].legend()\n", + "axes[0].grid(True, alpha=0.3)\n", + "\n", + "# 回撤曲线\n", + "nav = equity_curve['nav']\n", + "running_max = nav.cummax()\n", + "drawdown = (nav - running_max) / running_max\n", + "axes[1].fill_between(equity_curve.index, 0, drawdown.values, color='red', alpha=0.3)\n", + "axes[1].set_title('回撤曲线')\n", + "axes[1].grid(True, alpha=0.3)\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. 绩效评估" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 基准:股票池等权组合\n", + "bench_ret = price_data.pct_change().mean(axis=1)\n", + "\n", + "evaluator = PerformanceEvaluator(equity_curve, benchmark_returns=bench_ret, risk_free_rate=0.03)\n", + "print(evaluator.summary())\n", + "\n", + "report_df = evaluator.full_report()\n", + "pd.DataFrame(list(report_df.items()), columns=['指标', '数值']).set_index('指标')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## 因子权重敏感性分析\n", + "\n", + "对比等权合成与单因子策略的 IC 表现:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 单因子 vs 多因子 IC 对比\n", + "all_factors = {\n", + " '动量': z_mom,\n", + " '低波': z_lowvol,\n", + " '价值': z_value,\n", + " '质量': z_quality,\n", + " '多因子(等权)': multi_factor,\n", + "}\n", + "\n", + "print(f\"{'因子':<12} {'IC均值':>8} {'ICIR':>8} {'IC>0占比':>10}\")\n", + "print('-' * 42)\n", + "for name, fac in all_factors.items():\n", + " a = FactorAnalyzer(fac.stack(), fwd_10d.stack())\n", + " s = a.ic_summary()\n", + " print(f\"{name:<12} {s['IC_Mean']:>8.4f} {s['IR']:>8.4f} {s['IC>0_Ratio']:>10.2%}\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/alpha/质量因子策略.ipynb b/alpha/质量因子策略.ipynb new file mode 100644 index 0000000..bfb7ef7 --- /dev/null +++ b/alpha/质量因子策略.ipynb @@ -0,0 +1,298 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 质量因子策略 — 构建与回测\n", + "\n", + "## 策略思路\n", + "质量投资(Quality Investing):买入盈利能力强、财务稳健的公司。\n", + "本策略使用 **ROE、毛利率、资产负债率** 合成质量因子:\n", + "- ROE(净资产收益率)越高 → 质量越好\n", + "- 毛利率越高 → 质量越好\n", + "- 资产负债率越低 → 财务更稳健\n", + "\n", + "每月调仓,持有质量因子最高的一批股票。\n", + "\n", + "## 研究流程\n", + "1. 加载数据(价格 + 财务指标)\n", + "2. 质量因子计算与 IC 分析\n", + "3. 分层回测验证因子有效性\n", + "4. 构建策略并回测\n", + "5. 绩效评估" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "sys.path.insert(0, '..')\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from alpha.config import AlphaConfig\n", + "from alpha.factors import FactorAnalyzer\n", + "from alpha.strategy import Strategy, QuantileSignal, EqualWeightAllocator\n", + "from alpha.backtest import BacktestEngine\n", + "from alpha.evaluation import PerformanceEvaluator\n", + "from alpha.data_loader import DataLoader\n", + "\n", + "%matplotlib inline\n", + "plt.rcParams['font.sans-serif'] = ['SimHei']\n", + "plt.rcParams['axes.unicode_minus'] = False\n", + "\n", + "print('模块导入成功')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. 加载数据\n", + "\n", + "质量因子需要财务指标(ROE / 毛利率 / 资产负债率),通过 `DataLoader.load_factor` 加载。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "START_DATE = '2020-01-01'\n", + "END_DATE = '2024-12-31'\n", + "\n", + "loader = DataLoader()\n", + "\n", + "# 价格数据 + 财务数据\n", + "price_data = loader.load_prices(START_DATE, END_DATE)\n", + "roe_data = loader.load_factor('roe', START_DATE, END_DATE) # 净资产收益率\n", + "gross_margin_data = loader.load_factor('gross_margin', START_DATE, END_DATE) # 毛利率\n", + "debt_data = loader.load_factor('debt_to_assets', START_DATE, END_DATE) # 资产负债率\n", + "\n", + "print(f'价格数据: {price_data.shape}')\n", + "print(f'ROE数据: {roe_data.shape}')\n", + "print(f'毛利率数据: {gross_margin_data.shape}')\n", + "print(f'资产负债率数据: {debt_data.shape}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. 质量因子计算与 IC 分析\n", + "\n", + "质量因子 = Z(ROE) + Z(毛利率) + Z(-资产负债率)\n", + "\n", + "财务指标为季度数据,`DataLoader` 已重采样到交易日并做向前填充。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 截面标准化函数\n", + "def zscore_cross_section(df):\n", + " return (df - df.mean(axis=1)) / (df.std(axis=1) + 1e-12)\n", + "\n", + "z_roe = zscore_cross_section(roe_data)\n", + "z_gm = zscore_cross_section(gross_margin_data)\n", + "z_debt = -zscore_cross_section(debt_data) # 负债率越低越好\n", + "\n", + "quality_factor = z_roe + z_gm + z_debt\n", + "\n", + "# 未来 20 日收益率(质量因子预测周期较长)\n", + "fwd_20d = price_data.pct_change(20).shift(-20)\n", + "\n", + "factor_stacked = quality_factor.stack()\n", + "fwd_stacked = fwd_20d.stack()\n", + "\n", + "analyzer = FactorAnalyzer(factor_stacked, fwd_stacked)\n", + "ic = analyzer.compute_ic(method='rank')\n", + "\n", + "print('质量因子 IC 汇总:')\n", + "for k, v in analyzer.ic_summary().items():\n", + " print(f' {k}: {v:.4f}')\n", + "\n", + "ic.plot(figsize=(12, 3), title='质量因子 Rank IC 时间序列')\n", + "plt.axhline(y=0, color='r', linestyle='--')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. 分层回测" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "quantile_ret = analyzer.quantile_returns(n_quantiles=5)\n", + "print('各分位组平均收益 (Q1=质量最差, Q5=质量最好):')\n", + "print(quantile_ret)\n", + "\n", + "quantile_ret['avg_return'].plot(kind='bar', figsize=(8, 4), color='steelblue')\n", + "plt.title('质量因子分层收益 (未来20日)')\n", + "plt.ylabel('平均收益率')\n", + "plt.grid(True, alpha=0.3)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. 构建策略并回测" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 回测配置\n", + "config = AlphaConfig(\n", + " initial_cash=1_000_000,\n", + " commission_rate=0.0003,\n", + " slippage=0.001,\n", + " stamp_tax=0.001,\n", + ")\n", + "\n", + "# 质量策略:持有质量因子最高的 20% 股票\n", + "strategy = Strategy(\n", + " name='质量因子策略-ROE+毛利率+低负债',\n", + " factors=[],\n", + " signal_generator=QuantileSignal(\n", + " n_quantiles=5, long_quantile=5, short_quantile=0,\n", + " ),\n", + " weight_allocator=EqualWeightAllocator(max_positions=10),\n", + " description='每月持有ROE高、毛利率高、负债率低的优质公司'\n", + ")\n", + "\n", + "engine = BacktestEngine(config)\n", + "equity_curve = engine.run(\n", + " strategy=strategy,\n", + " price_data=price_data,\n", + " factor_data={'quality': quality_factor},\n", + " rebalance_freq='M',\n", + ")\n", + "\n", + "print(f'回测完成, 共 {len(equity_curve)} 个交易日')\n", + "print(f'累计收益率: {(equity_curve[\"nav\"].iloc[-1] - 1) * 100:.2f}%')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. 净值曲线与回撤" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, axes = plt.subplots(2, 1, figsize=(14, 8))\n", + "\n", + "# 净值曲线\n", + "axes[0].plot(equity_curve.index, equity_curve['nav'], label='策略净值', color='brown')\n", + "axes[0].axhline(y=1.0, color='gray', linestyle='--')\n", + "axes[0].set_title('质量因子策略净值曲线')\n", + "axes[0].legend()\n", + "axes[0].grid(True, alpha=0.3)\n", + "\n", + "# 回撤曲线\n", + "nav = equity_curve['nav']\n", + "running_max = nav.cummax()\n", + "drawdown = (nav - running_max) / running_max\n", + "axes[1].fill_between(equity_curve.index, 0, drawdown.values, color='red', alpha=0.3)\n", + "axes[1].set_title('回撤曲线')\n", + "axes[1].grid(True, alpha=0.3)\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. 绩效评估" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 基准:股票池等权组合\n", + "bench_ret = price_data.pct_change().mean(axis=1)\n", + "\n", + "evaluator = PerformanceEvaluator(equity_curve, benchmark_returns=bench_ret, risk_free_rate=0.03)\n", + "print(evaluator.summary())\n", + "\n", + "report_df = evaluator.full_report()\n", + "pd.DataFrame(list(report_df.items()), columns=['指标', '数值']).set_index('指标')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## 质量分项指标对比\n", + "\n", + "对比各质量指标单独用于选股的效果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 各分项因子 IC 对比\n", + "for name, fac in [\n", + " ('ROE', z_roe),\n", + " ('毛利率', z_gm),\n", + " ('低负债率', z_debt),\n", + " ('质量合成', quality_factor),\n", + "]:\n", + " a = FactorAnalyzer(fac.stack(), fwd_stacked)\n", + " s = a.ic_summary()\n", + " print(f'{name}: IC均值={s[\"IC_Mean\"]:.4f}, ICIR={s[\"IR\"]:.4f}, '\n", + " f'IC>0占比={s[\"IC>0_Ratio\"]:.2%}')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file