Skip to main content

barter_data_sniper/streams/builder/
dynamic.rs

1use crate::{
2    error::DataError,
3    exchange::{
4        binance::{futures::BinanceFuturesUsd, market::BinanceMarket, spot::BinanceSpot},
5        bitfinex::{market::BitfinexMarket, Bitfinex},
6        bitmex::{market::BitmexMarket, Bitmex},
7        bybit::{futures::BybitPerpetualsUsd, market::BybitMarket, spot::BybitSpot},
8        coinbase::{market::CoinbaseMarket, Coinbase},
9        gateio::{
10            future::{GateioFuturesBtc, GateioFuturesUsd},
11            market::GateioMarket,
12            option::GateioOptions,
13            perpetual::{GateioPerpetualsBtc, GateioPerpetualsUsd},
14            spot::GateioSpot,
15        },
16        kraken::{market::KrakenMarket, Kraken},
17        okx::{market::OkxMarket, Okx},
18    },
19    instrument::InstrumentData,
20    streams::{
21        consumer::{init_market_stream, MarketStreamResult, STREAM_RECONNECTION_POLICY},
22        reconnect::stream::ReconnectingStream,
23    },
24    subscription::{
25        book::{OrderBookEvent, OrderBookL1, OrderBooksL1},
26        liquidation::{Liquidation, Liquidations},
27        trade::{PublicTrade, PublicTrades},
28        SubKind, Subscription,
29    },
30    Identifier,
31};
32use barter_instrument_copy::exchange::ExchangeId;
33use barter_integration_copy::{
34    channel::{mpsc_unbounded, UnboundedRx, UnboundedTx},
35    error::SocketError,
36    Validator,
37};
38use fnv::FnvHashMap;
39use futures::{
40    stream::{select_all, SelectAll},
41    Stream,
42};
43use futures_util::{future::try_join_all, StreamExt};
44use itertools::Itertools;
45use std::sync::Arc;
46use tokio_stream::wrappers::UnboundedReceiverStream;
47use vecmap::VecMap;
48
49#[derive(Debug)]
50pub struct DynamicStreams<InstrumentKey> {
51    pub trades:
52        VecMap<ExchangeId, UnboundedReceiverStream<MarketStreamResult<InstrumentKey, PublicTrade>>>,
53    pub l1s:
54        VecMap<ExchangeId, UnboundedReceiverStream<MarketStreamResult<InstrumentKey, OrderBookL1>>>,
55    pub l2s: VecMap<
56        ExchangeId,
57        UnboundedReceiverStream<MarketStreamResult<InstrumentKey, OrderBookEvent>>,
58    >,
59    pub liquidations:
60        VecMap<ExchangeId, UnboundedReceiverStream<MarketStreamResult<InstrumentKey, Liquidation>>>,
61}
62
63impl<InstrumentKey> DynamicStreams<InstrumentKey> {
64    /// Initialise a set of `Streams` by providing one or more [`Subscription`] batches.
65    ///
66    /// Each batch (ie/ `impl Iterator<Item = Subscription>`) will initialise at-least-one
67    /// WebSocket `Stream` under the hood. If the batch contains more-than-one [`ExchangeId`] and/or
68    /// [`SubKind`], it will be further split under the hood for compile-time reasons.
69    ///
70    /// ## Examples
71    /// Please see barter-data-rs/examples/dynamic_multi_stream_multi_exchange.rs for a
72    /// comprehensive example of how to use this market data stream initialiser.
73    pub async fn init<SubBatchIter, SubIter, Sub, Instrument>(
74        subscription_batches: SubBatchIter,
75    ) -> Result<Self, DataError>
76    where
77        SubBatchIter: IntoIterator<Item = SubIter>,
78        SubIter: IntoIterator<Item = Sub>,
79        Sub: Into<Subscription<ExchangeId, Instrument, SubKind>>,
80        Instrument: InstrumentData<Key = InstrumentKey> + Ord + 'static,
81        InstrumentKey: Clone + Send + 'static,
82        Subscription<BinanceSpot, Instrument, PublicTrades>: Identifier<BinanceMarket>,
83        Subscription<BinanceSpot, Instrument, PublicTrades>: Identifier<BinanceMarket>,
84        Subscription<BinanceSpot, Instrument, OrderBooksL1>: Identifier<BinanceMarket>,
85        Subscription<BinanceFuturesUsd, Instrument, PublicTrades>: Identifier<BinanceMarket>,
86        Subscription<BinanceFuturesUsd, Instrument, OrderBooksL1>: Identifier<BinanceMarket>,
87        Subscription<BinanceFuturesUsd, Instrument, Liquidations>: Identifier<BinanceMarket>,
88        Subscription<Bitfinex, Instrument, PublicTrades>: Identifier<BitfinexMarket>,
89        Subscription<Bitmex, Instrument, PublicTrades>: Identifier<BitmexMarket>,
90        Subscription<BybitSpot, Instrument, PublicTrades>: Identifier<BybitMarket>,
91        Subscription<BybitPerpetualsUsd, Instrument, PublicTrades>: Identifier<BybitMarket>,
92        Subscription<Coinbase, Instrument, PublicTrades>: Identifier<CoinbaseMarket>,
93        Subscription<GateioSpot, Instrument, PublicTrades>: Identifier<GateioMarket>,
94        Subscription<GateioFuturesUsd, Instrument, PublicTrades>: Identifier<GateioMarket>,
95        Subscription<GateioFuturesBtc, Instrument, PublicTrades>: Identifier<GateioMarket>,
96        Subscription<GateioPerpetualsUsd, Instrument, PublicTrades>: Identifier<GateioMarket>,
97        Subscription<GateioPerpetualsBtc, Instrument, PublicTrades>: Identifier<GateioMarket>,
98        Subscription<GateioOptions, Instrument, PublicTrades>: Identifier<GateioMarket>,
99        Subscription<Kraken, Instrument, PublicTrades>: Identifier<KrakenMarket>,
100        Subscription<Kraken, Instrument, OrderBooksL1>: Identifier<KrakenMarket>,
101        Subscription<Okx, Instrument, PublicTrades>: Identifier<OkxMarket>,
102    {
103        // Validate & dedup Subscription batches
104        let batches = validate_batches(subscription_batches)?;
105
106        // Generate required Channels from Subscription batches
107        let channels = Channels::try_from(&batches)?;
108
109        let futures =
110            batches.into_iter().map(|mut batch| {
111                batch.sort_unstable_by_key(|sub| (sub.exchange, sub.kind));
112                let by_exchange_by_sub_kind =
113                    batch.into_iter().chunk_by(|sub| (sub.exchange, sub.kind));
114
115                let batch_futures =
116                    by_exchange_by_sub_kind
117                        .into_iter()
118                        .map(|((exchange, sub_kind), subs)| {
119                            let subs = subs.into_iter().collect::<Vec<_>>();
120                            let txs = Arc::clone(&channels.txs);
121                            async move {
122                                match (exchange, sub_kind) {
123                                    (ExchangeId::BinanceSpot, SubKind::PublicTrades) => {
124                                        init_market_stream(
125                                            STREAM_RECONNECTION_POLICY,
126                                            subs.into_iter()
127                                                .map(|sub| {
128                                                    Subscription::new(
129                                                        BinanceSpot::default(),
130                                                        sub.instrument,
131                                                        PublicTrades,
132                                                    )
133                                                })
134                                                .collect(),
135                                        )
136                                        .await
137                                        .map(|stream| {
138                                            tokio::spawn(stream.boxed().forward_to(
139                                                txs.trades.get(&exchange).unwrap().clone(),
140                                            ))
141                                        })
142                                    }
143                                    (ExchangeId::BinanceSpot, SubKind::OrderBooksL1) => {
144                                        init_market_stream(
145                                            STREAM_RECONNECTION_POLICY,
146                                            subs.into_iter()
147                                                .map(|sub| {
148                                                    Subscription::new(
149                                                        BinanceSpot::default(),
150                                                        sub.instrument,
151                                                        OrderBooksL1,
152                                                    )
153                                                })
154                                                .collect(),
155                                        )
156                                        .await
157                                        .map(|stream| {
158                                            tokio::spawn(stream.boxed().forward_to(
159                                                txs.l1s.get(&exchange).unwrap().clone(),
160                                            ))
161                                        })
162                                    }
163                                    (ExchangeId::BinanceFuturesUsd, SubKind::PublicTrades) => {
164                                        init_market_stream(
165                                            STREAM_RECONNECTION_POLICY,
166                                            subs.into_iter()
167                                                .map(|sub| {
168                                                    Subscription::new(
169                                                        BinanceFuturesUsd::default(),
170                                                        sub.instrument,
171                                                        PublicTrades,
172                                                    )
173                                                })
174                                                .collect(),
175                                        )
176                                        .await
177                                        .map(|stream| {
178                                            tokio::spawn(stream.boxed().forward_to(
179                                                txs.trades.get(&exchange).unwrap().clone(),
180                                            ))
181                                        })
182                                    }
183                                    (ExchangeId::BinanceFuturesUsd, SubKind::OrderBooksL1) => {
184                                        init_market_stream(
185                                            STREAM_RECONNECTION_POLICY,
186                                            subs.into_iter()
187                                                .map(|sub| {
188                                                    Subscription::<_, Instrument, _>::new(
189                                                        BinanceFuturesUsd::default(),
190                                                        sub.instrument,
191                                                        OrderBooksL1,
192                                                    )
193                                                })
194                                                .collect(),
195                                        )
196                                        .await
197                                        .map(|stream| {
198                                            tokio::spawn(stream.boxed().forward_to(
199                                                txs.l1s.get(&exchange).unwrap().clone(),
200                                            ))
201                                        })
202                                    }
203                                    (ExchangeId::BinanceFuturesUsd, SubKind::Liquidations) => {
204                                        init_market_stream(
205                                            STREAM_RECONNECTION_POLICY,
206                                            subs.into_iter()
207                                                .map(|sub| {
208                                                    Subscription::<_, Instrument, _>::new(
209                                                        BinanceFuturesUsd::default(),
210                                                        sub.instrument,
211                                                        Liquidations,
212                                                    )
213                                                })
214                                                .collect(),
215                                        )
216                                        .await
217                                        .map(|stream| {
218                                            tokio::spawn(stream.boxed().forward_to(
219                                                txs.liquidations.get(&exchange).unwrap().clone(),
220                                            ))
221                                        })
222                                    }
223                                    (ExchangeId::Bitfinex, SubKind::PublicTrades) => {
224                                        init_market_stream(
225                                            STREAM_RECONNECTION_POLICY,
226                                            subs.into_iter()
227                                                .map(|sub| {
228                                                    Subscription::new(
229                                                        Bitfinex,
230                                                        sub.instrument,
231                                                        PublicTrades,
232                                                    )
233                                                })
234                                                .collect(),
235                                        )
236                                        .await
237                                        .map(|stream| {
238                                            tokio::spawn(stream.boxed().forward_to(
239                                                txs.trades.get(&exchange).unwrap().clone(),
240                                            ))
241                                        })
242                                    }
243                                    (ExchangeId::Bitmex, SubKind::PublicTrades) => {
244                                        init_market_stream(
245                                            STREAM_RECONNECTION_POLICY,
246                                            subs.into_iter()
247                                                .map(|sub| {
248                                                    Subscription::new(
249                                                        Bitmex,
250                                                        sub.instrument,
251                                                        PublicTrades,
252                                                    )
253                                                })
254                                                .collect(),
255                                        )
256                                        .await
257                                        .map(|stream| {
258                                            tokio::spawn(stream.boxed().forward_to(
259                                                txs.trades.get(&exchange).unwrap().clone(),
260                                            ))
261                                        })
262                                    }
263                                    (ExchangeId::BybitSpot, SubKind::PublicTrades) => {
264                                        init_market_stream(
265                                            STREAM_RECONNECTION_POLICY,
266                                            subs.into_iter()
267                                                .map(|sub| {
268                                                    Subscription::new(
269                                                        BybitSpot::default(),
270                                                        sub.instrument,
271                                                        PublicTrades,
272                                                    )
273                                                })
274                                                .collect(),
275                                        )
276                                        .await
277                                        .map(|stream| {
278                                            tokio::spawn(stream.boxed().forward_to(
279                                                txs.trades.get(&exchange).unwrap().clone(),
280                                            ))
281                                        })
282                                    }
283                                    (ExchangeId::BybitPerpetualsUsd, SubKind::PublicTrades) => {
284                                        init_market_stream(
285                                            STREAM_RECONNECTION_POLICY,
286                                            subs.into_iter()
287                                                .map(|sub| {
288                                                    Subscription::new(
289                                                        BybitPerpetualsUsd::default(),
290                                                        sub.instrument,
291                                                        PublicTrades,
292                                                    )
293                                                })
294                                                .collect(),
295                                        )
296                                        .await
297                                        .map(|stream| {
298                                            tokio::spawn(stream.boxed().forward_to(
299                                                txs.trades.get(&exchange).unwrap().clone(),
300                                            ))
301                                        })
302                                    }
303                                    (ExchangeId::Coinbase, SubKind::PublicTrades) => {
304                                        init_market_stream(
305                                            STREAM_RECONNECTION_POLICY,
306                                            subs.into_iter()
307                                                .map(|sub| {
308                                                    Subscription::new(
309                                                        Coinbase,
310                                                        sub.instrument,
311                                                        PublicTrades,
312                                                    )
313                                                })
314                                                .collect(),
315                                        )
316                                        .await
317                                        .map(|stream| {
318                                            tokio::spawn(stream.boxed().forward_to(
319                                                txs.trades.get(&exchange).unwrap().clone(),
320                                            ))
321                                        })
322                                    }
323                                    (ExchangeId::GateioSpot, SubKind::PublicTrades) => {
324                                        init_market_stream(
325                                            STREAM_RECONNECTION_POLICY,
326                                            subs.into_iter()
327                                                .map(|sub| {
328                                                    Subscription::new(
329                                                        GateioSpot::default(),
330                                                        sub.instrument,
331                                                        PublicTrades,
332                                                    )
333                                                })
334                                                .collect(),
335                                        )
336                                        .await
337                                        .map(|stream| {
338                                            tokio::spawn(stream.boxed().forward_to(
339                                                txs.trades.get(&exchange).unwrap().clone(),
340                                            ))
341                                        })
342                                    }
343                                    (ExchangeId::GateioFuturesUsd, SubKind::PublicTrades) => {
344                                        init_market_stream(
345                                            STREAM_RECONNECTION_POLICY,
346                                            subs.into_iter()
347                                                .map(|sub| {
348                                                    Subscription::new(
349                                                        GateioFuturesUsd::default(),
350                                                        sub.instrument,
351                                                        PublicTrades,
352                                                    )
353                                                })
354                                                .collect(),
355                                        )
356                                        .await
357                                        .map(|stream| {
358                                            tokio::spawn(stream.boxed().forward_to(
359                                                txs.trades.get(&exchange).unwrap().clone(),
360                                            ))
361                                        })
362                                    }
363                                    (ExchangeId::GateioFuturesBtc, SubKind::PublicTrades) => {
364                                        init_market_stream(
365                                            STREAM_RECONNECTION_POLICY,
366                                            subs.into_iter()
367                                                .map(|sub| {
368                                                    Subscription::new(
369                                                        GateioFuturesBtc::default(),
370                                                        sub.instrument,
371                                                        PublicTrades,
372                                                    )
373                                                })
374                                                .collect(),
375                                        )
376                                        .await
377                                        .map(|stream| {
378                                            tokio::spawn(stream.boxed().forward_to(
379                                                txs.trades.get(&exchange).unwrap().clone(),
380                                            ))
381                                        })
382                                    }
383                                    (ExchangeId::GateioPerpetualsUsd, SubKind::PublicTrades) => {
384                                        init_market_stream(
385                                            STREAM_RECONNECTION_POLICY,
386                                            subs.into_iter()
387                                                .map(|sub| {
388                                                    Subscription::new(
389                                                        GateioPerpetualsUsd::default(),
390                                                        sub.instrument,
391                                                        PublicTrades,
392                                                    )
393                                                })
394                                                .collect(),
395                                        )
396                                        .await
397                                        .map(|stream| {
398                                            tokio::spawn(stream.boxed().forward_to(
399                                                txs.trades.get(&exchange).unwrap().clone(),
400                                            ))
401                                        })
402                                    }
403                                    (ExchangeId::GateioPerpetualsBtc, SubKind::PublicTrades) => {
404                                        init_market_stream(
405                                            STREAM_RECONNECTION_POLICY,
406                                            subs.into_iter()
407                                                .map(|sub| {
408                                                    Subscription::new(
409                                                        GateioPerpetualsBtc::default(),
410                                                        sub.instrument,
411                                                        PublicTrades,
412                                                    )
413                                                })
414                                                .collect(),
415                                        )
416                                        .await
417                                        .map(|stream| {
418                                            tokio::spawn(stream.boxed().forward_to(
419                                                txs.trades.get(&exchange).unwrap().clone(),
420                                            ))
421                                        })
422                                    }
423                                    (ExchangeId::GateioOptions, SubKind::PublicTrades) => {
424                                        init_market_stream(
425                                            STREAM_RECONNECTION_POLICY,
426                                            subs.into_iter()
427                                                .map(|sub| {
428                                                    Subscription::new(
429                                                        GateioOptions::default(),
430                                                        sub.instrument,
431                                                        PublicTrades,
432                                                    )
433                                                })
434                                                .collect(),
435                                        )
436                                        .await
437                                        .map(|stream| {
438                                            tokio::spawn(stream.boxed().forward_to(
439                                                txs.trades.get(&exchange).unwrap().clone(),
440                                            ))
441                                        })
442                                    }
443                                    (ExchangeId::Kraken, SubKind::PublicTrades) => {
444                                        init_market_stream(
445                                            STREAM_RECONNECTION_POLICY,
446                                            subs.into_iter()
447                                                .map(|sub| {
448                                                    Subscription::new(
449                                                        Kraken,
450                                                        sub.instrument,
451                                                        PublicTrades,
452                                                    )
453                                                })
454                                                .collect(),
455                                        )
456                                        .await
457                                        .map(|stream| {
458                                            tokio::spawn(stream.boxed().forward_to(
459                                                txs.trades.get(&exchange).unwrap().clone(),
460                                            ))
461                                        })
462                                    }
463                                    (ExchangeId::Kraken, SubKind::OrderBooksL1) => {
464                                        init_market_stream(
465                                            STREAM_RECONNECTION_POLICY,
466                                            subs.into_iter()
467                                                .map(|sub| {
468                                                    Subscription::new(
469                                                        Kraken,
470                                                        sub.instrument,
471                                                        OrderBooksL1,
472                                                    )
473                                                })
474                                                .collect(),
475                                        )
476                                        .await
477                                        .map(|stream| {
478                                            tokio::spawn(stream.boxed().forward_to(
479                                                txs.l1s.get(&exchange).unwrap().clone(),
480                                            ))
481                                        })
482                                    }
483                                    (ExchangeId::Okx, SubKind::PublicTrades) => init_market_stream(
484                                        STREAM_RECONNECTION_POLICY,
485                                        subs.into_iter()
486                                            .map(|sub| {
487                                                Subscription::new(Okx, sub.instrument, PublicTrades)
488                                            })
489                                            .collect(),
490                                    )
491                                    .await
492                                    .map(|stream| {
493                                        tokio::spawn(
494                                            stream.boxed().forward_to(
495                                                txs.trades.get(&exchange).unwrap().clone(),
496                                            ),
497                                        )
498                                    }),
499                                    (exchange, sub_kind) => {
500                                        Err(DataError::Unsupported { exchange, sub_kind })
501                                    }
502                                }
503                            }
504                        });
505
506                try_join_all(batch_futures)
507            });
508
509        try_join_all(futures).await?;
510
511        Ok(Self {
512            trades: channels
513                .rxs
514                .trades
515                .into_iter()
516                .map(|(exchange, rx)| (exchange, rx.into_stream()))
517                .collect(),
518            l1s: channels
519                .rxs
520                .l1s
521                .into_iter()
522                .map(|(exchange, rx)| (exchange, rx.into_stream()))
523                .collect(),
524            l2s: channels
525                .rxs
526                .l2s
527                .into_iter()
528                .map(|(exchange, rx)| (exchange, rx.into_stream()))
529                .collect(),
530            liquidations: channels
531                .rxs
532                .liquidations
533                .into_iter()
534                .map(|(exchange, rx)| (exchange, rx.into_stream()))
535                .collect(),
536        })
537    }
538
539    /// Remove an exchange [`PublicTrade`] `Stream` from the [`DynamicStreams`] collection.
540    ///
541    /// Note that calling this method will permanently remove this `Stream` from [`Self`].
542    pub fn select_trades(
543        &mut self,
544        exchange: ExchangeId,
545    ) -> Option<UnboundedReceiverStream<MarketStreamResult<InstrumentKey, PublicTrade>>> {
546        self.trades.remove(&exchange)
547    }
548
549    /// Select and merge every exchange [`PublicTrade`] `Stream` using
550    /// [`SelectAll`](futures_util::stream::select_all).
551    pub fn select_all_trades(
552        &mut self,
553    ) -> SelectAll<UnboundedReceiverStream<MarketStreamResult<InstrumentKey, PublicTrade>>> {
554        select_all(std::mem::take(&mut self.trades).into_values())
555    }
556
557    /// Remove an exchange [`OrderBookL1`] `Stream` from the [`DynamicStreams`] collection.
558    ///
559    /// Note that calling this method will permanently remove this `Stream` from [`Self`].
560    pub fn select_l1s(
561        &mut self,
562        exchange: ExchangeId,
563    ) -> Option<UnboundedReceiverStream<MarketStreamResult<InstrumentKey, OrderBookL1>>> {
564        self.l1s.remove(&exchange)
565    }
566
567    /// Select and merge every exchange [`OrderBookL1`] `Stream` using
568    /// [`SelectAll`](futures_util::stream::select_all).
569    pub fn select_all_l1s(
570        &mut self,
571    ) -> SelectAll<UnboundedReceiverStream<MarketStreamResult<InstrumentKey, OrderBookL1>>> {
572        select_all(std::mem::take(&mut self.l1s).into_values())
573    }
574
575    /// Remove an exchange [`OrderBook`] `Stream` from the [`DynamicStreams`] collection.
576    ///
577    /// Note that calling this method will permanently remove this `Stream` from [`Self`].
578    pub fn select_l2s(
579        &mut self,
580        exchange: ExchangeId,
581    ) -> Option<UnboundedReceiverStream<MarketStreamResult<InstrumentKey, OrderBookEvent>>> {
582        self.l2s.remove(&exchange)
583    }
584
585    /// Select and merge every exchange [`OrderBook`] `Stream` using
586    /// [`SelectAll`](futures_util::stream::select_all).
587    pub fn select_all_l2s(
588        &mut self,
589    ) -> SelectAll<UnboundedReceiverStream<MarketStreamResult<InstrumentKey, OrderBookEvent>>> {
590        select_all(std::mem::take(&mut self.l2s).into_values())
591    }
592
593    /// Remove an exchange [`Liquidation`] `Stream` from the [`DynamicStreams`] collection.
594    ///
595    /// Note that calling this method will permanently remove this `Stream` from [`Self`].
596    pub fn select_liquidations(
597        &mut self,
598        exchange: ExchangeId,
599    ) -> Option<UnboundedReceiverStream<MarketStreamResult<InstrumentKey, Liquidation>>> {
600        self.liquidations.remove(&exchange)
601    }
602
603    /// Select and merge every exchange [`Liquidation`] `Stream` using
604    /// [`SelectAll`](futures_util::stream::select_all).
605    pub fn select_all_liquidations(
606        &mut self,
607    ) -> SelectAll<UnboundedReceiverStream<MarketStreamResult<InstrumentKey, Liquidation>>> {
608        select_all(std::mem::take(&mut self.liquidations).into_values())
609    }
610
611    /// Select and merge every exchange `Stream` for every data type using [`select_all`]
612    ///
613    /// Note that using [`MarketEvent<Instrument, DataKind>`] as the `Output` is suitable for most
614    /// use cases.
615    pub fn select_all<Output>(self) -> impl Stream<Item = Output>
616    where
617        InstrumentKey: Send + 'static,
618        Output: 'static,
619        MarketStreamResult<InstrumentKey, PublicTrade>: Into<Output>,
620        MarketStreamResult<InstrumentKey, OrderBookL1>: Into<Output>,
621        MarketStreamResult<InstrumentKey, OrderBookEvent>: Into<Output>,
622        MarketStreamResult<InstrumentKey, Liquidation>: Into<Output>,
623    {
624        let Self {
625            trades,
626            l1s,
627            l2s,
628            liquidations,
629        } = self;
630
631        let trades = trades
632            .into_values()
633            .map(|stream| stream.map(MarketStreamResult::into).boxed());
634
635        let l1s = l1s
636            .into_values()
637            .map(|stream| stream.map(MarketStreamResult::into).boxed());
638
639        let l2s = l2s
640            .into_values()
641            .map(|stream| stream.map(MarketStreamResult::into).boxed());
642
643        let liquidations = liquidations
644            .into_values()
645            .map(|stream| stream.map(MarketStreamResult::into).boxed());
646
647        let all = trades.chain(l1s).chain(l2s).chain(liquidations);
648
649        select_all(all)
650    }
651}
652
653pub fn validate_batches<SubBatchIter, SubIter, Sub, Instrument>(
654    batches: SubBatchIter,
655) -> Result<Vec<Vec<Subscription<ExchangeId, Instrument, SubKind>>>, DataError>
656where
657    SubBatchIter: IntoIterator<Item = SubIter>,
658    SubIter: IntoIterator<Item = Sub>,
659    Sub: Into<Subscription<ExchangeId, Instrument, SubKind>>,
660    Instrument: InstrumentData + Ord,
661{
662    batches
663        .into_iter()
664        .map(validate_subscriptions::<SubIter, Sub, Instrument>)
665        .collect()
666}
667
668pub fn validate_subscriptions<SubIter, Sub, Instrument>(
669    batch: SubIter,
670) -> Result<Vec<Subscription<ExchangeId, Instrument, SubKind>>, DataError>
671where
672    SubIter: IntoIterator<Item = Sub>,
673    Sub: Into<Subscription<ExchangeId, Instrument, SubKind>>,
674    Instrument: InstrumentData + Ord,
675{
676    // Validate Subscriptions
677    let mut batch = batch
678        .into_iter()
679        .map(Sub::into)
680        .map(Validator::validate)
681        .collect::<Result<Vec<_>, SocketError>>()?;
682
683    // Remove duplicate Subscriptions
684    batch.sort();
685    batch.dedup();
686
687    Ok(batch)
688}
689
690struct Channels<InstrumentKey> {
691    txs: Arc<Txs<InstrumentKey>>,
692    rxs: Rxs<InstrumentKey>,
693}
694
695impl<'a, Instrument> TryFrom<&'a Vec<Vec<Subscription<ExchangeId, Instrument, SubKind>>>>
696    for Channels<Instrument::Key>
697where
698    Instrument: InstrumentData,
699{
700    type Error = DataError;
701
702    fn try_from(
703        value: &'a Vec<Vec<Subscription<ExchangeId, Instrument, SubKind>>>,
704    ) -> Result<Self, Self::Error> {
705        let mut txs = Txs::default();
706        let mut rxs = Rxs::default();
707
708        for sub in value.iter().flatten() {
709            match sub.kind {
710                SubKind::PublicTrades => {
711                    if let (None, None) =
712                        (txs.trades.get(&sub.exchange), rxs.trades.get(&sub.exchange))
713                    {
714                        let (tx, rx) = mpsc_unbounded();
715                        txs.trades.insert(sub.exchange, tx);
716                        rxs.trades.insert(sub.exchange, rx);
717                    }
718                }
719                SubKind::OrderBooksL1 => {
720                    if let (None, None) = (txs.l1s.get(&sub.exchange), rxs.l1s.get(&sub.exchange)) {
721                        let (tx, rx) = mpsc_unbounded();
722                        txs.l1s.insert(sub.exchange, tx);
723                        rxs.l1s.insert(sub.exchange, rx);
724                    }
725                }
726                SubKind::OrderBooksL2 => {
727                    if let (None, None) =
728                        (txs.l2s.get(&sub.exchange), rxs.trades.get(&sub.exchange))
729                    {
730                        let (tx, rx) = mpsc_unbounded();
731                        txs.l2s.insert(sub.exchange, tx);
732                        rxs.l2s.insert(sub.exchange, rx);
733                    }
734                }
735                SubKind::Liquidations => {
736                    if let (None, None) = (
737                        txs.liquidations.get(&sub.exchange),
738                        rxs.liquidations.get(&sub.exchange),
739                    ) {
740                        let (tx, rx) = mpsc_unbounded();
741                        txs.liquidations.insert(sub.exchange, tx);
742                        rxs.liquidations.insert(sub.exchange, rx);
743                    }
744                }
745                unsupported => return Err(DataError::UnsupportedSubKind(unsupported)),
746            }
747        }
748
749        Ok(Channels {
750            txs: Arc::new(txs),
751            rxs,
752        })
753    }
754}
755
756struct Txs<InstrumentKey> {
757    trades: FnvHashMap<ExchangeId, UnboundedTx<MarketStreamResult<InstrumentKey, PublicTrade>>>,
758    l1s: FnvHashMap<ExchangeId, UnboundedTx<MarketStreamResult<InstrumentKey, OrderBookL1>>>,
759    l2s: FnvHashMap<ExchangeId, UnboundedTx<MarketStreamResult<InstrumentKey, OrderBookEvent>>>,
760    liquidations:
761        FnvHashMap<ExchangeId, UnboundedTx<MarketStreamResult<InstrumentKey, Liquidation>>>,
762}
763
764impl<InstrumentKey> Default for Txs<InstrumentKey> {
765    fn default() -> Self {
766        Self {
767            trades: Default::default(),
768            l1s: Default::default(),
769            l2s: Default::default(),
770            liquidations: Default::default(),
771        }
772    }
773}
774
775struct Rxs<InstrumentKey> {
776    trades: FnvHashMap<ExchangeId, UnboundedRx<MarketStreamResult<InstrumentKey, PublicTrade>>>,
777    l1s: FnvHashMap<ExchangeId, UnboundedRx<MarketStreamResult<InstrumentKey, OrderBookL1>>>,
778    l2s: FnvHashMap<ExchangeId, UnboundedRx<MarketStreamResult<InstrumentKey, OrderBookEvent>>>,
779    liquidations:
780        FnvHashMap<ExchangeId, UnboundedRx<MarketStreamResult<InstrumentKey, Liquidation>>>,
781}
782
783impl<InstrumentKey> Default for Rxs<InstrumentKey> {
784    fn default() -> Self {
785        Self {
786            trades: Default::default(),
787            l1s: Default::default(),
788            l2s: Default::default(),
789            liquidations: Default::default(),
790        }
791    }
792}