1use async_trait::async_trait;
4use chrono::Utc;
5use rust_decimal::Decimal;
6use std::collections::HashMap;
7use std::sync::{Arc, RwLock};
8
9use super::market::MarketManager;
10use crate::domain::{Money, Order, OrderDirection, OrderStatus, Position, Wallet};
11use crate::exchange::{
12 ExchangeError, ExchangeInfo, ExchangeProvider, ExchangeResult, Instrument, InstrumentType,
13 MarketData, MarketStatus, OrderBook, OrderBookEntry,
14};
15
16#[derive(Debug, Clone)]
18struct AccountState {
19 cash: Money,
20 positions: HashMap<String, Position>,
21 orders: HashMap<String, Order>,
22}
23
24impl AccountState {
25 fn new(currency: &str, initial_cash: Decimal) -> Self {
26 Self {
27 cash: Money::new(initial_cash, currency),
28 positions: HashMap::new(),
29 orders: HashMap::new(),
30 }
31 }
32
33 fn to_wallet(&self, currency: &str) -> Wallet {
34 let mut wallet = Wallet::with_cash(currency, self.cash.clone());
35 for pos in self.positions.values() {
36 wallet.add_position(pos.clone());
37 }
38 wallet
39 }
40}
41
42#[derive(Debug)]
44pub struct SimulatedExchange {
45 info: ExchangeInfo,
46 market: Arc<RwLock<MarketManager>>,
47 accounts: Arc<RwLock<HashMap<String, AccountState>>>,
48 base_currency: String,
49 latency_ms: u64,
51 fail_next: Arc<RwLock<Option<ExchangeError>>>,
53}
54
55impl SimulatedExchange {
56 #[must_use]
58 pub fn new(base_currency: impl Into<String>) -> Self {
59 let currency = base_currency.into();
60 Self {
61 info: ExchangeInfo {
62 id: "SIMULATOR".to_string(),
63 name: "Simulated Exchange".to_string(),
64 url: "https://localhost/".to_string(),
65 supported_types: vec![
66 InstrumentType::Stock,
67 InstrumentType::Etf,
68 InstrumentType::Crypto,
69 ],
70 },
71 market: Arc::new(RwLock::new(MarketManager::new(¤cy))),
72 accounts: Arc::new(RwLock::new(HashMap::new())),
73 base_currency: currency,
74 latency_ms: 0,
75 fail_next: Arc::new(RwLock::new(None)),
76 }
77 }
78
79 pub fn set_latency(&mut self, ms: u64) {
81 self.latency_ms = ms;
82 }
83
84 pub fn fail_next_with(&self, error: ExchangeError) {
86 *self.fail_next.write().unwrap() = Some(error);
87 }
88
89 pub fn add_instrument(
91 &self,
92 symbol: impl Into<String>,
93 name: impl Into<String>,
94 price: Decimal,
95 lot_size: u32,
96 ) {
97 self.market
98 .write()
99 .unwrap()
100 .add_instrument(symbol, name, price, lot_size);
101 }
102
103 pub fn create_account(&self, account_id: impl Into<String>, initial_cash: Decimal) {
105 let mut accounts = self.accounts.write().unwrap();
106 accounts.insert(
107 account_id.into(),
108 AccountState::new(&self.base_currency, initial_cash),
109 );
110 }
111
112 pub fn add_position(&self, account_id: &str, position: Position) {
114 let mut accounts = self.accounts.write().unwrap();
115 if let Some(account) = accounts.get_mut(account_id) {
116 account
117 .positions
118 .insert(position.symbol().to_string(), position);
119 }
120 }
121
122 pub fn set_price(&self, symbol: &str, price: Decimal) {
124 self.market.write().unwrap().set_price(symbol, price);
125 }
126
127 pub fn set_market_status(&self, status: MarketStatus) {
129 self.market.write().unwrap().set_market_status(status);
130 }
131
132 pub fn tick(&self) {
134 self.market.write().unwrap().tick();
135 }
136
137 #[must_use]
139 pub fn market(&self) -> Arc<RwLock<MarketManager>> {
140 Arc::clone(&self.market)
141 }
142
143 async fn simulate_latency(&self) {
145 if self.latency_ms > 0 {
146 tokio::time::sleep(std::time::Duration::from_millis(self.latency_ms)).await;
147 }
148 }
149
150 fn check_failure(&self) -> ExchangeResult<()> {
152 let mut fail_next = self.fail_next.write().unwrap();
153 if let Some(error) = fail_next.take() {
154 return Err(error);
155 }
156 Ok(())
157 }
158}
159
160#[async_trait]
161impl ExchangeProvider for SimulatedExchange {
162 fn info(&self) -> &ExchangeInfo {
163 &self.info
164 }
165
166 async fn ping(&self) -> ExchangeResult<()> {
167 self.simulate_latency().await;
168 self.check_failure()?;
169 Ok(())
170 }
171
172 async fn get_wallet(&self, account_id: &str) -> ExchangeResult<Wallet> {
173 self.simulate_latency().await;
174 self.check_failure()?;
175
176 let accounts = self.accounts.read().unwrap();
177 let account = accounts.get(account_id).ok_or_else(|| {
178 ExchangeError::AuthenticationFailed(format!("Account {account_id} not found"))
179 })?;
180
181 let market = self.market.read().unwrap();
183 let mut wallet = account.to_wallet(&self.base_currency);
184
185 for pos in wallet.positions_mut() {
186 if let Some(data) = market.get_market_data(pos.symbol()) {
187 pos.update_price(data.last_price);
188 }
189 }
190
191 Ok(wallet)
192 }
193
194 async fn get_instrument(&self, symbol: &str) -> ExchangeResult<Instrument> {
195 self.simulate_latency().await;
196 self.check_failure()?;
197
198 let market = self.market.read().unwrap();
199 market
200 .get_instrument(symbol)
201 .cloned()
202 .ok_or_else(|| ExchangeError::InstrumentNotFound(symbol.to_string()))
203 }
204
205 async fn list_instruments(&self) -> ExchangeResult<Vec<Instrument>> {
206 self.simulate_latency().await;
207 self.check_failure()?;
208
209 let market = self.market.read().unwrap();
210 Ok(market.list_instruments())
211 }
212
213 async fn get_market_data(&self, symbol: &str) -> ExchangeResult<MarketData> {
214 self.simulate_latency().await;
215 self.check_failure()?;
216
217 let market = self.market.read().unwrap();
218 market
219 .get_market_data(symbol)
220 .ok_or_else(|| ExchangeError::InstrumentNotFound(symbol.to_string()))
221 }
222
223 async fn get_order_book(&self, symbol: &str, depth: u32) -> ExchangeResult<OrderBook> {
224 self.simulate_latency().await;
225 self.check_failure()?;
226
227 let market = self.market.read().unwrap();
228 let state = market
229 .get_state(symbol)
230 .ok_or_else(|| ExchangeError::InstrumentNotFound(symbol.to_string()))?;
231
232 let mut bids = Vec::new();
234 let mut asks = Vec::new();
235 let base_price = state.price;
236
237 for i in 0..depth {
238 let offset = Decimal::from(i + 1) * Decimal::new(1, 2); bids.push(OrderBookEntry {
241 price: Money::new(base_price - offset, &self.base_currency),
242 quantity: Decimal::from(100 * (depth - i)),
243 });
244
245 asks.push(OrderBookEntry {
246 price: Money::new(base_price + offset, &self.base_currency),
247 quantity: Decimal::from(100 * (depth - i)),
248 });
249 }
250
251 Ok(OrderBook {
252 symbol: symbol.to_string(),
253 bids,
254 asks,
255 timestamp: Utc::now(),
256 })
257 }
258
259 async fn place_order(&self, account_id: &str, mut order: Order) -> ExchangeResult<Order> {
260 self.simulate_latency().await;
261 self.check_failure()?;
262
263 let mut accounts = self.accounts.write().unwrap();
264 let account = accounts.get_mut(account_id).ok_or_else(|| {
265 ExchangeError::AuthenticationFailed(format!("Account {account_id} not found"))
266 })?;
267
268 let market = self.market.read().unwrap();
269 let instrument = market
270 .get_instrument(order.symbol())
271 .ok_or_else(|| ExchangeError::InstrumentNotFound(order.symbol().to_string()))?;
272 let market_data = market
273 .get_market_data(order.symbol())
274 .ok_or_else(|| ExchangeError::InstrumentNotFound(order.symbol().to_string()))?;
275
276 if !market_data.market_status.is_tradeable() {
278 return Err(ExchangeError::MarketClosed {
279 instrument: order.symbol().to_string(),
280 });
281 }
282
283 let price = market_data.last_price.amount();
285 let lot_value = price * Decimal::from(instrument.lot_size);
286 let total_value = lot_value * Decimal::from(order.quantity_lots());
287
288 match order.direction() {
289 OrderDirection::Buy => {
290 if account.cash.amount() < total_value {
292 return Err(ExchangeError::InsufficientFunds {
293 required: total_value.to_string(),
294 available: account.cash.amount().to_string(),
295 });
296 }
297
298 let new_cash = account.cash.amount() - total_value;
300 account.cash = Money::new(new_cash, &self.base_currency);
301
302 let quantity = Decimal::from(order.quantity_lots() * instrument.lot_size);
303 if let Some(pos) = account.positions.get_mut(order.symbol()) {
304 pos.update_quantity(pos.quantity() + quantity);
305 pos.update_price(market_data.last_price.clone());
306 } else {
307 let new_pos = Position::builder(order.symbol(), &instrument.exchange)
308 .quantity(quantity)
309 .lot_size(instrument.lot_size)
310 .current_price(market_data.last_price.clone())
311 .build();
312 account
313 .positions
314 .insert(order.symbol().to_string(), new_pos);
315 }
316 }
317 OrderDirection::Sell => {
318 let pos = account.positions.get_mut(order.symbol()).ok_or_else(|| {
320 ExchangeError::InsufficientFunds {
321 required: order.quantity_lots().to_string(),
322 available: "0".to_string(),
323 }
324 })?;
325
326 let sell_quantity = Decimal::from(order.quantity_lots() * instrument.lot_size);
327 if pos.quantity() < sell_quantity {
328 return Err(ExchangeError::InsufficientFunds {
329 required: sell_quantity.to_string(),
330 available: pos.quantity().to_string(),
331 });
332 }
333
334 let new_quantity = pos.quantity() - sell_quantity;
336 if new_quantity.is_zero() {
337 account.positions.remove(order.symbol());
338 } else {
339 pos.update_quantity(new_quantity);
340 }
341
342 let new_cash = account.cash.amount() + total_value;
343 account.cash = Money::new(new_cash, &self.base_currency);
344 }
345 }
346
347 order.set_exchange_order_id(format!(
349 "SIM-{}",
350 Utc::now().timestamp_nanos_opt().unwrap_or(0)
351 ));
352 order.record_fill(order.quantity_lots(), market_data.last_price);
353 order.set_status(OrderStatus::Filled);
354
355 account.orders.insert(order.id().to_string(), order.clone());
357
358 Ok(order)
359 }
360
361 async fn cancel_order(&self, account_id: &str, order_id: &str) -> ExchangeResult<Order> {
362 self.simulate_latency().await;
363 self.check_failure()?;
364
365 let mut accounts = self.accounts.write().unwrap();
366 let account = accounts.get_mut(account_id).ok_or_else(|| {
367 ExchangeError::AuthenticationFailed(format!("Account {account_id} not found"))
368 })?;
369
370 let mut order = account
371 .orders
372 .get(order_id)
373 .cloned()
374 .ok_or_else(|| ExchangeError::OrderNotFound(order_id.to_string()))?;
375
376 if order.status().is_terminal() {
377 return Err(ExchangeError::InvalidOrder(format!(
378 "Order {order_id} is already in terminal state"
379 )));
380 }
381
382 order.set_status(OrderStatus::Cancelled);
383 account.orders.insert(order_id.to_string(), order.clone());
384
385 Ok(order)
386 }
387
388 async fn get_order(&self, account_id: &str, order_id: &str) -> ExchangeResult<Order> {
389 self.simulate_latency().await;
390 self.check_failure()?;
391
392 let accounts = self.accounts.read().unwrap();
393 let account = accounts.get(account_id).ok_or_else(|| {
394 ExchangeError::AuthenticationFailed(format!("Account {account_id} not found"))
395 })?;
396
397 account
398 .orders
399 .get(order_id)
400 .cloned()
401 .ok_or_else(|| ExchangeError::OrderNotFound(order_id.to_string()))
402 }
403
404 async fn get_active_orders(&self, account_id: &str) -> ExchangeResult<Vec<Order>> {
405 self.simulate_latency().await;
406 self.check_failure()?;
407
408 let accounts = self.accounts.read().unwrap();
409 let account = accounts.get(account_id).ok_or_else(|| {
410 ExchangeError::AuthenticationFailed(format!("Account {account_id} not found"))
411 })?;
412
413 Ok(account
414 .orders
415 .values()
416 .filter(|o| o.status().is_active())
417 .cloned()
418 .collect())
419 }
420}
421
422#[cfg(test)]
423mod tests {
424 use super::*;
425 use rust_decimal_macros::dec;
426
427 fn create_test_exchange() -> SimulatedExchange {
428 let exchange = SimulatedExchange::new("USD");
429 exchange.add_instrument("AAPL", "Apple Inc.", dec!(150), 1);
430 exchange.add_instrument("GOOGL", "Alphabet Inc.", dec!(2800), 1);
431 exchange.create_account("test_account", dec!(100000));
432 exchange
433 }
434
435 #[tokio::test]
436 async fn test_get_wallet() {
437 let exchange = create_test_exchange();
438
439 let wallet = exchange.get_wallet("test_account").await.unwrap();
440 assert_eq!(wallet.cash().amount(), dec!(100000));
441 assert_eq!(wallet.position_count(), 0);
442 }
443
444 #[tokio::test]
445 async fn test_place_buy_order() {
446 let exchange = create_test_exchange();
447
448 let order = Order::market("ORD1", "AAPL", "SIMULATOR", OrderDirection::Buy, 10);
449 let result = exchange.place_order("test_account", order).await.unwrap();
450
451 assert_eq!(result.status(), OrderStatus::Filled);
452 assert_eq!(result.filled_lots(), 10);
453
454 let wallet = exchange.get_wallet("test_account").await.unwrap();
456 assert_eq!(wallet.cash().amount(), dec!(100000) - dec!(1500)); assert_eq!(wallet.get_position("AAPL").unwrap().quantity(), dec!(10));
458 }
459
460 #[tokio::test]
461 async fn test_place_sell_order() {
462 let exchange = create_test_exchange();
463
464 let buy_order = Order::market("ORD1", "AAPL", "SIMULATOR", OrderDirection::Buy, 10);
466 exchange
467 .place_order("test_account", buy_order)
468 .await
469 .unwrap();
470
471 let sell_order = Order::market("ORD2", "AAPL", "SIMULATOR", OrderDirection::Sell, 5);
473 let result = exchange
474 .place_order("test_account", sell_order)
475 .await
476 .unwrap();
477
478 assert_eq!(result.status(), OrderStatus::Filled);
479
480 let wallet = exchange.get_wallet("test_account").await.unwrap();
481 assert_eq!(wallet.get_position("AAPL").unwrap().quantity(), dec!(5));
482 }
483
484 #[tokio::test]
485 async fn test_insufficient_funds() {
486 let exchange = SimulatedExchange::new("USD");
487 exchange.add_instrument("EXPENSIVE", "Expensive Stock", dec!(1000000), 1);
488 exchange.create_account("poor_account", dec!(100));
489
490 let order = Order::market("ORD1", "EXPENSIVE", "SIMULATOR", OrderDirection::Buy, 1);
491 let result = exchange.place_order("poor_account", order).await;
492
493 assert!(matches!(
494 result,
495 Err(ExchangeError::InsufficientFunds { .. })
496 ));
497 }
498
499 #[tokio::test]
500 async fn test_market_closed() {
501 let exchange = create_test_exchange();
502 exchange.set_market_status(MarketStatus::Closed);
503
504 let order = Order::market("ORD1", "AAPL", "SIMULATOR", OrderDirection::Buy, 10);
505 let result = exchange.place_order("test_account", order).await;
506
507 assert!(matches!(result, Err(ExchangeError::MarketClosed { .. })));
508 }
509
510 #[tokio::test]
511 async fn test_fail_next() {
512 let exchange = create_test_exchange();
513 exchange.fail_next_with(ExchangeError::NetworkError("Simulated failure".into()));
514
515 let result = exchange.ping().await;
516 assert!(matches!(result, Err(ExchangeError::NetworkError(_))));
517
518 let result = exchange.ping().await;
520 assert!(result.is_ok());
521 }
522}