Skip to main content

solver/numeric/
result.rs

1use crate::models::traits::ControlOutput;
2
3/// Generic output from a numerical HJB solver, independent of any specific
4/// application (market making, option pricing, etc.).
5///
6/// Contains the value function and optimal controls at a query state.
7/// Use [`GridSolution::to_spreads`] to convert to market-making spreads
8/// when the model is a market-making model.
9#[derive(Debug, Clone)]
10pub struct GridSolution<const N: usize> {
11    /// Value function V(t, x) at the query state.
12    pub value: f64,
13
14    /// Optimal control intensities at the query state.
15    pub control: ControlOutput<N>,
16
17    /// Full value function on grid (Some for FD solvers, None for BSDE).
18    pub value_grid: Option<Vec<f64>>,
19}
20
21impl<const N: usize> GridSolution<N> {
22    /// Create a `GridSolution` from raw components.
23    pub fn new(value: f64, control: ControlOutput<N>, value_grid: Option<Vec<f64>>) -> Self {
24        Self {
25            value,
26            control,
27            value_grid,
28        }
29    }
30}
31
32/// Market-making spreads derived from solver output.
33///
34/// Computed from the generic [`GridSolution`] via the exponential fill-rate
35/// formula: delta = -ln(lambda / A) / kappa.
36#[derive(Debug, Clone)]
37pub struct SpreadResult {
38    /// Optimal bid spread (distance from mid to bid).
39    pub bid_spread: f64,
40
41    /// Optimal ask spread (distance from mid to ask).
42    pub ask_spread: f64,
43
44    /// Optimal bid intensity: A * exp(-kappa * bid_spread).
45    pub bid_intensity: f64,
46
47    /// Optimal ask intensity: A * exp(-kappa * ask_spread).
48    pub ask_intensity: f64,
49}
50
51impl<const N: usize> GridSolution<N> {
52    /// Convert generic solver output to market-making spreads.
53    ///
54    /// Uses the first control dimension (index 0) for bid/ask intensities.
55    /// This is the convention for all market-making models where state
56    /// dimension 0 is inventory q.
57    ///
58    /// # Panics
59    ///
60    /// Panics if N = 0 (no control dimensions exist).
61    pub fn to_spreads(&self, a: f64, kappa: f64) -> SpreadResult {
62        assert!(N > 0, "GridSolution<0> has no control dimensions");
63        let bid_intensity = self.control.lambda_plus[0];
64        let ask_intensity = self.control.lambda_minus[0];
65        let bid_spread = -(bid_intensity / a).ln() / kappa;
66        let ask_spread = -(ask_intensity / a).ln() / kappa;
67        SpreadResult {
68            bid_spread,
69            ask_spread,
70            bid_intensity,
71            ask_intensity,
72        }
73    }
74}