Skip to main content

engine/
exchange.rs

1use crate::matcher::Matcher;
2use crate::portfolio::Portfolio;
3use crate::types::{AgentId, FillEvent, MarketState, OrderRequest};
4use std::collections::HashMap;
5
6/// Central exchange that manages matching and per-agent portfolio tracking.
7/// Wraps a Matcher and maintains a HashMap<AgentId, Portfolio>.
8pub struct Exchange<M: Matcher> {
9    matcher: M,
10    portfolios: HashMap<AgentId, Portfolio>,
11    order_agent_map: HashMap<u64, AgentId>,
12    last_fills: Vec<(AgentId, FillEvent)>,
13    fill_history: Vec<(AgentId, FillEvent)>,
14}
15
16impl<M: Matcher> Exchange<M> {
17    pub fn new(matcher: M) -> Self {
18        Self {
19            matcher,
20            portfolios: HashMap::new(),
21            order_agent_map: HashMap::new(),
22            last_fills: Vec::new(),
23            fill_history: Vec::new(),
24        }
25    }
26
27    pub fn register_agent(&mut self, id: AgentId, initial_cash: f64, transaction_cost_rate: f64) {
28        self.portfolios
29            .insert(id, Portfolio::new(initial_cash, transaction_cost_rate));
30    }
31
32    pub fn get_portfolio(&self, id: AgentId) -> Option<&Portfolio> {
33        self.portfolios.get(&id)
34    }
35
36    /// Submit order requests tagged with an agent ID.
37    /// CancelAll only cancels the submitting agent's orders.
38    pub fn submit_orders(&mut self, agent_id: AgentId, requests: Vec<OrderRequest>) {
39        for request in requests {
40            match request {
41                OrderRequest::New(order) => {
42                    self.order_agent_map.insert(order.id, agent_id);
43                    self.matcher.add_order(order);
44                }
45                OrderRequest::Cancel(id) => {
46                    self.order_agent_map.remove(&id);
47                    self.matcher.cancel_order(id);
48                }
49                OrderRequest::CancelAll => {
50                    let agent_order_ids: Vec<u64> = self
51                        .order_agent_map
52                        .iter()
53                        .filter(|&(_, &aid)| aid == agent_id)
54                        .map(|(&oid, _)| oid)
55                        .collect();
56                    for oid in &agent_order_ids {
57                        self.matcher.cancel_order(*oid);
58                        self.order_agent_map.remove(oid);
59                    }
60                }
61            }
62        }
63    }
64
65    /// Match pending orders against the current market state.
66    /// Updates agent portfolios and records fill history.
67    pub fn resolve_matches(&mut self, state: &MarketState) -> Vec<FillEvent> {
68        let fills = self.matcher.process_quote(state);
69
70        self.last_fills.clear();
71
72        for fill in &fills {
73            if let Some(&agent_id) = self.order_agent_map.get(&fill.order_id) {
74                if let Some(portfolio) = self.portfolios.get_mut(&agent_id) {
75                    portfolio.update(fill);
76                }
77                self.last_fills.push((agent_id, fill.clone()));
78                self.fill_history.push((agent_id, fill.clone()));
79                self.order_agent_map.remove(&fill.order_id);
80            }
81        }
82
83        fills
84    }
85
86    /// Returns fills from the most recent resolve_matches call
87    pub fn last_fills(&self) -> &[(AgentId, FillEvent)] {
88        &self.last_fills
89    }
90
91    /// Returns the complete fill history for a specific agent
92    pub fn fill_history_for(&self, agent_id: AgentId) -> Vec<&FillEvent> {
93        self.fill_history
94            .iter()
95            .filter(|(aid, _)| *aid == agent_id)
96            .map(|(_, fill)| fill)
97            .collect()
98    }
99
100    /// Returns the complete fill history
101    pub fn fill_history(&self) -> &[(AgentId, FillEvent)] {
102        &self.fill_history
103    }
104
105    pub fn get_matcher(&self) -> &M {
106        &self.matcher
107    }
108
109    /// Delegates to the matcher's augment_parameters, returning any runtime
110    /// state that should be injected into the next tick's MarketState.
111    pub fn get_matcher_parameters(&self) -> HashMap<String, f64> {
112        self.matcher.augment_parameters()
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119    use crate::matcher::SimpleMatcher;
120    use crate::types::{DEFAULT_AGENT, Order, Side};
121
122    fn sample_state(bid: f64, ask: f64) -> MarketState {
123        MarketState {
124            timestamp: 0.0,
125            best_bid: bid,
126            best_ask: ask,
127            last_price: None,
128            true_volatility: None,
129            true_drift: None,
130            parameters: None,
131        }
132    }
133
134    #[test]
135    fn test_register_and_get_portfolio() {
136        let mut exchange = Exchange::new(SimpleMatcher::new());
137        exchange.register_agent(DEFAULT_AGENT, 10000.0, 0.0);
138
139        let portfolio = exchange.get_portfolio(DEFAULT_AGENT).unwrap();
140        assert_eq!(portfolio.cash, 10000.0);
141        assert_eq!(portfolio.position, 0.0);
142    }
143
144    #[test]
145    fn test_submit_and_match_orders() {
146        let mut exchange = Exchange::new(SimpleMatcher::new());
147        exchange.register_agent(DEFAULT_AGENT, 10000.0, 0.0);
148
149        let requests = vec![OrderRequest::New(Order::new(1, Side::Buy, 100.10, 1.0))];
150        exchange.submit_orders(DEFAULT_AGENT, requests);
151
152        let state = sample_state(99.95, 100.05);
153        let fills = exchange.resolve_matches(&state);
154
155        assert_eq!(fills.len(), 1);
156        assert_eq!(fills[0].price, 100.05);
157
158        let portfolio = exchange.get_portfolio(DEFAULT_AGENT).unwrap();
159        assert_eq!(portfolio.position, 1.0);
160        assert!((portfolio.cash - (10000.0 - 100.05)).abs() < 1e-10);
161    }
162
163    #[test]
164    fn test_multi_agent_isolation() {
165        let mut exchange = Exchange::new(SimpleMatcher::new());
166        exchange.register_agent(0, 10000.0, 0.0);
167        exchange.register_agent(1, 5000.0, 0.0);
168
169        exchange.submit_orders(
170            0,
171            vec![OrderRequest::New(Order::new(1, Side::Buy, 100.10, 1.0))],
172        );
173        exchange.submit_orders(
174            1,
175            vec![OrderRequest::New(Order::new(2, Side::Sell, 99.90, 2.0))],
176        );
177
178        let state = sample_state(99.95, 100.05);
179        let fills = exchange.resolve_matches(&state);
180
181        assert_eq!(fills.len(), 2);
182
183        let p0 = exchange.get_portfolio(0).unwrap();
184        assert_eq!(p0.position, 1.0);
185
186        let p1 = exchange.get_portfolio(1).unwrap();
187        assert_eq!(p1.position, -2.0);
188    }
189
190    #[test]
191    fn test_cancel_all_per_agent() {
192        let mut exchange = Exchange::new(SimpleMatcher::new());
193        exchange.register_agent(0, 10000.0, 0.0);
194        exchange.register_agent(1, 10000.0, 0.0);
195
196        exchange.submit_orders(
197            0,
198            vec![OrderRequest::New(Order::new(1, Side::Buy, 99.50, 1.0))],
199        );
200        exchange.submit_orders(
201            1,
202            vec![OrderRequest::New(Order::new(2, Side::Buy, 99.50, 1.0))],
203        );
204
205        // Agent 0 cancels all - should only cancel agent 0's orders
206        exchange.submit_orders(0, vec![OrderRequest::CancelAll]);
207
208        // Only agent 1's order should remain and fill
209        let state = sample_state(99.40, 99.50);
210        let fills = exchange.resolve_matches(&state);
211
212        assert_eq!(fills.len(), 1);
213        assert_eq!(fills[0].order_id, 2);
214    }
215
216    #[test]
217    fn test_fill_history_tracking() {
218        let mut exchange = Exchange::new(SimpleMatcher::new());
219        exchange.register_agent(0, 10000.0, 0.0);
220        exchange.register_agent(1, 10000.0, 0.0);
221
222        exchange.submit_orders(
223            0,
224            vec![OrderRequest::New(Order::new(1, Side::Buy, 100.10, 1.0))],
225        );
226        exchange.submit_orders(
227            1,
228            vec![OrderRequest::New(Order::new(2, Side::Sell, 99.80, 1.0))],
229        );
230
231        let state = sample_state(99.95, 100.05);
232        exchange.resolve_matches(&state);
233
234        assert_eq!(exchange.fill_history().len(), 2);
235        assert_eq!(exchange.fill_history_for(0).len(), 1);
236        assert_eq!(exchange.fill_history_for(1).len(), 1);
237        assert_eq!(exchange.last_fills().len(), 2);
238    }
239
240    #[test]
241    fn test_last_fills_cleared_on_new_resolve() {
242        let mut exchange = Exchange::new(SimpleMatcher::new());
243        exchange.register_agent(0, 10000.0, 0.0);
244
245        exchange.submit_orders(
246            0,
247            vec![OrderRequest::New(Order::new(1, Side::Buy, 100.10, 1.0))],
248        );
249        let state = sample_state(99.95, 100.05);
250        exchange.resolve_matches(&state);
251        assert_eq!(exchange.last_fills().len(), 1);
252
253        // Resolve again with no pending orders
254        exchange.resolve_matches(&state);
255        assert_eq!(exchange.last_fills().len(), 0);
256        // But fill history is preserved
257        assert_eq!(exchange.fill_history().len(), 1);
258    }
259
260    #[test]
261    fn test_cancelall_removes_multiple_orders_from_same_agent() {
262        // CancelAll must remove all orders previously submitted by that agent,
263        // not just the most recent one.
264        let mut exchange = Exchange::new(SimpleMatcher::new());
265        exchange.register_agent(0, 10000.0, 0.0);
266
267        exchange.submit_orders(
268            0,
269            vec![
270                OrderRequest::New(Order::new(1, Side::Buy, 101.0, 1.0)),
271                OrderRequest::New(Order::new(2, Side::Buy, 102.0, 1.0)),
272                OrderRequest::New(Order::new(3, Side::Sell, 98.0, 1.0)),
273            ],
274        );
275        exchange.submit_orders(0, vec![OrderRequest::CancelAll]);
276
277        let fills = exchange.resolve_matches(&sample_state(99.0, 100.0));
278        assert_eq!(fills.len(), 0);
279    }
280
281    #[test]
282    fn test_portfolio_position_accumulates_across_ticks() {
283        // Filling orders in separate ticks must accumulate position and
284        // deduct cash cumulatively from the same portfolio.
285        let mut exchange = Exchange::new(SimpleMatcher::new());
286        exchange.register_agent(0, 10000.0, 0.0);
287
288        exchange.submit_orders(
289            0,
290            vec![OrderRequest::New(Order::new(1, Side::Buy, 101.0, 1.0))],
291        );
292        exchange.resolve_matches(&sample_state(99.0, 100.0));
293
294        exchange.submit_orders(
295            0,
296            vec![OrderRequest::New(Order::new(2, Side::Buy, 101.0, 2.0))],
297        );
298        exchange.resolve_matches(&sample_state(99.0, 100.0));
299
300        let p = exchange.get_portfolio(0).unwrap();
301        assert!((p.position - 3.0).abs() < 1e-10);
302        assert!((p.cash - (10000.0 - 300.0)).abs() < 1e-10);
303    }
304
305    #[test]
306    fn test_transaction_cost_applied_on_sell() {
307        // A non-zero transaction cost must reduce cash received on a sell.
308        // proceeds = best_bid * qty * (1 - cost_rate)
309        let cost_rate = 0.001;
310        let mut exchange = Exchange::new(SimpleMatcher::new());
311        exchange.register_agent(0, 10000.0, cost_rate);
312
313        exchange.submit_orders(
314            0,
315            vec![OrderRequest::New(Order::new(1, Side::Sell, 98.0, 1.0))],
316        );
317        exchange.resolve_matches(&sample_state(99.0, 100.0));
318
319        let p = exchange.get_portfolio(0).unwrap();
320        let expected_proceeds = 99.0 * (1.0 - cost_rate);
321        assert!((p.cash - (10000.0 + expected_proceeds)).abs() < 1e-10);
322        assert!((p.position - (-1.0)).abs() < 1e-10);
323    }
324}