σμ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.3·~50 分上級

pytestによるテスト

ロードマップ上の位置モジュール 10/11 · トピック 3/13
10.3 pytestによるテスト
—ラボスコア— / 100合計XP—連続日数—

なぜテストするのか、テストとは何か

トレーディングのバグは画面上の例外ではなく、もっともらしく見える間違った数字です。損益の符号が反転している、手数料が二重にかかる、明日の価格をこっそり使うシグナルなどです。テストは、答えが分かっている値と照らしてコードを確認する小さなプログラムで、こうしたバグを市場ではなく手元で見つけます。pytest は test_* という名前の関数を見つけ、それぞれ実行し、assert が失敗したものを報告します。テストはフレームワークなしでも読めます。入力を用意し、関数を呼び、期待を assert するだけです。

# A test is a function with an assert; pytest runs every test_* function it finds
def net_pnl(entry, exit_price, quantity, fee):
    return (exit_price - entry) * quantity - fee

def test_net_pnl_long_winner():
    assert net_pnl(100.0, 102.0, 50, 1.0) == 99.0

def test_net_pnl_short_loser():
    assert net_pnl(100.0, 102.0, -50, 1.0) == -101.0

for test in (test_net_pnl_long_winner, test_net_pnl_short_loser):
    test()                                           # pytest does this for you and reports failures
    print(test.__name__, "passed")

浮動小数、エラー、多数のケース

浮動小数は近似値なので、0.1 + 0.2 == 0.3 は偽です。数値は許容誤差付きで比べます。pytest.approx(expected) は小さな相対差(既定では100万分の1。加えて 1e-12 というごく小さな絶対値の下限があります)を許し、相対誤差が役に立たない0付近の値には絶対許容誤差を加えられます。不正な入力が拒否されることを確かめるには、pytest.raises(ValueError) を使います。その例外が起きたときだけ成功し、起きない(または別の例外)なら失敗します。1つのテストを多数のケースで走らせるには @pytest.mark.parametrize で入力と期待値の表を渡します。各行が別々に報告され、ケースの追加は1行の変更で済みます。

# Floats need a tolerance: approx accepts a tiny relative difference, == does not
import pytest

print(0.1 + 0.2 == 0.3)                              # False
print(0.1 + 0.2 == pytest.approx(0.3))               # True
print(1e-10 == pytest.approx(0.0))                   # False  near zero a relative tolerance cannot help
print(1e-10 == pytest.approx(0.0, abs=1e-9))         # True   a larger absolute tolerance does
# raises: the test passes only if the expected exception happens
import pytest

def leverage(gross, equity):
    if equity <= 0:
        raise ValueError("equity must be positive")
    return gross / equity

with pytest.raises(ValueError) as info:
    leverage(1000.0, 0.0)
print("raised:", info.value)                         # raised: equity must be positive

try:
    with pytest.raises(ValueError):
        leverage(1000.0, 500.0)                      # no exception: pytest.raises itself fails
except BaseException as outcome:
    print(type(outcome).__name__)                    # Failed
# parametrize in miniature: one function, a table of cases, each row judged on its own
def fee(notional, rate=0.001, minimum=1.0):
    return max(notional * rate, minimum)

cases = [
    ((100_000.0,), 100.0),
    ((500.0,), 1.0),                                 # the minimum fee applies
    ((2_000.0, 0.002), 4.0),
    ((0.0,), 1.0),
]
for index, (args, expected) in enumerate(cases):
    got = fee(*args)
    print(index, "ok" if abs(got - expected) <= 1e-9 * abs(expected) else f"FAIL got {got}")

フィクスチャ、シード、何をテストするか

フィクスチャは、テストが名前で要求する共通の準備です。小さな価格系列、シードを固定した乱数生成器、一時ファイルなどです。乱数データではシードを固定する(または表示する)ことで、失敗を再現できます。100回に1回失敗して再現できないテストは、ないほうがましです。内部の細部ではなく、気にする動作をテストします。手で確かめた既知のケース、境界(空の入力、ゼロ、要素1つ)、常に成り立つべき不変条件(確率は0から1の間、同じ価格で買って売れば手数料分だけ損をする)、そして直したバグごとの回帰テストです。あらゆる入力で成り立つ規則を述べられるなら、多数のランダム入力から反例を探すのは、安くて強力なテストです。

# A seed makes a random test replayable; a property is searched for a counterexample
import random

def find_counterexample(prop, inputs):
    for value in inputs:
        if not prop(value):
            return value
    return None

rng = random.Random(42)                              # fixed seed: the same inputs on every run
returns = [round(rng.uniform(-0.05, 0.05), 4) for _ in range(1000)]

def compound_is_positive_growth(r):
    return (1 + r) > 0                               # a daily return above -100% keeps the value positive

print(find_counterexample(compound_is_positive_growth, returns))      # None  the rule holds for all 1000
print(find_counterexample(lambda r: r > -0.049, returns))             # a return that breaks a wrong rule
失敗しえないテストは何も証明しない意図的にコードを壊して、各テストをまず失敗させましょう。狙ったバグを捕まえられると分かります。関数自身の式をテストにコピーしてはいけません。両方が同じ誤りを持てば一致してテストが通ってしまいます。期待値は独立に、または手計算で求めます。また、テストは高速で、ネットワークや時計に依存させないようにして、変更のたびに実際に走らせましょう。
  • テストは入力を用意し、関数を呼び、結果を assert する。pytest はすべての test_* 関数を実行して失敗を報告する
  • 浮動小数は pytest.approx で比べ(0付近では絶対許容誤差を加える)、エラーは pytest.raises で期待し、ケース表には parametrize を使う
  • 乱数シードを固定し、境界・不変条件・直したバグをテストし、反例を探し、信頼する前に各テストを一度失敗させる

次は Python コードのプロファイリングです。高速化を試みる前に、時間がどこに使われているかを測ります。

ラボ:採点される内容

  • is_close で許容誤差付きに浮動小数を比べる25点
  • run_cases でケース表を実行する25点
  • raises でエラーを期待する25点
  • find_counterexample で反例を探す25点
エクスプローラー · lab-119-1
▾ lab-119-1
…
bash · ブラウザ内
learner@quantcraft:~/lab-119-1$
Pythonは未読み込みPythonLn 1, Col 1Spaces: 4UTF-8

このトピックをリセットしますか?

lab-119-1 の内容はすべてスターターファイルに戻り、ターミナルも消去されます。現在の解答は失われ、元に戻せません。

リセットされるもの
  • main.py → スターターコード
  • ターミナル履歴とテスト出力
  • 未提出の変更
保持されるもの
  • ラボの最高スコア
  • XPと連続日数
  • 講義の進捗とメモ

コードを実行するにはログインしてください。

レクチャーはすべて無料で読めます。コードの実行、テスト、ラボの提出にはアカウントの作成かログインが必要です。

  • ブラウザ内IDEとbashターミナル。インストール不要
  • 即時採点、XP、ロードマップでの現在地
アカウントを作成ログイン
または
GitHubで続ける