Home Verified Methods Reports Papers Methodology About
Home › Worked examples › VPIN Verification Report: One Tape, Two Classifiers, Two Defects Fixed

VPIN Verification Report: One Tape, Two Classifiers, Two Defects Fixed

Verdict

The implementation reproduces the defining property of VPIN — a rising reading under informed flow — on a tape where the informed episode is known in advance: 0.3292 in the balanced segment against 0.6376 in the informed segment, a ratio of 1.94×. It did so only after two defects in its degenerate-case handling were found and fixed: one on 16 August 2026, one on 15 September 2026.

Verification record
v1.1 · first run 16 August 2026 · published as a report 4 September 2026 · second defect added 15 September 2026
Implementation under test
quantmedia-research/vpin-order-flow-toxicity/vpin.py, in the public repository
Environment
Python 3.11.9; numpy 2.4.6; pandas 3.0.5; scipy 1.17.1 — all newer than the package pins, so this doubles as a forward-compatibility check
Verifier
Cemil Ertürk
Status
Independent. Not peer reviewed. Synthetic data only; no claim about any real security.

On the same synthetic tape, mean VPIN is 0.4410 with bulk volume classification and 0.1779 with the tick rule. The classifier changes the reading substantially, so a VPIN threshold needs more context than a number.

Reproduction record
Checked 4 September 2026. Seed: 20260808.
Source
vpin-order-flow-toxicity at revision 70ae45c.
Environment used
Python 3.11.9, NumPy 2.4.6, pandas 3.0.5, SciPy 1.17.1.
Data
Synthetic. These examples test the implementation and illustrate the method; they do not measure trading performance on real securities.

Set up the experiment

The generator creates 20,000 trades with total volume 5,344,047. A segment with one-sided flow is inserted into an otherwise balanced tape. Both classifiers receive the same trades, bucket size of 21,376 and rolling window of 50 buckets.

cd quantmedia-research/vpin-order-flow-toxicity python example.py

Compare the output

MeasureBulk volume classificationTick rule
Complete buckets250250
Mean VPIN0.44100.1779
Minimum0.28050.1247
Maximum0.82570.2918

For bulk volume classification, the mean in the balanced segment is 0.3292, compared with 0.6376 in the planted one-sided segment: a ratio of 1.94. This describes the two segments in this tape, not a forecast accuracy or trading return.

The buckets account for 5,344,000 volume units. The remaining 47 units are in an incomplete final bucket and are excluded by design. The console rounds this coverage to 100.0%; it is not exactly 100%.

What to inspect in the code

Bulk volume classification estimates buy and sell volume from standardized bucket price changes. The tick rule uses trade-to-trade price direction. Neither method observes the true identity or intent of a trader. Their difference here shows why changing the classification rule can change an apparent toxicity signal.

The tests also cover a special case: constant positive price changes have zero dispersion. The implementation handles that case by price-change sign, so a uniformly rising tape does not incorrectly receive a balanced 50/50 split. Flat changes receive an even split.

# From the repository root: python quantmedia-research/tests/test_vpin.py # Expected: 15 passed

Limits and a useful next experiment

This is one seed and one constructed tape. It does not verify that VPIN predicts volatility in a real market. Try a different bucket size or window, keeping the trade tape fixed, to separate estimator sensitivity from changes in the underlying inputs. That experiment has now been run on this tape: see VPIN: why BVC and the tick rule disagree. Its numbers are not folded into this report.

Download the expected bucket output or inspect the tests at the checked revision.

Defects found during verification

Degenerate tape returned the wrong sign

Bulk volume classification standardises each bucket’s price change by the dispersion of price changes. On a monotone tape — every trade at a higher price than the last — that dispersion is zero, and the original branch handled the division by splitting volume 50/50. So a perfectly one-sided tape, the most toxic input imaginable, returned VPIN = 0. Exactly backwards.

Detection: a test constructing a monotone tape and asserting VPIN > 0.9. Fix: in the degenerate branch, classify by the sign of the price change (up → buy, down → sell, flat → 50/50). Guard: the test is one of 15 in tests/test_vpin.py.

2. A flat tape read as maximally toxic under the tick rule (15 September 2026)

The tick rule signs each trade by its price change and lets an unchanged price inherit the previous sign, which is standard. The implementation also treated the first trade as buyer-initiated. On a tape whose price never moves there is no change to inherit from, so that opening guess propagated through every trade: 100% buy volume in every bucket, VPIN = 1, on an input that carries no information at all.

Detection: the degenerate-input suite, which runs each method on inputs whose answer is obvious; a flat tape was expected to read 0 under both classifiers and read 0 under BVC, 1 under the tick rule. Fix: trades before the first price change carry no sign and split evenly. Guard: tests/test_vpin.py now asserts the flat tape reads 0; the published numbers on this page are unchanged to four decimals (the first trade of the synthetic tape is the only one affected) and verify_examples.py still matches the committed CSV to nine decimals.

What this verification does not establish

Reproduce

python quantmedia-research/verify_examples.py re-runs this example in a clean directory and asserts the shipped CSV matches to nine decimal places. Then python tests/test_vpin.py from quantmedia-research/: expected 15 passed.