Skip to article frontmatterSkip to article content
Contents
Kyle, Glosten-Milgrom en liquiditeit
Maarten Nies

Kyle, Glosten-Milgrom en liquiditeit

1Overzicht

Dit college vraagt hoe private informatie via de handel in de prijs komt, en of beleggers betaald krijgen voor de kosten die dat meebrengt. Een market maker die weet dat een deel van zijn klanten meer weet, moet een spread en een prijsimpact rekenen, zodat de prijs de informatie geleidelijk opneemt. Illiquiditeit gaat samen met hogere verwachte en lagere gelijktijdige rendementen, maar de premie op liquiditeitsrisico is na publicatie niet meer te zien.

  • We leiden de spread van Glosten en Milgrom af en het evenwicht van Kyle, met één en met vele handelsronden.

  • We vertalen die theorie met de maten van Roll en Amihud naar dagkoersen, en volgen hoe liquiditeit als niveau en als risico in verwachte rendementen komt.

  • We simuleren hoe vaak de Roll-maat in een kwartaal of een jaar niet te berekenen is.

  • We repliceren op vijftig grote Amerikaanse aandelen dat illiquiditeit piekt in 2008 en maart 2020 en samengaat met lage marktrendementen Amihud (2002), en op de reeks van Pástor en Stambaugh dat liquiditeit in crisismaanden instort.

Tot het midden van de jaren tachtig was de prijs in de theorie een getal dat “de markt” zette, zonder bied- of laatkoers en zonder een handelaar die hem noteerde. Glosten en Milgrom Glosten & Milgrom (1985) lieten in 1985 zien dat een concurrerende market maker (een handelaar die altijd bereid is te kopen en te verkopen) toch een spread moet rekenen. De reden is dat een deel van zijn tegenpartijen beter geïnformeerd is. Kyle Kyle (1985) liet in hetzelfde jaar zien hoe een insider zijn informatie verstopt achter ongeïnformeerde order flow (de som van alle koop- en verkooporders). Beide modellen zijn theorieën met toetsbare voorspellingen, maar de empirie die volgde, van Roll Roll (1984) tot Pástor en Stambaugh Pástor & Stambaugh (2003), maakte van liquiditeit een risicofactor en daarmee een feit dat nog op een verklaring wacht. Santa-Clara zet dat feit in zijn lijst van wat we weten zo neer Santa-Clara (2026):

Liquidity is priced, and it disappears when you need it.

2Intuïtie: waarom zou dit waar zijn?

Denk aan een handelaar die iedereen bedient die wil kopen of verkopen. De meeste klanten handelen om redenen die niets met de waarde van het aandeel te maken hebben, zoals een pensioenfonds dat geld nodig heeft. Een klein deel weet iets, en de handelaar kan de groepen niet uit elkaar houden. Verkoopt hij aan elke koper tegen de gemiddelde waarde, dan verliest hij stelselmatig aan de geïnformeerde kopers, want die kopen juist als het aandeel meer waard is.

Hij verkoopt daarom tegen de waarde gegeven dat iemand wil kopen, en koopt tegen de waarde gegeven dat iemand wil verkopen. Het verschil is geen winstmarge, maar een premie die de ongeïnformeerde klanten betalen voor wat de handelaar aan de geïnformeerde verliest. Na een reeks kooporders gelooft hij in goed nieuws, zodat zijn koersen omhoog schuiven en de spread kleiner wordt naarmate hij zekerder is.

Kyle voegde de strategie van de insider toe. Een insider die de waarde kent, wil veel kopen, maar elke extra order drijft de koers op en verraadt hem. Hij verstopt zijn orders daarom tussen die van de ongeïnformeerde handelaren, en hoe meer ruis er is, hoe meer hij kan kopen zonder op te vallen. Zijn winst komt uit de zak van de ongeïnformeerde handelaren.

Als handelen zo geld kost, vragen beleggers voor illiquide aandelen een hoger rendement. Liquiditeit verdwijnt bovendien voor iedereen tegelijk, zoals in 1987, bij de val van LTCM in 1998 (VaR, RiskMetrics en LTCM), in 2008 en in maart 2020, en dus precies wanneer beleggers moeten verkopen. We verwachten daarom dat de spread het grootst is bij de grootste onzekerheid en met elke order krimpt. Verder verwachten we dat illiquide aandelen gemiddeld meer opleveren en dat koersen dalen in een maand waarin de liquiditeit onverwacht opdroogt.

3Toy-voorbeeld: één market maker, één order, één insider

De eerste codecel laadt de pakketten die het hele college gebruikt.

import io

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import requests
from scipy import stats

import hap
from hap import cache as hap_cache
from hap import data as hap_data
hap.plotting.setup()

rng = np.random.default_rng(20240101)

3.1Glosten-Milgrom met één transactie

grootheidwaarde
waarde van het aandeel morgenVH=102V_H = 102 of VL=98V_L = 98 euro, elk met kans 12\tfrac12
geïnformeerde handelarenfractie μ=0,2\mu = 0{,}2, kopen bij VHV_H en verkopen bij VLV_L
ongeïnformeerde handelarenkopen of verkopen met kans 12\tfrac12
market makerrisiconeutraal, zonder kosten, verdient door concurrentie verwacht niets

Omdat de market maker verwacht niets verdient, is zijn laatkoers de verwachte waarde gegeven een kooporder en zijn biedkoers de verwachte waarde gegeven een verkooporder. Met de regel van Bayes volgen die koersen in vijf stappen.

  1. De kans op een koop is 0,2+0,8⋅12=0,60{,}2 + 0{,}8\cdot\tfrac12 = 0{,}6 als de waarde hoog is en 0,8⋅12=0,40{,}8\cdot\tfrac12 = 0{,}4 als ze laag is, zodat P(koop)=0,5P(\text{koop}) = 0{,}5.

  2. Dan is P(H∣koop)=0,5⋅0,6/0,5=0,6P(H\mid\text{koop}) = 0{,}5\cdot0{,}6/0{,}5 = 0{,}6 en P(H∣verkoop)=0,4P(H\mid\text{verkoop}) = 0{,}4.

  3. De laatkoers is 0,6⋅102+0,4⋅98=100,400{,}6\cdot102 + 0{,}4\cdot98 = 100{,}40 en de biedkoers 0,4⋅102+0,6⋅98=99,600{,}4\cdot102 + 0{,}6\cdot98 = 99{,}60, dus de spread is 0,80=μ(VH−VL)0{,}80 = \mu(V_H - V_L).

  4. Na een koop is het oordeel 0,60{,}6, zodat een volgende koop kans 0,6⋅0,6+0,4⋅0,4=0,520{,}6\cdot0{,}6 + 0{,}4\cdot0{,}4 = 0{,}52 heeft, waarvan 0,6⋅0,6=0,360{,}6\cdot0{,}6 = 0{,}36 bij een hoge waarde. De laatkoers wordt dan 98+4⋅0,36/0,52=100,7798 + 4\cdot0{,}36/0{,}52 = 100{,}77 en de biedkoers 98+4⋅0,6⋅0,4/0,48=100,0098 + 4\cdot0{,}6\cdot0{,}4/0{,}48 = 100{,}00.

  5. De verwachte volgende transactieprijs is 0,52⋅100,77+0,48⋅100,00=100,400{,}52\cdot100{,}77 + 0{,}48\cdot100{,}00 = 100{,}40, gelijk aan de huidige.

De spread krimpt dus van 0,80 naar 0,77 euro zodra de market maker iets heeft geleerd, en de prijs die we na één koop verwachten, is de prijs van nu. Die tweede eigenschap heet de martingaaleigenschap.

3.2Kyle met één handelsronde

grootheidwaarde
liquidatiewaarde vvnormaal, verwachting p0=50p_0 = 50 euro, standaarddeviatie σv=4\sigma_v = 4 euro
order uu van de noise tradersnormaal, verwachting nul, standaarddeviatie σu=10 000\sigma_u = 10\,000 aandelen
insiderkent vv en kiest zijn order
market makerziet alleen de totale order flow en zet één prijs

Het evenwicht van Kyle nemen we hier over zonder afleiding, want de afleiding volgt pas in de theorie. De insider koopt β=σu/σv\beta = \sigma_u/\sigma_v aandelen per euro dat de waarde boven p0p_0 ligt, en de prijs stijgt λ=σv/(2σu)\lambda = \sigma_v/(2\sigma_u) euro per aandeel order flow.

  1. Hier is β=10 000/4=2500\beta = 10\,000/4 = 2500 en λ=4/20 000=0,0002\lambda = 4/20\,000 = 0{,}0002, zodat een order van 10.000 aandelen de prijs 2 euro verschuift.

  2. Bij v=53v = 53 en u=−4000u = -4000 koopt de insider 2500⋅3=75002500\cdot3 = 7500 aandelen, is de order flow 3500 en wordt de prijs 50+0,0002⋅3500=50,7050 + 0{,}0002\cdot3500 = 50{,}70.

  3. De insider verdient 7500⋅2,30=17 2507500\cdot2{,}30 = 17\,250 euro. De noise traders verkochten per saldo 4000 aandelen en verliezen 9200 euro, en de market maker verkocht er 3500 en verliest 8050 euro.

De som is nul, en dat de market maker hier verliest, is toeval, want gemiddeld speelt hij quitte. De codecel rekent beide markten na en zet handberekening en code naast elkaar.

def gm_quotes(pi, mu, v_high, v_low):
    """Bid and ask of a zero-profit Glosten-Milgrom market maker with belief pi = P(V = v_high)."""
    buy_h, buy_l = mu + (1 - mu) / 2, (1 - mu) / 2
    post_buy = pi * buy_h / (pi * buy_h + (1 - pi) * buy_l)
    post_sell = pi * (1 - buy_h) / (pi * (1 - buy_h) + (1 - pi) * (1 - buy_l))
    return v_low + (v_high - v_low) * post_sell, v_low + (v_high - v_low) * post_buy

V_H, V_L, MU = 102.0, 98.0, 0.2
bid0, ask0 = gm_quotes(0.5, MU, V_H, V_L)
pi1 = 0.5 * 0.6 / 0.5                                  # belief after one buy order
bid1, ask1 = gm_quotes(pi1, MU, V_H, V_L)
p_buy_next = pi1 * 0.6 + (1 - pi1) * 0.4
next_price = p_buy_next * ask1 + (1 - p_buy_next) * bid1

sigma_v, sigma_u, p0 = 4.0, 10_000.0, 50.0
beta_k = sigma_u / sigma_v
lam_k = sigma_v / (2 * sigma_u)
v, u = 53.0, -4_000.0
theta = beta_k * (v - p0)                              # insider order
y = theta + u                                          # total order flow
p1 = p0 + lam_k * y
pnl = {"insider": theta * (v - p1), "noise traders": u * (v - p1), "market maker": -y * (v - p1)}

hand_vs_code = pd.DataFrame(
    {
        "met de hand": [99.60, 100.40, 0.80, 100.77, 100.00, 100.40,
                        2500, 0.0002, 50.70, 17_250, -9_200, -8_050, 0],
        "code": [bid0, ask0, ask0 - bid0, ask1, bid1, next_price,
                 beta_k, lam_k, p1, pnl["insider"], pnl["noise traders"], pnl["market maker"],
                 sum(pnl.values())],
    },
    index=[
        "GM: biedkoers", "GM: laatkoers", "GM: spread",
        "GM: laatkoers na een koop", "GM: biedkoers na een koop", "GM: verwachte volgende prijs",
        "Kyle: beta (aandelen per euro)", "Kyle: lambda (euro per aandeel)", "Kyle: prijs",
        "Kyle: winst insider", "Kyle: winst noise traders", "Kyle: winst market maker", "Kyle: som",
    ],
)
assert np.allclose(hand_vs_code["met de hand"], hand_vs_code["code"], atol=0.005)
hand_vs_code.round(4)
Loading...

Code en handberekening komen overeen, op de afronding van de laatkoers na een koop na. De spread van 0,80 euro is wat een ongeïnformeerde klant voor een aan- en verkoop betaalt, alleen omdat een op de vijf handelaren de waarde kent. De prijsimpact λ\lambda is voor de insider een kost van dezelfde soort. Elk aandeel dat hij koopt, drijft zijn eigen koopprijs op, zodat hij bij een voordeel van 3 euro niet onbeperkt koopt.

4Theorie

De theorie gaat in drie stappen van de handelsvloer naar verwachte rendementen en sluit af met het marktontwerp van vandaag. Eerst leiden we de spread van Glosten en Milgrom en het evenwicht van Kyle af. Daarna halen we met de maten van Roll en Amihud dezelfde grootheden uit dagkoersen, en tot slot volgen we liquiditeit als niveau en als risico. De kern is dat spread en prijsimpact geen kosten van de market maker zijn, maar de prijs van informatie.

4.1Opzet en notatie

We gebruiken de symbolen van Kyle, zodat λ\lambda de prijsimpact van order flow is en β\beta de handelsintensiteit van de insider, en niet de prijs van risico of de subjectieve discontofactor. De prijs van marktrisico heet daarom λm\lambda_m. De liquidatiewaarde van het aandeel is vv (de payoff), de order van de insider θ\theta, de order van de noise traders uu en de totale order flow y=θ+uy = \theta + u. Alle handelaren zijn risiconeutraal en de rente is nul.

Bij Glosten en Milgrom komen handelaren één voor één met één aandeel, en zet de market maker vooraf een bied- en een laatkoers. Bij Kyle dienen alle handelaren tegelijk een marktorder in, een order zonder limietprijs, en zet de market maker één prijs op basis van de som. Het eerste model levert dus spreads en het tweede prijsimpact, maar in beide dwingt concurrentie de prijs naar de verwachte waarde van vv gegeven de order flow.

4.2Glosten-Milgrom: de spread als adverse selectie

De spread van Glosten en Milgrom bestaat alleen omdat een order iets verraadt over de waarde. Een market maker die alle orders tegen dezelfde prijs uitvoert, verliest aan geïnformeerde handelaren en wint niets terug van de rest. Omdat een koop op goed nieuws wijst en een verkoop op slecht nieuws, speelt hij alleen quitte als zijn laatkoers boven zijn biedkoers ligt.

Proof 1

Schrijf qH=1+μ2q_H = \tfrac{1+\mu}{2} en qL=1−μ2q_L = \tfrac{1-\mu}{2} voor de kans op een koop gegeven hoge en lage waarde, zodat qH+qL=1q_H + q_L = 1 en qH−qL=μq_H - q_L = \mu. De kans op een koop is Dk=12(1+μ(2π−1))D_k = \tfrac12\bigl(1 + \mu(2\pi-1)\bigr) en op een verkoop Dv=12(1−μ(2π−1))D_v = \tfrac12\bigl(1 - \mu(2\pi-1)\bigr). Bayes geeft a=VL+ΔV πqH/Dka = V_L + \Delta V\,\pi q_H/D_k en b=VL+ΔV πqL/Dvb = V_L + \Delta V\,\pi q_L / D_v, dus

a−b=ΔV π qHDv−qLDkDkDv=ΔV π (1−π)(qH2−qL2)14(1−μ2(2π−1)2),a - b = \Delta V\,\pi\,\frac{q_H D_v - q_L D_k}{D_k D_v} = \Delta V\,\pi\,\frac{(1-\pi)(q_H^2 - q_L^2)}{\tfrac14\bigl(1 - \mu^2(2\pi-1)^2\bigr)},

en met qH2−qL2=(qH−qL)(qH+qL)=μq_H^2 - q_L^2 = (q_H - q_L)(q_H + q_L) = \mu volgt (1). De transactieprijs na order nn is pn=E[V∣Fn]p_n = \E[V\mid\mathcal{F}_n], en omdat Fn⊆Fn+1\mathcal{F}_n \subseteq \mathcal{F}_{n+1}, geeft de toreigenschap E[pn+1∣Fn]=E[V∣Fn]=pn\E[p_{n+1}\mid\mathcal{F}_n] = \E[V\mid\mathcal{F}_n] = p_n. □\square

Het bewijs past de regel van Bayes toe op beide koersen en trekt ze van elkaar af. Bij π=12\pi = \tfrac12 en μ=0,2\mu = 0{,}2 geeft (1) de spread van 0,80 euro uit het toy-voorbeeld. Na één koop is π=0,6\pi = 0{,}6, zodat de noemer 0,99840{,}9984 wordt en de spread 4⋅0,2⋅0,24⋅4/0,9984=0,774\cdot0{,}2\cdot0{,}24\cdot4/0{,}9984 = 0{,}77. De spread is dus het grootst bij de grootste onzekerheid en krimpt naarmate de prijs de informatie opneemt, terwijl een hogere μ\mu hem breder maakt omdat elke order dan meer verraadt.

Zo’n spread bestaat zonder voorraadkosten of risicoaversie en is dus zuiver adverse selection (het verlies aan beter geïnformeerde tegenpartijen), het uitgangspunt van het leerboek van O’Hara O'Hara (1995). De martingaaleigenschap geldt alleen ten opzichte van de publieke informatie, want voor iemand die VV kent, loopt de prijs voorspelbaar op of af. Het model maakt zo de stelling van Samuelson uit Fama en de efficiënte markt precies, want een prijs die een verwachte waarde is, verandert alleen onvoorspelbaar voor wie alleen de orderhistorie kent.

4.3Kyle: het evenwicht met één handelsronde

In de markt van Kyle neemt de prijs na één ronde precies de helft van de onzekerheid over vv weg, gemeten als variantie, hoeveel ruis er ook is. De insider weet dat zijn eigen order de prijs opdrijft. Daarom koopt hij, zoals een monopolist de helft van de concurrerende hoeveelheid levert, de helft van de positie waarbij de prijs tot vv zou stijgen. De market maker zet de prijs op de regressie van vv op yy. Handelt de insider agressiever, dan wordt yy informatiever en stijgt λ\lambda, en een hogere λ\lambda maakt de insider weer voorzichtiger, zodat het evenwicht ligt waar die twee reacties elkaar vinden.

Proof 3

Stap 1: de beste order van de insider bij gegeven λ\lambda. Neem aan dat de market maker p=p0+λyp = p_0 + \lambda y zet met λ>0\lambda > 0. Voor gegeven vv is de verwachte winst E[(v−p0−λθ−λu)θ∣v]=(v−p0)θ−λθ2\E[(v - p_0 - \lambda\theta - \lambda u)\theta \mid v] = (v - p_0)\theta - \lambda\theta^2, omdat E[u]=0\E[u] = 0. Die is strikt concaaf in θ\theta zodra λ>0\lambda > 0, en de eerste-ordevoorwaarde geeft θ=(v−p0)/(2λ)\theta = (v - p_0)/(2\lambda), dus β=1/(2λ)\beta = 1/(2\lambda).

Stap 2: de prijsregel van de market maker bij gegeven β\beta. Met θ=β(v−p0)\theta = \beta(v - p_0) zijn vv en y=β(v−p0)+uy = \beta(v-p_0) + u gezamenlijk normaal. Volgens Lemma 1 is E[v∣y]=p0+λy\E[v\mid y] = p_0 + \lambda y met

λ=Cov⁡(v,y)Var⁡(y)=βΣ0β2Σ0+σu2.\lambda = \frac{\Cov(v, y)}{\Var(y)} = \frac{\beta\Sigma_0}{\beta^2\Sigma_0 + \sigma_u^2}.

Stap 3: het vaste punt geeft λ\lambda en β\beta. Substitueer β=1/(2λ)\beta = 1/(2\lambda), zodat Σ0/(4λ)+λσu2=Σ0/(2λ)\Sigma_0/(4\lambda) + \lambda\sigma_u^2 = \Sigma_0/(2\lambda), oftewel λ2=Σ0/(4σu2)\lambda^2 = \Sigma_0/(4\sigma_u^2). De tweede-ordevoorwaarde kiest de positieve wortel λ=σv/(2σu)\lambda = \sigma_v/(2\sigma_u), en dan is β=σu/σv\beta = \sigma_u/\sigma_v. Elk lineair evenwicht moet aan stap 1 tot en met 3 voldoen, dus het is uniek.

Stap 4: de restvariantie en de winsten. Volgens het lemma is Var⁡(v∣y)=Σ0−β2Σ02/(β2Σ0+σu2)\Var(v\mid y) = \Sigma_0 - \beta^2\Sigma_0^2/(\beta^2\Sigma_0 + \sigma_u^2), en met β2Σ0=σu2\beta^2\Sigma_0 = \sigma_u^2 is dat Σ0/2\Sigma_0/2. De verwachte winst van de insider is E[β(v−p0) (v−p0−λβ(v−p0)−λu)]=βΣ0(1−λβ)=12σuσv\E[\beta(v-p_0)\,(v - p_0 - \lambda\beta(v-p_0) - \lambda u)] = \beta\Sigma_0(1 - \lambda\beta) = \tfrac12\sigma_u\sigma_v. Die van de noise traders is E[u(v−p)]=−λσu2=−12σvσu\E[u(v - p)] = -\lambda\sigma_u^2 = -\tfrac12\sigma_v\sigma_u, en de market maker verdient −E[y(v−p)]=0-\E[y(v-p)] = 0, omdat v−E[v∣y]v - \E[v\mid y] ongecorreleerd is met yy. □\square

In het bewijs neemt de insider λ\lambda als gegeven en koopt hij (v−p0)/(2λ)(v-p_0)/(2\lambda), de helft van de positie (v−p0)/λ(v-p_0)/\lambda waarbij de prijs tot vv zou stijgen. De market maker neemt β\beta als gegeven en haalt λ\lambda uit het projectietheorema. Met de getallen uit het toy-voorbeeld verdient de insider gemiddeld 12⋅4⋅10 000=20 000\tfrac12\cdot4\cdot10\,000 = 20\,000 euro en daalt de variantie van vv van 16 naar 8.

De diepte van de markt, 1/λ1/\lambda, stijgt met de ruis en daalt met de private informatie. Verdubbelt σu\sigma_u, dan handelt de insider twee keer zoveel en verdient hij twee keer zoveel, maar blijft de helft van de variantie over, zoals het beeld van de verstopte insider deed verwachten. Meer noise traders maken de markt dus dieper en de insider rijker, maar de prijs niet dommer. Zo ontsnapt het model aan de paradox van Grossman en Stiglitz, omdat de verliezen van de noise traders het verzamelen van informatie betalen.

4.4Kyle met vele handelsronden

Mag de insider over vele ronden handelen, dan komt zijn informatie in een constant tempo in de prijs. Wat hij nu verraadt, kan hij later niet meer uitbuiten, dus houdt hij informatie achter tot hij onverschillig is tussen nu en later handelen.

Kyle deelt de handelsperiode op in NN ronden met Δt=1/N\Delta t = 1/N, waarin de noise traders per ronde een order Δun∼N(0,σu2Δt)\Delta u_n \sim N(0, \sigma_u^2\Delta t) inleggen Kyle (1985). De insider handelt Δθn=βn(v−pn−1)Δt\Delta\theta_n = \beta_n(v - p_{n-1})\Delta t, de prijs wordt pn=pn−1+λn(Δθn+Δun)p_n = p_{n-1} + \lambda_n(\Delta\theta_n + \Delta u_n), en Σn\Sigma_n is de onzekerheid over vv die na ronde nn over is. De coëfficiënt αn\alpha_n, geen alpha in de gewone betekenis, meet wat een resterend prijsverschil v−pnv - p_n de insider later oplevert. Een grote αn\alpha_n maakt hem in ronde nn dus terughoudender.

Kyle (1985, stelling 2 en 3) bewijst dit door te veronderstellen dat de resterende winst kwadratisch is in v−pnv - p_n en die veronderstelling achteruit te bevestigen vanaf αN=δN=0\alpha_N = \delta_N = 0. In de limiet verdient de insider twee keer zoveel als in één ronde, omdat hij zijn informatie over de tijd kan spreiden.

4.5Numerieke oplossing van Kyles recursie

Al bij tien ronden ligt het evenwicht dicht bij de continue limiet uit de propositie, met een constante prijsimpact en een lineair dalende onzekerheid. Per ronde geven de eerste twee vergelijkingen samen een derdegraadsvergelijking in λn\lambda_n. We kiezen de positieve wortel met αnλn<12\alpha_n\lambda_n < \tfrac12, omdat alleen die een positieve βn\beta_n en de tweede-ordevoorwaarde geeft. Omdat het stelsel homogeen is in Σ\Sigma, lossen we achteruit op vanaf ΣN=1\Sigma_N = 1. Aan het eind vermenigvuldigen we alle Σn\Sigma_n met één factor kk, λn\lambda_n met k\sqrt{k} en βn\beta_n met 1/k1/\sqrt{k}. Dat doen we voor 1, 4, 10 en 50 ronden met σv=σu=1\sigma_v = \sigma_u = 1.

def kyle_recursion(n_auctions, sigma_v=1.0, sigma_u=1.0):
    """Kyle (1985) N-auction linear equilibrium, solved backwards from Sigma_N = 1 and rescaled.

    Returns a DataFrame indexed by auction n = 1..N with lambda_n, beta_n and Sigma_n
    (posterior variance after auction n). ``attrs`` holds Sigma_0 and the insider's
    total expected profit alpha_0 Sigma_0 + delta_0.
    """
    dt, s2 = 1.0 / n_auctions, sigma_u**2
    lam, beta = np.empty(n_auctions), np.empty(n_auctions)
    sigma = np.empty(n_auctions + 1)
    sigma[-1], alpha, delta = 1.0, 0.0, 0.0
    for n in range(n_auctions - 1, -1, -1):
        # lambda solves -2 a s2 dt L^3 + 2 s2 dt L^2 + 2 a S L - S = 0 (from the first two equations)
        roots = np.roots([-2 * alpha * s2 * dt, 2 * s2 * dt, 2 * alpha * sigma[n + 1], -sigma[n + 1]])
        roots = roots[np.abs(roots.imag) < 1e-10].real
        L = roots[(roots > 0) & (alpha * roots < 0.5)].min()
        lam[n], beta[n] = L, L * s2 / sigma[n + 1]
        sigma[n] = sigma[n + 1] / (1 - beta[n] * L * dt)
        delta += alpha * L**2 * s2 * dt
        alpha = 1.0 / (4 * L * (1 - alpha * L))
    k = sigma_v**2 / sigma[0]            # homogeneity: Sigma*k, lambda*sqrt(k), beta/sqrt(k)
    out = pd.DataFrame(
        {"lambda": lam * np.sqrt(k), "beta": beta / np.sqrt(k), "Sigma": sigma[1:] * k},
        index=pd.RangeIndex(1, n_auctions + 1, name="ronde"),
    )
    out.attrs = {"Sigma_0": sigma[0] * k, "profit": alpha / np.sqrt(k) * sigma_v**2 + delta * np.sqrt(k)}
    return out

solutions = {n: kyle_recursion(n) for n in (1, 4, 10, 50)}
pd.DataFrame(
    {
        "lambda eerste ronde": [s["lambda"].iloc[0] for s in solutions.values()],
        "lambda laatste ronde": [s["lambda"].iloc[-1] for s in solutions.values()],
        "Sigma_N / Sigma_0": [s["Sigma"].iloc[-1] / s.attrs["Sigma_0"] for s in solutions.values()],
        "verwachte winst insider": [s.attrs["profit"] for s in solutions.values()],
    },
    index=pd.Index(list(solutions), name="rondes N"),
).round(4)
Loading...

Bij N=1N = 1 geeft de recursie het evenwicht van Theorem 2 terug, met λ=0,5\lambda = 0{,}5 en de helft van de variantie over. Naarmate NN groeit, gaat λ\lambda naar 1 en de restvariantie naar nul, terwijl de verwachte winst bij 50 ronden 0,95 is. Om te zien of de prijs zich ook in steekproeven zo gedraagt, simuleren we 20.000 markten met 50 ronden.

kyle50 = solutions[50]
n_paths, N = 20_000, 50
dt = 1.0 / N
v_true = rng.normal(0.0, 1.0, n_paths)
price = np.zeros(n_paths)
profit = np.zeros(n_paths)
price_changes = np.empty((N, n_paths))
resid_var = np.empty(N)
for n in range(N):
    order = kyle50["beta"].iloc[n] * (v_true - price) * dt
    noise = rng.normal(0.0, np.sqrt(dt), n_paths)
    new_price = price + kyle50["lambda"].iloc[n] * (order + noise)
    profit += order * (v_true - new_price)
    price_changes[n] = new_price - price
    price = new_price
    resid_var[n] = np.var(v_true - price)

autocorr = np.corrcoef(price_changes[1:].ravel(), price_changes[:-1].ravel())[0, 1]
print(f"restvariantie na ronde 25: simulatie {resid_var[24]:.3f}, theorie {kyle50['Sigma'].iloc[24]:.3f}")
print(f"restvariantie na ronde 50: simulatie {resid_var[-1]:.3f}, theorie {kyle50['Sigma'].iloc[-1]:.3f}")
print(f"autocorrelatie opeenvolgende prijsveranderingen: {autocorr:+.4f}")
print(f"gemiddelde prijsverandering: {price_changes.mean():+.5f}")
print(f"insiderwinst per markt: gemiddelde {profit.mean():.3f} (theorie {kyle50.attrs['profit']:.3f}), "
      f"standaarddeviatie {profit.std():.3f}")
restvariantie na ronde 25: simulatie 0.528, theorie 0.531
restvariantie na ronde 50: simulatie 0.027, theorie 0.027
autocorrelatie opeenvolgende prijsveranderingen: +0.0003
gemiddelde prijsverandering: +0.00011
insiderwinst per markt: gemiddelde 0.950 (theorie 0.951), standaarddeviatie 1.024

De gesimuleerde restvariantie is na 25 ronden 0,528 tegen 0,531 in theorie, en opeenvolgende prijsveranderingen zijn ongecorreleerd. De prijs is dus een martingaal ten opzichte van de order flow, ook al handelt er iemand die de uitkomst kent. De linker figuur legt de punten van de simulatie naast de rechte lijn 1−t1 - t van de continue limiet.

Source
fig, axes = plt.subplots(1, 2, figsize=(11, 4.5))
for n, sol in solutions.items():
    if n == 1:
        continue
    t = np.r_[0, sol.index / n]
    axes[0].plot(t, np.r_[1.0, sol["Sigma"] / sol.attrs["Sigma_0"]], marker="o" if n == 4 else None, label=f"theorie, N = {n}")
    axes[1].plot(sol.index / n, sol["lambda"], marker="o" if n == 4 else None, label=f"N = {n}")
axes[0].plot(np.arange(1, N + 1) / N, resid_var, "k.", ms=4, label="simulatie, N = 50")
axes[0].plot([0, 1], [1, 0], color="grey", ls="--", lw=1, label="continue limiet $1-t$")
axes[0].set_title("Restonzekerheid $\\Sigma_n/\\Sigma_0$ neemt vrijwel lineair af")
axes[0].set_xlabel("Tijd binnen de handelsperiode $t = n/N$")
axes[0].set_ylabel("$\\Sigma_n / \\Sigma_0$")
axes[0].legend()
axes[1].axhline(1.0, color="grey", ls="--", lw=1, label="continue limiet $\\sigma_v/\\sigma_u$")
axes[1].set_title("Prijsimpact $\\lambda_n$ per ronde")
axes[1].set_xlabel("Tijd binnen de handelsperiode $t = n/N$")
axes[1].set_ylabel("$\\lambda_n$")
axes[1].legend()
plt.show()
<Figure size 1210x495 with 2 Axes>
<Figure size 1210x495 with 2 Axes>

Figure 1:Links volgt de restvariantie al bij tien ronden bijna de rechte lijn 1−t1 - t, en de gesimuleerde punten vallen op het theoretische pad, zodat de informatie in een constant tempo in de prijs komt. Rechts is de prijsimpact vrijwel constant, behalve in de laatste ronden.

De figuur toont gemiddelden, maar de winst van één insider spreidt ruim, en over die spreiding zegt de propositie niets. De standaarddeviatie van de insiderwinst per markt is 1,024, iets meer dan het gemiddelde van 0,950, zodat een insider met perfecte informatie na twintig handelsdagen een tt-waarde van ongeveer 20⋅0,950/1,024≈4,1\sqrt{20}\cdot0{,}950/1{,}024 \approx 4{,}1 haalt. Een handelaar met een klein voordeel heeft daar jaren voor nodig. Het probleem van de standaardfout van 2% bestaat dus ook op de handelsvloer, want ook iemand met echte informatie kan zijn voordeel moeilijk bewijzen.

4.6Roll: de spread uit de autocovariantie

Uit gewone transactiekoersen is de spread terug te rekenen, omdat de sprong tussen bied en laat een negatieve autocovariantie achterlaat. De fundamentele waarde verandert onvoorspelbaar, maar transacties vinden afwisselend tegen de biedkoers en de laatkoers plaats. Na een transactie tegen de laatkoers volgt daarom vaker een daling dan een stijging, en dat heen en weer springen, de bid-ask bounce, maakt opeenvolgende koersveranderingen negatief gecorreleerd.

Een spread van 0,80 euro, zoals in het toy-voorbeeld, laat volgens (6) een autocovariantie van −0,802/4=−0,16-0{,}80^2/4 = -0{,}16 achter. Op rendementen geeft dezelfde formule de relatieve spread. Het model laat geen ruimte voor andere autocorrelatie, zodat een positieve autocovariantie de wortel ongedefinieerd maakt en een negatieve door overreactie als spread wordt gelezen. Het signaal is bovendien klein, want bij een spread van één basispunt en een dagvolatiliteit van 1,5% is ∣ρ1∣|\rho_1| van de orde 10-5.

4.7Amihud: prijsimpact uit dagdata

Ook zonder transactiedata is Kyles λ\lambda bij benadering te meten, als koersbeweging per dollar omzet. Grote koersbewegingen bij weinig omzet wijzen op een ondiepe markt, en Amihud Amihud (2002) definieerde daarom voor aandeel ii met DiD_i handelsdagen in een jaar

ILLIQi=1Di∑d=1Di∣ri,d∣DVOLi,d,\mathrm{ILLIQ}_{i} = \frac{1}{D_{i}} \sum_{d=1}^{D_{i}} \frac{|r_{i,d}|}{\mathrm{DVOL}_{i,d}},

met ri,dr_{i,d} het dagrendement en DVOLi,d\mathrm{DVOL}_{i,d} de omzet in dollars. Een aandeel dat op een dag met 10 miljoen dollar omzet 1% beweegt, heeft die dag een ILLIQ van 0,1% per miljoen dollar.

In echte markten komt er ook publiek nieuws in de prijs en is de omzet groter dan de netto order flow, zodat ILLIQ alleen een ruisend signaal van λ\lambda is. Het signaal is wel bruikbaar, omdat de ruis over een jaar dagdata grotendeels uitmiddelt en de rangorde van aandelen naar diepte blijft staan.

4.8Liquiditeit in de prijs: het niveau

Illiquide aandelen moeten meer opleveren, maar minder dan evenredig met hun spread. Een belegger die een aandeel na hh jaar verkoopt, betaalt de spread één keer, dus s/hs/h per jaar. Aandelen met hoge spreads belanden daardoor bij beleggers met een lange horizon. Voor hen weegt de spread het minst, zodat het vereiste rendement minder hard stijgt dan de spread. Amihud en Mendelson Amihud & Mendelson (1986) vonden precies dat het verwachte rendement met de spread stijgt, maar steeds minder snel.

Amihud bracht de vraag later naar de tijdreeks, met een regressie van het overrendement van de markt op de illiquiditeit van de markt,

Rm,te=g0+g1ln⁡ILLIQm,t−1+g2ln⁡ILLIQm,tu+εt,R^e_{m,t} = g_0 + g_1 \ln\mathrm{ILLIQ}_{m,t-1} + g_2 \ln\mathrm{ILLIQ}^{u}_{m,t} + \varepsilon_t ,

waarin ln⁡ILLIQu\ln\mathrm{ILLIQ}^{u} de onverwachte illiquiditeit is, de schok uit een AR(1). Verwachte illiquiditeit verhoogt het verwachte overrendement, dus g1>0g_1 > 0. Onverwachte illiquiditeit verlaagt het rendement in dezelfde periode, dus g2<0g_2 < 0, omdat een blijvende schok het vereiste rendement voor de toekomst verhoogt en de prijs vandaag laat dalen. Op jaardata voor NYSE-aandelen over 1964–1997 vond Amihud beide tekens.

4.9Liquiditeit als risico: Pástor-Stambaugh en Acharya-Pedersen

Omdat marktliquiditeit in de tijd varieert en alle aandelen tegelijk raakt, is ze ook een risico. Ze is een toestandsvariabele zoals in het ICAPM (Merton: continue tijd en het ICAPM), waar een activum dat slecht rendeert als de toestand verslechtert, meer moet opleveren. Een aandeel dat daalt wanneer de markt illiquide wordt, verliest dus waarde op het moment dat beleggers geld het hardst nodig hebben.

Pástor en Stambaugh Pástor & Stambaugh (2003) maten de liquiditeit van aandeel ii in maand tt als de coëfficiënt γi,t\gamma_{i,t} in de regressie op dagdata

ri,d+1,te=θi,t+ϕi,t ri,d,t+γi,t sign⁡(ri,d,te) vi,d,t+ϵi,d+1,t,r^e_{i,d+1,t} = \theta_{i,t} + \phi_{i,t}\, r_{i,d,t} + \gamma_{i,t}\, \operatorname{sign}(r^e_{i,d,t})\, v_{i,d,t} + \epsilon_{i,d+1,t},

met rer^e het rendement boven de markt, vv hier de dollaromzet en θi,t\theta_{i,t} en ϕi,t\phi_{i,t} gewone regressiecoëfficiënten. Getekende omzet benadert de order flow, en in een illiquide markt wordt een koersbeweging die door orders komt deels teruggedraaid, zodat γi,t\gamma_{i,t} negatief is. Het gemiddelde over aandelen, min het voorspelbare deel, levert innovaties Lt\mathcal{L}_t, en de liquiditeitsbèta βi,L\beta_{i,\mathcal{L}} is de helling daarop naast de drie Fama-French-factoren. Aandelen met een hoge voorspelde liquiditeitsbèta leverden na correctie voor vier factoren ongeveer 7,5% per jaar meer op dan aandelen met een lage.

Acharya en Pedersen Acharya & Pedersen (2005) leidden in een economie met wisselende handelskosten ci,tc_{i,t} een CAPM af voor rendementen na kosten. In bruto rendementen en in onze notatie geeft dat

E[Rie]=E[ci]+λmβi,1+λmβi,2−λmβi,3−λmβi,4,\E[R^e_{i}] = \E[c_i] + \lambda_m\beta_{i,1} + \lambda_m\beta_{i,2} - \lambda_m\beta_{i,3} - \lambda_m\beta_{i,4},

met λm\lambda_m de prijs van marktrisico na kosten (de verwachte marktpremie min de handelskosten van de markt, enkele procenten per jaar) en βi,1\beta_{i,1} de gewone marktbèta. De andere drie bèta’s belonen een aandeel dat illiquide wordt als de markt illiquide wordt (Cov⁡(ci,cm)\Cov(c_i, c_m)), dat slecht rendeert als de markt illiquide wordt (Cov⁡(ri,cm)\Cov(r_i, c_m)), en dat illiquide wordt als de markt daalt (Cov⁡(ci,rm)\Cov(c_i, r_m)). Met ILLIQ als maat voor cic_i schatten ze over 1963–1999 dat liquiditeitsrisico ongeveer 1,1% per jaar bijdraagt aan het verschil tussen de meest en de minst illiquide aandelen. De drie bèta’s zijn wel sterk gecorreleerd, zodat de verdeling over de kanalen onzeker is.

4.10Marktontwerp: van specialist naar batchveiling

De modellen van 1985 beschrijven een specialist, maar vandaag voeren algoritmen de orders uit in een doorlopend orderboek. Budish, Cramton en Shim Budish et al. (2015) lieten zien dat de correlatie tussen een indexfuture en een indexfonds op de schaal van milliseconden wegvalt. Dat levert mechanische arbitragekansen op, en concurrentie verkleint die niet maar drijft alleen de vereiste snelheid op. Een handelaar met een staande koers wordt na elk publiek signaal door de snelste handelaar uitgebuit, net als de market maker van Glosten en Milgrom, en dat verlies zit in de spread. De auteurs stelden daarom frequent batch auctions voor, veilingen die alle orders van bijvoorbeeld een tiende seconde tegen één prijs uitvoeren.

5Simulatie: de Roll-schatter in eindige steekproeven

Hoe vaak is de Roll-maat in een steekproef van gewone lengte niet te berekenen? Tweede momenten zijn meestal goed meetbaar, maar het signaal −s2/4-s^2/4 is klein naast de variantie van de koersveranderingen. De standaardfout van een steekproefautocovariantie is bij een i.i.d.-benadering ongeveer Var⁡(Δp)/T\Var(\Delta p)/\sqrt{T}, zodat voor de kans op een positieve en dus onbruikbare schatting geldt

P(Cov⁡^>0)≈Φ ⁣(T ρ1),ρ1=−s2/4σe2+s2/2.P\bigl(\widehat{\Cov} > 0\bigr) \approx \Phi\!\left(\sqrt{T}\,\rho_1\right), \qquad \rho_1 = -\frac{s^2/4}{\sigma_e^2 + s^2/2}.

Voor de spread van 0,80 euro uit het toy-voorbeeld bij een dagvolatiliteit van 1 euro is ρ1=−0,16/1,32=−0,121\rho_1 = -0{,}16/1{,}32 = -0{,}121. Over een kwartaal van 60 dagen is de kans dan Φ(−60⋅0,121)=Φ(−0,94)≈0,17\Phi(-\sqrt{60}\cdot0{,}121) = \Phi(-0{,}94) \approx 0{,}17, maar bij s/σe=0,01s/\sigma_e = 0{,}01 is ze bijna een half. We simuleren het model voor 22 verhoudingen s/σes/\sigma_e tussen 0,01 en 3, met T=60T = 60 en T=250T = 250 dagen en 4000 steekproeven per punt.

def roll_positive_share(spread_ratio, n_days, n_samples=4_000):
    """Share of samples in which the Roll autocovariance is positive (sigma_e = 1)."""
    e = rng.normal(0.0, 1.0, (n_samples, n_days))
    q = rng.choice([-1.0, 1.0], (n_samples, n_days + 1))
    dp = e + spread_ratio / 2 * np.diff(q, axis=1)
    dp = dp - dp.mean(axis=1, keepdims=True)
    cov = (dp[:, 1:] * dp[:, :-1]).sum(axis=1) / (n_days - 1)
    return (cov > 0).mean()

ratios = np.geomspace(0.01, 3.0, 22)
roll_sim = pd.DataFrame(
    {T: [roll_positive_share(r, T) for r in ratios] for T in (60, 250)}, index=pd.Index(ratios, name="s / sigma")
)
roll_theory = pd.DataFrame(
    {T: stats.norm.cdf(np.sqrt(T) * -(ratios**2 / 4) / (1 + ratios**2 / 2)) for T in (60, 250)}, index=ratios
)
roll_sim.iloc[[0, 5, 10, 14, 17, 21]].round(3)
Loading...

Bij s/σe=0,01s/\sigma_e = 0{,}01 is de autocovariantie in 46% van de kwartalen en 48% van de jaren positief, en pas bij een verhouding rond 1 bestaat de maat bijna altijd. De figuur zet simulatie en benadering naast elkaar, met de grote liquide aandelen in het grijze gebied.

Source
fig, ax = plt.subplots()
for i, T in enumerate((60, 250)):
    ax.plot(ratios, roll_sim[T], "o", color=f"C{i}", label=f"simulatie, T = {T} dagen")
    ax.plot(ratios, roll_theory[T], "-", color=f"C{i}", lw=1, label=f"benadering, T = {T} dagen")
ax.axvspan(0.003, 0.1, color="grey", alpha=0.15, label="grote liquide aandelen")
ax.set_xscale("log")
ax.set_xlim(0.008, 3.5)
ax.set_title("Hoe vaak is de Roll-maat ongedefinieerd?")
ax.set_xlabel("Spread gedeeld door volatiliteit van de efficiënte prijs, $s/\\sigma_e$ (log-schaal)")
ax.set_ylabel("Fractie steekproeven met positieve autocovariantie")
ax.legend()
plt.show()
<Figure size 990x550 with 1 Axes>
<Figure size 990x550 with 1 Axes>

Figure 2:Voor grote liquide aandelen (grijs gebied) is de Roll-maat in bijna de helft van de steekproeven ongedefinieerd, ook als het model precies klopt. Een langere steekproef helpt maar met een factor T\sqrt{T}. In het grijze gebied verschuift de curve van een kwartaal naar een jaar daardoor nauwelijks, en pas bij grotere spreads daalt ze zichtbaar.

Benadering en simulatie liggen vrijwel op elkaar. Beide laten zien dat een variantie goed meetbaar is, tenzij het deel dat we zoeken een honderdduizendste van het totaal is. De ruis is bovendien niet symmetrisch, want wie de Roll-maat alleen middelt over de jaren waarin ze bestaat, overschat de spread stelselmatig.

6Replicatie op echte data

6.1Amihud en Roll per aandeel

ILLIQ zet de vijftig aandelen in een plausibele volgorde, terwijl de Roll-maat vooral ruis meet. De codecel berekent per aandeel de dagelijkse ILLIQ in basispunten per miljoen dollar omzet en per jaar de Roll-spread uit de autocovariantie van de dagrendementen.

TICKERS = [
    "AAPL", "ADBE", "AMZN", "ANET", "APH", "AVGO", "BAC", "BRK-B", "CMG", "CPRT",
    "CRM", "CSX", "CTAS", "CVX", "DE", "DHR", "DXCM", "EW", "FAST", "FTNT",
    "GILD", "GOOGL", "IDXX", "ISRG", "KO", "LRCX", "MA", "MNST", "NFLX", "NKE",
    "NOW", "NVDA", "ODFL", "ORLY", "PANW", "PG", "QCOM", "ROST", "SBUX", "SHW",
    "SMCI", "TJX", "TSCO", "TSLA", "UNH", "UNP", "V", "WFC", "WMT", "WRB",
]
prices = hap_data.yahoo(TICKERS, start="2002-01-01", end="2026-08-01")
volume = hap_data.yahoo(TICKERS, start="2002-01-01", end="2026-08-01", field="Volume")

returns = prices.pct_change(fill_method=None).loc["2002-01-03":]
dollar_volume_mln = (prices * volume).where(volume > 0).loc[returns.index] / 1e6
illiq_daily = returns.abs() / dollar_volume_mln * 1e4       # basis points per million dollars

def first_autocov(series, min_obs=100):
    """First-order autocovariance of a return series; NaN if fewer than min_obs observations."""
    x = series.dropna().to_numpy()
    return np.cov(x[1:], x[:-1])[0, 1] if len(x) > min_obs else np.nan

autocov_year = returns.groupby(returns.index.year).agg(first_autocov)
roll_year_bp = 2 * np.sqrt(-autocov_year.where(autocov_year < 0)) * 1e4

per_stock = pd.DataFrame(
    {
        "ILLIQ 2021–2026 (bp per $ mln)": illiq_daily.loc["2021":].mean(),
        "Roll-spread 2021–2026 (bp)": roll_year_bp.loc[2021:].median(),
        "fractie jaren Roll ongedefinieerd": (autocov_year >= 0).sum() / autocov_year.notna().sum(),
    }
).sort_values("ILLIQ 2021–2026 (bp per $ mln)")
print(f"aandeel-jaren met positieve autocovariantie: {(autocov_year >= 0).sum().sum()} van "
      f"{autocov_year.notna().sum().sum()} ({(autocov_year >= 0).sum().sum() / autocov_year.notna().sum().sum():.1%})")
per_stock.iloc[[0, 1, 2, 3, 4, -5, -4, -3, -2, -1]].round(3)
aandeel-jaren met positieve autocovariantie: 427 van 1168 (36.6%)
Loading...

De tabel toont de vijf meest en de vijf minst liquide namen. Apple, Tesla en Amazon zijn het diepst en Super Micro het ondiepst, met ruim twee ordes van grootte ertussen. De Roll-maat is in 427 van de 1168 aandeel-jaren ongedefinieerd, ruim een derde, wat past bij de simulatie. Waar ze bestaat, geeft ze spreads van 60 tot 170 basispunten. Zulke waarden ontstaan omdat Rolls formule alle negatieve autocorrelatie in dagrendementen meet en niet alleen de bid-ask bounce. Bovendien tellen alleen de jaren met een negatieve schatting mee, het selectie-effect uit de simulatie. Per jaar bekijken we daarna de mediane Roll-spread en de mediane ILLIQ.

by_year = pd.DataFrame(
    {
        "mediane Roll-spread (bp)": roll_year_bp.median(axis=1),
        "fractie ongedefinieerd": (autocov_year >= 0).sum(axis=1) / autocov_year.notna().sum(axis=1),
        "mediane ILLIQ (bp per $ mln)": illiq_daily.groupby(illiq_daily.index.year).mean().median(axis=1),
    }
)
by_year.round(3)
Loading...

De Roll-maat pikt de crises wel op, met de hoogste mediane spread in 2020 en 2008, vooral omdat volatiliteit en omkeringen dan toenemen. De mediane ILLIQ daalt met ongeveer een factor dertig, omdat deze vijftig aandelen zo sterk zijn gegroeid, zodat we die trend eerst uit een marktbrede reeks moeten halen.

6.2Marktbrede illiquiditeit en het marktrendement

Marktbrede illiquiditeit piekt in 2008 en maart 2020 en gaat samen met lage gelijktijdige marktrendementen. We nemen per aandeel per maand de logaritme van de gemiddelde ILLIQ, trekken per aandeel het gemiddelde af en middelen over aandelen. Daarna schatten we de maandelijkse versie van Amihuds tijdreeksregressie met Newey-West-standaardfouten.

days = illiq_daily.resample("ME").count()
log_illiq = np.log(illiq_daily.resample("ME").mean().where(days >= 15))
market_illiq = log_illiq.sub(log_illiq.mean()).mean(axis=1).rename("illiq")
deviation = (market_illiq - market_illiq.rolling(12).mean().shift(1)).rename("afwijking")

ar1 = hap.stats.newey_west(market_illiq, market_illiq.shift(1).rename("vertraagd"), lags=0)
innovation = (market_illiq - ar1.params["const"] - ar1.params["vertraagd"] * market_illiq.shift(1)).rename("innovatie")

ff3 = hap_data.market_monthly()
design = pd.concat([market_illiq.shift(1).rename("illiq vertraagd"), innovation], axis=1, sort=True)
amihud_fit = hap.stats.newey_west(ff3["Mkt-RF"].reindex(design.index), design, lags=3)

print(f"AR(1)-helling marktilliquiditeit: {ar1.params['vertraagd']:.3f}")
print("Hoogste afwijkingen van het voorgaande jaargemiddelde (log-punten):")
print(deviation.nlargest(6).round(2).to_string())
pd.DataFrame(
    {"coëfficiënt": amihud_fit.params, "NW-standaardfout": amihud_fit.bse, "t-waarde": amihud_fit.tvalues}
).round(4)
AR(1)-helling marktilliquiditeit: 0.986
Hoogste afwijkingen van het voorgaande jaargemiddelde (log-punten):
date
2020-03-31    0.93
2008-11-30    0.87
2008-10-31    0.80
2008-12-31    0.73
2020-04-30    0.51
2009-01-31    0.48
Loading...

Het teken van g2g_2 is gerepliceerd, want een onverwachte stijging van de marktilliquiditeit gaat samen met een fors en zeer significant lager overrendement in dezelfde maand. Een deel daarvan zit mechanisch in de constructie, omdat een koersdaling ∣r∣|r| verhoogt en de dollaromzet verlaagt. De coëfficiënt g1g_1 op de vertraagde illiquiditeit is klein, negatief en niet significant. Om een voorspellend verband voor het marktrendement aan te tonen, zijn echter decennia nodig (Voorspelbaarheid van rendementen), en 24 jaar van vijftig overlevenden is daarvoor te kort. De figuur laat onder de maanden zien waarin de illiquiditeit boven het gemiddelde van het voorgaande jaar uitschiet.

Source
fig, axes = plt.subplots(2, 1, figsize=(10, 6.5), sharex=True)
axes[0].plot(market_illiq.index, market_illiq, label="log-illiquiditeit (gemiddeld over aandelen)")
axes[0].set_title("Marktbrede Amihud-illiquiditeit van vijftig grote aandelen")
axes[0].set_ylabel("Log-ILLIQ t.o.v. aandeelgemiddelde")
axes[0].legend(loc="upper right")
axes[1].bar(deviation.index, deviation, width=25, color=np.where(deviation > 0, "C1", "C0"))
for date, text in [("2008-11-30", "okt.–nov. 2008"), ("2020-03-31", "maart 2020")]:
    axes[1].annotate(text, (pd.Timestamp(date), deviation.loc[date]), xytext=(10, 5), textcoords="offset points")
axes[1].set_title("Afwijking van het gemiddelde van de voorgaande twaalf maanden")
axes[1].set_ylabel("Log-punten")
hap.plotting.timeline_axis(axes[1], every=2)
axes[1].set_xlabel("Jaar")
plt.show()
<Figure size 1100x715 with 2 Axes>
<Figure size 1100x715 with 2 Axes>

Figure 3:Boven daalt de illiquiditeit van deze vijftig aandelen sterk, omdat ze zo gegroeid zijn. Onder zijn maart 2020 en oktober tot december 2008 de grootste uitschieters, precies de maanden waarin veel beleggers moesten verkopen.

6.3Pástor-Stambaugh

De reeks van Pástor en Stambaugh laat zien dat liquiditeit in crises instort. Omdat hap.data geen loader voor deze reeks heeft, haalt de functie hieronder het bestand van Stambaughs Wharton-pagina één keer op en bewaart het in de cache.

PS_URL = "https://finance.wharton.upenn.edu/~stambaug/liq_data_1962_2025.txt"

def pastor_stambaugh_liquidity():
    """Pastor-Stambaugh (2003) monthly liquidity series, cached as a parquet snapshot.

    Columns: agg_liq (level, their Figure 1), innov_liq (innovation L_t, eq. 8) and
    traded_liq (cap-weighted 10-1 portfolio on historical liquidity betas), decimals.
    """
    # TODO: naar hap.data (loader voor Stambaugh's Wharton-pagina)
    def build():
        text = requests.get(PS_URL, timeout=60).text
        rows = [line for line in text.splitlines() if line.strip() and not line.lstrip().startswith("%")]
        frame = pd.read_csv(io.StringIO("\n".join(rows)), sep=r"\s+", header=None,
                            names=["month", "agg_liq", "innov_liq", "traded_liq"])
        frame.index = pd.DatetimeIndex(
            pd.to_datetime(frame.pop("month").astype(str), format="%Y%m") + pd.offsets.MonthEnd(0), name="date"
        )
        return frame.mask(frame <= -99)

    return hap_cache.load_cached("pastor_stambaugh__liq_1962_2025", build)

ps = pastor_stambaugh_liquidity()
ps_market = pd.concat([ps, ff3["Mkt-RF"]], axis=1, sort=True).dropna(subset=["innov_liq", "Mkt-RF"])
print(f"reeks: {ps.index[0]:%Y-%m} t/m {ps.index[-1]:%Y-%m}")
print(f"correlatie L_t met Mkt-RF, 1966–1999: {ps_market.loc['1966':'1999', ['innov_liq', 'Mkt-RF']].corr().iloc[0, 1]:.2f}"
      f" (werkdocument: 0.36)")
print(f"correlatie L_t met Mkt-RF, 1962–2025: {ps_market[['innov_liq', 'Mkt-RF']].corr().iloc[0, 1]:.2f}")
both = pd.concat([ps["innov_liq"], innovation], axis=1, sort=True).dropna()
print(f"correlatie L_t met onze Amihud-innovatie, {both.index[0]:%Y}–{both.index[-1]:%Y}: {both.corr().iloc[0, 1]:.2f}")
ps["agg_liq"].nsmallest(6).rename("laagste liquiditeitsniveau").round(3).to_frame()
reeks: 1962-08 t/m 2025-12
correlatie L_t met Mkt-RF, 1966–1999: 0.35 (werkdocument: 0.36)
correlatie L_t met Mkt-RF, 1962–2025: 0.30
correlatie L_t met onze Amihud-innovatie, 2002–2025: -0.23
Loading...

De drie diepste maanden uit het artikel staan ook in de bijgewerkte reeks bij de laagste. De correlatie met het marktrendement ligt vrijwel op de gepubliceerde waarde, al gebruiken we de French-marktfactor in plaats van de NYSE-AMEX-index. Onze Amihud-innovatie correleert negatief met hun liquiditeitsinnovatie, zoals het hoort bij een maat voor illiquiditeit, maar zwak, omdat de twee maten verschillende kanten van liquiditeit meten. In de figuur vallen de diepe, korte uitschieters naar beneden op.

Source
fig, ax = plt.subplots(figsize=(10, 4.5))
ax.plot(ps.index, ps["agg_liq"], lw=1)
for date in ps["agg_liq"].nsmallest(5).index:
    ax.annotate(f"{date:%Y-%m}", (date, ps.loc[date, "agg_liq"]), xytext=(4, -10), textcoords="offset points", fontsize=8)
ax.axhline(0, color="grey", lw=0.8)
ax.set_title("Marktbrede liquiditeit volgens Pástor en Stambaugh, 1962–2025")
ax.set_xlabel("Jaar")
ax.set_ylabel("Geschaalde gemiddelde $\\hat\\gamma_t$")
hap.plotting.timeline_axis(ax, every=5)
plt.show()
<Figure size 1100x495 with 1 Axes>
<Figure size 1100x495 with 1 Axes>

Figure 4:De liquiditeit is meestal rustig en stort af en toe diep in, bijna altijd in een bekende crisismaand. Die asymmetrie maakt van liquiditeit een risico en niet alleen een kost.

Tot slot schatten we de vierfactor-alpha van de verhandelbare factor over de hele periode en over de jaren voor en na publicatie.

momentum = hap_data.french("F-F_Momentum_Factor")
factors4 = pd.concat([hap_data.french("F-F_Research_Data_Factors")[["Mkt-RF", "SMB", "HML"]], momentum],
                     axis=1, sort=True)
rows = {}
for start, end in [("1968", "2025"), ("1968", "2003"), ("2004", "2025")]:
    y = ps["traded_liq"].loc[start:end].dropna()
    fit = hap.stats.newey_west(y, factors4.reindex(y.index), lags=6)
    rows[f"{start}–{end}"] = {
        "maanden": len(y),
        "gemiddeld rendement (% p.j.)": 1200 * y.mean(),
        "vierfactor-alpha (% p.j.)": 1200 * fit.params["const"],
        "standaardfout alpha (% p.j.)": 1200 * fit.bse["const"],
        "t-waarde": fit.tvalues["const"],
    }
pd.DataFrame(rows).T.round(2)
Loading...

De alpha over de hele periode is net significant, maar rust volledig op de jaren tot en met 2003, het jaar van publicatie. Daarna is de alpha licht negatief, en de standaardfout is zo groot dat 22 jaar data een premie als die van vóór 2003 aantonen noch uitsluiten. De tabel hieronder zet de drie perioden naast het origineel.

6.4Origineel en hier

grootheidorigineelhier
g2g_2, onverwachte illiquiditeitnegatief (jaren 1964–1997)−0,135-0{,}135, t=−7,87t = -7{,}87 (maanden 2002–2026)
g1g_1, verwachte illiquiditeitpositief−0,002-0{,}002, t=−1,19t = -1{,}19
persistentie van illiquiditeithoog, jaarlijkse AR(1)0,986 per maand, 0,98612≈0,840{,}986^{12} \approx 0{,}84 per jaar
Roll-maat ongedefinieerdvoor een deel van de aandelen36,6% van de aandeel-jaren
diepste liquiditeitsmaandenokt. 1987, nov. 1973, sep. 1998plaats 1, 2 en 5
correlatie Lt\mathcal{L}_t met marktrendement, 1966–19990,360,35
alpha liquiditeitsfactor (% per jaar)7,5 (voorspelde bèta’s, 1966–1999)3,6, t=2,06t = 2{,}06 (1968–2025)
alpha vóór publicatie (% per jaar)5,4, t=2,72t = 2{,}72 (1968–2003)
alpha na publicatie (% per jaar)—−0,81-0{,}81, standaardfout 3,03, t=−0,27t = -0{,}27 (2004–2025)

Gedeeltelijk geslaagd. Het teken van g2g_2, de crisismaanden en de correlatie met het marktrendement komen uit zoals verwacht, en de Roll-maat is in ruim een derde van de aandeel-jaren ongedefinieerd. Dat g1>0g_1 > 0 niet uitkomt en de premie kleiner is dan gepubliceerd, lag binnen de verwachting. Dat de premie na 2003 niet meer te zien is, hadden we niet voorzien.

7Wat er brak, en wat daarna kwam

Wat het model verklaart. De microstructuurtheorie verklaart waarom er een spread is zonder dat iemand kosten maakt en waarom grote orders de prijs bewegen. Ze verklaart ook waarom prijzen voor het publiek een martingaal zijn terwijl insiders winst maken, en ze zegt hoeveel private informatie in de prijs komt, de helft in één ronde en alles bij doorlopende handel. Daarmee kreeg de efficiënte markt een mechanisme, en via Amihud en Pástor-Stambaugh raakte de handelsvloer verbonden met verwachte rendementen. Dat illiquiditeit in crises piekt en samengaat met lage gelijktijdige rendementen, is robuust.

Waar het breekt. Het model breekt niet in zijn logica, maar bij de meting. De Roll-maat is in ruim een derde van de aandeel-jaren ongedefinieerd en elders opgeblazen, omdat ze alle negatieve autocorrelatie als spread leest en alleen negatieve schattingen meetellen. ILLIQ meet Kyles λ\lambda bovendien alleen met veel ruis. De premie op liquiditeitsrisico is in de twee decennia na publicatie in de eigen factor van Pástor en Stambaugh niet meer te zien, en de standaardfout is daar te groot om iets uit te sluiten.

Risico of vergissing? In de Chicago-lezing is illiquiditeit een kost die rationele beleggers in de prijs verwerken, en liquiditeitsrisico een toestandsvariabele die in slechte tijden toeslaat. Houders van illiquide activa verzekeren in die lezing de markt en worden daarvoor betaald, zodat het risico na 2003 kleiner was of we alleen ruis zien. In de Yale-lezing is illiquiditeit een vorm van de limits of arbitrage uit Behavioral finance en limits of arbitrage. Prijzen wijken af omdat arbitrageurs de posities niet kunnen volhouden, en de premie verdwijnt zodra genoeg kapitaal de kans ziet, zoals McLean en Pontiff voor gepubliceerde anomalieën vonden (De factor zoo).

Een risicopremie vergoedt verliezen in crises en een vergissing verdwijnt na publicatie, maar onze replicatie laat beide zien en beslist de vraag dus niet. Santa-Clara’s praktijkles past op beide lezingen, want wie illiquide activa houdt, kan ze volgens hem niet zomaar verkopen tegen de prijs uit de laatste waardering Santa-Clara (2026).

Wat er daarna kwam. De volgende vraag is wie die handelskosten betaalt en wie eraan verdient. De industrie als financier: indexfondsen, DFA, AQR zoekt het antwoord in de beleggingsindustrie, van actieve fondsen die hun informatievoordeel moeten terugverdienen tot indexfondsen die zo weinig mogelijk handelen.

8Oefeningen

Solution to Exercise 1

(1) Met z=(2π−1)2z = (2\pi-1)^2 is 4π(1−π)=1−z4\pi(1-\pi) = 1 - z en de spread μΔV(1−z)/(1−μ2z)\mu\Delta V(1-z)/(1-\mu^2 z). Die daalt in zz omdat μ2<1\mu^2 < 1, dus het maximum ligt bij π=12\pi = \tfrac12, met spread μΔV\mu\Delta V, dus 0,800{,}80 en 2,002{,}00.

(2) Bij μ=0,5\mu = 0{,}5 is de likelihoodratio van een koop 0,75/0,25=30{,}75/0{,}25 = 3, zodat de odds π/(1−π)\pi/(1-\pi) van 1 naar 3, 9 en 27 gaan en π3=27/28=0,9643\pi_3 = 27/28 = 0{,}9643.

belief_path, rows = 0.5, []
for k in range(1, 4):
    b, a = gm_quotes(belief_path, 0.5, V_H, V_L)
    belief_path = belief_path * 0.75 / (belief_path * 0.75 + (1 - belief_path) * 0.25)
    rows.append({"order": k, "bid vóór order": b, "ask vóór order": a, "oordeel na koop": belief_path})
print(pd.DataFrame(rows).round(4).to_string(index=False))

belief_path, n_buys = 0.5, 0
bid, ask = gm_quotes(belief_path, 0.2, V_H, V_L)
while ask - bid >= 0.10:
    belief_path = belief_path * 0.6 / (belief_path * 0.6 + (1 - belief_path) * 0.4)
    n_buys += 1
    bid, ask = gm_quotes(belief_path, 0.2, V_H, V_L)
print(f"\n(3) bij mu = 0.2: spread onder 0.10 na {n_buys} kooporders op rij (oordeel {belief_path:.4f})")
 order  bid vóór order  ask vóór order  oordeel na koop
     1            99.0        101.0000           0.7500
     2           100.0        101.6000           0.9000
     3           101.0        101.8571           0.9643

(3) bij mu = 0.2: spread onder 0.10 na 9 kooporders op rij (oordeel 0.9746)

(3) Elke koop vermenigvuldigt de odds met 1,51{,}5, en na negen kooporders op rij is de spread onder 0,100{,}10. Het tempo van prijsontdekking hangt dus af van de informatieve waarde van één order, de likelihoodratio (1+μ)/(1−μ)(1+\mu)/(1-\mu).

Solution to Exercise 2

(1) De winst en β\beta zijn evenredig met σu\sigma_u, λ\lambda is er omgekeerd evenredig mee en Var⁡(v∣p)=12σv2\Var(v\mid p) = \tfrac12\sigma_v^2 hangt er niet van af. Bij σu=20 000\sigma_u = 20\,000 is dus β=5000\beta = 5000, λ=0,0001\lambda = 0{,}0001, de verwachte winst 40 00040\,000 en Var⁡(v∣p)=8\Var(v\mid p) = 8. De simulatie hieronder controleert dat.

n_markets = 200_000
rows = {}
for s_u in (10_000.0, 20_000.0):
    b_k, l_k = s_u / 4.0, 4.0 / (2 * s_u)
    v_sim = rng.normal(0.0, 4.0, n_markets)
    u_sim = rng.normal(0.0, s_u, n_markets)
    y_sim = b_k * v_sim + u_sim
    p_sim = l_k * y_sim
    gain = b_k * v_sim * (v_sim - p_sim)
    slope = np.cov(v_sim, y_sim)[0, 1] / np.var(y_sim)
    rows[f"sigma_u = {s_u:,.0f}"] = {
        "lambda (regressie v op y)": slope,
        "gemiddelde winst insider": gain.mean(),
        "Var(v | p)": np.var(v_sim - p_sim),
        "t-waarde na 20 dagen": np.sqrt(20) * gain.mean() / gain.std(),
    }
pd.DataFrame(rows).round(5)
Loading...

De winst en de standaarddeviatie daarvan schalen allebei met σu\sigma_u, zodat de tt-waarde na twintig dagen, ongeveer 2,6, niet van σu\sigma_u afhangt. Ze ligt onder de 4,1 bij vijftig ronden, omdat de insider daar zijn informatie over de tijd spreidt en per eenheid risico meer verdient. Een insider in een drukke markt verdient meer, maar valt statistisch niet meer op.

Solution to Exercise 3
traded = ps["traded_liq"].dropna()
window_rows = []
for end in range(120, len(traded) + 1, 12):
    y = traded.iloc[end - 120:end]
    fit = hap.stats.newey_west(y, factors4.reindex(y.index), lags=6)
    alpha_ann, se_ann = 1200 * fit.params["const"], 1200 * fit.bse["const"]
    window_rows.append({"venster eindigt": f"{y.index[-1]:%Y-%m}", "alpha (% p.j.)": alpha_ann,
                        "ondergrens 95%": alpha_ann - 1.96 * se_ann, "bovengrens 95%": alpha_ann + 1.96 * se_ann})
windows = pd.DataFrame(window_rows).set_index("venster eindigt")

full_fit = hap.stats.newey_west(traded, factors4.reindex(traded.index), lags=6)
resid_vol_ann = np.sqrt(12) * full_fit.resid.std()
years_needed = (2 * resid_vol_ann / 0.03) ** 2
print(f"fractie vensters met ondergrens > 0: {(windows['ondergrens 95%'] > 0).mean():.0%}")
print(f"residuele volatiliteit: {resid_vol_ann:.1%} per jaar; jaren nodig voor t = 2 bij alpha = 3%: {years_needed:.0f}")
windows.iloc[::5].round(2)
fractie vensters met ondergrens > 0: 20%
residuele volatiliteit: 12.6% per jaar; jaren nodig voor t = 2 bij alpha = 3%: 71
Loading...

De intervallen van tien jaar zijn ongeveer vijftien procentpunt breed, en slechts een vijfde van de vensters ligt volledig boven nul. Met een residuele volatiliteit van 12,6% per jaar is ruim zeventig jaar data nodig om een premie van 3% met t=2t = 2 te zien. De premie rust dus op het gemiddelde van een halve eeuw, en geen enkel decennium kan die op zich bevestigen of weerleggen.

References
  1. Amihud, Y. (2002). Illiquidity and Stock Returns: Cross-Section and Time-Series Effects. Journal of Financial Markets, 5(1), 31–56. 10.1016/S1386-4181(01)00024-6
  2. Glosten, L. R., & Milgrom, P. R. (1985). Bid, Ask and Transaction Prices in a Specialist Market with Heterogeneously Informed Traders. Journal of Financial Economics, 14(1), 71–100. 10.1016/0304-405X(85)90044-3
  3. Kyle, A. S. (1985). Continuous Auctions and Insider Trading. Econometrica, 53(6), 1315–1335. 10.2307/1913210
  4. Roll, R. (1984). A Simple Implicit Measure of the Effective Bid-Ask Spread in an Efficient Market. The Journal of Finance, 39(4), 1127–1139. 10.1111/j.1540-6261.1984.tb03897.x
  5. Pástor, Ľ., & Stambaugh, R. F. (2003). Liquidity Risk and Expected Stock Returns. Journal of Political Economy, 111(3), 642–685. 10.1086/374184
  6. Santa-Clara, P. (2026). What I Learned About Asset Pricing. LinkedIn post. https://www.linkedin.com/pulse/what-i-learned-asset-pricing-pedro-santa-clara-apjce/
  7. O’Hara, M. (1995). Market Microstructure Theory. Blackwell. 10.1093/rfs/8.4.1235
  8. Amihud, Y., & Mendelson, H. (1986). Asset Pricing and the Bid-Ask Spread. Journal of Financial Economics, 17(2), 223–249. 10.1016/0304-405X(86)90065-6
  9. Acharya, V. V., & Pedersen, L. H. (2005). Asset Pricing with Liquidity Risk. Journal of Financial Economics, 77(2), 375–410. 10.1016/j.jfineco.2004.06.007
  10. Budish, E., Cramton, P., & Shim, J. (2015). The High-Frequency Trading Arms Race: Frequent Batch Auctions as a Market Design Response. Quarterly Journal of Economics, 130(4), 1547–1621. 10.1093/qje/qjv027

Deze onafhankelijke site is niet verbonden aan of onderschreven door QuantEcon. De vormgeving is gebaseerd op het MIT-gelicentieerde QuantEcon MyST-thema. Zie THIRD_PARTY_NOTICES.md voor de licentie-informatie.