量化学习与练习

一:期望抛掷次数

问题:有一枚特殊的硬币,每次抛掷正面朝上的概率为 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_*.pklparam_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)
相关推荐
quantdash_cc4 天前
告别自建 Requests/BS4 网页爬虫:基于 QuantDash 搭建零维保的高性能量化行情流水线
开发语言·爬虫·python·pandas·量化·quantdash
quantdash_cc4 天前
基于 QuantDash 5 分钟 K 线的网格交易策略参数网格搜索寻优实战
android·开发语言·pandas·量化·quantdash
lichao89042714 天前
19种古法图表形态胜率测试
量化
我怎么又饿了呀20 天前
DataWhale—量化金融(task8 最大回撤 和 仓位管理)
python·金融·量化
alicema11111 个月前
BRAIN 研究顾问(Research Consultant)兼职
python·量化
henrylin99991 个月前
Quant Agent 完整系统规格
python·量化
henrylin99992 个月前
美股-后端服务模块
量化·美股
学Linux的语莫2 个月前
大模型量化知识总结
人工智能·模型·量化