问题描述
解决方案
这是一个非常实用的量化交易需求,通常被称为“篮子交易”或“清单交易”。
为了实现这个策略,我们需要完成以下步骤:
- 准备 CSV 文件:定义好文件的格式(例如包含“证券代码”和“交易数量”两列)。
- 编写 Python 策略:使用
pandas读取文件,遍历每一行,并调用 QMT 的order_shares函数进行下单。 - 控制执行频率:设置逻辑确保策略启动后只执行一次,避免重复下单。
1. CSV 文件格式准备
假设您的 CSV 文件名为 trade_list.csv,内容格式如下(请确保包含表头):
| stock_code | volume |
|---|---|
| 600000.SH | 100 |
| 000001.SZ | -100 |
| 600519.SH | 200 |
- stock_code: 完整的证券代码(包含后缀 .SH 或 .SZ)。
- volume: 交易数量(正数为买入,负数为卖出)。
2. QMT 策略代码实现
以下是完整的策略代码。请将代码复制到 QMT 的策略编辑器中。
# -*- coding: gbk -*-
import pandas as pd
import os
def init(ContextInfo):
"""
初始化函数,策略启动时执行一次
"""
# 1. 设置资金账号 (请修改为您自己的资金账号)
ContextInfo.accid = '6000000000'
ContextInfo.set_account(ContextInfo.accid)
# 2. 设置 CSV 文件路径 (请修改为您的实际文件路径)
# 注意:Windows路径建议使用双斜杠 \\ 或反斜杠 /,或者在字符串前加 r
ContextInfo.csv_path = r'D:\QMT_Data\trade_list.csv'
# 3. 设置一个标志位,防止重复交易
# True 表示已经执行过,False 表示尚未执行
ContextInfo.has_traded = False
print("策略初始化完成,等待行情驱动...")
def handlebar(ContextInfo):
"""
行情驱动函数,每根K线或Tick触发一次
"""
# 1. 检查是否为实时行情的最后一根K线
# 我们只在最新的时间点执行交易,不希望在回测历史K线时触发
if not ContextInfo.is_last_bar():
return
# 2. 检查是否已经执行过交易
if ContextInfo.has_traded:
return
# 3. 开始读取文件并交易
trade_from_csv(ContextInfo)
# 4. 标记为已执行,防止下一根Tick重复下单
ContextInfo.has_traded = True
def trade_from_csv(ContextInfo):
"""
读取CSV并执行交易的逻辑函数
"""
file_path = ContextInfo.csv_path
# 检查文件是否存在
if not os.path.exists(file_path):
print(f"错误:找不到文件 {file_path}")
return
try:
# 读取 CSV 文件 (假设文件编码为 gbk 或 utf-8,根据实际情况调整)
# 如果您的CSV是用Excel保存的,通常是 gbk 编码
df = pd.read_csv(file_path, encoding='gbk')
print(f"成功读取文件,共 {len(df)} 条交易指令")
# 遍历每一行数据
for index, row in df.iterrows():
stock_code = row['stock_code'] # 获取代码
volume = int(row['volume']) # 获取数量
# 过滤无效数据
if volume == 0:
continue
# 执行下单
# order_shares: 指定股数交易
# 参数说明: 代码, 数量, 报价类型('LATEST'为最新价), 价格(最新价模式下填0), ContextInfo, 账号
order_shares(stock_code, volume, 'LATEST', 0, ContextInfo, ContextInfo.accid)
action = "买入" if volume > 0 else "卖出"
print(f"指令发送: {action} {stock_code} 数量: {abs(volume)}")
except Exception as e:
print(f"读取文件或交易过程中发生错误: {e}")
3. 代码关键点解析
-
# -*- coding: gbk -*-:- 这是 Python 文件的编码声明,QMT 环境通常要求使用 GBK 编码,这行代码能防止中文注释乱码。
-
ContextInfo.has_traded标志位:handlebar函数会随着行情(Tick)不断被调用。为了防止策略一启动就对着同一个清单疯狂重复下单,我们设置了一个has_traded变量。一旦交易完成,将其设为True,后续的行情推送将直接被忽略。
-
ContextInfo.is_last_bar():- 这个判断非常重要。当您点击“运行”时,QMT 可能会先快速回放一部分历史 K 线。加上这个判断,确保只有在“此时此刻”的最新行情下才读取文件下单,避免在历史时间点错误下单。
-
pd.read_csv:- 利用 Pandas 库读取 CSV。注意
encoding参数,如果您的 CSV 是 Excel 另存为的,通常是'gbk';如果是其他编辑器生成的,可能是'utf-8'。如果读取乱码,请尝试更改此参数。
- 利用 Pandas 库读取 CSV。注意
-
order_shares:- 这是最直接的下单函数。
'LATEST'代表以最新价下单。如果您希望以对手价下单,可以改为'COMPETE',或者指定价格'FIX'。volume为正代表买入,为负代表卖出。
4. 使用前注意事项
- 修改账号:请务必将代码中的
ContextInfo.accid = '6000000000'修改为您真实的资金账号。 - 修改路径:请将
ContextInfo.csv_path修改为您 CSV 文件的实际绝对路径。 - 实盘模式:请确保策略运行在“实盘”模式下(在策略编辑器顶部选择“实盘”而非“回测”),并且交易账号已登录。
- 文件权限:确保 CSV 文件没有被 Excel 打开占用,否则 Python 可能无法读取。
Q&A
Q: 如果我想用限价单(指定价格)而不是最新价交易怎么办?
A: 您需要在 CSV 中增加一列 price,然后修改 order_shares 的调用方式。
代码修改如下:
# 假设 CSV 有一列叫 'price'
price = row['price']
# 使用 'FIX' 类型,并传入价格
order_shares(stock_code, volume, 'FIX', price, ContextInfo, ContextInfo.accid)
Q: 怎么让策略每天定时(比如早上9:30)自动读取文件交易?
A: 可以在 handlebar 中增加时间判断。
import datetime
# 获取当前K线时间
timetag = ContextInfo.get_bar_timetag(ContextInfo.barpos)
current_time = timetag_to_datetime(timetag, '%H:%M:%S')
# 比如在 09:30:00 到 09:31:00 之间且未交易过时执行
if "09:30:00" <= current_time <= "09:31:00" and not ContextInfo.has_traded:
trade_from_csv(ContextInfo)
ContextInfo.has_traded = True
Q: 为什么读取 CSV 时报错 UnicodeDecodeError?
A: 这通常是编码问题。请尝试将 pd.read_csv(..., encoding='gbk') 改为 encoding='utf-8',或者反之。这取决于您的 CSV 文件是如何保存的。