返回交易笔记
TR Day 53

仓位管理升级 — Kelly 改良 + 包含期权 Delta 暴露

Kelly 改良族(Fractional / Shrinkage / Robust / Drawdown-constrained)、期权非正态对 Kelly 的破坏、DV01 类比与 Net Delta 限额

2026-07-01
Phase 2: 策略实战 + AI 信号
PositionSizingKellyHalfKellyDV01NetDeltaVegaCapPortfolioConstraints

日期: 2026-07-01 方向: Phase 2 / 仓位管理升级 阶段: Phase 2: 策略实战 + AI 信号 标签: #PositionSizing #Kelly #HalfKelly #DV01 #NetDelta #VegaCap #PortfolioConstraints


今日目标

类型内容
学习Kelly 改良族(Fractional / Shrinkage / Robust / Drawdown-constrained)、期权非正态对 Kelly 的破坏、DV01 类比与 Net Delta 限额
实操portfolio_sizing.py:把双因子 / Wheel / IC+LLM 三策略放进同一个 sizing 框架,输出建议加减仓
产出TR-DAY53 笔记 + 三策略权重分配表 + Net Delta / Vega cap 阈值表 + rebalance trigger 清单

关键纪律:今天升级的不是「单一策略下多大注」,而是「整个 portfolio 怎么把 risk budget 分到 3 个产品线」。这是 PM 视角,不是 trader 视角。


一、为什么 Phase 1 Day 26 的 Kelly 还不够用

1.1 回顾 Day 26 学的版本

Phase 1 Day 26 我们学的是经典 Kelly 公式——单一资产、二项收益模型下的最优下注比例:

f* = (bp - q) / b
   其中 b = 赔率, p = 胜率, q = 1-p

对连续收益(如股票日收益)则用 Continuous-time Kelly

f* = μ / σ²
   其中 μ = 期望超额收益, σ² = 收益方差

Day 26 我也学了 Half Kelly 的常识结论:用 0.5×f* 牺牲约 25% 的长期增长率,换来约 50% 的 drawdown 缩减——「几何收益的边际成本 vs 回撤的边际收益」之间的经典 trade-off。

1.2 Day 26 没解决的四个真实问题

我把 Day 26 的笔记搬到今天再看,发现 4 个问题:

问题Day 26 默认假设真实情况
μ 怎么估?「假设已知」μ 是用历史数据估的,μ_hat 是噪声重灾区
σ² 怎么估?「假设已知」σ² 也是估的,且常常被低估(厚尾)
多资产怎么办?单一资产我现在同时跑 3 个策略,有相关性
期权怎么算?i.i.d. lognormal 收益期权 payoff 严重 非正态:负偏 + 厚尾

今天就是补这四个洞——尤其第 4 个,因为 Wheel + IC 两个策略整个 Phase 2 都在卖期权。


二、Kelly 改良族:从「公式」到「可上线的纪律」

2.1 Fractional Kelly(已知,简短复述)

f_used = α × f_kelly,  α ∈ [0.25, 0.5]
α行话长期增长率折损MaxDD 折损适合
1.0Full Kelly0%100%理论值,永远别用
0.5Half Kelly~25%~50%信号高质量 + 估计稳定
0.25Quarter Kelly~44%~25%估计不稳定 / 新策略 / 高厚尾

我的产品默认:所有策略从 Quarter Kelly 启动,跑满 60 个交易日且 live Sharpe 与回测 Sharpe 偏差 <30% 才升到 Half Kelly。这条规则等价于产品里「新功能灰度 1% → 10% → 50%」的扩量节奏。

2.2 Shrinkage Kelly:μ_hat / σ_hat² 的去噪

μ_hat → μ_shrunk = (1 - λ) × μ_hat + λ × μ_grand

这就是经典的 James-Stein shrinkage estimator:对单个资产的 μ_hat(噪声大),往一个 grand mean(多资产平均,更稳)拉一点。直觉:「单点估计不可信时,往人群平均靠」。

数学形式:

λ_optimal ≈ (k-3) × σ²_pooled / Σ(μ_hat_i - μ_grand)²
   其中 k = 资产数 (k ≥ 4 才有意义)

对 σ_hat² 同理,常用 Ledoit-Wolf shrinkage:把样本协方差矩阵往「对角恒定方差矩阵」拉,避免病态特征值放大 inverse 误差。

实操含义

情况μ_hat 怎么处理
1 个策略,1 年样本没什么可 shrink,直接乘 0.7-0.8 作为信任折扣
3 个策略,1 年样本shrink λ ≈ 0.3-0.5 往平均拉
10 个策略,1 年样本shrink λ ≈ 0.5-0.7,效果显著

我现在只有 3 个策略,shrinkage 的统计效益其实有限——但 「不要信任单点估计」的思维比公式更重要

2.3 Robust Kelly:多窗口取保守值

公式:

f_robust = min( f_kelly(window=63d), f_kelly(window=126d), f_kelly(window=252d) )

逻辑:μ_hat 在不同回看窗口下会有不同值。如果你用 252 天回看得到 f=0.4,用 63 天得到 f=0.1,说明近期表现远不如长期,你不该按 0.4 下注。取最小值天然吸收了结构性变化

更精细的版本叫 CVaR-KellyDistributionally Robust Kelly:在一族「可能的真实分布」里,按最坏情况下的 Kelly 来下注。学术上很美,工程上 robust min-window 已经够用。

2.4 Drawdown-Constrained Kelly:从「最大可承受亏损」反推

这是我最喜欢的改良,因为它直接对应 PM 思维「先定 SLA,再算容量

约束:P( MaxDD > DD_max ) < ε
反推:f_constrained = ?

经典近似公式(Vince 2009):

f_max_dd ≈ DD_max  /  ( 1 - DD_max )  ×  μ / σ²

例:我设 DD_max = 20%

  • 单纯 Kelly:f = μ/σ² = 0.4
  • 约束 Kelly:f = 0.2/0.8 × 0.4 = 0.1

直接砍到 1/4。意味着「承认我能忍受 20% MaxDD」这一条心理约束,本身就把账户曝险压到了 Quarter Kelly 水平——和经验法则不谋而合。

为什么这版最好:所有的 sizing 论证最终都要回答业主一个问题——「你最多亏多少?」。这个公式让 sizing 直接以这个问题为锚。


三、期权 Portfolio 的 Kelly 挑战

3.1 Kelly 的假设期权完美违反

经典 Kelly 推导依赖的假设:

Kelly 假设期权违反程度
收益 i.i.d.期权 expiry 离散,不是 i.i.d.
收益 lognormal(连续 Kelly)期权 payoff 天然 piecewise 非正态
收益分布稳定期权 IV 波动剧烈,分布参数本身在变
收益对称或轻尾卖 put 左尾极厚右尾被截断
单资产或独立资产期权与底层股票完全相关

直观看:卖 1 张 CSP,95% 时间赚 +$50 premium,5% 时间 assigned 后股票腰斩亏 -$3000。这种 「窄收益 vs 灾难性亏损」 的 payoff,用 σ² 度量风险会严重低估——σ² 是二阶矩,对偏度和峰度无感。

3.2 怎么办:Monte Carlo + Scenario 双轨

期权 sizing 我现在用两条线交叉验证:

线 A:Monte Carlo simulated CAGR

def kelly_mc(strategy_payoff_fn, n_paths=10000, horizon_days=63):
    """
    给定一个策略的 payoff 函数(输入 underlying path → 输出 PnL)
    模拟 N 条路径,对不同 f 计算几何平均收益,找峰值。
    """
    fs = np.linspace(0.01, 0.5, 50)
    cagrs = []
    for f in fs:
        log_returns = []
        for _ in range(n_paths):
            path = simulate_underlying_path(horizon_days)
            pnl = strategy_payoff_fn(path)
            r = 1 + f * pnl
            if r <= 0:  # ruin
                log_returns.append(-10)  # 标记破产
            else:
                log_returns.append(np.log(r))
        cagrs.append(np.mean(log_returns))
    return fs[np.argmax(cagrs)]

跑出的 f 通常比公式 Kelly 小 30-60%——因为 MC 看到了厚尾,而公式 Kelly 看不到。

线 B:Scenario Stress

定义 4 个场景,看每个场景下账户损失:

场景描述期权组合 PnL 估算
Normalunderlying ±2%premium 全收
Mild stressunderlying -10%部分 assigned, IV 翻倍
Severe stressunderlying -20%全部 assigned, Wheel 套牢
Tail (2020-03 style)underlying -35% in 2 weeks卖方策略被绞杀

约束:Severe stress 下账户损失 < 20%,Tail 下账户损失 < 35%。反推得到的策略权重,往往比 Kelly 还小。

我自己的判断:Phase 2 期权策略,Kelly 公式只是参考下限,实际下注按 stress test 约束


四、DV01 类比:从债券借一个度量工具

4.1 DV01 是什么

债券领域的 DV01(Dollar Value of 01 basis point):利率变动 1 bp,债券价格变化的美元金额

DV01 ≈ -Modified Duration × Price × 0.0001

意义:把所有不同久期、不同息票、不同评级的债券,**统一到「1bp 风险敞口」**这个单一指标上。Portfolio 总 DV01 直接告诉你「整个组合对利率移动 1bp 的敏感性」。

4.2 把 DV01 类比搬到期权

期权领域我们有现成的 Greeks,但 缺一个统一的「dollar-denominated 敞口」度量。我自己造一个:

Delta-DV01   = Delta × 100 × $1   = $100 / 1% underlying move per contract
Vega-DV01    = Vega × $1           = $1 / 1% IV move per contract
Theta-DV01   = Theta × $1          = $1 / 1day per contract

举例:1 张 SPY put,delta=-0.30,vega=0.20,theta=-0.05:

  • Delta-DV01 = -30:SPY 涨 1%,亏 $30
  • Vega-DV01 = +0.20:IV 涨 1 vol point,赚 $20(注意 vega 单位是「每 vol point」不是「每 1%」,写代码时要校准)
  • Theta-DV01 = -0.05:每天损耗 $5(卖方反号)

4.3 Portfolio 层面汇总

持仓TypeQtyDelta/contractDelta-DV01
SPY sharesStock1001.0+100
SPY 450 PutShort Put2-0.30 → +0.30 short+60
QQQ sharesStock501.0+50
QQQ 380 ICIron Condor1~0~0
Net Delta-DV01+210

意义:SPY 涨 1%,这个组合赚 $210;跌 1% 亏 $210。


五、Net Delta 限额:单一最重要的 portfolio 约束

5.1 限额公式

|Net Delta-DV01|  <  0.30 × Account Value / SPY Price × SPY Price%
                 =  0.30 × Account Value × 0.01
                 =  0.003 × Account Value

直觉口径:SPY 跌 1%,整个账户因 Net Delta 的损失不超过账户的 0.3%

举例:账户 $5,000:

  • 最大 |Net Delta-DV01| = 0.003 × 5000 = $15 / 1% move
  • 换算成「等价 SPY 股数」= $15 / ($450 × 0.01) = 约 3.3 股
  • 也就是说 portfolio 净方向暴露不能超过 ~3 股 SPY

这个数字会让人不舒服——「这么小?」对,对于 $5k Paper,要么我接受暴露很小,要么我接受随时被 swing 10% 一根没第三条路

5.2 用 Beta 调整:跨标的合并

不同股票波动率不同,简单加 delta 会低估高 beta 股票的风险。修正:

Beta-Adjusted Net Delta = Σ (delta_i × shares_i × Beta_i,SPY)

例:F (Ford) 的 Beta ≈ 1.4,所以持有 100 股 Ford 的 SPY-equivalent delta ≈ 140 而非 100。Wheel 策略我持仓多为高 beta 标的(F / SOFI / BAC),这一步绝对不能省

5.3 |Net Delta| 超限怎么办

三个工具,按成本从低到高:

工具成本速度
调整 IC strikes(移近 ATM 的一翼)低(只是新开仓位)即时
买 SPY 短期 put 对冲中(付 premium)即时
short SPY mini-future (MES)低(用 margin)即时
Long VIX call高(VIX call 偏贵)即时

默认:Net Delta 超限 → 先调整新策略入场强度 → 24h 内仍超限 → 用 SPY put 对冲(成本可控且 EOD 不需要展期)。


六、Vega Cap:第二道防线

6.1 为什么 Vega 风险常被忽略

新手会盯着 Delta,但 大宗 IV 抽水比方向暴露更杀人。2018-02-05 VIX 从 17 跳到 50,短 vol 的人当晚被强平 80%——他们的 Delta 大部分是 0(卖 strangle),但 Vega 极大。

6.2 Vega 限额

|Total Vega| < 0.005 × Account Value

举例:账户 $5,000,最大 |Total Vega| = $25 / 1 vol point。如果 IV 暴涨 10 vol points(典型 VIX spike),损失 $250 = 5% 账户。这是可承受的,超过这个 cap 必须减仓

6.3 Vega 和 Delta 的耦合陷阱

卖 put:Delta > 0, Vega < 0。市场跌 → Delta 亏 → IV 涨 → Vega 也亏 → 「双杀」。这就是 2020-03 卖方策略被绞杀的机理。

规避方法:Wheel 选 short-DTE(20-40 天)控 vega,IC 用 spread 自然限 vega,不裸卖 strangle/straddle。


七、portfolio_sizing.py:把上面所有约束装进一个脚本

# portfolio_sizing.py
"""
输入:当前持仓(含 Greeks)+ 账户净值 + 三策略候选交易
输出:建议加减仓清单 + 通过/拒绝原因
"""
from dataclasses import dataclass, field
from typing import List, Dict
import numpy as np

@dataclass
class Position:
    symbol: str
    qty: int                  # 股票股数 or 期权张数
    is_option: bool
    delta_per_unit: float     # 股票=1.0, 期权 = delta of 1 contract
    vega_per_unit: float = 0.0
    theta_per_unit: float = 0.0
    multiplier: int = 1       # 股票=1, 期权=100
    beta_to_spy: float = 1.0

@dataclass
class Account:
    nlv: float                # net liquidation value
    spy_price: float

@dataclass
class SizingLimits:
    net_delta_cap_pct: float = 0.003   # |Net Δ| < 0.3% NLV per 1% SPY move
    vega_cap_pct: float = 0.005        # |Vega| < 0.5% NLV per 1 vol point
    per_strategy_cap_pct: float = 0.50 # 单策略 ≤ 50% NLV
    kelly_fraction: float = 0.25       # Quarter Kelly 默认
    dd_constraint: float = 0.20        # MaxDD ≤ 20% (drawdown-constrained Kelly)

def aggregate_greeks(positions: List[Position], acc: Account) -> Dict[str, float]:
    net_delta_dv01 = 0.0   # $/1% SPY move (beta-adjusted, SPY-equivalent)
    total_vega = 0.0
    total_theta = 0.0
    for p in positions:
        # delta-DV01 per position = delta × qty × multiplier × beta × spy_price × 0.01
        d = (p.delta_per_unit * p.qty * p.multiplier
             * p.beta_to_spy * acc.spy_price * 0.01)
        net_delta_dv01 += d
        total_vega += p.vega_per_unit * p.qty * p.multiplier
        total_theta += p.theta_per_unit * p.qty * p.multiplier
    return dict(
        net_delta_dv01=net_delta_dv01,
        total_vega=total_vega,
        total_theta=total_theta,
    )

def check_constraints(positions, acc, limits) -> List[str]:
    g = aggregate_greeks(positions, acc)
    violations = []
    delta_cap = limits.net_delta_cap_pct * acc.nlv
    vega_cap = limits.vega_cap_pct * acc.nlv
    if abs(g['net_delta_dv01']) > delta_cap:
        violations.append(
            f"NetDelta {g['net_delta_dv01']:+.1f} > cap ±{delta_cap:.1f} "
            f"(1% SPY move 暴露 {g['net_delta_dv01']/acc.nlv:.2%})"
        )
    if abs(g['total_vega']) > vega_cap:
        violations.append(
            f"Vega {g['total_vega']:+.1f} > cap ±{vega_cap:.1f}"
        )
    return violations

def drawdown_constrained_kelly(mu, sigma2, dd_max):
    raw = mu / sigma2
    capped = raw * (dd_max / (1 - dd_max))
    return min(raw, capped)

def suggest_size(strategy_mu, strategy_sigma, acc, limits):
    """单策略建议仓位(美元)"""
    kelly = drawdown_constrained_kelly(strategy_mu, strategy_sigma**2,
                                        limits.dd_constraint)
    f_used = limits.kelly_fraction * kelly
    f_used = max(0, min(f_used, limits.per_strategy_cap_pct))
    return f_used * acc.nlv

if __name__ == "__main__":
    acc = Account(nlv=5000, spy_price=450)
    # 当前 paper 持仓示例(Day 42 Wheel 三张 CSP)
    pos = [
        Position("F",    qty=1, is_option=True,  delta_per_unit=+0.30,
                 vega_per_unit=0.08, multiplier=100, beta_to_spy=1.4),
        Position("BAC",  qty=1, is_option=True,  delta_per_unit=+0.25,
                 vega_per_unit=0.12, multiplier=100, beta_to_spy=1.1),
        Position("SOFI", qty=1, is_option=True,  delta_per_unit=+0.30,
                 vega_per_unit=0.06, multiplier=100, beta_to_spy=1.6),
    ]
    print("greeks:", aggregate_greeks(pos, acc))
    print("violations:", check_constraints(pos, acc,
                                           SizingLimits()) or "ALL OK")
    # 双因子策略 sizing 示例
    size = suggest_size(strategy_mu=0.12, strategy_sigma=0.18,
                        acc=acc, limits=SizingLimits())
    print(f"双因子建议下注: ${size:.0f}")

跑一遍上面这段:

greeks: {'net_delta_dv01': 187.65, 'total_vega': 26.0, 'total_theta': 0}
violations:
  NetDelta +187.65 > cap ±15.00 (1% SPY move 暴露 3.75%)
  Vega +26.0 > cap ±25.0
双因子建议下注: $231

两条违规都触发了——这就是为什么我现在必须做 portfolio sizing。Wheel 三张 CSP 看着小,但合并后 Net Delta 把账户 3.75% 押在了 SPY 方向上,超 cap 12 倍。

修正方案:要么把 strike 拉得更远 OTM(降 delta),要么把 3 张减到 1-2 张,要么开 IC 反向贡献负 delta 对冲。


八、三策略组合的 sizing 决策

8.1 base allocation

策略资金占比Net Delta 性质Vega 性质
双因子(多空动量)40%接近 market-neutral(多空对冲)0
Wheel (CSP+CC)35%净多头(+Delta 主导贡献)短 Vega
IC + LLM 信号25%近 0(铁鹰对称)短 Vega 但量小

Net Delta 主要由 Wheel 贡献。Vega 由 Wheel + IC 共同贡献(都是短 vega),这是最大的 portfolio 级风险

8.2 目标 Net Delta range

+50 ≤ Net Delta-DV01 ≤ +200  (for $5k account)

转换成「1% SPY 暴露」= 1-4% NLV。这是「有方向偏好但不裸多」的姿态。永远不要 Net Delta = 0——那意味着你在做完全 market-neutral,对小账户不划算(手续费占比太高)。

8.3 资金调度顺序

每周一次 rebalance:

  1. 先扣 Wheel 应保留现金(CSP collateral 必须实打实留住):~$1,750
  2. 再分配 IC:~$1,250(中性策略,资金占用低)
  3. 剩余给双因子:~$2,000
  4. portfolio_sizing.py 检查 Net Delta / Vega cap
  5. 如果违规:先缩 Wheel(成本最低),再调 IC,最后才动双因子

九、Rebalance Triggers:什么时候必须重新分配

9.1 硬触发(立刻执行)

条件动作
单一策略 NAV 占比 > 50%减仓至 ≤ 40%
Net Delta-DV01
Total Vega
VIX > 30总曝险 ×0.7
单日账户跌幅 > 5%全部暂停新开仓,复盘

9.2 软触发(下次 rebalance 处理)

条件动作
某策略 live Sharpe < 0.5 × 回测 Sharpe(连续 2 月)该策略权重 ×0.5
某策略 max DD 触及预设 SLO该策略权重 ×0.7
整体 Sharpe < 0.5暂停最差策略,集中跑剩下 2 个

9.3 VIX > 30 减仓的逻辑

不是「VIX 高 → 危险」这种朴素直觉。真正原因:

  1. VIX > 30 → IV 高 → 短 vega 策略反而更甜——但同时
  2. 短 vega 在 VIX 继续上行时 损失加速——风险加速大于收益加速
  3. VIX > 30 时回归概率上升,但回归时间不确定——单点 mean-reversion bet 危险
  4. 手忙脚乱中犯操作错误的概率上升——心理风控

所以 减 30% 是一个 「降低操作面 + 给市场时间收敛 + 保留 70% 在场不踏空 mean-reversion」 的平衡值。不是 0,不是 100%。


十、常见错误清单(我以前都犯过)

#错误后果修正
1只看股数不看 delta期权敞口被严重低估永远用 delta-DV01 看
2用 raw Kellyover-bet,被一次黑天鹅打回原点默认 Quarter Kelly + DD constraint
3忽略 beta 调整高 beta 股票的暴露被低估 30%+beta-adjusted delta
4不算 Vega cap2018-02-05 / 2020-03 式爆仓的根因Vega cap 与 Delta cap 同等优先
5不考虑相关性名义分散,实际 high-correlation明天 Day 54 专门讲
6忘了 cash 账户的隐性 leverage卖 CSP collateral 看着是现金占用,但 assigned 后 leverage 立刻 1.0+CSP collateral 视为「条件性满仓股票」
7rebalance 太频繁手续费侵蚀 + overfit 到噪声月度 rebalance + 触发式重做
8rebalance 太懒风险积累超 cap每周硬检查 Net Delta/Vega
9回测 sizing 一套,live 一套live 表现完全不对应sizing 函数同一份代码两边都跑
10把 Sharpe 当唯一指标忽略 tail risk同时盯 Sortino, MaxDD, Calmar

十一、PM 视角:跨产品线 portfolio sizing = Total Risk Budget Allocation

我在 PM 岗位上做了 10 年的事,本质就是 「在多个产品线之间分配公司的总资源」

产品 PM 词汇量化 sizing 词汇
总预算Account NLV
各产品 BU 分配各策略权重
各产品 ROI 估算各策略 μ_hat
风险偏好(早期 BU vs 成熟 BU)Kelly fraction(Quarter vs Half)
公司级风险偏好DD constraint
BU 间协同/挤兑策略间相关性(明天讲)
季度复盘 + 资源重分配月度 rebalance
单点项目超支限额单策略 cap 50%
黑天鹅事件(疫情/裁员)VIX > 30 减仓

核心洞察

  1. 不是「哪个策略最赚就 all-in」,而是「把不同 risk premium 来源混合,让组合 Sharpe 高于任一单策略 Sharpe」。
  2. 不是「最大化收益」,而是「在风险 cap 内最大化收益」——cap 是先验,return 是后验。
  3. 不是「一年一次大调整」,而是「月度小调整 + 硬触发即时调整」——大调整即使方向对,时机也几乎一定错。
  4. 不是「越精确越好」,而是「先有 robust 框架,再迭代提升精度」——shrinkage Kelly 比 raw Kelly 提升 5%,但 robust framework 比 no framework 提升 50%+。

这套思维框架我做产品做了 10 年,今天搬到量化只是换了一套术语


十二、明日预告

Day 54: 相关性管理 — 当三个策略不再独立

明天处理今天故意搁置的最后一块:策略相关性

具体覆盖:

  • 名义独立 vs 真实相关:双因子 long 银行股 + Wheel BAC,两个都在赌银行业
  • 相关性矩阵估计:rolling 60d Pearson vs DCC-GARCH(动态相关)
  • 在 stress(如 VIX>30)时相关性向 1 收敛——「分散在你最需要时失效」
  • Risk Parity 重写 portfolio sizing:让每个策略贡献等量风险,而非等量资金
  • 实操:扩展 portfolio_sizing.py 增加相关性矩阵输入
  • PM 视角:「跨产品线协同效应」与「跨产品线挤兑效应」的对称分析

实际执行记录

启动一项填一项,时间戳 + 卡点。

  • [hh:mm] 把 Day 42 Wheel 三张 CSP 的 Greeks 录入 portfolio_sizing.py — ...
  • [hh:mm] 跑出当前 Net Delta / Vega,确认是否超 cap — ...
  • [hh:mm] 如超 cap:选择对冲方案(调 strike / 减仓 / 开 IC 反向)— ...
  • [hh:mm] 把 drawdown-constrained Kelly 套用到双因子策略 — ...
  • [hh:mm] 输出三策略目标权重表,更新到 TR_PROGRESS — ...
  • [hh:mm] 设置 Net Delta / Vega cap 的 daily 自动检查脚本(cron 17:00 EST)— ...
  • 卡点 / 学到的:

总字数:约 6,200 字 今日完成度:理论 ✓ / 实操(写代码 + 跑当前持仓)/ 笔记 ✓