Skip to main content

maxt_bindings_common/
foreign.rs

1use std::collections::HashSet;
2use std::sync::Arc;
3
4use maxt::{
5    AccountStream, Adapter, Balance, BoxFuture, Candle, CandleRequest, Exchange, Feature,
6    FundingPayment, FundingRate, HistoryRequest, MarginRequest, MarginSummary, Market, MarketInfo,
7    MarketKind, MarketStream, Order, OrderBook, OrderRequest, Page, Position, Result, StreamConfig,
8    Subscription, Ticker, Trade,
9};
10
11use crate::{AdapterCall, AdapterReply, ForeignDispatcher};
12
13/// A Rust [`Adapter`] backed by a [`ForeignDispatcher`].
14pub struct ForeignAdapter {
15    exchange: Exchange,
16    features: HashSet<Feature>,
17    dispatcher: Arc<dyn ForeignDispatcher>,
18}
19
20impl ForeignAdapter {
21    /// Creates an adapter with binding-owned exchange and feature metadata.
22    pub fn new(
23        exchange: Exchange,
24        features: impl IntoIterator<Item = Feature>,
25        dispatcher: Arc<dyn ForeignDispatcher>,
26    ) -> Self {
27        Self {
28            exchange,
29            features: features.into_iter().collect(),
30            dispatcher,
31        }
32    }
33
34    /// The configured features without duplicates.
35    pub fn features(&self) -> &HashSet<Feature> {
36        &self.features
37    }
38
39    /// The binding-specific dispatcher.
40    pub fn dispatcher(&self) -> &dyn ForeignDispatcher {
41        self.dispatcher.as_ref()
42    }
43}
44
45macro_rules! dispatch {
46    ($self:expr, $call:expr, $variant:path, $expected:literal) => {{
47        let future = $self.dispatcher.dispatch($call);
48        Box::pin(async move {
49            match future.await? {
50                $variant(value) => Ok(value),
51                reply => Err(unexpected_reply($expected, &reply)),
52            }
53        })
54    }};
55}
56
57impl Adapter for ForeignAdapter {
58    fn exchange(&self) -> Exchange {
59        self.exchange
60    }
61
62    fn supports(&self, feature: Feature) -> bool {
63        self.features.contains(&feature)
64    }
65
66    fn markets(&self, kind: MarketKind) -> BoxFuture<'_, Result<Vec<MarketInfo>>> {
67        dispatch!(
68            self,
69            AdapterCall::Markets { kind },
70            AdapterReply::Markets,
71            "Markets"
72        )
73    }
74
75    fn trades(&self, market: &Market, limit: Option<u32>) -> BoxFuture<'_, Result<Vec<Trade>>> {
76        dispatch!(
77            self,
78            AdapterCall::Trades {
79                market: market.clone(),
80                limit,
81            },
82            AdapterReply::Trades,
83            "Trades"
84        )
85    }
86
87    fn order_book(&self, market: &Market, depth: Option<u32>) -> BoxFuture<'_, Result<OrderBook>> {
88        dispatch!(
89            self,
90            AdapterCall::OrderBook {
91                market: market.clone(),
92                depth,
93            },
94            AdapterReply::OrderBook,
95            "OrderBook"
96        )
97    }
98
99    fn ticker(&self, market: &Market) -> BoxFuture<'_, Result<Ticker>> {
100        dispatch!(
101            self,
102            AdapterCall::Ticker {
103                market: market.clone(),
104            },
105            AdapterReply::Ticker,
106            "Ticker"
107        )
108    }
109
110    fn candles(&self, request: &CandleRequest) -> BoxFuture<'_, Result<Vec<Candle>>> {
111        dispatch!(
112            self,
113            AdapterCall::Candles {
114                request: request.clone(),
115            },
116            AdapterReply::Candles,
117            "Candles"
118        )
119    }
120
121    fn subscribe(
122        &self,
123        subscription: &Subscription,
124        config: &StreamConfig,
125    ) -> BoxFuture<'_, Result<MarketStream>> {
126        dispatch!(
127            self,
128            AdapterCall::Subscribe {
129                subscription: subscription.clone(),
130                config: config.clone(),
131            },
132            AdapterReply::MarketStream,
133            "MarketStream"
134        )
135    }
136
137    fn balances(&self) -> BoxFuture<'_, Result<Vec<Balance>>> {
138        dispatch!(
139            self,
140            AdapterCall::Balances,
141            AdapterReply::Balances,
142            "Balances"
143        )
144    }
145
146    fn open_orders(&self, market: Option<&Market>) -> BoxFuture<'_, Result<Vec<Order>>> {
147        dispatch!(
148            self,
149            AdapterCall::OpenOrders {
150                market: market.cloned(),
151            },
152            AdapterReply::OpenOrders,
153            "OpenOrders"
154        )
155    }
156
157    fn subscribe_account(&self, config: &StreamConfig) -> BoxFuture<'_, Result<AccountStream>> {
158        dispatch!(
159            self,
160            AdapterCall::SubscribeAccount {
161                config: config.clone(),
162            },
163            AdapterReply::AccountStream,
164            "AccountStream"
165        )
166    }
167
168    fn place_order(&self, request: &OrderRequest) -> BoxFuture<'_, Result<Order>> {
169        dispatch!(
170            self,
171            AdapterCall::PlaceOrder {
172                request: request.clone(),
173            },
174            AdapterReply::PlaceOrder,
175            "PlaceOrder"
176        )
177    }
178
179    fn cancel_order(&self, market: &Market, order_id: &str) -> BoxFuture<'_, Result<Order>> {
180        dispatch!(
181            self,
182            AdapterCall::CancelOrder {
183                market: market.clone(),
184                order_id: order_id.to_owned(),
185            },
186            AdapterReply::CancelOrder,
187            "CancelOrder"
188        )
189    }
190
191    fn positions(&self, market: Option<&Market>) -> BoxFuture<'_, Result<Vec<Position>>> {
192        dispatch!(
193            self,
194            AdapterCall::Positions {
195                market: market.cloned(),
196            },
197            AdapterReply::Positions,
198            "Positions"
199        )
200    }
201
202    fn margin_summary(&self) -> BoxFuture<'_, Result<MarginSummary>> {
203        dispatch!(
204            self,
205            AdapterCall::MarginSummary,
206            AdapterReply::MarginSummary,
207            "MarginSummary"
208        )
209    }
210
211    fn funding_rates(&self, request: &HistoryRequest) -> BoxFuture<'_, Result<Page<FundingRate>>> {
212        dispatch!(
213            self,
214            AdapterCall::FundingRates {
215                request: request.clone(),
216            },
217            AdapterReply::FundingRates,
218            "FundingRates"
219        )
220    }
221
222    fn funding_payments(
223        &self,
224        request: &HistoryRequest,
225    ) -> BoxFuture<'_, Result<Page<FundingPayment>>> {
226        dispatch!(
227            self,
228            AdapterCall::FundingPayments {
229                request: request.clone(),
230            },
231            AdapterReply::FundingPayments,
232            "FundingPayments"
233        )
234    }
235
236    fn set_margin(&self, request: &MarginRequest) -> BoxFuture<'_, Result<()>> {
237        let future = self.dispatcher.dispatch(AdapterCall::SetMargin {
238            request: request.clone(),
239        });
240        Box::pin(async move {
241            match future.await? {
242                AdapterReply::Unit => Ok(()),
243                reply => Err(unexpected_reply("Unit", &reply)),
244            }
245        })
246    }
247}
248
249fn unexpected_reply(expected: &str, reply: &AdapterReply) -> maxt::Error {
250    maxt::Error::adapter(format!(
251        "foreign dispatcher returned {} where {expected} was required",
252        reply.kind()
253    ))
254}