Files
ftdt-quant-lab/strategies/cartea_jaimungal.py
T

149 lines
5.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Cartea-Jaimungal HFT Model — Stochastic Control for High-Frequency Trading.
Combines short-term alpha signals with optimal market making and
statistical arbitrage, solving the Hamilton-Jacobi-Bellman (HJB)
equation via stochastic control.
Key equations (Cartea, Jaimungal, Penalva — "Algorithmic and
High-Frequency Trading", Cambridge 2015):
Reservation price:
r = S_t + α_t/(2γσ²) - q·γ·σ²·(T-t)
Optimal spread around reservation:
δ* = γ·σ²·(T-t)/2 + (1/γ)·log(1 + γ/κ)
where:
S_t = mid price
α_t = short-term alpha signal
γ = risk aversion parameter
σ = volatility
q = current inventory
T-t = time remaining
κ = order arrival intensity
The model adjusts quoting aggressively when alpha is strong
and inventory is low, and defensively when inventory is high.
Usage:
from strategies.cartea_jaimungal import CarteaJaimungal
cj = CarteaJaimungal(gamma=0.1, sigma=0.01, kappa=1.5)
bid, ask, res_price = cj.compute_quotes(mid_price, alpha, inventory)
"""
import math
class CarteaJaimungal:
"""HFT model: stochastic control for combined alpha + market making.
Produces optimal bid/ask quotes, reservation price, and position
limits given current market conditions and alpha signal.
"""
def __init__(self, gamma: float = 0.1, sigma: float = 0.01, kappa: float = 1.5,
T: float = 10.0, max_inventory: float = 0.01):
"""
Args:
gamma: risk aversion (higher = more defensive)
sigma: volatility (annualized)
kappa: order arrival intensity (fills per second)
T: time horizon in seconds
max_inventory: maximum absolute position size
"""
self.gamma = gamma
self.sigma = sigma
self.kappa = kappa
self.T = T
self.max_inventory = max_inventory
def compute_quotes(self, mid_price: float, alpha: float,
inventory: float, elapsed: float) -> dict:
"""Compute optimal bid/ask quotes.
Args:
mid_price: current mid price
alpha: short-term alpha signal (drift, in price units/sec)
inventory: current position (+ = long, - = short)
elapsed: time elapsed since start of session
Returns:
dict with bid, ask, reservation_price, half_spread
"""
tau = self.T - elapsed
if tau < 0.01:
tau = 0.01 # Prevent singularity at expiry
sig2 = self.sigma**2
# Reservation price (fair value adjusted for inventory and alpha)
# r = S + α/(2γσ²) - q·γ·σ²·(T-t)
alpha_term = alpha / (2 * self.gamma * sig2) if sig2 > 1e-10 else 0
inventory_penalty = inventory * self.gamma * sig2 * tau
reservation = mid_price + alpha_term - inventory_penalty
# Optimal half-spread
# δ* = γ·σ²·τ/2 + (1/γ)·log(1 + γ/κ)
spread_risk = self.gamma * sig2 * tau / 2.0
if self.kappa > 0 and self.gamma > 0:
log_term = (1.0 / self.gamma) * math.log(1.0 + self.gamma / self.kappa)
else:
log_term = 0.001
half_spread = max(spread_risk + log_term, 0.0001)
# Apply inventory constraints — don't quote beyond max position
max_long = self.max_inventory
max_short = -self.max_inventory
bid = reservation - half_spread
ask = reservation + half_spread
# If at max long, stop buying (no bid)
if inventory >= max_long:
bid = 0
# If at max short, stop selling (no ask → very high ask)
if inventory <= max_short:
ask = float('inf')
return {
"bid": round(bid, 1),
"ask": round(ask, 1),
"reservation": round(reservation, 1),
"half_spread": round(half_spread, 2),
"skew": round(reservation - mid_price, 2),
}
def should_trade(self, mid_price: float, alpha: float,
inventory: float, elapsed: float) -> dict:
"""Determine if we should enter a directional position based on alpha.
Returns dict with side, size, and confidence.
"""
quotes = self.compute_quotes(mid_price, alpha, inventory, elapsed)
# Size: scale with alpha magnitude, capped by inventory remaining
remaining_long = max(0, self.max_inventory - inventory)
remaining_short = max(0, self.max_inventory + inventory)
alpha_strength = abs(alpha)
threshold = self.gamma * self.sigma**2 * 0.1 # Minimum edge
signal = None
size = 0.0
confidence = 0.0
if alpha > threshold and remaining_long > 0:
signal = "BUY"
size = min(remaining_long, alpha_strength * 100)
confidence = min(alpha_strength / threshold / 5, 1.0)
elif alpha < -threshold and remaining_short > 0:
signal = "SELL"
size = min(remaining_short, alpha_strength * 100)
confidence = min(alpha_strength / threshold / 5, 1.0)
return {
"signal": signal,
"size": round(size, 6),
"confidence": round(confidence, 4),
"quotes": quotes,
}