solver/models/
avellaneda_drift.rs1use super::control::{ControlProblem, StateDerivatives};
2use super::market_making::MarketMakingControl;
3use super::normal_cdf;
4use crate::numeric::finite_difference::discretization::{DimensionKind, Transport};
5use crate::numeric::finite_difference::pde::PdeProblem;
6
7#[derive(Clone)]
12pub struct AvellanedaDrift {
13 pub gamma: f64,
14 pub sigma: f64,
15 pub kappa: f64,
16 pub a: f64,
17 pub mu: f64,
19}
20
21impl AvellanedaDrift {
22 pub fn get_spreads(&self, derivs: &StateDerivatives<2>) -> (f64, f64) {
24 let base_spread = (1.0 / self.gamma) * (1.0 + self.gamma / self.kappa).ln();
25
26 let dv_dq_buy = derivs.fwd[0];
27 let dv_dq_sell = derivs.bwd[0];
28
29 let mut delta_bid = base_spread - dv_dq_buy;
30 let mut delta_ask = base_spread + dv_dq_sell;
31
32 let min_spread = -5.0;
33 delta_bid = delta_bid.max(min_spread);
34 delta_ask = delta_ask.max(min_spread);
35
36 (delta_bid, delta_ask)
37 }
38
39 pub fn fill_rate_base(&self, _state: &[f64; 2]) -> f64 {
41 self.a
42 }
43
44 pub fn fill_rate_decay(&self) -> f64 {
46 self.kappa
47 }
48}
49
50impl ControlProblem<2> for AvellanedaDrift {
51 type Control = MarketMakingControl;
52
53 fn optimize(&self, _t: f64, _state: &[f64; 2], derivs: &StateDerivatives<2>) -> Self::Control {
54 let base_spread = (1.0 / self.gamma) * (1.0 + self.gamma / self.kappa).ln();
55 let delta_bid = (base_spread - derivs.fwd[0]).max(-5.0);
56 let delta_ask = (base_spread + derivs.bwd[0]).max(-5.0);
57
58 MarketMakingControl::new(
59 self.a * (-self.kappa * delta_bid).exp(),
60 self.a * (-self.kappa * delta_ask).exp(),
61 )
62 }
63
64 fn running_reward(&self, _t: f64, _state: &[f64; 2], control: &Self::Control) -> f64 {
65 (control.bid_intensity + control.ask_intensity) / (self.gamma + self.kappa)
67 }
68
69 fn bsde_driver(
70 &self,
71 t: f64,
72 state: &[f64; 2],
73 control: &Self::Control,
74 derivs: &StateDerivatives<2>,
75 _dt: f64,
76 ) -> f64 {
77 self.driver(t, state, control, derivs)
78 }
79
80 fn generator(
81 &self,
82 _t: f64,
83 state: &[f64; 2],
84 _control: &Self::Control,
85 _derivs: &StateDerivatives<2>,
86 ) -> f64 {
87 let q = state[0];
89 -0.5 * self.gamma * self.sigma.powi(2) * q.powi(2) + q * self.mu
90 }
91
92 fn terminal(&self, _state: &[f64; 2]) -> f64 {
93 0.0
94 }
95
96 fn discount_rate(&self, _state: &[f64; 2]) -> f64 {
97 0.0
98 }
99
100 fn constant_discount_rate(&self) -> Option<f64> {
101 Some(0.0)
102 }
103
104 fn next_step(&self, _t: f64, state: &[f64; 2], dt: f64, noise: &[f64; 2]) -> [f64; 2] {
105 let mut next = *state;
106
107 let u = normal_cdf(noise[0]);
108 let base_spread = (1.0 / self.gamma) * (1.0 + self.gamma / self.kappa).ln();
109 let delta_bid = base_spread;
110 let delta_ask = base_spread;
111 let lambda_bid = self.a * (-self.kappa * delta_bid).exp();
112 let lambda_ask = self.a * (-self.kappa * delta_ask).exp();
113 let p_bid = (lambda_bid * dt).clamp(0.0, 1.0);
114 let p_ask = (lambda_ask * dt).clamp(0.0, 1.0);
115 if u < p_bid {
116 next[0] += 1.0;
117 } else if u > 1.0 - p_ask {
118 next[0] -= 1.0;
119 }
120
121 next[1] += self.mu * dt + self.sigma * dt.sqrt() * noise[1];
122 next
123 }
124
125 fn is_reduced_value(&self) -> bool {
126 true
127 }
128
129 fn is_diffusion_dimension(&self, dim: usize) -> bool {
130 dim == 1
131 }
132
133 fn gradient_step(&self, _dim: usize) -> f64 {
134 1.0
135 }
136}
137
138impl PdeProblem<2> for AvellanedaDrift {
139 fn dimension_kind(&self, dim: usize) -> DimensionKind {
140 match dim {
141 0 => DimensionKind::DiscreteJump,
142 _ => DimensionKind::Diffusion,
143 }
144 }
145
146 fn transport(
147 &self,
148 _t: f64,
149 state: &[f64; 2],
150 control: &Self::Control,
151 derivs: &StateDerivatives<2>,
152 ) -> Transport<2> {
153 let q = state[0];
154 let hamiltonian =
155 (control.bid_intensity + control.ask_intensity) / (self.gamma + self.kappa);
156 let jump_transport =
157 control.bid_intensity * derivs.fwd[0] - control.ask_intensity * derivs.bwd[0];
158 let local = -0.5 * self.gamma * self.sigma.powi(2) * q.powi(2) + q * self.mu;
159 Transport::new(
160 [control.bid_intensity, 0.0],
161 [control.ask_intensity, 0.0],
162 hamiltonian + local - jump_transport,
163 )
164 }
165}