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}