CutPointLab

Try next

A curated set of experiments to run, ordered by how much you'll learn per minute spent. Some run with the existing CLI flags; some need a small edit to ml/ first. Each card tells you the why, not just the how.

Starter

Run these first. Each one is a single CLI command and demonstrates a core concept.

Try a 30-day horizon

✓ Tried — view

Why this: Longer horizons are systematically harder to predict — uncertainty compounds. Comparing a 7d run to a 30d run on the same features tells you how quickly the signal decays.

You'll learn: Expect RMSE to be larger and the 80% band to be much wider for the 30d model. If coverage stays near 80% on both, the model is honestly representing increased uncertainty rather than just being more wrong.

Command
python -m ml.train_wti --horizon 30 --folds 1 --test-window-days 20 --min-train-rows 15 --notes "30d horizon — uncertainty decay test"

Try a 1-day horizon

✓ Tried — view

Why this: Daily moves are mostly noise, but the model has more recent information. Tests whether short-horizon prediction is easy (it usually isn't) or whether the noise dominates the signal entirely.

You'll learn: If 1d RMSE >> 7d RMSE per-day, your features lag the market. If they're comparable, your features are timely.

Command
python -m ml.train_wti --horizon 1 --folds 2 --test-window-days 15 --min-train-rows 15 --notes "1d horizon — daily noise test"

Squeeze more folds out of the existing data

✓ Tried — view

Why this: With one fold, your metrics could be a coincidence. Multiple non-overlapping test windows give you variance bars on RMSE — you'll see whether the model's behaviour is stable across regimes.

You'll learn: Per-fold RMSEs should look similar if the model's signal is real. Big spread = the model only works in some periods.

Command
python -m ml.train_wti --folds 4 --test-window-days 10 --min-train-rows 15 --min-test-rows 3 --notes "4 folds × 10d each"

Intermediate

Each one needs a small (~5 line) code edit. The biggest learning is here.

Ablation: drop the WTI lag features

✓ Tried — view

Why this: Right now the model can mostly cheat by extrapolating WTI's own recent moves. Removing WTI lags forces it to lean on the exogenous features (Brent, refined-product spreads, Fed funds). If RMSE barely changes, your other features are surprisingly informative; if it explodes, WTI's own history was carrying the model.

You'll learn: How much of your model's accuracy is just autoregressive momentum vs. genuine cross-feature signal.

Command
python -m ml.train_wti --exclude-features WTI_SPOT --min-train-rows 252 --notes "ablation: no WTI features [suggestion:ablation-wti-lags]"

Sweep the learning rate

✓ Tried — view

Why this: learning_rate=0.05 is a reasonable default. Try 0.01 (slower, smoother fit, less likely to overfit) and 0.2 (faster, more reactive). Watch how the train/test gap shifts.

You'll learn: Bias-variance tradeoff in gradient boosting. A too-low LR underfits; a too-high LR overfits the limited data we have.

Command
python -m ml.train_wti --learning-rate 0.01 --min-train-rows 252  # then again with 0.2

Predict the absolute price instead of log return

✓ Tried — view

Why this: Returns target is finance-correct but unintuitive. An absolute-price target gives you metrics in dollars, which are easy to reason about ("the model was off by $1.20 on average"). It also turns MAPE into a useful number again.

You'll learn: Why financial ML usually targets returns: prices have trends, returns are roughly stationary. A price model on Q1 2024 will look amazing on metrics — and probably fail catastrophically on Q2.

Command
python -m ml.train_wti --target price --min-train-rows 252 --notes "absolute price target"

Train tighter quantiles (p25 / p75 — a 50% band)

✓ Tried — view

Why this: An 80% band is wide. A 50% band is the model's 'most likely' range. Comparing the two coverages tells you about calibration shape: if 50% coverage is way below 50%, the model has fat tails relative to what its quantile fit captured.

You'll learn: Coverage should be calibrated *across* quantile levels, not just at one. This is a real check on whether the model 'knows what it knows.'

Command
python -m ml.train_wti --with-50-band --min-train-rows 252

Test the coiled spring: does washed-out positioning predict returns?

✓ Tried — view

Why this: Issue No. 2's asymmetry argument in model form: managed-money WTI net length sits in the bottom 5% of weeks since 2010. Event-study it — after bottom-decile COT prints, what do forward 1/4/12-week WTI returns look like vs. the unconditional distribution? One flag on the trainer (--extra-features COT_WTI_MM_NET) plus a notebook-style read of the event study.

You'll learn: Whether positioning extremes are signal or noise at our horizons — and whether the desk's editorial thesis survives a backtest. Publishable either way: 'we tested our own take' is the brand.

Command
python -m ml.train_wti --extra-features COT_WTI_MM_NET --min-train-rows 252 --notes "[suggestion:cot-coiled-spring]"

Needs: COT is weekly since 2010, so the joined frame shrinks to ~2010+. For the event study proper, add ml/event_study_cot.py grouping forward returns by COT percentile decile.

Classify next week's distillate draw/build before the WPSR print

✓ Tried — view

Why this: The market was stunned by a 5M-barrel draw the week analysts expected a build. A weekly classifier — features: spot cracks, retail spreads, utilization, degree days, rig counts — that beats the consensus sign even 60% of the time is front-runnable editorial content every Wednesday.

You'll learn: Working with a weekly target from daily features (aggregation windows, as-of joins), and precision/recall on an imbalanced seasonal target instead of RMSE.

Command
(needs a sibling trainer — ml/train_wpsr_direction.py)

Needs: Target = sign(Δ EIA_DISTILLATE_STOCKS_US week-over-week), features from the daily frame resampled to WPSR weeks with a strict as-of cutoff the Tuesday before each print.

Advanced

Real engineering effort — but each unlocks a big capability for the lab.

Macro-only model: drop all oil prices

✓ Tried — view

Why this: Train using only TSA passenger throughput, Fed funds, and CAPEX series. Can demand-side and macro features alone forecast oil returns? Almost certainly worse than the full model — but how much worse is the actual return on having oil-price features.

You'll learn: How to value a feature in causal terms ('what's the predictive lift of including X?') vs. statistical terms ('what's its SHAP contribution?').

Command
python -m ml.train_wti --features FED_FUNDS_EFFECTIVE,DXY_BROAD,UST_10Y,HY_OAS,BREAKEVEN_5Y --min-train-rows 252

Add a conformal-prediction calibration step

✓ Tried — view

Why this: If your coverage_80 isn't 80%, the bands aren't calibrated. Conformal prediction is a model-agnostic post-hoc fix: hold out the last 20% of your training data, compute residual quantiles on it, then widen the model's bands by the residual quantile. Bands get re-calibrated to the desired coverage at the cost of being slightly wider on average.

You'll learn: How to ship a model with intervals you can actually trust — the missing step between 'training' and 'deployment' for any quantile model.

Command
python -m ml.train_wti --conformal --min-train-rows 252

Forecast the Gulf 3-2-1 crack itself

✓ Tried — view

Why this: The WTI target was always a placeholder — CutPoint's product thesis is refining margins. Reading Room Issue No. 2 documented the 3-2-1 at its 99.8th percentile; a model that forecasts the crack's 7-day change (or just its direction) from inventories, utilization, turnaround season, and positioning is the first model that's about the business.

You'll learn: Whether margin moves are forecastable from the desk's own primary series — and which features carry it (distillate stocks? utilization? COT?). This becomes the backbone for a subscriber-facing signal.

Command
(needs a target builder — ~20 lines)

Needs: Add a build_crack_frame() to ml/features.py: crack = (2×EER_EPMRU_PF4_RGC_DPG + EER_EPD2F_PF4_Y35NY_DPG)×42/3 − WTI_SPOT (same recipe as the Reading Room charts), target = H-day change. The trainer needs a --target-series flag or a sibling train_crack.py.

Benchmark a time-series foundation model, zero-shot

✓ Tried — view

Why this: Pretrained forecasters (Amazon Chronos-2, Google TimesFM, Salesforce MOIRAI-2) now beat tuned statistical baselines zero-shot on many benchmarks — but gradient boosting still wins where domain features matter. Running Chronos-Bolt (CPU-friendly) on our exact walk-forward folds answers whether 100B pretraining time-points know something about oil that our 44 features don't.

You'll learn: Where the specialist-vs-generalist frontier sits on YOUR data. Research finds classical methods beat zero-shot foundation models on ~20% of datasets with as little as 2% of the training data — is oil one of them?

Command
pip install chronos-forecasting, then a ~50-line ml/bench_chronos.py that replays walk_forward_folds() and persists a model row with model_type='chronos_zero_shot'.

Needs: Score it with the same _evaluate() so RMSE/coverage are directly comparable to the LightGBM rows in /lab.

Does satellite flaring lead product cracks?

Why this: CutPoint ingests a VIIRS flaring index over Russian refineries — alt-data almost nobody else in the newsletter tier has. If drone-strike-driven flaring anomalies lead European diesel cracks by days, that's both a tradeable signal and a headline chart no competitor can print.

You'll learn: Lead-lag analysis with irregular satellite data (cloud gaps, revisit cadence), and how to be honest about small-N event windows.

Command
(analysis first: cross-correlate VIIRS_RU_REFINERY_FLARES vs. the NYH HO crack at lags 0–10d)

Needs: Start with ml/diagnose-style lead-lag scan; only build a model if the cross-correlogram shows anything. Negative result is still an Issue section.