基于小市值因子和动量因子的全A股量化策略
由ydong创建,最终由ydong 被浏览 101 用户
策略介绍
量化投资策略通过数学和统计方法,从历史数据中提取出有用的信息,指导投资决策。今天我们要介绍的是一个基于小市值因子和动量因子的全A股量化策略。该策略通过选择市值较小且动量较高的股票,力图在不进行额外风险控制的情况下,实现高年化收益。
小市值因子,即市值较小的股票往往具有较高的预期收益,这是因为小市值股票通常具有较高的成长性,但也伴随着较高的风险。动量因子,是指股票的价格具有惯性,即过去表现好的股票在未来一段时间内仍然可能表现较好。在实际操作中,动量因子常通过一定期间内的价格变化率来衡量。
该策略的优点在于通过组合小市值因子和动量因子,能够捕捉到市场中的高成长性机会,获得较高的年化收益。而缺点在于不进行风险控制,可能会在市场波动较大时面临较高的回撤风险。
策略流程
该量化策略的具体流程如下:
- 股票池:选择全A股市场,剔除停牌和ST股票。
- 因子计算:计算每只股票的小市值因子和动量因子。
- 综合排序:综合考虑小市值因子和动量因子,对股票进行排序。
- 持仓配置:选择排名靠前的30只股票,等权重分配持仓。
- 调仓逻辑:每月初进行一次调仓,根据最新的因子排序结果进行调整。
- 风险管理:不进行特别的风险管理。
策略实现
模块说明
为了实现上述策略,我们将使用BigQuant平台提供的多种模块,包括股票基础选股、因子特征计算、打分到仓位分配、数据抽取和高性能回测。
股票基础选股模块:cn_stock_basic_selector
- 用于筛选股票池,剔除停牌和ST股票。
- 参数:
drop_suspended=True
:剔除停牌股票st_statuses=["正常"]
:剔除ST股票
因子特征计算模块:input_features_dai
- 用于计算小市值因子和动量因子,并进行综合排序。
- 参数:
-
expr
:c_rank(float_market_cap) AS market_cap_rank m_avg(close, 20) / m_avg(close, 60) AS momentum market_cap_rank + momentum AS score
-
打分到仓位分配模块:score_to_position
- 根据综合评分进行仓位分配,选择排名前30的股票,等权重持仓。
- 参数:
score_field="score DESC"
:按综合评分从高到低排序hold_count=30
:持仓股票数量为30只position_expr="1 AS position"
:等权重分配
数据抽取模块:extract_data_dai
- 用于抽取构建好的因子数据。
- 参数:无特别修改
高性能回测模块:bigtrader
- 实现策略的回测逻辑,包括初始化函数和交易逻辑。
- 参数:
initialize
:初始化函数handle_data
:交易逻辑
因子表达式
构建因子表达式是策略的核心部分。我们使用以下因子表达式:
- 小市值因子:
c_rank(float_market_cap) AS market_cap_rank
- 动量因子:
m_avg(close, 20) / m_avg(close, 60) AS momentum
- 综合评分:
market_cap_rank + momentum AS score
小市值因子通过市值的倒数排名实现,动量因子通过20日均价与60日均价的比值实现。综合评分通过将两者相加得到。
策略实现
from bigmodule import M
# <aistudiograph>
# @param(id="m5", name="initialize")
def m5_initialize_bigquant_run(context): # type: ignore
from bigtrader.finance.commission import PerOrder
# 系统已经设置了默认的交易手续费和滑点,要修改手续费可使用如下函数
context.set_commission(PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))
# @param(id="m5", name="handle_data")
def m5_handle_data_bigquant_run(context, data): # type: ignore
import pandas as pd
# 下一个交易日不是调仓日,则不生成信号
if not context.rebalance_period.is_signal_date(data.current_dt.date()):
return
# 从传入的数据 context.data 中读取今天的信号数据
today_df = context.data[context.data["date"] == data.current_dt.strftime("%Y-%m-%d")]
target_instruments = set(today_df["instrument"])
# 获取当前已持有股票
holding_instruments = set(context.get_account_positions().keys())
# 卖出不在目标持有列表中的股票
for instrument in holding_instruments - target_instruments:
context.order_target_percent(instrument, 0)
# 买入目标持有列表中的股票
for i, x in today_df.iterrows():
position = 0.0 if pd.isnull(x.position) else float(x.position)
context.order_target_percent(x.instrument, position)
# @module(position="-437,-854", comment="""使用基本信息对股票池过滤""", comment_collapsed=False)
m1 = M.cn_stock_basic_selector.v6( # type: ignore
exchanges=["上交所", "深交所", "北交所"],
st_statuses=["正常"],
drop_suspended=True,
m_name="m1"
)
# @module(position="-315,-727", comment="""因子特征""", comment_collapsed=False)
m2 = M.input_features_dai.v29( # type: ignore
input_1=m1.data,
mode="表达式",
expr="""
c_rank(float_market_cap) AS market_cap_rank
m_avg(close, 20) / m_avg(close, 60) AS momentum
market_cap_rank + momentum AS score
""",
expr_filters="",
expr_tables="cn_stock_prefactors",
extra_fields="date, instrument",
order_by="date, instrument",
expr_drop_na=True,
extract_data=False,
m_name="m2"
)
# @module(position="-207,-592", comment="""持股数量、打分到仓位""", comment_collapsed=False)
m3 = M.score_to_position.v3( # type: ignore
input_1=m2.data,
score_field="score DESC",
hold_count=30,
position_expr="1 AS position",
total_position=1,
extract_data=False,
m_name="m3"
)
# @module(position="-98,-463", comment="""抽取预测数据""", comment_collapsed=False)
m4 = M.extract_data_dai.v17( # type: ignore
sql=m3.data,
start_date="2016-06-01",
start_date_bound_to_trading_date=True,
end_date="2024-04-29",
end_date_bound_to_trading_date=True,
before_start_days=90,
debug=False,
m_name="m4"
)
# @module(position="43,-350", comment="""交易,日线,设置初始化函数和K线处理函数,以及初始资金、基准等""", comment_collapsed=False)
m5 = M.bigtrader.v20( # type: ignore
data=m4.data,
start_date="",
end_date="",
initialize=m5_initialize_bigquant_run,
handle_data=m5_handle_data_bigquant_run,
capital_base=1000000,
frequency="daily",
product_type="股票",
rebalance_period_type="月度交易日",
rebalance_period_days="1",
rebalance_period_roll_forward=True,
backtest_engine_mode="标准模式",
before_start_days=0,
volume_limit=1,
order_price_field_buy="open",
order_price_field_sell="open",
benchmark="沪深300指数",
plot_charts=True,
debug=False,
backtest_only=False,
m_name="m5"
)
# </aistudiograph>
\