When Equal Weight Beats Optimization
Equal weight beats optimized position sizing when your sample is short. A seeded Python simulation shows the crossover appear, then close as the sample grows.
Equal weight beats optimized position sizing whenever the sample is too short to pin down expected returns, and short samples are the normal case rather than the exception. An optimizer is sound arithmetic applied to an input nobody measures well. The 1/N portfolio, equal weight, estimates nothing at all, which is why it holds up when the estimates are bad.
Does equal weight really beat optimization?
Under one specific condition, yes. Take the terms one at a time. Equal weight, written 1/N, splits capital evenly across N positions and requires no forecast of any kind. Mean variance optimization solves for the weights with the best expected return per unit of estimated variance. The Kelly criterion solves for the weights that maximize long run compound growth; across uncorrelated assets its answer reduces to a weight proportional to each asset's expected excess return divided by its variance. Our Kelly criterion position sizing guide walks through that formula in detail.
Both optimizers need the same input: an expected return per asset. That is the quantity a sample estimates worst. Volatility comes out of a short window with workable precision. The mean does not, and the gap between the two is large enough to see in any price history. The panel below takes six widely held names, measures each calendar year from 2015 through 2024 on its own, and reports how far apart the highest and lowest yearly estimate sit. It does that twice per name: once for the annualized average daily return, once for the annualized volatility.
The exact SQL behind every number
WITH prices AS
(
SELECT
ticker,
date,
toFloat64(max(close)) AS c
FROM global_markets.stocks_daily_aggs
WHERE ticker IN ('AAPL', 'MSFT', 'NVDA', 'SPY', 'KO', 'JNJ')
AND date >= '2015-01-01'
AND date < '2025-01-01'
GROUP BY ticker, date
),
rets AS
(
SELECT
ticker,
date,
c / lagInFrame(c, 1) OVER (PARTITION BY ticker ORDER BY date ASC ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) - 1 AS ret
FROM prices
),
yearly AS
(
SELECT
ticker,
toYear(date) AS yr,
avg(ret) * 252 * 100 AS mean_pct,
stddevPop(ret) * sqrt(252) * 100 AS vol_pct
FROM rets
WHERE isFinite(ret)
GROUP BY ticker, yr
HAVING count() >= 200
)
SELECT
ticker,
round(max(mean_pct) - min(mean_pct), 1) AS mean_estimate_range_pct,
round(max(vol_pct) - min(vol_pct), 1) AS vol_estimate_range_pct
FROM yearly
GROUP BY ticker
ORDER BY mean_estimate_range_pct DESCFor NVDA the yearly mean estimate spans 184.7 percentage points across those ten years, against 28.6 points of range in the volatility estimate for the same name over the same years. The steadiest name in the panel, KO, still moved its yearly mean estimate through 23.9 points. Any scheme that hands last year's average return to an optimizer as this year's expected return is feeding it a number with that much play in it.
How long a sample do you need to estimate an expected return?
The standard error of an estimated mean return is the asset's volatility divided by the square root of the sample length in years. In years, not in observations: sampling the same twelve months hourly instead of daily buys nothing. Put a 20 percent volatility asset through that formula and one year of history gives a standard error of 20 percentage points a year. Four years halves it to 10. Getting it down to one point, the precision you would want before ranking two assets whose true means differ by two points, takes four centuries of data.
Volatility is a different case. Its precision improves with the count of observations rather than the calendar span, so a few months of daily data already places it within a few points. The panel below cuts twenty years of one index fund's daily returns into non overlapping blocks of a fixed length, computes both estimates inside every block, and reports how far the estimates scatter from block to block.
The exact SQL behind every number
WITH prices AS
(
SELECT
date,
toFloat64(max(close)) AS c
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
AND date >= '2005-01-01'
AND date < '2025-01-01'
GROUP BY date
),
rets AS
(
SELECT
date,
c / lagInFrame(c, 1) OVER (ORDER BY date ASC ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) - 1 AS ret
FROM prices
),
numbered AS
(
SELECT
ret,
row_number() OVER (ORDER BY date ASC) AS i
FROM rets
WHERE isFinite(ret)
),
sweep AS
(
SELECT
arrayJoin([21, 63, 126, 252, 504]) AS n,
i,
ret
FROM numbered
),
blocks AS
(
SELECT
n,
intDiv(i - 1, n) AS blk,
avg(ret) * 252 * 100 AS mean_pct,
stddevPop(ret) * sqrt(252) * 100 AS vol_pct
FROM sweep
GROUP BY n, blk
HAVING count() = n
)
SELECT
concat(toString(n), ' sessions') AS sample_length,
round(stddevPop(mean_pct), 1) AS mean_estimate_spread_pct,
round(stddevPop(vol_pct), 1) AS vol_estimate_spread_pct,
count() AS window_count
FROM blocks
GROUP BY n
ORDER BY n ASCAt blocks of 21 sessions the mean estimate scatters across 53.7 percentage points, against 10.9 points for the volatility estimate measured on the identical blocks. Stretch the block out to 504 sessions and the mean scatter narrows to 10.6 points. It narrows along a square root: every halving of the error costs four times the data.
A seeded simulation you can run yourself
None of that proves the estimation error is big enough to hand the win to 1/N. This does. The recipe needs a Python install and nothing else: no data files, no downloads, no third party packages. It draws its own returns from parameters you choose, so the true answer is known and both allocations can be scored against it.
- Import two modules,
import randomandimport statistics, plusfrom math import log. Set the seed on the next line withrandom.seed(20260816). - Define five assets with true annual expected returns of 5, 6, 7, 8 and 9 percent, each with a true annual volatility of 20 percent, uncorrelated. Convert to a daily step with
mu_d = mu_a / 252andsd_d = sd_a / (252 ** 0.5). - Pick a sample length T from the sweep
[60, 125, 250, 500, 1000, 2500, 5000]. - Draw T daily returns for each asset with
random.gauss(mu_d, sd_d). That draw is the entire history the manager gets to see. - Estimate each asset's mean with
statistics.meanand its volatility withstatistics.stdev. Those two estimates are all the optimizer knows. - Build the equal weight portfolio by assigning 0.2 to each of the five. It uses none of the estimates.
- Build the optimized portfolio by setting each raw weight to
mu_hat / (sd_hat ** 2), then dividing every weight by the sum of the absolute raw weights, which puts both portfolios on the same gross exposure. - Draw a fresh out of sample path of 2520 daily returns per asset from the true parameters. Neither allocation has seen it.
- Score both on that path with the average of
log(1 + r), where r is the weighted sum of the five asset returns on the day. - Repeat steps 4 through 9 for 2000 independent trials at each T, and record the share of trials in which equal weight outscores the optimized weights.
Read the sweep from short T to long T. At the short end, equal weight wins the clear majority of trials. The share falls steadily as T grows, and at the long end the optimized weights win most of them. Somewhere in the middle the two cross. The crossover point moves with the seed and with the spread between the true means. The direction does not move: more data favors the optimizer, less data favors 1/N. To replicate, change the integer inside random.seed(20260816) and run the sweep again.
Why the optimizer amplifies a bad mean estimate
Look at where the estimates enter the weight. The estimated mean sits in the numerator, the estimated variance in the denominator. Double the mean estimate and the weight doubles. Shave a quarter off the variance estimate and the weight rises by a third. In a lucky sample both errors point the same way: a run of good draws lifts the measured mean and often damps the measured variance over the same window. The optimizer reads that asset as the standout of the group and sizes it accordingly. Out of sample it is an ordinary member of the group again. Equal weight never saw the lucky sample.
The simulation is the gentle version of this problem. Five uncorrelated assets leave a diagonal covariance matrix with nothing to invert. Real portfolios are correlated, and a sample covariance matrix built from barely more observations than assets sits close to singular. Inverting it multiplies small input errors into very large weights, which is how an optimizer arrives at a position worth several times capital in one name against an offsetting short in its close substitute. Our note on concentration risk in a portfolio covers what that kind of weight does to a drawdown.
The input does not sit still either. The panel below tracks a rolling one year average daily return, annualized and sampled monthly, for a broad index fund alongside a large consumer staples name.
The exact SQL behind every number
WITH prices AS
(
SELECT
ticker,
date,
toFloat64(max(close)) AS c
FROM global_markets.stocks_daily_aggs
WHERE ticker IN ('SPY', 'KO')
AND date >= '2015-10-01'
AND date < '2025-01-01'
GROUP BY ticker, date
),
rets AS
(
SELECT
ticker,
date,
c / lagInFrame(c, 1) OVER (PARTITION BY ticker ORDER BY date ASC ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) - 1 AS ret
FROM prices
),
trailing AS
(
SELECT
ticker,
date,
avg(ret) OVER (PARTITION BY ticker ORDER BY date ASC ROWS BETWEEN 251 PRECEDING AND CURRENT ROW) * 252 * 100 AS trailing_mean_pct,
row_number() OVER (PARTITION BY ticker ORDER BY date ASC) AS i
FROM rets
WHERE isFinite(ret)
)
SELECT
toString(toStartOfMonth(date)) AS month,
round(avgIf(trailing_mean_pct, ticker = 'SPY'), 1) AS spy_trailing_mean_pct,
round(avgIf(trailing_mean_pct, ticker = 'KO'), 1) AS ko_trailing_mean_pct
FROM trailing
WHERE i >= 252
AND date >= '2017-01-01'
GROUP BY month
ORDER BY month ASCEvery point on those two lines is a number some optimizer would have accepted as an expected return on that date. The first monthly reading for SPY is 17.7% and the last is 25.8%, with 96 readings in between. KO opens the same window at -0.4%. A weighting scheme that consumes this series month by month inherits every wobble in it.
Do half Kelly and shrinkage fix it?
They soften it. Four patches turn up in practice, and each one trades away part of the theoretical optimum for less sensitivity to the estimate.
- Half Kelly halves every weight. The Kelly growth curve is flat near its peak and steep below it, so a half stake gives up a small share of the theoretical growth rate while cutting the damage from an overstated edge by much more. Volatility targeting applies the same instinct to the denominator, sizing from the one input a short sample can supply.
- Shrinkage pulls every estimated mean toward the cross sectional average of the estimates, by an amount that grows as the sample shrinks. Push it to the limit and every asset carries the same expected return; with equal volatilities the optimizer then returns equal weight on its own. Equal weight is the fully shrunk portfolio.
- Constraints, meaning no shorting and a cap per position, bound how far one lucky estimate can push a single weight. They are damage control rather than an improvement in the estimate.
- Dropping expected returns entirely leaves minimum variance and risk parity, which use only the covariance. That is the input the sample can actually supply.
None of the four manufactures information the sample does not contain. What they change is how much of the portfolio rides on information you may not have.
When optimization earns its keep
The crossover runs in both directions. Once T grows large relative to the spread between the true means, the optimized allocation pulls ahead and stays there, and the wider that spread, the earlier it happens. Optimization is also the right tool whenever the input is one the data supplies: covariance and hedge ratios estimate far better than expected returns do.
Equal weight carries its own bill. It ignores information you genuinely have, and in a small universe it concentrates as heavily as any optimizer would. Treat it as the baseline any candidate weighting has to beat on data the weights never saw. Fitting weights and scoring them on the same sample is look-ahead bias in backtesting wearing a different hat, and it flatters the optimizer every time. For an honest range around a measured edge instead of a single point estimate, bootstrapped confidence intervals are the standard tool.
FAQ
Does equal weight really beat mean variance optimization?
In short samples it often does. An optimizer needs an estimate of each asset's expected return, and that estimate carries a standard error of roughly the asset's volatility divided by the square root of the sample length in years. While that error stays larger than the true differences between the assets, the optimizer is sorting noise. Equal weight has no estimate to get wrong.
How many observations do you need to estimate an expected return?
More than almost anyone has. For an asset with 20 percent volatility, one year of daily data leaves a standard error near 20 percentage points a year, and halving that error takes four times the calendar span. Adding intraday observations inside the same year does not help. Volatility, by contrast, is usable from a few months of data.
Is half Kelly simply a smaller bet?
It is a smaller bet on favorable terms. The Kelly growth curve is flat near its peak, so halving the stake gives up a small share of the theoretical growth rate while roughly halving the volatility of the path and shrinking the cost of an overstated edge.
What is the 1/N portfolio?
It is the portfolio that puts an equal fraction of capital into each of N positions and rebalances on a schedule. It needs no return forecast and no covariance estimate, which is what makes it the standard benchmark for every weighting scheme that does need them.
How the panels are built
All three panels read daily closes and compute simple close to close returns, with duplicate rows collapsed to one close per ticker per date. Means are annualized by multiplying the average daily return by 252 sessions; volatilities are annualized by multiplying the daily standard deviation by the square root of 252. The yearly panel keeps a calendar year only where at least 200 sessions are present. The block panel uses non overlapping blocks and discards the final partial block at each length, and its window count column shows how many blocks each length produced. No dividends are included anywhere, so these are price returns and the level of any mean is understated for dividend payers. The spread of the estimates is the point of the panels, not the level.
Every panel here carries the exact SQL beneath the chart, and the simulation carries its seed. To run the same estimate stability check across your own list of names, ask for it in plain English on the Strasmore terminal.