先看一下我们的收益: JoinQuant直达这里看看
下面讲解原理和代码。
目录
一、是否为st
二、是否停牌
三、市值小、roe大
四、编写回测代码
今天来研究一下多因子回测模型,这里以‘市值’、‘roe’作为例子。
几个标准:沪深300里选股,市值小、roe大、排出st 停牌、定期调仓
一、是否为st
# 是否st
is_st_info = get_extras('is_st', ['000001.XSHE', '000018.XSHE'], start_date='2025-04-01', end_date='2025-04-10', df=True)
print(is_st_info)
000001.XSHE 000018.XSHE
2025-04-01 False True
2025-04-02 False True
2025-04-03 False True
2025-04-07 False True
2025-04-08 False True
2025-04-09 False True
2025-04-10 False True
然后我们改装成为函数
# 获取st股票
def get_st_stocks(stock_list, end_date, count):
is_st_info = get_extras('is_st', stock_list, end_date=end_date, count=count, df=True)
st_stocks = []
for stock in stock_list:
if is_st_info[stock].any(): # 检查该股票是否在任何一天为True
st_stocks.append(stock)
return st_stocks
x = get_st_stocks(['300956.XSHE', '300542.XSHE', '000018.XSHE'], end_date='2025-04-12', count=5)
print(x)
二、是否停牌
# 是否停牌
x = get_price(['300956.XSHE', '300542.XSHE'], count=5, end_date='2025-04-12',
fields='paused',
frequency='daily', skip_paused=False, panel=False)
print(x)
time code paused
0 2025-04-07 300956.XSHE 0.0
1 2025-04-08 300956.XSHE 0.0
2 2025-04-09 300956.XSHE 0.0
3 2025-04-10 300956.XSHE 1.0
4 2025-04-11 300956.XSHE 1.0
5 2025-04-07 300542.XSHE 0.0
6 2025-04-08 300542.XSHE 1.0
7 2025-04-09 300542.XSHE 1.0
8 2025-04-10 300542.XSHE 1.0
9 2025-04-11 300542.XSHE 1.0
同样,我们改装成函数
# 是否停牌
def get_paused_stocks(stock_list, end_date, count):
paused_info = get_price(stock_list, count=count, end_date=end_date,
fields='paused', frequency='daily',
skip_paused=False, panel=False)
paused_stocks = []
for stock in stock_list:
if (paused_info[paused_info['code'] == stock]['paused'] == 1.0).any(): # 检查该股票是否在任何一天为1.0
paused_stocks.append(stock)
return paused_stocks
x = get_paused_stocks(['000001.XSHE', '300956.XSHE', '300542.XSHE'], end_date='2025-04-12', count=5)
print(x)
三、市值小、roe大
获取某一天沪深300股票中yb天不停牌、不st,且满足市值和ROE排名前N的股票
from jqdata import *
def get_mystocks(date, yb=63, N=20):
"""
获取某一天沪深300股票中yb天不停牌、不st,且满足市值和ROE排名前N的股票
:param date: 指定日期
:param yb: 多少天不停牌
:param N: 排名前多少
:return: 满足条件的股票代码列表
"""
# 获取沪深300的成分股
hs300_stocks = get_index_stocks('000300.XSHG', date=date)
# 获取股票的基本面数据
q = query(
valuation.code,
valuation.market_cap,
indicator.roe
).filter(
valuation.code.in_(hs300_stocks)
)
df = get_fundamentals(q, date=date)
#print(df.head())
# 获取停牌股票
paused_stocks = get_paused_stocks(hs300_stocks, end_date=date, count=yb)
#print(paused_stocks)
# 获取st股票
st_stocks = get_st_stocks(hs300_stocks, end_date=date, count=yb)
#print(st_stocks)
# 获取剔除停牌和ST的股票代码列表
all_paused_stocks = set(paused_stocks + st_stocks)
# 从df中筛选出剔除停牌和ST的股票
df = df[~df['code'].isin(all_paused_stocks)]
# 对市值进行升序排名,对ROE进行降序排名
df['market_cap_rank'] = df['market_cap'].rank(method='first', ascending=True)
df['roe_rank'] = df['roe'].rank(method='first', ascending=False)
df['composite_rank'] = df['market_cap_rank'] + df['roe_rank']
# 按综合排名升序排序并选取前N
df_sorted = df.sort_values(by='composite_rank', ascending=True).head(N)
# 提取股票代码并返回
mystocks = df_sorted['code'].tolist()
return mystocks
mystocks = get_mystocks('2024-12-31')
print(mystocks)
['300628.XSHE', '002555.XSHE', '000876.XSHE', '300394.XSHE', '603833.XSHG', '605117.XSHG', '300316.XSHE', '000975.XSHE', '000661.XSHE', '002007.XSHE', '601021.XSHG', '601799.XSHG', '300896.XSHE', '688082.XSHG', '000807.XSHE', '000408.XSHE', '300832.XSHE', '600570.XSHG', '002920.XSHE', '600066.XSHG']
四、编写回测代码
# 导入函数库
from jqdata import *
import datetime
import numpy as np
# 策略初始化
def initialize(context):
set_benchmark('000300.XSHG')
set_option('use_real_price', True)
log.set_level('order', 'error')
set_order_cost(OrderCost(close_tax=0, open_commission=0, close_commission=0, min_commission=0), type='stock')
g.t = 0
g.tc = 15 # 调仓频率
g.yb = 63 # 样本长度
g.N = 20 # 持仓数量
run_daily(market_open, time='open', reference_security='000300.XSHG')
def market_open(context):
now = context.current_dt
before_1d_time = now - timedelta(days=1)
end_date = before_1d_time.strftime("%Y-%m-%d")
if g.t % g.tc == 0:
mystocks = get_mystocks(end_date, g.yb, g.N)
rebalance_position(context, mystocks)
g.t += 1
# 获取st股票
def get_st_stocks(stock_list, end_date, count):
is_st_info = get_extras('is_st', stock_list, end_date=end_date, count=count, df=True)
st_stocks = []
for stock in stock_list:
if is_st_info[stock].any(): # 检查该股票是否在任何一天为True
st_stocks.append(stock)
return st_stocks
# 是否停牌
def get_paused_stocks(stock_list, end_date, count):
paused_info = get_price(stock_list, count=count, end_date=end_date,
fields='paused', frequency='daily',
skip_paused=False, panel=False)
paused_stocks = []
for stock in stock_list:
if (paused_info[paused_info['code'] == stock]['paused'] == 1.0).any(): # 检查该股票是否在任何一天为1.0
paused_stocks.append(stock)
return paused_stocks
def get_mystocks(date, yb=63, N=20):
"""
获取某一天沪深300股票中yb天不停牌,且满足市值和ROE排名前N的股票
:param date: 指定日期
:param yb: 多少天不停牌
:param N: 排名前多少
:return: 满足条件的股票代码列表
"""
# 获取沪深300的成分股
hs300_stocks = get_index_stocks('000300.XSHG', date=date)
# 获取股票的基本面数据
q = query(
valuation.code,
valuation.market_cap,
indicator.roe
).filter(
valuation.code.in_(hs300_stocks)
)
df = get_fundamentals(q, date=date)
#print(df.head())
# 获取停牌股票
paused_stocks = get_paused_stocks(hs300_stocks, end_date=date, count=yb)
#print(paused_stocks)
# 获取st股票
st_stocks = get_st_stocks(hs300_stocks, end_date=date, count=yb)
#print(st_stocks)
# 获取剔除停牌和ST的股票代码列表
all_paused_stocks = set(paused_stocks + st_stocks)
# 从df中筛选出剔除停牌和ST的股票
df = df[~df['code'].isin(all_paused_stocks)]
# 对市值进行升序排名,对ROE进行降序排名
df['market_cap_rank'] = df['market_cap'].rank(method='first', ascending=True)
df['roe_rank'] = df['roe'].rank(method='first', ascending=False)
df['composite_rank'] = df['market_cap_rank'] + df['roe_rank']
# 按综合排名升序排序并选取前N
df_sorted = df.sort_values(by='composite_rank', ascending=True).head(N)
# 提取股票代码并返回
mystocks = df_sorted['code'].tolist()
return mystocks
"""
###################### 工具 ######################
调仓:
先卖出持仓中不在 stock_list 中的股票
再等价值买入 stock_list 中的股票
"""
def rebalance_position(context, stock_list):
current_holding = context.portfolio.positions.keys()
stocks_to_sell = list(set(current_holding) - set(stock_list))
# 卖出
bulk_orders(stocks_to_sell, 0)
total_value = context.portfolio.total_value
# 买入
bulk_orders(stock_list, total_value/len(stock_list))
# 批量买卖股票
def bulk_orders(stock_list,target_value):
for i in stock_list:
order_target_value(i, target_value)
上面就是所有的源码了。
参考:这里
原文太老了,代码也比较繁琐,故简化很多。