问题描述
解决方案
在 QMT 中,利用原始财务报表数据计算自定义指标并进行选股,主要依赖 ContextInfo.get_financial_data 接口。
以下是实现该需求的完整步骤和代码示例。我们将以计算 资产负债率 (Debt-to-Asset Ratio) 为例,公式为:
$$ \text{资产负债率} = \frac{\text{负债合计 (Total Liabilities)}}{\text{资产总计 (Total Assets)}} $$
核心步骤
- 确定字段名称:查阅 QMT API 文档附录中的财务数据字段对照表。
- 表名:
ASHAREBALANCESHEET(资产负债表) - 负债合计字段:
tot_liab - 资产总计字段:
tot_assets
- 表名:
- 获取数据:使用
ContextInfo.get_financial_data批量获取股票池的这两个字段数据。 - 计算指标:利用 Pandas 进行向量化计算。
- 选股逻辑:筛选出资产负债率满足条件(例如小于 50%)的股票。
- 交易执行:根据筛选结果调整持仓。
策略代码实现
以下是一个完整的 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("调仓完成")
关键点解析
-
数据获取接口 (
get_financial_data):- 字段格式:必须是
表名.字段名的格式。例如ASHAREBALANCESHEET.tot_liab。 - 时间参数:
startDate和endDate设置为相同(当前日期),可以获取该时间点上每只股票最新已公告的财报数据。 report_type='announce_time':这是非常重要的参数。设置为announce_time意味着系统会根据公告日期来匹配数据。例如,在 4 月 1 日运行策略时,系统会返回 3 月 31 日之前已经公告的最新财报(可能是去年年报或今年一季报),这能有效避免回测中的“未来函数”问题。
- 字段格式:必须是
-
数据处理:
- 接口返回的是
pandas.DataFrame,索引(Index)是股票代码,列(Columns)是请求的字段。 - 利用 Pandas 的列运算功能
df['A'] / df['B']可以快速计算出整列的财务指标,效率远高于循环。
- 接口返回的是
-
数据清洗:
- 财务数据可能存在空值(NaN)或异常值(如资产为0),计算前务必使用
dropna()或条件过滤进行清洗,否则会导致计算结果异常或报错。
- 财务数据可能存在空值(NaN)或异常值(如资产为0),计算前务必使用
-
常用财务表名参考:
- 资产负债表:
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: 原因可能有两点:
- 数据源差异:QMT 使用的是原始财报数据,而行情软件可能使用了调整后的数据。
- 时间匹配:QMT 的
announce_time模式严格按照公告日匹配,而某些软件可能按照报告期(如直接看Q1数据)展示,导致在财报披露期间的数据取值不同。
Q: 可以同时计算市盈率 (PE) 吗?
A: 可以,但 PE 需要结合行情数据(总市值)和财务数据(净利润)。您可以先用 get_market_data_ex 获取市值,再用 get_financial_data 获取净利润,然后两者相除。或者直接使用 QMT 内置的因子库 Valuation_and_Market_Cap.PE。