Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions src/ml4t_coursework/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ class Conformance:
delta: str = ""
stamped_at: str = ""
helper_version: str = ""
unit: str = ""

def __str__(self) -> str:
head = f"{self.component}: {'conformant' if self.conformant else 'NOT conformant'}"
Expand All @@ -68,7 +67,6 @@ def __str__(self) -> str:
def to_dict(self) -> dict:
return {
"component": self.component,
"unit": self.unit,
"conformant": self.conformant,
"checks": [{"name": c.name, "passed": c.passed, "detail": c.detail} for c in self.checks],
"reference_delta": self.delta,
Expand Down Expand Up @@ -181,7 +179,7 @@ def run_invariants(contract, obj) -> list[CheckResult]:

def evaluate(contract, obj) -> Conformance:
"""Run all five checks, stopping the gating ones at the first failure."""
result = Conformance(component=contract.name, unit=contract.units[0], conformant=True)
result = Conformance(component=contract.name, conformant=True)
stages: list[Callable[[], Any]] = [
lambda: [run_interface(contract, obj)],
lambda: [run_leakage(contract, obj)],
Expand Down
2 changes: 1 addition & 1 deletion src/ml4t_coursework/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def status() -> str:
else:
failed = [c["name"] for c in meta.get("checks", []) if not c["passed"]]
state = f"NOT conformant ({', '.join(failed) or 'unknown'})"
lines.append(f" {name:<20} unit {contract.units[0]:<5} {state}")
lines.append(f" {name:<20} {state}")
return "components in your project folder:\n" + "\n".join(lines)


Expand Down
12 changes: 9 additions & 3 deletions src/ml4t_coursework/contracts.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
"""What a component is, and the registry of the twenty the course asks for.
"""What a component is, and the registry of the twenty the courses ask for.

A contract says what a component must do. It deliberately does not say which unit writes it: a
unit number belongs to one course's outline, and more than one course installs this package. A
Research to Production student is handed all twenty and never sat the Foundations unit that
writes the baseline, so a number here would appear in their own submission report naming a
lesson they never took. Each course keeps that mapping beside its own units, where it cannot
drift out of step with them - and it did drift, in both directions, while it lived here.

One `Contract` per component, declared beside its reference implementation in `reference/`. The
contract is authored *from* the reference, which is what guarantees it is passable, and every
Expand All @@ -23,7 +30,6 @@
class Contract:
name: str
kind: str
units: tuple[str, ...]
summary: str
probe: Callable[[Any], Any]
interface: Callable[[Any], None]
Expand All @@ -39,7 +45,7 @@ def __post_init__(self) -> None:
raise ValueError(f"{self.name}: kind must be 'callable' or 'config', got {self.kind!r}")

def describe(self) -> str:
lines = [f"{self.name} ({self.kind}, written in unit {' and '.join(self.units)})",
lines = [f"{self.name} ({self.kind})",
f" {self.summary}",
f" interface: {self.interface_detail}",
f" leakage probe: {self.leakage_note}"]
Expand Down
18 changes: 15 additions & 3 deletions src/ml4t_coursework/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,26 @@ def on_colab() -> bool:


def mount_drive(quiet: bool = True) -> bool:
"""Mount Google Drive when running on Colab. A no-op anywhere else."""
"""Mount Google Drive when running on Colab. A no-op anywhere else.

`google.colab` being importable does not mean a Drive can be mounted. The
published Colab runtime image has the package but no backend to mount
through, so `drive.mount` raises there; a headless or automated session can
also reach a mount it cannot complete. Returning False sends `home` to the
environment override or the home directory, which is the fallback it already
implements. Raising instead would fail before that fallback is ever consulted.
"""
try:
from google.colab import drive # type: ignore
except ImportError:
return False
if not (DRIVE_MOUNT / "MyDrive").is_dir():
if (DRIVE_MOUNT / "MyDrive").is_dir():
return True
try:
drive.mount(str(DRIVE_MOUNT))
return True
except Exception:
return False
return (DRIVE_MOUNT / "MyDrive").is_dir()


def home(create: bool = True) -> Path:
Expand Down
6 changes: 3 additions & 3 deletions src/ml4t_coursework/reference/allocator.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""`allocator` - units 8.1 and 8.2. From a position to a weight.
"""`allocator`. From a position to a weight.

8.1 maps the signal to weights. 8.2 tightens the same component with the constraint set, which is
The first unit maps the signal to weights. The second tightens the same component with the
constraint set, which is
where the cap and the gross budget stop being implicit.
"""

Expand Down Expand Up @@ -129,7 +130,6 @@ def _finite(obj) -> str:
register(Contract(
name="allocator",
kind="callable",
units=("7.1", "7.2"),
summary="Turns positions into weights inside a stated gross budget and per-name cap.",
probe=_probe,
interface=_interface,
Expand Down
3 changes: 1 addition & 2 deletions src/ml4t_coursework/reference/availability_lag.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""`availability_lag` - unit 2.2. The lag before a datum may be used.
"""`availability_lag`. The lag before a datum may be used.

This is the decision the rest of the pipeline silently depends on, and its leakage probe is the
one check `certification.md` calls the most valuable in the course.
Expand Down Expand Up @@ -87,7 +87,6 @@ def _leading_gap(obj) -> str:
register(Contract(
name="availability_lag",
kind="callable",
units=("2.2",),
summary="Delays every observation by the time it takes to become usable.",
probe=_probe,
interface=_interface,
Expand Down
3 changes: 1 addition & 2 deletions src/ml4t_coursework/reference/backtest_config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""`backtest_config` - unit 7.2. The assumptions the engine makes, said out loud."""
"""`backtest_config`. The assumptions the engine makes, said out loud."""

from __future__ import annotations

Expand Down Expand Up @@ -40,7 +40,6 @@ def _rebalance_is_a_period(obj) -> str:
register(Contract(
name="backtest_config",
kind="config",
units=("6.2",),
summary="Engine, fill assumption and rebalance frequency, with what the engine cannot answer.",
probe=probe,
interface=interface_for(FIELDS),
Expand Down
7 changes: 3 additions & 4 deletions src/ml4t_coursework/reference/baseline_strategy.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""`baseline_strategy` - unit 2.5. The auditable non-ML rule everything else must beat."""
"""`baseline_strategy`. The auditable non-ML rule everything else must beat."""

from __future__ import annotations

Expand Down Expand Up @@ -81,8 +81,8 @@ def _invested(obj) -> str:
require(len(active) > 100, "the baseline is invested",
"a rule that holds something over most of the sample",
f"positions on only {len(active)} of {len(out)} sessions",
"A baseline that is mostly in cash is a comparison against cash, which 7.3 already "
"makes separately.")
"A baseline that is mostly in cash is a comparison against cash, which the cost "
"model already prices separately.")
worst = float((active - 1.0).abs().max())
require(worst < 1e-6, "the baseline is invested",
"a fully invested book on every active session",
Expand Down Expand Up @@ -114,7 +114,6 @@ def _finite(obj) -> str:
register(Contract(
name="baseline_strategy",
kind="callable",
units=("2.5",),
summary="An auditable non-ML rule, fixed before any result is seen.",
probe=_probe,
interface=_interface,
Expand Down
5 changes: 2 additions & 3 deletions src/ml4t_coursework/reference/cost_model.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""`cost_model` - unit 8.3. What trading takes out, before you decide anything survived.
"""`cost_model`. What trading takes out, before you decide anything survived.

The two decisions in 8.3 - the form, and where its parameters come from - are taught rather than
The two decisions here - the form, and where its parameters come from - are taught rather than
built, so the shipped form is the one every student uses and what they choose is its parameters.
The contract is on the shape any cost model must have, so a student who changes the form still
gets checked.
Expand Down Expand Up @@ -103,7 +103,6 @@ def _finite(obj) -> str:
register(Contract(
name="cost_model",
kind="callable",
units=("7.3",),
summary="Charges the strategy for what it traded, in the units the return is measured in.",
probe=_probe,
interface=_interface,
Expand Down
3 changes: 1 addition & 2 deletions src/ml4t_coursework/reference/data_panel.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""`data_panel` - unit 2.1. The stored price panel, loaded with its close convention stated.
"""`data_panel`. The stored price panel, loaded with its close convention stated.

The file on disk is wide, one column per asset, because that is how a price download arrives and
how it stores compactly. What the rest of the pipeline works in is long: one row per (date,
Expand Down Expand Up @@ -129,7 +129,6 @@ def _unbalanced(obj) -> str:
register(Contract(
name="data_panel",
kind="callable",
units=("2.1",),
summary="Loads the stored price file and returns it long, one row per (date, asset).",
probe=_probe,
interface=_interface,
Expand Down
5 changes: 2 additions & 3 deletions src/ml4t_coursework/reference/exit_rule.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""`exit_rule` - unit 8.4. Position controls, and the one threshold worth calibrating."""
"""`exit_rule`. Position controls, and the one threshold worth calibrating."""

from __future__ import annotations

Expand Down Expand Up @@ -135,7 +135,7 @@ def _fires(obj) -> str:
require(closed > 0, "the rule does something",
"a position closed somewhere in a 35% fall held throughout",
"a rule that never fires even then",
"A threshold so wide it never triggers is not a control; 8.4's own result is that a "
"A threshold so wide it never triggers is not a control; this unit's own result is that a "
"stop grid need not identify a stable threshold, which is a different finding from "
"never testing one.")
return f"{closed} position-days closed by the rule"
Expand All @@ -144,7 +144,6 @@ def _fires(obj) -> str:
register(Contract(
name="exit_rule",
kind="callable",
units=("7.4",),
summary="Closes a position that has breached its control, and leaves it closed.",
probe=_probe,
interface=_interface,
Expand Down
5 changes: 2 additions & 3 deletions src/ml4t_coursework/reference/features.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""`features` - units 5.1 and 5.2. A feature is a hypothesis about a driver.
"""`features`. A feature is a hypothesis about a driver.

5.1 builds one feature that encodes a named driver. 5.2 tightens the same component so the
The first unit builds one feature that encodes a named driver. The second tightens it so the
lookback is read against the label horizon and the values are normalized inside each date's
cross-section rather than against pooled history.
"""
Expand Down Expand Up @@ -104,7 +104,6 @@ def _finite(obj) -> str:
register(Contract(
name="features",
kind="callable",
units=("4.1", "4.2"),
summary="Turns prices into the predictors the model sees, normalized within each date.",
probe=_probe,
interface=_interface,
Expand Down
3 changes: 1 addition & 2 deletions src/ml4t_coursework/reference/fold_splitter.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""`fold_splitter` - unit 3.1. Walk-forward folds with a label buffer."""
"""`fold_splitter`. Walk-forward folds with a label buffer."""

from __future__ import annotations

Expand Down Expand Up @@ -102,7 +102,6 @@ def _buffered(obj) -> str:
register(Contract(
name="fold_splitter",
kind="callable",
units=("3.1",),
summary="Splits a date index into walk-forward train/validation folds with a label buffer.",
probe=_probe,
interface=_interface,
Expand Down
3 changes: 1 addition & 2 deletions src/ml4t_coursework/reference/holdout_split.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""`holdout_split` - unit 3.2. A dated cutoff, written down and not opened."""
"""`holdout_split`. A dated cutoff, written down and not opened."""

from __future__ import annotations

Expand Down Expand Up @@ -72,7 +72,6 @@ def _covers(obj) -> str:
register(Contract(
name="holdout_split",
kind="callable",
units=("3.2",),
summary="Seals the last stretch of the sample behind a dated cutoff.",
probe=_probe,
interface=_interface,
Expand Down
8 changes: 4 additions & 4 deletions src/ml4t_coursework/reference/labeler.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""`labeler` - units 3.3 and 3.4. The outcome the model is asked to predict.
"""`labeler`. The outcome the model is asked to predict.

3.3 fixes the horizon and what overlap costs. 3.4 tightens the same component so entry and exit
The first unit fixes the horizon and what overlap costs. The second tightens the same component
so entry and exit
are prices actually reachable after the decision, rather than the close of the bar the decision
was made on.
"""
Expand Down Expand Up @@ -93,7 +94,7 @@ def _stamped_on_the_decision(obj) -> str:


def _executable(obj) -> str:
"""3.4's tightening: the entry price is one a decision at t could actually have transacted."""
"""The executable-price tightening: the entry is a price a decision at t could have transacted."""
panel = fixtures.panel()
out = obj(panel)
close = panel["close"].unstack("asset")
Expand Down Expand Up @@ -122,7 +123,6 @@ def _finite(obj) -> str:
register(Contract(
name="labeler",
kind="callable",
units=("3.3", "3.4"),
summary="Turns prices into the outcome a decision at t is judged on, stamped at t.",
probe=_probe,
interface=_interface,
Expand Down
5 changes: 2 additions & 3 deletions src/ml4t_coursework/reference/model_gbm.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""`model_gbm` - unit 6.3. Gradient boosting, on the same features and the same split as 6.1."""
"""`model_gbm`. Gradient boosting, on the same features and the same split as the linear model."""

from __future__ import annotations

Expand Down Expand Up @@ -65,7 +65,7 @@ def _interface(obj) -> None:
require(isinstance(out, pd.Series), "interface", "predict to return a Series",
f"a {type(out).__name__}")
require(out.index.equals(X_va.index), "interface",
"one prediction per row it was asked about, on the same index as 6.1's model, because "
"one prediction per row it was asked about, on the same index as the linear model, because "
"the two are compared",
f"{len(out)} predictions against {len(X_va)} rows")

Expand Down Expand Up @@ -103,7 +103,6 @@ def _finite(obj) -> str:
register(Contract(
name="model_gbm",
kind="callable",
units=("5.3",),
summary="A gradient-boosted model with capacity chosen rather than defaulted.",
probe=_probe,
interface=_interface,
Expand Down
3 changes: 1 addition & 2 deletions src/ml4t_coursework/reference/model_linear.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""`model_linear` - unit 6.1. The baseline every later result is read against."""
"""`model_linear`. The baseline every later result is read against."""

from __future__ import annotations

Expand Down Expand Up @@ -114,7 +114,6 @@ def _finite(obj) -> str:
register(Contract(
name="model_linear",
kind="callable",
units=("5.1",),
summary="A regularized linear model: the baseline every later result is read against.",
probe=_probe,
interface=_interface,
Expand Down
3 changes: 1 addition & 2 deletions src/ml4t_coursework/reference/objective.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""`objective` - unit 1.2. What the strategy is judged on, fixed before any result exists."""
"""`objective`. What the strategy is judged on, fixed before any result exists."""

from __future__ import annotations

Expand Down Expand Up @@ -26,7 +26,6 @@ def reference():
register(Contract(
name="objective",
kind="config",
units=("1.4",),
summary="The metric the strategy is judged on and the cost tier it is judged under.",
probe=probe,
interface=interface_for(FIELDS),
Expand Down
3 changes: 1 addition & 2 deletions src/ml4t_coursework/reference/preprocessor.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""`preprocessor` - unit 4.4. Winsorize, scale, impute, all fitted on training rows only."""
"""`preprocessor`. Winsorize, scale, impute, all fitted on training rows only."""

from __future__ import annotations

Expand Down Expand Up @@ -136,7 +136,6 @@ def _finite(obj) -> str:
register(Contract(
name="preprocessor",
kind="callable",
units=("3.6",),
summary="Winsorizes, scales and imputes, with every parameter learned on training rows.",
probe=_probe,
interface=_interface,
Expand Down
3 changes: 1 addition & 2 deletions src/ml4t_coursework/reference/quality_gates.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""`quality_gates` - unit 2.3. What a breach does, and to which bar."""
"""`quality_gates`. What a breach does, and to which bar."""

from __future__ import annotations

Expand Down Expand Up @@ -130,7 +130,6 @@ def _keeps_good(obj) -> str:
register(Contract(
name="quality_gates",
kind="callable",
units=("2.3",),
summary="Voids bars that fail a stated quality gate, and reports which gate caught what.",
probe=_probe,
interface=_interface,
Expand Down
3 changes: 1 addition & 2 deletions src/ml4t_coursework/reference/signal.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""`signal` - unit 7.1. From a score to a position: which names, and which way."""
"""`signal`. From a score to a position: which names, and which way."""

from __future__ import annotations

Expand Down Expand Up @@ -91,7 +91,6 @@ def _two_sided(obj) -> str:
register(Contract(
name="signal",
kind="callable",
units=("6.1",),
summary="Turns model scores into which names to hold and which way.",
probe=_probe,
interface=_interface,
Expand Down
3 changes: 1 addition & 2 deletions src/ml4t_coursework/reference/strategy_spec.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""`strategy_spec` - unit 1.1. The family you are in, and the mechanism you are claiming."""
"""`strategy_spec`. The family you are in, and the mechanism you are claiming."""

from __future__ import annotations

Expand Down Expand Up @@ -26,7 +26,6 @@ def reference():
register(Contract(
name="strategy_spec",
kind="config",
units=("1.3",),
summary="The strategy family and the economic mechanism the return is claimed to come from.",
probe=probe,
interface=interface_for(FIELDS),
Expand Down
Loading