financialkit.market_microstructure#

Demsetz’s quoted, effective and realized spreads and the price impact; the Roll and Corwin-Schultz spread estimators; Lee-Ready trade classification with the tick test, the quote rule and lagged quotes; Garman’s Poisson dealer, Ho-Stoll inventory quotes and Avellaneda-Stoikov market making, simulated around a Brownian mid price from financialkit.integrators.brownian_increments(); Kyle’s insider and the Glosten-Milgrom dealer; Hasbrouck’s trade-impact VAR; PIN by maximum likelihood and VPIN by volume buckets; the square-root impact law, Bertsimas-Lo dynamic programming and Almgren-Chriss efficient execution; Amihud illiquidity and Kyle’s lambda by regression.

financialkit.market_microstructure: spreads, dealers, information, trade impact and execution.

Demsetz’s quoted, effective and realized spreads, and the Roll and Corwin-Schultz estimators that need no quotes; Lee-Ready trade classification; Garman’s Poisson dealer, Ho-Stoll inventory quotes and Avellaneda-Stoikov market making (simulated around a Brownian mid from financialkit.integrators); Kyle’s insider and Glosten-Milgrom’s dealer; Hasbrouck’s trade-impact VAR; PIN by maximum likelihood and VPIN by volume buckets; the square-root impact law, Bertsimas-Lo and Almgren-Chriss optimal execution; Amihud illiquidity and Kyle’s lambda by regression. Trade signs are +1 for buys and -1 for sells.

class financialkit.market_microstructure.AvellanedaStoikov(risk_aversion, sigma, k, T)[source]#

Bases: QuotingStrategy

Avellaneda-Stoikov’s inventory strategy: the optimal spread around the reservation price.

Parameters:
  • risk_aversion (float) – \(\gamma\).

  • sigma (float) – Mid-price volatility in price units per square root of time.

  • k (float) – Decay of the fill intensity with distance from the mid.

  • T (float) – Terminal time.

quotes(mid, inventory, t)[source]#

Bid and ask given the mid price and inventory of every path at time t.

Parameters:
Return type:

tuple[ndarray, ndarray]

Returns:

bid, ask (ndarray, shape (n_paths,))

class financialkit.market_microstructure.BertsimasLoResult(delta_w, delta_x, a, b, c, d, trades, expected_cost)[source]#

Bases: object

Bertsimas and Lo’s (1998) dynamic-programming execution policy.

Before period k (0-based) with \(W_k\) shares left and last signal \(X_{k-1}\), the optimal order is \(S_k = \delta_{w,k} W_k + \delta_{x,k} X_{k-1}\), and the expected remaining cost is \(P_{k-1} W_k + a_k W_k^2 + b_k X_{k-1} W_k + c_k X_{k-1}^2 + d_k\).

Parameters:
a: ndarray#

Quadratic cost coefficient of the value function.

Type:

ndarray, shape (T,)

b: ndarray#

Signal-shares coefficient of the value function.

Type:

ndarray, shape (T,)

c: ndarray#

Squared-signal coefficient of the value function.

Type:

ndarray, shape (T,)

d: ndarray#

Constant of the value function.

Type:

ndarray, shape (T,)

delta_w: ndarray#

Fraction of the remaining shares traded each period.

Type:

ndarray, shape (T,)

delta_x: ndarray#

Shares traded per unit of the last signal.

Type:

ndarray, shape (T,)

expected_cost: float#

Expected total cost from the start, \(E[\sum_k P_k S_k]\).

trades: ndarray#

Orders executed along the given signal path.

Type:

ndarray, shape (T,)

class financialkit.market_microstructure.ExecutionResult(times, holdings, trades, expected_cost, variance)[source]#

Bases: object

An execution schedule for selling (or buying) a block of shares.

Parameters:
expected_cost: float#

Expected implementation shortfall.

holdings: ndarray#

Shares still to trade at each date, from X down to 0.

Type:

ndarray, shape (N + 1,)

times: ndarray#

Trading dates \(t_0 = 0, \dots, t_N = T\).

Type:

ndarray, shape (N + 1,)

trades: ndarray#

Shares traded in each interval, \(n_k = x_{k-1} - x_k\).

Type:

ndarray, shape (N,)

variance: float#

Variance of the implementation shortfall.

class financialkit.market_microstructure.GarmanResult(times, stock, cash, failed, failure_time)[source]#

Bases: object

Garman’s (1976) dealer under Poisson order flow, simulated.

Parameters:
cash: ndarray#

Dealer’s cash after each event.

Type:

ndarray, shape (n_paths, n_events + 1)

failed: ndarray#

Whether stock or cash ran out within the simulated events.

Type:

ndarray of bool, shape (n_paths,)

failure_time: ndarray#

Time of failure, inf for survivors.

Type:

ndarray, shape (n_paths,)

stock: ndarray#

Dealer’s inventory of shares after each event.

Type:

ndarray, shape (n_paths, n_events + 1)

times: ndarray#

Event times, starting at 0.

Type:

ndarray, shape (n_paths, n_events + 1)

class financialkit.market_microstructure.GlostenMilgromResult(bid, ask, belief, trades, informed, v_low, v_high, true_value)[source]#

Bases: object

A simulated sequence of Glosten-Milgrom trades and quotes.

Parameters:
ask: ndarray#

Ask quoted before each trade, \(E[V \mid \text{history}, \text{buy}]\).

Type:

ndarray, shape (n,)

belief: ndarray#

Dealer’s probability that the value is high, before and after each trade.

Type:

ndarray, shape (n + 1,)

bid: ndarray#

Bid quoted before each trade, \(E[V \mid \text{history}, \text{sell}]\).

Type:

ndarray, shape (n,)

informed: ndarray#

Whether each trader was informed.

Type:

ndarray of bool, shape (n,)

trades: ndarray#

+1 buy or -1 sell.

Type:

ndarray, shape (n,)

true_value: float#

The value the informed traders know.

v_high: float#

High value.

v_low: float#

Low value.

class financialkit.market_microstructure.KyleResult(lam, beta, insider_profit, posterior_variance, sigma_v, sigma_u, p0)[source]#

Bases: object

Kyle’s (1985) single-auction linear equilibrium.

Parameters:
beta: float#

Insider’s trading intensity \(\beta = \sigma_u / \sigma_v\), so x = beta (v - p0).

insider_profit: float#

Insider’s expected profit \(\sigma_v \sigma_u / 2\), equal to the noise traders’ expected loss.

lam: float#

price change per unit of order flow.

Type:

Price impact \(\lambda = \sigma_v / (2\sigma_u)\)

p0: float#

Prior mean of the value, the price before trading.

posterior_variance: float#

half the private information is revealed.

Type:

\(\operatorname{Var}(v \mid p) = \sigma_v^2 / 2\)

sigma_u: float#

Standard deviation of the noise traders’ order flow.

sigma_v: float#

Standard deviation of the asset’s liquidation value.

class financialkit.market_microstructure.MarketMakingResult(times, mid, bid, ask, inventory, cash)[source]#

Bases: object

A simulated market maker quoting around a Brownian mid price.

Parameters:
ask: ndarray#

Ask quoted during each step.

Type:

ndarray, shape (n_paths, n_steps)

bid: ndarray#

Bid quoted during each step.

Type:

ndarray, shape (n_paths, n_steps)

cash: ndarray#

Cash account.

Type:

ndarray, shape (n_paths, n_steps + 1)

inventory: ndarray#

Shares held.

Type:

ndarray, shape (n_paths, n_steps + 1)

mid: ndarray#

Mid price.

Type:

ndarray, shape (n_paths, n_steps + 1)

property pnl: ndarray#

Terminal profit, cash plus inventory marked at the mid.

Type:

ndarray, shape (n_paths,)

times: ndarray#

Time grid.

Type:

ndarray, shape (n_steps + 1,)

class financialkit.market_microstructure.PINResult(alpha, delta, mu, eps_b, eps_s, pin, pin_se, loglik)[source]#

Bases: object

Easley-Kiefer-O’Hara-Paperman model fitted by maximum likelihood.

Parameters:
alpha: float#

Probability of an information event on a day.

delta: float#

Probability that an information event is bad news.

eps_b: float#

Arrival rate of uninformed buyers.

eps_s: float#

Arrival rate of uninformed sellers.

loglik: float#

Maximised log-likelihood.

mu: float#

Arrival rate of informed traders on event days.

pin: float#

Probability of informed trading \(\alpha\mu / (\alpha\mu + \varepsilon_b + \varepsilon_s)\).

pin_se: float#

Delta-method standard error of pin from the observed information.

class financialkit.market_microstructure.QuotingStrategy[source]#

Bases: ABC

A market maker’s rule for its bid and ask.

abstractmethod quotes(mid, inventory, t)[source]#

Bid and ask given the mid price and inventory of every path at time t.

Parameters:
Return type:

tuple[ndarray, ndarray]

Returns:

bid, ask (ndarray, shape (n_paths,))

class financialkit.market_microstructure.SymmetricQuotes(half_spread)[source]#

Bases: QuotingStrategy

Quotes a fixed half-spread either side of the mid, ignoring inventory.

Parameters:

half_spread (float or callable) – A number, or f(t) returning one.

quotes(mid, inventory, t)[source]#

Bid and ask given the mid price and inventory of every path at time t.

Parameters:
Return type:

tuple[ndarray, ndarray]

Returns:

bid, ask (ndarray, shape (n_paths,))

class financialkit.market_microstructure.TradeImpactResult(coef_r, coef_x, impulse, permanent_impact, trade_share, innovation_cov)[source]#

Bases: object

Hasbrouck’s (1991) vector autoregression of quote revisions and trades.

Parameters:
coef_r: ndarray#

Return equation, columns [x_t, r_{t-1..t-p}, x_{t-1..t-p}] (no intercept).

Type:

ndarray, shape (1 + 2 * lags,)

coef_x: ndarray#

Trade equation, columns [r_{t-1..t-p}, x_{t-1..t-p}].

Type:

ndarray, shape (2 * lags,)

impulse: ndarray#

Cumulative quote revision after a unit trade innovation.

Type:

ndarray, shape (horizon + 1,)

innovation_cov: ndarray#

Covariance of the return and trade innovations.

Type:

ndarray, shape (2, 2)

permanent_impact: float#

Long-run price impact of a unit trade innovation, the last entry of impulse.

trade_share: float#

Fraction of the efficient price’s variance explained by trades, Hasbrouck’s \(R^2_w\).

class financialkit.market_microstructure.VPINResult(vpin, buy_volume, sell_volume, bucket_end)[source]#

Bases: object

Volume-synchronized probability of informed trading.

Parameters:
bucket_end: ndarray#

Index of the bar in which each bucket filled.

Type:

ndarray of int, shape (n_buckets,)

buy_volume: ndarray#

Buy volume classified into each bucket.

Type:

ndarray, shape (n_buckets,)

property imbalance: ndarray#

\(|V^B - V^S| / V\) for each bucket.

Type:

ndarray, shape (n_buckets,)

sell_volume: ndarray#

Sell volume classified into each bucket.

Type:

ndarray, shape (n_buckets,)

vpin: ndarray#

Rolling mean order imbalance; nan until window buckets are full.

Type:

ndarray, shape (n_buckets,)

financialkit.market_microstructure.align_quotes(trade_times, quote_times, bid, ask, delay=0.0)[source]#

The quotes in force delay time units before each trade.

Lee and Ready (1991) compared each trade with the quote standing five seconds earlier, because quote updates triggered by a trade were often time-stamped before it.

Parameters:
  • trade_times (ArrayLike)

  • quote_times (ArrayLike) – Increasing times at which the quote changed.

  • bid (ArrayLike) – Quote after each change.

  • ask (ArrayLike) – Quote after each change.

  • delay (float) – Look-back, in the units of the times.

Return type:

tuple[ndarray, ndarray]

Returns:

bid, ask (ndarray, shape (n,)) – nan where no quote had yet been posted.

Examples

>>> b, a = align_quotes([3.0, 7.0], [0.0, 5.0], [10.0, 11.0], [10.2, 11.2], delay=2.5)
>>> b.tolist(), a.tolist()
([10.0, 10.0], [10.2, 10.2])
financialkit.market_microstructure.almgren_chriss(X, T, n_steps, sigma, eta, risk_aversion, gamma=0.0, epsilon=0.0)[source]#

Almgren-Chriss efficient liquidation of X shares over [0, T].

Minimising \(E + \lambda V\) gives

\[x_j = X\,\frac{\sinh(\kappa (T - t_j))}{\sinh(\kappa T)}, \qquad \cosh(\kappa\tau) = 1 + \frac{\lambda\sigma^2\tau^2}{2\tilde\eta},\]

so \(1/\kappa\) is the half-life of the trade: a risk-neutral trader (\(\lambda = 0\)) sells evenly, and a very risk-averse one sells almost everything at once.

Parameters:
  • X (float) – Shares to sell.

  • T (float) – Horizon.

  • n_steps (int) – Number of trading intervals \(N\).

  • sigma (float) – Price volatility per unit time (price units).

  • eta (float) – Temporary impact per unit of trading rate.

  • risk_aversion (float) – \(\lambda \ge 0\).

  • gamma (float) – Permanent impact and fixed cost per share.

  • epsilon (float) – Permanent impact and fixed cost per share.

Return type:

ExecutionResult

Returns:

ExecutionResult

Examples

>>> almgren_chriss(10.0, 1.0, 5, sigma=0.3, eta=0.05, risk_aversion=0.0).holdings.round(6).tolist()
[10.0, 8.0, 6.0, 4.0, 2.0, 0.0]
financialkit.market_microstructure.amihud_illiquidity(returns, dollar_volume, scale=1000000.0)[source]#

Amihud’s ratio \(\frac{1}{D}\sum_d |r_d| / \mathrm{DVol}_d\), over days with trading.

Parameters:
  • returns (ArrayLike) – Daily returns.

  • dollar_volume (ArrayLike) – Daily dollar volume.

  • scale (float) – Multiplier, conventionally \(10^6\) (impact per million dollars).

Return type:

float

Returns:

float

Examples

>>> amihud_illiquidity([0.01, -0.02, 0.0], [2e6, 4e6, 0.0])
0.005
financialkit.market_microstructure.avellaneda_stoikov_spread(risk_aversion, sigma, time_left, k)[source]#

Optimal total spread \(\gamma\sigma^2 (T - t) + \frac{2}{\gamma}\ln(1 + \gamma / k)\).

As \(\gamma \to 0\) it tends to \(2/k\), the spread that maximises expected profit from fills at rate \(A e^{-k\delta}\).

Return type:

ndarray

Parameters:

Examples

>>> round(float(avellaneda_stoikov_spread(0.1, 2.0, 1.0, 1.5)), 4)
1.6908
financialkit.market_microstructure.bertsimas_lo(shares, n_periods, theta, gamma=0.0, rho=0.0, sigma_eta=0.0, x0=0.0, signals=None, p0=0.0)[source]#

Bertsimas and Lo’s optimal schedule for buying shares over n_periods.

The execution price follows \(P_k = P_{k-1} + \theta S_k + \gamma X_k + \varepsilon_k\) with a serially correlated signal \(X_k = \rho X_{k-1} + \eta_k\), and the trader minimises \(E[\sum_k P_k S_k]\) subject to \(\sum_k S_k = \bar S\). The value function is quadratic, and backward induction gives

\[\delta_{w,k} = 1 - \frac{\theta}{2a_{k+1}}, \quad \delta_{x,k} = \frac{\rho b_{k+1}}{2a_{k+1}}, \quad a_k = \theta - \frac{\theta^2}{4a_{k+1}}, \quad b_k = \rho\left(\gamma + \frac{\theta b_{k+1}}{2a_{k+1}}\right),\]

from \(a = \theta\), \(b = \gamma\rho\) in the last period. With \(\gamma = 0\) the policy is \(S_k = W_k / (T - k)\), an even split, and the expected cost is \(P_0\bar S + \theta \bar S^2 (T + 1) / (2T)\).

Parameters:
  • shares (float) – \(\bar S\).

  • n_periods (int) – \(T\).

  • theta (float) – Permanent impact per share.

  • gamma (float) – Signal loading, persistence and innovation volatility.

  • rho (float) – Signal loading, persistence and innovation volatility.

  • sigma_eta (float) – Signal loading, persistence and innovation volatility.

  • x0 (float) – Signal observed before the first trade, \(X_{-1}\).

  • signals (ArrayLike | None) – Realised \(X_{k-1}\) before each trade; the expected path \(\rho^k x_0\) if omitted.

  • p0 (float) – Price before trading.

Return type:

BertsimasLoResult

Returns:

BertsimasLoResult

Examples

>>> res = bertsimas_lo(1000.0, 4, theta=1e-4)
>>> res.trades.round(9).tolist(), round(res.expected_cost, 6)
([250.0, 250.0, 250.0, 250.0], 62.5)
financialkit.market_microstructure.bulk_volume_classification(price_changes, volumes, sigma=None)[source]#

Buy volume of each bar by bulk volume classification, \(V \Phi(\Delta P / \sigma_{\Delta P})\).

Parameters:
  • price_changes (ArrayLike) – Price change over each bar.

  • volumes (ArrayLike) – Volume traded in each bar.

  • sigma (float | None) – Scale of price changes; their sample standard deviation if omitted.

Return type:

ndarray

Returns:

ndarray, shape (n,)

Examples

>>> bulk_volume_classification([0.0, 1.0], [100.0, 100.0], sigma=1.0).round(2).tolist()
[50.0, 84.13]
financialkit.market_microstructure.corwin_schultz_spread(high, low, clip=True)[source]#

Corwin-Schultz relative spread from each pair of consecutive days’ highs and lows.

The variance in a day’s high-low range grows with the horizon but the spread does not, so comparing one two-day range with two one-day ranges separates them:

\[\alpha = \frac{\sqrt{2\beta} - \sqrt\beta}{3 - 2\sqrt2} - \sqrt{\frac{\gamma}{3 - 2\sqrt2}}, \qquad S = \frac{2(e^\alpha - 1)}{1 + e^\alpha},\]

with \(\beta\) the sum of the two squared daily log ranges and \(\gamma\) the squared two-day log range.

Parameters:
  • high (ArrayLike) – Daily highs and lows.

  • low (ArrayLike) – Daily highs and lows.

  • clip (bool) – Set negative two-day estimates to zero, as Corwin and Schultz do before averaging.

Return type:

ndarray

Returns:

ndarray, shape (n - 1,) – Average over a month for a monthly estimate.

Examples

>>> corwin_schultz_spread([101.0, 101.2], [99.0, 99.1]).round(4).tolist()
[0.0169]
financialkit.market_microstructure.effective_spread(price, bid, ask, side, relative=False)[source]#

Effective spread \(2 q (p - m)\): twice the premium paid over the midquote.

Parameters:
  • price (ArrayLike) – Trade prices.

  • bid (ArrayLike) – Quotes prevailing at each trade.

  • ask (ArrayLike) – Quotes prevailing at each trade.

  • side (ArrayLike) – +1 for a buyer-initiated trade, -1 for a seller-initiated one.

  • relative (bool) – Divide by the midquote.

Return type:

ndarray

Returns:

ndarray, shape (n,)

Examples

A buy inside the quotes pays less than the quoted spread.

>>> effective_spread([100.03], [99.95], [100.05], [1]).round(6).tolist()
[0.06]
financialkit.market_microstructure.execution_cost(holdings, T, sigma, eta, gamma=0.0, epsilon=0.0)[source]#

Expected shortfall and variance of any schedule in the Almgren-Chriss model.

\[E = \tfrac12\gamma X^2 + \varepsilon\sum|n_k| + \frac{\tilde\eta}{\tau}\sum n_k^2, \qquad V = \sigma^2\tau\sum_{k=1}^N x_k^2, \qquad \tilde\eta = \eta - \tfrac12\gamma\tau.\]
Parameters:
  • holdings (ArrayLike) – \(x_0 = X, \dots, x_N = 0\) on an even grid over [0, T].

  • T (float) – Horizon, price volatility, temporary and permanent impact, and the fixed half-spread cost.

  • sigma (float) – Horizon, price volatility, temporary and permanent impact, and the fixed half-spread cost.

  • eta (float) – Horizon, price volatility, temporary and permanent impact, and the fixed half-spread cost.

  • gamma (float) – Horizon, price volatility, temporary and permanent impact, and the fixed half-spread cost.

  • epsilon (float) – Horizon, price volatility, temporary and permanent impact, and the fixed half-spread cost.

Return type:

tuple[float, float]

Returns:

expected_cost, variance (float)

Examples

>>> execution_cost([10.0, 5.0, 0.0], T=2.0, sigma=1.0, eta=0.1)
(5.0, 25.0)
financialkit.market_microstructure.fit_impact_law(participation, impact)[source]#

Fit \(I = Y q^\delta\) by least squares on logs.

Parameters:
  • participation (ArrayLike) – \(q = Q / V\), positive.

  • impact (ArrayLike) – Measured impact (divided by volatility), positive.

Return type:

OLSResult

Returns:

OLSResult – coef = [log Y, delta]; the square-root law says delta = 0.5.

Examples

>>> q = [0.001, 0.01, 0.1]
>>> fit_impact_law(q, square_root_impact(q, 1.0, 1.0, Y=0.7)).coef.round(6).tolist()
[-0.356675, 0.5]
financialkit.market_microstructure.fit_pin(buys, sells)[source]#

Fit the EKOP model by maximum likelihood (scipy.optimize.minimize()).

The probabilities are optimised on the logit scale and the rates on the log scale, from a small grid of starting points as Yan and Zhang (2012) recommend, because the likelihood has local maxima.

Parameters:
  • buys (ArrayLike) – Daily counts of buyer- and seller-initiated trades.

  • sells (ArrayLike) – Daily counts of buyer- and seller-initiated trades.

Return type:

PINResult

Returns:

PINResult

financialkit.market_microstructure.garman_ruin_probability(inventory, buy_rate, sell_rate)[source]#

Probability that Garman’s dealer eventually runs out of stock.

Customers buy (the dealer sells one share) at rate buy_rate and sell at rate sell_rate, so the inventory is a gambler’s-ruin random walk:

\[P(\text{ruin} \mid I_0) = \min\left(1, \left(\lambda_a / \lambda_b\right)^{I_0}\right).\]
Parameters:
  • inventory (ArrayLike) – Starting inventory \(I_0 \ge 0\).

  • buy_rate (float) – \(\lambda_a\), the arrival rate of buy orders at the ask.

  • sell_rate (float) – \(\lambda_b\), the arrival rate of sell orders at the bid.

Return type:

ndarray

Returns:

ndarray

Examples

>>> garman_ruin_probability([0, 3, 10], 2.0, 3.0).round(4).tolist()
[1.0, 0.2963, 0.0173]
>>> float(garman_ruin_probability(50, 1.0, 1.0))
1.0
financialkit.market_microstructure.glosten_milgrom_quotes(belief, alpha, v_low, v_high, eta=0.5)[source]#

Zero-profit bid and ask when a fraction alpha of traders know the value.

The value is \(V_H\) with probability \(\theta\) (the belief) and \(V_L\) otherwise. An informed trader buys when it is high and sells when it is low; an uninformed one buys with probability \(\eta\). Then

\[a = E[V \mid \text{buy}] = V_L + (V_H - V_L)\, \frac{\theta(\alpha + (1-\alpha)\eta)}{\theta\alpha + (1-\alpha)\eta},\]

and the bid is the same with a sell.

Parameters:
  • belief (ArrayLike) – \(\theta = P(V = V_H)\).

  • alpha (float) – Probability that a trader is informed, in [0, 1).

  • v_low (float)

  • v_high (float)

  • eta (float) – Probability that an uninformed trader buys.

Return type:

tuple[ndarray, ndarray]

Returns:

bid, ask (ndarray)

Examples

>>> b, a = glosten_milgrom_quotes(0.5, 0.2, 100.0, 110.0)
>>> float(b), float(a)
(104.0, 106.0)
financialkit.market_microstructure.hasbrouck_var(returns, trades, lags=5, horizon=20)[source]#

Fit Hasbrouck’s trade-impact VAR and its cumulative impulse response.

\[\begin{split}r_t &= \sum_{i=1}^p a_i r_{t-i} + \sum_{i=0}^p b_i x_{t-i} + v_{1,t} \\ x_t &= \sum_{i=1}^p c_i r_{t-i} + \sum_{i=1}^p d_i x_{t-i} + v_{2,t}\end{split}\]

Each equation is fitted by least squares (financialkit.time_series.utils.ols()) after demeaning.

Parameters:
  • returns (ArrayLike) – Midquote revisions (log or price changes) in trade time.

  • trades (ArrayLike) – Trade signs or signed volume at the same events.

  • lags (int) – Autoregressive order \(p\).

  • horizon (int) – Number of events over which to accumulate the response.

Return type:

TradeImpactResult

Returns:

TradeImpactResult

financialkit.market_microstructure.ho_stoll_quotes(value, inventory, alpha, beta, risk_aversion, sigma, tau=1.0)[source]#

Optimal bid and ask of a risk-averse dealer, a one-period Ho-Stoll model.

Orders arrive at \(\lambda = \alpha - \beta\delta\) on each side, \(\delta\) being the distance of the quote from the true value \(p\). The dealer maximises expected trading profit minus \(\tfrac{\gamma}{2}\sigma^2\tau\) times the squared end-of-period inventory. The first-order conditions give Ho and Stoll’s two results:

\[\delta_a + \delta_b = \frac{\alpha}{\beta}, \qquad \frac{a + b}{2} = p - \frac{\kappa I}{2(1 + \kappa\beta)}, \quad \kappa = \gamma\sigma^2\tau:\]

the spread does not depend on inventory, and the quotes slide down when the dealer is long.

Parameters:
  • value (ArrayLike) – True value \(p\).

  • inventory (ArrayLike) – Dealer’s inventory \(I\).

  • alpha (float) – Intercept and slope of the order-arrival rate.

  • beta (float) – Intercept and slope of the order-arrival rate.

  • risk_aversion (float) – \(\gamma \ge 0\).

  • sigma (float) – Volatility of the value, per unit time.

  • tau (float) – Holding horizon.

Return type:

tuple[ndarray, ndarray]

Returns:

bid, ask (ndarray)

Examples

>>> b, a = ho_stoll_quotes(100.0, [-10, 0, 10], alpha=10.0, beta=20.0, risk_aversion=0.1, sigma=0.2)
>>> (a - b).round(6).tolist(), ((a + b) / 2).round(4).tolist()
([0.5, 0.5, 0.5], [100.0185, 100.0, 99.9815])
financialkit.market_microstructure.kyle_equilibrium(sigma_v, sigma_u, p0=0.0)[source]#

Kyle’s single-auction equilibrium \(\lambda = \sigma_v / (2\sigma_u)\), \(\beta = \sigma_u / \sigma_v\).

The value is \(v \sim N(p_0, \sigma_v^2)\), noise flow \(u \sim N(0, \sigma_u^2)\), the insider submits \(x = \beta(v - p_0)\) and the market maker sets \(p = p_0 + \lambda(x + u)\).

Parameters:
  • sigma_v (float) – Standard deviation of the value.

  • sigma_u (float) – Standard deviation of the noise order flow.

  • p0 (float) – Prior mean of the value.

Return type:

KyleResult

Returns:

KyleResult

Examples

Doubling the noise halves the price impact and doubles the insider’s profit.

>>> [(r.lam, r.insider_profit) for r in (kyle_equilibrium(2.0, 1.0), kyle_equilibrium(2.0, 2.0))]
[(1.0, 1.0), (0.5, 2.0)]
financialkit.market_microstructure.kyle_lambda(price_changes, signed_volume)[source]#

Regress price changes on signed order flow, \(\Delta p = c + \lambda y + e\).

Return type:

OLSResult

Returns:

OLSResult – coef = [c, lambda] with standard errors.

Parameters:
  • price_changes (ArrayLike)

  • signed_volume (ArrayLike)

Examples

>>> kyle_lambda([0.3, 0.0, 0.5], [2.0, -1.0, 4.0]).coef.round(10).tolist()
[0.1, 0.1]
financialkit.market_microstructure.lee_ready(prices, bid, ask)[source]#

Lee-Ready classification: the quote rule, with the tick test at the midquote.

Parameters:
  • prices (ArrayLike) – Trade prices in time order.

  • bid (ArrayLike) – Quotes to compare each trade with (already lagged if desired).

  • ask (ArrayLike) – Quotes to compare each trade with (already lagged if desired).

Return type:

ndarray

Returns:

ndarray, shape (n,) – +1 buy, -1 sell, 0 if neither rule decides.

Examples

>>> lee_ready([10.05, 10.00, 10.00, 9.95], [9.95] * 4, [10.05] * 4).tolist()
[1.0, -1.0, -1.0, -1.0]
financialkit.market_microstructure.midquote(bid, ask)[source]#

The quote midpoint \((b + a) / 2\), checking the quotes are not crossed.

Return type:

ndarray

Parameters:
  • bid (ArrayLike)

  • ask (ArrayLike)

Examples

>>> midquote([99.9, 100.0], [100.1, 100.4]).tolist()
[100.0, 100.2]
financialkit.market_microstructure.pin_loglik(params, buys, sells)[source]#

Log-likelihood of daily buy and sell counts under the EKOP mixture.

Parameters:
Return type:

float

Returns:

float

financialkit.market_microstructure.price_impact(bid, ask, later_mid, side)[source]#

Price impact \(2 q (m_{t+\Delta} - m_t)\), so effective = realized + impact.

Return type:

ndarray

Parameters:
  • bid (ArrayLike)

  • ask (ArrayLike)

  • later_mid (ArrayLike)

  • side (ArrayLike)

Examples

>>> p, b, a, later, q = [100.05], [99.95], [100.05], [100.02], [1]
>>> float(effective_spread(p, b, a, q)[0] - realized_spread(p, later, q)[0] - price_impact(b, a, later, q)[0]) == 0.0
True
financialkit.market_microstructure.quote_rule(prices, bid, ask)[source]#

Quote rule: +1 above the midquote, -1 below, 0 at it.

Return type:

ndarray

Parameters:
  • prices (ArrayLike)

  • bid (ArrayLike)

  • ask (ArrayLike)

Examples

>>> quote_rule([10.05, 10.00, 9.96], [9.95, 9.95, 9.95], [10.05, 10.05, 10.05]).tolist()
[1.0, 0.0, -1.0]
financialkit.market_microstructure.quoted_spread(bid, ask, relative=False)[source]#

Quoted spread \(a - b\), or \((a - b) / m\) if relative.

Return type:

ndarray

Parameters:
  • bid (ArrayLike)

  • ask (ArrayLike)

  • relative (bool)

Examples

>>> quoted_spread([99.95], [100.05], relative=True).round(6).tolist()
[0.001]
financialkit.market_microstructure.realized_spread(price, later_mid, side)[source]#

Realized spread \(2 q (p - m_{t+\Delta})\): what the liquidity provider keeps.

Parameters:
Return type:

ndarray

Returns:

ndarray, shape (n,)

financialkit.market_microstructure.reservation_price(mid, inventory, risk_aversion, sigma, time_left)[source]#

Avellaneda-Stoikov indifference price \(r = s - q\gamma\sigma^2 (T - t)\).

Return type:

ndarray

Parameters:
  • mid (ArrayLike)

  • inventory (ArrayLike)

  • risk_aversion (float)

  • sigma (float)

  • time_left (ArrayLike)

Examples

>>> reservation_price(100.0, [-2, 0, 2], 0.1, 2.0, 0.5).tolist()
[100.4, 100.0, 99.6]
financialkit.market_microstructure.roll_spread(prices)[source]#

Roll’s estimator \(s = 2\sqrt{-\operatorname{cov}(\Delta p_t, \Delta p_{t-1})}\).

If trades alternate at random between bid and ask around an efficient price, the bounce makes the first-order autocovariance of price changes \(-s^2/4\).

Parameters:

prices (ArrayLike) – Transaction prices (or log prices, giving a relative spread).

Return type:

float

Returns:

float – nan when the autocovariance is positive, which happens in practice and which the model cannot explain.

Examples

Bounce of 0.1 either side of a constant value:

>>> import numpy as np
>>> p = 100.0 + 0.1 * np.random.default_rng(0).choice([-1.0, 1.0], 100_000)
>>> round(roll_spread(p), 2)
0.2
financialkit.market_microstructure.simulate_garman(stock, cash, ask, bid, buy_rate, sell_rate, n_events, n_paths=1, rng=None)[source]#

Simulate Garman’s dealer event by event until stock or cash runs out.

A buy order (rate \(\lambda_a\)) takes one share at the ask; a sell order (rate \(\lambda_b\)) delivers one at the bid. The dealer fails when the stock is zero or the cash cannot pay the bid. Both inventories drift upward only if \(\lambda_b > \lambda_a\) and \(a\lambda_a > b\lambda_b\), which needs \(a > b\): a spread.

Parameters:
  • stock (int) – Starting shares.

  • cash (float) – Starting cash; np.inf isolates the stock inventory.

  • ask (float) – Fixed quotes.

  • bid (float) – Fixed quotes.

  • buy_rate (float) – \(\lambda_a(a)\) and \(\lambda_b(b)\) at those quotes.

  • sell_rate (float) – \(\lambda_a(a)\) and \(\lambda_b(b)\) at those quotes.

  • n_events (int) – Orders simulated per path.

  • n_paths (int)

  • rng (Generator | int | None)

Return type:

GarmanResult

Returns:

GarmanResult – Paths are frozen after failure.

financialkit.market_microstructure.simulate_glosten_milgrom(n_trades, alpha, v_low, v_high, belief0=0.5, eta=0.5, true_value=None, rng=None)[source]#

A sequence of Glosten-Milgrom trades with Bayesian quote updates.

Parameters:
Return type:

GlostenMilgromResult

Returns:

GlostenMilgromResult – After a buy the belief becomes the one that priced the ask, and after a sell the one that priced the bid.

financialkit.market_microstructure.simulate_kyle(eq, n_auctions, beta=None, rng=None)[source]#

Draw independent single-period Kyle markets.

Parameters:
  • eq (KyleResult) – Supplies \(\sigma_v\), \(\sigma_u\), \(p_0\) and the market maker’s \(\lambda\).

  • n_auctions (int)

  • beta (float | None) – Insider’s intensity; the equilibrium value if omitted. Any other value shows why it is optimal.

  • rng (Generator | int | None)

Return type:

tuple[ndarray, ndarray, ndarray]

Returns:

value, order_flow, price (ndarray, shape (n_auctions,)) – \(v\), total flow \(x + u\) and the clearing price.

financialkit.market_microstructure.simulate_market_making(strategy, s0, sigma, T, n_steps, A, k, n_paths=1, rng=None)[source]#

Simulate a market maker as in Avellaneda and Stoikov’s Section 4.

The mid price is arithmetic Brownian motion (increments from financialkit.integrators.brownian_increments()). In each step of length \(dt\) the ask is lifted with probability \(A e^{-k\delta_a} dt\) and the bid hit with probability \(A e^{-k\delta_b} dt\), one share each.

Parameters:
  • strategy (QuotingStrategy)

  • s0 (float) – Initial mid price.

  • sigma (float) – Mid-price volatility.

  • T (float) – Horizon.

  • n_steps (int) – Choose it so that \(A\,dt\) stays well below one.

  • A (float) – Fill-intensity parameters.

  • k (float) – Fill-intensity parameters.

  • n_paths (int)

  • rng (Generator | int | None)

Return type:

MarketMakingResult

Returns:

MarketMakingResult

financialkit.market_microstructure.simulate_pin(n_days, alpha, delta, mu, eps_b, eps_s, rng=None)[source]#

Daily buy and sell counts from the EKOP model.

Return type:

tuple[ndarray, ndarray]

Returns:

buys, sells (ndarray of int, shape (n_days,))

Parameters:
financialkit.market_microstructure.square_root_impact(quantity, daily_volume, sigma, Y=1.0)[source]#

Square-root law \(I = Y \sigma \sqrt{Q / V}\) for the impact of a metaorder.

Parameters:
  • quantity (ArrayLike) – Shares in the order, \(Q\).

  • daily_volume (ArrayLike) – Average daily volume, \(V\).

  • sigma (ArrayLike) – Daily volatility (relative impact) or its price equivalent.

  • Y (float) – Order-one prefactor, measured empirically.

Return type:

ndarray

Returns:

ndarray

Examples

Trading 1% of daily volume in a 2%-volatility stock costs about 20 bp.

>>> float(square_root_impact(1e4, 1e6, 0.02))
0.002
financialkit.market_microstructure.tick_test(prices)[source]#

Tick test: +1 on an uptick or zero-uptick, -1 on a downtick or zero-downtick.

Return type:

ndarray

Returns:

ndarray, shape (n,) – 0 until the price first changes.

Parameters:

prices (ArrayLike)

Examples

>>> tick_test([10.0, 10.0, 10.1, 10.1, 10.0, 10.0]).tolist()
[0.0, 0.0, 1.0, 1.0, -1.0, -1.0]
financialkit.market_microstructure.trade_signs(side)[source]#

Validate trade signs: every entry must be +1 (buy) or -1 (sell).

Return type:

ndarray

Parameters:

side (ArrayLike)

Examples

>>> trade_signs([1, -1, 1]).tolist()
[1.0, -1.0, 1.0]
financialkit.market_microstructure.vpin(price_changes, volumes, bucket_volume, window=50, sigma=None)[source]#

Volume-synchronized probability of informed trading.

Bars are classified by bulk_volume_classification(), their volume is poured into consecutive buckets of size bucket_volume (a bar may straddle buckets), and VPIN is the mean of \(|V^B_\tau - V^S_\tau| / V\) over the last window buckets.

Parameters:
Return type:

VPINResult

Returns:

VPINResult