手把手教你用BigQuant写第一个量化策略:高股息+低估值实战教程
由bq74n3yq创建,最终由bq74n3yq 被浏览 17 用户
一、假设已初步了解BigQuant平台
\
-
注册并登录BigQuant账号
-
了解BigQuant基础界面:Quant Agent、AI Studio、知识库、AI策略广场等等
-
数据覆盖:了解平台提供的数据种类、历史长度与可用频率。常用数据表举例:
- 股票后复权日行情cn_stock_bar1d
- 技术指标预计算表cn_stock_factors_ta
- 估值数据(PE/PB/PS/市值等)cn_stock_valuation
- 股票池列表(每日)cn_stock_instruments
- 数据平台:https://bigquant.com/data/home
-
Bigtrader:BigQuant平台提供的交易引擎库,支持模拟真实市场环境下的股票买卖操作。它允许用户定义初始化函数(initialize)、调仓逻辑函数(handle_data)等,从而构建和测试自己的投资策略。
-
dai:数据查询接口,用于从BigQuant的大数据仓库中提取所需的历史数据。例如,通过SQL查询语句获取特定条件下的股票信息。
-
from bigmodule import M:BigQuant特有的图形化编程模块,简化了复杂策略的搭建过程。M.* 模块提供了丰富的预置功能,如股票筛选器(cn_stock_basic_selector)、因子输入(input_features_dai)等,方便用户快速构建策略。
-
内置因子函数(如 m_ta_sma、m_lag、c_group_avg 等)、回测引擎接口、手续费模型、撮合规则(开盘价还是收盘价下单)等。
\
二、策略详细流程
下面通过一个“高股息 + 低估值 + 行业相对低估 + 趋势过滤”的等权定期轮动策略来练习如何在BigQuant平台上创建和运行自己的交易策略 。
Step 0:确定投资目标
- 追求绝对收益核心是 “多资产 + 对冲 + 风控纪律”,不管市场涨跌,追求正收益。通过资产配置多元化降低相关性;加入负相关资产或使用使用对冲工具(股指期货/期权)来实现对冲;严格止损止盈,如设置单票最大亏损-8%,整体回撤超-5%减仓;坚持现金为王机制。
- 追求超额收益:你的策略在多大程度上“押注”了某个特定的投资风格或风险来源,比如动量因子抓住了上涨趋势就是赚钱的因子。还可以根据经济周期(复苏/过热/滞胀/衰退)切换板块实现行业轮动;聚焦高Alpha领域:如专精特新、国产替代、AI应用落地;集中持仓:前十大重仓占比>50%,敢于下注高确定性机会。
- 还有策略直接投资低波资产,波动越低的股票,配得越重,避免高估值、高换手、高Beta股票,选择长期持有,减少情绪干扰和交易损耗,且实证表明低波动股票预期收益并不低于高波动股票。 因此,低波动投资策略在市场中越来越受到重视,尤其是在追求稳健收益的投资者中。
为尽可能兼顾两者——现实中可以采取多策略融合的折中方案。
\
Step 1:回测/交易环境初始化(只在回测开始时执行一次)
\
def initialize(context):设置手续费/滑点、设定参数(调仓周期、持仓数)、预先拉取并计算信号数据、放到 context 里缓存等。def initialize(context: bigtrader.IContext):的意思是:“定义一个叫initialize的函数,它接收一个由 BigQuant 提供的策略上下文对象(名为context),用于初始化策略。”其中:bigtrader.IContext是类型提示,帮助你写代码时获得智能补全和减少错误,不是必须的,但强烈推荐保留。
# @param(id="m5", name="initialize")
def m5_initialize_bigquant_run(context):
"""
只执行一次:设置手续费、调仓参数、初始化计数器
"""
from bigtrader.finance.commission import PerOrder
context.set_commission(PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))
# 策略参数
context.rebalance_days = 6
context.extension["bar_index"] = 0
\
def handle_data(context, data):每个交易 bar 都会执行(日频就是每天一次)用来做交易决策与下单:判断是否调仓日、读取当日信号、卖出不在目标池的股票、买入并调整到目标权重等。context:策略运行的“全局状态/容器”。你在 initialize 里存的 context.data、context.stock_num,后面 handle_data 都能用;还包含账户信息、下单接口、日志等。data:当前 bar 的行情数据与时间信息,常用 data.current_dt 得到当前日期。
\
# @param(id="m5", name="handle_data")
def m5_handle_data_bigquant_run(context, data):
"""
每根K线执行:按调仓周期读取当日信号,卖出非目标、买入目标到目标仓位
"""
import pandas as pd
context.extension["bar_index"] += 1
# 控制调仓频率:每 rebalance_days 的第 1 天调仓
if context.extension["bar_index"] % context.rebalance_days != 1:
return
# 取当日信号(m4 已经把历史信号抽成了全量数据)
today = data.current_dt.strftime("%Y-%m-%d")
sig = context.data
today_df = sig[sig["date"] == today]
# 若当日无信号(例如停牌/数据缺失导致),则不操作
if today_df is None or len(today_df) == 0:
return
target_instruments = set(today_df["instrument"].tolist())
holding_instruments = set(context.get_account_positions().keys())
# 先卖出:不在目标里的全部清仓
for inst in holding_instruments - target_instruments:
context.order_target_percent(inst, 0)
# 再买入/调仓:按 position 字段等权
for _, row in today_df.iterrows():
pos = row.get("position", 0)
pos = 0.0 if pd.isnull(pos) else float(pos)
context.order_target_percent(row["instrument"], pos)
- 其他函数:成交回报处理函数,每个成交发生时执行一次;委托回报处理函数,每个委托变化时执行一次;盘后处理函数,每日盘后执行一次
\
Step2:确定股票池/可交易过滤
常见股票池就是设置市场范围,如全 A / 沪深 / 指数成分(如沪深300、中证500/1000)。
基础过滤(强烈建议):非 ST(st_status=0);不停牌(suspended=0);上市天数 > 60/90(list_days);流动性门槛:成交额 amount;流通市值 float_market_cap 等
较复杂的价格异常过滤(可选):接近退市价、连续跌停等
# @module(position="140,40", comment="""股票池过滤:剔除ST/停牌/退市等基础过滤""")
m1 = M.cn_stock_basic_selector.v8(
exchanges=["""上交所""", """深交所"""],
list_sectors=["""主板"""],
indexes=["""中证500""", """上证指数""", """创业板指""", """深证成指""", """上证50""", """科创50""", """沪深300""", """中证1000""", """中证100""", """深证100"""],
st_statuses=["""正常"""],
margin_tradings=["""两融标的""", """非两融标的"""],
drop_suspended=True,
m_cached=False,
m_name="""m1"""
)
\
此处从 cn_stock_prefactors 取数,并做如下风险排除(先筛掉“垃圾股”):
\
st_status = 0 -- 非ST股
AND suspended = 0 -- 未停牌
AND list_sector NOT IN (3, 4) -- 排除科创板(3)、创业板(4)?(注:需确认编码含义)
AND is_risk_warning = 0 -- 无退市风险警示
AND list_days > 270 -- 上市超270天(约9个月),避开次新股
AND float_market_cap > 500000000 -- 流通市值 > 5亿,保证流动性
\
Step3:拉取数据→ 构建因子/特征 → 过滤/排序/打分 → 生成目标权重表
常见因子:
- 价值:PE、PB、股息率等(偏均值回归)
- 成长:营收/净利润同比、环比增长等
- 质量:ROE、毛利率、负债率等
- 动量/反转:过去 k 日/月收益、短期反转等
- 波动/风险:历史波动率、回撤、Beta
- 流动性/资金:换手、量比、主力净流入等
因子构造要点
- 统一量纲:用截面 rank / zscore(如 c_rank、c_zscore)避免市值、价格等量纲差异影响。
- 去极值与标准化(可选):clip、zscore;避免极端值主导排序。
- 行业/市值中性(可选):用行业分组排序(c_group_pct_rank)或做中性化,减少风格暴露。
- 避免未来函数:所有因子只能用当日及以前数据(m_lag/m_avg 等)
因子打分
规则/打分法(更稳、解释力):
每个因子做 rank 或 zscore;加权求和:score = w1rank1 + w2rank2 + ...按 score 排序取前 N
机器学习法(更灵活,需更严格验证):
用 StockRanker/树模型/线性模型等输出预测分数;时间序列走样本外检验,控制过拟合
\
BigQuant平台 中,用户可以通过 DAI(Data Access Interface) 拉取金融数据。其中最常用的两种方式是:DAI SQL(结构化查询语言)、DAI 表达式(Expression / 函数式写法)
言归正传!在这个策略中,主要关注价值指标:
| 指标 | 核心计算公式 | 通俗理解 |
|---|---|---|
| 市盈率 (PE) | 股价 ÷ 每股收益 \n(或:总市值 ÷ 净利润) | 回本年限:假设利润不变,赚回本金要多少年。 |
| 市净率 (PB) | 股价 ÷ 每股净资产 \n(或:总市值 ÷ 净资产) | 家底折扣:买这公司,是打几折买它的家底。 |
| 股息率 | 每股分红 ÷ 股价 × 100% \n(或:分红总额 ÷ 总市值 × 100%) | 租金回报:持有股票一年能拿多少“利息”。 |
| … | … | … |
① 估值与分红筛选(价值+红利因子):
AND pb IS NOT NULL AND pb <= 1.8 -- PB ≤ 1.8,低估值
AND pe_ttm IS NOT NULL AND pe_ttm > 0 -- PE为正(盈利公司)
AND dividend_yield_ratio IS NOT NULL AND dividend_yield_ratio > 0 -- 有分红
PB ≤ 1.8是典型的价值股门槛(银行、能源、公用事业常满足),分红确保是真金白银回馈股东,而非“伪成长”
② 行业相对估值(避免“伪便宜”):
c_group_avg(sw2021_level2, pe_ttm) AS ind_pe_median
QUALIFY
pe_ttm <= ind_pe_median * 1.2
sw2021_level2:申万二级行业,ind_pe_median:该行业内所有股票的 PE 中位数,要求个股 PE ≤ 行业中位数的 1.2 倍,偏向行业内相对便宜
→ 防止在整体高估的行业中误选“相对便宜但绝对贵”的股票(如2021年的白酒)
==直接让QuantAgent来帮你计算指标相关系数👉== ==https://bigquant.com/quantagent/share/482f0506-b672-4ea3-a80f-1fa303812778==
③ 技术面确认(弱动量/趋势过滤):
m_lag(close, 1) > bbi
- m_lag(close, 1):昨日收盘价
- BBI(Bull and Bear Index,多空指标)通常是将多条不同周期的移动平均线取平均,作为综合趋势指标。多空指标 = (MA3 + MA6 + MA12 + MA24) / 4
- 昨日收盘 > BBI 筛选出股价处于短期均线上方的股票,趋势偏强,避免“价值陷阱”——便宜但持续下跌的股票
④ 排序与权重:
ORDER BY date, dividend_yield_ratio DESC
1.0 / $stock_num AS weight
- 按股息率降序排序 → 优先选分红最高的
- 等权重分配:每只股票权重 = 1/29 ≈ 3.45%
\
# 预计算:提前算好整个回测期所有调仓日的股票池
sql = """
SELECT
date,
instrument,
close,
dividend_yield_ratio,
pb,
pe_ttm,
float_market_cap,
sw2021_level2,
m_ta_sma(close, 3) AS ma3,
m_ta_sma(close, 6) AS ma6,
m_ta_sma(close, 12) AS ma12,
m_ta_sma(close, 24) AS ma24,
(m_ta_sma(close, 3) + m_ta_sma(close, 6) + m_ta_sma(close, 12) + m_ta_sma(close, 24)) / 4 AS bbi,
c_group_avg(sw2021_level2, pe_ttm) AS ind_pe_median,
1.0 / $stock_num AS weight
FROM cn_stock_prefactors
WHERE
st_status = 0
AND suspended = 0
AND list_sector NOT IN (3, 4)
AND is_risk_warning = 0
AND list_days > 270
AND float_market_cap > 500000000
AND pb IS NOT NULL
AND pb <= 1.8
AND pe_ttm IS NOT NULL
AND pe_ttm > 0
AND dividend_yield_ratio IS NOT NULL
AND dividend_yield_ratio > 0
QUALIFY
pe_ttm <= ind_pe_median * 1.2
AND m_lag(close, 1) > bbi
ORDER BY date, dividend_yield_ratio DESC
"""
# 批量查询历史数据(避免handle_data重复查询)
df = dai.query(
sql,
filters={"date": [context.add_trading_days(context.start_date, -30), context.end_date]},
params={"stock_num": stock_num}
).df()
\
风格:偏“价值红利”选股(高股息 + 低PB/PE),再叠加“行业相对估值约束”和“站上BBI的趋势确认”。
组合构建:固定数量、等权、定期轮动,降低单票权重风险,交易规则简单透明。
收益来源:红利收益 + 价值修复 + 趋势过滤提升胜率。
\
Step4:回测交易逻辑→ 调仓周期、成交价、风控、仓位
\
回测时必须关注:
- 基准对比:沪深300/中证500/1000等
- 收益风险:年化收益、波动、Sharpe、最大回撤、Calmar
- 稳定性:滚动窗口表现、分年度/分市场阶段表现
- 换手与成本:手续费+滑点+冲击成本,是否吞噬收益
- 归因:行业暴露、市值暴露、因子暴露(是否只是“买小盘/买高beta”)
bigtrader.run 是 BigQuant/BigTrader 的核心运行入口,通常用于启动策略回测、策略优化或实盘运行任务。它负责:
- 装载策略代码与策略配置;
- 初始化数据源、账户与风控模块;
- 调度回测/实盘事件(如按时间序列推进 tick/bar、执行委托与撮合);
- 记录日志、绩效与交易记录,并可导出结果。
performance = bigtrader.run(
market=bigtrader.Market.CN_STOCK,
frequency=bigtrader.Frequency.DAILY,
start_date="2021-01-01",
end_date="2025-11-18",
capital_base=1000000,
benchmark="000300.SH",
initialize=initialize,
handle_data=bigtrader.HandleDataLib.handle_data_weight_based,
order_price_field_buy="open",
order_price_field_sell="open",
)
通过高股息 + 低估值的组合,试图在控制回撤的同时获取稳定正收益,是典型的 “追求超额收益,同时兼顾一定低波动” 的策略。 调仓频率较低(每6天调仓一次 ≈ 每周一次),属于中低频策略,非高频交易,也非长期持有不动(持股周期约1~2周)。
三、执行并分析策略
执行策略并查看结果:
总体评价
-
在2021–2025这轮熊市中表现优异:策略上涨117%,而沪深300下跌13% → 超额收益显著;
-
风格稳健:低Beta、低波动、高股息特征明显;
-
但超额能力不足:Alpha和信息比率偏低,说明主要靠市场反弹+低估值修复赚钱,而非选股能力。
\
基准对比:选哪个指数更公平?
我的策略是全市场选股 + 高股息低估值,但持仓偏向中大盘(流通市值 > 5亿),行业集中在金融、能源、公用事业。因此选沪深300(000300.SH)作为基准部分合适,可以选中证500(000905.SH)更合适,覆盖中盘股,行业更均衡,更能反映“非龙头”股票表现。也可以直接对标“高股息”策略——中证红利指(000922.SH),成分股PB/PE/分红率与该策略高度相似。
\
收益来源需归因
- 策略集中于银行、煤炭、电力等高股息行业,超额收益可能来自行业Beta而非选股Alpha。
- 市值暴露以中盘为主(接近中证500),非小盘或高Beta策略。
- 核心因子暴露为红利+价值,需警惕因子长期有效性变化。
\
四、 检验与参数优化
写完一个量化策略只是整个量化投资流程的第一步。要将策略真正投入实盘、稳定盈利,还需要完成一系列严谨的回测工作。以下是典型的回测后续步骤:
\
- 数据检查与防“未来函数”
- 确认使用的数据在当时是否可得:财报披露日、生效日、复权方式、指数成分调入调出时间
- 检查是否误用未来价格/未来财务值/未来成分
- 缺失值、异常值、幸存者偏差处理(退市、ST、停牌)
\
- 回测质量与稳健性评估
- 不同区间、不同市场状态(牛/熊/震荡)分段回测
- 参数敏感性(小范围扰动是否崩)
- 蒙特卡洛/重排收益序列、不同调仓频率、不同滑点/费用假设下的稳定性
\
- 交易可执行性与微观结构
- 下单规则:开盘/收盘/均价成交假设是否现实
- 流动性约束:成交量比例、冲击成本、最小成交额、涨跌停无法成交
- 换手与容量评估(资金放大后是否还有效)
\
- 风控与组合约束落地
- 仓位上限、单票上限、行业/风格暴露约束、止损/止盈/回撤控制
- 黑名单规则(ST、退市风险、异常波动)
- 极端情景压力测试(跳空、熔断、流动性枯竭)
\
- 归因分析与可解释性
- 业绩归因:收益来自选股还是择时、来自哪些行业/风格因子
- 回撤归因:最大回撤发生原因与持仓集中度问题
- 验证策略“赚钱逻辑”与统计显著性是否一致
\
- 模拟盘验证(Paper Trading)
- AI Studio中直接提交模拟到 “我的策略”
- 用真实成交规则跑一段时间,核对回测与实盘偏差(滑点、成交率、延迟)
- 逐日对账:资金曲线、持仓、成交、费用
\
五、实盘落地
==具体实盘操作👉====BigTrader AI量化交易终端(股票实盘)==
附录
from bigquant import bigtrader, dai
def initialize(context: bigtrader.IContext):
# 设置全局配置(一次性)
context.set_commission(bigtrader.PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))
# 定义策略参数(后续不变)
rebalance_days = 6
stock_num = 29
context.logger.info("开始计算选股数据...")
# 预计算:提前算好整个回测期所有调仓日的股票池
sql = """
SELECT
date,
instrument,
close,
dividend_yield_ratio,
pb,
pe_ttm,
float_market_cap,
sw2021_level2,
m_ta_sma(close, 3) AS ma3,
m_ta_sma(close, 6) AS ma6,
m_ta_sma(close, 12) AS ma12,
m_ta_sma(close, 24) AS ma24,
(m_ta_sma(close, 3) + m_ta_sma(close, 6) + m_ta_sma(close, 12) + m_ta_sma(close, 24)) / 4 AS bbi,
c_group_avg(sw2021_level2, pe_ttm) AS ind_pe_median,
1.0 / $stock_num AS weight
FROM cn_stock_prefactors
WHERE
st_status = 0
AND suspended = 0
AND list_sector NOT IN (3, 4)
AND is_risk_warning = 0
AND list_days > 270
AND float_market_cap > 500000000
AND pb IS NOT NULL
AND pb <= 1.8
AND pe_ttm IS NOT NULL
AND pe_ttm > 0
AND dividend_yield_ratio IS NOT NULL
AND dividend_yield_ratio > 0
QUALIFY
pe_ttm <= ind_pe_median * 1.2
AND m_lag(close, 1) > bbi
ORDER BY date, dividend_yield_ratio DESC
"""
# 批量查询历史数据(避免handle_data重复查询)
df = dai.query(
sql,
filters={"date": [context.add_trading_days(context.start_date, -30), context.end_date]},
params={"stock_num": stock_num}
).df()
context.logger.info(f"数据计算完成,共 {len(df)} 条记录")
# 数据预处理:筛选调仓日数据
df = df.groupby('date').head(stock_num)
df = bigtrader.TradingDaysRebalance(rebalance_days, context=context).select_rebalance_data(df)
# 存入context供后续使用
context.data = df
context.logger.info(f"选股完成,每个调仓日将持有 {stock_num} 只股票")
performance = bigtrader.run(
market=bigtrader.Market.CN_STOCK,
frequency=bigtrader.Frequency.DAILY,
start_date="2021-01-01",
end_date="2025-11-18",
capital_base=1000000,
benchmark="000300.SH",
initialize=initialize,
handle_data=bigtrader.HandleDataLib.handle_data_weight_based,
order_price_field_buy="open",
order_price_field_sell="open",
)
performance.render()
\