Skip to main content

paper-to-live

A backtested strategy, walked from its paper Sharpe ratio to the number a desk would actually underwrite.

$ pip install -e .
$ paper-to-live run
========================================================================
  FROM PAPER TO LIVE
========================================================================

  A search over 322 moving-average rules (35 effectively independent),
  1008 trading days, 50m of capital against 300m of daily volume.

------------------------------------------------------------------------
  WHERE THE SHARPE RATIO WENT
------------------------------------------------------------------------
  Paper (what the backtest said)           +0.82  rule 23/40
  less execution                           -0.20  207 bps a year
  less financing                           -0.27  2.83% at 4y
------------------------------------------------------------------------
  Surviving                                +0.35  42% of the paper figure

------------------------------------------------------------------------
  AND WHETHER ANY OF IT IS REAL
------------------------------------------------------------------------
  P(true Sharpe > 0) after search           0.70  wanted: > 0.95
  P(the ranking is overfit)                 0.31  0.50 is pure noise

------------------------------------------------------------------------
  WHAT IT WOULD HAVE TAKEN TO HOLD
------------------------------------------------------------------------
  Expected shortfall (99%, 1 day)        228 bps  from 11 tail days
  Deepest drawdown                         13.0%  372 days under water
  Turnover                                   42x  22 orders, largest 33% of a day

========================================================================

  CHECKS
------------------------------------------------------------------------
  Positions reproduce the backtest       0.0e+00  0 ambiguous days
  Curve reprices its own quotes          5.6e-17  4 instruments
  Peak impact would have overstated      415 bps  published coefficients are peaks
  Largest order, first half of day           61%  26 slices, 21.4 bps

That is the whole output. The rest of this page is what each line means.

The question

A backtest reports the Sharpe ratio of whichever rule came first out of a parameter search. Four things stand between that number and a funded book, and each of them is a library in its own right:

Stage What it answers Library
Selection How much of the winner is the search? holdout
Execution What does putting it on cost? slippage
Risk What would it have taken to hold? shortfall
Funding What does the capital cost? tenor

This repository is the four of them in a line. It is a demonstration rather than a product: the market it searches is simulated, and the screen the curve is built from is a plausible one rather than a real one. What is not a demonstration is the arithmetic between the stages, which is where the paper number goes.

What the run above says

The search tries 322 moving-average crossover rules over four years of a simulated market with nothing in it to find — the generator's drift term is set to zero, so every rule is trading noise. The best of them still comes back with an annualised Sharpe ratio of 0.82, because the best of 322 draws is not a draw.

From there:

  • Execution takes 0.20. The winning rule reverses 21 times. Each reversal trades twice the book, which at 50m against 300m of daily volume is a third of a day's volume in one order, and the square-root law charges 20 basis points for it. Over four years that is 207 basis points a year.
  • Financing takes 0.27. A Sharpe ratio is defined on returns in excess of a risk-free rate, and a backtest that subtracts nothing has set that rate to zero. The four-year point of the bootstrapped curve is 2.83%, and against the strategy's own volatility that is a hurdle of 0.27 before it earns anything.
  • 0.35 survives, 42% of what the backtest reported.

And then the part that matters more than any of it: the deflated Sharpe ratio is 0.70. That is the probability the winner's true Sharpe ratio is above zero, once the size of the search is accounted for. Not above 0.35. Above zero. A coin-flip's worth of evidence that the strategy is not worthless, for something that looked like a 0.82 four lines earlier.

The design decision that mattered

The sweep hands back what each rule earned, not what it held — and the cost of a strategy is set by how often and how far it moves, which a return series does not show. Two rules with the same Sharpe ratio, one trading twice and one trading two hundred times, are the same column of numbers and are not the same strategy.

So the first thing the pipeline does after picking a winner is recover its daily holdings by dividing each strategy return by that day's market return, snap the result to a whole unit, and then check that the recovered schedule reproduces the series it came from. The check raises rather than warning, because every later stage prices the trades those positions imply: a silent mismatch would be a cost number computed for a strategy nobody ran. The Positions reproduce the backtest line above is that check, and on the default run its worst error is exactly zero.

The one case it cannot see is a day the market did not move, where any holding reproduces a zero return. Rather than absorb that, the recovery carries the previous position forward and publishes the count of days it had to.

Things that are easy to get wrong, and are checked here

  • Deflating by the number of rules rather than the number of bets. A 23-day and a 25-day moving average are nearly the same strategy. Behind 322 rules there are 35 effectively independent trials, from the eigenvalues of their correlation matrix, and deflating by 322 would overstate the correction as badly as deflating by 1 understates it.
  • Charging the peak impact. Published impact coefficients are peaks, but a schedule pays the average of the impact path — two thirds of the peak under the square root. Quoting the peak overstates the bill by half, which on this run is 415 basis points.
  • Scheduling under one impact law and charging under another. Almgren-Chriss has a closed form only for linear impact; the cost here comes from the square-root law, which has the better evidence behind it. The linear coefficient is fitted to agree with the square-root law at the order being scheduled, and a test asserts that the risk-neutral trajectory then costs exactly what the law charges.
  • Reading a tail number without its sample. A 99% expected shortfall over four years is an average of about ten observations. The count is printed next to the estimate rather than left in a footnote.
  • Funding overnight. A position held for four years is financed at the four-year point of the curve, not at the front of it.

Running it differently

The conclusion is not a property of the seed, and the way to see that is to change it:

$ paper-to-live run --seed 7      # a different simulated market
$ paper-to-live run --notional 5000000   # a tenth of the size
$ paper-to-live run --json        # every figure, for a spreadsheet

At a tenth of the capital the annual execution drag falls from 207 basis points to 66 — a third, not a tenth, because the impact law is a square root — and what survives rises from 0.35 to 0.48. The deflated Sharpe ratio does not move at all, since the size of the book has nothing to do with the size of the search. That is the point: execution and financing are real costs and they are not the reason this strategy fails.

A test sweeps the seeds and asserts that no draw produces a deflated Sharpe ratio a reader would fund. Another asserts that 0.95 is a threshold this setup could reach, since an assertion no input can violate tests nothing.

What is checked, and against what

Everything above is this repository's own output, which is the easiest kind of number to produce and the hardest to believe. So here is what each line was checked against, before the install line. Every figure is produced by paper-to-live run --json, which CI runs against the built wheel and which this repository's suite reads back, so a figure that moves fails on the commit that moved it:

Check Result
The search run against a market with no exploitable structure winner's paper Sharpe 0.82, deflated to 0.70; under 0.95 at every seed swept
0.95 shown to be reachable at all, so the row above is evidence a 0.10 daily Sharpe over the same 1008 days clears it; a quarter of that edge does not
The erosion column, as an identity rather than an arrangement 0.8226 less 0.2039 less 0.2732 is 0.3455, residue 0.0
322 crossover rules, counted as independent trials 35.0 effective, by the eigenvalue method
Recovered positions against the return series they came from worst error exactly 0.0 over 1008 days, 0 of them ambiguous
The bootstrapped curve repricing the quotes it was built from 5.6e-17 worst error, 4 instruments
Published impact coefficients used as averages rather than as peaks 415 bps against the 207 bps actually charged
The financing haircut by three routes, one of them another package the hurdle, the drop and holdout.financing_impact agree to 3e-16

The first row is the one the project exists for. The generator's drift term is zero, so the simulated market has nothing in it to find and whatever wins a search over it won by luck. That makes the deflated Sharpe ratio a measurement against a known answer: the true Sharpe ratio is zero, and the honest report of a 0.82 winner is a coin flip's worth of evidence that it is not worthless. Coming back with 0.70 is the library being right rather than the library producing a number. A test sweeps six seeds and asserts the figure never reaches 0.95 at any of them, so the conclusion is not a property of the seed this page happens to print.

The second row guards the first. An assertion no input can violate tests nothing, so 0.95 has to be shown attainable on the same sample length and the same trial dispersion — otherwise "never above 0.95" is a statement about the threshold rather than about the market.

The last row checks this repository against a different one. The funding stage reports the Sharpe ratio before financing, the ratio after it, and the hurdle; the first two differ by the third, because the hurdle is zero / annual_volatility and the drop is daily * sqrt(252) / sd, which is the same number reached two ways inside one dataclass. Nothing asserted that they agreed. holdout.financing_impact is a third route from outside this package entirely, and its closed form rate / volatility is a fourth with a residual that has to be zero because the rate is constant over the horizon. All of them land on 0.22417514. An agreement with an outside implementation is worth more than any assertion this repository could make on its own, which is also why it is checked at three valuation dates rather than at the one printed above.

That comparison also settled a convention the module had only argued for in a comment. The stage reads a continuously compounded zero rate and charges zero / 252 a day, where subtracting from an arithmetic return wants expm1(zero / 252). The looseness is 1.3e-05 of annualised Sharpe ratio against a haircut of 0.224 — four orders below the thing being corrected, so the convention is justified by a measurement rather than by the argument.

The third and fifth rows are exact zeros, not small numbers. The erosion column either adds up or the four figures printed above are not a decomposition of anything. The recovered holdings either reproduce the series they were derived from or every cost below them was computed for a strategy nobody ran — which is why that check raises rather than warning.

The repricing error is quoted as a bound rather than as digits, deliberately. The last bits a root-found quantity lands on are not reproducible across interpreters — the same figure in tenor comes back at -8.3e-14 on CPython 3.12 and +1.3e-13 on 3.11 — so ten significant places would be a number that happens to be true where it was measured. The bound is the claim.

paper-to-live run --json     # every figure above, as a payload

Behind them: 100 tests on Python 3.10 through 3.13, run against the four libraries built from their default branches at job time.

Installing

The four libraries are not on a package index yet, so until they are, install them from source first:

$ pip install git+https://github.com/DaniyalMlk/holdout.git \
              git+https://github.com/DaniyalMlk/slippage.git \
              git+https://github.com/DaniyalMlk/shortfall.git \
              git+https://github.com/DaniyalMlk/tenor.git
$ pip install -e .

CI does the same thing, building each library from its default branch at job time with no cache: this repository exists to show that the four fit together, and a stale wheel would hide the day they stop.

When they are released, that first command becomes pip install paper-to-live and nothing else changes. The declared requirements are already the bounded ranges a published distribution has to carry — a git+https direct reference is refused on upload, so it cannot be what the metadata says — which means the metadata here is correct and currently unsatisfiable.

That is a trap worth closing rather than remembering, so the release workflow closes it. Its first job asks the index whether every declared requirement can actually be resolved, with no wheelhouse on the path, which is the resolve pip install paper-to-live performs. A version tag pushed before the four libraries exist fails there instead of publishing something nobody can install — and a published version can never be replaced. The job turns green on its own on the day they are released, with nothing edited.

Layout

Module Stage
selection.py The candidate set and the deflation of its winner
execution.py Parent orders, impact, and the net return series
risk.py Value at risk, expected shortfall, drawdown
funding.py The discount curve and the financing hurdle
pipeline.py The four in order, and the erosion between them
report.py The table above

Each stage is a pure function of the settings and the stage before it, so any one of them can be run alone.

See ROADMAP.md.

Metadata

Release files for paper-to-live 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for paper-to-live 0.1.0
File Size Uploaded Provenance
paper_to_live-0.1.0.tar.gz 46.5 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for paper-to-live 0.1.0
File Interpreter ABI Platform
paper_to_live-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 74.7 kB

Release files / paper_to_live-0.1.0.tar.gz

Download URL paper_to_live-0.1.0.tar.gz
Size 46.5 kB
Tags Source
SHA-256 checksum
How to use checksums
180426aef500e5690a8d967cb73228bed38040fcc9d3969c782f4c770a9f9794
BLAKE2b-256 checksum
How to use checksums
af2b3eb7b85d8fe2554a1d2015e2138127858138607ba1ea07ff9e0c7089ff64
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 27, 2026.

Transparency log

Release files / paper_to_live-0.1.0-py3-none-any.whl

Download URL paper_to_live-0.1.0-py3-none-any.whl
Size 28.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
574505c5f74c4f9a6b9199fa0e4c16d7c722e164ede4b7ad1294810465a4fc80
BLAKE2b-256 checksum
How to use checksums
2d299a5f677351b1a30bcf1a183c61559485f85d57c9291bd08f9d2e3d696903
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 27, 2026.

Transparency log

Security for paper-to-live 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for paper_to_live-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 27, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page