σμ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 AcademySkills
Ctrl K
AO
+1.2k Learners
LoginJoin ↗
Skills
AO
+1.2k Learners
Login
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Python skill›Module 11 · Production systems & frontier models›Topic 11.2·~1 hrExpert

CI/CD for quant code

Skill positionModule 11 of 11 · Topic 2 of 13
11.2 CI/CD for quant code
—Lab score— / 100Total XP—Streak—

Checks that run themselves

Tests only protect you if they are run, every time, by something that is not your memory. Continuous integration (CI) is a server that, on every push and pull request, checks out the code in a clean environment and runs the checks you defined: lint, type check, tests, a small backtest. If anything fails, the change is blocked. Continuous delivery/deployment (CD) goes further: when the checks pass on the main branch, the new version is built and released automatically. In quant work the stakes are concrete: a refactor that silently shifts a pricing result by a basis point should be caught by a machine before it reaches a portfolio.

# A test matrix: every operating system with every Python version, minus the combinations you skip
from itertools import product

os_list = ["linux", "macos", "windows"]
py_versions = ["3.11", "3.12"]
exclude = {("windows", "3.11")}

jobs = [(os_name, version) for os_name, version in product(os_list, py_versions) if (os_name, version) not in exclude]
print(len(jobs))                                     # 5
print(jobs[0], jobs[-1])                             # ('linux', '3.11') ('windows', '3.12')

What a quant pipeline checks

A typical pipeline has stages that fail fast: lint (style and obvious errors) and type checking run in seconds, then the unit tests, then slower checks such as a small regression backtest compared with a stored result. Make the checks deterministic: fixed random seeds, no network, no dependence on today's date, and data from a small committed snapshot rather than a live feed. Run them on a matrix of Python versions and operating systems you claim to support. To keep feedback quick, run only the tests that a change can affect (test selection), with a safe fallback to the whole suite when you cannot tell.

# Test selection: run only the tests a change can affect; an unknown file means "run everything"
def select_tests(changed_files, mapping):
    chosen = set()
    for path in changed_files:
        matches = [tests for prefix, tests in mapping.items() if path.startswith(prefix)]
        if not matches:
            return sorted({test for tests in mapping.values() for test in tests})
        for tests in matches:
            chosen.update(tests)
    return sorted(chosen)

mapping = {"src/pricing/": ["tests/test_bs.py", "tests/test_greeks.py"], "src/risk/": ["tests/test_var.py"]}
print(select_tests(["src/risk/var.py"], mapping))                    # ['tests/test_var.py']
print(select_tests(["src/risk/var.py", "setup.cfg"], mapping))       # all three tests: setup.cfg could affect anything

Gates, secrets and the workflow file

The pipeline is described in a file kept in the repository, for example .github/workflows/ci.yml for GitHub Actions: when it runs (on:), the jobs, the matrix and the steps, each step either uses: a ready-made action or run:s a command. Treat the file as code, reviewed like any other. Add a deploy gate: only the main branch deploys, and only when every required job succeeded (a skipped or missing job is not a success). Protect the branch so it cannot be merged without passing checks. Keep API keys and passwords in the CI system's secrets, never in the repository, and give each job only the permissions it needs. Release to a paper-trading or staging environment first.

# The deploy gate: main branch only, and every required job must have succeeded
def can_deploy(results, required, branch):
    return branch == "main" and all(results.get(job) == "success" for job in required)

print(can_deploy({"lint": "success", "tests": "success"}, ["lint", "tests"], "main"))     # True
print(can_deploy({"lint": "success", "tests": "skipped"}, ["lint", "tests"], "main"))     # False  skipped is not success
print(can_deploy({"lint": "success"}, ["lint", "tests"], "main"))                         # False  a missing job is not success
print(can_deploy({"lint": "success", "tests": "success"}, ["lint", "tests"], "dev"))      # False  only main deploys
# The workflow file is plain text, so it can be generated and checked like any other output
def workflow_yaml(name, python_versions, command):
    versions = ", ".join(f'"{version}"' for version in python_versions)
    lines = [f"name: {name}", "on: [push, pull_request]", "jobs:", "  test:", "    runs-on: ubuntu-latest",
             "    strategy:", "      matrix:", f"        python-version: [{versions}]", "    steps:",
             "      - uses: actions/checkout@v4", "      - uses: actions/setup-python@v5", "        with:",
             "          python-version: ${{ matrix.python-version }}", "      - run: pip install -e .[test]",
             f"      - run: {command}"]
    return "\n".join(lines) + "\n"

text = workflow_yaml("ci", ["3.11", "3.12"], "pytest -q")
print(text.splitlines()[7])                          #         python-version: ["3.11", "3.12"]
print(text.splitlines()[-1])                         #       - run: pytest -q
A green pipeline is only as good as its testsCI proves that the checks pass, not that the strategy is good. Keep the suite fast so people run it, fix a flaky test or delete it (a test that fails at random teaches everyone to ignore red), and treat a failing build as the team's first priority. Pin the tooling versions used in CI, and keep a way to run exactly the same checks on your own machine, so "works on my machine" and "fails in CI" are never a mystery.
  • CI runs lint, type checks and tests in a clean environment on every push; CD releases automatically only when they pass on main.
  • Make checks deterministic (seeds, snapshots, no network), run them on a matrix of Python versions and systems, and select tests by what changed.
  • Describe the pipeline in a reviewed workflow file, gate deploys on main and required jobs, keep secrets in the CI system, and fix flaky tests.

Next up: scheduled pipelines, running data and trading jobs at the right time, in the right order.

Lab · what's graded

  • Expand the test matrix with matrix_jobs25 pts
  • Run only what matters with select_tests25 pts
  • Guard the release with can_deploy25 pts
  • Generate the workflow with workflow_yaml25 pts
Explorer · lab-131-1
▾ lab-131-1
…
bash · in-browser
learner@quantcraft:~/lab-131-1$
Python not loaded yetPythonLn 1, Col 1Spaces: 4UTF-8

Reset this topic?

Everything in lab-131-1 goes back to the starter files and the terminal is cleared. Your current solution will be lost — this can't be undone.

Will be reset
  • main.py → starter code
  • Terminal history and test output
  • Unsubmitted changes
Will be kept
  • Best lab score
  • Your XP and streak
  • Lecture progress and notes

Log in to run your code.

Every lecture is free to read. Create an account or log in to run code, run tests and submit this lab.

  • In-browser IDE with a bash terminal — nothing to install
  • Instant grading, XP and your place on the skill
Create an accountLog in
OR
Continue with GitHub