feat:添加了基本策略。

This commit is contained in:
2026-08-04 21:38:46 +08:00
parent b9e288b0a6
commit 45a554585b
8 changed files with 2055 additions and 0 deletions
Binary file not shown.
+283
View File
@@ -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)
+296
View File
@@ -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
}
+277
View File
@@ -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
}
+307
View File
@@ -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
}
+274
View File
@@ -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
}
+320
View File
@@ -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
}
+298
View File
@@ -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
}