Measure the raw mathematical edge of a trading signal — capital-free, with confidence intervals and a reality check.
Project description
crucible
Measure the edge before you ever open a $100k account.
Most trading "edges" are artifacts of a small sample. crucible.edge takes a
trade log and tells you — with a confidence interval and a p-value — whether
the edge is real. No account, no position sizing, no equity curve. It's the thing
you run before a backtester.
pip install crucible-quant # core: metrics + stats + simulator (numpy/pandas only)
pip install "crucible-quant[examples]" # + yfinance, to run the demo below on real data
Installed as
crucible-quant, imported ascrucible(import crucible).
30-second example
import yfinance as yf
from crucible.edge import barrier_trades, edge_report, reality_check
from crucible.strategies import ma_cross
px = yf.download("ES=F", start="2010-01-01") # any OHLC frame works
entries = ma_cross(px, fast=20, slow=50) # your signal: a boolean Series
trades = barrier_trades(px, entries, side="long", # signal -> trade log (in R)
tp=2.0, sl=1.0, timeout=20) # 2R target, 1R stop, 20-bar cap
print(edge_report(trades)) # the full capital-free scorecard
print(reality_check(trades)) # <-- the verdict
======================================================
EDGE REPORT (capital-free)
======================================================
Trades : 214
Win rate : 38.3 %
------------------------------------------------------
Expectancy : +0.081 R [PASS]
Profit factor : 1.34 [PASS]
Payoff ratio : 2.16 [INFO]
SQN-100 : 1.72 [INFO]
------------------------------------------------------
Excursion ratio : 1.28 [PASS]
======================================================
VERDICT (expectancy): +0.081 R 95% CI [-0.031, +0.196]
p(edge>0) = 0.071 -> FRAGILE
point positive, but the CI straddles zero — not distinguishable
from noise at this sample size. Do NOT size it up.
That FRAGILE block is the whole point: a positive expectancy that a backtester
would have shown you as a rising equity curve is, at this sample size,
indistinguishable from noise. crucible says so out loud.
Runnable versions live in
examples/:quickstart.pyandvalidation.pyuse synthetic data (no network);real_data_yfinance.pypulls real prices from Yahoo Finance (pip install "crucible-quant[examples]") and runs the full pipeline — trypython examples/real_data_yfinance.py --ticker QQQ.
What's in the box
TradeLog— one documented schema (rin R-multiples, plus optionalmfe/mae/bars_held/prob). Everything speaks it.- Edge metrics — expectancy, profit factor, payoff ratio, SQN, and the excursion family (MFE/MAE efficiency, E-ratio, time asymmetry, exit efficiency).
- The honesty layer —
bootstrap_ci,p_value_positive,reality_check(HELD / FRAGILE / FAIL), andrandom_entry_null(did your signal beat coin-flip timing on the same prices?). - A generic barrier simulator —
barrier_trades: OHLC + a boolean entry signal → aTradeLog. No instrument specifics. - Example signals —
ma_cross,macd_cross. Demos, not endorsed edges.
Does the edge survive out of sample? — crucible.validation
The pooled reality check tells you if an edge is real on the whole history.
crucible.validation asks the harder question — does it hold on data the analysis
never touched?
from crucible.validation import holdout, walk_forward, sign_permutation_pvalue
# 1. Early-train / late-confirm — leakage-controlled temporal split
print(holdout(trades, "2019-01-01", embargo_weeks=8)) # verdict is the LATE period
# 2. Sign-permutation p-value (Masters) — could the edge come from noise?
print(sign_permutation_pvalue(trades))
# 3. Pardo walk-forward — optimize params in-sample, confirm out-of-sample, stitch
wf = walk_forward(px, ma_cross, param_grid={"fast": [10, 20], "slow": [50, 100]},
is_days=365 * 3, oos_days=365)
print(wf) # per-fold IS->OOS efficiency (WFE)
print(reality_check(wf.stitched)) # the stitched-OOS verdict — the honest one
Also here: sidak_correction and whites_reality_check (max-statistic across every
variant you searched) for when a grid search flatters the best result.
See examples/validation.py.
A shareable tearsheet — crucible.report
pip install "crucible-quant[report]"
from crucible.report import tearsheet
tearsheet(trades, "sheet.html", title="SPY — 20/50 MA cross")
Writes a self-contained HTML page (plotly.js inlined, renders offline): the
verdict banner, the metric scorecard, the R-multiple distribution, cumulative R,
MFE/MAE excursion, and the bootstrap expectancy distribution behind the CI. Still
capital-free — it charts summed R, never an equity curve. See
examples/tearsheet.py.
What this is — and isn't
✅ Trade-level edge metrics, excursion efficiency, bootstrap CIs, a random-entry reality check — all capital-free.
❌ No capital, position sizing, commissions, CAGR, drawdown, or
Monte-Carlo-on-equity. If you want an equity curve, hand the TradeLog to
quantstats. crucible stops at the edge.
Releasing
Releases publish to PyPI via GitHub Actions using Trusted Publishing (OIDC —
no API tokens are stored anywhere). Changes are tracked in
CHANGELOG.md.
One-time setup (maintainer, before the first publish):
- Create two GitHub environments — repo Settings → Environments — named
pypiandtestpypi. (Add a required-reviewer rule onpypifor a manual approval gate, if you want one.) - Register a pending Trusted Publisher at
https://pypi.org/manage/account/publishing/:
PyPI project
crucible-quant, ownermspinola, repocrucible, workflowrelease.yml, environmentpypi. Repeat on https://test.pypi.org/manage/account/publishing/ with environmenttestpypifor dry runs.
Cutting a release:
- Bump
versioninpyproject.tomland move theCHANGELOG.mdentry from Unreleased to the new version. - (Optional dry run) Actions → Release → Run workflow →
testpypi. - Tag and push — the tag must match the
pyprojectversion or the run fails:git tag v0.1.0 git push origin v0.1.0 # builds, twine-checks, publishes to PyPI
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file crucible_quant-0.1.0.tar.gz.
File metadata
- Download URL: crucible_quant-0.1.0.tar.gz
- Upload date:
- Size: 28.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
17919abaf0834e93c106450f31ae756b6ff26be1b9e850f4196255480a64638e
|
|
| MD5 |
f1fdfdb4413f753527e9736f21e0d9d3
|
|
| BLAKE2b-256 |
ffae2f78c6d192326f57d4b26e60a76276a54176175bfc2852bb984343158926
|
Provenance
The following attestation bundles were made for crucible_quant-0.1.0.tar.gz:
Publisher:
release.yml on mspinola/crucible
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
crucible_quant-0.1.0.tar.gz -
Subject digest:
17919abaf0834e93c106450f31ae756b6ff26be1b9e850f4196255480a64638e - Sigstore transparency entry: 2169952489
- Sigstore integration time:
-
Permalink:
mspinola/crucible@80c2a8c71c72207cf642086c888cf4750af69802 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/mspinola
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@80c2a8c71c72207cf642086c888cf4750af69802 -
Trigger Event:
push
-
Statement type:
File details
Details for the file crucible_quant-0.1.0-py3-none-any.whl.
File metadata
- Download URL: crucible_quant-0.1.0-py3-none-any.whl
- Upload date:
- Size: 27.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6a349548e23a547261f2dee53ba1bea1c5a712e580cb27f6b5d49c12bb7bad5b
|
|
| MD5 |
9802709fc983e74946ecb39128e76c4f
|
|
| BLAKE2b-256 |
0e0d8dc44bf7749e9a1047cc5474840ff7f92b71c9975631a377d66d8bc03b00
|
Provenance
The following attestation bundles were made for crucible_quant-0.1.0-py3-none-any.whl:
Publisher:
release.yml on mspinola/crucible
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
crucible_quant-0.1.0-py3-none-any.whl -
Subject digest:
6a349548e23a547261f2dee53ba1bea1c5a712e580cb27f6b5d49c12bb7bad5b - Sigstore transparency entry: 2169952540
- Sigstore integration time:
-
Permalink:
mspinola/crucible@80c2a8c71c72207cf642086c888cf4750af69802 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/mspinola
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@80c2a8c71c72207cf642086c888cf4750af69802 -
Trigger Event:
push
-
Statement type: