feat:添加了基本策略。
This commit is contained in:
Binary file not shown.
@@ -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)
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user