🚀 AI 一键生成 qmt 策略代码

QMT 如何实现对多账户或账户组的同步交易操作?

qmt | 发布时间: 2025/12/2
以下内容由 EasyQuant 生成。

问题描述

我需要同时管理多个交易账户,如何编写一个策略,让它能对一个账户组里的所有账户执行相同的买卖操作?

解决方案

在 QMT 中管理多个交易账户并执行相同操作,主要有两种实现方式:

  1. Python 循环方式(推荐):在代码中定义账户列表,通过循环遍历列表,对每个账户单独调用下单函数。这种方式灵活性高,便于单独控制每个账户的资金和风控,且日志清晰。
  2. QMT 账号组方式:利用 passorder 函数的 orderType 参数(如 1201),直接传入账号组名称或以逗号分隔的账号字符串。

下面我将提供一个完整的 Python 循环方式 的策略示例。该策略会定义一个账户列表,并使用简单的双均线策略(金叉买入、死叉卖出)来驱动所有账户同步交易。

策略代码示例

# -*- coding: gbk -*-
import pandas as pd

def init(ContextInfo):
    """
    初始化函数
    """
    # 1. 定义需要管理的账户列表
    # 请在此处填写您实际的资金账号,可以是股票账号或期货账号
    ContextInfo.account_list = ['60000001', '60000002']
    
    # 2. 设定账号类型
    # 'STOCK': 股票, 'FUTURE': 期货
    ContextInfo.account_type = 'STOCK'
    
    # 3. 绑定账号以接收回报(重要:循环绑定所有账号)
    for acc in ContextInfo.account_list:
        ContextInfo.set_account(acc)
    
    # 4. 设置股票池(示例:平安银行)
    ContextInfo.stock_code = '000001.SZ'
    ContextInfo.set_universe([ContextInfo.stock_code])
    
    # 5. 策略参数
    ContextInfo.short_period = 5   # 短周期
    ContextInfo.long_period = 10   # 长周期
    ContextInfo.trade_vol = 100    # 每次交易数量(股)

def handlebar(ContextInfo):
    """
    K线周期运行函数
    """
    # 获取当前K线位置
    index = ContextInfo.barpos
    
    # 获取历史行情数据 (使用 get_market_data_ex 获取更稳定)
    # 获取过去 long_period + 2 根K线,确保有足够数据计算均线
    data = ContextInfo.get_market_data_ex(
        ['close'], 
        [ContextInfo.stock_code], 
        period=ContextInfo.period, 
        count=ContextInfo.long_period + 2,
        dividend_type='front'
    )
    
    # 提取收盘价序列
    if ContextInfo.stock_code not in data:
        return
        
    close_series = data[ContextInfo.stock_code]['close']
    
    # 确保数据长度足够
    if len(close_series) < ContextInfo.long_period:
        return

    # 计算均线
    ma_short = close_series.rolling(ContextInfo.short_period).mean()
    ma_long = close_series.rolling(ContextInfo.long_period).mean()
    
    # 获取最新和上一个时间点的均线值
    current_short = ma_short.iloc[-1]
    prev_short = ma_short.iloc[-2]
    current_long = ma_long.iloc[-1]
    prev_long = ma_long.iloc[-2]
    
    # 交易信号判断
    # 金叉:短线上穿长线
    buy_signal = (prev_short <= prev_long) and (current_short > current_long)
    # 死叉:短线下穿长线
    sell_signal = (prev_short >= prev_long) and (current_short < current_long)
    
    # 如果是最后一根K线(实时行情),则执行交易逻辑
    if ContextInfo.is_last_bar():
        
        if buy_signal:
            print(f"触发买入信号: {ContextInfo.stock_code}")
            # 遍历账户列表,对每个账户执行买入
            for acc_id in ContextInfo.account_list:
                trade_func(ContextInfo, acc_id, 'buy')
                
        elif sell_signal:
            print(f"触发卖出信号: {ContextInfo.stock_code}")
            # 遍历账户列表,对每个账户执行卖出
            for acc_id in ContextInfo.account_list:
                trade_func(ContextInfo, acc_id, 'sell')

def trade_func(ContextInfo, account_id, action):
    """
    自定义下单执行函数
    """
    stock_code = ContextInfo.stock_code
    volume = ContextInfo.trade_vol
    
    # 构造下单参数
    # opType: 23-买入, 24-卖出
    # orderType: 1101-单股单账号普通股数下单
    # prType: 5-最新价 (也可以用 11-指定价)
    
    if action == 'buy':
        opType = 23
        print(f"账号 [{account_id}] 执行买入 {stock_code} {volume}股")
        passorder(opType, 1101, account_id, stock_code, 5, -1, volume, ContextInfo)
        
    elif action == 'sell':
        # 卖出前可以先检查持仓(可选优化)
        # 这里直接发送卖出指令
        opType = 24
        print(f"账号 [{account_id}] 执行卖出 {stock_code} {volume}股")
        passorder(opType, 1101, account_id, stock_code, 5, -1, volume, ContextInfo)

代码核心逻辑解析

  1. 账户列表定义 (ContextInfo.account_list):
    init 函数中,我们将所有需要管理的资金账号存储在一个 Python 列表(List)中。

  2. 循环绑定 (set_account):
    QMT 的 set_account 接口用于绑定账号以接收交易回报(如成交、委托状态变化)。为了确保所有账号都能接收回报,我们在 init 中遍历列表并逐一绑定。

  3. 循环下单 (trade_func):
    当策略触发买卖信号时,不直接调用 passorder,而是调用自定义的 trade_func。在该函数内部,我们遍历 ContextInfo.account_list,针对每个 account_id 调用一次 passorder

    • passorderorderType 参数设置为 1101,代表“单股、单账号、普通、股/手方式下单”。这是最基础且最稳健的下单方式。

替代方案:使用账号组下单 (orderType 12xx)

如果您希望使用 QMT 原生的账号组功能,可以修改 trade_func 中的 passorder 调用方式。

修改方法:

  1. 将所有账号拼接成一个字符串,用逗号分隔,例如 "60000001,60000002"
  2. passorderorderType 参数改为 1201(单股、账号组、普通、股/手方式下单)。

代码片段示例:

# 将列表转换为逗号分隔的字符串
account_group_str = ",".join(ContextInfo.account_list)

# 使用 1201 模式下单,accountID 参数传入拼接好的字符串
# 这条指令会同时对组内所有账号下单
passorder(23, 1201, account_group_str, stock_code, 5, -1, volume, ContextInfo)

两种方式对比:

  • 循环方式 (1101):代码可控性更强,可以在循环中加入判断逻辑(例如:账号A买100股,账号B资金多买200股),且如果某个账号报错不会影响其他账号。
  • 账号组方式 (1201):代码更简洁,由 QMT 底层处理分发,适合所有账号完全同权重的简单场景。

常见问题 Q&A

Q: 如何在策略中查询每个账户的资金情况?
A: 可以使用 get_trade_detail_data 函数。

# 查询指定账号的资金
acct_info = get_trade_detail_data('60000001', 'stock', 'account')
for i in acct_info:
    print(f"账号: {i.m_strAccountID}, 可用资金: {i.m_dAvailable}")

Q: 如果我想让不同账户买入不同的数量怎么办?
A: 建议使用循环方式。您可以定义一个字典来配置每个账户的交易量:

# 定义账户和对应的交易量
ContextInfo.account_config = {
    '60000001': 100,
    '60000002': 500
}

# 在循环中读取对应的数量
for acc_id, vol in ContextInfo.account_config.items():
    passorder(23, 1101, acc_id, stock_code, 5, -1, vol, ContextInfo)

Q: 为什么我的策略没有下单?
A: 请检查以下几点:

  1. ContextInfo.account_list 中的账号是否已在 QMT 交易终端登录。
  2. passorder 中的 opType 是否正确(股票买入是 23,卖出是 24)。
  3. 是否处于回测模式。回测模式下多账号逻辑可能无法完全模拟实盘的资金分配,建议先用单账号回测逻辑验证策略,再切换到实盘模式使用多账号代码。