1use chrono::{DateTime, Utc};
4use rust_decimal::Decimal;
5use std::collections::HashMap;
6
7use crate::domain::Money;
8use crate::exchange::{Instrument, InstrumentType, MarketData, MarketStatus};
9
10#[derive(Debug, Clone)]
12pub struct MarketState {
13 pub price: Decimal,
15 pub spread_percent: Decimal,
17 pub volume: Decimal,
19 pub status: MarketStatus,
21 pub last_update: DateTime<Utc>,
23}
24
25impl MarketState {
26 #[must_use]
28 pub fn new(price: Decimal) -> Self {
29 Self {
30 price,
31 spread_percent: Decimal::new(1, 1), volume: Decimal::new(1_000_000, 0),
33 status: MarketStatus::Open,
34 last_update: Utc::now(),
35 }
36 }
37
38 #[must_use]
40 pub const fn with_spread(mut self, spread_percent: Decimal) -> Self {
41 self.spread_percent = spread_percent;
42 self
43 }
44
45 #[must_use]
47 pub const fn with_status(mut self, status: MarketStatus) -> Self {
48 self.status = status;
49 self
50 }
51
52 #[must_use]
54 pub fn bid(&self) -> Decimal {
55 let half_spread = self.spread_percent / Decimal::from(200);
56 self.price * (Decimal::ONE - half_spread)
57 }
58
59 #[must_use]
61 pub fn ask(&self) -> Decimal {
62 let half_spread = self.spread_percent / Decimal::from(200);
63 self.price * (Decimal::ONE + half_spread)
64 }
65
66 pub fn set_price(&mut self, price: Decimal) {
68 self.price = price;
69 self.last_update = Utc::now();
70 }
71
72 #[must_use]
74 pub fn to_market_data(&self, symbol: &str, currency: &str) -> MarketData {
75 MarketData {
76 symbol: symbol.to_string(),
77 last_price: Money::new(self.price, currency),
78 bid: Some(Money::new(self.bid(), currency)),
79 ask: Some(Money::new(self.ask(), currency)),
80 volume_24h: Some(self.volume),
81 high_24h: Some(Money::new(self.price * Decimal::new(102, 2), currency)),
82 low_24h: Some(Money::new(self.price * Decimal::new(98, 2), currency)),
83 change_24h: None,
84 change_percent_24h: None,
85 market_status: self.status,
86 timestamp: self.last_update,
87 }
88 }
89}
90
91#[derive(Debug, Clone, Copy)]
93pub enum PriceModel {
94 Static,
96 RandomWalk {
98 volatility: Decimal,
100 },
101 MeanReverting {
103 mean: Decimal,
105 reversion_speed: Decimal,
107 volatility: Decimal,
109 },
110 Trending {
112 drift: Decimal,
114 volatility: Decimal,
116 },
117}
118
119#[derive(Debug)]
121pub struct PriceGenerator {
122 model: PriceModel,
123 rng_seed: u64,
124 step_count: u64,
125}
126
127impl PriceGenerator {
128 #[must_use]
130 pub const fn new(model: PriceModel) -> Self {
131 Self {
132 model,
133 rng_seed: 42,
134 step_count: 0,
135 }
136 }
137
138 #[must_use]
140 pub const fn with_seed(mut self, seed: u64) -> Self {
141 self.rng_seed = seed;
142 self
143 }
144
145 #[must_use]
147 pub fn next_price(&mut self, current: Decimal) -> Decimal {
148 self.step_count += 1;
149
150 match self.model {
151 PriceModel::Static => current,
152 PriceModel::RandomWalk { volatility } => {
153 let random = self.pseudo_random();
154 let change = volatility * random / Decimal::from(100);
155 current * (Decimal::ONE + change)
156 }
157 PriceModel::MeanReverting {
158 mean,
159 reversion_speed,
160 volatility,
161 } => {
162 let random = self.pseudo_random();
163 let noise = volatility * random / Decimal::from(100);
164 let reversion = reversion_speed * (mean - current) / Decimal::from(100);
165 current + reversion + current * noise
166 }
167 PriceModel::Trending { drift, volatility } => {
168 let random = self.pseudo_random();
169 let noise = volatility * random / Decimal::from(100);
170 let trend = drift / Decimal::from(100);
171 current * (Decimal::ONE + trend + noise)
172 }
173 }
174 }
175
176 fn pseudo_random(&self) -> Decimal {
178 let seed = self
180 .rng_seed
181 .wrapping_mul(self.step_count)
182 .wrapping_add(12345);
183 let value = (seed % 2000) as i64 - 1000;
184 Decimal::new(value, 3) }
186}
187
188#[derive(Debug, Default)]
190pub struct MarketManager {
191 states: HashMap<String, MarketState>,
192 instruments: HashMap<String, Instrument>,
193 generators: HashMap<String, PriceGenerator>,
194 base_currency: String,
195}
196
197impl MarketManager {
198 #[must_use]
200 pub fn new(base_currency: impl Into<String>) -> Self {
201 Self {
202 states: HashMap::new(),
203 instruments: HashMap::new(),
204 generators: HashMap::new(),
205 base_currency: base_currency.into(),
206 }
207 }
208
209 pub fn add_instrument(
211 &mut self,
212 symbol: impl Into<String>,
213 name: impl Into<String>,
214 price: Decimal,
215 lot_size: u32,
216 ) {
217 let symbol = symbol.into();
218 let name = name.into();
219
220 let instrument = Instrument {
221 symbol: symbol.clone(),
222 name,
223 exchange_id: format!("SIM_{symbol}"),
224 exchange: "SIMULATOR".to_string(),
225 instrument_type: InstrumentType::Stock,
226 currency: self.base_currency.clone(),
227 lot_size,
228 min_price_increment: Decimal::new(1, 2),
229 min_quantity: 1,
230 tradeable: true,
231 margin_available: false,
232 short_available: false,
233 };
234
235 let state = MarketState::new(price);
236
237 self.instruments.insert(symbol.clone(), instrument);
238 self.states.insert(symbol.clone(), state);
239 self.generators
240 .insert(symbol, PriceGenerator::new(PriceModel::Static));
241 }
242
243 pub fn set_price_model(&mut self, symbol: &str, model: PriceModel) {
245 self.generators
246 .insert(symbol.to_string(), PriceGenerator::new(model));
247 }
248
249 #[must_use]
251 pub fn get_state(&self, symbol: &str) -> Option<&MarketState> {
252 self.states.get(symbol)
253 }
254
255 #[must_use]
257 pub fn get_instrument(&self, symbol: &str) -> Option<&Instrument> {
258 self.instruments.get(symbol)
259 }
260
261 #[must_use]
263 pub fn get_market_data(&self, symbol: &str) -> Option<MarketData> {
264 let state = self.states.get(symbol)?;
265 Some(state.to_market_data(symbol, &self.base_currency))
266 }
267
268 #[must_use]
270 pub fn list_instruments(&self) -> Vec<Instrument> {
271 self.instruments.values().cloned().collect()
272 }
273
274 pub fn tick(&mut self) {
276 let symbols: Vec<String> = self.states.keys().cloned().collect();
277 for symbol in symbols {
278 if let Some(generator) = self.generators.get_mut(&symbol) {
279 if let Some(state) = self.states.get_mut(&symbol) {
280 let new_price = generator.next_price(state.price);
281 state.set_price(new_price);
282 }
283 }
284 }
285 }
286
287 pub fn set_market_status(&mut self, status: MarketStatus) {
289 for state in self.states.values_mut() {
290 state.status = status;
291 }
292 }
293
294 pub fn set_price(&mut self, symbol: &str, price: Decimal) {
296 if let Some(state) = self.states.get_mut(symbol) {
297 state.set_price(price);
298 }
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305 use rust_decimal_macros::dec;
306
307 #[test]
308 fn test_market_state_spread() {
309 let state = MarketState::new(dec!(100)).with_spread(dec!(0.2)); let bid = state.bid();
312 let ask = state.ask();
313
314 assert!(bid < dec!(100));
315 assert!(ask > dec!(100));
316 assert!((ask - bid) / dec!(100) * dec!(100) < dec!(0.21)); }
318
319 #[test]
320 fn test_price_generator_static() {
321 let mut gen = PriceGenerator::new(PriceModel::Static);
322 let price = dec!(100);
323
324 for _ in 0..10 {
325 let next = gen.next_price(price);
326 assert_eq!(next, price);
327 }
328 }
329
330 #[test]
331 fn test_price_generator_random_walk() {
332 let mut gen = PriceGenerator::new(PriceModel::RandomWalk {
333 volatility: dec!(2),
334 });
335 let mut price = dec!(100);
336
337 let mut changed = false;
339 for _ in 0..10 {
340 let next = gen.next_price(price);
341 if next != price {
342 changed = true;
343 }
344 price = next;
345 }
346 assert!(changed);
347 }
348
349 #[test]
350 fn test_market_manager() {
351 let mut manager = MarketManager::new("USD");
352 manager.add_instrument("AAPL", "Apple Inc.", dec!(150), 1);
353 manager.add_instrument("GOOGL", "Alphabet Inc.", dec!(2800), 1);
354
355 assert_eq!(manager.list_instruments().len(), 2);
356
357 let state = manager.get_state("AAPL").unwrap();
358 assert_eq!(state.price, dec!(150));
359
360 let data = manager.get_market_data("AAPL").unwrap();
361 assert_eq!(data.symbol, "AAPL");
362 assert_eq!(data.last_price.amount(), dec!(150));
363 }
364
365 #[test]
366 fn test_market_manager_tick() {
367 let mut manager = MarketManager::new("USD");
368 manager.add_instrument("TEST", "Test Stock", dec!(100), 1);
369 manager.set_price_model(
370 "TEST",
371 PriceModel::RandomWalk {
372 volatility: dec!(5),
373 },
374 );
375
376 let _initial = manager.get_state("TEST").unwrap().price;
377 manager.tick();
378 manager.tick();
379 manager.tick();
380
381 let final_price = manager.get_state("TEST").unwrap().price;
383 assert!(final_price > Decimal::ZERO);
386 }
387}