Turnover Analysis: The Transaction Cost Reality Check
A factor that looks great on paper may not survive transaction costs. Turnover analysis quantifies how much your portfolio changes over time and whether the net edge is positive.
You have a factor with IC = 0.04 and a long-short spread of 0.15% per day. Sounds great. But if your portfolio turns over 50% per day, you’re paying 0.15% × 50 = 7.5% per day in transaction costs. Your net edge is gone.
Turnover analysis is how you avoid this trap.
What Is Portfolio Turnover?
Portfolio turnover measures how much your portfolio weights change over time:
Where w_i are the portfolio weights for each asset. Turnover of 50% means half your portfolio changed in that period.
In a long-short equity strategy:
- Daily rebalancing → 50-100% annual turnover (depends on factor IC decay)
- Weekly rebalancing → 15-30% annual turnover
- Monthly rebalancing → 5-15% annual turnover
The Net Edge Calculation
For a long-short strategy, the net edge after transaction costs is:
Where TransactionCostRate is the cost of trading 1% of portfolio (typically 1-5 basis points for liquid equities, higher for illiquid stocks).
import numpy as np
import pandas as pd
def compute_portfolio_turnover(weights_df):
"""
Compute turnover for each rebalancing period.
Parameters
----------
weights_df : DataFrame (date × asset)
Portfolio weights over time
Returns
-------
turnover_series : Series
Turnover at each rebalancing date
"""
turnover = []
dates = weights_df.index
for i in range(1, len(dates)):
w_prev = weights_df.iloc[i-1].fillna(0)
w_curr = weights_df.iloc[i].fillna(0)
t = 0.5 * np.sum(np.abs(w_curr - w_prev))
turnover.append({'date': dates[i], 'turnover': t})
return pd.DataFrame(turnover).set_index('date')['turnover']
def compute_net_edge(weights_df, returns_df, spread_bps=10, cost_per_pct_bps=5):
"""
Compute net edge after transaction costs.
Parameters
----------
weights_df : DataFrame (date × asset)
Portfolio weights
returns_df : DataFrame (date × asset)
Asset returns
spread_bps : float
Gross long-short spread in basis points per day
cost_per_pct_bps : float
Transaction cost per 1% turnover in basis points
"""
turnover = compute_portfolio_turnover(weights_df)
# Daily gross spread earned
daily_spread = spread_bps / 10000
# Daily turnover rate
daily_turnover_rate = turnover / 100 # Convert % to fraction
# Transaction cost per day
daily_cost = daily_turnover_rate * (cost_per_pct_bps / 10000)
# Net edge
net_edge = daily_spread - daily_cost
return {
'mean_turnover_pct': turnover.mean(),
'mean_daily_cost_bps': daily_cost.mean() * 10000,
'net_edge_daily_bps': net_edge.mean() * 10000,
'net_edge_annual_bps': net_edge.mean() * 252 * 10000
}
Why Turnover Matters More Than You Think
Compounding effect. A 40% annual turnover strategy pays transaction costs on 40% of the portfolio every year. Over 10 years, you’ve paid 400% of the portfolio in transaction costs. A 10% turnover strategy pays 100% over 10 years.
Bid-ask spread. For illiquid stocks, the bid-ask spread can be 10-50 bps. Trading 20% of the portfolio in illiquid stocks means paying 2-10 bps per rebalance. This can easily exceed the gross edge.
Market impact. Large orders move the market against you. If you’re trading 5% of a stock’s daily volume, your market impact can be 10-30 bps. This is especially problematic for small-cap factors that trade illiquid names.
Turnover and Factor Decay Are Linked
The faster your factor decays (short half-life), the faster your portfolio ranking changes, and the higher your turnover. This is why factor half-life is a critical determinant of viability.
A factor with IC = 0.05 but a 2-day half-life requires daily rebalancing to capture the signal. If your transaction costs exceed the edge, the factor is not tradeable.
A factor with IC = 0.03 but a 30-day half-life only requires monthly rebalancing. The lower transaction costs may make it more viable.
Practical Thresholds
| Annual Turnover | Assessment |
|---|---|
| < 20% | Low — most factors can sustain this |
| 20-50% | Moderate — requires low transaction costs |
| 50-100% | High — requires very liquid markets or large edge |
| > 100% | Very high — usually not viable after costs |
For a long-short equity strategy targeting 10-20% annual return, you want annual turnover < 50%. For a market-neutral strategy, you want < 30%.
Common Pitfalls
Only reporting gross spread. A common mistake: reporting the backtest return without accounting for transaction costs. Always report net return after estimated transaction costs.
Underestimating market impact. Transaction cost models that only include broker commissions and exchange fees miss market impact. For factors trading small-cap stocks, market impact can be 5-10x the spread.
Ignoring rebalancing frequency. If your backtest rebalances daily but your strategy rebalances monthly, your turnover estimate is wrong by a factor of 20.
Where It Fits
Turnover analysis is a critical component of backtesting that is often neglected. Factor decay studies determine the optimal rebalancing frequency. Factor combination must account for the turnover of each factor when building multi-factor portfolios.
References
- López de Prado, Advances in Financial Machine Learning, Ch. 10. Wiley, 2018. ISBN: 978-1-119-48208-6.
- Ang, Asset Management, Ch. 12. Oxford University Press, 2014. ISBN: 978-0-19-995932-3.
- Garleanu, Pedersen, “Dynamic Portfolio Choice with Friction,” Journal of Financial Economics, 2016.