1. Promise

One-sentence promise

After this lecture, you can produce a complete deliverable bundle—code, config, data references, metrics, plots, assumptions, and limitations—so another quant can reproduce, audit, and challenge your model without asking for missing pieces.

End tag line

A model without artefacts is not evidence.


2. Learning objectives

After reading this lecture, you will be able to:

  • Define the final deliverable as an artefact bundle, not a slide deck.
  • List the minimum required artefacts: code, config, data references, metrics, plots, assumptions, limitations.
  • Write assumptions and limitations in a committee-ready way.
  • Create a minimal “evidence pack” folder structure.
  • Produce plots that support auditability (not marketing).

3. Prerequisites and setup

Required maths and prior knowledge

  • None beyond earlier lectures.

Python environment assumptions

  • Python 3.9+ in a notebook or script.

Packages used

  • numpy, pandas, matplotlib (optional for demos)

4. Conceptual core

4.1 The key idea

A deliverable is not “the model”.

A deliverable is an evidence pack: everything needed to reproduce results and evaluate fitness for purpose.

Formally, define the modelling pipeline as: Ŷ=fθ(X;config,data,code) \hat{Y} = f_{\theta}(X;\ \text{config},\ \text{data},\ \text{code}) A reproducible deliverable fixes:

  • the code version,
  • the configuration (hyperparameters, thresholds, filters),
  • the data references (what, where, when),
  • the evaluation protocol,
  • the outputs (metrics, plots),
  • and the narrative (assumptions, limitations).

Validator sentence

If it cannot be reproduced, it cannot be validated.


4.2 Final artefacts (minimum set)

Think in six artefact classes.

(1) Code

  • Training code (feature building, fitting).
  • Inference code (predict, post-process, decision rule).
  • Evaluation code (CV, stability, error analysis).
  • Utility code (logging, seed control, version printing).

Minimum requirement: one command to rerun end-to-end.

(2) Config

A single source of truth for:

  • universe definition and filters,
  • horizon, target definition,
  • split design (dates, folds, embargo),
  • model hyperparameters,
  • thresholds (classification) or sizing rules (trading),
  • cost assumptions (if decision-level).

Config must be versioned and human-readable (YAML/JSON/TOML).

(3) Metrics

At least:

  • Primary metric (pre-registered).
  • Secondary metrics (stability / calibration / tail).
  • Subperiod and regime tables.
  • Worst-block metric.

Report metric with:

  • units,
  • sample period,
  • and which split it belongs to (train/val/test).

(4) Plots

Minimum plots (choose those appropriate to the problem):

  • Time-series CV fold metric plot (stability).
  • Error anatomy plot: |et||e_t| over time or reliability plot.
  • Distribution plot: residuals or score distribution.
  • Decision-level plot: cumulative P&L / drawdown if applicable.

Plots should be labelled, dated, and tied to config.

(5) Assumptions

Assumptions are statements you rely on.

Write them as testable claims or scope boundaries.

Examples:

  • Data is point-in-time and survivorship-free.
  • Transaction costs are constant and equal to X bps.
  • Execution occurs at next open with no slippage beyond costs.
  • Missing values are forward-filled up to N steps.
  • Regime stationarity is assumed over the training window.

(6) Limitations

Limitations are known failure modes and non-claims.

Examples:

  • Performance degrades in high-volatility regimes.
  • Model is not calibrated; probabilities must not be used for pricing.
  • Not validated for illiquid assets or corporate action days.
  • Not robust to timestamp errors or delayed data feeds.

Key difference

Assumptions justify why you think it works. Limitations state where you know it may not work.


4.3 A minimal folder structure (example)

project/
  README.md
  config/
    run.yaml
  src/
    build_features.py
    train.py
    predict.py
    evaluate.py
    utils.py
  artefacts/
    run_id_YYYYMMDD_HHMM/
      config_snapshot.yaml
      metrics.json
      metrics_subperiod.csv
      metrics_regime.csv
      plots/
        cv_fold_metric.png
        error_over_time.png
        residual_hist.png
      report.md

Rule

An artefact folder is immutable once created.


4.4 Minimal reproducibility contract

A run is reproducible if you can state:

  • Code hash / version tag.
  • Data version identifiers (tables, snapshots, query hash).
  • Config snapshot.
  • Random seeds.
  • Environment (Python version, package versions).

In notation:

Run=(code_hash,data_id,config_hash,seed,env) \text{Run} = (\text{code\_hash},\ \text{data\_id},\ \text{config\_hash},\ \text{seed},\ \text{env})


5. Control lens

5.1 Analogue and failure mode

Control analogue

A controller design is delivered with:

  • plant model assumptions,
  • operating region,
  • stability margins,
  • constraints,
  • and implementation details.

Finance analogue

A model must be delivered with:

  • data and evaluation protocol,
  • assumptions and limitations,
  • operational constraints,
  • and reproducible artefacts.

The failure is the same: “It worked in simulation” is not a deliverable.


6. Worked example (what to ship)

A simple forecasting model for a daily decision should ship:

  • Code: feature build, baseline, model, evaluation.
  • Config: horizon, split dates, baseline choice.
  • Metrics: RMSE/MAE by fold and year; worst-year.
  • Plots: fold RMSE, error over time, residual histogram.
  • Assumptions: execution timing, cost assumptions if trading.
  • Limitations: stress regime degradation.

7. Python lab (create a tiny evidence pack)

This code creates a local folder and writes: - config snapshot (json), - metrics (json), - one plot (png), - a short report (md).

import json
import os
from datetime import datetime

import numpy as np
import matplotlib.pyplot as plt

# -----------------------------
# Create a run id and folders
# -----------------------------
run_id = datetime.now().strftime("run_%Y%m%d_%H%M%S")
base = os.path.join("artefacts", run_id)
plots_dir = os.path.join(base, "plots")
os.makedirs(plots_dir, exist_ok=True)

# -----------------------------
# Example "config snapshot"
# -----------------------------
config = {
    "problem": "toy_forecast",
    "target": "y_{t+1}",
    "horizon": 1,
    "split": {"type": "time_holdout", "train_frac": 0.7},
    "baseline": "last_value",
    "model": "linear_lags",
    "seed": 0,
}

with open(os.path.join(base, "config_snapshot.json"), "w") as f:
    json.dump(config, f, indent=2)

# -----------------------------
# Generate a tiny dataset + metrics
# -----------------------------
rng = np.random.default_rng(config["seed"])
T = 300
t = np.arange(T)
y = 0.02 * t + 0.6 * np.sin(2*np.pi*t/20) + rng.normal(0, 0.8, T)

# Baseline: last value
y_hat = np.roll(y, 1)
y_hat[0] = np.nan

# Split
idx = np.arange(T)
valid = ~np.isnan(y_hat)
idx = idx[valid]
y_v = y[valid]
y_hat_v = y_hat[valid]

split = int(config["split"]["train_frac"] * len(idx))
test_y = y_v[split:]
test_hat = y_hat_v[split:]

rmse = float(np.sqrt(np.mean((test_y - test_hat)**2)))
mae = float(np.mean(np.abs(test_y - test_hat)))

metrics = {"test_rmse": rmse, "test_mae": mae, "n_test": int(len(test_y))}

with open(os.path.join(base, "metrics.json"), "w") as f:
    json.dump(metrics, f, indent=2)

# -----------------------------
# One plot
# -----------------------------
plt.figure(figsize=(8, 4))
plt.plot(test_y[:80], label="y (test)")
plt.plot(test_hat[:80], label="baseline last value")
plt.title("Deliverable plot: baseline forecast on test window")
plt.xlabel("Time index (test)")
plt.ylabel("Value")
plt.legend()
plt.tight_layout()
plt.savefig(os.path.join(plots_dir, "baseline_test_overlay.png"), dpi=150)
plt.close()

# -----------------------------
# Short report (assumptions + limitations)
# -----------------------------
report = f"""# Evidence pack: {run_id}

## Summary
This run evaluates a toy baseline forecast for a synthetic series.

## Config
- Target: {config["target"]}
- Horizon: {config["horizon"]}
- Split: {config["split"]}

## Metrics (test)
- RMSE: {rmse:.4f}
- MAE: {mae:.4f}
- n_test: {len(test_y)}

## Assumptions
- Synthetic data is stationary enough for demonstration.
- Baseline uses y_t to predict y_(t+1).

## Limitations
- Not a trading evaluation.
- No regime stability checks included.
"""

with open(os.path.join(base, "report.md"), "w") as f:
    f.write(report)

print("Created evidence pack at:", base)
print("Files:", os.listdir(base))

8. Diagnostics and failure modes (deliverables)

  • Metrics without config are not interpretable.
  • Plots without labels and dates are not evidence.
  • Results without assumptions are not auditable.
  • Models without limitations are overclaims.
  • If artefacts are mutable, results cannot be trusted.

9. Mini-exercises

Exercise 1

List the six artefact classes in the evidence pack.

Solution Code, config, metrics, plots, assumptions, limitations.


Exercise 2 (trap)

You have a strong plot and a strong metric. You do not have the config snapshot. Is the result valid?

Solution No. You cannot reproduce or audit without the config.


10. Summary

  • Deliverables are artefact bundles, not slide decks.
  • The evidence pack is the minimum standard for validation and reuse.
  • Assumptions and limitations are as important as the metric.

11. Out of scope

  • Full CI/CD, model registry, and production deployment pipelines.
  • Enterprise data lineage systems.