🚀 AI 一键生成 qmt 策略代码

如何在 QMT 平台构建并管理一个多策略投资组合?

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

问题描述

我想同时运行好几个不同的交易策略,怎么把它们组合起来,并分配资金进行管理?

解决方案

在 QMT 中组合多个策略并进行资金分配,最佳的实践方式是采用 面向对象编程 (OOP) 的思想。

我们可以将每个独立的策略封装成一个 Python class(类)。主程序的 init 函数负责实例化这些策略并分配资金比例,主程序的 handlebar 函数负责循环调用这些策略的执行逻辑。

核心设计思路

  1. 策略封装:每个策略是一个独立的类,拥有自己的参数(如均线周期)、标的股票和状态变量。
  2. 资金管理:在主程序中获取账户总资产,根据预设的比例(例如 策略A 40%,策略B 60%)计算每个策略可用的资金上限。
  3. 统一调度:主图的 K 线驱动 handlebarhandlebar 再驱动所有子策略运行。

代码实现

以下是一个完整的示例代码。它包含两个不同的策略:

  1. TrendStrategy (趋势策略):双均线金叉买入,死叉卖出。
  2. ReversalStrategy (反转策略):RSI 超卖买入,超买卖出。

这两个策略将同时运行,分别交易不同的股票,并按比例分配资金。

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

# ==============================================================================
# 1. 定义策略基类与子策略
# ==============================================================================

class BaseStrategy:
    """
    策略基类,定义通用接口
    """
    def __init__(self, name, stock_code, capital_ratio):
        self.name = name                # 策略名称
        self.stock_code = stock_code    # 策略操作的标的
        self.capital_ratio = capital_ratio # 资金分配比例 (0.0 - 1.0)

    def on_bar(self, context, total_asset):
        """
        每根K线执行的逻辑,由主函数调用
        total_asset: 当前账户总资产
        """
        pass

class TrendStrategy(BaseStrategy):
    """
    子策略1:双均线趋势策略
    逻辑:短期均线上穿长期均线买入,下穿卖出
    """
    def __init__(self, name, stock_code, capital_ratio, short_period=5, long_period=20):
        super().__init__(name, stock_code, capital_ratio)
        self.short_period = short_period
        self.long_period = long_period

    def on_bar(self, context, total_asset):
        # 1. 获取数据
        # 获取足够的历史数据以计算均线
        data = context.get_market_data_ex(
            ['close'], 
            [self.stock_code], 
            period='1d', 
            count=self.long_period + 5, 
            dividend_type='front'
        )
        
        if self.stock_code not in data or data[self.stock_code].empty:
            return

        close_prices = data[self.stock_code]['close']
        
        if len(close_prices) < self.long_period:
            return

        # 2. 计算指标
        ma_short = close_prices.rolling(self.short_period).mean()
        ma_long = close_prices.rolling(self.long_period).mean()
        
        curr_short = ma_short.iloc[-1]
        curr_long = ma_long.iloc[-1]
        prev_short = ma_short.iloc[-2]
        prev_long = ma_long.iloc[-2]

        # 3. 计算本策略可用资金 (目标持仓市值)
        # 策略的目标市值 = 总资产 * 分配比例
        target_value = total_asset * self.capital_ratio

        # 4. 交易逻辑
        # 金叉:短线上穿长线 -> 买入(满仓该策略分配的额度)
        if prev_short < prev_long and curr_short > curr_long:
            print(f"[{self.name}] {self.stock_code} 金叉买入,目标市值: {target_value:.2f}")
            order_target_value(self.stock_code, target_value, context, context.account_id)
            
        # 死叉:短线下穿长线 -> 卖出(清仓)
        elif prev_short > prev_long and curr_short < curr_long:
            print(f"[{self.name}] {self.stock_code} 死叉卖出,目标市值: 0")
            order_target_value(self.stock_code, 0, context, context.account_id)

class ReversalStrategy(BaseStrategy):
    """
    子策略2:RSI反转策略
    逻辑:RSI < 30 买入,RSI > 70 卖出
    """
    def __init__(self, name, stock_code, capital_ratio, rsi_period=14):
        super().__init__(name, stock_code, capital_ratio)
        self.rsi_period = rsi_period

    def calculate_rsi(self, series, period):
        delta = series.diff()
        up = delta.clip(lower=0)
        down = -1 * delta.clip(upper=0)
        ma_up = up.rolling(window=period).mean()
        ma_down = down.rolling(window=period).mean()
        rsi = ma_up / (ma_up + ma_down) * 100
        return rsi

    def on_bar(self, context, total_asset):
        # 1. 获取数据
        data = context.get_market_data_ex(
            ['close'], 
            [self.stock_code], 
            period='1d', 
            count=self.rsi_period + 20, 
            dividend_type='front'
        )
        
        if self.stock_code not in data or data[self.stock_code].empty:
            return

        close_prices = data[self.stock_code]['close']
        
        if len(close_prices) < self.rsi_period:
            return

        # 2. 计算指标
        rsi_series = self.calculate_rsi(close_prices, self.rsi_period)
        current_rsi = rsi_series.iloc[-1]

        # 3. 计算本策略可用资金
        target_value = total_asset * self.capital_ratio

        # 4. 交易逻辑
        # 超卖:RSI < 30 -> 抄底买入
        if current_rsi < 30:
            print(f"[{self.name}] {self.stock_code} RSI超卖({current_rsi:.2f}),买入")
            order_target_value(self.stock_code, target_value, context, context.account_id)
            
        # 超买:RSI > 70 -> 止盈卖出
        elif current_rsi > 70:
            print(f"[{self.name}] {self.stock_code} RSI超买({current_rsi:.2f}),卖出")
            order_target_value(self.stock_code, 0, context, context.account_id)

# ==============================================================================
# 2. QMT 主程序入口
# ==============================================================================

def init(ContextInfo):
    # 设置账号 (请替换为您的真实账号)
    ContextInfo.account_id = '600000248' 
    ContextInfo.set_account(ContextInfo.account_id)
    
    # 初始化策略列表
    ContextInfo.strategies = []
    
    # --- 配置策略组合 ---
    
    # 策略A:趋势策略,操作 平安银行,分配 40% 资金
    s1 = TrendStrategy(
        name="趋势策略A", 
        stock_code="000001.SZ", 
        capital_ratio=0.4, 
        short_period=5, 
        long_period=10
    )
    ContextInfo.strategies.append(s1)
    
    # 策略B:反转策略,操作 贵州茅台,分配 60% 资金
    s2 = ReversalStrategy(
        name="反转策略B", 
        stock_code="600519.SH", 
        capital_ratio=0.6, 
        rsi_period=14
    )
    ContextInfo.strategies.append(s2)
    
    # 设置股票池(将所有策略用到的股票加入,以便回测或行情订阅)
    all_stocks = [s.stock_code for s in ContextInfo.strategies]
    ContextInfo.set_universe(all_stocks)
    
    # 回测参数设置
    ContextInfo.start = '20220101'
    ContextInfo.end = '20230101'
    ContextInfo.benchmark = '000300.SH'
    ContextInfo.capital = 1000000 # 回测初始资金

def handlebar(ContextInfo):
    # 跳过未完成的K线(如果是日线策略)
    if not ContextInfo.is_last_bar():
        return

    # 1. 获取当前账户总资产
    # 回测模式下使用 ContextInfo.capital + 盈亏 (简化处理直接取净值 * 初始资金,或者模拟计算)
    # 实盘模式下使用 get_trade_detail_data 获取真实资产
    
    total_asset = 0
    if ContextInfo.do_back_test:
        # 回测中获取当前净值 * 初始资金作为总资产估算
        # 注意:QMT回测中直接获取动态总资产比较复杂,这里用 get_net_value 近似
        # 或者简单地假设资金固定,仅做演示。
        # 更严谨的回测写法是根据持仓市值+现金计算。
        net_value = ContextInfo.get_net_value(ContextInfo.barpos)
        total_asset = ContextInfo.capital * net_value
    else:
        # 实盘模式:获取账户资金
        acct_info = get_trade_detail_data(ContextInfo.account_id, 'stock', 'account')
        if len(acct_info) > 0:
            total_asset = acct_info[0].m_dBalance # 总资产
        else:
            print("未获取到账户信息")
            return

    # 2. 遍历并运行所有策略
    for strategy in ContextInfo.strategies:
        try:
            strategy.on_bar(ContextInfo, total_asset)
        except Exception as e:
            print(f"策略 {strategy.name} 运行出错: {e}")

代码详解

  1. BaseStrategy

    • 这是一个父类,定义了所有策略共有的属性:name(策略名)、stock_code(操作标的)、capital_ratio(资金占比)。
    • 它规定了所有子类必须实现 on_bar 方法。
  2. TrendStrategyReversalStrategy

    • 这是具体的策略逻辑实现。
    • 它们在 __init__ 中接收各自特有的参数(如均线周期、RSI周期)。
    • on_bar 中,它们独立获取数据、计算指标。
    • 关键点:使用 order_target_value 下单。目标价值计算公式为 total_asset * self.capital_ratio。这意味着如果总资产是 100 万,分配比例 0.4,策略会努力将持仓市值调整到 40 万。如果当前持仓 0,它会买入 40 万;如果当前持仓 40 万且触发卖出信号,它会卖出至 0。
  3. init 函数

    • 创建了一个列表 ContextInfo.strategies
    • 实例化不同的策略对象,并添加到列表中。在这里你可以随意组合不同的策略、不同的股票和不同的资金比例。
    • 注意:所有策略的资金比例之和建议不要超过 1.0(100%),否则可能导致资金不足下单失败。
  4. handlebar 函数

    • 这是 QMT 的驱动核心。
    • 它首先获取账户的总资产(区分了回测和实盘的获取方式)。
    • 然后通过一个 for 循环,依次调用每个策略对象的 on_bar 方法,将控制权暂时移交给子策略。

这种架构的优点

  • 隔离性:修改“趋势策略”的代码不会影响“反转策略”。
  • 可扩展性:如果想加第三个策略,只需写一个新的类,并在 initappend 进去即可,无需修改 handlebar 的逻辑。
  • 资金风控:通过 capital_ratio 严格控制每个策略能使用的最大资金量,防止某个策略亏损过大拖累整体,或者某个策略占用过多资金导致其他策略无法开仓。

注意事项

  1. 标的冲突:本示例假设不同策略操作不同的股票。如果两个策略同时操作同一只股票(例如策略A想买,策略B想卖),上述代码会产生冲突(后执行的策略会覆盖先执行的策略的仓位目标)。如果需要多策略操作同一只股票,需要引入更复杂的“虚拟持仓”管理系统。
  2. 数据获取:使用 get_market_data_ex 是最高效的方式,它返回 Pandas DataFrame,方便计算指标。
  3. 实盘账号:请务必在 init 中将 ContextInfo.account_id 修改为您真实的资金账号。

Q&A

Q: 如果我想让策略A分配固定金额(如10万元),而不是比例,怎么改?
A: 可以在 BaseStrategy 中增加一个标记,比如 is_fixed_capital=True。在 on_bar 计算 target_value 时,判断如果是固定金额模式,直接使用该金额;如果是比例模式,则用 total_asset * ratio

Q: 这种写法支持期货吗?
A: 框架支持,但下单函数需要修改。期货通常使用 buy_open, sell_close 等函数,且需要处理多空方向。order_target_value 主要用于股票。你需要在子策略中重写下单逻辑。

Q: 如何查看每个策略的盈亏?
A: QMT 的原生界面只显示账户总盈亏。如果需要分策略统计,你需要在 BaseStrategy 中自己记录每次交易的盈亏,或者将交易记录输出到 CSV 文件中进行盘后分析。