问题描述
解决方案
这是一个基于 QMT 平台的动态选股策略脚本。
策略逻辑说明
该脚本实现了以下筛选逻辑:
- 股票池范围:以 沪深300 成分股为基础池。
- 剔除垃圾股:剔除 *ST、ST 股票,剔除 停牌 股票。
- 基本面筛选:筛选市盈率(PE)在 0 到 30 之间的股票。
- 技术面筛选:筛选当前收盘价 大于 20日均线 的股票(多头趋势)。
- 执行频率:在日线级别上,每个交易日运行一次筛选,并更新系统的股票池 (
ContextInfo.set_universe)。
QMT 策略代码
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
def init(ContextInfo):
"""
初始化函数
"""
print("策略初始化...")
# 设置基准,这里设为沪深300
ContextInfo.benchmark = '000300.SH'
# 设定初始股票池为空,后续动态更新
ContextInfo.set_universe([])
# 定义筛选参数
ContextInfo.target_sector = '沪深300' # 基础板块
ContextInfo.pe_min = 0 # 最小市盈率
ContextInfo.pe_max = 30 # 最大市盈率
ContextInfo.ma_period = 20 # 均线周期
def handlebar(ContextInfo):
"""
K线周期运行函数
"""
# 为了避免盘中每个Tick都计算,我们只在K线生成的新Bar时计算
# 如果是回测模式,每根K线都会触发;如果是实盘,建议配合定时器或is_new_bar使用
if not ContextInfo.is_new_bar():
return
# 获取当前K线的时间
current_date = timetag_to_datetime(ContextInfo.get_bar_timetag(ContextInfo.barpos), '%Y%m%d')
print(f"当前日期: {current_date},开始执行选股...")
# 1. 获取基础股票池 (沪深300)
stock_list = ContextInfo.get_stock_list_in_sector(ContextInfo.target_sector)
if not stock_list:
print("未获取到板块成分股")
return
# 2. 剔除 ST 和 停牌股
valid_stocks = []
for code in stock_list:
# 剔除停牌
if ContextInfo.is_suspended_stock(code):
continue
# 剔除 ST / *ST
name = ContextInfo.get_stock_name(code)
if 'ST' in name:
continue
valid_stocks.append(code)
if not valid_stocks:
print("剔除ST和停牌后无剩余股票")
return
# 3. 获取基本面数据 (PE - 市盈率)
# 使用 get_factor_data 获取因子数据
# 字段: Valuation_and_Market_Cap.PE (市盈率)
factor_fields = ['Valuation_and_Market_Cap.PE']
# 注意:get_factor_data 获取的是时间序列数据,我们需要取最后一天
# 这里简单处理,start和end都设为当前日期
factor_data = ContextInfo.get_factor_data(
factor_fields,
valid_stocks,
current_date,
current_date
)
# 筛选 PE 符合条件的股票
fundamental_select = []
# 处理返回的数据格式,通常是 Dict 或 DataFrame
# 如果返回的是 Dict {code: df}
if isinstance(factor_data, dict):
for code in valid_stocks:
if code in factor_data:
df = factor_data[code]
if not df.empty:
pe_val = df['Valuation_and_Market_Cap.PE'].iloc[-1]
if ContextInfo.pe_min < pe_val < ContextInfo.pe_max:
fundamental_select.append(code)
# 如果返回的是 DataFrame (视版本和参数而定)
elif isinstance(factor_data, pd.DataFrame):
# 这里假设是 Panel 转成的 DF 或者直接是 DF,具体需根据实盘调试
# 为保证稳健性,建议使用上面的 dict 遍历方式,或者打印 type(factor_data) 确认
pass
if not fundamental_select:
print("基本面筛选后无股票")
return
# 4. 获取技术面数据 (收盘价 和 均线)
# 获取过去 ma_period + 5 天的数据,确保能计算出 MA
market_data = ContextInfo.get_market_data_ex(
['close'],
fundamental_select,
period='1d',
count=ContextInfo.ma_period + 1,
dividend_type='front' # 前复权
)
final_pool = []
for code in fundamental_select:
if code in market_data:
df_price = market_data[code]
if len(df_price) >= ContextInfo.ma_period:
# 计算 MA20
ma_value = df_price['close'].rolling(window=ContextInfo.ma_period).mean().iloc[-1]
current_price = df_price['close'].iloc[-1]
# 筛选:收盘价 > 20日均线
if current_price > ma_value:
final_pool.append(code)
# 5. 更新股票池
print(f"选股完成,共选中 {len(final_pool)} 只股票")
if len(final_pool) > 0:
print(f"部分选中股票示例: {final_pool[:5]}")
# 将选出的股票设定为当前的 ContextInfo 股票池
# 后续的交易函数可以直接对这个池子进行操作
ContextInfo.set_universe(final_pool)
# 可选:在这里直接根据 final_pool 进行买卖操作
# rebalance_portfolio(ContextInfo, final_pool)
代码关键点解析
-
# -*- coding: gbk -*-:- QMT 的 Python 编辑器默认编码通常是 GBK,加上这行可以防止中文注释乱码。
-
ContextInfo.get_stock_list_in_sector:- 用于获取板块成分股,支持 '沪深300'、'中证500'、'上证50' 等,也可以是用户自定义的板块名称。
-
ContextInfo.get_factor_data:- 这是 QMT 获取基本面因子的核心接口。
Valuation_and_Market_Cap.PE是市盈率因子。如果需要市净率可以使用Valuation_and_Market_Cap.PB。- 注意:使用此接口前,请确保在 QMT 客户端的【数据管理】中下载了对应的【多因子数据】。
-
ContextInfo.get_market_data_ex:- 这是获取行情数据的高效接口。
count=ContextInfo.ma_period + 1:为了计算 20 日均线,我们需要至少 20 根 K 线的数据。dividend_type='front':技术分析通常使用前复权数据,以消除分红配股对价格走势的干扰。
-
ContextInfo.set_universe(final_pool):- 这一步将筛选出的股票列表更新到策略环境中。如果你在图表上运行,这不会改变图表显示的股票,但会改变策略内部逻辑(如
get_universe())获取到的列表。
- 这一步将筛选出的股票列表更新到策略环境中。如果你在图表上运行,这不会改变图表显示的股票,但会改变策略内部逻辑(如
如何使用
- 打开 QMT 客户端,进入【模型研究】或【策略交易】。
- 新建一个 Python 策略。
- 将上述代码复制粘贴进去。
- 重要:在运行或回测前,请务必在【数据管理】中补充下载历史行情数据和多因子数据。
- 点击【运行】或【回测】。
Q&A
Q: 如何修改筛选条件,例如改成筛选市净率(PB)小于 2 的股票?
A: 修改 init 函数中的参数,并更改 get_factor_data 的字段。
- 在
init中添加ContextInfo.pb_max = 2。 - 将
factor_fields改为['Valuation_and_Market_Cap.PB']。 - 在筛选逻辑中判断
df['Valuation_and_Market_Cap.PB'].iloc[-1] < ContextInfo.pb_max。
Q: 为什么 get_factor_data 返回的数据是空的?
A: 这通常是因为本地没有下载多因子数据。请在 QMT 客户端顶部菜单栏找到【数据】->【数据管理】,选择【多因子数据】,勾选对应的市场和时间段进行下载补充。
Q: 这个脚本会自动买入选中的股票吗?
A: 目前的代码只负责筛选(生成股票池)。如果需要自动交易,需要在 handlebar 函数的最后添加交易逻辑,例如遍历 final_pool 调用 passorder 或 order_target_value 函数进行下单。