σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft Academy学习路线
Ctrl K
AO
+1.2k 位学习者
登录加入 ↗
学习路线
AO
+1.2k 位学习者
登录
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Python 学习路线›模块 10 · 高性能 Python 工程›主题 10.1·~45分钟高级

整洁代码

路线进度模块 10/11 · 主题 1/13
10.1 整洁代码
—实验得分— / 100总 XP—连续天数—

代码被阅读的次数远多于被编写的次数

交易代码库会存活多年,并被没写过它的人修改,常常在压力之下,也常常是半年后的你自己。整洁代码是能被快速读懂、能被安全修改的代码。它不是风格规则,而是让错误的修改变得显眼。一个命名糟糕、一次做三件事、把限额藏在公式里的函数,正是昂贵 bug 的藏身之处,比如没人记得被写死的风险限额。

# The same calculation, written two ways
def f(d, e, m, w):
    r = []
    if sum([abs(x) for x in d.values()]) / e > m:
        r.append("LEVERAGE")
    for k in sorted(d):
        if abs(d[k]) / sum([abs(x) for x in d.values()]) > w:
            r.append("CONCENTRATION:" + k)
    return r

book = {"AAPL": 80_000.0, "MSFT": -10_000.0, "NVDA": 10_000.0}
print(f(book, 50_000.0, 1.5, 0.5))                  # works, but what is d? e? m? w?

名称、小函数、没有隐藏的数字

三个习惯完成了大部分工作。名称要名副其实:用 gross_exposure 而不是 f,用 max_leverage 而不是 m。函数要小且单一职责:每个函数做一件几个词就能说清的事,因此可以单独测试和复用,更大的函数通过调用它们构建。不要埋藏数字:写在公式里的 1.5 这样的限额是谁也无法安全找到或修改的魔法数字;把它作为参数传入(或命名为常量)。把总敞口放在自己的函数里只算一次,也消除了重复的表达式。

# Small functions with names that say what they return; the limits come in as arguments
def gross_exposure(positions):
    return sum(abs(value) for value in positions.values())

def leverage(positions, equity):
    return gross_exposure(positions) / equity

def risk_flags(positions, equity, max_leverage, max_weight):
    flags = []
    if leverage(positions, equity) > max_leverage:
        flags.append("LEVERAGE")
    gross = gross_exposure(positions)
    for symbol in sorted(positions):
        if abs(positions[symbol]) / gross > max_weight:
            flags.append(f"CONCENTRATION:{symbol}")
    return flags

book = {"AAPL": 80_000.0, "MSFT": -10_000.0, "NVDA": 10_000.0}
print(risk_flags(book, 50_000.0, 1.5, 0.5))         # ['LEVERAGE', 'CONCENTRATION:AAPL']
print(risk_flags(book, 50_000.0, 3.0, 0.9))         # []  the same code, looser limits

守住边界并说明原因

先处理不良输入,并立即返回或抛出异常(卫语句),这样主路径就能不加嵌套地从上读到下。资本为零时的杠杆 gross / equity 应当是清晰的 ValueError,而不是从深处冒出的 ZeroDivisionError;空的投资组合应返回 0.0 而不是崩溃。注释应说明代码无法表达的为什么(# 交易所按分取整),而不是重复代码已经显示的是什么。docstring 说明函数接受什么、返回什么。最后,优先使用纯函数:只依赖参数、没有副作用,用少量输入就能测试。

# A guard clause stops bad input at the door and says exactly what is wrong
def leverage(positions, equity):
    if equity <= 0:
        raise ValueError("equity must be positive")
    return sum(abs(value) for value in positions.values()) / equity

print(leverage({"AAPL": 60_000.0, "MSFT": -30_000.0}, 45_000.0))     # 2.0
print(leverage({}, 45_000.0))                                         # 0.0  an empty book is fine

try:
    leverage({"AAPL": 60_000.0}, 0.0)
except ValueError as error:
    print("rejected:", error)                                         # rejected: equity must be positive
# A pure function answers the same way every time; hidden state does not
history = []

def running_notional(price, quantity):          # remembers earlier calls: the answer depends on the past
    history.append(price * quantity)
    return sum(history)

def notional(price, quantity):                  # depends only on its arguments
    return price * quantity

print(running_notional(10.0, 5), running_notional(10.0, 5))     # 50.0 100.0  same call, different answers
print(notional(10.0, 5), notional(10.0, 5))                     # 50.0 50.0   always the same
整洁不等于聪明不要为了显得聪明而压缩代码:大家都看得懂的三行循环,胜过需要注释的一行技巧。也不要拆成几十个微小函数;当函数读起来像一个清晰的步骤时就停下。整理代码与改变行为是两种不同的修改:在测试前后都通过的情况下重构,这样看到的任何差异都来自新逻辑,而不是整理。
  • 名称要名副其实,函数要小而单一职责,更大的函数通过调用它们来构建。
  • 不用魔法数字:限额作为参数或命名常量传入;重复的值只计算一次。
  • 先用清晰的错误挡住不良输入,注释写为什么而非是什么,并优先使用可测试的纯函数。

下一节:类型提示与静态检查,让工具在代码运行之前发现错误。

实验:评分内容

  • 用 net_exposure 计算净敞口25 分
  • 用 gross_exposure 汇总每个头寸25 分
  • 在 leverage 中保护分母25 分
  • 在 risk_flags 中组合各项检查25 分
资源管理器 · lab-117-1
▾ lab-117-1
…
bash · 浏览器内
learner@quantcraft:~/lab-117-1$
Python 尚未加载PythonLn 1, Col 1Spaces: 4UTF-8

要重置此主题吗?

lab-117-1 中的所有内容将恢复为初始文件,终端也会被清空。你当前的解答将丢失,且无法撤销。

将被重置
  • main.py → 初始代码
  • 终端历史与测试输出
  • 未提交的更改
将被保留
  • 实验最高得分
  • 你的 XP 与连续天数
  • 讲解进度与笔记

登录后即可运行你的代码。

所有讲解均可免费阅读。创建账户或登录后,即可运行代码、运行测试并提交此实验。

  • 浏览器内 IDE 与 bash 终端,无需安装
  • 即时评分、XP 以及你在学习路线中的位置
创建账户登录
或
使用 GitHub 继续