1use 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)]
16pub struct AvellanedaImpact {
17 pub gamma: f64,
18 pub sigma: f64,
19 pub kappa: f64,
20 pub a: f64,
21 pub xi: f64,
24}
25
26impl AvellanedaImpact {
27 pub fn get_spreads(&self, derivs: &StateDerivatives<2>, q: f64) -> (f64, f64) {
29 let base_spread = (1.0 / self.gamma) * (1.0 + self.gamma / self.kappa).ln();
30
31 let dv_dq_buy = derivs.fwd[0];
32 let dv_dq_sell = derivs.bwd[0];
33
34 let mut delta_bid = base_spread - dv_dq_buy + (q + 1.0) * self.xi;
35 let mut delta_ask = base_spread + dv_dq_sell - (q - 1.0) * self.xi;
36
37 let min_spread = -5.0;
38 delta_bid = delta_bid.max(min_spread);
39 delta_ask = delta_ask.max(min_spread);
40
41 (delta_bid, delta_ask)
42 }
43
44 pub fn fill_rate_base(&self, _state: &[f64; 2]) -> f64 {
46 self.a
47 }
48
49 pub fn fill_rate_decay(&self) -> f64 {
51 self.kappa
52 }
53}
54
55impl ControlProblem<2> for AvellanedaImpact {
56 type Control = MarketMakingControl;
57
58 fn optimize(&self, _t: f64, state: &[f64; 2], derivs: &StateDerivatives<2>) -> Self::Control {
59 let q = state[0];
60 let base_spread = (1.0 / self.gamma) * (1.0 + self.gamma / self.kappa).ln();
61 let delta_bid = (base_spread - derivs.fwd[0] + (q + 1.0) * self.xi).max(-5.0);
62 let delta_ask = (base_spread + derivs.bwd[0] - (q - 1.0) * self.xi).max(-5.0);
63
64 MarketMakingControl::new(
65 self.a * (-self.kappa * delta_bid).exp(),
66 self.a * (-self.kappa * delta_ask).exp(),
67 )
68 }
69
70 fn running_reward(&self, _t: f64, _state: &[f64; 2], control: &Self::Control) -> f64 {
71 (control.bid_intensity + control.ask_intensity) / (self.gamma + self.kappa)
72 }
73
74 fn bsde_driver(
75 &self,
76 t: f64,
77 state: &[f64; 2],
78 control: &Self::Control,
79 derivs: &StateDerivatives<2>,
80 _dt: f64,
81 ) -> f64 {
82 self.driver(t, state, control, derivs)
83 }
84
85 fn generator(
86 &self,
87 _t: f64,
88 state: &[f64; 2],
89 _control: &Self::Control,
90 _derivs: &StateDerivatives<2>,
91 ) -> f64 {
92 let q = state[0];
93 -0.5 * self.gamma * self.sigma.powi(2) * q.powi(2)
94 }
95
96 fn terminal(&self, state: &[f64; 2]) -> f64 {
97 let q = state[0];
98 -0.5 * self.xi * q.powi(2)
99 }
100
101 fn discount_rate(&self, _state: &[f64; 2]) -> f64 {
102 0.0
103 }
104
105 fn constant_discount_rate(&self) -> Option<f64> {
106 Some(0.0)
107 }
108
109 fn next_step(&self, _t: f64, state: &[f64; 2], dt: f64, noise: &[f64; 2]) -> [f64; 2] {
110 let mut next = *state;
111
112 let q = state[0];
113 let u = normal_cdf(noise[0]);
114 let base_spread = (1.0 / self.gamma) * (1.0 + self.gamma / self.kappa).ln();
115 let delta_bid = base_spread + (q + 1.0) * self.xi;
119 let delta_ask = base_spread - (q - 1.0) * self.xi;
120 let lambda_bid = self.a * (-self.kappa * delta_bid).exp();
121 let lambda_ask = self.a * (-self.kappa * delta_ask).exp();
122 let p_bid = (lambda_bid * dt).clamp(0.0, 1.0);
123 let p_ask = (lambda_ask * dt).clamp(0.0, 1.0);
124 if u < p_bid {
125 next[0] += 1.0;
126 } else if u > 1.0 - p_ask {
127 next[0] -= 1.0;
128 }
129
130 next[1] += self.sigma * dt.sqrt() * noise[1];
131 next
132 }
133
134 fn next_step_controlled(
135 &self,
136 _t: f64,
137 state: &[f64; 2],
138 control: &Self::Control,
139 dt: f64,
140 noise: &[f64; 2],
141 ) -> [f64; 2] {
142 let mut next = *state;
143
144 let u = normal_cdf(noise[0]);
148 let p_bid = (control.bid_intensity * dt).clamp(0.0, 1.0);
149 let p_ask = (control.ask_intensity * dt).clamp(0.0, 1.0);
150 if u < p_bid {
151 next[0] += 1.0;
152 } else if u > 1.0 - p_ask {
153 next[0] -= 1.0;
154 }
155
156 next[1] += self.sigma * dt.sqrt() * noise[1];
157 next
158 }
159
160 fn is_reduced_value(&self) -> bool {
161 true
162 }
163
164 fn is_diffusion_dimension(&self, dim: usize) -> bool {
165 dim == 1
166 }
167
168 fn gradient_step(&self, _dim: usize) -> f64 {
169 1.0
170 }
171}
172
173impl PdeProblem<2> for AvellanedaImpact {
174 fn dimension_kind(&self, dim: usize) -> DimensionKind {
175 match dim {
176 0 => DimensionKind::DiscreteJump,
177 _ => DimensionKind::Diffusion,
178 }
179 }
180
181 fn transport(
182 &self,
183 _t: f64,
184 state: &[f64; 2],
185 control: &Self::Control,
186 derivs: &StateDerivatives<2>,
187 ) -> Transport<2> {
188 let q = state[0];
189 let hamiltonian =
190 (control.bid_intensity + control.ask_intensity) / (self.gamma + self.kappa);
191 let jump_transport =
192 control.bid_intensity * derivs.fwd[0] - control.ask_intensity * derivs.bwd[0];
193 let local = -0.5 * self.gamma * self.sigma.powi(2) * q.powi(2);
194 Transport::new(
195 [control.bid_intensity, 0.0],
196 [control.ask_intensity, 0.0],
197 hamiltonian + local - jump_transport,
198 )
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 fn model() -> AvellanedaImpact {
207 AvellanedaImpact {
208 gamma: 0.5,
209 sigma: 0.5,
210 kappa: 1.5,
211 a: 140.0,
212 xi: 0.01,
213 }
214 }
215
216 #[test]
217 fn controlled_step_uses_optimal_intensities() {
218 let m = model();
219 let state = [1.0, 100.0];
220
221 let ctrl = MarketMakingControl::new(0.0, 0.0);
224 let next = ControlProblem::next_step_controlled(&m, 0.0, &state, &ctrl, 0.01, &[0.0, 0.0]);
225 assert_eq!(next[0], 1.0);
226
227 let ctrl = MarketMakingControl::new(1000.0, 0.0);
230 let next = ControlProblem::next_step_controlled(&m, 0.0, &state, &ctrl, 0.01, &[0.0, 0.0]);
231 assert_eq!(next[0], 2.0);
232 }
233
234 #[test]
235 fn impact_model_is_reduced_value() {
236 let m = model();
237 assert!(ControlProblem::is_reduced_value(&m));
238 assert!(ControlProblem::is_diffusion_dimension(&m, 1));
239 assert!(!ControlProblem::is_diffusion_dimension(&m, 0));
240 }
241}