1use crate::models::control::{ControlProblem, StateDerivatives};
24use crate::models::market_making::MarketMakingControl;
25use crate::models::normal_cdf;
26use crate::numeric::finite_difference::discretization::{
27 DimensionKind, JumpKernel, JumpTransition, Transport,
28};
29use crate::numeric::finite_difference::pde::PdeProblem;
30
31#[derive(Clone, Copy, Debug)]
33pub struct AvellanedaLotSize {
34 pub gamma: f64,
36 pub sigma: f64,
38 pub kappa: f64,
40 pub a: f64,
42 pub lot_size: i32,
44}
45
46impl AvellanedaLotSize {
47 pub fn new(gamma: f64, sigma: f64, kappa: f64, a: f64, lot_size: i32) -> Self {
61 assert!(lot_size > 0, "lot size must be positive");
62 Self {
63 gamma,
64 sigma,
65 kappa,
66 a,
67 lot_size,
68 }
69 }
70
71 fn base_spread(&self) -> f64 {
72 (1.0 / self.gamma) * (1.0 + self.gamma / self.kappa).ln()
73 }
74}
75
76impl ControlProblem<2> for AvellanedaLotSize {
77 type Control = MarketMakingControl;
78
79 fn optimize(&self, _t: f64, _state: &[f64; 2], derivs: &StateDerivatives<2>) -> Self::Control {
80 let base = self.base_spread();
81 let delta_bid = (base - derivs.fwd[0]).max(-10.0);
82 let delta_ask = (base + derivs.bwd[0]).max(-10.0);
83 MarketMakingControl::new(
84 self.a * (-self.kappa * delta_bid).exp(),
85 self.a * (-self.kappa * delta_ask).exp(),
86 )
87 }
88
89 fn running_reward(&self, _t: f64, _state: &[f64; 2], control: &Self::Control) -> f64 {
90 (control.bid_intensity + control.ask_intensity) / (self.gamma + self.kappa)
91 }
92
93 fn bsde_driver(
94 &self,
95 t: f64,
96 state: &[f64; 2],
97 control: &Self::Control,
98 derivs: &StateDerivatives<2>,
99 _dt: f64,
100 ) -> f64 {
101 self.driver(t, state, control, derivs)
102 }
103
104 fn generator(
105 &self,
106 _t: f64,
107 state: &[f64; 2],
108 _control: &Self::Control,
109 _derivs: &StateDerivatives<2>,
110 ) -> f64 {
111 -0.5 * self.gamma * self.sigma.powi(2) * state[0].powi(2)
112 }
113
114 fn terminal(&self, _state: &[f64; 2]) -> f64 {
115 0.0
116 }
117
118 fn discount_rate(&self, _state: &[f64; 2]) -> f64 {
119 0.0
120 }
121
122 fn constant_discount_rate(&self) -> Option<f64> {
123 Some(0.0)
124 }
125
126 fn next_step(&self, _t: f64, state: &[f64; 2], dt: f64, noise: &[f64; 2]) -> [f64; 2] {
127 let mut next = *state;
128 let u = normal_cdf(noise[0]);
129 let base = self.base_spread();
130 let lambda_bid = self.a * (-self.kappa * base).exp();
131 let lambda_ask = self.a * (-self.kappa * base).exp();
132 let p_bid = (lambda_bid * dt).clamp(0.0, 1.0);
133 let p_ask = (lambda_ask * dt).clamp(0.0, 1.0);
134 if u < p_bid {
135 next[0] += self.lot_size as f64;
136 } else if u > 1.0 - p_ask {
137 next[0] -= self.lot_size as f64;
138 }
139 next[1] += self.sigma * dt.sqrt() * noise[1];
140 next
141 }
142
143 fn next_step_controlled(
144 &self,
145 _t: f64,
146 state: &[f64; 2],
147 control: &Self::Control,
148 dt: f64,
149 noise: &[f64; 2],
150 ) -> [f64; 2] {
151 let mut next = *state;
152 let u = normal_cdf(noise[0]);
153 let p_bid = (control.bid_intensity * dt).clamp(0.0, 1.0);
154 let p_ask = (control.ask_intensity * dt).clamp(0.0, 1.0);
155 if u < p_bid {
156 next[0] += self.lot_size as f64;
157 } else if u > 1.0 - p_ask {
158 next[0] -= self.lot_size as f64;
159 }
160 next[1] += self.sigma * dt.sqrt() * noise[1];
161 next
162 }
163
164 fn is_reduced_value(&self) -> bool {
165 true
166 }
167
168 fn is_diffusion_dimension(&self, dim: usize) -> bool {
169 dim == 1
170 }
171
172 fn gradient_step(&self, dim: usize) -> f64 {
173 if dim == 0 { self.lot_size as f64 } else { 1.0 }
174 }
175}
176
177impl PdeProblem<2> for AvellanedaLotSize {
178 fn dimension_kind(&self, dim: usize) -> DimensionKind {
179 match dim {
180 0 => DimensionKind::Jump,
181 _ => DimensionKind::Diffusion,
182 }
183 }
184
185 fn transport(
186 &self,
187 _t: f64,
188 state: &[f64; 2],
189 control: &Self::Control,
190 derivs: &StateDerivatives<2>,
191 ) -> Transport<2> {
192 let q = state[0];
193 let hamiltonian =
194 (control.bid_intensity + control.ask_intensity) / (self.gamma + self.kappa);
195 let jump_transport =
196 control.bid_intensity * derivs.fwd[0] - control.ask_intensity * derivs.bwd[0];
197 let local = -0.5 * self.gamma * self.sigma.powi(2) * q.powi(2);
198 Transport::new(
199 [control.bid_intensity, 0.0],
200 [control.ask_intensity, 0.0],
201 hamiltonian + local - jump_transport,
202 )
203 }
204
205 fn jump_kernel(
206 &self,
207 _t: f64,
208 _state: &[f64; 2],
209 control: &Self::Control,
210 _derivs: &StateDerivatives<2>,
211 _transport: &Transport<2>,
212 ) -> JumpKernel<2> {
213 let mut kernel = JumpKernel::empty();
214 if control.bid_intensity > 0.0 {
215 kernel.dims[0].push(JumpTransition::new(self.lot_size, control.bid_intensity));
216 }
217 if control.ask_intensity > 0.0 {
218 kernel.dims[0].push(JumpTransition::new(-self.lot_size, control.ask_intensity));
219 }
220 kernel
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227
228 #[test]
229 fn lot_size_one_reduces_to_unit_jump_kernel() {
230 let m = AvellanedaLotSize::new(0.5, 0.5, 1.5, 140.0, 1);
231 let derivs = StateDerivatives::new([0.0; 2], [0.0; 2]);
232 let state = [0.0, 0.0];
233 let control = ControlProblem::optimize(&m, 0.0, &state, &derivs);
234 let kernel =
235 PdeProblem::jump_kernel(&m, 0.0, &state, &control, &derivs, &Transport::zero());
236 assert_eq!(kernel.dims[0][0].amplitude, 1);
237 assert_eq!(kernel.dims[0][1].amplitude, -1);
238 }
239
240 #[test]
241 fn lot_size_two_jumps_by_two() {
242 let m = AvellanedaLotSize::new(0.5, 0.5, 1.5, 140.0, 2);
243 let derivs = StateDerivatives::new([0.0; 2], [0.0; 2]);
244 let state = [0.0, 0.0];
245 let control = ControlProblem::optimize(&m, 0.0, &state, &derivs);
246 let kernel =
247 PdeProblem::jump_kernel(&m, 0.0, &state, &control, &derivs, &Transport::zero());
248 assert_eq!(kernel.dims[0][0].amplitude, 2);
249 assert_eq!(kernel.dims[0][1].amplitude, -2);
250 }
251
252 #[test]
253 fn next_step_controlled_jumps_by_lot_size() {
254 let m = AvellanedaLotSize::new(0.5, 0.5, 1.5, 140.0, 3);
255 let state = [0.0, 0.0];
256 let control = MarketMakingControl::new(1000.0, 0.0);
257 let next =
258 ControlProblem::next_step_controlled(&m, 0.0, &state, &control, 0.01, &[0.0, 0.0]);
259 assert_eq!(next[0], 3.0);
260 }
261}