小果排序多因子回测系统专家
一、系统概述
使用教程:https://gitcode.com/qq_50882340/xg_rank_factor_backtrader
1.1 系统定位
小果排序多因子回测系统(xg_rank_factor_backtrader)是一套专为排名轮动型多因子策略设计的专业级量化回测框架。它将复杂的多因子选股流程标准化为可配置、可扩展的流水线作业,让研究者能够高效地进行因子挖掘、策略回测和绩效分析。
1.2 设计哲学
- 解耦与复用:将数据加载、因子计算、条件筛选、排序打分、交易执行、绩效评估各环节解耦,每个模块可独立优化和复用。
- 透明与可解释:每个调仓日的排序逻辑、因子权重、得分明细均可追溯,策略决策过程完全透明。
- 灵活与扩展:支持用户自定义因子公式、自定义数据注入(标的 + 指数)、自定义交易规则,满足从简单到复杂的各类策略需求。
1.3 核心回测流程(四步)
第一步 买入条件过滤:按 buy_condi_factor 用 and / or / not 逻辑筛出初步候选池
(is_open_buy_condi=False 时跳过,直接全市场排序)
第二步 排序因子打分:rank_factor 中每个因子得分 = 因子值 × 相关性系数 × 权重
相关性系数:正相关 = +1,负相关 = -1;Bool 因子参与排序时按 1/0 处理
第三步 综合排序取前N:按 total_factor_rank(降序/升序/无)对总分排序,
取前 hold_stock_limit 名形成交易股票池
第四步 先卖后买轮动:持股不在交易股票池 → 卖出;交易池中未持股 → 买入;
受 min_hold_days、止盈(sell_type)、涨跌停过滤、单票仓位上限等约束
1.4 因子值语义约定
- Bool 类型因子(如金叉/死叉、价格在均线上等):
0表示符合条件(True),1表示不符合条件(False),用于买入条件等于 0判定。 - 数值类型因子(连续涨跌天数、价格、动量等):正常数值比较(大于/小于/等于)。
- 买入条件比较时系统会兼容
True/False、'T'/'t'/'TRUE'、'F'/'f'/'FALSE'等布尔形态,1和0始终按数值处理。
1.5 数据文件字段要求
| 字段名 | 说明 | 是否必须 |
|---|---|---|
date |
日期 | ✅ 必须 |
close |
收盘价 | ✅ 必须 |
open |
开盘价 | ✅ 必须 |
high |
最高价 | ✅ 必须 |
low |
最低价 | ✅ 必须 |
volume |
成交量 | ✅ 必须 |
preClose |
前收盘价(用于复权与涨跌停过滤) | ⚠️ 推荐 |
zdf |
涨跌幅(不存在时自动计算) | ⚠️ 推荐 |
amount |
成交金额 | ⚠️ 可选 |
证券代码 |
股票代码(缺失时自动添加) | ⚠️ 可选 |
证券名称 |
股票名称 | ⚠️ 可选 |
> 自定义指数数据列要求:使用 add_index_data_from_* 注入的指数数据只需包含 date 与 close(且 close > 0)。系统会自动识别日期列/日期索引并升序排列;若同时存在 open 列也会执行 open > 0 过滤。注入成功后,回测的指数基准将优先使用注入数据(等价于股票数据的 use_custom_data 自动切换机制)。
二、初始化参数完整参考
> 以下为 __init__ 方法全部参数的详细说明,按功能分组。
2.1 时间与标的 (Time & Universe)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
start_date |
str |
'20250101' |
回测起始日期,格式 YYYYMMDD 或 YYYY-MM-DD |
end_date |
str |
'20261201' |
回测结束日期,格式同上 |
stock_list |
list |
['159915.SZ','513100.SH','518880.SH'] |
候选标的池,元素需带交易所后缀 |
index_stock |
str |
'000300.SH' |
基准指数代码(注入自定义指数数据后以注入数据为准) |
2.2 资金与费用 (Capital & Cost)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
cash |
float |
100000 |
初始资金(元) |
comm |
float |
0.0001 |
手续费率(万1 = 0.0001) |
min_commission |
float |
0 |
单笔最低佣金(元),0表示不设下限 |
slippage |
float |
0 |
滑点比例(0.001 = 千1) |
risk_free_rate |
float |
0.02 |
无风险利率(年化),用于夏普比率计算 |
2.3 交易与仓位 (Trading & Position)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
trader_type |
str |
'百分比' |
买入资金分配方式:'百分比' / '金额' / '数量' |
trader_value |
float |
0.5 |
交易值,含义随 trader_type 变化 |
hold_stock_limit |
int |
2 |
最大持仓数,也是排名取前N的N值 |
max_single_position_ratio |
float |
1.0 |
单只标的占总资产比例上限,范围 (0,1] |
min_hold_days |
int |
1 |
最小持有天数,期间不触发轮动卖出 |
interval |
int |
1 |
调仓间隔(交易日数) |
max_workers |
int |
4 |
数据加载并发线程数 |
2.4 因子与策略 (Factor & Strategy)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
is_open_user_factor |
bool |
True |
是否启用自定义因子计算 |
user_factor_list |
list |
['close','high','low','open','amount','volume','zdf'] |
自定义公式所需的基础列 |
user_factor_cacal |
dict |
内置示例 | 自定义因子公式字典 |
is_open_buy_condi |
bool |
True |
是否启用买入条件过滤 |
buy_condi_factor |
dict |
内置示例 | 买入条件配置字典 |
rank_factor |
dict |
内置示例 | 排序因子配置字典 |
total_factor_rank |
str |
'降序' |
综合得分排序方向:'降序' / '升序' / '无' |
2.5 止盈与风控 (Risk Management)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
sell_type |
str |
'金额' |
止盈类型:'none' / '金额' / '数量' / '百分比' |
sell_zdf |
float |
0.03 |
触发止盈的收益率阈值 |
sell_value |
float |
1000 |
止盈卖出的值 |
enable_limit_up_down_filter |
bool |
True |
是否过滤涨跌停(|涨跌幅|≥9.5% 剔除) |
use_custom_data |
bool |
False |
是否使用内存注入数据(注入成功会自动切换) |
2.6 trader_type 与 trader_value 匹配表
trader_type |
trader_value 含义 |
买入金额计算逻辑 |
|---|---|---|
'百分比' |
总资产比例 | 每只买入 ≈ 总资产 × 比例 ÷ 待买数量 |
'金额' |
固定总金额 | 每只买入 ≈ 固定金额 ÷ 待买数量 |
'数量' |
固定总股数 | 每只买入 ≈ 固定股数 |
2.7 sell_type 与 sell_value 匹配表
sell_type |
sell_value 含义 |
触发后行为 |
|---|---|---|
'none' |
忽略 | 不止盈,仅做排名轮动卖出 |
'金额' |
卖出市值金额 | 卖出约指定金额,不足则全卖 |
'数量' |
卖出股数 | 卖出指定股数,不足则全卖 |
'百分比' |
卖出仓位比例 | 卖出指定比例,≥1 则全卖 |
| 其他值 | 忽略 | 全部卖出 |
三、自定义因子公式引擎详解
3.1 公式编写规范
- 数据对象:公式中固定使用
df代表当前股票的全量日线 DataFrame。 - 函数库:自动导入
xg_tdx_func中全部函数,无需额外 import。 - 返回值:支持
pd.Series(长度与 df 一致)、int/float(广播为常量列)、或可转换为 Series 的对象。 - 命名约束:自定义因子名不可与已有列名重复。
- 执行方式:每只股票独立
eval计算,公式异常会被静默跳过(可通过verbose观察结果列确认是否生成成功)。
3.2 支持的常用指标函数速查
趋势类指标
| 函数 | 说明 | 示例 |
|---|---|---|
MA(series, n) |
简单移动平均 | MA(df['close'], 5) |
EMA(series, n) |
指数移动平均 | EMA(df['close'], 12) |
MACD(close, fast, slow, signal) |
MACD指标 | MACD(df['close'], 12, 26, 9) |
BOLL(close, n, k) |
布林带 | BOLL(df['close'], 20, 2) |
SAR(high, low, step, max) |
抛物线转向 | SAR(df['high'], df['low'], 0.02, 0.2) |
超买超卖类
| 函数 | 说明 | 示例 |
|---|---|---|
RSI(close, n) |
相对强弱指标 | RSI(df['close'], 14) |
KDJ(high, low, close, n, m1, m2) |
KDJ随机指标 | KDJ(df['high'], df['low'], df['close'], 9, 3, 3) |
CCI(high, low, close, n) |
顺势指标 | CCI(df['high'], df['low'], df['close'], 14) |
WR(high, low, close, n) |
威廉指标 | WR(df['high'], df['low'], df['close'], 14) |
量能类指标
| 函数 | 说明 | 示例 |
|---|---|---|
OBV(close, volume) |
能量潮 | OBV(df['close'], df['volume']) |
MFI(high, low, close, volume, n) |
资金流量指标 | MFI(df['high'], df['low'], df['close'], df['volume'], 14) |
VR(volume, n) |
成交量比率 | VR(df['volume'], 24) |
波动类指标
| 函数 | 说明 | 示例 |
|---|---|---|
STD(series, n) |
n周期标准差 | STD(df['close'], 20) |
逻辑与形态类
| 函数 | 说明 | 示例 |
|---|---|---|
CROSS(series1, series2) |
上穿金叉 | CROSS(MA(df['close'],5), MA(df['close'],10)) |
CROSS_DOWN(series1, series2) |
下穿死叉 | CROSS_DOWN(MA(df['close'],5), MA(df['close'],10)) |
REF(series, n) |
引用n周期前数据 | REF(df['close'], 5) |
HHV(series, n) |
n周期最高值 | HHV(df['high'], 20) |
LLV(series, n) |
n周期最低值 | LLV(df['low'], 20) |
COUNT(condition, n) |
n周期内条件成立次数 | COUNT(df['close'] > MA(df['close'],5), 10) |
EVERY(condition, n) |
n周期内条件始终成立 | EVERY(df['close'] > MA(df['close'],5), 5) |
IF(condition, true_val, false_val) |
条件判断 | IF(df['close'] > MA(df['close'],5), 1, 0) |
3.3 典型自定义因子示例
user_factor_cacal = {
# 动量因子:20日涨跌幅
"动量_20日": "df['close'] / REF(df['close'], 20) - 1",
# 波动因子:20日标准差
"波动率_20日": "STD(df['close'], 20)",
# 均线乖离率
"乖离率_5日": "(df['close'] - MA(df['close'], 5)) / MA(df['close'], 5)",
# 均线多头排列评分 (0-100)
"均线多头评分": (
"IF(MA(df['close'],3) > MA(df['close'],5), 25, 0) + "
"IF(MA(df['close'],5) > MA(df['close'],10), 25, 0) + "
"IF(MA(df['close'],10) > MA(df['close'],20), 25, 0) + "
"IF(MA(df['close'],20) > MA(df['close'],30), 25, 0)"
),
# MACD金叉信号 (0=符合)
"MACD金叉": "IF(CROSS(MACD(df['close'],12,26,9)[0], MACD(df['close'],12,26,9)[1]), 0, 1)",
# RSI超卖信号 (0=符合)
"RSI超卖": "IF(RSI(df['close'],14) < 30, 0, 1)",
# 成交量放大
"成交量放大": "IF(df['volume'] > MA(df['volume'],5) * 1.5, 1, 0)",
}
四、策略配置详解与最佳实践
4.1 买入条件配置 (buy_condi_factor)
配置格式
buy_condi_factor = {
"因子名A": {"选择类型": "and|or|not", "选择方向": "等于|大于|小于", "值": 目标值},
}
逻辑组合规则
and:所有 and 条件必须同时满足。or:同组 or 条件至少满足其一。not:条件必须不满足。
最佳实践
- 对于布尔型因子,推荐使用
0表示符合条件(True),1表示不符合(False)。 - 避免使用过多条件导致候选池过小,建议控制在 3-5 个核心条件。
- 可以使用
or组合相似的替代条件,增加候选池的鲁棒性。
4.2 排序因子配置 (rank_factor)
配置格式
rank_factor = {
"因子名A": {"相关性": "正相关|负相关", "权重": 权重值},
}
得分计算公式
单因子得分 = 因子值 × 相关性系数 × 权重
综合得分 = Σ(所有排序因子的单因子得分)
其中:相关性系数:正相关 = +1,负相关 = -1
最佳实践
- 权重值反映因子的相对重要性,建议使用 1-10 之间的整数。
- 布尔型因子的权重建议设为 1-2,避免过度影响得分。
- 避免使用高度相关的因子组合,以防多重共线性。
- 建议同时包含趋势类(动量)、均值回复类(波动率)、质量类(ROE)等不同类型的因子。
4.3 交易参数调优建议
| 参数 | 建议范围 | 说明 |
|---|---|---|
interval |
1-20 | 日频策略用1-5,周频策略用5-10 |
min_hold_days |
1-10 | 增加此值可降低换手率 |
hold_stock_limit |
5-30 | 越少越集中,越多越分散 |
max_single_position_ratio |
0.1-0.3 | 控制单只标的集中度风险 |
sell_zdf |
0.05-0.30 | 止盈阈值,与策略周期匹配 |
五、完整函数清单(含内部方法)
> 框架类 xg_rank_factor_backtrader 共 62 个核心方法 + 10 个指数数据接口(合计 72 个),以下按功能模块列出,便于二次开发和深度集成。
5.1 初始化和配置管理
| 方法签名 | 说明 |
|---|---|
__init__(start_date, end_date, stock_list, ...) |
构造回测实例,配置全部参数 |
_validate_factor_config() |
验证买入因子和排序因子配置的格式合法性 |
_validate_params() |
验证参数边界(如持仓数>0、日期范围等) |
_print_config() |
打印完整的初始化配置信息 |
get_config() -> dict |
获取当前策略的全部配置参数 |
5.2 数据加载与注入
| 方法签名 | 说明 |
|---|---|
get_stock_data(stock_code: str) -> pd.DataFrame |
获取单只股票数据(优先自定义数据,其次本地文件) |
get_all_stock_data() -> dict |
多线程并发加载全部标的的历史数据 |
get_index_data() -> pd.DataFrame |
获取指数数据:优先返回注入的自定义指数数据(has_index_data() 为 True 时),否则加载本地指数文件(带缓存) |
_load_single_stock(stock: str) -> tuple |
多线程任务:加载单只股票数据 |
_normalize_dataframe(df: pd.DataFrame, stock_code: str) -> pd.DataFrame |
标准化注入的DataFrame(日期处理、列补齐、复权计算) |
adjust_price(df: pd.DataFrame) -> pd.DataFrame |
价格复权处理(前复权/后复权) |
_calculate_buy_hold_net_value(stock_data: dict) -> tuple |
计算等权持有不动策略净值 |
_calculate_index_net_value(index_data: pd.DataFrame) -> tuple |
计算指数净值 |
5.3 自定义数据注入接口(7种)
| 方法签名 | 说明 |
|---|---|
add_stock_data_from_dataframe(stock_code: str, df: pd.DataFrame) -> bool |
从DataFrame注入数据 |
add_stock_data_from_excel(stock_code: str, file_path: str, sheet_name=0, **kwargs) -> bool |
从Excel文件注入 |
add_stock_data_from_csv(stock_code: str, file_path: str, **kwargs) -> bool |
从CSV文件注入(自动尝试编码) |
add_stock_data_from_json(stock_code: str, file_path: str, orient='records', **kwargs) -> bool |
从JSON文件注入 |
add_stock_data_from_dict(stock_code: str, data: dict) -> bool |
从字典注入 |
add_stock_data_from_parquet(stock_code: str, file_path: str, columns=None, **kwargs) -> bool |
从Parquet文件注入 |
add_stock_data_from_bytes(stock_code: str, file_bytes: bytes, file_type: str='csv', **kwargs) -> bool |
从字节流注入(适合文件上传) |
5.4 自定义数据管理
| 方法签名 | 说明 |
|---|---|
clear_custom_data() |
清空所有已注入的自定义数据 |
remove_stock_data(stock_code: str) -> bool |
移除指定股票的自定义数据 |
get_custom_data_keys() -> list |
获取所有已注入数据的股票代码列表 |
has_custom_data(stock_code: str) -> bool |
检查指定股票是否已有自定义数据 |
5.5 指数数据注入接口(7种)
> 将自定义指数数据注入内存,注入成功后 has_index_data() 为 True,回测的指数基准优先使用注入数据(等价于股票数据 use_custom_data 机制,自动切换)。数据只需 date、close 两列。
| 方法签名 | 说明 |
|---|---|
add_index_data_from_dataframe(df: pd.DataFrame) -> bool |
从DataFrame加载指数数据 |
add_index_data_from_csv(file_path: str, **kwargs) -> bool |
从CSV文件加载指数数据(自动尝试多编码) |
add_index_data_from_excel(file_path: str, sheet_name=0, **kwargs) -> bool |
从Excel文件加载指数数据 |
add_index_data_from_json(file_path: str, orient='records', **kwargs) -> bool |
从JSON文件加载指数数据 |
add_index_data_from_dict(data: dict) -> bool |
从字典加载指数数据(格式 {'date': [...], 'close': [...]}) |
add_index_data_from_parquet(file_path: str, columns=None, **kwargs) -> bool |
从Parquet文件加载指数数据 |
add_index_data_from_bytes(file_bytes: bytes, file_type: str='csv', **kwargs) -> bool |
从字节数据加载指数数据(file_type 支持 csv/excel/json,适合上传场景) |
5.6 指数数据管理
| 方法签名 | 说明 |
|---|---|
clear_index_data() |
清空自定义指数数据(恢复使用本地指数文件) |
has_index_data() -> bool |
检查是否已有自定义指数数据 |
get_index_data_info() -> dict |
获取指数数据信息:{'has_data': bool, 'rows': int, 'start_date': str, 'end_date': str, 'columns': list}(无数据时返回 {'has_data': False}) |
5.7 因子计算与条件判定
| 方法签名 | 说明 |
|---|---|
_normalize_factor_name(factor_name: str, df_columns: list) -> str |
规范化因子名称(处理空格、下划线等) |
_is_bool_string(value) -> bool |
判断字符串是否为布尔字符串('T','true','F','false') |
_is_bool_like(value) -> bool |
判断值是否为布尔类型(含字符串布尔) |
_convert_to_bool(value) -> bool |
将各种形式的布尔值转换为Python布尔 |
_convert_factor_value_for_rank(value) -> float |
将因子值转换为排序用数值(布尔转0/1) |
_check_factor_condition_single(value, target, direction) -> bool |
检查单个因子条件 |
check_factor_condition(df: pd.DataFrame, factor_name: str, config: dict) -> pd.Series |
检查因子条件,返回布尔掩码 |
filter_by_buy_conditions(df_date: pd.DataFrame) -> list |
应用买入条件过滤,返回符合条件的股票列表 |
calculate_rank_score(df_date: pd.DataFrame, stock_list: list) -> pd.Series |
计算排序因子加权综合得分 |
get_trading_stocks(df_date: pd.DataFrame) -> tuple |
获取交易日期的目标交易股票池及得分 |
5.8 交易执行与计算
| 方法签名 | 说明 |
|---|---|
calculate_commission(amount: float) -> float |
计算交易佣金(含最低佣金) |
calculate_buy_shares(amount: float, price: float) -> tuple |
计算买入股数和实际成本(含滑点) |
calculate_sell_shares_by_amount(shares: int, price: float, sell_amount: float) -> tuple |
按金额计算卖出股数和实得金额 |
calculate_sell_shares_by_percent(shares: int, price: float, sell_percent: float) -> tuple |
按比例计算卖出股数和实得金额 |
calculate_sell_shares_by_count(shares: int, price: float, sell_count: int) -> tuple |
按数量计算卖出股数和实得金额 |
calculate_sell_shares_all(shares: int, price: float) -> tuple |
全部卖出计算 |
check_take_profit(stock: str, current_price: float, buy_price: float, shares: int) -> tuple |
止盈条件检查与卖出计算 |
_record_daily_position(date, cash, positions, factor_maps, merged_records) |
记录每日持仓和账户状态 |
5.9 回测执行
| 方法签名 | 说明 |
|---|---|
run_backtest() -> dict |
执行完整回测流程,返回绩效指标 |
_print_results() |
打印回测结果摘要 |
5.10 数据生成与转换
| 方法签名 | 说明 |
|---|---|
_convert_to_serializable(obj) |
将对象转换为JSON可序列化格式 |
_parse_date(date_str) -> pd.Timestamp |
灵活的日期解析函数 |
generate_equity_curve_data() -> dict |
生成净值曲线数据(含策略/基准/指数净值) |
generate_trade_data() -> dict |
生成成交明细数据 |
generate_position_data() -> dict |
生成每日持仓数据 |
generate_account_data() -> dict |
生成每日账户数据 |
5.11 绩效评估与报告
| 方法签名 | 说明 |
|---|---|
calculate_performance_metrics(index_data: pd.DataFrame = None) -> dict |
计算核心绩效指标 |
calculate_annual_performance() -> dict |
计算年度收益统计 |
get_performance_metrics() -> dict |
获取绩效指标(自动触发回测) |
get_equity_curve_data() -> dict |
获取净值曲线数据 |
get_trade_data() -> dict |
获取成交数据 |
get_position_data() -> dict |
获取持仓数据 |
get_account_data() -> dict |
获取账户数据 |
get_annual_performance() -> dict |
获取年度收益统计 |
get_backtrader_data() -> dict |
获取回测全量数据 |
generate_report() -> str |
生成格式化文本报告 |
save_to_json(filename: str = None) -> str |
保存回测结果为JSON文件 |
save_backtrader_data(user: str = 'test') -> str |
分类保存回测结果为Excel+JSON |
六、输出数据结构详解
> 框架共提供 8 类对外输出数据,均由回测结束后生成(首次调用 get_* 或 run_backtest 时自动触发缓存)。
6.1 绩效指标字段 (performance_metrics)
| 字段 | 类型 | 说明 |
|---|---|---|
start_date / end_date |
str |
回测起止日期 |
total_days |
int |
交易总天数 |
final_value |
float |
最终总资产 |
total_pnl |
float |
总盈亏额 |
total_return |
float |
总收益率(小数,如0.25=25%) |
annual_return |
float |
年化收益率 |
max_drawdown |
float |
最大回撤(负数) |
sharpe_ratio |
float |
夏普比率 |
volatility |
float |
年化波动率 |
total_trades |
int |
总交易笔数 |
win_rate |
float |
胜率 |
profit_loss_ratio |
float |
盈亏比 |
final_holdings |
float |
期末持仓总股数 |
remaining_cash |
float |
期末剩余现金 |
benchmark_final_value |
float |
基准(持有不动)期末净值 |
benchmark_total_return |
float |
基准(持有不动)收益率 |
index_final_value |
float |
指数期末净值 |
index_total_return |
float |
指数收益率 |
excess_return |
float |
相对基准的超额收益 |
6.2 年度收益统计字段 (annual_performance)
> 按自然年分组统计,get_annual_performance() / get_backtrader_data() 返回。键为年份字符串,值为如下字段:
| 字段 | 类型 | 说明 |
|---|---|---|
year |
int |
年份 |
start_date / end_date |
str |
当年统计起止日期 |
total_days |
int |
当年交易日数 |
total_return |
float |
当年总收益率(小数) |
annual_return |
float |
当年收益(按区间计算) |
max_drawdown |
float |
当年最大回撤(负数) |
sharpe_ratio |
float |
当年夏普比率 |
sortino_ratio |
float |
当年索提诺比率(近似 = 夏普×0.9) |
calmar_ratio |
float |
当年卡玛比率(收益/回撤绝对值) |
volatility |
float |
当年年化波动率 |
win_rate |
float |
胜率(简化统计) |
benchmark_total_return |
float |
当年基准(持有不动)收益率 |
index_total_return |
float |
当年指数收益率 |
excess_return / excess_return_pct |
float |
相对基准超额收益(小数 / 百分数) |
6.3 净值曲线字段 (equity_curve_data)
| 字段 | 类型 | 说明 |
|---|---|---|
dates |
list |
日期字符串列表 |
total_value |
list |
每日总资产 |
cumulative_pnl |
list |
累计盈亏额 |
cash |
list |
每日现金余额 |
holdings |
list |
每日持仓总股数 |
net_value |
list |
策略净值(总资产/初始资金) |
drawdown |
list |
回撤百分比 |
total_return |
list |
累计收益率(小数) |
daily_return |
list |
日收益率 |
benchmark_net_value |
list |
基准(持有不动)净值 |
index_net_value |
list |
指数净值 |
6.4 成交记录字段 (trade_data / trade_log)
> get_trade_data() 返回 {'trades': [...], 'statistics': {...}}。其中 trades 元素(即 trade_log 明细)字段如下:
| 字段 | 类型 | 说明 |
|---|---|---|
date |
str |
交易日期 |
stock |
str |
股票代码 |
type |
str |
交易类型(买入 / 调仓卖出 / 止盈卖出) |
price |
float |
成交价格 |
shares |
int |
成交股数 |
amount |
float |
成交金额 |
commission |
float |
手续费 |
reason |
str |
交易原因(如"排名买入(排名:2,得分:3.5)"、"排名轮动: 不在交易股票池"、"金额止盈 卖出1000元") |
statistics 子结构:
| 字段 | 类型 | 说明 |
|---|---|---|
total_trades |
int |
总交易笔数 |
buy_trades |
int |
买入笔数 |
sell_trades |
int |
卖出笔数 |
total_commission |
float |
累计手续费 |
6.5 持仓数据字段 (position_data)
> get_position_data() 返回 {'positions': [...], 'statistics': {'total_days': N}},每日一行。
| 字段 | 类型 | 说明 |
|---|---|---|
date |
str |
日期 |
holdings |
float |
当日持仓总股数 |
cash |
float |
当日现金余额 |
total_value |
float |
当日总资产 |
cumulative_pnl |
float |
累计盈亏额 |
total_return |
float |
累计收益率(百分数) |
details |
str |
当日逐票持仓明细(代码:股数,分号分隔) |
6.6 账户数据字段 (account_data)
> get_account_data() 返回 {'account_history': [...], 'statistics': {}},每日一行。
| 字段 | 类型 | 说明 |
|---|---|---|
date |
str |
日期 |
total_value |
float |
当日总资产 |
cash |
float |
当日现金余额 |
holdings |
float |
当日持仓总股数 |
cumulative_pnl |
float |
累计盈亏额 |
net_value |
float |
策略净值 |
total_return |
float |
累计收益率(百分数) |
daily_return |
float |
日收益率(百分数) |
benchmark_net_value |
float |
基准(持有不动)净值 |
index_net_value |
float |
指数净值 |
6.7 导出目录结构
> save_backtrader_data(user='xxx') 将回测结果分类保存为 Excel + JSON 双格式;save_to_json() 保存单文件全量 JSON;get_backtrader_data() 内存中返回同构数据。指数数据目录保存的是回测实际使用的指数数据(注入的自定义指数数据或本地指数)。
策略模型/排序多因子策略/<user>/
├── 指数数据/
│ ├── 指数数据.xlsx
│ └── 指数数据.json
├── 成交数据/
│ ├── 成交数据.xlsx
│ └── 成交数据.json
├── 持股数据/
│ ├── 持股数据.xlsx
│ └── 持股数据.json
├── 账户数据/
│ ├── 账户数据.xlsx
│ └── 账户数据.json
├── 绩效指标/
│ ├── 绩效指标.xlsx
│ └── 绩效指标.json
├── 净值曲线/
│ ├── 净值曲线.xlsx
│ └── 净值曲线.json
├── 策略参数/
│ ├── 策略参数.xlsx
│ └── 策略参数.json
└── 年度收益统计/
├── 年度收益统计.xlsx
└── 年度收益统计.json
七、完整实战示例合集
7.1 示例1:均线多头 + 动量排序轮动策略
from xg_rank_factor_backtrader.xg_rank_factor_backtrader import xg_rank_factor_backtrader
bt = xg_rank_factor_backtrader(
start_date='20230101',
end_date='20261201',
stock_list=['159915.SZ', '513100.SH', '518880.SH', '510300.SH', '512880.SH', '159949.SZ'],
index_stock='000300.SH',
cash=500000,
comm=0.0003,
trader_type='百分比',
trader_value=0.8,
hold_stock_limit=4,
max_single_position_ratio=0.3,
interval=5,
min_hold_days=3,
is_open_user_factor=True,
user_factor_list=['close', 'open', 'high', 'low', 'volume', 'zdf', 'amount'],
user_factor_cacal={
"收盘价大于5日均线": "IF(df['close'] > MA(df['close'], 5), 0, 1)",
"5日均线在10日均线上": "IF(MA(df['close'],5) > MA(df['close'],10), 0, 1)",
"20日动量": "df['close'] / REF(df['close'], 20) - 1",
"60日动量": "df['close'] / REF(df['close'], 60) - 1",
"均线多头评分": (
"IF(MA(df['close'],3) > MA(df['close'],5), 20, 0) + "
"IF(MA(df['close'],5) > MA(df['close'],10), 20, 0) + "
"IF(MA(df['close'],10) > MA(df['close'],20), 20, 0) + "
"IF(MA(df['close'],20) > MA(df['close'],30), 20, 0) + "
"IF(MA(df['close'],30) > MA(df['close'],60), 20, 0)"
),
"RSI_14": "RSI(df['close'], 14)",
"波动率_20日": "STD(df['close'], 20)",
},
is_open_buy_condi=True,
buy_condi_factor={
"收盘价大于5日均线": {"选择类型": "and", "选择方向": "等于", "值": 0},
"5日均线在10日均线上": {"选择类型": "and", "选择方向": "等于", "值": 0},
"20日动量": {"选择类型": "and", "选择方向": "大于", "值": 0.01},
},
rank_factor={
"均线多头评分": {"相关性": "正相关", "权重": 2},
"20日动量": {"相关性": "正相关", "权重": 3},
"60日动量": {"相关性": "正相关", "权重": 2},
"RSI_14": {"相关性": "正相关", "权重": 1},
"波动率_20日": {"相关性": "负相关", "权重": 1},
},
total_factor_rank='降序',
sell_type='百分比',
sell_zdf=0.15,
sell_value=0.3, # 涨15%后卖出30%仓位
enable_limit_up_down_filter=True,
max_workers=8,
)
metrics = bt.run_backtest()
print(bt.generate_report())
bt.save_backtrader_data(user="均线动量策略")
7.2 示例2:纯因子排名轮动(关闭买入过滤)
bt = xg_rank_factor_backtrader(
start_date='20240101',
end_date='20261201',
stock_list=['159915.SZ', '513100.SH', '518880.SH', '510300.SH', '512880.SH'],
cash=200000,
hold_stock_limit=3,
interval=3,
min_hold_days=2,
is_open_buy_condi=False, # 关键:关闭买入条件筛选
is_open_user_factor=True,
user_factor_cacal={
"20日涨跌幅": "df['close'] / REF(df['close'], 20) - 1",
"5日涨跌幅": "df['close'] / REF(df['close'], 5) - 1",
"20日标准差": "STD(df['close'], 20)",
"换手率": "df['volume'] / MA(df['volume'], 20)", # 需数据含volume
},
rank_factor={
"20日涨跌幅": {"相关性": "正相关", "权重": 2},
"5日涨跌幅": {"相关性": "正相关", "权重": 1},
"20日标准差": {"相关性": "负相关", "权重": 1},
},
total_factor_rank='降序',
trader_type='金额',
trader_value=50000,
sell_type='none',
)
bt.run_backtest()
print(bt.generate_report())
7.3 示例3:自定义数据注入 + 多因子回测
import pandas as pd
# 准备数据(假设已有CSV文件)
bt = xg_rank_factor_backtrader(
start_date='20230101',
end_date='20261201',
stock_list=['159915.SZ', '513100.SH', '518880.SH', '510300.SH'],
cash=300000,
hold_stock_limit=2,
is_open_buy_condi=False,
is_open_user_factor=True,
user_factor_cacal={
"RSI_14": "RSI(df['close'], 14)",
"MACD金叉": "IF(CROSS(MACD(df['close'],12,26,9)[0], MACD(df['close'],12,26,9)[1]), 0, 1)",
},
rank_factor={
"RSI_14": {"相关性": "正相关", "权重": 1},
"MACD金叉": {"相关性": "正相关", "权重": 2},
},
total_factor_rank='降序',
sell_type='金额',
sell_zdf=0.05,
sell_value=2000,
)
# 注入自定义数据
bt.add_stock_data_from_csv('159915.SZ', 'data/159915.csv')
bt.add_stock_data_from_excel('513100.SH', 'data/513100.xlsx', sheet_name='Sheet1')
bt.add_stock_data_from_json('518880.SH', 'data/518880.json')
bt.add_stock_data_from_dataframe('510300.SH', pd.read_parquet('data/510300.parquet'))
bt.run_backtest()
print(bt.generate_report())
bt.save_to_json('result/multi_factor_backtest.json')
7.4 示例4:提取回测结果进行二次分析
# 运行回测
bt.run_backtest()
# 获取各项数据
metrics = bt.get_performance_metrics()
curve = bt.get_equity_curve_data()
trades = bt.get_trade_data()
positions = bt.get_position_data()
# 打印关键指标
print(f"总收益率: {metrics['total_return']*100:.2f}%")
print(f"年化收益率: {metrics['annual_return']*100:.2f}%")
print(f"最大回撤: {metrics['max_drawdown']*100:.2f}%")
print(f"夏普比率: {metrics['sharpe_ratio']:.3f}")
print(f"胜率: {metrics['win_rate']*100:.2f}%")
print(f"交易笔数: {metrics['total_trades']}")
# 使用matplotlib绘制净值曲线
import matplotlib.pyplot as plt
dates = curve['dates']
plt.figure(figsize=(12, 6))
plt.plot(dates, curve['net_value'], label='策略净值', linewidth=2)
plt.plot(dates, curve['benchmark_net_value'], label='基准(持有不动)', linewidth=1.5, linestyle='--')
if curve.get('index_net_value'):
plt.plot(dates, curve['index_net_value'], label='指数', linewidth=1.5, linestyle=':')
plt.legend()
plt.xlabel('日期')
plt.ylabel('净值')
plt.title('策略净值 vs 基准 vs 指数')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
# 绘制回撤曲线
plt.figure(figsize=(12, 4))
plt.fill_between(dates, 0, curve['drawdown'], color='red', alpha=0.3)
plt.plot(dates, curve['drawdown'], color='red', linewidth=1)
plt.xlabel('日期')
plt.ylabel('回撤 (%)')
plt.title('策略回撤曲线')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
7.5 示例5:注入自定义指数数据做基准比较
bt = xg_rank_factor_backtrader(
start_date='20240101',
end_date='20261201',
stock_list=['159915.SZ', '513100.SH', '518880.SH'],
index_stock='000300.SH', # 注入自定义指数数据后,以注入数据为准
cash=100000,
hold_stock_limit=2,
)
# 注入自定义指数数据(date + close 两列即可,均支持)
bt.add_index_data_from_csv('data/index_000300.csv') # CSV(自动尝试编码)
# bt.add_index_data_from_dataframe(df_index) # 或 DataFrame
# bt.add_index_data_from_excel('data/index.xlsx') # 或 Excel
# bt.add_index_data_from_json('data/index.json') # 或 JSON
# bt.add_index_data_from_dict({'date': [...], 'close': [...]}) # 或 字典
# bt.add_index_data_from_parquet('data/index.parquet') # 或 Parquet
# 校验注入结果
print(bt.has_index_data()) # True
print(bt.get_index_data_info()) # {'has_data': True, 'rows': N, 'start_date':..., 'end_date':..., 'columns': [...]}
bt.run_backtest()
print(bt.generate_report())
# 如需恢复使用本地指数文件
# bt.clear_index_data()
八、高级技巧与性能优化
8.1 因子组合优化建议
- 因子多样性:组合使用趋势因子(动量)、均值回复因子(波动率)、质量因子(ROE/毛利率)、情绪因子(换手率)等不同类型。
- 因子正交化:避免使用高度相关的因子(如5日涨跌幅和10日涨跌幅),可考虑做中性化处理。
- 权重调优:使用网格搜索或贝叶斯优化寻找最优权重组合。
8.2 交易成本控制
- 降低换手:增大
interval和min_hold_days可显著降低交易频率。 - 滑点敏感度:对流动性较差的标的,适当调高
slippage参数。 - 佣金优化:
min_commission可模拟券商最低收费,避免小额交易被高佣金侵蚀。
8.3 数据加载优化
- 列裁剪:
user_factor_list只配置必要的列,减少IO开销。 - 并行加载:
max_workers可根据CPU核心数设置(建议不超过8)。 - 数据缓存:
get_stock_data和get_index_data内置缓存,避免重复读取;指数注入数据命中缓存时不再读取本地文件。
8.4 策略鲁棒性提升
- 涨跌停过滤:开启
enable_limit_up_down_filter避免无法交易的样本。 - 最小持有天数:防止策略过度响应短期噪声。
- 仓位上限:
max_single_position_ratio防止单只标的过度集中。
九、常见问题与解决方案 (FAQ)
| 问题 | 解决方案 |
|---|---|
| Q1: 提示"数据文件不存在" | 检查 data/历史数据 或 data/全部因子数据 目录下是否有 {代码}.parquet,或使用 add_stock_data_from_* 注入数据。 |
| Q2: 排序因子大量缺失 | 检查因子名是否在数据列或 user_factor_cacal 中定义,查看 generate_report() 尾部的因子缺失警告。 |
| Q3: 买入条件配置后候选池为空 | 检查条件是否过于严格;确认布尔因子使用 0 表示符合条件;可暂时关闭买入条件进行调试。 |
| Q4: 交易笔数异常多 | 增大 interval 和 min_hold_days;减少排序因子的数量或降低其敏感性。 |
| Q5: 自定义数据注入后仍走本地目录 | 确认注入返回 True,使用 get_custom_data_keys() 验证,系统会自动切换 use_custom_data=True。 |
| Q6: 收益率为0或异常 | 检查数据价格列是否全为正数;确认 cash 初始值设置正确;检查买入逻辑是否成功执行。 |
| Q7: 布尔因子在排序中失去区分度 | 系统已将布尔值转为0/1,确保权重设置合理;建议布尔因子权重设为1-2。 |
| Q8: 如何查看某个调仓日的详细排序 | 在回测时设置 verbose=True(默认),控制台会打印每个调仓日的排序详情。 |
| Q9: 自定义指数数据注入后未生效 | 确认注入返回 True,用 has_index_data() / get_index_data_info() 验证;数据必须含 date/close 且 close > 0,否则注入会失败。 |
十、扩展开发指南
10.1 自定义新的技术指标
在 xg_tdx_func 库中添加新函数,然后在 user_factor_cacal 中直接调用。
10.2 自定义交易逻辑
继承 xg_rank_factor_backtrader 类,重写以下核心方法:
get_trading_stocks():修改交易股票池的选取逻辑check_take_profit():自定义止盈/止损规则calculate_buy_shares():自定义仓位分配算法
10.3 自定义绩效指标
在 calculate_performance_metrics() 中添加新的指标计算逻辑。
1. 指数数据读取接口
新增了以下方法用于加载自定义指数数据:
方法 说明
add_index_data_from_dataframe(df) 从DataFrame加载指数数据
add_index_data_from_csv(file_path) 从CSV文件加载指数数据
add_index_data_from_excel(file_path, sheet_name) 从Excel文件加载指数数据
add_index_data_from_json(file_path) 从JSON文件加载指数数据
add_index_data_from_dict(data) 从字典加载指数数据
add_index_data_from_parquet(file_path) 从Parquet文件加载指数数据
add_index_data_from_bytes(bytes, file_type) 从字节数据加载指数数据
2. 指数数据管理方法
方法 说明
clear_index_data() 清空自定义指数数据
has_index_data() 检查是否有自定义指数数据
get_index_data_info() 获取指数数据信息
小果排序多因子回测系统,让多因子策略开发更高效、更透明。如需源码、更多因子库或技术支持,欢迎联系小果(微信:xg_quant)。