返回列表 发布新帖

qmt教程--qmt常见量化问题解答2

31 1

今天继续按更新计划更新教程内容,今天更新qmt常见的问题解答2,以前更新过1的内容量化教程--qmt量化常见问题解答1

1`ContextInfo`对象的逐K线保存机制具体是怎样的?

ContextInfo由底层维护并传递给系统函数。同一根K线(bar)内,ContextInfo本质上是同一个变量,对其修改只对本次handlebar调用的下文起作用。handlebar里对ContextInfo的修改在该bar结束后才会保存,在下一个bar体现出来。每次handlebar调用前会对ContextInfo进行深拷贝,如果新的分笔不是新K线的第一个分笔,则判断上一个分笔不是K线最后分笔,ContextInfo对象会被回退为之前深拷贝的那个。此机制目的是模拟K线效果,只在K线结束的分笔生效一次。

d7de1b99b7a8d056cf6457888e6569c9.png

例子参考思路

#coding:gbk
def init(ContextInfo):
    # 在初始化函数中,为ContextInfo添加一个计数器属性,初始值为0
    ContextInfo.counter = 0
def handlebar(ContextInfo):
    # 每进来一个tick,计数器加1
    ContextInfo.counter += 1
    # 打印当前计数器的值
    # 在盘中,你会发现这个数字在同一根K线内会不断变化,但最终不会被保存
    print(f"Bar Index: {ContextInfo.barpos}, Tick Counter: {ContextInfo.counter}, Is Last Bar: {ContextInfo.is_last_bar()}")
    # 只在最后一根K线(最新K线)执行交易逻辑
    if ContextInfo.is_last_bar():
        print(f"------ 这是最新K线的tick,计数器的最终值(不一定会被保存):{ContextInfo.counter} ------")

2`passorder`函数中的`quickTrade`参数(快速交易参数)有哪几种取值?分别代表什么含义?

quickTrade参数默认为0。

  • **传0**:只在K线结束分笔时调用passorder产生有效信号,其他情况调用不产生信号。适用于希望K线结束下单(信号不闪烁)的场景。
  • **传1**:在当前K线为最新K线时(ContextInfo.is_last_bar()为True)调用passorder产生有效信号,历史K线调用不产生信号。适用于希望盘中出现信号立即下单,但接受信号闪烁风险的场景。
  • **传2**:任何情况下调用passorder都产生有效信号,不会丢弃任何一次调用的信号。**注意**:在定时器回调、行情回调函数、after_init函数中调用下单函数时,必须传2,确保不会漏单。

7adefb6994ad5033758bb5f4418525af.png

例子参考

#coding:gbk
def init(ContextInfo):
    # 在init函数中下单,必须设置quickTrade=2
    passorder(23, 1101, account, '01.SZ', 5, 0, 100, 'init下单', 2, '教学示例', ContextInfo)
    print("init函数中立即下单(quickTrade=2)")
def after_init(ContextInfo):
    # 在after_init函数中下单,必须设置quickTrade=2
    passorder(23, 1101, account, '01.SZ', 5, 0, 200, 'after_init下单', 2, '教学示例', ContextInfo)
    print("after_init函数中立即下单(quickTrade=2)")
def handlebar(ContextInfo):
    if not ContextInfo.is_last_bar():
        return  # 历史K线跳过


    # 常见场景:K线走完才下单,信号不闪烁
    # 使用quickTrade=0(默认值)
    passorder(23, 1101, account, '1.SZ', 5, 0, 100, 'K线结束下单', 0, '教学示例', ContextInfo)
    print("K线结束时产生信号(quickTrade=0)")


    # 如果需要盘中立即响应,可使用quickTrade=1
    # passorder(23, 1101, account, '1.SZ', 5, 0, 100, '盘中立即下单', 1, '教学示例', ContextInfo)

3为什么调用`passorder`后立刻查询`get_trade_detail_data`,查不到对应委托,可用资金也没变化?

QMT的交易接口是异步的。以quickTrade=2的passorder为例,调用后会立刻发出委托然后返回,不会等待委托回报,也不会阻塞Python线程。委托、成交、持仓、账号信息的更新是在客户端后台进行的,Python策略无法手动控制。get_trade_detail_data和四种交易回调函数都是从客户端本地缓存中读取数据/触发调用,不是实时查询柜台。客户端本地缓存状态定期接收柜台推送刷新(有交易主推的柜台约50ms一次)。因此不能认为查询到的状态与柜**全实时一致。

0fc9d97cb3e5c5f9c8a7041cb3fe6618.png

例子参考

#coding:gbk
def init(C):
    """初始化:设置账号"""
    C.acc = '4'  # 账号


def handlebar(C):
    """每根K线触发"""
    if not C.is_last_bar():
        return


    now = timetag_to_datetime(C.get_bar_timetag(C.barpos), '%Y-%m-%d %H:%M:%S')


    # ===== 场景演示1:下单前查询资金和持仓 =====
    print(f"\n{now} === 下单前状态 ===")
    accounts_before = get_trade_detail_data(C.acc, 'stock', 'account')
    if accounts_before:
        acct = accounts_before
        print(f"可用资金: {acct.m_dAvailable}")


    positions_before = get_trade_detail_data(C.acc, 'stock', 'position')
    for pos in positions_before:
        print(f"持仓: {pos.m_strInstrumentID} 可用数量: {pos.m_nCanUseVolume}")


    # ===== 下单:立即委托(quickTrade=2) =====
    stock_code = '1.SZ'
    volume = 100  # 1手


    print(f"\n{now} === 开始下单 === 买入 {stock_code} {volume}股")


    # 使用quickTrade=2,任何位置调用都立即产生委托
    # 23=买入,1101=限价,14=最新价
    remark = f"教学示例_{now}"
    passorder(23, 1101, C.acc, stock_code, 14, -1, volume, 
              remark, 2, remark, C)


    print(f"passorder调用完毕,已返回")


    # ===== 场景演示2:下单后立刻查询 =====
    print(f"\n{now} === 下单后立即查询(通常查不到委托)===")


    # 查询委托
    orders = get_trade_detail_data(C.acc, 'stock', 'order')
    print(f"委托数量: {len(orders)}")
    for o in orders:
        print(f"  委托: {o.m_strInstrumentID} 状态: {o.m_nOrderStatus} "
              f"备注: {o.m_strRemark}")


    # 查询资金(可用资金不会立即变化)
    accounts_after = get_trade_detail_data(C.acc, 'stock', 'account')
    if accounts_after:
        acct_after = accounts_after
        print(f"可用资金: {acct_after.m_dAvailable}")


    # 演示查询到的委托数量与下单前相同(可能查不到新委托)
    print(f"\n结论:如下单前委托总数 = {len(orders_before)},下单后 = {len(orders_after)}")
    print("可能查不到刚下的委托,因为本地缓存尚未刷新")

4实盘策略中如何设计可靠的委托状态管理机制,防止超单?

常见的做法是:

  1. 使用**全局变量字典**保存委托状态。
  2. 给每一笔委托一个独立的userOrderId(投资备注)作为字典的key,委托状态(如'待报'、'已报'、'部分成交'、'全部成交'、'已撤'等)作为字典的value。
  3. 下单后,默认将该笔委托的状态设置为'待报'。
  4. 之后通过交易回调函数或定期查询get_trade_detail_data来获取委托回报,并更新字典中对应委托的状态。
  5. 如果某品种股票存在'待报'或'已报'状态的委托,则暂停该品种的后续报单,防止发生超单。可参考实盘示例7(调整至目标持仓Demo)的实现。

a46b7ff8879796251f3cee06a0c0d9bf.png

参考

#coding:gbk
"""
示例说明:一个简化的交易系统核心部分,演示如何通过维护委托状态字典来防止超单。
此代码并非完整可执行的策略,而是突出了核心的设计模式。
"""
import datetime
import time
class a():
    pass
A = a() # 创建一个全局对象来保存状态
def init(C):
    """策略初始化函数"""
    A.acct = account # 账号
    A.acct_type = accountType # 账号类型
    # 定义一个全局字典,用于保存每只股票的委托状态
    # key: 股票代码(例如 '01.SZ')
    # value: 该股票最近一次未完成的委托的“投资备注”
    A.waiting_dict = {}
    # 可选:记录所有委托及其发出时间的字典,用于超时撤单
    A.all_order_ref_dict = {}
    A.withdraw_secs = 60 # 撤单等待时间(秒)
def handlebar(C):
    """每个Bar触发一次的交易处理函数"""
    if not C.is_last_bar():
        return
    now_time = datetime.datetime.now().strftime('%H%M%S')
    if now_time < '093000' or now_time > "150000":
        return
    # 1. 获取账户信息
    account_info = get_trade_detail_data(A.acct, A.acct_type, 'account')
    if len(account_info) == 0:
        print(f'账号{A.acct} 未登录')
        return
    acct = account_info
    available_cash = int(acct.m_dAvailable)
    # 2. 获取当前持仓
    position_list = get_trade_detail_data(A.acct, A.acct_type, 'position')
    current_position = {i.m_strInstrumentID + '.' + i.m_strExchangeID: int(i.m_nCanUseVolume) for i in position_list}
    # 3. 更新委托状态字典 (核心防超单机制)
    refresh_waiting_dict(C)
    # 4. 下新单前检查:如果有品种还处于等待状态,则暂停该品种的后续报单
    #    这里的“等待状态”指该品种的委托还在等待被查询到或处理完成
    if A.waiting_dict:
        print(f"当前有未完成委托的品种: {list(A.waiting_dict.keys())},暂停这些品种的后续报单")
        # 假设我们只交易一个股票,这里简化为:如果等待字典非空,就不操作
        # 如果是多品种策略,这里需要遍历你的目标持仓列表,跳过在waiting_dict中的品种
        return # 简单起见,这里直接返回
    # 5. 模拟你的交易信号和下单逻辑
    target_stock = '01.SZ' # 假设要交易的股票
    target_vol = 1000
    current_vol = current_position.get(target_stock, 0)
    if current_vol < target_vol and available_cash > 10000:
        # 计算需要买入的数量
        buy_vol = min((target_vol - current_vol), int(available_cash / 10 / 100) * 100)
        if buy_vol >= 100:
            # 生成唯一的投资备注
            msg = f"调仓买入 {target_stock} {int(time.time())}"
            print(f"发出买入委托: {msg}, 数量: {buy_vol}")
            # 在下单前,立刻将该品种加入等待字典,标记为等待状态
            A.waiting_dict[target_stock] = msg
            A.all_order_ref_dict[msg] = time.time()
            # 执行下单 (实际开发中这里是 passorder 函数)
            # passorder(23, 1101, A.acct, target_stock, 14, 0, buy_vol, '调仓策略', 2, msg, C)
            print(f"模拟下单: 股票 {target_stock}, 数量 {buy_vol}, 备注 {msg}")
    # 6. (可选)检查并撤销超时委托
    cancel_timeout_orders(C)
def refresh_waiting_dict(C):
    """
    核心函数:更新委托状态字典
    从柜台获取委托信息,如果某品种的委托已经达到终态(已成、已撤、废单等),
    则从 waiting_dict 中删除该品种,允许后续重新报单。
    """
    if not A.waiting_dict:
        return
    # 获取当前所有委托
    order_list = get_trade_detail_data(A.acct, A.acct_type, 'order')
    # 构建一个以投资备注为key的委托状态字典
    order_status_dict = {order.m_strRemark: order.m_nOrderStatus for order in order_list}
    # 要删除的品种列表
    stocks_to_remove = []
    for stock, remark in A.waiting_dict.items():
        if remark in order_status_dict:
            status = order_status_dict[remark]
            # 委托终态:56(已成), 53(部撤), 54(已撤), 57(废单)
            if status in [56, 53, 54, 57]:
                print(f"股票 {stock} 的委托 {remark} 状态为 {status} (终态),从等待字典中删除")
                stocks_to_remove.append(stock)
        else:
            # 如果没有查到该委托(比如刚刚下单还未被柜台接收),保持等待状态,打印日志
            pass
    # 执行删除
    for stock in stocks_to_remove:
        del A.waiting_dict[stock]
        if stock in A.all_order_ref_dict:
            # 同时清理对应的引用字典
            del A.all_order_ref_dict[remark_in_dict_for_this_stock]
def cancel_timeout_orders(C):
    """(可选)撤销超时的委托,防止委托一直挂在那里"""
    if not A.all_order_ref_dict:
        return
    order_list = get_trade_detail_data(A.acct, A.acct_type, 'order')
    for order in order_list:
        if order.m_strRemark in A.all_order_ref_dict:
            elapsed_time = time.time() - A.all_order_ref_dict[order.m_strRemark]
            # 如果委托超时且处于可撤状态
            if elapsed_time > A.withdraw_secs:
                # 可撤状态:50(已报), 51(已报待撤), 52(部成待撤), 55(部成), 86(已确认)等
                if order.m_nOrderStatus in [50, 51, 52, 55, 86]:
                    print(f"超时撤单: {order.m_strRemark}")
                    # cancel(order.m_strOrderSysID, A.acct, 'stock', C)

5QMT的行情数据主要分为哪几种类型?各自的特点和适用场景是什么

主要分为三种:

  1. **本地数据**:指下载到本地的历史行情数据加密文件。适合回测模式使用,对应接口为get_market_data_ex(subscribe=False)或get_local_data。速度快,无数量限制,但盘中不会更新。
  2. **全推数据**:客户端启动后自动接收的全市场最新数据快照(包括日线OHLC、成交量、五档盘口等)。只有最新值,无历史值。对应接口为get_full_tick和subscribe_whole_quote。盘中50ms更新一次,速度快,不用订阅,无品种数量限制,适合获取实时快照。
  3. **订阅数据**:指向行情服务器订阅指定品种的实时行情。有最大数量限制(如300个),订阅超过限制会导致数据重复填充。对应接口为subscribe_quote和get_market_data_ex(subscribe=True)。可以获取实时和历史数据,适合对特定品种进行持续跟踪。

470789542e7d0d8eb11328c3d8b410c9.png

参考

#coding:gbk
"""
QMT 行情数据三种类型教学示例
本文件整合了本地数据、全推数据和订阅数据的简单示例,
仅供学习参考,不构成投资建议。
"""
# =============================================================================
# 示例一:本地数据 (Local Data)
# -- 适合回测模式,速度快,无数量限制,盘中不更新。
# =============================================================================
def handlebar_local_data(C):
    """
    在回测模型的 handlebar 函数中,获取本地历史数据。
    核心:get_market_data_ex(subscribe=False)
    """
    # 获取浦发银行(600000.SH) 在 2022年1月的日线数据
    data = C.get_market_data_ex(
        fields=['close', 'open', 'high', 'low', 'volume'],
        stock_code=['0.SH'],
        period='1d',
        start_time='20220101',
        end_time='20220131',
        subscribe=False  # 关键参数:不订阅,只读本地
    )
    # 打印数据查看
    print("本地数据示例 - 浦发银行2022年1月数据:")
    print(data['0.SH'])
# =============================================================================
# 示例二:全推数据 (Full Push Data)
# -- 适合实盘实时监控,实时更新(50ms),无历史数据,无需订阅,无数量限制。
# =============================================================================
def init_full_push(C):
    """
    在实盘模型的 init 函数中,设置要关注的股票列表。
    """
    C.stock_list = ["01.SZ", "1.SH", "0.SH"]
def handlebar_full_push(C):
    """
    在实盘模型的 handlebar 函数中,获取全推最新数据。
    核心:get_full_tick()
    """
    # 调用 get_full_tick 获取全推最新行情
    full_tick_data = C.get_full_tick(C.stock_list)


    # 打印贵州茅台的最新行情
    maotai_tick = full_tick_data.get('9.SH')
    if maotai_tick:
        print("\n全推数据示例 - 最新行情:")
        print("最新价:", maotai_tick['lastPrice'])
        print("买一价:", maotai_tick['bidPrice'])
        print("卖一价:", maotai_tick['askPrice'])
# =============================================================================
# 示例三:订阅数据 (Subscribed Data)
# -- 适合实盘精细跟踪,可获取指定品种的实时和历史数据,有数量限制(如300个)。
# =============================================================================
def callback_subscribed_data(data):
    """
    订阅数据的回调函数,当有新的数据推送时,此函数会被调用。
    """
    print("\n订阅数据示例 - 收到行情推送:")
    print(data)
def init_subscribed_data(C):
    """
    在实盘模型的 init 函数中,订阅指定品种的行情。
    核心:subscribe_quote()
    """
    # 订阅贵州茅台(600519.SH)的日线行情,并指定回调函数
    C.sub_id = C.subscribe_quote(
        stock_code='19.SH',
        period='1d',       # 订阅周期
        dividend_type='none', # 不复权
        callback=callback_subscribed_data  # 指定回调函数
    )
    print("\n订阅成功,订阅号:", C.sub_id)
"""
使用说明:
1. 在实际使用时,您需要根据策略类型(回测/实盘)选择对应的函数。
2. 示例一(handlebar_local_data)主要用于回测模型的 handlebar 中。
3. 示例二(handlebar_full_push)通常需要配合 init(init_full_push)使用,适用于实盘。
4. 示例三(init_subscribed_data 和回调函数 callback_subscribed_data)适用于实盘,用于精细跟踪特定品种。
"""

更多的内容后面继续更新

4dc2022c3cd41f51c3558e3677269cd1.png

不懂的问我就可以,加我备注入群可以加入量化研究群

39308fba9541c2ffecbf555e5f6a5bae.jpg

量化需要的找我就可以,专业技术支持服务,解答

48cf751be499aa1d992e932511c9ec9a.png

评论1

落花忆流年楼主
发表于 5 小时前 | 显示全部楼层
不懂的问我就可以作者微信xg_quant

回复

您需要登录后才可以回帖 登录 | 立即注册

客服专线

400-080-8112

用思考的速度交易,用真诚的态度合作,我们是认真的!
  • 关注公众号
  • 添加微信客服
Copyright © 2001-2026 迅投QMT社区 版权所有 All Rights Reserved. 京ICP备2025122616号-3
关灯 快速发帖
扫一扫添加微信客服
QQ客服返回顶部
快速回复 返回顶部 返回列表