本示例展示一个基于 5 日均线和 20 日均线交叉信号的双均线回测策略。短期均线上穿长期均线(金叉)时买入 100 股,短期均线下穿长期均线(死叉)时卖出全部持仓。策略使用 get_market_data_ex 获取历史收盘价并计算均线,通过 passorder 在回测环境中模拟下单。
真实委托代码默认关闭
本示例中的 passorder 调用仅在回测环境中产生模拟委托,不会发送真实委托。示例中的真实委托代码默认关闭。如需在仿真交易或真实交易环境中运行,需要手动开启交易开关并确认账户权限,具体操作以当前客户端版本为准。
示例目标
完成一次完整的双均线回测,验证以下能力:
- 正确获取历史收盘价数据
- 正确计算短期和长期均线
- 正确识别金叉和死叉信号
- 在回测环境中模拟下单和成交
适用环境
| 环境 | 是否支持 |
|---|---|
| 回测环境 | 支持 |
| 模拟信号 | 不适用 |
| 仿真交易环境 | 不适用 |
| 真实交易环境 | 不适用 |
使用周期
日线(1d)。
是否可能产生真实委托
否。本示例仅在回测环境中运行,passorder 调用产生的是回测模拟委托,不会发送至交易柜台。
完整代码
def init(ContextInfo):
"""策略初始化"""
ContextInfo.set_universe(['600000.SH'])
ContextInfo.short_ma = 5 # 短期均线周期
ContextInfo.long_ma = 20 # 长期均线周期
ContextInfo.days = 0 # 已处理的 Bar 计数
def handlebar(ContextInfo):
"""主策略逻辑"""
ContextInfo.days += 1
# 数据不足时跳过
if ContextInfo.days < ContextInfo.long_ma:
return
if not ContextInfo.is_last_bar():
return
# 获取短期均线所需的历史收盘价(多取一根用于计算前一日均线值)
ma5_data = ContextInfo.get_market_data_ex(
['close'], ['600000.SH'], '1d',
-ContextInfo.short_ma - 1, -1
)
# 获取长期均线所需的历史收盘价
ma20_data = ContextInfo.get_market_data_ex(
['close'], ['600000.SH'], '1d',
-ContextInfo.long_ma - 1, -1
)
# 数据有效性检查
if '600000.SH' not in ma5_data or '600000.SH' not in ma20_data:
print('警告:获取行情数据失败,跳过')
return
closes_5 = ma5_data['600000.SH']['close'].values
closes_20 = ma20_data['600000.SH']['close'].values
if len(closes_5) < ContextInfo.short_ma + 1 or len(closes_20) < ContextInfo.long_ma + 1:
print('警告:历史数据不足,跳过')
return
# 计算当前和前一日的均线值
short_ma_now = sum(closes_5[-ContextInfo.short_ma:]) / ContextInfo.short_ma
short_ma_prev = sum(closes_5[-ContextInfo.short_ma - 1:-1]) / ContextInfo.short_ma
long_ma_now = sum(closes_20[-ContextInfo.long_ma:]) / ContextInfo.long_ma
long_ma_prev = sum(closes_20[-ContextInfo.long_ma - 1:-1]) / ContextInfo.long_ma
# 获取持仓信息
pos = ContextInfo.get_position('600000.SH')
has_position = pos and pos.m_nVolume > 0
# 最新收盘价作为委托价格
price = float(closes_20[-1])
# 金叉买入:短期均线从下方上穿长期均线
if short_ma_prev <= long_ma_prev and short_ma_now > long_ma_now and not has_position:
passorder(0, 0, ContextInfo.accountid, '600000.SH', 0, price, 100,
'ma_strategy', 0, 0)
print(f'金叉买入: 100 股 @ {price:.2f} | MA5={short_ma_now:.2f} MA20={long_ma_now:.2f}')
# 死叉卖出:短期均线从上方下穿长期均线
elif short_ma_prev >= long_ma_prev and short_ma_now < long_ma_now and has_position:
passorder(0, 1, ContextInfo.accountid, '600000.SH', 0, price,
pos.m_nCanUseVolume, 'ma_strategy', 0, 0)
print(f'死叉卖出: {pos.m_nCanUseVolume} 股 @ {price:.2f} | MA5={short_ma_now:.2f} MA20={long_ma_now:.2f}')
代码说明
init 函数
def init(ContextInfo):
ContextInfo.set_universe(['600000.SH'])
ContextInfo.short_ma = 5
ContextInfo.long_ma = 20
ContextInfo.days = 0
| 参数 | 值 | 说明 |
|---|---|---|
| 股票池 | ['600000.SH'] |
浦发银行 |
| 短期均线周期 | 5 | 5 日均线 |
| 长期均线周期 | 20 | 20 日均线 |
| days 计数器 | 0 | 记录已处理的 Bar 数量 |
数据获取
ma5_data = ContextInfo.get_market_data_ex(
['close'], ['600000.SH'], '1d',
-ContextInfo.short_ma - 1, -1
)
get_market_data_ex返回一个字典,键为证券代码,值为包含请求字段的 DataFrame。- 参数
-ContextInfo.short_ma - 1到-1表示获取最近 6 根 K 线的收盘价(5 根用于当前均线,多 1 根用于前一日均线)。 - 长期均线同理,获取最近 21 根 K 线的收盘价。
均线计算
short_ma_now = sum(closes_5[-ContextInfo.short_ma:]) / ContextInfo.short_ma
short_ma_prev = sum(closes_5[-ContextInfo.short_ma - 1:-1]) / ContextInfo.short_ma
short_ma_now:最近 5 根 K 线收盘价的均值。short_ma_prev:前一日往前 5 根 K 线收盘价的均值。- 通过比较
now和prev的均线关系,判断是否发生交叉。
信号判断与下单
# 金叉:前一日短期均线 <= 长期均线,今日短期均线 > 长期均线
if short_ma_prev <= long_ma_prev and short_ma_now > long_ma_now and not has_position:
passorder(0, 0, ContextInfo.accountid, '600000.SH', 0, price, 100,
'ma_strategy', 0, 0)
passorder 参数说明:
| 位置 | 参数 | 值 | 说明 |
|---|---|---|---|
| 1 | opType | 0 | 股票 |
| 2 | orderType | 0/1 | 0=买入,1=卖出 |
| 3 | accountid | ContextInfo.accountid | 账户 ID |
| 4 | orderCode | '600000.SH' | 证券代码 |
| 5 | prType | 0 | 价格类型(以 price 参数定价) |
| 6 | price | price | 委托价格 |
| 7 | volume | 100 / 持仓量 | 委托数量 |
| 8 | strategyName | 'ma_strategy' | 策略名称 |
| 9 | quickTrade | 0 | 不启用快速交易 |
| 10 | userOrderId | 0 | 用户自定义委托号 |
回测参数设置
在 QMT 客户端中运行回测前,请按以下参数设置:
| 设置项 | 推荐值 | 说明 |
|---|---|---|
| 回测模式 | 回测 | 使用历史数据回放 |
| 回测起始日期 | 近 1 年以上 | 确保有足够的数据产生多次交叉信号 |
| 回测结束日期 | 最近交易日 | |
| 周期 | 日线 | 与策略逻辑匹配 |
| 初始资金 | 100000 | 回测初始资金 |
| 滑点 | 1 个最小变动单位 | 模拟成交滑点 |
| 手续费 | 万分之五 | 模拟交易手续费 |
| 品种 | 600000.SH | 确保该品种数据已下载 |
技巧
回测前请确保 600000.SH 的日线历史数据已完整下载。可在客户端的数据管理模块中手动下载。
预期结果
回测完成后,预期观察到以下结果:
- 日志输出:输出面板中应出现「金叉买入」和「死叉卖出」的日志记录。
- 收益曲线:回测结果面板中应显示收益曲线,包含资金变化和持仓变化。
- 交易记录:交易明细中应包含买入和卖出记录。
- 统计指标:应显示总收益率、最大回撤、胜率等统计指标。
预期日志示例
策略初始化完成
金叉买入: 100 股 @ 10.50 | MA5=10.32 MA20=10.18
死叉卖出: 100 股 @ 10.80 | MA5=10.72 MA20=10.85
金叉买入: 100 股 @ 11.20 | MA5=11.05 MA20=10.90
...
提示
具体的买入卖出价格和均线数值取决于回测时间段内的实际行情数据,以上仅为示例格式。
成功标准
| 判断项 | 预期结果 |
|---|---|
| 数据获取 | 无「获取行情数据失败」警告 |
| 信号触发 | 回测期间至少出现 1 次金叉或死叉信号 |
| 委托记录 | 交易明细中有对应的买入/卖出记录 |
| 收益曲线 | 回测结果面板显示完整的收益曲线 |
| 无报错 | 输出面板无红色错误信息 |
已知限制
- 双均线策略在震荡行情中可能频繁产生虚假信号,导致回撤增大。
- 回测结果受滑点、手续费等参数设置影响,不同参数设置下结果可能有较大差异。
passorder的prType=0在不同客户端版本中的行为可能略有差异,以当前客户端版本为准。- 回测中使用收盘价作为委托价格,实际交易中收盘价可能无法成交,回测结果仅供参考。
- 本策略未考虑涨跌停限制、停牌等异常情况,回测结果可能与实际交易存在偏差。