3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
在 QMT 中,要导出回测的每一笔交易明细,最标准的方法是利用策略生命周期中的 stop(ContextInfo) 函数。
stop() 函数会在回测结束时自动运行一次。此时,我们可以调用 get_trade_detail_data 获取整个回测期间产生的所有成交记录,将其转换为 DataFrame 并导出为 CSV 文件。
以下是具体的实现步骤和代码示例。
handlebar 中正常进行买卖操作。stop 函数中,使用 get_trade_detail_data(..., 'DEAL') 获取所有成交单对象。pandas 库将数据保存为 CSV 文件,方便后续在 Excel 或 Python 中计算盈亏比、最大连亏等指标。你可以将以下代码复制到 QMT 的 Python 编辑器中进行回测。代码包含了一个简单的均线策略用于生成交易数据,以及核心的导出逻辑。
# -*- coding: gbk -*-
import pandas as pd
import os
def init(ContextInfo):
# 1. 设置账号信息 (回测模式下账号ID通常可以随意填,或者使用默认)
ContextInfo.accID = '6000000000'
ContextInfo.accountType = 'STOCK'
ContextInfo.set_account(ContextInfo.accID)
# 2. 策略参数设置 (示例:简单的均线策略)
ContextInfo.stock = '600000.SH'
ContextInfo.set_universe([ContextInfo.stock])
ContextInfo.period = 5 # 5日均线
# 3. 设置导出路径 (请确保D盘存在,或者修改为你电脑上存在的路径)
ContextInfo.export_path = 'D:/qmt_backtest_records.csv'
def handlebar(ContextInfo):
# --- 示例策略逻辑:为了生成交易记录 ---
# 获取历史收盘价
closes = ContextInfo.get_history_data(ContextInfo.period + 1, '1d', 'close')
if ContextInfo.stock not in closes:
return
close_list = closes[ContextInfo.stock]
if len(close_list) < ContextInfo.period:
return
ma5 = sum(close_list[-5:]) / 5
current_price = close_list[-1]
# 获取当前持仓
positions = get_trade_detail_data(ContextInfo.accID, ContextInfo.accountType, 'POSITION')
curr_vol = 0
for pos in positions:
if pos.m_strInstrumentID == ContextInfo.stock:
curr_vol = pos.m_nVolume
break
# 简单的交易逻辑
if current_price > ma5 and curr_vol == 0:
# 均线上方买入
order_shares(ContextInfo.stock, 1000, 'fix', current_price, ContextInfo, ContextInfo.accID)
elif current_price < ma5 and curr_vol > 0:
# 均线下方卖出
order_shares(ContextInfo.stock, -curr_vol, 'fix', current_price, ContextInfo, ContextInfo.accID)
def stop(ContextInfo):
"""
回测结束时触发,用于导出交易记录
"""
print("回测结束,开始导出交易记录...")
# 1. 获取所有成交记录 ('DEAL')
# 注意:这里获取的是回测期间产生的所有成交明细
deal_list = get_trade_detail_data(ContextInfo.accID, ContextInfo.accountType, 'DEAL')
if not deal_list:
print("本次回测无成交记录。")
return
# 2. 解析成交对象属性
data = []
for deal in deal_list:
# 解析买卖方向:48代表买入,49代表卖出 (参考API文档附录)
direction = "买入" if deal.m_nDirection == 48 else "卖出"
record = {
"日期": deal.m_strTradeDate,
"时间": deal.m_strTradeTime,
"代码": deal.m_strInstrumentID,
"名称": deal.m_strInstrumentName,
"方向": direction,
"成交均价": deal.m_dPrice,
"成交数量": deal.m_nVolume,
"成交金额": deal.m_dTradeAmount,
"手续费": deal.m_dComssion,
"平仓盈亏": deal.m_dCloseProfit, # 注意:股票回测中此字段可能不准确,通常需自己计算
"备注": deal.m_strRemark
}
data.append(record)
# 3. 转换为DataFrame并导出
df = pd.DataFrame(data)
# 按日期和时间排序
df = df.sort_values(by=['日期', '时间'])
try:
# 导出为CSV,使用gbk编码防止中文乱码
df.to_csv(ContextInfo.export_path, index=False, encoding='gbk')
print(f"交易记录已成功导出至: {ContextInfo.export_path}")
print(f"共导出 {len(df)} 条成交记录")
except Exception as e:
print(f"导出失败: {e}")
print("请检查路径是否存在或文件是否被占用。")
get_trade_detail_data(..., 'DEAL'):
'DEAL' 表示获取成交明细。对象属性解析:
m_strTradeDate / m_strTradeTime: 交易时间。m_nDirection: 买卖方向。QMT底层使用枚举值,48 代表买入 (ENTRUST_BUY),49 代表卖出 (ENTRUST_SELL)。m_dPrice: 成交价格。m_nVolume: 成交数量。m_dComssion: 手续费。关于盈亏计算:
m_dCloseProfit (平仓盈亏) 属性,但在股票回测模式下,该字段有时可能不会自动计算或计算逻辑与预期不同(它在期货回测中更准确)。导出 CSV 后,你可以使用 Python (Pandas) 进行如下分析:
总盈利金额 / abs(总亏损金额) 或 平均盈利金额 / abs(平均亏损金额)。Q: 为什么我导出的 CSV 是乱码?
A: 代码中使用了 encoding='gbk',这是为了适配 Excel 打开中文 CSV 的默认编码。如果你使用其他软件(如 Mac 上的 Numbers 或某些文本编辑器),可能需要将其改为 encoding='utf-8-sig'。
Q: stop 函数没有执行怎么办?
A: 确保你的回测正常跑完了全程。如果中途手动停止或报错中断,stop 函数可能不会被触发。
Q: 能否导出委托记录(包括未成交的)?
A: 可以。将 get_trade_detail_data 的第三个参数改为 'ORDER' 即可获取所有委托单,包括已撤单和废单。