Transaction Costs & Market Impact: Models & Analysis › Estimating Transaction Cost Parameters

Michael BrenndoerferJanuary 8, 20267 min read
Quantitative FinanceAll chapters
Part 14 of 15Strategy Development and Backtesting
Chapter 74Transaction Costs & Market Impact: Models & Analysis › Estimating Transaction Cost Parameters
Reading section 6 of 16 · Estimating Transaction Cost Parameters · 7 minSection 6 of 16 · 7 min

Book contents

Section 6 of 16 · 7 min read

Transaction Costs & Market Impact: Models & Analysis › Estimating Transaction Cost ParametersOne section · Sections in this chapter6 / 16 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Modeling transaction costs requires estimating the relevant parameters from market data. These parameters vary across assets, time periods, and market conditions. Accurate estimation is essential for realistic backtesting and strategy evaluation. The challenge lies in the fact that we can only observe transaction costs for trades we execute, and these observations are inherently noisy due to the many factors influencing each individual trade. Nevertheless, systematic approaches to estimation can yield useful parameter values that significantly improve our cost models.

Spread Estimation

The bid-ask spread can be estimated directly from quote data or inferred from trade data when quotes are unavailable. The choice of method depends on the data available and the precision required.

From quote data: The quoted spread at time tt is simply:

st=askt−bidts_t = \text{ask}_t - \text{bid}_t

where:

  • sts_t: quoted spread at time tt
  • askt\text{ask}_t: lowest sell price
  • bidt\text{bid}_t: highest buy price

This direct measurement is straightforward when high-frequency quote data is available. However, the quoted spread may overstate actual trading costs if orders execute at prices better than the posted quotes, a phenomenon called price improvement.

The effective spread, which accounts for orders that execute at prices better than the quoted spread, is calculated from trade data:

seff=2⋅Dt⋅(Pt−Mt)s_{\text{eff}} = 2 \cdot D_t \cdot (P_t - M_t)

where:

  • seffs_{\text{eff}}: effective spread
  • DtD_t: direction indicator (+1 for buys, -1 for sells)
  • PtP_t: trade price
  • MtM_t: quote midpoint at trade time

The factor of 2 appears because we measure the distance from the midpoint to the execution price, which represents half the round-trip cost. Multiplying by the direction indicator DtD_t ensures that the spread is positive regardless of whether the trade was a buy or a sell. A buy trade should execute above the midpoint (positive deviation), while a sell trade should execute below (negative deviation), so the direction indicator aligns these cases.

Roll's spread estimator: When quote data is unavailable, Roll (1984) showed that the spread can be estimated from the autocovariance of price changes:

s^=2−Cov(ΔPt,ΔPt−1)\hat{s} = 2\sqrt{-\text{Cov}(\Delta P_t, \Delta P_{t-1})}

where:

  • s^\hat{s}: estimated effective spread
  • Cov\text{Cov}: covariance operator
  • ΔPt\Delta P_t: price change (Pt−Pt−1P_t - P_{t-1})

This estimator relies on the fact that bid-ask bounce creates negative serial correlation in observed price changes. If the true value is stable, a buy trade executed at the ask followed by a sell trade at the bid results in a negative price change, while a sell followed by a buy results in a positive change. This oscillation creates a negative covariance proportional to the square of the spread.

When prices bounce between bid and ask without any change in fundamental value, consecutive returns tend to have opposite signs: an uptick to the ask is often followed by a downtick to the bid, and vice versa. This bid-ask bounce creates a predictable negative correlation. Roll showed that under idealized conditions, the covariance equals −s2/4-s^2/4, where ss is the spread, which leads directly to the estimator above.

In[15]:
Code
# Simulate trade data with bid-ask bounce
np.random.seed(42)

n_trades = 10000
true_spread = 0.02  # 2 cent spread on a $50 stock
mid_price = 50.0

# Simulate efficient price (random walk)
efficient_returns = np.random.normal(0, 0.0001, n_trades)
efficient_price = mid_price * np.exp(np.cumsum(efficient_returns))

# Simulate trade prices with bid-ask bounce
buy_sell = np.random.choice([-1, 1], size=n_trades)  # Random buy/sell
trade_prices = efficient_price + buy_sell * true_spread / 2

# Estimate spread using Roll's estimator
price_changes = np.diff(trade_prices)
autocovariance = np.cov(price_changes[:-1], price_changes[1:])[0, 1]
roll_spread = 2 * np.sqrt(-autocovariance) if autocovariance < 0 else np.nan

# Direct spread calculation if we had bid-ask data
# (In practice, you'd use actual bid-ask quotes)
direct_spread_pct = true_spread / mid_price * 100
estimated_spread_pct = (
    roll_spread / np.mean(trade_prices) * 100
    if not np.isnan(roll_spread)
    else np.nan
)
error_pct = abs(roll_spread - true_spread) / true_spread * 100
Out[16]:
Console
Spread Estimation Results:
----------------------------------------
True spread: $0.0200 (0.04%)
Roll estimator: $0.0200 (0.04%)
Estimation error: 0.2%

The Roll estimator closely approximates the true spread using only close-to-close price data. This demonstrates how transaction costs can be inferred from price dynamics even when high-frequency quote data is unavailable, though the estimate becomes noisy in the absence of a strong bid-ask bounce signal.

Impact Parameter Estimation

Estimating market impact parameters requires data on trade sizes, volumes, and subsequent price movements. The typical approach is to regress observed price changes on participation rates. This regression framework allows us to estimate both the impact coefficient η\eta and the impact exponent γ\gamma simultaneously.

For the square-root model, taking logs turns the relationship into a linear regression:

log⁡(Impact)=log⁡(ση)+γlog⁡(QV)\log(\text{Impact}) = \log(\sigma \eta) + \gamma \log\left(\frac{Q}{V}\right)

where:

  • Impact\text{Impact}: market impact cost
  • σ\sigma: asset volatility
  • η\eta: impact coefficient
  • γ\gamma: impact exponent
  • QQ: trade quantity
  • VV: daily trading volume

This allows estimation of both η\eta and γ\gamma using standard regression techniques. The slope of the regression in log-log space directly estimates γ\gamma, while the intercept allows us to back out η\eta once we know the volatility. This linearization is useful because it turns a potentially complex nonlinear estimation problem into a simple ordinary least squares regression.

In[17]:
Code
from scipy import stats

# Simulate market impact data
np.random.seed(123)

n_observations = 500
daily_volume = 5_000_000  # 5 million shares
daily_vol = 0.02

# True parameters
true_eta = 0.15
true_gamma = 0.5

# Generate random trade sizes (1,000 to 500,000 shares)
trade_sizes = np.random.uniform(1000, 500000, n_observations)
participation_rates = trade_sizes / daily_volume

# Generate observed impacts with noise
true_impacts = daily_vol * true_eta * (participation_rates**true_gamma)
noise = np.random.normal(0, 0.001, n_observations)
observed_impacts = np.maximum(true_impacts + noise, 0.0001)  # Ensure positive

# Regression to estimate parameters
log_impacts = np.log(observed_impacts)
log_participation = np.log(participation_rates)

slope, intercept, r_value, p_value, std_err = stats.linregress(
    log_participation, log_impacts
)

estimated_gamma = slope
estimated_eta = np.exp(intercept) / daily_vol
r_squared = r_value**2
fitted_line = intercept + slope * log_participation
Out[18]:
Console
Market Impact Parameter Estimation:
---------------------------------------------
Parameter                  True    Estimated
---------------------------------------------
Gamma (exponent)          0.500        0.281
Eta (coefficient)         0.150        0.060

Regression R-squared: 0.0548
Out[19]:
Visualization
Scatter plot of log impact versus log participation rate with a visible lower floor and a fitted regression line whose slope is 0.281.
Regression of log market impact on log participation rate. Additive noise and clipping at a positive floor flatten the fitted slope to gamma = 0.281, well below the true simulated value of 0.5.

The naive regression does not recover the true exponent in this example: it estimates γ≈0.281\gamma \approx 0.281 rather than 0.5. The simulated impacts include additive noise and are clipped to remain positive before taking logs, which creates the horizontal floor visible in the scatter and biases the slope downward. This is a useful warning for empirical work: the log transformation does not repair censoring or a noise model that is poorly matched to the regression. A more careful specification would model the observation process directly or use multiplicative noise before fitting in log space.

In practice, impact parameter estimation is complicated by selection bias (you only observe impacts for trades you made), endogeneity (your trading strategy depends on expected costs), and non-stationarity (parameters vary over time). Sophisticated estimation methods address these issues, but even simple estimates provide valuable guidance for strategy development.