Example Workload: Financial Risk Calculations
An illustrative financial-risk batch dissected stage by stage: Monte Carlo, correlations and moving averages — what a compiler accelerates, what it cannot.
Published May 8, 2026
A nightly risk batch in Python has a recognisable anatomy: simulate, correlate, average, report. Each verb is a loop over numbers, and each loop is written in pure Python because the quant team prototypes in notebooks and the code never got rewritten. That anatomy happens to be the best-matched shape in Pyvorin's benchmark table — the financial category measured a geometric mean of 13.5x across its two workloads, and the numerical and statistics categories that flank it are similarly strong. Whether your specific batch sits near that figure is a measurement question, and this page shows how to answer it.
Two labels before anything else. This is an example workload, not a customer story; no institution and no production deployment is claimed. And every speedup cited below names a real workload in the benchmark artefact dated 13 September 2026, so each figure can be checked against the published table rather than taken on trust. Modelled arithmetic is labelled as an illustrative model with its assumptions stated.
The workload shape
Picture a simplified overnight risk job for a portfolio of positions. Four stages run in sequence. Simulation: generate tens of thousands of correlated price paths and value the portfolio along each one — the Monte Carlo core of a value-at-risk figure. Correlation: recompute pairwise return correlations from the trailing window of market data. Smoothing: moving averages and rolling volatility per position, because risk figures feed trend charts as well as limits. Reporting: aggregate the results, format the tables, write them to files.
Stages one to three are numeric loops over in-memory data — acceleratable in principle. Stage four is string formatting and I/O — not. But "acceleratable in principle" needs two qualifications drawn straight from the measurements. A loop that calls a library function for its heavy work spends its time in C already; the compiler accelerates the Python bytecode between calls, not the calls. And a loop so small that compilation overhead dwarfs it will measure below 1x, as the suite's micro-workloads do.
Measured anchors
The benchmark table contains close analogues for every numeric stage, and their spread teaches the right lesson.
| Risk stage | Closest suite workload | Measured | Read |
|---|---|---|---|
| Monte Carlo path valuation | numerical.monte_carlo_pi | 28.3x | Strong; the loop shape matches |
| Pairwise correlations | statistics.correlation | 52.5x | The strongest match in the suite for matrix-shaped risk maths |
| Moving averages / rolling stats | financial.moving_average | 22.5x | Direct named match |
| Compound / accrual loops | financial.compound_interest | 8.1x | Solid; the more conservative financial anchor |
| Covariance-style matrix work | numerical.matrix_multiply | 4.9x | Moderate; honest middle of the range |
| Report formatting | parsing / string categories | 0.7x – 1.0x | Do not compile; string work regresses |
The range across those numeric rows — 4.9x to 52.5x — is the honest headline. Marketing would pick 52.5x. Engineering reads the spread and understands that input size, loop body and memory access pattern decide where a workload lands inside it. Note also what is absent from the table: there is no published workload named "monte carlo VaR", no portfolio object model, no volatility surface. The analogues above are the evidence; anything more specific than they support is a claim waiting to be measured.
Some rows near this workload disappoint, and they deserve the same visibility. statistics.mean_variance measured 1.0x — a statistics workload with no win, because its time goes where the compiler cannot follow. graph.floyd_warshall, a plausible shape for exposure netting, measured 1.0x. And the suite's worst overall results come from workloads so small that compilation overhead dominates. Size your loops before you celebrate them.
An illustrative model
Assume, for the arithmetic only, that profiling shows 70% of your batch in the numeric stages, 20% in market-data parsing, and 10% in reporting and I/O. Assume further — labelled, not measured — 15x on the numeric share, inside the measured range above. The model gives 0.20 + 0.70/15 + 0.10 = 0.347 of the old runtime, roughly 2.9x end to end. Change the numeric share to 40% and the parsing share to 50%, and the same 15x assumption yields 0.50 + 0.40/15 + 0.10 = 0.63, or 1.6x. The model's whole purpose is to show that the answer is dominated by your measured stage shares, which is why the profiler runs before the compiler.
One assumption in that arithmetic needs flagging separately. A Monte Carlo path generator that draws random numbers from a C-implemented generator inside the loop hands much of its time to C already; a generator written in pure Python does not. The suite's 28.3x anchor is the pure-Python shape. Which one you have is a fact about your code, discoverable in an afternoon.
Picking the first function to compile
The order of operations matters more than the flags. Start with the function your profiler ranks first — usually the Monte Carlo loop — and compile it alone, leaving the rest of the batch untouched. One function gives you an interpretable number within the hour; a wholesale conversion gives a blended figure nobody can attribute, and attribution is what turns a measurement into a decision. Resist widening the compiled surface until the first number is verified, written up and accepted by whoever owns model risk. That acceptance step is where pilots quietly die, and it is far cheaper for a pilot to die on one function than on forty.
There is a second reason to start narrow. The fallback path records every function that is declined or deoptimised, and a small compiled module makes those events easy to read. A large one turns the diagnostics into noise. Keep the first compiled surface small enough that a non-zero fallback_count in the JSON output is a conversation, not an archaeology project.
The latency-context judgement
Risk code lives in two different worlds, and the compiler only belongs in one of them. The world described above is batch: overnight, deadline-measured in hours, cost measured in compute-hours. There, a 2.9x modelled reduction is a real operational win — the batch finishes before the desk arrives, and the compute bill scales down with runtime on elastic capacity.
The other world is intraday: pre-trade risk checks, margin calculations on the order path, real-time limit monitoring. There, the requirement is a bounded response measured in microseconds, under a hard tail-latency budget, inside a process whose behaviour must be predictable. Compiled batch Python is not a low-latency path, and this page will not pretend otherwise. If your requirement is a 20-microsecond worst-case response with no tail, the correct engineering answer is a language and runtime chosen for that contract, with the Python analytics feeding it from outside. Pyvorin is a throughput tool for CPU-bound batch Python; treating it as a latency tool will disappoint you in production, not in a benchmark.
Proving it on your own batch
The speed-proof method applies unchanged. Take the correlation function, wrap it in a zero-argument entrypoint, and measure both backends in one matched invocation:
python -m pyvorin support your_risk.py
python -m pyvorin run your_risk.py --function entry --compare --runs 5 --warmup 2
python -m pyvorin bench your_risk.py --function entry --runs 7 --warmup 2 --json
The output to trust has three parts: Correct: YES, meaning both backends returned the identical result; COMPILED_FULL on the target function, meaning the Pyvorin side actually ran native code; and the paired timings with matched warmup and run counts. Report the median from the --json samples, state the compile cost alongside the steady-state figure, and do the break-even division in public. The full discipline is in how to run a speed proof; the harness mechanics are in how to benchmark a function. Both apply unchanged to risk code, and neither takes an afternoon to run — the expensive part is deciding to run them at all. A team that scripts the three commands into its nightly batch validation gets a fresh proof with every library change, for the cost of three lines in a job definition.
Limits, stated plainly
The anchors come from one host, one date, one suite. Their distribution is the map; your measurement is the territory. The string and parsing stages of this workload will not speed up and may slow slightly if compiled — leave them on CPython and let the fallback path cover any function the compiler declines, as detailed in unsupported code and the fallback path. And where risk checks sit on a latency-critical path rather than in a batch window, the honest recommendation is a different tool entirely.
Where to go next
- How to run a speed proof — the audit-grade measurement method used to verify claims like these.
- Benchmarks — the full 71-workload table, including the rows that regressed.
- Pyvorin for risk management — the industry view of the same workload family.
- Quick start — install Pyvorin and benchmark your first risk function.
Last reviewed 8 May 2026 against the benchmark artefact dated 13 September 2026 (71 workloads, 54 faster / 17 slower than CPython, suite geomean 3.16x, financial category geomean 13.5x). Anchor figures are extracted from that artefact; end-to-end arithmetic is a labelled illustrative model. This page describes an example workload; it is not a customer case study.