Quant Memo
Foundational

Dependency Management and Lockfiles

How quant teams pin the exact versions of every library a strategy depends on, so a backtest run today reproduces the same numbers a year from now.

Prerequisites: Version Control with Git

A backtest that produced a Sharpe ratio of 1.8 last month should produce the same 1.8 today if you rerun it on the same data. In practice it often doesn't, and the culprit is rarely the strategy code — it's that a library it depends on quietly updated itself and changed behavior underneath you. A pandas upgrade can change how it handles duplicate timestamps; a scikit-learn upgrade can change a default hyperparameter; a numpy upgrade can change floating-point rounding in a way that nudges a threshold-based signal across a boundary. None of this shows up as an error. It shows up as a number that quietly stopped matching, weeks or months later, when nobody remembers what changed.

Dependency management is the discipline of declaring exactly which libraries your code needs, and lockfiles are the mechanism for pinning exactly which versions — down to the patch release — were actually installed when it last worked. A typical project has two layers: a human-edited file (requirements.txt, pyproject.toml) stating loose intent like "pandas, at least version 2", and a machine-generated lockfile (requirements.lock, poetry.lock, uv.lock) recording the precise version of every package and sub-dependency that was resolved and tested together. The loose file is what you edit; the lockfile is what you actually install from in anything that needs to be reproducible.

Worked example. A researcher's laptop has scipy==1.11.4 installed. She writes a mean-reversion signal using scipy.stats.zscore and backtests it, getting a Sharpe of 1.6. Six months later a new team member clones the repo, runs pip install -r requirements.txt with no version pins, and gets whatever the latest scipy happens to be — say 1.13.0. The signal now backtests at a Sharpe of 1.3. Nothing crashed, no error was raised, and both numbers look plausible in isolation. Only by diffing environments (pip freeze on both machines) does the team discover that scipy's numerical solver changed its default convergence tolerance between versions, shifting output values by a fraction of a percent — enough to move trades across a signal threshold. With a committed lockfile, the new team member would have installed the exact same 1.11.4 and reproduced the original 1.6 immediately.

What this means in practice

Every research repo should commit a lockfile alongside the code, and every backtest result worth trusting should record which lockfile (or commit hash of one) produced it. This matters more in quant research than in most software, because the failure mode isn't a crash you'd notice — it's a silently different number that looks completely reasonable on its own. Lockfiles pair naturally with virtual environments and containers, which isolate which interpreter and packages actually run, not just which versions are declared.

A lockfile pins the exact version of every dependency, direct and transitive, so that "install and run" produces bit-for-bit the same environment every time — turning an unreproducible backtest into a reproducible one.

If you can't explain why a number changed between two runs, check git diff on the lockfile before you check the strategy code — an unpinned dependency upgrade is one of the most common silent causes.

Related concepts

Practice in interviews

Further reading

  • Python Packaging Authority, pip documentation, dependency resolution
ShareTwitterLinkedIn