🚀 AI 一键生成 qmt 策略代码

QMT 量化实战:如何实现可转债折溢价套利策略?

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

问题描述

如何编写一个可转债折溢价套利策略?

解决方案

这是一个关于可转债折溢价套利策略的详细设计与实现方案。

在量化交易中,可转债(Convertible Bond)的套利主要分为两种逻辑:

  1. 折价套利(无风险/低风险套利):当转债价格 < 转股价值时,买入转债 -> 转股 -> 卖出股票。
  2. 双低/折溢价轮动(统计套利):买入“价格低 + 溢价率低”的转债,博取估值修复和正股上涨的收益。这是目前资金容量较大且在QMT上最容易实现的策略。

由于QMT回测引擎默认不支持在策略代码中直接模拟“T+1转股”的动作(需要复杂的持仓调整),下文将为你提供一个基于“双低”(价格+溢价率)因子的轮动策略代码。这是可转债最主流的量化策略。

策略逻辑:双低轮动策略

  1. 核心公式
    • 转股价值 = (100 / 转股价) × 正股价格
    • 转股溢价率 = (转债价格 - 转股价值) / 转股价值 × 100%
    • 双低值 = 转债价格 + 转股溢价率 × 100
  2. 选债逻辑
    • 选取市场上双低值最小的前N只转债。
    • 过滤掉剩余规模过小(防止流动性风险)或价格过高(防止强赎风险)的标的。
  3. 交易逻辑
    • 每日或每周进行一次持仓检查。
    • 卖出不再属于“双低”排名的持仓,买入新的双低标的。

QMT Python 策略代码

注意:计算溢价率需要正股价格转股价

  1. 正股价格可以通过API获取。
  2. 转股价的历史数据在标准行情API中通常不直接提供(需要购买额外的F10数据或财务数据包)。
    • 为了保证代码可直接运行,本示例采用**“低价策略”**(价格是双低因子的主要权重)作为演示,并保留了双低计算的框架供你填入转股价数据。
# -*- coding: gbk -*-
import pandas as pd
import numpy as np

def init(ContextInfo):
    """
    策略初始化函数
    """
    # 1. 设置资金账号 (请替换为你的实际账号)
    ContextInfo.accID = '6000000000' 
    ContextInfo.set_account(ContextInfo.accID)
    
    # 2. 设置回测参数
    ContextInfo.start = '20220101'
    ContextInfo.end = '20230101'
    ContextInfo.benchmark = '000300.SH' # 参考基准
    ContextInfo.capital = 1000000       # 初始资金
    
    # 3. 设定费率 (转债通常无印花税,手续费较低,这里设为万一)
    ContextInfo.set_commission(0, [0, 0, 0.0001, 0.0001, 0, 0])
    ContextInfo.set_slippage(1, 0.02)   # 设置滑点 0.02元
    
    # 4. 策略参数
    ContextInfo.hold_num = 10           # 持仓数量
    ContextInfo.adjust_period = 1       # 调仓周期(天)
    ContextInfo.days_counter = 0        # 计数器
    
    # 5. 设定股票池 (这里为了演示,手动列举了一些转债,实盘可订阅板块)
    # 实际使用建议使用 ContextInfo.get_stock_list_in_sector('沪深转债')
    # 注意:需要确保本地有这些转债的历史数据
    ContextInfo.cb_list = [
        '110059.SH', '113050.SH', '128062.SZ', '127061.SZ', '110088.SH',
        '123128.SZ', '113642.SH', '128111.SZ', '110052.SH', '123031.SZ',
        '127027.SZ', '113632.SH', '118000.SH', '123106.SZ', '113537.SH'
    ]
    ContextInfo.set_universe(ContextInfo.cb_list)

def handlebar(ContextInfo):
    """
    K线周期运行函数 (日线回测)
    """
    # 跳过非交易时间或未完成的K线
    if not ContextInfo.is_last_bar():
        return

    # 1. 调仓周期判断
    index = ContextInfo.barpos
    realtime = ContextInfo.get_bar_timetag(index)
    ContextInfo.days_counter += 1
    if ContextInfo.days_counter % ContextInfo.adjust_period != 0:
        return

    print(f'>> 开始调仓检测: {timetag_to_datetime(realtime, "%Y-%m-%d")}')

    # 2. 获取行情数据
    # 获取收盘价
    market_data = ContextInfo.get_market_data_ex(
        ['close', 'vol'], 
        ContextInfo.cb_list, 
        period='1d', 
        count=1, 
        subscribe=True
    )
    
    if not market_data:
        return

    # 3. 计算因子 (双低 = 价格 + 溢价率*100)
    # 由于API直接获取历史转股价较难,这里演示【低价策略】(双低策略的简化版)
    # 如果你有转股价数据,可解开下方注释进行完整双低计算
    
    factor_data = []
    
    for code, df in market_data.items():
        if df.empty:
            continue
            
        close_price = df['close'].iloc[-1]
        volume = df['vol'].iloc[-1]
        
        # 过滤停牌或无成交的标的
        if volume <= 0 or np.isnan(close_price):
            continue
            
        # --- 完整双低策略逻辑扩展区域 (需要外部数据源) ---
        # underlying_code = get_underlying_stock(code) # 需自定义映射函数
        # stock_price = get_stock_price(underlying_code)
        # convert_price = get_convert_price(code)      # 难点:需获取历史转股价
        # convert_value = (100.0 / convert_price) * stock_price
        # premium_rate = (close_price - convert_value) / convert_value * 100
        # double_low = close_price + premium_rate
        # -------------------------------------------
        
        # 【演示逻辑】:使用“价格”作为排序因子 (低价转债通常溢价率也较低,具有债底保护)
        # 过滤掉价格过高(>130)的转债,防止追高
        if close_price < 130:
            factor_data.append({
                'code': code,
                'factor': close_price, # 此处用价格代替双低值
                'price': close_price
            })
    
    # 4. 排序选债
    # 按因子从小到大排序 (低价优先)
    df_factor = pd.DataFrame(factor_data)
    if df_factor.empty:
        return
        
    df_factor = df_factor.sort_values(by='factor', ascending=True)
    
    # 取前N只
    target_list = df_factor['code'].head(ContextInfo.hold_num).tolist()
    print(f"目标持仓: {target_list}")

    # 5. 交易执行
    # 获取当前持仓
    positions = get_trade_detail_data(ContextInfo.accID, 'stock', 'position')
    current_holdings = [obj.m_strInstrumentID + '.' + obj.m_strExchangeID for obj in positions]
    
    # 卖出不在目标列表中的
    for code in current_holdings:
        if code not in target_list:
            # 检查是否是转债持仓(防止误卖其他股票)
            if code in ContextInfo.cb_list: 
                passorder(24, 1101, ContextInfo.accID, code, 14, -1, 0, ContextInfo)
                print(f"卖出: {code}")
    
    # 买入目标列表中的
    # 简单均仓模型:总资金 / 目标数量
    # 注意:实盘中需考虑可用资金
    target_value = ContextInfo.capital / ContextInfo.hold_num
    
    for code in target_list:
        # 使用 order_target_value 自动调整到目标市值
        # 注意:转债一手是10张,约1000元,QMT会自动向下取整
        order_target_value(code, target_value, ContextInfo, ContextInfo.accID)
        print(f"调整持仓: {code} 至市值 {target_value}")

# 辅助函数:时间戳转换
def timetag_to_datetime(timetag, format_str):
    import time
    return time.strftime(format_str, time.localtime(timetag / 1000))

代码关键点解析

  1. 数据获取 (get_market_data_ex)

    • 使用了 get_market_data_ex 批量获取转债的收盘价。这是QMT中最高效的数据获取方式。
    • 注意:你需要确保在QMT客户端的“数据管理”中下载了转债的历史日线数据,否则回测会没有数据。
  2. 因子计算 (核心逻辑)

    • 代码中演示的是低价策略factor = close_price)。这是因为在纯API环境下,获取历史动态的“转股价”比较困难。
    • 如何升级为真正的双低策略?
      • 你需要准备一份包含历史转股价的CSV文件,或者通过 ContextInfo.get_financial_data 尝试获取(但转股价变动通常属于公告事件,不在标准财报字段中)。
      • 如果是在实盘运行(非回测),你可以通过 get_full_tick 获取当天的实时转股溢价率(部分券商行情源支持),或者手动维护一个转股价字典。
  3. 交易执行 (order_target_value)

    • 使用了 order_target_value 函数。这个函数非常方便,它会自动计算当前持仓和目标市值的差额,自动下单买入或卖出,使该标的持仓市值达到指定金额。
  4. 标的池 (set_universe)

    • 代码中硬编码了一个 cb_list。在实际使用中,建议使用 ContextInfo.get_stock_list_in_sector('沪深转债') 来获取全市场转债,并配合 get_instrument_detail 过滤掉已退市的品种。

如何进行折价套利(进阶)

如果你想做真正的折价套利(买入转债->转股),逻辑如下,但QMT回测不支持,仅限实盘参考:

  1. 监控:实时计算 溢价率
  2. 触发:当 溢价率 < -1.5% (覆盖交易成本) 时。
  3. 操作
    • buy_open 买入转债。
    • 调用券商特定的转股接口(QMT标准API无转股函数,通常需要通过 passorder 发送特定的指令代码,如上海市场通常是卖出操作,代码为转股代码)。
    • 风险对冲:由于转股得到的股票T+1才能卖,你需要在期货市场(如IC/IF)做空,或者融券卖出正股(如果能融到券)来锁定T+0的利润。

常见问题 Q&A

Q: 为什么代码里没有计算转股溢价率?
A: QMT的标准API主要提供行情和基础财务数据。历史上的“转股价”是随分红、下修等事件变动的,标准API很难直接获取准确的历史转股价序列。对于回测,建议先下载好包含溢价率的外部数据(如CSV),在 init 中读取到内存中使用。

Q: 如何在实盘中获取实时溢价率?
A: 在实盘模式下,你可以通过订阅Level-2数据或计算实时数据:
转股价值 = (100 / 当前转股价) * 正股现价
你需要维护一个当前转股价的字典(可以每天早上从外部数据源更新一次)。

Q: 回测没有成交?
A: 1. 检查是否下载了“沪深转债”的日线数据。2. 检查 ContextInfo.cb_list 中的代码格式是否正确(如 110059.SH)。3. 检查初始资金是否足够买入一手转债(约1000元)。