3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
这是一个基于 Fama-French 三因子模型(Fama-French Three-Factor Model) 的 QMT 量化选股策略实现。
Fama-French 三因子模型认为股票的超额收益可以由三个因子解释:
策略执行流程:
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
import time
def init(ContextInfo):
"""
策略初始化函数
"""
# 1. 设置账号 (请替换为您自己的资金账号)
ContextInfo.accid = 'YOUR_ACCOUNT_ID'
ContextInfo.set_account(ContextInfo.accid)
# 2. 策略参数设置
ContextInfo.target_num = 20 # 持仓股票数量
ContextInfo.index_code = '000905.SH' # 股票池:中证500
ContextInfo.rebalance_period = 'monthly' # 调仓周期
# 3. 记录上一次调仓的月份,用于控制月度调仓
ContextInfo.last_month = -1
print("Fama-French 三因子策略初始化完成")
def handlebar(ContextInfo):
"""
K线周期运行函数
"""
# 获取当前K线的时间
bar_time = ContextInfo.get_bar_timetag(ContextInfo.barpos)
current_date_str = timetag_to_datetime(bar_time, '%Y%m%d')
current_month = int(current_date_str[4:6])
# --- 1. 判断是否触发调仓 (每月调仓一次) ---
# 如果当前月份与上一次调仓月份相同,则跳过
if current_month == ContextInfo.last_month:
return
# 更新调仓月份标记
ContextInfo.last_month = current_month
print(f"开始进行月度调仓,当前日期: {current_date_str}")
# --- 2. 获取股票池与过滤 ---
# 获取指数成分股
stock_list = ContextInfo.get_sector(ContextInfo.index_code)
# 过滤停牌、ST股
valid_stocks = filter_stocks(ContextInfo, stock_list)
if not valid_stocks:
print("无有效股票,跳过本期调仓")
return
# --- 3. 获取因子数据 (市值 和 PB) ---
# 使用 QMT 的 get_factor_data 接口获取多因子数据
# 字段说明:
# Valuation_and_Market_Cap.MktValue : 总市值
# Valuation_and_Market_Cap.PB : 市净率
fields = ['Valuation_and_Market_Cap.MktValue', 'Valuation_and_Market_Cap.PB']
# 获取数据 (注意:get_factor_data 返回的数据格式需处理)
factor_data = ContextInfo.get_factor_data(fields, valid_stocks, current_date_str, current_date_str)
# 将数据转换为 DataFrame 格式方便计算
# 此时 factor_data 可能是一个 DataFrame (index=stock, columns=fields)
if not isinstance(factor_data, pd.DataFrame) or factor_data.empty:
print("因子数据获取失败")
return
df = factor_data.copy()
# --- 4. 数据清洗与因子计算 ---
# 去除空值
df.dropna(inplace=True)
# 剔除 PB <= 0 的股票 (通常为亏损或资不抵债)
df = df[df['Valuation_and_Market_Cap.PB'] > 0]
if df.empty:
return
# --- 5. 因子打分 (Rank) ---
# 规模因子(SMB): 市值越小越好 -> 升序排名 (rank越小越好)
df['rank_size'] = df['Valuation_and_Market_Cap.MktValue'].rank(ascending=True)
# 价值因子(HML): PB越低越好 (即BM越高越好) -> 升序排名 (rank越小越好)
df['rank_value'] = df['Valuation_and_Market_Cap.PB'].rank(ascending=True)
# 综合打分: 等权重相加
df['total_score'] = df['rank_size'] + df['rank_value']
# 按总分从小到大排序,取前 N 只
df.sort_values(by='total_score', ascending=True, inplace=True)
target_list = df.index[:ContextInfo.target_num].tolist()
print(f"本期选中股票 ({len(target_list)}只): {target_list}")
# --- 6. 执行交易 ---
rebalance_portfolio(ContextInfo, target_list)
def filter_stocks(ContextInfo, stock_list):
"""
过滤停牌和ST股票
"""
valid_list = []
for code in stock_list:
# 1. 剔除停牌
if ContextInfo.is_suspended_stock(code):
continue
# 2. 剔除 ST 股 (通过名称判断)
name = ContextInfo.get_stock_name(code)
if 'ST' in name or '退' in name:
continue
valid_list.append(code)
return valid_list
def rebalance_portfolio(ContextInfo, target_list):
"""
调仓执行函数:卖出不在目标池的,买入目标池的
"""
# 获取当前持仓
positions = ContextInfo.get_trade_detail_data(ContextInfo.accid, 'stock', 'position')
current_holdings = [pos.m_strInstrumentID for pos in positions if pos.m_nVolume > 0]
# 1. 卖出逻辑:不在目标列表中的股票全部清仓
for code in current_holdings:
if code not in target_list:
# 目标价值设为 0 即为清仓
order_target_value(code, 0, ContextInfo, ContextInfo.accid)
print(f"卖出清仓: {code}")
# 2. 买入逻辑:对目标列表股票进行等权重买入
if len(target_list) > 0:
# 计算每只股票的目标仓位比例 (1.0 / 股票数量)
# 注意:实盘中可能需要预留少量现金防止手续费不够,这里设为 0.98 总仓位
target_weight = 0.98 / len(target_list)
for code in target_list:
# 使用 order_target_percent 自动计算股数
order_target_percent(code, target_weight, ContextInfo, ContextInfo.accid)
print(f"调仓买入: {code}, 目标权重: {target_weight:.2%}")
数据获取 (get_factor_data):
Valuation_and_Market_Cap。MktValue 代表总市值,用于衡量规模因子。PB 代表市净率,用于衡量价值因子(低 PB 对应高价值)。排序逻辑 (rank):
rank() 函数对全市场(或股票池)进行排名。total_score = rank_size + rank_value 是一种简单有效的多因子合成方法。调仓控制:
current_month 和 ContextInfo.last_month 的对比,实现了月度调仓。只有在月份发生变化(即每个月的第一根 K 线)时才会触发交易逻辑。交易执行 (order_target_percent):
order_target_percent 是最简便的调仓方式,它会自动计算当前持仓与目标权重的差额,并自动下单买入或卖出。init 函数中的 'YOUR_ACCOUNT_ID' 替换为您真实的资金账号。Q: 为什么使用 PB 而不是 BM (账面市值比)?
A: 在 QMT 的因子库中,直接提供了 PB (市净率)。PB 是 BM 的倒数。Fama-French 原文中是买入 High BM 的股票,这等同于买入 Low PB 的股票。逻辑是一致的。
Q: 为什么回测时没有交易?
A: 请检查以下几点:
ContextInfo.accid 是否设置正确(回测模式下通常不需要真实账号,但代码中调用了 set_account,建议保留)。Q: 如何加入市场因子 (MKT)?
A: 在纯选股策略中,市场因子通常体现为“全额投资”或“持有高 Beta 股票”。上述代码默认满仓操作(0.98仓位),实际上已经暴露在市场风险下,符合三因子模型在多头端的应用。如果您想做多空对冲(Alpha 策略),则需要做空对应的股指期货。