Skip to content

Learn

Stock Backtest Research

Reconcile conflicting Sharpe ratios in stock backtest reports

Conflicting Sharpe ratios do not immediately imply that one stock backtest engine is broken. Reports may use different return intervals, standard-deviation conventions, annualization factors or risk-free inputs. Reconstruct the same underlying return sequence first, then change one convention at a time and reconcile the remaining difference.

This guide independently recalculates a tiny synthetic account sequence and produces a formula comparison table. It is an external check, not an assertion that CoTrading exposes these formulas or that the example has statistical investment significance. Keep one currency and market, and distinguish arithmetic agreement from evidence of future performance.

Establish that the reports describe one run

Match run identifiers, data and configuration references, covered sessions, cost treatment and terminal holdings. Obtain the actual equity sequence and supporting transaction ledger. If one report includes a later day or excludes an open position, its Sharpe difference is partly an input difference rather than only a formula disagreement.

Confirm that account values are measured after the same fees. A net equity sequence already contains booked transaction charges; subtracting those charges again from each return double-counts them. Reconcile cash, securities and costs before treating the series as a clean basis for metric comparison.

Identify external contributions and withdrawals. Simple adjacent-value ratios do not remove investor cash flows. Either obtain an appropriate flow-adjusted return series or perform that separate reconciliation first. The example has no external flows and five consecutive synthetic valuation marks, so ordinary adjacent ratios are appropriate under its declared boundary.

Write the formula contract explicitly

For each report, list return type, sampling interval, number of returns, benchmark or risk-free convention, standard-deviation denominator and annualization factor. Distinguish arithmetic mean returns from compounded growth and simple returns from logarithmic returns. Similar column names do not establish identical inputs.

In William Sharpe’s original discussion, the historical ratio concerns mean differential return relative to its variability, with an explicit discussion of time dependence. We use a constant zero risk-free return and a conventional square-root scaling for this arithmetic reconciliation. That scaling is an assumption, not a universal correction for serially correlated stock returns.

Our agreed contract uses simple per-session returns, sample standard deviation, 252 sessions per modeled year and zero risk-free rate. The choice 252 is illustrative. Do not automatically assign it to every stock market, frequency or calendar, and do not infer a full year’s coverage from four observations.

Reconstruct four returns from five values

The invented equity values are [100,101,100,102,101]. Adjacent simple returns are next/current - 1: approximately 1%, −0.990099%, 2% and −0.980392%. Five marks produce four returns, not five. The full-period account return is 1%, which is distinct from the arithmetic average of those four changes.

Retain session keys and verify cadence before calculation. A missing middle value must not be dropped and its two-session change treated as one ordinary session. Our sample checks missing numeric inputs but trusts its fixed consecutive-session fixture; it is not a general exchange-calendar validator.

The Python statistics documentation distinguishes sample stdev from population pstdev. Our computation uses the former for the agreed result and deliberately calculates the latter to reproduce one disagreement. No data science package or financial service is called.

Reproduce the mismatching reports

With mean excess return divided by sample deviation and multiplied by the square root of 252, the result is 2.73877777. Using population deviation on the same four returns gives 3.16246816. Keeping sample deviation but changing the annualization factor to 365 gives 3.29611955.

Calculation conventionSynthetic annualized ratio
Sample deviation, 2522.73877777
Population deviation, 2523.16246816
Sample deviation, 3653.29611955

For four returns, the population-based ratio is the sample-based ratio multiplied by sqrt(4/3). The 365-based ratio is multiplied by sqrt(365/252). The code asserts both relationships, explaining the differences without changing the observed trades or inventing another price history.

These high ratios arise from four invented observations. They do not support a claim that a stock strategy is unusually good. The purpose is to make convention errors large enough to inspect, not to estimate a credible long-run risk-adjusted performance measure.

Recalculate under the shared convention

The core calculation is:

returns = [b/a - 1 for a, b in zip(equity, equity[1:])]
period_rf = (1 + annual_rf) ** (1 / annual_sessions) - 1
excess = [r - period_rf for r in returns]
ratio = sqrt(annual_sessions) * mean(excess) / stdev(excess)

The risk-free conversion treats the supplied annual value as an effective annual rate. A continuously compounded rate, a quoted yield or daily varying benchmark requires a different declared transformation. The sample’s default is zero, so no such conversion uncertainty affects its printed table.

The program also independently derives sample deviation from squared deviations and checks agreement within a relative tolerance of 1e-12. That is an arithmetic assertion for this small fixture, not a suggested universal tolerance for every report export. Define numeric tolerances and display rounding before interpreting residual differences.

Preserve unavailable metrics

Two equity marks supply one return, insufficient for sample deviation. The sample returns insufficient, with no ratio. Three identical values produce zero variability and return zero_volatility. A sequence containing a missing value returns an invalid/missing status. None of those cases is converted to Sharpe zero.

The zero-volatility counterexample differs from an actually measured zero ratio with nonzero variability and zero mean excess return. Substituting zero would make undefined and valid values indistinguishable and could alter ranking. Likewise an infinite display should not be used to announce a risk-free profitable strategy from a constant toy sequence.

Nonpositive NAV, invalid annualization counts and invalid effective annual rates need explicit rejection or an alternative model. The ordinary simple-return formula assumes positive bases. Do not silently clip impossible values or let missing observations become zero returns simply to preserve the expected array length.

Locate a residual difference methodically

After unifying the formula, compare the exact input vector, date keys, costs, risk-free vector and intermediate mean/deviation. Change one convention at a time and retain the before/after table. If the recalculated values still differ beyond tolerance, investigate rounding, omitted sessions or an undocumented transformation.

Keep reported metrics beside recalculated ones rather than overwriting the original report. A reviewer should see which mismatch was explained by sample deviation and which remains unresolved. Do not force agreement by changing the annualization factor until a preferred number appears.

Retain unrounded intermediate returns when exporting the reconciliation. A spreadsheet containing only two-decimal percentage displays may not have enough information to reproduce the original ratio. Obtain the underlying values or state the achievable precision. More decimal places in the final answer cannot restore information discarded from the inputs. Also confirm that unavailable-status labels survive downstream export rather than being replaced by a convenient default zero.

Supporting trades remain useful for reconciling costs and exposure, but Sharpe is computed from the declared return series, not directly from a list of profitable trade amounts. Mixing per-trade and per-session ratios creates another denominator and timing problem, especially when trade durations differ.

Run and accept the reconciliation

Download the complete example. Use Python 3.12 or later and run python R620.py. Standard-library assertions reproduce the three ratios, verify independent sample arithmetic and convention factors, and preserve unavailable statuses for too few observations, zero volatility and missing input.

Accept the reconciliation when run identity and inputs match, formula choices are recorded and remaining numeric differences satisfy the predefined tolerance or have a documented explanation. Recalculate after changes to equity, costs, frequency, benchmark or annualization policy, preserving the original report version. This synthetic check verifies arithmetic consistency only, without validating historical prices, investment significance or product behavior.

FAQ

Can I compare daily and per-trade Sharpe ratios directly?

No. Their observations and time conventions differ and need a common explicitly constructed basis.

Is zero volatility the same as Sharpe zero?

No. The ratio is unavailable under this calculation when its deviation denominator is zero.