小市值与动量结合的量化策略
由jliang创建,最终由jliang 被浏览 240 用户
策略介绍
在量化投资领域,小市值股票和动量因子是两个广泛应用的选股指标。小市值股票因其相对较小的市值,更容易受到市场情绪和资金流入的影响,从而表现出高收益特性。而动量因子则反映了股票价格在一段时间内的趋势,具有延续性的特点。本文结合这两个因子,构建一个针对全A股市场的量化策略,旨在通过选择具有高动量的小市值股票来实现最大化的年化收益率。
策略背景
小市值股票和动量因子在量化投资中具有重要地位。小市值股票通常具备高成长潜力,能在短期内带来显著收益。动量因子则是基于“强者恒强,弱者恒弱”的市场动量效应,追踪价格上涨或下跌的趋势。结合这两个因子可以捕捉市场中的高收益机会。小市值股票的高波动性和动量因子的价格延续性,使得这一策略在牛市中表现尤为优异。
策略优势
- 高收益潜力:小市值股票由于其较小的市值,更容易受到市场资金的关注,涨幅潜力大。结合动量因子,可以筛选出短期内表现优异的股票,进一步提升收益。
- 简单易操作:策略仅依赖于市值和动量两个因子,不需要复杂的模型和数据处理,易于实现和执行。
- 市场适应性强:策略适用于全A股市场,无需特定行业或板块的限制,具有广泛的适用性。
策略核心
策略核心在于两点:一是选择市值较小的股票,二是选择动量较强的股票。具体步骤包括:
- 构建市值因子,选择市值较小的股票。
- 构建动量因子,选择动量较强的股票。
- 结合市值因子和动量因子进行排序,选择综合评分最高的股票。
- 每月调仓,持有评分最高的前50只股票,等权重分配。
策略劣势及改进
- 高波动性:小市值股票波动性较大,可能带来较高的风险。虽然用户明确表示不需要控制风险,但在实际应用中,建议加入一定的风险管理手段,如止损策略或仓位控制。
- 市场不稳定性:策略在市场下行阶段可能表现不佳,动量因子在市场剧烈波动时失效。可以考虑引入更多因子,如价值因子、质量因子等,进一步提升选股精度。
- 调仓成本:频繁调仓可能导致较高的交易成本,影响策略实际收益。建议优化调仓频率,或采用更高效的交易机制。
未来展望
未来,小市值与动量结合的量化策略仍然具有广阔的发展空间。随着市场数据的丰富和计算能力的提升,可以进一步引入机器学习和深度学习模型,优化因子组合和权重分配,提高策略的收益和稳定性。同时,结合更多的市场情绪和宏观经济数据,增强策略的适应性和抗风险能力。此外,跨市场应用也是一个重要方向,可以尝试在港股、美股等市场推广这一策略,实现全球化投资。
策略流程
该策略的具体流程如下:
- 股票池筛选:选取全A股市场作为股票池,覆盖上交所、深交所和北交所的所有股票。
- 子构建:使用市值因子和动量因子,分别计算股票的市值排名和动量值。
- 因子综合:将市值因子和动量因子进行综合,得到综合评分。
- 排序与持仓:根据综合评分从高到低排序,选择前50只股票进行等权重持仓。
- 调仓与回测:每月调仓一次,基于最新因子数据调整持仓组合,并进行策略回测。
BigQuant 平台策略实现
模块说明
cn_stock_basic_selector:A股-基础选股,用于筛选全A股市场股票池。 参数:
exchanges=["上交所", "深交所", "北交所"](全A股市场)
input_features_dai:输入特征(DAI SQL),用于输入因子表达式。 参数:
expr="""
c_rank(float_market_cap) AS score_market_cap
momentum_5
score_market_cap - momentum_5 AS score
"""
score_to_position:仓位分配,根据因子排序规则和持仓股票数量控制仓位。 参数:
score_field="score DESC"(根据综合因子从大到小排序)
hold_count=50(持有50只股票)
position_expr="1 AS position"(等权重分配)
extract_data_dai:数据抽取(DAI),用于抽取因子数据。 参数:
start_date="2021-01-01"(回测开始日期)
end_date="2024-04-29"(回测结束日期)
bigtrader:BigTrader(高性能回测),实现回测逻辑。 参数:
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"(每月第一个交易日调仓)
benchmark="沪深300指数"(基准为沪深300指数)
因子表达式
构建市值因子和动量因子:
c_rank(float_market_cap) AS score_market_cap
momentum_5
score_market_cap - momentum_5 AS score
策略代码
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=["上交所", "深交所", "北交所"],
list_sectors=["主板", "创业板", "科创板"],
indexes=["中证500", "上证指数", "创业板指", "深证成指", "上证50", "科创50", "沪深300", "中证1000", "中证100", "深证100"],
st_statuses=["正常", "ST", "*ST"],
margin_tradings=["两融标的", "非两融标的"],
sw2021_industries=["农林牧渔", "采掘", "基础化工", "钢铁", "有色金属", "建筑建材", "机械设备", "电子", "汽车", "交运设备", "信息设备", "家用电器", "食品饮料", "纺织服饰", "轻工制造", "医药生物", "公用事业", "交通运输", "房地产", "金融服务", "商贸零售", "社会服务", "信息服务", "银行", "非银金融", "综合", "建筑材料", "建筑装饰", "电力设备", "国防军工", "计算机", "传媒", "通信", "煤炭", "石油石化", "环保", "美容护理"],
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 score_market_cap
momentum_5
score_market_cap - momentum_5 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=50,
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="2021-01-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="2021-01-01",
end_date="2024-04-29",
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>
\