ml4t-coursework 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. ml4t_coursework-0.2.0/.github/workflows/ci.yml +93 -0
  2. ml4t_coursework-0.2.0/.github/workflows/release.yml +39 -0
  3. ml4t_coursework-0.2.0/.gitignore +8 -0
  4. ml4t_coursework-0.2.0/DATASET.md +66 -0
  5. ml4t_coursework-0.2.0/LICENSE +21 -0
  6. ml4t_coursework-0.2.0/PKG-INFO +130 -0
  7. ml4t_coursework-0.2.0/README.md +92 -0
  8. ml4t_coursework-0.2.0/pyproject.toml +52 -0
  9. ml4t_coursework-0.2.0/src/ml4t_coursework/__init__.py +29 -0
  10. ml4t_coursework-0.2.0/src/ml4t_coursework/checks.py +221 -0
  11. ml4t_coursework-0.2.0/src/ml4t_coursework/components.py +204 -0
  12. ml4t_coursework-0.2.0/src/ml4t_coursework/contracts.py +101 -0
  13. ml4t_coursework-0.2.0/src/ml4t_coursework/course.py +86 -0
  14. ml4t_coursework-0.2.0/src/ml4t_coursework/data/__init__.py +129 -0
  15. ml4t_coursework-0.2.0/src/ml4t_coursework/data/__main__.py +30 -0
  16. ml4t_coursework-0.2.0/src/ml4t_coursework/data/etf_close_fingerprint.csv +101 -0
  17. ml4t_coursework-0.2.0/src/ml4t_coursework/fixtures.py +67 -0
  18. ml4t_coursework-0.2.0/src/ml4t_coursework/project.py +87 -0
  19. ml4t_coursework-0.2.0/src/ml4t_coursework/py.typed +0 -0
  20. ml4t_coursework-0.2.0/src/ml4t_coursework/reference/__init__.py +6 -0
  21. ml4t_coursework-0.2.0/src/ml4t_coursework/reference/_config.py +51 -0
  22. ml4t_coursework-0.2.0/src/ml4t_coursework/reference/_shared.py +36 -0
  23. ml4t_coursework-0.2.0/src/ml4t_coursework/reference/allocator.py +143 -0
  24. ml4t_coursework-0.2.0/src/ml4t_coursework/reference/availability_lag.py +88 -0
  25. ml4t_coursework-0.2.0/src/ml4t_coursework/reference/backtest_config.py +57 -0
  26. ml4t_coursework-0.2.0/src/ml4t_coursework/reference/baseline_strategy.py +117 -0
  27. ml4t_coursework-0.2.0/src/ml4t_coursework/reference/cost_model.py +119 -0
  28. ml4t_coursework-0.2.0/src/ml4t_coursework/reference/data_panel.py +96 -0
  29. ml4t_coursework-0.2.0/src/ml4t_coursework/reference/exit_rule.py +151 -0
  30. ml4t_coursework-0.2.0/src/ml4t_coursework/reference/features.py +115 -0
  31. ml4t_coursework-0.2.0/src/ml4t_coursework/reference/fold_splitter.py +116 -0
  32. ml4t_coursework-0.2.0/src/ml4t_coursework/reference/holdout_split.py +85 -0
  33. ml4t_coursework-0.2.0/src/ml4t_coursework/reference/labeler.py +127 -0
  34. ml4t_coursework-0.2.0/src/ml4t_coursework/reference/model_gbm.py +118 -0
  35. ml4t_coursework-0.2.0/src/ml4t_coursework/reference/model_linear.py +128 -0
  36. ml4t_coursework-0.2.0/src/ml4t_coursework/reference/objective.py +41 -0
  37. ml4t_coursework-0.2.0/src/ml4t_coursework/reference/preprocessor.py +150 -0
  38. ml4t_coursework-0.2.0/src/ml4t_coursework/reference/quality_gates.py +125 -0
  39. ml4t_coursework-0.2.0/src/ml4t_coursework/reference/signal.py +105 -0
  40. ml4t_coursework-0.2.0/src/ml4t_coursework/reference/strategy_spec.py +42 -0
  41. ml4t_coursework-0.2.0/src/ml4t_coursework/reference/task_form.py +102 -0
  42. ml4t_coursework-0.2.0/src/ml4t_coursework/reference/universe.py +100 -0
  43. ml4t_coursework-0.2.0/src/ml4t_coursework/report.py +71 -0
  44. ml4t_coursework-0.2.0/src/ml4t_coursework/results.py +82 -0
  45. ml4t_coursework-0.2.0/tests/conftest.py +14 -0
  46. ml4t_coursework-0.2.0/tests/test_helper.py +200 -0
  47. ml4t_coursework-0.2.0/tests/test_multi_course.py +116 -0
  48. ml4t_coursework-0.2.0/tests/test_references.py +267 -0
  49. ml4t_coursework-0.2.0/uv.lock +2169 -0
@@ -0,0 +1,93 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, "release/**", "fix/**"]
6
+ pull_request:
7
+ branches: [main]
8
+ workflow_dispatch:
9
+ workflow_call:
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ concurrency:
15
+ group: ci-${{ github.workflow }}-${{ github.ref }}
16
+ cancel-in-progress: true
17
+
18
+ env:
19
+ OMP_NUM_THREADS: "1"
20
+ OPENBLAS_NUM_THREADS: "1"
21
+ MKL_NUM_THREADS: "1"
22
+
23
+ jobs:
24
+ lint:
25
+ name: Lint
26
+ runs-on: ubuntu-latest
27
+ steps:
28
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
29
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
30
+ with:
31
+ version: "0.10.9"
32
+ - run: uv python install 3.14
33
+ - run: uv sync --python 3.14 --all-extras --locked
34
+ - run: uv run ruff check --isolated --select E4,E7,E9,F src tests
35
+
36
+ qualify:
37
+ name: Qualify (${{ matrix.os }}, Python ${{ matrix.python-version }})
38
+ runs-on: ${{ matrix.os }}
39
+ timeout-minutes: 15
40
+ strategy:
41
+ fail-fast: false
42
+ matrix:
43
+ os: [ubuntu-latest, macos-latest, windows-latest]
44
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
45
+ steps:
46
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
47
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
48
+ with:
49
+ version: "0.10.9"
50
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
51
+ with:
52
+ python-version: ${{ matrix.python-version }}
53
+ - run: uv sync --python python --all-extras --locked
54
+ - name: Import package and data extra
55
+ run: >-
56
+ uv run --no-sync python -c
57
+ "import ml4t_coursework, yfinance; from ml4t_coursework import data;
58
+ assert data.fingerprint().shape == (100, 6)"
59
+ - run: uv run --no-sync pytest -q
60
+ - run: uv build --python python
61
+ - name: Install built wheel with data extra
62
+ shell: bash
63
+ run: |
64
+ uv venv --python python .artifact-venv
65
+ wheel="$(find dist -name '*.whl' -print -quit)"
66
+ uv pip install --python .artifact-venv "${wheel}[data]"
67
+ uv run --python .artifact-venv --no-project python -c \
68
+ "import ml4t_coursework, yfinance; from ml4t_coursework import data; assert data.fingerprint().shape == (100, 6)"
69
+
70
+ build:
71
+ name: Build Package
72
+ runs-on: ubuntu-latest
73
+ needs: [lint, qualify]
74
+ steps:
75
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
76
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
77
+ with:
78
+ version: "0.10.9"
79
+ - run: uv python install 3.12
80
+ - run: uv sync --python 3.12 --all-extras --locked
81
+ - run: uv build --python 3.12
82
+ - run: uv run twine check dist/*
83
+ - name: Validate release version
84
+ if: startsWith(github.ref, 'refs/tags/')
85
+ run: >-
86
+ uv run python -c
87
+ "import os; from ml4t_coursework import __version__;
88
+ tag=os.environ['GITHUB_REF_NAME'];
89
+ assert tag == f'v{__version__}', f'{tag} does not match v{__version__}'"
90
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
91
+ with:
92
+ name: dist
93
+ path: dist/
@@ -0,0 +1,39 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ jobs:
8
+ qualification:
9
+ name: Qualify Release
10
+ permissions:
11
+ contents: read
12
+ uses: ./.github/workflows/ci.yml
13
+
14
+ publish:
15
+ name: Publish to PyPI
16
+ runs-on: ubuntu-latest
17
+ needs: qualification
18
+ environment: pypi
19
+ permissions:
20
+ id-token: write
21
+ steps:
22
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
23
+ with:
24
+ name: dist
25
+ path: dist/
26
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
27
+
28
+ github-release:
29
+ name: Create GitHub Release
30
+ runs-on: ubuntu-latest
31
+ needs: publish
32
+ permissions:
33
+ contents: write
34
+ steps:
35
+ - name: Create GitHub release
36
+ env:
37
+ GH_TOKEN: ${{ github.token }}
38
+ GH_REPO: ${{ github.repository }}
39
+ run: gh release create "${GITHUB_REF_NAME}" --generate-notes --verify-tag
@@ -0,0 +1,8 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ build/
4
+ dist/
5
+ *.egg-info/
6
+ .venv/
7
+ .pytest_cache/
8
+ .ruff_cache/
@@ -0,0 +1,66 @@
1
+ # The course price panel
2
+
3
+ `etf_close.parquet`: split- and dividend-adjusted daily closing prices for 100 ETFs, 5,031
4
+ sessions from 2006-01-03 to 2025-12-31, a wide frame indexed by date with one column per symbol.
5
+ More than one ML4T course reads it, which is why the machinery that fetches and checks it lives
6
+ here rather than inside either of them.
7
+
8
+ **The file is not in any repository, and you fetch it once.** The price history is licensed and we
9
+ are not permitted to redistribute it. Everything else - the symbol list, the window, the code that
10
+ fetches it, and a fingerprint you can check your copy against - is in this package.
11
+
12
+ In a notebook:
13
+
14
+ ```python
15
+ from ml4t_coursework import data
16
+
17
+ data.download()
18
+ ```
19
+
20
+ It writes into your project folder and needs no path. From a shell, in a checkout with no Colab
21
+ around it:
22
+
23
+ ```bash
24
+ uv run --with "ml4t-coursework[data]" python -m ml4t_coursework.data --out data/etf_close.parquet
25
+ ```
26
+
27
+ Either way it takes a minute or two, then compares your copy against the packaged fingerprint and
28
+ tells you whether it is sound. Do this once. No unit notebook downloads anything, and none of them
29
+ will run until the file is there.
30
+
31
+ ## Will your numbers match ours?
32
+
33
+ Close enough that it does not matter, and here is the measurement rather than the reassurance.
34
+
35
+ Adjusted prices are revised. Every dividend and every split restates the entire history behind it,
36
+ so a series downloaded next year is not the same series of numbers as one downloaded today. That
37
+ sounds worse than it is: the revision is a *constant factor* applied to the whole series, and a
38
+ constant factor cancels out of every return. The courses compute on returns and on cross-sectional
39
+ ranks, so they do not see the factor at all.
40
+
41
+ Checked on 2026-08-17, a fresh download of all 100 symbols matched the copy the slides were
42
+ computed on, session for session. Fifty-eight symbols were identical; the rest differed by a single
43
+ scale factor, the largest of them 1.7%. The largest disagreement in any daily return anywhere in
44
+ the panel was 0.000003.
45
+
46
+ So expect your results to match the slides to three or four decimal places, and expect the fourth
47
+ to move as time passes and revisions accumulate. If a unit reports an information coefficient of
48
+ 0.0164 and you get 0.0161, nothing has gone wrong and there is nothing to debug. What the course is
49
+ teaching you to do is run the process and read the result correctly; a difference in the fourth
50
+ decimal is not a difference in the answer.
51
+
52
+ What would be worth investigating is a symbol missing entirely, a session count off by more than a
53
+ few, or a volatility more than 1% away from the fingerprint. Those are delistings, ticker reuse and
54
+ bad bars rather than revisions, and `data.check()` flags them by name.
55
+
56
+ ## Why there is no "exact reconstruction" file
57
+
58
+ We considered shipping the adjustment factors so you could transform a fresh download back into the
59
+ exact series behind the slides. Once the measurement above was in hand it stopped being worth
60
+ doing: the factor is one number per symbol, it cancels in returns, and applying it would change no
61
+ result you compute. A mechanism that reproduces the fourth decimal place of a number whose third
62
+ decimal is already immaterial is complexity without a payoff.
63
+
64
+ The fingerprint is deliberately one-way. It carries per-symbol session counts, dates, annualized
65
+ volatility and average daily move - enough to tell you your download is sound, not enough to
66
+ rebuild prices we are not allowed to hand you.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Stefan Jansen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.5
2
+ Name: ml4t-coursework
3
+ Version: 0.2.0
4
+ Summary: Project folder, component conformance checks and results log for the ML4T courses
5
+ Project-URL: Homepage, https://github.com/ml4t/coursework
6
+ Project-URL: Repository, https://github.com/ml4t/coursework
7
+ Project-URL: Issues, https://github.com/ml4t/coursework/issues
8
+ Project-URL: Changelog, https://github.com/ml4t/coursework/releases
9
+ Author: Stefan Jansen
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: backtesting,education,finance,machine-learning,trading
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Education
15
+ Classifier: Intended Audience :: Financial and Insurance Industry
16
+ Classifier: Intended Audience :: Science/Research
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Topic :: Office/Business :: Financial :: Investment
24
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.10
27
+ Requires-Dist: numpy>=1.24
28
+ Requires-Dist: pandas>=2.2
29
+ Requires-Dist: pyarrow>=14.0
30
+ Requires-Dist: scikit-learn>=1.3
31
+ Provides-Extra: data
32
+ Requires-Dist: yfinance>=0.2.40; extra == 'data'
33
+ Provides-Extra: dev
34
+ Requires-Dist: pytest>=8.0; extra == 'dev'
35
+ Requires-Dist: ruff==0.16.6; extra == 'dev'
36
+ Requires-Dist: twine==7.0.0; extra == 'dev'
37
+ Description-Content-Type: text/markdown
38
+
39
+ # ml4t-coursework
40
+
41
+ **Support package for the ML for Trading courses.** It is the plumbing their notebooks run on: the
42
+ student's project folder, the conformance checks that run when they save a piece of the pipeline
43
+ they have written, the results log the closing unit reads, and the report they submit. It is named
44
+ for what it holds rather than for a course, because more than one course installs it.
45
+
46
+ It is published here so that the first cell of a notebook on a free Colab runtime can be
47
+
48
+ ```python
49
+ !pip install -q "ml4t-coursework[data]"
50
+ ```
51
+
52
+ with no account, no credential and no repository to clone. That is the whole reason it is on PyPI.
53
+
54
+ It also owns the course price panel: the symbol list, the window, the one-time fetch and a one-way
55
+ fingerprint to check a download against. [The dataset note](https://github.com/ml4t/coursework/blob/main/DATASET.md)
56
+ explains what that panel is and why a fresh download differs from the slides in the fourth decimal
57
+ place. More than one course reads it, which is why the machinery is here rather than inside either
58
+ of them.
59
+
60
+ **This package is not the course.** The video, the reading, the exercises, the checks for
61
+ understanding and the market data all live elsewhere. What is here is useful mainly to someone
62
+ enrolled; it is installable by anyone, and it will not teach you anything on its own.
63
+
64
+ ## What it does
65
+
66
+ A notebook opens by saying which course it belongs to. That is what decides the project folder on
67
+ the student's Drive and which stage names their results rows may carry, so two courses on one Drive
68
+ never write over each other:
69
+
70
+ ```python
71
+ import ml4t_coursework as mlc
72
+ mlc.use("foundations")
73
+
74
+ from ml4t_coursework import save_component, load_component, append_result, report
75
+ ```
76
+
77
+ - **`save_component(name, obj)`** runs the component's contract test, prints what passed and what
78
+ did not, stamps the verdict, and writes the student's own source to their project folder.
79
+ - **`load_component(name)`** returns their implementation when it is conformant and the shipped
80
+ reference when it is not, and always says which. A wrong Part 4 does not block Part 7.
81
+ - **`append_result(row)`** appends one row per pipeline run. The final unit reads three of them
82
+ against each other and they are written weeks apart, so what persists is the numbers.
83
+ - **`report(answers)`** writes the submission report: every component's verdict, the results rows,
84
+ and the written answers.
85
+
86
+ `status()` shows where every component stands; `contract(name).describe()` shows what any one of
87
+ them has to satisfy; `catalog()` lists them all.
88
+
89
+ A course adds its own components and its own stage names without a change here:
90
+
91
+ ```python
92
+ from ml4t_coursework import Course, register_course, add_source
93
+
94
+ register_course(Course(key="...", title="...", folder="ml4t-...", env_var="ML4T_..._HOME",
95
+ stages=("...",), terminal_stages=("...",)))
96
+ add_source("your_package.components") # every module in it registers one contract
97
+ ```
98
+
99
+ ## How the checks work, and what they deliberately do not do
100
+
101
+ Each component carries a contract asserting **properties, never equality with a reference output**.
102
+ Two correct fold splitters legitimately differ, and an equality check would fail correct work and
103
+ teach the student to copy. Every contract runs the same four gating checks - interface, a leakage
104
+ probe, determinism, and the component's own domain invariants - plus a reference delta that is
105
+ reported and never enforced.
106
+
107
+ A component is validated on **its source re-executed in an empty namespace**, not on the object in
108
+ memory, because what a later notebook loads on a cold session is the file. Something that only
109
+ works because of another cell in the same session fails at save time, with a message naming the
110
+ missing symbol.
111
+
112
+ Reference implementations ship with the package. That is by design rather than an oversight:
113
+ the fallback is what stops one wrong component from blocking the rest of the course, so there is
114
+ nothing here that could be leaked.
115
+
116
+ ## Data
117
+
118
+ The course's price panel is not distributed with this package and is not ours to redistribute. What
119
+ ships is everything else - the symbol list, the window, the fetch, and a one-way fingerprint to
120
+ check a download against:
121
+
122
+ ```python
123
+ from ml4t_coursework import data
124
+ data.download() # once, into the project folder
125
+ data.load() # what every notebook reads
126
+ ```
127
+
128
+ ## Licence
129
+
130
+ MIT, in line with the other `ml4t-*` libraries. See `LICENSE`.
@@ -0,0 +1,92 @@
1
+ # ml4t-coursework
2
+
3
+ **Support package for the ML for Trading courses.** It is the plumbing their notebooks run on: the
4
+ student's project folder, the conformance checks that run when they save a piece of the pipeline
5
+ they have written, the results log the closing unit reads, and the report they submit. It is named
6
+ for what it holds rather than for a course, because more than one course installs it.
7
+
8
+ It is published here so that the first cell of a notebook on a free Colab runtime can be
9
+
10
+ ```python
11
+ !pip install -q "ml4t-coursework[data]"
12
+ ```
13
+
14
+ with no account, no credential and no repository to clone. That is the whole reason it is on PyPI.
15
+
16
+ It also owns the course price panel: the symbol list, the window, the one-time fetch and a one-way
17
+ fingerprint to check a download against. [The dataset note](https://github.com/ml4t/coursework/blob/main/DATASET.md)
18
+ explains what that panel is and why a fresh download differs from the slides in the fourth decimal
19
+ place. More than one course reads it, which is why the machinery is here rather than inside either
20
+ of them.
21
+
22
+ **This package is not the course.** The video, the reading, the exercises, the checks for
23
+ understanding and the market data all live elsewhere. What is here is useful mainly to someone
24
+ enrolled; it is installable by anyone, and it will not teach you anything on its own.
25
+
26
+ ## What it does
27
+
28
+ A notebook opens by saying which course it belongs to. That is what decides the project folder on
29
+ the student's Drive and which stage names their results rows may carry, so two courses on one Drive
30
+ never write over each other:
31
+
32
+ ```python
33
+ import ml4t_coursework as mlc
34
+ mlc.use("foundations")
35
+
36
+ from ml4t_coursework import save_component, load_component, append_result, report
37
+ ```
38
+
39
+ - **`save_component(name, obj)`** runs the component's contract test, prints what passed and what
40
+ did not, stamps the verdict, and writes the student's own source to their project folder.
41
+ - **`load_component(name)`** returns their implementation when it is conformant and the shipped
42
+ reference when it is not, and always says which. A wrong Part 4 does not block Part 7.
43
+ - **`append_result(row)`** appends one row per pipeline run. The final unit reads three of them
44
+ against each other and they are written weeks apart, so what persists is the numbers.
45
+ - **`report(answers)`** writes the submission report: every component's verdict, the results rows,
46
+ and the written answers.
47
+
48
+ `status()` shows where every component stands; `contract(name).describe()` shows what any one of
49
+ them has to satisfy; `catalog()` lists them all.
50
+
51
+ A course adds its own components and its own stage names without a change here:
52
+
53
+ ```python
54
+ from ml4t_coursework import Course, register_course, add_source
55
+
56
+ register_course(Course(key="...", title="...", folder="ml4t-...", env_var="ML4T_..._HOME",
57
+ stages=("...",), terminal_stages=("...",)))
58
+ add_source("your_package.components") # every module in it registers one contract
59
+ ```
60
+
61
+ ## How the checks work, and what they deliberately do not do
62
+
63
+ Each component carries a contract asserting **properties, never equality with a reference output**.
64
+ Two correct fold splitters legitimately differ, and an equality check would fail correct work and
65
+ teach the student to copy. Every contract runs the same four gating checks - interface, a leakage
66
+ probe, determinism, and the component's own domain invariants - plus a reference delta that is
67
+ reported and never enforced.
68
+
69
+ A component is validated on **its source re-executed in an empty namespace**, not on the object in
70
+ memory, because what a later notebook loads on a cold session is the file. Something that only
71
+ works because of another cell in the same session fails at save time, with a message naming the
72
+ missing symbol.
73
+
74
+ Reference implementations ship with the package. That is by design rather than an oversight:
75
+ the fallback is what stops one wrong component from blocking the rest of the course, so there is
76
+ nothing here that could be leaked.
77
+
78
+ ## Data
79
+
80
+ The course's price panel is not distributed with this package and is not ours to redistribute. What
81
+ ships is everything else - the symbol list, the window, the fetch, and a one-way fingerprint to
82
+ check a download against:
83
+
84
+ ```python
85
+ from ml4t_coursework import data
86
+ data.download() # once, into the project folder
87
+ data.load() # what every notebook reads
88
+ ```
89
+
90
+ ## Licence
91
+
92
+ MIT, in line with the other `ml4t-*` libraries. See `LICENSE`.
@@ -0,0 +1,52 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "ml4t-coursework"
7
+ version = "0.2.0"
8
+ description = "Project folder, component conformance checks and results log for the ML4T courses"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Stefan Jansen" }]
13
+ keywords = ["trading", "machine-learning", "finance", "backtesting", "education"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Education",
17
+ "Intended Audience :: Financial and Insurance Industry",
18
+ "Intended Audience :: Science/Research",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Topic :: Office/Business :: Financial :: Investment",
26
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
27
+ "Typing :: Typed",
28
+ ]
29
+ dependencies = ["numpy>=1.24", "pandas>=2.2", "pyarrow>=14.0", "scikit-learn>=1.3"]
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/ml4t/coursework"
33
+ Repository = "https://github.com/ml4t/coursework"
34
+ Issues = "https://github.com/ml4t/coursework/issues"
35
+ Changelog = "https://github.com/ml4t/coursework/releases"
36
+
37
+ [project.optional-dependencies]
38
+ dev = ["pytest>=8.0", "ruff==0.16.6", "twine==7.0.0"]
39
+ data = ["yfinance>=0.2.40"]
40
+
41
+ [tool.hatch.build.targets.wheel]
42
+ packages = ["src/ml4t_coursework"]
43
+ artifacts = ["src/ml4t_coursework/data/*.csv", "src/ml4t_coursework/py.typed"]
44
+
45
+ [tool.hatch.build.targets.sdist]
46
+ exclude = ["/.workspace", "/publish.sh"]
47
+
48
+ [tool.ruff]
49
+ line-length = 100
50
+
51
+ [tool.pytest.ini_options]
52
+ testpaths = ["tests"]
@@ -0,0 +1,29 @@
1
+ """The plumbing every ML4T course notebook runs on.
2
+
3
+ !pip install -q ml4t-coursework
4
+ import ml4t_coursework as mlc
5
+ mlc.use("foundations")
6
+
7
+ from ml4t_coursework import save_component, load_component, append_result, report
8
+
9
+ The package holds the student's project folder, the components they write and the contracts those
10
+ are checked against, their results log and their submission report. It is named for what it does
11
+ rather than for a course, because more than one course installs it.
12
+ """
13
+
14
+ __version__ = "0.2.0"
15
+
16
+ from .components import catalog, load_component, save_component, source_of, status
17
+ from .contracts import add_source
18
+ from .contracts import get as contract
19
+ from .course import Course, active_course, register_course, use
20
+ from .project import describe, home, setup
21
+ from .report import report
22
+ from .results import append_result, latest, results
23
+
24
+ __all__ = [
25
+ "save_component", "load_component", "append_result", "report",
26
+ "setup", "home", "describe", "status", "catalog", "contract", "results", "latest",
27
+ "source_of", "use", "Course", "register_course", "active_course", "add_source",
28
+ "__version__",
29
+ ]