Calculate the Lo-MacKinlay Variance Ratio test for the random walk hypothesis.

The test compares the Variance of q-period (overlapping) returns, the sum of q consecutive returns, to q times the Variance of single-period returns. Under the random walk hypothesis these should be equal, giving a Variance Ratio of 1. A Variance Ratio above 1 with a significant (low) p-value indicates positive autocorrelation (momentum/trending behavior), while a Variance Ratio below 1 with a significant p-value indicates negative autocorrelation (mean-reversion).

The result holds two statistics. The homoskedastic z(q) assumes a constant volatility; daily returns cluster in volatility, and under such heteroskedasticity z(q) rejects the random walk too often. The heteroskedasticity-robust z*(q) of Lo and MacKinlay stays valid when volatility clusters and is the safer reading for returns. get_arch_lm_test shows whether a series has that volatility clustering.

The formula is as follows:

\[\operatorname{VR}(q) = \sigma ^{2} (q) / \sigma ^{2} (1),\;\; \text{with}\; \sigma ^{2} (1) = \operatorname{Σ}(\text{rₜ} - \mu) ^{2} / (n - 1) \text{and}\; \sigma ^{2} (q) = \operatorname{Σ}(\text{rₜ} + ... + \text{rₜ₋q₊₁} - q \cdot \mu) ^{2} / m \;\text{over the overlapping q-period sums},\;\; m = q \cdot (n - q + 1) \cdot (1 - q / n) \text{and}\; \mu \;\text{the mean return} (\text{the unbiased estimators of Lo}\; \text{\&} \;\text{MacKinlay}),\;\; q = 2 \;\text{by default}\] \[z(q) = (\operatorname{VR}(q) - 1) / \operatorname{√φ}(q),\;\; \text{with}\; \text{φ} (q) = 2 \cdot (2q - 1) \cdot (q - 1) / (3q \cdot n)\] \[z \cdot (q) = \sqrt{n} \cdot (\operatorname{VR}(q) - 1) / \sqrt{\theta} (q),\;\; \text{with}\; \theta (q) = \operatorname{Σ}(k = \text{1..q-1}) 4 \cdot (1 - k / q) ^{2} \cdot \operatorname{δ}(k) \text{and}\; \text{δ} (k) = n \cdot \operatorname{Σ}(t = k + 1..n) (\text{rₜ} - \mu) ^{2} \cdot (\text{rₜ₋ₖ} - \mu) ^{2} / (\operatorname{Σ}(t = 1..n) (\text{rₜ} - \mu) ^{2}) ^{2}\] \[\text{P-Value} = 2 \cdot (1 - \Phi(| z |)) \text{for both statistics},\;\; \text{with Phi the standard normal CDF}\]

For more information about the method, see the following paper:

  • Lo, A.W., & MacKinlay, A.C. (1988). “Stock Market Prices Do Not Follow Random Walks: Evidence from a Simple Specification Test.” Review of Financial Studies, 1(1), 41-66.

Also known as: Lo-MacKinlay test, VR test.

Variance Ratio Test in Python or via the MCP

Install the Finance Toolkit with:

pip install financetoolkit -U

Then call get_variance_ratio_test as shown below.

from financetoolkit import Toolkit

toolkit = Toolkit(
    ["XLE", "XLF", "XLK"],
    api_key="FINANCIAL_MODELING_PREP_KEY",
    start_date="2021-01-01",
    end_date="2025-12-31",
)

toolkit.econometrics.get_variance_ratio_test()

Or, after setting up the Finance Toolkit MCP, ask:

Did the energy, financials and technology sector ETFs (XLE, XLF and XLK) follow a random walk from 2021 to 2025, according to the variance ratio test on their daily returns?

Which returns:

  XLE XLF XLK
Variance Ratio 1.0249 1.0249 0.9611
Variance Ratio Statistic 0.8815 0.8805 -1.3774
P-Value 0.3781 0.3786 0.1684
Robust Variance Ratio Statistic 0.6193 0.6256 -0.9679
Robust P-Value 0.5358 0.5316 0.3331

Using Your Own Data

get_variance_ratio_test runs on any data, not only prices: ratios, economic indicators, yield curves or your own dataset. Pass a DataFrame, a Series or a dictionary of them to Econometrics as data, with the dates in the rows or the columns, and set changes to how its changes are calculated: "percentage", "log", "difference" for rates and ratios, or None when the data are changes already. For example, for the monthly unemployment rates of the United States and Germany:

from financetoolkit import Economics, Econometrics

economics = Economics(start_date="2000-01-01")

unemployment = economics.get_unemployment_rate(
    countries=["United States", "Germany"], period="monthly"
)

econometrics = Econometrics(data=unemployment, changes="difference")

econometrics.get_variance_ratio_test()

Parameters

get_variance_ratio_test accepts the following parameters.

ParameterTypeDescriptionExample
periodstrThe data frequency for returns (daily, weekly, monthly, quarterly, or yearly). Defaults to “daily”."weekly"
within_periodboolWhether to calculate the test within the specified period or for the entire period. Thus whether to look at the test within a specific year (if period = ‘yearly’) or look at the entirety of all years. Defaults to False. Note that this requires intraday data when period = ‘daily’, since a single day only nests observations when intraday data was fetched.True
qintThe number of periods to sum returns over. Defaults to 2.4
include_benchmarkboolWhether to include “Benchmark” among the assets tested. Defaults to False.True
roundingint | NoneThe number of decimals to round the results to. Defaults to None.2
Share