backtest2live 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.
- backtest2live-0.2.0/.claude-plugin/marketplace.json +18 -0
- backtest2live-0.2.0/.github/workflows/ci.yml +62 -0
- backtest2live-0.2.0/.github/workflows/pr-title.yml +38 -0
- backtest2live-0.2.0/.github/workflows/release.yml +93 -0
- backtest2live-0.2.0/.github/workflows/testpypi.yml +77 -0
- backtest2live-0.2.0/.gitignore +17 -0
- backtest2live-0.2.0/.releaserc.json +31 -0
- backtest2live-0.2.0/CHANGELOG.md +7 -0
- backtest2live-0.2.0/LICENSE +21 -0
- backtest2live-0.2.0/PKG-INFO +218 -0
- backtest2live-0.2.0/README.md +193 -0
- backtest2live-0.2.0/RELEASING.md +25 -0
- backtest2live-0.2.0/ROADMAP.md +36 -0
- backtest2live-0.2.0/examples/orders_btcusdt_2024-03-30.csv +2879 -0
- backtest2live-0.2.0/examples/results_converged.csv +17 -0
- backtest2live-0.2.0/examples/results_iterate.csv +17 -0
- backtest2live-0.2.0/plugins/backtest2live/.claude-plugin/plugin.json +11 -0
- backtest2live-0.2.0/plugins/backtest2live/skills/convergence/SKILL.md +213 -0
- backtest2live-0.2.0/plugins/backtest2live/skills/convergence/examples/results_converged.csv +17 -0
- backtest2live-0.2.0/plugins/backtest2live/skills/convergence/examples/results_iterate.csv +17 -0
- backtest2live-0.2.0/plugins/backtest2live/skills/fill-gap/SKILL.md +43 -0
- backtest2live-0.2.0/pyproject.toml +41 -0
- backtest2live-0.2.0/scripts/release_version.py +56 -0
- backtest2live-0.2.0/src/backtest2live/__init__.py +12 -0
- backtest2live-0.2.0/src/backtest2live/__main__.py +5 -0
- backtest2live-0.2.0/src/backtest2live/cli.py +68 -0
- backtest2live-0.2.0/src/backtest2live/convergence.py +567 -0
- backtest2live-0.2.0/src/backtest2live/fillgap/__init__.py +57 -0
- backtest2live-0.2.0/src/backtest2live/fillgap/data.py +289 -0
- backtest2live-0.2.0/src/backtest2live/fillgap/engine.py +169 -0
- backtest2live-0.2.0/src/backtest2live/fillgap/report.py +101 -0
- backtest2live-0.2.0/src/backtest2live/fillgap/sim.py +192 -0
- backtest2live-0.2.0/tests/conftest.py +20 -0
- backtest2live-0.2.0/tests/fixtures/results_drawdown.csv +7 -0
- backtest2live-0.2.0/tests/fixtures/results_kill.csv +11 -0
- backtest2live-0.2.0/tests/golden/results_converged.md +39 -0
- backtest2live-0.2.0/tests/golden/results_drawdown.md +33 -0
- backtest2live-0.2.0/tests/golden/results_iterate.md +41 -0
- backtest2live-0.2.0/tests/golden/results_kill.md +41 -0
- backtest2live-0.2.0/tests/test_cli.py +114 -0
- backtest2live-0.2.0/tests/test_convergence.py +152 -0
- backtest2live-0.2.0/tests/test_fillgap.py +307 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
|
|
3
|
+
"name": "martianmobile",
|
|
4
|
+
"description": "Martian Mobile's public Claude Code plugins — quant research and trading-ops tooling, sanitized from internal workflows.",
|
|
5
|
+
"owner": {
|
|
6
|
+
"name": "Martian Mobile",
|
|
7
|
+
"email": "martin@martianmobile.com"
|
|
8
|
+
},
|
|
9
|
+
"plugins": [
|
|
10
|
+
{
|
|
11
|
+
"name": "backtest2live",
|
|
12
|
+
"description": "Check a backtest against what live execution would do, run locally. Convergence across a variant sweep (CONVERGED / ITERATE / KILL) with the numbers behind the verdict; fill-gap and more evaluators follow. Wraps the backtest2live CLI.",
|
|
13
|
+
"source": "./plugins/backtest2live",
|
|
14
|
+
"category": "data",
|
|
15
|
+
"homepage": "https://github.com/martianmobile/backtest2live"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
test:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
strategy:
|
|
15
|
+
fail-fast: false
|
|
16
|
+
matrix:
|
|
17
|
+
python: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v6
|
|
20
|
+
- uses: actions/setup-python@v6
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python }}
|
|
23
|
+
- run: pip install -e '.[test,data]'
|
|
24
|
+
- run: pytest -q
|
|
25
|
+
|
|
26
|
+
core-only:
|
|
27
|
+
# convergence must run with no third-party packages; fill-gap tests skip
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v6
|
|
31
|
+
- uses: actions/setup-python@v6
|
|
32
|
+
with:
|
|
33
|
+
python-version: "3.13"
|
|
34
|
+
- run: pip install -e '.[test]'
|
|
35
|
+
- run: pytest -q
|
|
36
|
+
|
|
37
|
+
min-deps:
|
|
38
|
+
# the lowest numpy/pandas the data extra allows
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v6
|
|
42
|
+
- uses: actions/setup-python@v6
|
|
43
|
+
with:
|
|
44
|
+
python-version: "3.9"
|
|
45
|
+
- run: pip install -e '.[test]' 'numpy==1.23.5' 'pandas==1.5.3'
|
|
46
|
+
- run: pytest -q
|
|
47
|
+
|
|
48
|
+
package:
|
|
49
|
+
runs-on: ubuntu-latest
|
|
50
|
+
steps:
|
|
51
|
+
- uses: actions/checkout@v6
|
|
52
|
+
- uses: actions/setup-python@v6
|
|
53
|
+
with:
|
|
54
|
+
python-version: "3.13"
|
|
55
|
+
- run: pip install build twine
|
|
56
|
+
- run: python -m build
|
|
57
|
+
- run: twine check --strict dist/*
|
|
58
|
+
- name: Wheel installs clean and runs
|
|
59
|
+
run: |
|
|
60
|
+
python -m venv /tmp/wheel && /tmp/wheel/bin/pip install dist/*.whl
|
|
61
|
+
/tmp/wheel/bin/bt2live --version
|
|
62
|
+
/tmp/wheel/bin/bt2live convergence examples/results_converged.csv --json > /dev/null
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: PR title
|
|
2
|
+
|
|
3
|
+
# The squash-merge commit takes the PR title, and semantic-release reads it:
|
|
4
|
+
# feat -> minor, fix -> patch. No "!" or BREAKING CHANGE before 1.0.
|
|
5
|
+
on:
|
|
6
|
+
pull_request_target:
|
|
7
|
+
types: [opened, edited, synchronize, reopened]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
pull-requests: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
conventional:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: amannn/action-semantic-pull-request@v6
|
|
17
|
+
env:
|
|
18
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
19
|
+
with:
|
|
20
|
+
types: |
|
|
21
|
+
feat
|
|
22
|
+
fix
|
|
23
|
+
perf
|
|
24
|
+
docs
|
|
25
|
+
test
|
|
26
|
+
ci
|
|
27
|
+
build
|
|
28
|
+
refactor
|
|
29
|
+
chore
|
|
30
|
+
revert
|
|
31
|
+
- name: No breaking-change marker before 1.0
|
|
32
|
+
env:
|
|
33
|
+
TITLE: ${{ github.event.pull_request.title }}
|
|
34
|
+
run: |
|
|
35
|
+
if printf '%s' "$TITLE" | grep -Eq '^[a-z]+(\([^)]*\))?!:'; then
|
|
36
|
+
echo "::error::'!' would release 1.0.0. Before 1.0, describe the break in the body under a feat: title."
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Closing a milestone ships it. Commits decide the version (feat -> minor,
|
|
4
|
+
# fix -> patch); the milestone title must match, or nothing is tagged.
|
|
5
|
+
on:
|
|
6
|
+
milestone:
|
|
7
|
+
types: [closed]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
inputs:
|
|
10
|
+
version:
|
|
11
|
+
description: "Version this release must compute, as the milestone title (e.g. v0.2)"
|
|
12
|
+
required: true
|
|
13
|
+
|
|
14
|
+
concurrency:
|
|
15
|
+
group: release
|
|
16
|
+
cancel-in-progress: false
|
|
17
|
+
|
|
18
|
+
permissions:
|
|
19
|
+
contents: read
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
release:
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
outputs:
|
|
25
|
+
published: ${{ steps.semantic.outputs.new_release_published }}
|
|
26
|
+
version: ${{ steps.semantic.outputs.new_release_version }}
|
|
27
|
+
steps:
|
|
28
|
+
- name: Generate app token
|
|
29
|
+
id: app-token
|
|
30
|
+
uses: actions/create-github-app-token@v3
|
|
31
|
+
with:
|
|
32
|
+
client-id: ${{ vars.RELEASE_BOT_CLIENT_ID }}
|
|
33
|
+
private-key: ${{ secrets.RELEASE_BOT_PRIVATE_KEY }}
|
|
34
|
+
# no owner/repositories: the token covers this repository only
|
|
35
|
+
|
|
36
|
+
- uses: actions/checkout@v6
|
|
37
|
+
with:
|
|
38
|
+
ref: main
|
|
39
|
+
fetch-depth: 0
|
|
40
|
+
persist-credentials: true
|
|
41
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
42
|
+
|
|
43
|
+
- name: Configure git author
|
|
44
|
+
run: |
|
|
45
|
+
git config user.name "martian-mobile-release-bot[bot]"
|
|
46
|
+
git config user.email "284492098+martian-mobile-release-bot[bot]@users.noreply.github.com"
|
|
47
|
+
|
|
48
|
+
- uses: actions/setup-python@v6
|
|
49
|
+
with:
|
|
50
|
+
python-version: "3.13"
|
|
51
|
+
- run: pip install build
|
|
52
|
+
|
|
53
|
+
- name: Semantic Release
|
|
54
|
+
id: semantic
|
|
55
|
+
uses: cycjimmy/semantic-release-action@b12c8f6015dc215fe37bc154d4ad456dd3833c90 # v6.0.0, pinned: runs with the write token
|
|
56
|
+
env:
|
|
57
|
+
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
58
|
+
EXPECTED_VERSION: ${{ github.event.milestone.title || inputs.version }}
|
|
59
|
+
with:
|
|
60
|
+
semantic_version: 25.0.9
|
|
61
|
+
extra_plugins: |
|
|
62
|
+
@semantic-release/git@11
|
|
63
|
+
@semantic-release/changelog@7
|
|
64
|
+
@semantic-release/exec@7
|
|
65
|
+
|
|
66
|
+
# A closed milestone that ships nothing is a stranded release, not a no-op.
|
|
67
|
+
- name: Fail when nothing was released
|
|
68
|
+
if: steps.semantic.outputs.new_release_published != 'true'
|
|
69
|
+
run: |
|
|
70
|
+
echo "::error::No release: no feat/fix commits since the last tag, or the version check failed above."
|
|
71
|
+
exit 1
|
|
72
|
+
|
|
73
|
+
- uses: actions/upload-artifact@v7
|
|
74
|
+
with:
|
|
75
|
+
name: dist
|
|
76
|
+
path: dist/
|
|
77
|
+
if-no-files-found: error
|
|
78
|
+
|
|
79
|
+
pypi:
|
|
80
|
+
needs: release
|
|
81
|
+
if: needs.release.outputs.published == 'true'
|
|
82
|
+
runs-on: ubuntu-latest
|
|
83
|
+
environment:
|
|
84
|
+
name: pypi
|
|
85
|
+
url: https://pypi.org/project/backtest2live/${{ needs.release.outputs.version }}/
|
|
86
|
+
permissions:
|
|
87
|
+
id-token: write # trusted publishing: no token stored
|
|
88
|
+
steps:
|
|
89
|
+
- uses: actions/download-artifact@v8
|
|
90
|
+
with:
|
|
91
|
+
name: dist
|
|
92
|
+
path: dist/
|
|
93
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
name: TestPyPI rehearsal
|
|
2
|
+
|
|
3
|
+
# Manual dry run of the PyPI path: builds <base>.dev<run>, publishes it to
|
|
4
|
+
# TestPyPI with trusted publishing, then installs it and runs the CLI.
|
|
5
|
+
on:
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
base:
|
|
9
|
+
description: "Base version for the dev build"
|
|
10
|
+
default: "0.2.0"
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
build:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
outputs:
|
|
19
|
+
version: ${{ steps.v.outputs.version }}
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v6
|
|
22
|
+
- uses: actions/setup-python@v6
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.13"
|
|
25
|
+
- id: v
|
|
26
|
+
env:
|
|
27
|
+
BASE: ${{ inputs.base }}
|
|
28
|
+
run: |
|
|
29
|
+
[[ "$BASE" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "::error::base must be X.Y.Z"; exit 1; }
|
|
30
|
+
echo "version=${BASE}.dev${GITHUB_RUN_NUMBER}" >> "$GITHUB_OUTPUT"
|
|
31
|
+
- env:
|
|
32
|
+
VERSION: ${{ steps.v.outputs.version }}
|
|
33
|
+
run: python scripts/release_version.py set "$VERSION"
|
|
34
|
+
- run: pip install build && python -m build
|
|
35
|
+
- uses: actions/upload-artifact@v7
|
|
36
|
+
with:
|
|
37
|
+
name: dist
|
|
38
|
+
path: dist/
|
|
39
|
+
|
|
40
|
+
publish:
|
|
41
|
+
needs: build
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
environment:
|
|
44
|
+
name: testpypi
|
|
45
|
+
url: https://test.pypi.org/project/backtest2live/${{ needs.build.outputs.version }}/
|
|
46
|
+
permissions:
|
|
47
|
+
id-token: write
|
|
48
|
+
steps:
|
|
49
|
+
- uses: actions/download-artifact@v8
|
|
50
|
+
with:
|
|
51
|
+
name: dist
|
|
52
|
+
path: dist/
|
|
53
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
54
|
+
with:
|
|
55
|
+
repository-url: https://test.pypi.org/legacy/
|
|
56
|
+
|
|
57
|
+
verify:
|
|
58
|
+
needs: [build, publish]
|
|
59
|
+
runs-on: ubuntu-latest
|
|
60
|
+
steps:
|
|
61
|
+
- uses: actions/checkout@v6
|
|
62
|
+
- uses: actions/setup-python@v6
|
|
63
|
+
with:
|
|
64
|
+
python-version: "3.13"
|
|
65
|
+
- name: Install from TestPyPI (dependencies from PyPI)
|
|
66
|
+
env:
|
|
67
|
+
VERSION: ${{ needs.build.outputs.version }}
|
|
68
|
+
run: |
|
|
69
|
+
for i in 1 2 3 4 5 6; do
|
|
70
|
+
if pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ \
|
|
71
|
+
"backtest2live[data]==$VERSION"; then exit 0; fi
|
|
72
|
+
sleep 20 # the index can lag the upload
|
|
73
|
+
done
|
|
74
|
+
echo "::error::backtest2live==$VERSION never became installable from TestPyPI"
|
|
75
|
+
exit 1
|
|
76
|
+
- run: bt2live --version
|
|
77
|
+
- run: bt2live convergence examples/results_converged.csv --json
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"branches": ["main"],
|
|
3
|
+
"tagFormat": "v${version}",
|
|
4
|
+
"plugins": [
|
|
5
|
+
"@semantic-release/commit-analyzer",
|
|
6
|
+
"@semantic-release/release-notes-generator",
|
|
7
|
+
"@semantic-release/changelog",
|
|
8
|
+
[
|
|
9
|
+
"@semantic-release/exec",
|
|
10
|
+
{
|
|
11
|
+
"verifyReleaseCmd": "python scripts/release_version.py check ${nextRelease.version}",
|
|
12
|
+
"prepareCmd": "python scripts/release_version.py set ${nextRelease.version} && python -m build"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
[
|
|
16
|
+
"@semantic-release/git",
|
|
17
|
+
{
|
|
18
|
+
"assets": ["CHANGELOG.md", "pyproject.toml", "plugins/backtest2live/.claude-plugin/plugin.json"],
|
|
19
|
+
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
[
|
|
23
|
+
"@semantic-release/github",
|
|
24
|
+
{
|
|
25
|
+
"assets": [{ "path": "dist/*" }],
|
|
26
|
+
"successCommentCondition": false,
|
|
27
|
+
"failCommentCondition": false
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
]
|
|
31
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# [0.2.0](https://github.com/martianmobile/backtest2live/compare/v0.1.0...v0.2.0) (2026-10-02)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* backtest2live package + bt2live CLI, tests and CI ([#12](https://github.com/martianmobile/backtest2live/issues/12)) ([f6b970b](https://github.com/martianmobile/backtest2live/commit/f6b970b44d5d711f2f7bf9197abab5776a4611aa)), closes [#11](https://github.com/martianmobile/backtest2live/issues/11)
|
|
7
|
+
* fill-gap evaluator — backtest fills vs a queue proxy ([#13](https://github.com/martianmobile/backtest2live/issues/13)) ([a3663c4](https://github.com/martianmobile/backtest2live/commit/a3663c4ba42f7979bf534d9fddd8f31a92e2a5f0)), closes [throu#prints](https://github.com/throu/issues/prints) [throu#fills](https://github.com/throu/issues/fills)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Martian Mobile
|
|
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,218 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: backtest2live
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Check a backtest against what live execution would do: convergence, fill-gap, and more, run locally.
|
|
5
|
+
Project-URL: Homepage, https://martianmobile.com
|
|
6
|
+
Project-URL: Source, https://github.com/martianmobile/backtest2live
|
|
7
|
+
Project-URL: Issues, https://github.com/martianmobile/backtest2live/issues
|
|
8
|
+
Author-email: Martian Mobile <martin@martianmobile.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: backtest,execution,fill-rate,overfitting,quant,trading
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Provides-Extra: data
|
|
20
|
+
Requires-Dist: numpy>=1.23; extra == 'data'
|
|
21
|
+
Requires-Dist: pandas>=1.5; extra == 'data'
|
|
22
|
+
Provides-Extra: test
|
|
23
|
+
Requires-Dist: pytest>=7; extra == 'test'
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# backtest2live
|
|
27
|
+
|
|
28
|
+
[](https://github.com/martianmobile/backtest2live/actions/workflows/ci.yml)
|
|
29
|
+
|
|
30
|
+
**Check a backtest against what live execution would do.** Evaluators run locally on your own files and public data, and each one returns a verdict with the numbers behind it.
|
|
31
|
+
|
|
32
|
+
Use it three ways: as a Python CLI (`bt2live`), as a library, or as a Claude Code plugin where the agent runs the CLI and interprets the verdict.
|
|
33
|
+
|
|
34
|
+
This is the public, MIT-licensed version of checks used in live crypto trading research at [Martian Mobile](https://martianmobile.com).
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Evaluators
|
|
39
|
+
|
|
40
|
+
| Command | Status | What it does |
|
|
41
|
+
|---------|--------|--------------|
|
|
42
|
+
| **`bt2live convergence`** | shipped | Read a variant parameter sweep → measure dispersion, IS/OOS rank stability, and parameter-plateau structure → verdict: **CONVERGED / ITERATE / KILL**. |
|
|
43
|
+
| **`bt2live fill-gap`** | shipped | Re-run your backtest's resting orders under a queue proxy on top-of-book and trades → your fill rate vs the proxy's: **CONSISTENT / OVERSTATED**. |
|
|
44
|
+
|
|
45
|
+
Roadmap: [ROADMAP.md](ROADMAP.md) and the [milestones](https://github.com/martianmobile/backtest2live/milestones).
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install backtest2live
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The core has no runtime dependencies (Python 3.9+). `fill-gap` needs numpy and pandas:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install 'backtest2live[data]'
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Claude Code plugin.** This repo is also a plugin marketplace. From inside Claude Code:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
/plugin marketplace add martianmobile/backtest2live
|
|
65
|
+
/plugin install backtest2live@martianmobile
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Then ask *"are these variants converged?"* or run `/convergence results.csv --metric sharpe_oos`. The skill installs the CLI if it is missing.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## `fill-gap`: do your backtest's passive fills survive a queue?
|
|
73
|
+
|
|
74
|
+
A backtest that fills a resting order whenever price touches it counts fills a live order would never get: the order sits behind the queue already displayed at that price. `fill-gap` re-runs each resting order in your order log against top-of-book and trade prints, and grants a fill only when:
|
|
75
|
+
|
|
76
|
+
- the volume printed at exactly your price, after you joined, covers the queue ahead of you plus your own size, or
|
|
77
|
+
- a trade prints through your price, which means the level was cleared.
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
bt2live fill-gap examples/orders_btcusdt_2024-03-30.csv --venue binance-um --pair BTCUSDT
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
## Verdict: OVERSTATED
|
|
85
|
+
|
|
86
|
+
**Your backtest fills 95.2% of its resting orders; the queue proxy fills 66.2%.
|
|
87
|
+
30.4% of the counted fills (834 of 2,740) are not granted on this data.**
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The report continues with fill rates by rule, median time to fill, misses by volatility tercile and by UTC hour, and P&L on missed fills if your log has a `pnl` column. `--json` gives the same result machine-readable. Exit code: `0` CONSISTENT, `1` OVERSTATED (more than `--tolerance`, default 10%, of your fills not granted), `3` error.
|
|
91
|
+
|
|
92
|
+
### Your order log
|
|
93
|
+
|
|
94
|
+
CSV, one row per order. Column names are matched case-insensitively:
|
|
95
|
+
|
|
96
|
+
| Role | Accepted names | Required |
|
|
97
|
+
|------|----------------|----------|
|
|
98
|
+
| time | `ts`, `timestamp`, `time`, `datetime`, `created_at` | yes (epoch s/ms/µs/ns or ISO-8601, UTC) |
|
|
99
|
+
| side | `side` (`buy`/`sell`) | yes |
|
|
100
|
+
| price | `price`, `px`, `limit_price` | yes |
|
|
101
|
+
| size | `size`, `qty`, `quantity`, `amount` | yes (base units) |
|
|
102
|
+
| backtest's fill | `filled` (true/false, 1/0) or `fill_qty` | no: without it, the touch rule is the baseline |
|
|
103
|
+
| end of life | `end_ts`, `cancel_ts`, `expire_ts` | no: default `--max-rest 60` seconds |
|
|
104
|
+
| P&L | `pnl` | no |
|
|
105
|
+
| id | `order_id` | no |
|
|
106
|
+
|
|
107
|
+
Orders marketable at arrival are taker orders and are skipped. `--latency-ms` delays every order's arrival at the book.
|
|
108
|
+
|
|
109
|
+
### Market data
|
|
110
|
+
|
|
111
|
+
- **`--venue binance-um --pair BTCUSDT`** downloads Binance's public USDT-M futures archive (`bookTicker` + `aggTrades`) for the days your orders cover, checks the published SHA-256, and caches it under `~/.cache/backtest2live` (`BT2LIVE_CACHE` overrides). About 100 MB per day for BTCUSDT. Binance stopped publishing `bookTicker` after **2024-03-30**, so this source covers orders up to that day.
|
|
112
|
+
- **`--book l1.csv --trades trades.csv`** uses your own recorded data, any venue and any date. Book: `ts, bid_px, bid_qty, ask_px, ask_qty` (every change of the best bid/ask). Trades: `ts, price, qty` plus `is_buyer_maker` (true when a seller hit the bid) or the taker's `side`.
|
|
113
|
+
|
|
114
|
+
### What the proxy cannot see
|
|
115
|
+
|
|
116
|
+
The touch rule is an upper bound: a resting order fills only if something trades at or through its price. The proxy is an estimate below it. Hidden size, your latency and your own impact make it too generous. Cancels ahead of you, and a queue position read from the size shown when the price reached you, make it too strict. How that nets out depends on venue, size and regime. Only your own live fills settle it; [Martian Mobile](https://martianmobile.com/fill-autopsy?utm_source=github&utm_medium=readme&utm_campaign=fill-gap) runs that check against live fills.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## `convergence`: has your variant sweep converged?
|
|
121
|
+
|
|
122
|
+
Given one row per variant (parameters + metrics), it measures:
|
|
123
|
+
|
|
124
|
+
- **Top-K dispersion** — are the best variants clustered, or is one a lucky outlier?
|
|
125
|
+
- **IS↔OOS rank stability** — does the in-sample ordering survive out-of-sample? (Spearman)
|
|
126
|
+
- **Parameter-space structure** — do winners sit on a plateau, or are they isolated points (overfit)? Is the best variant pinned to the edge of the swept range?
|
|
127
|
+
- **Sample sufficiency** — are any top variants below a trade-count floor?
|
|
128
|
+
|
|
129
|
+
…then returns a verdict:
|
|
130
|
+
|
|
131
|
+
| Verdict | Meaning |
|
|
132
|
+
|---------|---------|
|
|
133
|
+
| **CONVERGED** | Tight, OOS-stable, plateau region. Ship the representative variant. |
|
|
134
|
+
| **ITERATE** | Some criteria unmet (edge peak, weak stability, high dispersion). Suggested next sweep included. |
|
|
135
|
+
| **KILL** | No stable edge — IS doesn't survive OOS, or best ≈ median. Abandon or rethink. |
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
### Usage
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
bt2live convergence examples/results_converged.csv --metric sharpe_oos
|
|
143
|
+
bt2live convergence examples/results_converged.csv --json # machine-readable verdict
|
|
144
|
+
python -m backtest2live convergence results.csv # same, without the script shim
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Exit code encodes the verdict: `0` CONVERGED · `1` ITERATE · `2` KILL · `3` error (bad input or usage), so a CI job can gate on it.
|
|
148
|
+
|
|
149
|
+
### Input format
|
|
150
|
+
|
|
151
|
+
A CSV with one row per variant:
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
variant_id, lookback, threshold, sharpe_is, sharpe_oos, n_trades
|
|
155
|
+
v001, 20, 0.5, 1.82, 1.61, 412
|
|
156
|
+
v002, 25, 0.5, 1.85, 1.58, 401
|
|
157
|
+
...
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Column roles are inferred from names/types. IS/OOS pairs are detected by suffix (`_is`/`_oos`, `_in`/`_out`, `_train`/`_test`). For Parquet/SQLite, export to CSV first.
|
|
161
|
+
|
|
162
|
+
### Flags
|
|
163
|
+
|
|
164
|
+
| Flag | Default | Purpose |
|
|
165
|
+
|------|---------|---------|
|
|
166
|
+
| `--metric` | auto | Ranking metric column |
|
|
167
|
+
| `--top-k` | 5 | Top variants to assess |
|
|
168
|
+
| `--dispersion-threshold` | 0.05 | Max relative dispersion for CONVERGED |
|
|
169
|
+
| `--min-samples` | 200 | Sample floor per variant |
|
|
170
|
+
| `--rank-threshold` | 0.6 | Min IS/OOS Spearman for CONVERGED |
|
|
171
|
+
| `--lower-is-better` / `--higher-is-better` | auto | Metric direction override |
|
|
172
|
+
| `--id-column` / `--sample-column` | auto | Column-role overrides |
|
|
173
|
+
| `--save` | off | Also write `iteration_check_<timestamp>.md` in the current directory |
|
|
174
|
+
| `--json` | off | Print the verdict and numbers as JSON instead of Markdown |
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
### Example output
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
# Iteration Check | results_converged.csv | 2026-06-06
|
|
182
|
+
|
|
183
|
+
## Verdict: CONVERGED
|
|
184
|
+
|
|
185
|
+
**Top-5 cluster tightly (1.7% dispersion) on a parameter plateau, OOS-stable (ρ=0.99) — ship `v10`.**
|
|
186
|
+
|
|
187
|
+
## Convergence Analysis
|
|
188
|
+
| Metric | Top-K Mean | Top-K Std | Dispersion | Threshold |
|
|
189
|
+
|--------------------|-----------|-----------|------------|-----------|
|
|
190
|
+
| sharpe_oos (rank) | 1.58 | 0.03 | 1.7% | < 5% ✓ |
|
|
191
|
+
| sharpe_is | 1.82 | 0.02 | 1.3% | < 5% ✓ |
|
|
192
|
+
|
|
193
|
+
IS→OOS rank stability (Spearman): 0.99 (threshold > 0.60 ✓)
|
|
194
|
+
|
|
195
|
+
## Parameter-Space Check
|
|
196
|
+
- lookback: top-K at [20, 25, 30] — contiguous
|
|
197
|
+
- threshold: top-K at [0.5, 0.6] — contiguous
|
|
198
|
+
[x] Plateau region — robust, safe to deploy
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Limitations
|
|
204
|
+
|
|
205
|
+
- **Not a backtester.** It analyzes results you already have; it does not run strategies.
|
|
206
|
+
- **Not a live monitor.** Research-phase tooling — live trading needs different rails.
|
|
207
|
+
- **Metric-agnostic, not metric-smart.** It checks dispersion of whatever metric you point at; it doesn't know whether that metric is the right one.
|
|
208
|
+
- **Convergence ≠ profit.** Convergent variants can converge on losing strategies. The verdict reports convergence, not edge.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Why this exists
|
|
213
|
+
|
|
214
|
+
In real quant research, the failure mode isn't running too few backtests — it's calling one lucky variant a "winner" when its neighbors perform completely differently. Convergence-across-variants is the only honest signal that the parameter region has structure. This tool enforces the discipline — and gives the same treatment to the other ways a strategy can look better than it is.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
Built by [Martian Mobile](https://martianmobile.com). MIT licensed — see [LICENSE](LICENSE).
|