Skip to main content

solver/analytical/avellaneda/approximations/
gueant.rs

1use crate::analytical::traits::AnalyticalSolution;
2use crate::models::traits::ControlOutput;
3
4/// The asymptotic approximation (infinite horizon) for the Avellaneda-Stoikov model.
5/// Based on the Gueant-Lehalle-Tapia closed-form solution.
6///
7/// Reference: Guéant, O., Lehalle, C. A., & Tapia, J. (2013). Dealing with the inventory risk: a solution to the market making problem.
8pub struct AvellanedaGueant {
9    pub gamma: f64,
10    pub sigma: f64,
11    pub kappa: f64,
12    pub a: f64,
13}
14
15impl AvellanedaGueant {
16    pub fn new(gamma: f64, sigma: f64, kappa: f64, a: f64) -> Self {
17        Self {
18            gamma,
19            sigma,
20            kappa,
21            a,
22        }
23    }
24
25    /// Guéant-Lehalle-Tapia closed-form approximation for optimal spreads.
26    ///
27    /// This approximation assumes infinite time horizon ($T \to \infty$) and small spreads,
28    /// providing a stationary strategy that depends on inventory $q$ but not time $t$.
29    ///
30    /// The spreads are symmetric around the mid-price, shifted by inventory:
31    /// $\delta^\pm(q) \approx \text{RiskNeutral} \pm \text{InventorySkew} \cdot q$
32    ///
33    /// Reference: Guéant, O., Lehalle, C. A., & Tapia, J. (2013). Dealing with the inventory risk.
34    pub fn approximate_spreads(&self, q: f64) -> (f64, f64) {
35        let risk_factor = (self.sigma.powi(2) * self.gamma / (2.0 * self.kappa * self.a)).sqrt();
36        let const_term = (1.0 / self.gamma) * (1.0 + self.gamma / self.kappa).ln();
37        let correction =
38            (1.0 + self.gamma / self.kappa).powf(0.5 * (1.0 + self.kappa / self.gamma));
39
40        let delta_bid = const_term + (2.0 * q + 1.0) / 2.0 * risk_factor * correction;
41        let delta_ask = const_term - (2.0 * q - 1.0) / 2.0 * risk_factor * correction;
42
43        (delta_bid, delta_ask)
44    }
45}
46
47impl AnalyticalSolution<2> for AvellanedaGueant {
48    fn value_function(&self, _t: f64, _state: &[f64; 2]) -> f64 {
49        // Value function approximation is not implemented here, only controls
50        0.0
51    }
52
53    fn optimal_controls(&self, _t: f64, state: &[f64; 2]) -> ControlOutput<2> {
54        let q = state[0];
55        let (d_bid, d_ask) = self.approximate_spreads(q);
56
57        let lambda_bid = self.a * (-self.kappa * d_bid).exp();
58        let lambda_ask = self.a * (-self.kappa * d_ask).exp();
59
60        let flow = lambda_bid * d_bid + lambda_ask * d_ask;
61
62        ControlOutput {
63            lambda_plus: [lambda_bid, 0.0],
64            lambda_minus: [lambda_ask, 0.0],
65            flow,
66        }
67    }
68}