返回列表 发布新帖

《QMT量化实战系列》支持自定义筛选与排序的多因子策略,实测年化收益超100%

26 0
发表于 5 小时前 | 显示全部楼层 阅读模式
今天,公众号将为全网读者带来QMT量化实战系列!
公众号将为大家多维度、多策略、多场景来讲述QMT量化平台的实践应用。同时,我们对每段代码都做了解读说明,愿你在Quant的道路上学有所获!
此系列将由浅入深,每月1~2期,大家敬请期待!
今日复现策略:《支持自定义筛选与排序的多因子策略》
image.png
image.png
策略逻辑解读
🧠 数据获取
通过Tushare获取数据,合成因子

  1. """获取股票列表"""
  2.         pick_date = ContextInfo.pick_date
  3.         # 获取股票每日指标
  4.         daily_basic = pro.daily_basic(trade_date=pick_date)
  5.         # 获取股票历史列表
  6.         bak_basic = pro.bak_basic(trade_date=pick_date)
  7.         # 合并两个数据
  8.         merged_df = daily_basic.merge(bak_basic, on=['trade_date', 'ts_code'], how='inner', suffixes=('', '_bak'))
  9.         # 去除重复列
  10.         merged_df = merged_df[[col for col in merged_df.columns if not col.endswith('_bak')]]
  11.         # 计算上市天数
  12.         merged_df['listed_days'] = (
  13.         pd.to_datetime(str(pick_date), format='%Y%m%d', errors='coerce') -
  14.         pd.to_datetime(merged_df['list_date'].astype(str).str.strip(), format='%Y%m%d', errors='coerce')).dt.days
复制代码


因子清单
因子字段
中文描述
ts_code
TS股票代码
trade_date
交易日期
close
当日收盘价
turnover_rate
换手率(%)
turnover_rate_f
换手率(自由流通股)
volume_ratio
量比
pe
市盈率
pe_ttm
市盈率(TTM)
pb
市净率
ps
市销率
ps_ttm
市销率(TTM)
dv_ratio
股息率(%)
dv_ttm
股息率(TTM)(%)
total_share
总股本(万股 / 亿股)
float_share
流通股本(万股 / 亿股)
free_share
自由流通股本(万)
total_mv
总市值(万元)
circ_mv
流通市值(万元)
name
股票名称
industry
所属行业
area
所属地域
total_assets
总资产(亿)
liquid_assets
流动资产(亿)
fixed_assets
固定资产(亿)
reserved
公积金
reserved_pershare
每股公积金
eps
每股收益
bvps
每股净资产
list_date
上市日期
undp
未分配利润
per_undp
每股未分配利润
rev_yoy
收入同比(%)
profit_yoy
利润同比(%)
gpr
毛利率(%)
npr
净利润率(%)
holder_num
股东人数

🔍 筛选逻辑  
通过因子筛选股票,因子列表的因子都能用于筛选
  1. # 排除所有名称中包含“ST”或“st”的股票
  2. stocks_info = stocks_info[~stocks_info['name'].str.contains('ST', case=False, na=False)]
  3. # 排除上市天数小于365天的股票
  4. stocks_info = stocks_info[stocks_info['listed_days'] >= 365]
  5. # 排除创业板
  6. stocks_info = stocks_info[~stocks_info['ts_code'].str.startswith('30')]
  7. # 排除科创板
  8. stocks_info = stocks_info[~stocks_info['ts_code'].str.startswith('68')]
  9. # 保留北交所
  10. #stocks_info = stocks_info[stocks_info['ts_code'].str.startswith(('8', '9','4'))]
  11. # 保留深证
  12. stocks_info = stocks_info[stocks_info['ts_code'].str.startswith(('00'))]
  13. # 排除北交所
  14. #stocks_info = stocks_info[~stocks_info['ts_code'].str.startswith(('8', '9','4'))]
复制代码


📊   排序逻辑
支持多因子自定义排序,并允许为各因子分配独立权重,实现更精细的打分和排序逻辑。
单因子
  1. # 按总市值从小到大排序  权重1
  2. factors = [        {'指标': 'total_mv', '次序': '从小到大', '权重': 1}]
复制代码

image.png
双因子
  1. # 按总市值和流通市值排序  权重分别是1和2
  2. factors = [        {'指标': 'total_mv', '次序': '从小到大', '权重': 1},        {'指标': 'circ_mv', '次序': '从小到大', '权重': 2},]
复制代码

image.png
多因子
  1. # 按总市值、流通市值、收盘价排序  权重分别是10、20、2
  2. factors = [        {'指标': 'total_mv', '次序': '从小到大', '权重': 10},        {'指标': 'circ_mv', '次序': '从小到大', '权重': 20},        {'指标': 'close', '次序': '从小到大', '权重': 2},]
复制代码

image.png
🔁 交易逻辑卖出逻辑
  • 根据因子排序找出每个持仓的当前排名
  • 如果股票排名 ≥ sell_rank 或不在候选列表,全仓卖出
  1. """卖出方法"""
  2.         sell_codes = []
  3.         code_list= list(stocks_info['ts_code'])
  4.         positions = get_trade_detail_data(ContextInfo.account, 'stock', 'position')
  5.         pos_codes = [f"{p.m_strInstrumentID}.{p.m_strExchangeID}" for p in positions]
  6.         daily_data = ContextInfo.get_market_data_ex([],pos_codes, period = "1d", start_time = ContextInfo.trade_date, end_time =  ContextInfo.trade_date,count=1)
  7.         for item in positions:
  8.                 code = f"{item.m_strInstrumentID}.{item.m_strExchangeID}"
  9.                 volume = item.m_nVolume
  10.                 # 交易日收盘价
  11.                 price = round(daily_data[code]['close'].iloc[0],2)
  12.                 # 当前排名
  13.                 current_rank = code_list.index(code) if code in code_list else 9999
  14.                 # 当前排名大于卖出排名就卖出
  15.                 if current_rank>=ContextInfo.sell_rank:
  16.                         logger.info(f'[触发卖出] 标的:{code}  卖出数量:{item.m_nVolume}  最新价格:{price}  当前排名:{current_rank}  卖出排名:{ContextInfo.sell_rank}')
  17.                         passorder(24, 1101, ContextInfo.account, code, 11,price , volume, "示例", 2, "投资备注",ContextInfo)
  18.                         sell_codes.append(code)
复制代码
买入逻辑
  • 获取当前持仓股票代码集合
  • 从目标列表剔除已持仓和待卖股票,得到买入候选池
  • 计算最大可买入数量,确保持仓总数不超过预设上限
  • 根据账户总资产均分资金,计算每只股票的买入金额及对应买入股

  1. """买入方法"""
  2.         # 当前持仓代码集合
  3.         current_positions = get_trade_detail_data(ContextInfo.account, 'stock', 'position')
  4.         current_codes = {f"{p.m_strInstrumentID}.{p.m_strExchangeID}" for p in current_positions}
  5.         # 需要买入的股票代码(不在当前持仓也不在卖出列表)
  6.         buy_candidates = set(target_codes) - set(current_codes) - set(sell_codes)
  7.         # 最多还能买多少只股票 如果buy_candidates的数量小于max_new_buys那就都买
  8.         max_new_buys = min(len(buy_candidates), max(ContextInfo.stock_num - len(current_codes), 0))
  9.         buy_count = 0
  10.         for code in buy_candidates:
  11.                 if buy_count >= max_new_buys:
  12.        break
  13.                 # 获取账户总资产
  14.                 total_asset = get_trade_detail_data(ContextInfo.account, 'stock', 'account')[0].m_dBalance
  15.                 # 计算单个股票买入金额
  16.                 order_amount = round(total_asset / ContextInfo.stock_num, 2)
  17.                 # 获取交易日行情数据
  18.                 daily_data = ContextInfo.get_market_data_ex([], [code], period="1d", start_time=ContextInfo.trade_date, end_time=ContextInfo.trade_date, count=1)
  19.                 if code not in daily_data or daily_data[code].empty:
  20.        logger.warning(f"[跳过买入] 无法获取行情数据:{code}")
  21.        continue
  22.                 price = round(daily_data[code]['close'].iloc[0], 2)
  23.                 if price > 0:
  24.        quantity = max(int(order_amount / price) // 100 * 100, 0)
  25.                         if quantity > 0:
  26.        passorder(23, 1101, ContextInfo.account, code, 11, price, quantity, "示例", 2, "投资备注", ContextInfo)
  27.        logger.info(f'[触发买入] 标的:{code}  买入数量:{quantity}  买入价格:{price}')
  28.        buy_count += 1
  29.        #print_account(ContextInfo)
  30.                 else:
  31.        logger.warning(f"[跳过买入] 价格为0:{code}")
复制代码




  后续扩展
  • 月份择时
  • 止盈止损
  • 实盘代码
完整代码获取,联系作者开通合作券商账户即可免费加入知识星球(限时)
需要进量化交流群的添加作者微信
image.png



回复

您需要登录后才可以回帖 登录 | 立即注册

客服专线

400-080-8112

用思考的速度交易,用真诚的态度合作,我们是认真的!
  • 关注公众号
  • 添加微信客服
Copyright © 2001-2025 迅投QMT社区 版权所有 All Rights Reserved. 京ICP备2025122616号-3
关灯 快速发帖
扫一扫添加微信客服
QQ客服返回顶部
快速回复 返回顶部 返回列表