σμ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—連続日数—

コードは書かれるよりはるかに多く読まれる

トレーディングのコードベースは何年も生き、書いていない人が、しばしば時間に追われて、あるいは半年後の自分が変更します。クリーンなコードとは、すばやく理解でき、安全に変更できるコードです。スタイル規則の話ではなく、誤った変更を目に見えるようにすることです。名前が悪く、3つのことをし、式の中に上限を隠した関数は、高くつくバグの温床です。たとえば誰もハードコードされていると覚えていないリスク上限です。

# 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?

名前、小さな関数、隠れた数値なし

効果の大半は3つの習慣から生まれます。実体どおりに名前を付ける:f ではなく gross_exposure、m ではなく max_leverage。関数を小さく、目的を1つに:各関数は数語で言い表せる仕事を1つだけ行うので、単独でテストでき再利用でき、大きな関数はそれらを呼んで作ります。数値を埋め込まない:式の中に書いた 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

境界を守り、理由を書く

不正な入力は先に処理して、すぐに return か raise します(ガード節)。そうすれば主な流れはネストなしで上から読めます。資本ゼロでの gross / equity は、奥深くからの ZeroDivisionError ではなく明確な ValueError にし、空のポートフォリオはクラッシュではなく 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
クリーンは巧妙さではない賢く見せるためにコードを詰め込まないこと。誰もが理解できる3行のループは、コメントが必要な1行の技巧に勝ります。逆に、何十もの小さな関数に刻みすぎないことも大切で、関数が明確な1ステップとして読めたらそこで止めます。また、整理は動作の変更とは別の変更です。前後でテストが通る状態でリファクタリングすれば、見える違いは整理ではなく新しいロジックによるものだと分かります。
  • 実体どおりに名前を付け、関数は小さく目的を1つにし、大きな関数はそれらを呼んで作る
  • マジックナンバーを使わない。上限は引数か名前付き定数で渡し、繰り返される値は一度だけ計算する
  • 不正な入力は明確なエラーで先に防ぎ、コメントは内容でなく理由を書き、テストしやすい純粋関数を好む

次は型ヒントと静的チェックです。コードを実行する前にツールにミスを見つけてもらいます。

ラボ:採点される内容

  • 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で続ける