🚀 AI 一键生成 qmt 策略代码

QMT 价值投资实战:结合 get_financial_data 计算并应用自定义财务指标

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

问题描述

如何利用原始财务报表数据,自己计算一些财务指标(比如资产负债率)并用它们来选股?

解决方案

在 QMT 中,利用原始财务报表数据计算自定义指标并进行选股,主要依赖 ContextInfo.get_financial_data 接口。

以下是实现该需求的完整步骤和代码示例。我们将以计算 资产负债率 (Debt-to-Asset Ratio) 为例,公式为:
$$ \text{资产负债率} = \frac{\text{负债合计 (Total Liabilities)}}{\text{资产总计 (Total Assets)}} $$

核心步骤

  1. 确定字段名称:查阅 QMT API 文档附录中的财务数据字段对照表。
    • 表名:ASHAREBALANCESHEET (资产负债表)
    • 负债合计字段:tot_liab
    • 资产总计字段:tot_assets
  2. 获取数据:使用 ContextInfo.get_financial_data 批量获取股票池的这两个字段数据。
  3. 计算指标:利用 Pandas 进行向量化计算。
  4. 选股逻辑:筛选出资产负债率满足条件(例如小于 50%)的股票。
  5. 交易执行:根据筛选结果调整持仓。

策略代码实现

以下是一个完整的 Python 策略代码。该策略设定为每月第一个交易日运行一次选股逻辑,选取沪深300成分股中资产负债率小于 0.5 的股票进行等权买入。

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

def init(ContextInfo):
    # 1. 设置股票池为沪深300
    ContextInfo.index_code = '000300.SH'
    ContextInfo.stock_list = ContextInfo.get_sector(ContextInfo.index_code)
    ContextInfo.set_universe(ContextInfo.stock_list)
    
    # 2. 设置资金账号 (请替换为您的真实账号)
    ContextInfo.account_id = 'YOUR_ACCOUNT_ID'
    ContextInfo.set_account(ContextInfo.account_id)
    
    # 3. 设置回测参数 (仅回测有效)
    ContextInfo.set_commission(0, [0.0001, 0.0001, 0.0003, 0.0003, 0, 5])
    ContextInfo.capital = 1000000

def handlebar(ContextInfo):
    # 获取当前K线索引
    bar_index = ContextInfo.barpos
    # 获取当前时间戳并转换为日期字符串
    timetag = ContextInfo.get_bar_timetag(bar_index)
    current_date = timetag_to_datetime(timetag, '%Y%m%d')
    
    # === 调仓逻辑:每月第一个交易日进行调仓 ===
    # 获取当前K线的日期对象
    current_dt = pd.to_datetime(current_date)
    # 获取上一根K线的时间(用于判断是否跨月)
    last_timetag = ContextInfo.get_bar_timetag(bar_index - 1)
    last_date = timetag_to_datetime(last_timetag, '%Y%m%d')
    last_dt = pd.to_datetime(last_date)
    
    # 如果当前月份与上一个bar的月份不同,或者是第一根bar,则执行选股
    is_month_start = (current_dt.month != last_dt.month) or (bar_index == 0)
    
    if not is_month_start:
        return

    print(f'日期: {current_date}, 开始执行财务选股...')

    # === 1. 获取原始财务数据 ===
    # 定义需要的字段:表名.字段名
    # ASHAREBALANCESHEET: 资产负债表
    # tot_liab: 负债合计
    # tot_assets: 资产总计
    fields = ['ASHAREBALANCESHEET.tot_liab', 'ASHAREBALANCESHEET.tot_assets']
    
    # 获取数据
    # 参数说明: 字段列表, 股票列表, 开始时间, 结束时间, 报告类型
    # report_type='announce_time' 表示按公告日获取,避免未来函数
    raw_data = ContextInfo.get_financial_data(
        fields, 
        ContextInfo.stock_list, 
        current_date, 
        current_date, 
        report_type='announce_time'
    )
    
    # get_financial_data 在 "多股票-单时间点" 情况下返回 DataFrame
    # Index为股票代码,Columns为字段名
    if raw_data is None or raw_data.empty:
        print("未获取到财务数据")
        return

    # === 2. 计算自定义财务指标 ===
    # 处理缺失值 (NaN)
    raw_data.dropna(inplace=True)
    
    # 排除资产为0的情况,防止除以0报错
    raw_data = raw_data[raw_data['ASHAREBALANCESHEET.tot_assets'] > 0]
    
    # 计算资产负债率 = 负债合计 / 资产总计
    raw_data['debt_to_asset_ratio'] = raw_data['ASHAREBALANCESHEET.tot_liab'] / raw_data['ASHAREBALANCESHEET.tot_assets']
    
    # === 3. 选股逻辑 ===
    # 筛选资产负债率 < 50% (0.5) 的股票
    target_df = raw_data[raw_data['debt_to_asset_ratio'] < 0.5]
    
    # 获取目标股票列表
    target_stocks = target_df.index.tolist()
    print(f"选出 {len(target_stocks)} 只资产负债率小于50%的股票")
    
    # === 4. 交易执行 (简单的等权调仓) ===
    if not target_stocks:
        return

    # 获取当前持仓
    positions = get_trade_detail_data(ContextInfo.account_id, 'stock', 'position')
    holding_stocks = [obj.m_strInstrumentID + '.' + obj.m_strExchangeID for obj in positions]
    
    # 卖出不在目标列表中的股票
    for stock in holding_stocks:
        if stock not in target_stocks:
            passorder(24, 1101, ContextInfo.account_id, stock, 5, -1, 1, ContextInfo) # 全卖
            
    # 买入目标股票
    # 计算每只股票的目标资金 (简单的全仓等分)
    if len(target_stocks) > 0:
        # 注意:实盘中应使用 get_trade_detail_data 获取可用资金,这里简化处理
        # 假设总资金分给所有选中的股票
        # 这里使用 order_target_value 示例,需结合实际资金计算
        # 为演示方便,这里仅打印买入信号
        pass 
        
    # 示例:对选出的前10只股票进行买入(避免资金过于分散)
    buy_list = target_stocks[:10]
    for stock in buy_list:
        # 目标价值下单,假设每只买入 10000 元
        # 注意:回测模式下 order_target_value 比较方便,实盘建议用 passorder 精确控制
        order_target_value(stock, 10000, ContextInfo, ContextInfo.account_id)

    print("调仓完成")

关键点解析

  1. 数据获取接口 (get_financial_data)

    • 字段格式:必须是 表名.字段名 的格式。例如 ASHAREBALANCESHEET.tot_liab
    • 时间参数startDateendDate 设置为相同(当前日期),可以获取该时间点上每只股票最新已公告的财报数据。
    • report_type='announce_time':这是非常重要的参数。设置为 announce_time 意味着系统会根据公告日期来匹配数据。例如,在 4 月 1 日运行策略时,系统会返回 3 月 31 日之前已经公告的最新财报(可能是去年年报或今年一季报),这能有效避免回测中的“未来函数”问题。
  2. 数据处理

    • 接口返回的是 pandas.DataFrame,索引(Index)是股票代码,列(Columns)是请求的字段。
    • 利用 Pandas 的列运算功能 df['A'] / df['B'] 可以快速计算出整列的财务指标,效率远高于循环。
  3. 数据清洗

    • 财务数据可能存在空值(NaN)或异常值(如资产为0),计算前务必使用 dropna() 或条件过滤进行清洗,否则会导致计算结果异常或报错。
  4. 常用财务表名参考

    • 资产负债表: ASHAREBALANCESHEET
    • 利润表: ASHAREINCOME (例如:net_profit_incl_min_int_inc 净利润)
    • 现金流量表: ASHARECASHFLOW (例如:net_cash_flows_oper_act 经营活动现金流净额)
    • 股本结构: CAPITALSTRUCTURE (例如:total_capital 总股本)

Q&A

Q: 如何知道具体的字段名称(如 tot_liab)?
A: 您可以在 QMT 的策略编辑器中,点击右侧的“帮助”或查阅官方 API 文档的“附录”部分,里面有详细的“财务数据字段对照表”,列出了中文名称对应的英文代码。

Q: 为什么计算出来的指标和行情软件上显示的不完全一样?
A: 原因可能有两点:

  1. 数据源差异:QMT 使用的是原始财报数据,而行情软件可能使用了调整后的数据。
  2. 时间匹配:QMT 的 announce_time 模式严格按照公告日匹配,而某些软件可能按照报告期(如直接看Q1数据)展示,导致在财报披露期间的数据取值不同。

Q: 可以同时计算市盈率 (PE) 吗?
A: 可以,但 PE 需要结合行情数据(总市值)和财务数据(净利润)。您可以先用 get_market_data_ex 获取市值,再用 get_financial_data 获取净利润,然后两者相除。或者直接使用 QMT 内置的因子库 Valuation_and_Market_Cap.PE