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.
- ml4t_coursework-0.2.0/.github/workflows/ci.yml +93 -0
- ml4t_coursework-0.2.0/.github/workflows/release.yml +39 -0
- ml4t_coursework-0.2.0/.gitignore +8 -0
- ml4t_coursework-0.2.0/DATASET.md +66 -0
- ml4t_coursework-0.2.0/LICENSE +21 -0
- ml4t_coursework-0.2.0/PKG-INFO +130 -0
- ml4t_coursework-0.2.0/README.md +92 -0
- ml4t_coursework-0.2.0/pyproject.toml +52 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/__init__.py +29 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/checks.py +221 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/components.py +204 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/contracts.py +101 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/course.py +86 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/data/__init__.py +129 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/data/__main__.py +30 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/data/etf_close_fingerprint.csv +101 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/fixtures.py +67 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/project.py +87 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/py.typed +0 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/reference/__init__.py +6 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/reference/_config.py +51 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/reference/_shared.py +36 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/reference/allocator.py +143 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/reference/availability_lag.py +88 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/reference/backtest_config.py +57 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/reference/baseline_strategy.py +117 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/reference/cost_model.py +119 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/reference/data_panel.py +96 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/reference/exit_rule.py +151 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/reference/features.py +115 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/reference/fold_splitter.py +116 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/reference/holdout_split.py +85 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/reference/labeler.py +127 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/reference/model_gbm.py +118 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/reference/model_linear.py +128 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/reference/objective.py +41 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/reference/preprocessor.py +150 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/reference/quality_gates.py +125 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/reference/signal.py +105 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/reference/strategy_spec.py +42 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/reference/task_form.py +102 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/reference/universe.py +100 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/report.py +71 -0
- ml4t_coursework-0.2.0/src/ml4t_coursework/results.py +82 -0
- ml4t_coursework-0.2.0/tests/conftest.py +14 -0
- ml4t_coursework-0.2.0/tests/test_helper.py +200 -0
- ml4t_coursework-0.2.0/tests/test_multi_course.py +116 -0
- ml4t_coursework-0.2.0/tests/test_references.py +267 -0
- 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,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
|
+
]
|