8.回测系统详解
本文将深入探讨 QLib 量化投资平台中的回测系统,包括其核心架构、参数配置方法、交易成本模型以及结果分析工具。回测是量化策略开发过程中不可或缺的环节,它能够帮助我们评估策略的历史表现,验证策略逻辑的有效性,并为实盘交易提供参考依据。通过本章的学习,你将能够掌握 QLib 回测系统的使用方法,构建科学合理的回测流程,并对回测结果进行深入分析。
回测引擎架构
QLib 的回测系统采用了模块化的设计理念,将整个回测流程分解为多个独立的组件,这些组件协同工作,共同完成从策略信号到最终绩效评估的全过程。理解回测引擎的架构有助于我们更好地配置回测参数,排查回测过程中出现的问题,并对回测结果进行合理的解读。
核心组件
QLib 回测引擎主要由以下几个核心组件构成:
- 策略(Strategy) :负责生成交易信号和目标头寸。策略接收预测模型输出的信号,结合特定的交易规则,生成每日的交易决策。
- 执行器(Executor) :负责订单的生成、执行和管理。它根据策略输出的目标头寸,结合当前持仓和市场条件,生成具体的交易订单,并模拟订单的执行过程。
- 回测器(Backtester) :协调策略和执行器的工作,控制回测流程的时序推进。它负责在每个交易日调用策略生成交易决策,然后交由执行器执行,并记录交易结果。
- 绩效分析器(Analyzer) :负责回测结果的统计和分析。它收集回测过程中的交易数据,计算各种绩效指标,并生成可视化报告。
- 数据接口(Data Interface) :为回测提供市场数据支持,包括行情数据、财务数据等。它确保回测过程中使用的数据与实际交易环境一致。
这些组件之间通过明确定义的接口进行通信,形成了一个松耦合的系统架构。这种设计使得每个组件都可以独立开发、测试和优化,同时也方便用户根据自己的需求替换或扩展特定组件。
回测流程
QLib 回测引擎的工作流程可以概括为以下几个步骤:
- 初始化:加载历史数据,初始化策略、执行器和其他组件。
- 循环遍历交易日:按照时间顺序,逐个处理每个交易日。
- 生成交易决策:在每个交易日,调用策略的
generate_trade_decision方法,生成目标头寸。 - 执行交易:执行器根据目标头寸和当前持仓,生成并执行交易订单。
- 更新账户状态:根据交易结果,更新账户的现金、持仓等信息。
- 记录交易数据:记录每日的交易情况、持仓变化和账户净值等数据。
- 绩效分析:回测结束后,对记录的交易数据进行分析,生成绩效报告。
下面的流程图展示了 QLib 回测引擎的工作流程:
flowchart TD
A[初始化] --> B[加载历史数据]
B --> C[初始化策略和执行器]
C --> D[开始回测循环]
D --> E[获取当日市场数据]
E --> F[策略生成目标头寸]
F --> G[执行器生成订单]
G --> H[模拟订单执行]
H --> I[更新账户状态]
I --> J[记录交易数据]
J --> K{是否还有下一个交易日}
K -->|是| D
K -->|否| L[绩效分析]
L --> M[生成回测报告]
这种流程化的设计确保了回测过程的透明性和可重复性。每个步骤的输出都可以被记录和检查,便于调试和优化策略。
执行器详解
执行器(Executor)是回测引擎的核心组件之一,它负责将策略生成的目标头寸转换为实际的交易订单,并模拟订单在市场中的执行过程。QLib 提供了 SimulatorExecutor 作为默认的执行器,它能够模拟现实市场中的多种交易规则和约束。
SimulatorExecutor 的主要功能包括:
- 订单生成:根据目标头寸和当前持仓,计算需要调整的头寸,并生成相应的买卖订单。
- 价格模拟:根据配置的成交价格模型(如收盘价、开盘价、vwap 等),模拟订单的成交价格。
- 流动性约束:考虑市场流动性限制,模拟订单的部分成交或无法成交的情况。
- 交易成本计算:根据配置的交易成本模型,计算每次交易的佣金、印花税等成本。
- 订单管理:维护订单簿,处理订单的提交、修改和取消等操作。
下面是创建 SimulatorExecutor 实例的代码示例:
from qlib.backtest.executor import SimulatorExecutor
EXECUTOR_CONFIG = {
"time_per_step": "day", # 回测频率,默认为日线级别
"generate_portfolio_metrics": True, # 是否生成组合指标
"verbose": False, # 是否输出详细日志
}
executor_obj = SimulatorExecutor(**EXECUTOR_CONFIG)
SimulatorExecutor 的核心方法是 step,它在每个回测步骤(通常是每个交易日)被调用,执行以下操作:
- 获取当日的市场数据和策略生成的目标头寸。
- 计算目标头寸与当前持仓之间的差异,生成调仓订单。
- 根据市场条件和交易规则,模拟订单的执行过程。
- 更新账户的现金和持仓状态。
- 记录交易执行情况和账户状态变化。
执行器的设计直接影响回测的真实性和准确性。一个好的执行器应该能够尽可能真实地模拟实际交易环境中的各种约束和成本,从而使得回测结果更具参考价值。
回测参数配置
回测参数的配置是影响回测结果的关键因素之一。合理的参数设置能够提高回测的准确性和可靠性,减少过拟合的风险。QLib 提供了灵活的参数配置接口,允许用户根据自己的需求定制回测过程。
基本参数
回测的基本参数包括回测时间段、初始资金、基准指数等,这些参数直接影响回测的范围和评价标准。
backtest_config = {
"start_time": "2017-01-01", # 回测开始时间
"end_time": "2020-08-01", # 回测结束时间
"account": 100000000, # 初始资金,单位为元
"benchmark": "SH000300", # 基准指数代码
}
- start_time 和 end_time:定义回测的时间范围。选择合适的回测时间段非常重要,过长可能导致数据过时,过短则可能无法充分验证策略的有效性。通常建议回测时间至少包含一个完整的牛熊周期。
- account:设置初始资金规模。资金规模会影响交易的冲击成本和头寸调整的灵活性,应根据策略的实际应用场景设置合理的资金规模。
- benchmark:指定基准指数,用于衡量策略的超额收益。基准指数应选择与策略投资范围相匹配的指数,如沪深 300 指数、中证 500 指数等。
交易所参数
交易所参数用于模拟实际交易环境中的各种规则和成本,包括交易频率、涨跌幅限制、成交价格、交易成本等。
exchange_kwargs = {
"freq": "day", # 交易频率,如"day"、"min"等
"limit_threshold": 0.095, # 涨跌幅限制比例
"deal_price": "close", # 成交价格模型,如"close"、"open"、"vwap"等
"open_cost": 0.0005, # 买入成本比例
"close_cost": 0.0015, # 卖出成本比例
"min_cost": 5, # 最低交易成本,单位为元
}
freq:指定交易频率,日线级别("day")或分钟级别("min")等。不同的交易频率对应不同的回测精度和计算复杂度。
limit_threshold:设置涨跌幅限制,A 股市场通常为 10%(ST 股为 5%)。设置此参数可以模拟因涨跌停导致的无法成交情况。
deal_price:指定成交价格模型,常用的有:
- "close":以收盘价成交
- "open":以开盘价成交
- "vwap":以成交量加权平均价成交
- "twap":以时间加权平均价成交 选择合适的成交价格模型对回测结果影响很大,应根据策略的实际交易逻辑选择。
open_cost 和 close_cost:分别表示买入和卖出时的交易成本比例,包括佣金、印花税等。A 股市场的印花税仅在卖出时征收,因此 close_cost 通常高于 open_cost。
min_cost:设置最低交易成本,模拟实际交易中的最低佣金限制。
策略参数
策略参数根据所使用的具体策略类型而定。以 TopkDropoutStrategy 为例,其主要参数包括:
STRATEGY_CONFIG = {
"topk": 50, # 持有股票数量
"n_drop": 5, # 每日调仓数量
"signal": pred_score, # 预测信号
}
- topk:设置投资组合中持有的股票数量。数量越多,组合分散度越高,但可能降低收益潜力。
- n_drop:设置每日调整的股票数量。数量越多,组合换手率越高,交易成本也越高。
策略参数的选择通常需要通过参数优化来确定,但要注意避免过度优化导致的过拟合。建议在样本内数据上进行参数优化,然后在样本外数据上验证优化结果的稳健性。
配置文件示例
QLib 支持使用 YAML 格式的配置文件来定义回测参数,这种方式可以将所有参数集中管理,便于修改和版本控制。下面是一个完整的回测配置文件示例:
qlib_init:
provider_uri: "~/.qlib/qlib_data/cn_data"
region: cn
market: &market csi300
benchmark: &benchmark SH000300
data_handler_config: &data_handler_config
start_time: 2008-01-01
end_time: 2020-08-01
fit_start_time: 2008-01-01
fit_end_time: 2014-12-31
instruments: *market
port_analysis_config: &port_analysis_config
strategy:
class: TopkDropoutStrategy
module_path: qlib.contrib.strategy.strategy
kwargs:
topk: 50
n_drop: 5
signal: <PRED>
backtest:
start_time: 2017-01-01
end_time: 2020-08-01
account: 100000000
benchmark: *benchmark
exchange_kwargs:
limit_threshold: 0.095
deal_price: close
open_cost: 0.0005
close_cost: 0.0015
min_cost: 5
task:
model:
class: LGBModel
module_path: qlib.contrib.model.gbdt
kwargs:
loss: mse
colsample_bytree: 0.8879
learning_rate: 0.0421
subsample: 0.8789
lambda_l1: 205.6999
lambda_l2: 580.9768
max_depth: 8
num_leaves: 210
num_threads: 20
dataset:
class: DatasetH
module_path: qlib.data.dataset
kwargs:
handler:
class: Alpha158
module_path: qlib.contrib.data.handler
kwargs: *data_handler_config
segments:
train: [2008-01-01, 2014-12-31]
valid: [2015-01-01, 2016-12-31]
test: [2017-01-01, 2020-08-01]
record:
- class: SignalRecord
module_path: qlib.workflow.record_temp
kwargs: {}
- class: PortAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
config: *port_analysis_config
这个配置文件定义了从数据加载、模型训练到回测分析的完整流程。使用配置文件的好处是可以将整个工作流参数化,便于复现实验结果和进行参数调优。
交易成本模型
交易成本是影响策略实盘表现的重要因素之一,一个在回测中表现优异的策略,可能因为过高的交易成本而在实盘中无法盈利。因此,准确地模拟交易成本是保证回测真实性的关键。QLib 提供了灵活的交易成本模型,允许用户根据实际情况配置各种成本参数。
交易成本的组成
在量化交易中,交易成本通常包括以下几个部分:
- 佣金(Commission) :券商为执行交易收取的费用,通常按成交金额的一定比例收取,并有最低收费限制。
- 印花税(Stamp Duty) :政府对股票交易征收的税费,A 股市场仅在卖出时征收,税率为成交金额的 0.1%。
- 过户费(Transfer Fee) :股票过户时收取的费用,通常按成交金额的一定比例收取,双向征收。
- 滑点(Slippage) :由于市场流动性不足,实际成交价格与预期价格之间的差异。滑点通常与交易金额和市场波动性相关。
- 冲击成本(Impact Cost) :大额交易对市场价格造成的影响,导致实际成交均价偏离交易时点的市场价格。
在 QLib 中,可以通过 exchange_kwargs 参数配置大部分交易成本:
exchange_kwargs = {
"open_cost": 0.0005, # 买入成本(包括佣金、过户费等)
"close_cost": 0.0015, # 卖出成本(包括佣金、过户费、印花税等)
"min_cost": 5, # 最低佣金
"slippage": 0.001, # 滑点比例
}
需要注意的是,滑点和冲击成本通常难以精确量化,需要根据历史数据和经验进行估计。在实际回测中,可以通过敏感性分析来评估不同滑点假设对策略表现的影响。
成本计算示例
假设我们以 10 元/股的价格买入 1000 股某股票,交易成本参数配置如下:
- open_cost: 0.0005(0.05%)
- min_cost: 5 元
则本次交易的成本计算如下:
- 成交金额:10 * 1000 = 10,000 元
- 按比例计算的佣金:10,000 * 0.0005 = 5 元
- 由于 5 元达到最低佣金标准,因此实际佣金为 5 元
- 总成本:5 元
如果买入数量为 500 股:
- 成交金额:10 * 500 = 5,000 元
- 按比例计算的佣金:5,000 * 0.0005 = 2.5 元
- 由于 2.5 元低于最低佣金标准,因此实际佣金为 5 元
- 总成本:5 元
卖出时的成本计算类似,但需要加上印花税。假设 close_cost 为 0.0015(0.15%),其中包含 0.1% 的印花税:
- 成交金额:10 * 1000 = 10,000 元
- 佣金:10,000 * (0.0015 - 0.001) = 5 元(假设佣金为 0.05%)
- 印花税:10,000 * 0.001 = 10 元
- 总成本:5 + 10 = 15 元
QLib 的回测引擎会自动计算每笔交易的成本,并在账户净值中扣除。准确的成本计算能够帮助我们更真实地评估策略的盈利能力。
滑点模型
滑点是指实际成交价格与预期价格之间的差异,通常由市场流动性和交易规模决定。QLib 提供了多种滑点模型,以模拟不同市场条件下的成交价格。
- 固定滑点模型:假设滑点为固定比例,如 0.1%。这种模型简单直观,但可能无法反映实际市场中的滑点变化。
exchange_kwargs = {
"slippage": 0.001, # 固定滑点比例为0.1%
}
- 动态滑点模型:根据交易金额和市场波动性动态调整滑点。例如,交易金额越大,滑点越大;市场波动性越高,滑点越大。
QLib 允许用户自定义滑点模型,通过继承 Slippage 类并实现 get_slippage 方法:
from qlib.backtest.exchange import Slippage
class DynamicSlippage(Slippage):
def __init__(self, vol_coef=0.001, amount_coef=0.0001):
self.vol_coef = vol_coef
self.amount_coef = amount_coef
def get_slippage(self, instrument, date, price, amount, direction):
# 获取当日波动率
volatility = self.get_volatility(instrument, date)
# 计算滑点
slippage = self.vol_coef * volatility + self.amount_coef * abs(amount)
# 根据交易方向调整滑点符号
return slippage * direction
然后在回测配置中使用自定义的滑点模型:
exchange_kwargs = {
"slippage": DynamicSlippage(vol_coef=0.001, amount_coef=0.0001),
}
选择合适的滑点模型需要考虑策略的交易频率、持仓周期和交易规模等因素。对于高频交易策略,滑点模型的准确性尤为重要,可能需要更复杂的模型来模拟市场冲击。
成本对策略的影响
交易成本对策略表现的影响不容忽视,特别是对于高换手率的策略。下面通过一个简单的例子来说明成本对策略收益的影响。
假设有一个策略,年化收益率为 20%,换手率为 500%(即平均每周周转一次),交易成本为 0.2%(买入和卖出各 0.1%)。则该策略的年化交易成本为:
年化交易成本 = 换手率 × 单次交易成本 = 500% × 0.2% = 10%
扣除交易成本后的实际年化收益率为:20% - 10% = 10%
如果策略的换手率提高到 1000%,则年化交易成本将达到 20%,几乎吞噬所有收益。因此,在策略开发过程中,需要在收益和换手率之间寻找平衡,避免过度交易导致成本过高。
QLib 的回测系统会自动计算策略的换手率和交易成本,并在绩效报告中展示。通过分析这些指标,可以帮助我们评估策略的成本效率,优化交易频率和头寸调整幅度。
回测结果分析
回测完成后,对结果进行深入分析是评估策略有效性的关键步骤。QLib 提供了丰富的绩效分析工具,能够计算各种风险收益指标,并生成可视化报告。本节将介绍如何使用这些工具对回测结果进行全面分析。
核心绩效指标
评估一个量化策略的表现,通常需要考察多个维度的绩效指标,包括收益能力、风险水平、风险调整后收益等。QLib 的 risk_analysis 函数可以计算常用的绩效指标:
from qlib.contrib.evaluate import risk_analysis
# 假设report_normal是回测后得到的绩效报告DataFrame
analysis = {
"excess_return_without_cost": risk_analysis(
report_normal["return"] - report_normal["bench"]
),
"excess_return_with_cost": risk_analysis(
report_normal["return"] - report_normal["bench"] - report_normal["cost"]
)
}
常见的绩效指标包括:
- 年化收益率(annualized_return) :将策略的日收益率年化,衡量策略的盈利能力。计算公式为: [ ext{annualized_return} = (\prod_{i=1}<sup>{n} (1 + r_i))</sup>{252/n} - 1 ] 其中,( r_i ) 是日收益率,n 是回测天数,252 是 A 股市场的年均交易日数。
- 波动率(std) :衡量策略收益的波动性,反映策略的风险水平。计算公式为日收益率的标准差乘以年化因子(( \sqrt{252} ))。
- 夏普比率(sharpe_ratio) :衡量单位风险所获得的超额收益,计算公式为: [ ext{sharpe_ratio} = \frac{ ext{annualized_return} - r_f}{ ext{annualized_volatility}} ] 其中,( r_f ) 是无风险利率,通常取国债收益率或设为 0。
- 最大回撤(max_drawdown) :衡量策略在回测期间从峰值到谷底的最大损失比例,反映策略的极端风险。
- 信息比率(information_ratio) :衡量单位跟踪误差所获得的超额收益,计算公式为: [ ext{information_ratio} = \frac{ ext{annualized_excess_return}}{ ext{tracking_error}} ] 其中,超额收益是策略收益减去基准收益,跟踪误差是超额收益的标准差。
一个典型的风险分析结果如下:
risk
mean 0.000605
std 0.005481
annualized_return 0.152373
information_ratio 1.751319
max_drawdown -0.059055
这些指标从不同角度反映了策略的表现,需要综合考虑。例如,一个高收益但同时具有高波动率和大回撤的策略,可能并不比一个收益稍低但更稳健的策略更优。
绩效可视化
除了数值指标外,可视化分析也是回测结果分析的重要手段。QLib 提供了多种可视化工具,帮助我们直观地理解策略表现。
- 净值曲线:展示策略净值和基准净值的变化趋势,直观反映策略的累积收益和超额收益情况。
import matplotlib.pyplot as plt
# 绘制净值曲线
plt.figure(figsize=(12, 6))
plt.plot(report_normal["cum_return"], label="Strategy")
plt.plot(report_normal["cum_bench"], label="Benchmark")
plt.title("Cumulative Return")
plt.xlabel("Date")
plt.ylabel("Cumulative Return")
plt.legend()
plt.show()
- 回撤曲线:展示策略的累计回撤情况,帮助我们评估策略的风险承受能力。
# 绘制回撤曲线
plt.figure(figsize=(12, 6))
plt.plot(report_normal["drawdown"], label="Strategy Drawdown")
plt.title("Drawdown")
plt.xlabel("Date")
plt.ylabel("Drawdown")
plt.legend()
plt.show()
- 月度收益热力图:展示策略在不同月份的收益情况,帮助我们识别策略的季节性特征。
import seaborn as sns
# 计算月度收益
monthly_return = report_normal["return"].resample("M").sum()
monthly_return.index = monthly_return.index.strftime("%Y-%m")
# 绘制热力图
plt.figure(figsize=(15, 8))
sns.heatmap(monthly_return.values.reshape(-1, 12), annot=True, fmt=".2%", cmap="YlGnBu")
plt.title("Monthly Return Heatmap")
plt.xlabel("Month")
plt.ylabel("Year")
plt.show()
通过这些可视化工具,我们可以更直观地发现策略的优势和不足,为策略优化提供方向。
归因分析
归因分析是回测结果分析的高级阶段,它旨在识别策略收益的来源,帮助我们理解策略为什么会赚钱,以及哪些因素对策略表现的贡献最大。常见的归因方法包括行业归因、因子归因等。
QLib 提供了基础的归因分析功能,结合风险模型可以实现更深入的归因分析。以下是一个简单的行业归因示例:
# 假设positions_normal是回测期间的持仓数据
# 计算每个行业的持仓权重
industry_weights = positions_normal.groupby("industry")["weight"].mean()
# 计算每个行业的贡献度
industry_returns = get_industry_returns() # 获取行业指数收益
industry_contributions = industry_weights * industry_returns
# 绘制行业贡献度饼图
plt.figure(figsize=(10, 10))
plt.pie(industry_contributions, labels=industry_contributions.index, autopct='%1.1f%%')
plt.title("Industry Contribution")
plt.show()
因子归因则通过分析策略持仓对各种因子(如市值、估值、动量等)的暴露度,来评估这些因子对策略收益的贡献。QLib 的风险模型模块可以帮助我们实现因子归因:
from qlib.model.riskmodel import StructuredCovEstimator
# 初始化风险模型
risk_model = StructuredCovEstimator()
# 计算因子暴露和因子收益
factor_exposures = risk_model.get_exposures(returns)
factor_returns = risk_model.get_factor_returns(returns, factor_exposures)
# 计算因子贡献度
factor_contributions = factor_exposures.mean() * factor_returns.mean() * 252
# 绘制因子贡献度条形图
plt.figure(figsize=(12, 6))
factor_contributions.sort_values().plot(kind="barh")
plt.title("Factor Contribution")
plt.xlabel("Annualized Contribution")
plt.show()
归因分析能够帮助我们深入理解策略的收益来源,识别策略的优势因子和风险因子,为策略优化提供依据。例如,如果发现策略的大部分收益来自于动量因子,那么可以考虑加强对动量因子的利用,或者增加其他因子来分散风险。
稳健性检验
回测结果的稳健性是评估策略可靠性的重要指标。一个稳健的策略应该在不同的市场环境、不同的样本时间段都能表现出稳定的盈利能力。常见的稳健性检验方法包括:
- 样本外检验:将历史数据分为样本内(训练期)和样本外(测试期),在样本内优化策略,在样本外验证策略表现。如果策略在样本外表现显著下降,可能存在过拟合风险。
- 滚动回测:将回测时间段划分为多个子区间,在每个子区间上进行回测,观察策略表现的稳定性。如果策略在某些子区间表现极差,可能说明策略对特定市场环境敏感。
- 参数敏感性分析:改变策略的关键参数(如 topk、n_drop 等),观察策略表现的变化。如果策略表现对参数变化非常敏感,可能说明策略不够稳健。
- 蒙特卡洛检验:通过随机打乱交易信号或收益率序列,生成多个伪策略,比较真实策略与伪策略的表现。如果真实策略的表现显著优于伪策略,则说明策略具有真正的预测能力。
QLib 提供了滚动回测的示例代码,可以通过以下方式实现:
from qlib.workflow.task.rolling import Rolling
# 定义滚动回测参数
rolling = Rolling(
step=6, # 每6个月滚动一次
rtype="date",
ds_extra_len=40, # 额外的数据长度,用于特征计算
train_start="2008-01-01",
valid_start="2014-01-01",
valid_end="2016-12-31",
test_start="2017-01-01",
test_end="2020-08-01",
)
# 运行滚动回测
rolling.run(task, experiment_name="rolling_backtest")
通过滚动回测,可以得到策略在不同时间段的表现,评估策略的稳健性。如果策略在各个滚动窗口都能保持稳定的超额收益,说明策略具有较好的稳健性。
回测实践与常见问题
回测是一个复杂的过程,涉及数据质量、参数设置、成本模型等多个方面。在实际操作中,常常会遇到各种问题,导致回测结果失真或不可靠。本节将介绍回测实践中的一些最佳做法和常见问题的解决方案。
回测最佳实践
为了提高回测的可靠性和真实性,建议遵循以下最佳实践:
- 使用高质量数据:数据质量是回测的基础,应确保数据的准确性、完整性和一致性。避免使用未来数据(前瞻性偏差),确保数据的时间戳正确无误。
- 合理设置回测时间段:回测时间段应足够长,包含不同的市场状态(牛市、熊市、震荡市),以全面评估策略的表现。同时,应将数据分为训练集、验证集和测试集,避免过拟合。
- 准确模拟交易成本:根据策略的实际情况,合理设置交易成本参数,包括佣金、印花税、滑点等。对于高频策略,可能需要更复杂的成本模型。
- 考虑流动性约束:在回测中应考虑市场流动性的影响,避免假设可以无限量地以当前价格成交。可以通过设置持仓上限、交易量限制等方式模拟流动性约束。
- 进行充分的稳健性检验:通过样本外测试、滚动回测、参数敏感性分析等方法,全面评估策略的稳健性。避免过度优化参数,追求稳健性而非单纯的高收益。
- 记录回测过程:详细记录回测的参数设置、数据来源、代码版本等信息,确保回测结果的可重复性。使用 QLib 的 Recorder 模块可以方便地跟踪和管理回测实验。
常见问题与解决方案
- 前瞻性偏差(Look-ahead Bias) 问题:在回测中使用了未来的数据,导致策略表现虚高。 解决方案:严格按照时间顺序处理数据,确保在每个时间点只能使用该时间点之前可获得的数据。QLib 的 DataHandler 模块会自动处理数据的时间顺序,避免前瞻性偏差。
- 生存偏差(Survivorship Bias) 问题:回测中只包含了当前仍在市场中的股票,而忽略了已退市的股票,导致策略表现高估。 解决方案:使用包含退市股票的完整历史数据。QLib 的数据提供商通常包含完整的股票历史数据,包括退市股票。
- 过度拟合(Overfitting) 问题:策略过度优化以适应历史数据,导致在实盘或样本外数据中表现不佳。 解决方案:减少策略参数数量,使用样本外数据验证策略,进行参数敏感性分析,避免过度优化。
- 交易成本低估 问题:回测中设置的交易成本过低,导致策略实盘表现远逊于回测结果。 解决方案:根据实际交易情况,合理估计交易成本,包括佣金、印花税、滑点和冲击成本。可以通过实盘交易或模拟交易来校准成本参数。
- 流动性幻觉 问题:回测中假设可以按当前价格成交任意数量的股票,忽略了市场流动性的限制。 解决方案:在策略中加入流动性约束,如设置单个股票的持仓上限、每日交易量限制等。可以使用平均成交量(ADV)等指标来衡量股票的流动性。
- 数据窥探偏差(Data Snooping Bias) 问题:多次使用同一数据集进行策略测试和优化,导致策略结果被数据随机噪声所影响。 解决方案:使用严格的样本外测试,避免多次调整策略以适应特定数据集。可以采用交叉验证等方法来减少数据窥探偏差。
通过遵循最佳实践和解决常见问题,可以提高回测的可靠性和真实性,为实盘交易提供更有价值的参考。
回测系统的高级应用
QLib 的回测系统不仅支持基本的策略回测,还提供了多种高级功能,满足复杂的量化研究需求。本节将介绍回测系统的一些高级应用场景。
多策略组合回测
在实际投资中,通常会将多个不同的策略组合起来,以分散风险和提高收益稳定性。QLib 支持多策略组合回测,可以通过以下步骤实现:
- 分别定义多个独立的策略。
- 创建一个组合策略,将多个子策略的信号或头寸进行合并。
- 运行回测并分析组合策略的表现。
以下是一个简单的多策略组合示例:
class PortfolioStrategy(BaseStrategy):
def __init__(self, strategies, weights):
self.strategies = strategies
self.weights = weights
def generate_trade_decision(self, date):
# 获取每个子策略的目标头寸
decisions = []
for strategy in self.strategies:
decision = strategy.generate_trade_decision(date)
decisions.append(decision)
# 加权合并头寸
combined_decision = self.combine_decisions(decisions, self.weights)
return combined_decision
def combine_decisions(self, decisions, weights):
# 实现头寸合并逻辑
combined_pos = {}
for decision, weight in zip(decisions, weights):
pos = decision.get_target_pos()
for instrument, amount in pos.items():
if instrument in combined_pos:
combined_pos[instrument] += amount * weight
else:
combined_pos[instrument] = amount * weight
# 创建合并后的交易决策
return TradeDecision(target_pos=combined_pos)
# 创建子策略
strategy1 = TopkDropoutStrategy(topk=50, n_drop=5, signal=signal1)
strategy2 = EnhancedIndexingStrategy(model=model2, dataset=dataset2)
# 创建组合策略
portfolio_strategy = PortfolioStrategy(
strategies=[strategy1, strategy2],
weights=[0.6, 0.4] # 策略权重
)
多策略组合可以通过配置不同策略的权重,实现风险和收益的平衡。通过相关性较低的策略组合,可以有效降低整体组合的波动性,提高风险调整后收益。
高频回测
QLib 不仅支持日线级别的回测,还支持分钟级甚至 tick 级别的高频回测。高频回测需要处理更大量的数据,对性能要求更高,但能够更准确地模拟高频交易策略的表现。
进行高频回测时,需要注意以下几点:
- 数据准备:需要准备高频数据,如分钟线数据、订单簿数据等。QLib 提供了高频数据的处理工具,可以通过
HighFreqHandler加载和处理高频数据。 - 回测频率设置:在执行器配置中设置高频回测频率,如"min"表示分钟级回测。
executor_config = {
"time_per_step": "min", # 分钟级回测
"generate_portfolio_metrics": True,
}
- 交易成本模型:高频交易的成本结构可能与低频交易不同,需要更精确的滑点和冲击成本模型。
- 性能优化:高频回测需要处理大量数据,可能需要进行性能优化,如使用数据缓存、并行计算等技术。
QLib 的 examples/highfreq 目录下提供了高频回测的示例代码,包括高频数据处理、策略实现和回测分析等完整流程。
事件驱动回测
传统的时间驱动回测按照固定的时间间隔(如每天、每分钟)触发策略决策,而事件驱动回测则是在特定事件发生时(如价格变动、成交量突增、订单成交等)触发策略决策。事件驱动回测可以更精确地模拟实际交易中的决策过程,特别适合于套利策略、做市策略等对时间敏感的策略。
QLib 通过 EventDrivenExecutor 支持事件驱动回测:
from qlib.backtest.executor import EventDrivenExecutor
executor_config = {
"event_queue": event_queue, # 事件队列
"time_per_step": None, # 不使用固定时间间隔
"generate_portfolio_metrics": True,
}
executor = EventDrivenExecutor(**executor_config)
事件驱动回测的核心是事件队列,它包含了回测期间发生的各种市场事件。策略通过订阅特定类型的事件,在事件发生时做出交易决策。QLib 支持多种事件类型,如行情事件、订单事件、成交事件等,并允许用户自定义事件类型。
事件驱动回测能够更真实地模拟实际交易环境,特别是对于需要快速响应市场变化的策略。然而,事件驱动回测的实现复杂度较高,需要处理事件的生成、排序和分发等问题。
回测与实盘的衔接
回测的最终目的是为实盘交易提供参考,因此回测系统与实盘系统的衔接非常重要。QLib 提供了多种工具,帮助用户将回测策略平滑过渡到实盘交易。
- 策略代码复用:QLib 的策略接口设计使得回测策略可以直接用于实盘,只需更换数据源和执行器即可。
- 模型保存与加载:通过
model.save()和model.load()方法,可以将回测中训练好的模型保存下来,用于实盘预测。 - 在线预测更新:QLib 提供了在线预测更新机制,可以定期重新训练模型并更新预测信号,保持策略的时效性。
- 实盘模拟:通过
OnlineExecutor可以模拟实盘交易环境,测试策略在接近实盘条件下的表现。
以下是一个模型保存和加载的示例:
# 回测中保存模型
model.save("model.pkl")
# 实盘中加载模型
from qlib.contrib.model.gbdt import LGBModel
model = LGBModel.load("model.pkl")
# 生成实盘预测信号
pred_score = model.predict(real_time_dataset)
通过这些工具,可以最大限度地减少回测与实盘之间的差异,提高策略实盘盈利的可能性。然而,需要注意的是,即使回测结果再好,实盘交易仍然存在不确定性,建议先进行小资金实盘测试,逐步扩大资金规模。