问题描述
解决方案
在 QMT (Quantitative Trading Platform) 中,获取上市公司十大股东数据主要使用 ContextInfo.get_top10_share_holder 接口。
基于股东变化构建策略通常遵循**“筹码集中度”逻辑:如果前十大股东持股比例总和增加,通常意味着筹码趋于集中,主力资金可能在收集筹码,视为看多信号;反之,如果持股比例下降,意味着筹码分散,主力可能在派发,视为看空**信号。
以下是详细的 API 说明及完整的策略代码实现。
1. 核心 API 说明
函数名: ContextInfo.get_top10_share_holder(stock_list, data_name, start_time, end_time)
- stock_list: (list) 股票代码列表,如
['600519.SH']。 - data_name: (string) 数据类型。
'holder': 十大股东(包含流通和非流通)。'flow_holder': 十大流通股东。
- start_time: (string) 开始时间,如
'20220101'。 - end_time: (string) 结束时间,如
'20230101'。
返回值:
返回 pandas.DataFrame(单股多季度或多股单季度)或 pandas.Panel(多股多季度)。
主要字段包括:holdRatio (持股比例), holdNum (持股数量), changReason (变动原因) 等。
2. 策略逻辑设计
本策略是一个低频趋势策略,逻辑如下:
- 标的:选取一只代表性股票(如贵州茅台
600519.SH)。 - 周期:日线级别回测(因为财报数据发布频率较低,无需分钟级)。
- 信号生成:
- 获取过去一年的十大流通股东数据。
- 按发布日期分组,计算每一期十大流通股东的持股比例总和。
- 对比最新一期与上一期的持股比例总和。
- 买入信号:如果最新一期总和 > 上一期总和(筹码集中),且当前无持仓,则全仓买入。
- 卖出信号:如果最新一期总和 < 上一期总和(筹码分散),且当前有持仓,则清仓卖出。
- 风控:由于财报数据有滞后性(公告日晚于报告期),策略在实盘中依赖于数据的及时更新。
3. 策略代码实现
# -*- coding: gbk -*-
import pandas as pd
import time
def init(ContextInfo):
# 1. 设置策略基本参数
ContextInfo.stock = '600519.SH' # 标的股票:贵州茅台
ContextInfo.account_id = '600000248' # 请替换为您的真实资金账号
ContextInfo.set_account(ContextInfo.account_id)
# 设置股票池
ContextInfo.set_universe([ContextInfo.stock])
# 2. 策略控制变量
ContextInfo.check_interval = 5 # 每隔5天检查一次数据,减少运算量
ContextInfo.days_counter = 0
def handlebar(ContextInfo):
# 跳过非交易日或未完成的K线
if not ContextInfo.is_last_bar():
return
# 限制检查频率
ContextInfo.days_counter += 1
if ContextInfo.days_counter < ContextInfo.check_interval:
return
ContextInfo.days_counter = 0
# 1. 获取当前时间
current_time = ContextInfo.get_bar_timetag(ContextInfo.barpos)
# 将时间戳转换为字符串格式 YYYYMMDD
end_date = timetag_to_datetime(current_time, '%Y%m%d')
# 设置开始时间为一年前,确保能取到至少两期财报数据
start_date = timetag_to_datetime(current_time - 365 * 24 * 3600 * 1000, '%Y%m%d')
# 2. 获取十大流通股东数据
# data_name 使用 'flow_holder' 获取流通股东,也可以用 'holder' 获取全部股东
try:
df = ContextInfo.get_top10_share_holder(
[ContextInfo.stock],
'flow_holder',
start_date,
end_date
)
except Exception as e:
print(f"获取数据异常: {e}")
return
if df is None or df.empty:
return
# 3. 数据处理
# 返回的 df index 通常是日期,columns 是字段名
# 我们需要按日期分组,计算持股比例(holdRatio)的总和
# 确保索引是日期格式以便排序
df.index = pd.to_datetime(df.index)
# 按日期分组求和,计算每一期十大股东的持股比例总和
# holdRatio 单位通常是百分比
concentration_series = df.groupby(df.index)['holdRatio'].sum().sort_index()
# 确保至少有两期数据进行比较
if len(concentration_series) < 2:
print("历史财报数据不足,无法比较")
return
# 获取最近两期的数据
last_ratio = concentration_series.iloc[-1] # 最新一期
prev_ratio = concentration_series.iloc[-2] # 上一期
report_date = concentration_series.index[-1].strftime('%Y-%m-%d')
# 获取当前持仓
positions = get_trade_detail_data(ContextInfo.account_id, 'stock', 'position')
current_holding = 0
for pos in positions:
if pos.m_strInstrumentID == ContextInfo.stock:
current_holding = pos.m_nVolume
break
# 4. 交易逻辑
# 信号:筹码集中(最新比例 > 上期比例) -> 买入
if last_ratio > prev_ratio:
if current_holding == 0:
print(f"[{end_date}] 筹码集中信号: 最新({last_ratio:.2f}%) > 上期({prev_ratio:.2f}%)。报告期:{report_date}。执行买入。")
# 全仓买入 (使用 order_target_percent 需要在回测设置中设置初始资金)
order_target_percent(ContextInfo.stock, 0.9, ContextInfo, ContextInfo.account_id)
else:
# 已经是持仓状态,继续持有
pass
# 信号:筹码分散(最新比例 < 上期比例) -> 卖出
elif last_ratio < prev_ratio:
if current_holding > 0:
print(f"[{end_date}] 筹码分散信号: 最新({last_ratio:.2f}%) < 上期({prev_ratio:.2f}%)。报告期:{report_date}。执行卖出。")
# 清仓
order_target_percent(ContextInfo.stock, 0, ContextInfo, ContextInfo.account_id)
else:
print(f"[{end_date}] 筹码无变化。")
4. 代码关键点解析
- 数据获取范围:
get_top10_share_holder需要足够长的时间窗口(代码中设置为过去365天),因为财报是季度发布的。如果时间窗口太短,可能只获取到一期数据,无法计算变化。 - 数据聚合:API 返回的数据包含前10名股东的详细信息(即每个日期有10行数据)。代码中使用
df.groupby(df.index)['holdRatio'].sum()将同一日期的10位股东持股比例加总,得到当期的“集中度”。 - 交易执行:使用了
order_target_percent函数。0.9代表调整仓位至总资产的 90%(买入)。0代表调整仓位至 0%(清仓卖出)。
- 时间处理:使用了
timetag_to_datetime将 QMT 的毫秒时间戳转换为 API 所需的'YYYYMMDD'字符串格式。
5. 注意事项
- 数据滞后性:这是此类策略最大的风险点。例如,一季报(截止3月31日的数据)可能在4月20日才公告。在回测中,QMT 的数据通常是按照公告日生效的,这避免了“未来函数”问题,但也意味着信号会有显著延迟。
- 数据清洗:有时上市公司会发布修正公告,导致同一报告期有两条记录,或者数据缺失。实际生产环境中需要增加更健壮的数据清洗逻辑。
- 回测模式:请确保在“回测模式”下运行此代码,并下载好对应的财务数据和行情数据。
Q&A
Q: 如何区分“十大股东”和“十大流通股东”?
A: 在 get_top10_share_holder 函数的第二个参数中设置。传入 'holder' 获取前十大股东(包含限售股),传入 'flow_holder' 获取前十大流通股东。通常二级市场交易更关注流通股东的变化。
Q: 这个策略适合高频交易吗?
A: 不适合。股东数据每季度才更新一次(年报、一季报、半年报、三季报),信号频率极低,属于中长线基本面/资金面策略。
Q: 如果我想跟踪特定机构(如“证金公司”)的持仓怎么办?
A: API 返回的 DataFrame 中包含 holdName(股东名称)字段。你可以在代码中遍历数据,筛选 holdName 包含特定关键字的行,然后单独计算其持股变化。