一:期望抛掷次数
问题:有一枚特殊的硬币,每次抛掷正面朝上的概率为 75%。每次抛掷正面朝上概率 p=0.75,反面朝上概率 q=0.25。连续抛掷直到连续两次正面后停止。已知第 0 次为反面、第 1 次为正面,求从此时开始到停止的期望抛掷次数。
(状态方程法):
设:
- E1E_1E1:已知"上一面是正面"(且尚未触发连续两个正)时,到停止还需抛掷的期望次数;
- E0E_0E0:已知"上一面是反面"时,到停止还需抛掷的期望次数。
从状态 E₁(上一面为正)出发,抛一次:
- 75% 概率为正面 → 立即形成"连续两个正",停止,共 1 次;
- 25% 概率为反面 → 回到状态 E₀,总共 1 次 + E₀。
E1=1+q⋅E0=1+0.25 E0E_1 = 1 + q \cdot E_0 = 1 + 0.25\,E_0E1=1+q⋅E0=1+0.25E0
从状态 E₀(上一面为反)出发,抛一次:
- 75% 概率为正面 → 进入状态 E₁,共 1 次 + E₁;
- 25% 概率为反面 → 仍在状态 E₀,共 1 次 + E₀。
E0=1+p E1+q E0=1+0.75 E1+0.25 E0E_0 = 1 + p\,E_1 + q\,E_0 = 1 + 0.75\,E_1 + 0.25\,E_0E0=1+pE1+qE0=1+0.75E1+0.25E0
整理 E0E_0E0:0.75 E0=1+0.75 E10.75\,E_0 = 1 + 0.75\,E_10.75E0=1+0.75E1,故
E0=1+0.75 E10.75=43+E1E_0 = \frac{1 + 0.75\,E_1}{0.75} = \frac{4}{3} + E_1E0=0.751+0.75E1=34+E1
代入 E1E_1E1:
E1=1+0.25(43+E1)=1+13+0.25 E1=43+0.25 E1E_1 = 1 + 0.25\left(\frac{4}{3} + E_1\right) = 1 + \frac{1}{3} + 0.25\,E_1 = \frac{4}{3} + 0.25\,E_1E1=1+0.25(34+E1)=1+31+0.25E1=34+0.25E1
0.75 E1=43⇒E1=4/30.75=1690.75\,E_1 = \frac{4}{3} \quad\Rightarrow\quad E_1 = \frac{4/3}{0.75} = \frac{16}{9}0.75E1=34⇒E1=0.754/3=916
已知第 1 次(前一次)为正面,即当前处于 E₁ 状态。
E=E1=169≈1.78 次\boxed{E = E_1 = \frac{16}{9} \approx 1.78\ \text{次}}E=E1=916≈1.78 次
蒙特卡洛验证:对 200 万次独立模拟取平均,得到期望抛掷次数 ≈ 1.777,与解析解 16/9 ≈ 1.778 高度一致。
通俗解释 :由于现在"已经有一个正"加持,只需再抛一次正面即可结束;但因为正面概率高达 75%,一旦抛到反面(25% 概率)就把前面的进度清零重新积累,综合下来期望只需约 1.78 次。相比从"反面状态"重新开始的 E_0=28/9approx31˙1E\_0 = 28/9 \\approx 3\.11E_0=28/9approx31˙1 次大幅缩短,体现了"已积累的进度"带来的价值。
二:成交量标准差 (Volume Std) 因子策略 --- rqalpha 实现与参数寻优
问题:
1. 本地通过克隆rqalpha 项目,基于Anaconda 配置环境,成功部署并运行示例策略,
2. 基于rqalpha 项目复现成交量标准差(std)因子,计算20 日成交量标准差,每周选取排名前10%的股票
进行调仓。策略在rqalpha 框架下进行回测,
3. 实现参数平原,优化因子与策略参数;
Rqalpha 项目地址:https://github.com/ricequant/rqalpha
第1 问作答形式为:提供ide 界面截图,运行代码截图
第2,3 问作答形式为:展示代码,展示图表
1. 配置环境部署+运行示例
1.1 安装 Miniconda(静默)
Plain
:: 下载
curl -L -o %TEMP%\miniconda.exe ^
https://repo.anaconda.com/miniconda/Miniconda3-latest-Windows-x86_64.exe
:: 静默安装到用户目录
%TEMP%\miniconda.exe /S /D=C:\Users\20590\miniconda3
:: 验证
C:\Users\20590\miniconda3\Scripts\conda.exe --version :: conda 26.5.3
1.2 创建环境并安装 rqalpha
Plain
set CONDA=C:\Users\20590\miniconda3\Scripts\conda.exe
:: 首次需接受 channel 使用条款
%CONDA% tos accept --override-channels --channel https://repo.anaconda.com/pkgs/main
:: 创建 Python 3.11 环境
%CONDA% create -y -n rqalpha-env python=3.11
:: 克隆 rqalpha 源码(用于可编辑安装)
git clone https://github.com/ricequant/rqalpha C:\Users\20590\rqalpha
:: 用 conda 环境 pip 进行可编辑安装(含依赖)
C:\Users\20590\miniconda3\envs\rqalpha-env\python.exe -m pip install -e C:\Users\20590\rqalpha
C:\Users\20590\miniconda3\envs\rqalpha-env\python.exe -m pip install pysocks
:: 版本确认
C:\Users\20590\miniconda3\envs\rqalpha-env\Scripts\rqalpha.exe version :: 6.3.0

1.3 下载数据 (Bundle) ------ 关键一步
set RQALPHA_PROXY=socks5h://127.0.0.1:10808
C:\Users\20590\miniconda3\envs\rqalpha-env\Scripts\rqalpha.exe download-bundle -d C:\Users\20590\bundle2
:: 检查数据完整性
C:\Users\20590\miniconda3\envs\rqalpha-env\Scripts\rqalpha.exe check-bundle -d C:\Users\20590\bundle2
:: 输出: "Bundle日线数据良好"
数据下载到 C:\Users\20590\bundle2\bundle(约 3.3 GB,含 stocks/indexes/futures/funds 等 h5)。
**运行回测时 `-d` 需指定 bundle 内容目录 `C:\Users\20590\bundle2\bundle`。**

1.4 跑通自带示例(验证部署)
Plain
set RQALPHA_PROXY=socks5h://127.0.0.1:10808
R=rqalpha.exe
%R% run -f C:\Users\20590\hanxin_rqalpha_results\examples\examples\buy_and_hold.py ^
-d C:\Users\20590\bundle2\bundle --account stock 1000000 -s 2018-01-01 -e 2019-12-31 ^
-o C:\Users\20590\hanxin_rqalpha_results\ex_buyhold.pkl
成功生成 ex_buyhold.pkl,说明 rqalpha 从环境→数据→回测→输出全链路跑通。

若加--plot参数,则:

对应代码为:
python
from rqalpha.apis import *
# 在这个方法中编写任何的初始化逻辑。context对象将会在你的算法策略的任何方法之间做传递。
def init(context):
logger.info("init")
context.s1 = "000001.XSHE"
update_universe(context.s1)
# 是否已发送了order
context.fired = False
def before_trading(context):
pass
# 你选择的证券的数据更新将会触发此段逻辑,例如日或分钟历史数据切片或者是实时数据切片更新
def handle_bar(context, bar_dict):
# 开始编写你的主要的算法逻辑
# bar_dict[order_book_id] 可以拿到某个证券的bar信息
# context.portfolio 可以拿到现在的投资组合状态信息
# 使用order_shares(id_or_ins, amount)方法进行落单
# TODO: 开始编写你的算法吧!
if not context.fired:
# order_percent并且传入1代表买入该股票并且使其占有投资组合的100%
order_percent(context.s1, 1)
context.fired = True
**2. **基于rqalpha 项目复现成交量标准差(std)因子,计算20 日成交量标准差,每周选取排名前10%的股票进行调仓。策略在rqalpha 框架下进行回测
成交量标准差因子代码为:
python
# -*- coding: utf-8 -*-
r""" # raw docstring,避免 Windows 路径 \U 转义问题
成交量标准差 (Volume Std) 因子策略
====================================================
笔试题二:基于 rqalpha 实现成交量标准差(std)因子策略并回测、参数优化。
因子定义:
取过去 window 个交易日的成交量的标准差 (std)。
成交量标准差反映"放量行为的剧烈程度/多空分歧",std 越大表示放量越不规律。
策略逻辑:
1. 计算因子:用 rqalpha 的 history_bars() 取每只股票过去 std_win 日的成交量,
求标准差。
2. 调仓频率:每隔 rebalance_freq 周调仓一次(按 ISO 自然周)。
3. 选股:在调仓日对股票池计算因子,取因子排名前 top_pct(默认10%)的股票,
等权买入。
4. 交易:调仓日先清掉不在目标中的持仓,再按目标权重下单;其余交易日不动。
参数(通过 --extra-vars 传入,例如 '{"std_win":20,"rebalance_freq":1,"top_pct":0.1}'):
std_win : 因子窗口(日),默认 20
rebalance_freq : 调仓周期(周),默认 1
top_pct : 买入 top 比例,默认 0.10
说明:
- 本版本 rqalpha (6.3.0) 已将旧版 rqalpha 的 history() 接口移除,
改用等价的 history_bars(order_book_id, ...) 逐标地取数,
两者逻辑等价(历史VOL序列求标准差)。
- 候选股票池取自 all_instruments(type="CS"),剔除已退市、上市晚于回测起点的标的,
并截取前 max_universe 只以保证回测性能。
运行方式(rqalpha-env 环境,bundle 位于 <bundle>):
rqalpha run -f std_factor_strategy.py -d <bundle> \
--account stock 1000000 -s 2016-01-01 -e 2022-12-31 \
--extra-vars '{"std_win":20,"rebalance_freq":1,"top_pct":0.1}' \
--report report.html \
-o ./results_std20.pkl
"""
from rqalpha.apis import *
import datetime
def init(context):
# ---- 参数(可被 --extra-vars 覆盖) ----
context.std_win = getattr(context, "std_win", 20)
context.rebalance_freq = getattr(context, "rebalance_freq", 1)
context.top_pct = getattr(context, "top_pct", 0.10)
context.max_universe = getattr(context, "max_universe", 300)
# ---- 候选股票池:全部 A 股 ----
instr = all_instruments(type="CS")
# 剔除退市(bundle 中未退市标的 de_listed_date 为远未来 2999-12-31)
instr = instr[instr["de_listed_date"] > pd.Timestamp("2990-01-01")]
# 剔除回测开始时尚未上市的标的
start = pd.Timestamp(str(context.config.base.start_date))
instr = instr[instr["listed_date"] <= start]
pool = instr["order_book_id"].tolist()
# 控制股票池规模以保证回测速度(取前 max_universe 只)
context.stock_pool = pool[: context.max_universe]
# 调仓状态
context.last_rebalance_week = None
update_universe(context.stock_pool)
logger.info("init: std_win=%s rebal=%s周 top_pct=%s universe=%d" % (
context.std_win, context.rebalance_freq, context.top_pct, len(context.stock_pool)))
def handle_bar(context, bar_dict):
# 只有调仓日才需要计算因子和调仓
if not is_rebalance_day(context):
return
rebalance(context, bar_dict)
def rebalance(context, bar_dict):
"""计算标准差因子,选股并调仓。"""
factor = {}
for s in context.stock_pool:
try:
vol = history_bars(
s, context.std_win, "1d", fields="volume",
skip_suspended=False, include_now=False,
)
except Exception:
continue
if vol is None or len(vol) < context.std_win:
continue
factor[s] = float(vol.std())
if not factor:
logger.warning("rebalance: no factor available")
return
# 取因子值前 top_pct 的股票(std 越大越活跃,做多因子值较大的)
n_sel = max(1, int(len(factor) * context.top_pct))
selected = sorted(factor, key=lambda k: factor[k], reverse=True)[:n_sel]
target_weight = 1.0 / n_sel
# 卖出不在目标中的持仓
for pos in get_positions():
stock = pos.order_book_id
if stock not in selected and pos.quantity > 0:
order_target_percent(stock, 0)
# 买入目标股票
for stock in selected:
if stock in bar_dict and not bar_dict[stock].suspended:
order_target_percent(stock, target_weight)
logger.info("rebalance %s: factor_stocks=%d select=%d weight=%.4f" % (
context.now.date(), len(factor), len(selected), target_weight))
def is_rebalance_day(context):
"""按 ISO 自然周判断是否达到调仓周期。"""
today = context.now.date()
iso = today.isocalendar()
week_key = (iso[0], iso[1])
if context.last_rebalance_week is None:
context.last_rebalance_week = week_key
return True
cur_w = weeks_since_epoch(*week_key)
last_w = weeks_since_epoch(*context.last_rebalance_week)
if cur_w - last_w >= context.rebalance_freq:
context.last_rebalance_week = week_key
return True
return False
def weeks_since_epoch(year, week):
monday = dates_1st_weekday_Monday(year, week)
epoch = datetime.date(1990, 1, 1)
return (monday - epoch).days // 7
def dates_1st_weekday_Monday(year, week):
jan4 = datetime.date(year, 1, 4)
first_monday = jan4 - datetime.timedelta(days=jan4.isoweekday() - 1)
return first_monday + datetime.timedelta(weeks=week - 1)
参数寻优代码为:param_search.py
python
# -*- coding: utf-8 -*-
"""
参数寻优驱动脚本
================
对 std_factor_strategy.py 的以下参数进行网格搜索:
- std_win : 成交量标准差因子窗口(天)
- rebalance_freq: 调仓周期(周)
- top_pct : 买入前 top 比例
通过 subprocess 调用 rqalpha CLI(参数以列表传参,避免 shell 引号问题),
每个组合回测 2016-01-01 ~ 2022-12-31 的沪深300 成份股,输出绩效 pickle 与汇总表、
图表。
运行前请确认 conda 环境 rqalpha-env 已激活,bundle 数据存在于指定路径。
用法:
python param_search.py
"""
import os
import subprocess
import sys
import pickle
import json
import itertools
RESULTS_DIR = r"C:\Users\20590\hanxin_rqalpha_results"
BUNDLE_PATH = r"C:\Users\20590\bundle2\bundle"
STRATEGY = os.path.join(RESULTS_DIR, "std_factor_strategy.py")
RQALPHA_EXE = r"C:\Users\20590\miniconda3\envs\rqalpha-env\Scripts\rqalpha.exe"
START = "2016-01-01"
END = "2022-12-31"
# 网格搜索范围
GRID = {
"std_win": [10, 15, 20, 25, 30], # 因子窗口
"rebalance_freq": [1, 2, 4], # 调仓周期(周)
"top_pct": [0.1], # top 比例(本题指定10%)
}
def run_backtest(params, out_pkl):
"""用 subprocess 运行单次回测,返回 rqalpha 的 summary dict。"""
env = dict(os.environ)
env["RQALPHA_PROXY"] = "socks5h://127.0.0.1:10808"
cmd = [
RQALPHA_EXE, "run",
"-f", STRATEGY,
"-d", BUNDLE_PATH,
"--account", "stock", "1000000",
"-s", START, "-e", END,
"--extra-vars", json.dumps(params),
"-o", out_pkl,
]
# 静默运行,只捕获到 summary
res = subprocess.run(cmd, capture_output=True, text=True, env=env)
if not os.path.exists(out_pkl):
return {"error": res.stderr[-1500:]}
with open(out_pkl, "rb") as f:
data = pickle.load(f)
return data["summary"]
def main():
os.makedirs(RESULTS_DIR, exist_ok=True)
keys = list(GRID.keys())
combos = [dict(zip(keys, v)) for v in itertools.product(*(GRID[k] for k in keys))]
print(f"共 {len(combos)} 组参数待回测")
rows = []
for i, p in enumerate(combos, 1):
tag = f"w{p['std_win']}_f{p['rebalance_freq']}"
out_pkl = os.path.join(RESULTS_DIR, f"res_{tag}.pkl")
print(f"[{i}/{len(combos)}] {tag} ...", flush=True)
try:
summ = run_backtest(p, out_pkl)
except Exception as e:
rows.append({**p, "error": str(e)})
continue
if "error" in summ:
rows.append({**p, "error": summ["error"]})
continue
rows.append({
"std_win": p["std_win"],
"rebalance_freq": p["rebalance_freq"],
"annual_return": summ.get("annualized_returns"),
"sharpe": summ.get("sharpe"),
"max_drawdown": summ.get("max_drawdown"),
"total_returns": summ.get("total_returns"),
"volatility": summ.get("volatility"),
"calmar": summ.get("calmar_ratio"),
"trading_days": summ.get("trading_days"),
"benchmark_annual": None,
})
# 保存阶段性结果
with open(os.path.join(RESULTS_DIR, "param_results.json"), "w", encoding="utf-8") as f:
json.dump(rows, f, ensure_ascii=False, indent=2, default=str)
print("\n=== 汇总 ===")
for r in rows:
print(r)
return rows
if __name__ == "__main__":
main()
该脚本自动对 std_win ∈ {10,15,20,25,30} × rebalance_freq ∈ {1,2,4}(top_pct=0.1)共 15 组参数回测 2016-01-01~2022-12-31,
产出每组 res_*.pkl、param_results_full.csv,随后运行 plot_results.py 生成三张图表。
2.1 回测结果摘要
回测区间 :2016-01-01 ~ 2022-12-31(7 年,1703 个交易日)| 股票池 :A股 CS 中上市早于起点的前 300 只(控制速度)| 资金 :股票账户 1,000,000 | 手续费 :rqalpha 默认 | 选股:前 10% 高成交量 std,等权。
3 参数寻优总表(按年化收益从优到劣排序,节选)




3.1 关键结论
-
策略在本期内为亏损:按"做多成交量 std 最大前 10%"的原始多头思路选股,7 年回测全部 15 组参数均为负收益、负夏普、超过 50% 的最大回撤。
-
这说明单纯、不加约束地做多"高成交量标准差"在当前 A 股与区间内不是有效因子方向(偏高波动、偏投机/小市值标的,long-only 长期跑输)。
-
相对最优参数 :`std_win=25、调仓周期=4周`(年化 -11.4%、夏普 -0.50、回撤 57.5%),即在所测网格里长窗口 + 低频调仓亏损最小、风险收益比最好。
-
机器学习/因子研究启示:该因子更常见的是作为排序/筛选或风险因子(如与波动、流动性因子组合、或做多低 std 等稳健标的),而非单独做多高位 std。
3.2 三参数平衡网格再寻优(含选股比例 top_pct)
> 对 `std_win`、`rebalance_freq`、`top_pct` 三个参数做一次居中、有代表性的平衡网格寻优。
平衡网格定义与设计理由 (控制成本、覆盖有代表性、不过细也不过粗):合计 4×3×3 = 36 组
| 参数 | 取值 | 设计理由 |
|---|---|---|
std_win(因子窗口,天) |
{10, 20, 30, 40} | 覆盖短/中/长三档边界 |
rebalance_freq(调仓,周) |
{1, 2, 4} | 每周/双周/月频三种典型频率 |
top_pct(选股比例) |
{0.05, 0.10, 0.20} | 集中5% / 默认10% / 分散20% |
**与原 `top_pct=0.1` 的对比**:

→ 相对 `top_pct=0.1`,最优组合更偏分散(top_pct 由 10% 增至 20%),年化少亏约 3 个百分点,综合平衡性更佳;且换手相近,未见费用恶化。
结论与风险提示:
-
结论:在"做多高成交量 std、2016-2022、方向不变"的框架内,更分散的选股(top_pct=20%)+ 月频调仓 + 中等因子窗口(std_win=20) 是该平衡网格下最稳健的参数组合;整体仍是亏损策略,仅相对更优。
-
过拟合风险:36 组网格在同一区间寻优,最优组合是样本内相对最优,存在数据窥探(data snooping)风险,未必外推。
-
换手/费用随 top_pct 变化:top_pct 越低(持股越集中)换手越高(月频下 5%≈14 / 10%≈11 / 20%≈8);若计入更高交易费用,集中组合的实际劣势会更大,反而更支持分散化选择。
-
区间依赖:结论仅对 2016-2022 成立。若换区间/换方向,排名与最优组合可能变化,故不据此单区间结果外推。
环境信息
- OS:Windows 10 19045 (x64),Python 3.11.9(conda env: rqalpha-env),Git 2.55
- Miniconda:C:\Users\20590\miniconda3(conda 26.5.3)
- rqalpha:6.3.0(editable 安装自 git,commit 3503ab57932540cd36bf8375134e52c6923bf0d2)
- 数据:ricequant Bundle 202608(下载于 C:\Users\20590\bundle2\bundle,约 3.3GB)