Liquidity Risk and Other Non-Market Risks › Measuring Liquidity Risk

Michael BrenndoerferDecember 24, 202511 min read
Quantitative FinanceAll chapters
Part 10 of 15Risk Management and Financial Risk Controls
Chapter 57Liquidity Risk and Other Non-Market Risks › Measuring Liquidity Risk
Reading section 6 of 14 · Measuring Liquidity Risk · 11 minSection 6 of 14 · 11 min

Book contents

Section 6 of 14 · 11 min read

Liquidity Risk and Other Non-Market Risks › Measuring Liquidity RiskOne section · Sections in this chapter6 / 14 ↓
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

Unlike market risk, where VaR provides a standardized measure, liquidity risk lacks a single dominant metric. We use multiple complementary measures to assess different dimensions of liquidity.

No single measure captures liquidity risk because market liquidity and funding liquidity are distinct phenomena. Liquidity varies across time, across assets, and across market conditions. What matters is current liquidity and how liquidity might change under stress. These complexities require a portfolio of metrics rather than a single summary statistic.

Spread-Based Measures

The simplest liquidity measures are based on bid-ask spreads. These measures capture the immediate cost of transacting and provide a first-order approximation of market liquidity:

  • Absolute spread: Best ask minus best bid
  • Relative spread: Spread divided by mid-price (percentage cost)
  • Effective spread: The actual cost of execution, which may differ from quoted spreads due to price improvement or slippage

The absolute spread tells you the dollar cost per share of crossing from bid to ask. The relative spread normalizes this cost as a percentage of the asset price, allowing comparison across assets with different price levels. A $0.10 spread on a $10 stock (1%) is very different from a $0.10 spread on a $100 stock (0.1%), and the relative spread captures this distinction.

The effective spread deserves special attention because it measures the cost traders incur rather than what the order book displays at a moment in time. Trades may execute inside the quoted spread if a market maker offers price improvement, or outside the spread if the order is large enough to consume multiple price levels. By comparing actual execution prices to the contemporaneous mid-price, the effective spread reveals true transaction costs.

In[15]:
Code
def calculate_effective_spread(trade_price, mid_price, side):
    """
    Calculate the effective spread from actual trade data.

    The effective spread measures the actual transaction cost,
    which may differ from the quoted spread.
    """
    if side == "buy":
        effective_half_spread = trade_price - mid_price
    else:  # sell
        effective_half_spread = mid_price - trade_price

    # Full round-trip cost
    effective_spread = 2 * effective_half_spread
    relative_effective = effective_spread / mid_price * 100

    return effective_spread, relative_effective


# Simulated trade data
np.random.seed(42)
n_trades = 1000
mid_prices = 100 + np.cumsum(np.random.randn(n_trades) * 0.1)
quoted_spreads = 0.05 + np.random.exponential(
    0.02, n_trades
)  # Varies around 5 cents
sides = np.random.choice(["buy", "sell"], n_trades)

# Trade prices with realistic execution
trade_prices = []
for i, (mid, spread, side) in enumerate(zip(mid_prices, quoted_spreads, sides)):
    # Add some price improvement (negative) or slippage (positive)
    execution_noise = np.random.normal(0, 0.01)
    if side == "buy":
        trade_prices.append(mid + spread / 2 + execution_noise)
    else:
        trade_prices.append(mid - spread / 2 - execution_noise)

# Calculate effective spreads
effective_spreads = []
for tp, mid, side in zip(trade_prices, mid_prices, sides):
    eff, _ = calculate_effective_spread(tp, mid, side)
    effective_spreads.append(eff)

avg_quoted = np.mean(quoted_spreads)
avg_effective = np.mean(effective_spreads)
Out[16]:
Console
Spread Analysis from Trade Data
----------------------------------------
Average quoted spread:    $0.0702
Average effective spread: $0.0701
Ratio (effective/quoted): 1.00

Volume-Based Measures

Volume metrics capture how easily large positions can be traded. While spread-based measures focus on the cost of small transactions, volume-based measures address the capacity dimension of liquidity:

  • Average daily volume (ADV): The typical trading volume over a period
  • Days to liquidate: Position size divided by a sustainable daily trading rate (typically 10-25% of ADV)
  • Participation rate: Your trading volume as a fraction of total market volume

Average daily volume establishes a benchmark for what the market normally absorbs. Trading significantly more than average volume signals to the market that something unusual is occurring and typically triggers adverse price movements. The "days to liquidate" metric translates position sizes into time, revealing how long it would take to exit a position at a sustainable pace.

The participation rate, sometimes called the "footprint," measures how visible your trading is to other market participants. A 5% participation rate means you are contributing one of every twenty trades; you are present but not dominant. A 50% participation rate means you are half of all market activity; you cannot hide, and other participants will adjust their behavior in response to your presence. High participation rates both increase price impact and expose you to predatory trading from others who recognize your need to transact.

In[17]:
Code
def calculate_liquidation_time(
    position_value, avg_daily_volume, avg_price, max_participation=0.10
):
    """
    Estimate the time required to liquidate a position
    without excessive market impact.

    Parameters:
    -----------
    position_value : total value of position to liquidate
    avg_daily_volume : average daily share volume
    avg_price : current price per share
    max_participation : maximum acceptable fraction of daily volume (default 10%)
    """
    position_shares = position_value / avg_price
    daily_capacity = avg_daily_volume * max_participation
    days_to_liquidate = position_shares / daily_capacity

    return {
        "position_shares": position_shares,
        "daily_capacity": daily_capacity,
        "days_to_liquidate": days_to_liquidate,
    }


# Portfolio with varying liquidity profiles
portfolio = {
    "AAPL": {"value": 50_000_000, "adv": 70_000_000, "price": 175},
    "MSFT": {"value": 40_000_000, "adv": 25_000_000, "price": 380},
    "SmallCap1": {"value": 10_000_000, "adv": 500_000, "price": 45},
    "SmallCap2": {"value": 5_000_000, "adv": 100_000, "price": 22},
}

liquidation_analysis = {}
for name, data in portfolio.items():
    liquidation_analysis[name] = calculate_liquidation_time(
        data["value"], data["adv"], data["price"]
    )

total_portfolio_value = sum(p["value"] for p in portfolio.values())
max_liquidation_days = max(
    a["days_to_liquidate"] for a in liquidation_analysis.values()
)
Out[18]:
Console
Liquidation Analysis (10% daily participation limit)
============================================================
Position            Value          ADV  Days to Liquidate
------------------------------------------------------------
AAPL         $      50.0M $      70.0M               0.0
MSFT         $      40.0M $      25.0M               0.0
SmallCap1    $      10.0M $       0.5M               4.4
SmallCap2    $       5.0M $       0.1M              22.7
------------------------------------------------------------
Total        $     105.0M                           22.7 (bottleneck)
Out[19]:
Visualization
Bar chart showing liquidation times of about 0.04 days for AAPL, 0.04 for MSFT, 4.4 for SmallCap1, and 22.7 for SmallCap2, with a dashed five-day target line.
Estimated time to liquidate specific portfolio positions assuming a 10% daily market participation rate. The two large-cap positions can be exited in a fraction of a day, while SmallCap1 takes about 4.4 days and SmallCap2 creates the bottleneck at roughly 22.7 days.

Even though the small-cap positions represent a small fraction of portfolio value, they create a liquidity bottleneck. The entire portfolio cannot be liquidated faster than the least liquid position allows. This is a critical consideration for funds that may face redemption pressure.

The bottleneck effect has practical implications for portfolio construction. A portfolio might have an average liquidity profile that appears acceptable, yet contain positions that would take weeks or months to exit. During a redemption event, investors request cash, not proportional slices of each position. If the liquid positions are sold first to meet redemptions, the remaining portfolio becomes increasingly illiquid, potentially trapping remaining investors in positions they cannot exit. Managing this risk requires understanding both the portfolio's liquidity distribution and its average.

Liquidity-Adjusted Value at Risk (LVaR)

Standard VaR assumes positions can be liquidated instantaneously at current market prices. Liquidity-adjusted VaR incorporates the time and cost required to exit positions.

The gap between standard VaR and the reality of trading in markets represents a significant model risk. VaR answers the question: "How much could you lose if prices move against you over some horizon?" But this question implicitly assumes you can exit at those prices. In reality, the process of exiting, especially in stressed markets when others are also trying to exit, will incur additional costs. LVaR attempts to capture these costs within the VaR framework.

The simplest LVaR adjustment adds the expected liquidation cost to the standard VaR:

LVaR=VaR+Liquidation Cost\text{LVaR} = \text{VaR} + \text{Liquidation Cost}

where:

  • LVaR: Liquidity-adjusted Value at Risk
  • VaR: standard Value at Risk (market risk)
  • Liquidation Cost: estimated cost to close the position, accounting for spread, price impact, and time to liquidate

This additive approach treats liquidity cost as deterministic, which is a simplification. In reality, liquidity costs are uncertain and often correlate with market stress: spreads widen, market depth evaporates, and price impact increases precisely when VaR losses are being realized. More sophisticated LVaR approaches model the joint distribution of market returns and liquidity costs, recognizing that the worst market losses often coincide with the worst liquidity conditions.

The components of liquidation cost each capture a distinct friction. The spread cost reflects the immediate penalty of crossing the bid-ask spread to execute. The market impact cost reflects the price movement caused by the trade itself. The extended holding risk captures the additional market risk incurred when positions cannot be liquidated within the standard VaR horizon. For illiquid positions that take days or weeks to exit, this extended holding period substantially increases risk exposure.

In[20]:
Code
def calculate_lvar(
    position_value,
    standard_var,
    spread_pct,
    days_to_liquidate,
    daily_volatility,
    confidence=0.99,
):
    """
    Calculate Liquidity-adjusted VaR.

    Components:
    1. Standard VaR (market risk)
    2. Bid-ask spread cost
    3. Market impact cost
    4. Additional risk from extended liquidation period
    """
    # Spread cost (half spread for one-way trade)
    spread_cost = position_value * spread_pct / 2

    # Market impact (using simplified square-root impact)
    impact_cost = (
        position_value * 0.5 * daily_volatility * np.sqrt(days_to_liquidate)
    )

    # Extended holding risk (if liquidation takes longer than VaR horizon)
    # Additional risk scales with sqrt of time
    if days_to_liquidate > 1:
        var_scaling = np.sqrt(days_to_liquidate)
        extended_var = standard_var * (var_scaling - 1)
    else:
        extended_var = 0

    lvar = standard_var + spread_cost + impact_cost + extended_var

    return {
        "standard_var": standard_var,
        "spread_cost": spread_cost,
        "impact_cost": impact_cost,
        "extended_risk": extended_var,
        "lvar": lvar,
        "adjustment_pct": (lvar / standard_var - 1) * 100,
    }


# Example calculation
lvar_result = calculate_lvar(
    position_value=10_000_000,  # $10M position
    standard_var=500_000,  # $500K VaR
    spread_pct=0.003,  # 30 bps spread
    days_to_liquidate=5,  # 5 days to exit
    daily_volatility=0.02,  # 2% daily vol
)
Out[21]:
Console
Liquidity-Adjusted VaR Calculation
---------------------------------------------
Standard VaR (99%, 1-day):  $     500,000
Spread cost:                $      15,000
Market impact cost:         $     223,607
Extended holding risk:      $     618,034
---------------------------------------------
Liquidity-Adjusted VaR:     $   1,356,641
Adjustment: +171.3%
Out[22]:
Visualization
Stacked bar chart comparing standard VaR against LVaR decomposed into spread cost, impact cost, and extended holding risk components, with a dashed line marking the total LVaR level.
Decomposition of Liquidity-Adjusted Value at Risk (LVaR) into its constituent components. Incorporating spread costs, market impact, and extended holding-period risk raises the estimate from $500K to about $1.36M—roughly 2.7 times standard VaR.

The liquidity adjustment raises the risk estimate from 500,000toabout500,000 to about 1.36 million in this example, or roughly 2.7 times standard VaR. For portfolios with significant illiquid holdings, the gap between standard VaR and LVaR can be even larger, revealing hidden risks that standard measures miss.

This substantial adjustment illustrates why liquidity risk cannot be treated as a footnote to market risk. For this position, standard VaR is about 63% below the LVaR estimate. Risk managers who rely solely on standard VaR may believe they are operating with comfortable risk buffers when in fact their true exposure is much larger. The difference between VaR and LVaR represents a form of "liquidity illusion" that can prove fatal during market stress.