rustrade_data/exchange/binance/
mod.rs1use self::{
2 book::l1::BinanceOrderBookL1, channel::BinanceChannel, futures::BinanceFuturesUsd,
3 kline::BinanceKline, market::BinanceMarket, spot::BinanceSpot,
4 subscription::BinanceSubResponse, trade::BinanceTrade,
5};
6use crate::{
7 ExchangeWsStream, NoInitialSnapshots,
8 exchange::{Connector, ExchangeServer, ExchangeSub, StreamSelector},
9 instrument::InstrumentData,
10 subscriber::{WebSocketSubscriber, validator::WebSocketSubValidator},
11 subscription::{
12 Map,
13 book::OrderBooksL1,
14 candle::{CandleInterval, Candles},
15 trade::PublicTrades,
16 },
17 transformer::stateless::StatelessTransformer,
18};
19use rustrade_instrument::exchange::ExchangeId;
20use rustrade_integration::protocol::websocket::{WebSocketSerdeParser, WsMessage};
21use std::{fmt::Debug, marker::PhantomData};
22use url::Url;
23
24pub mod book;
27
28pub mod channel;
31
32mod error;
34pub use error::BinanceDataError;
35
36pub mod historical;
37pub use historical::BinanceHistoricalClient;
38
39pub mod futures;
42
43pub mod kline;
44
45pub mod market;
48
49pub mod spot;
52
53pub mod subscription;
57
58pub mod trade;
61
62pub type BinanceWsStream<Transformer> = ExchangeWsStream<WebSocketSerdeParser, Transformer>;
64
65#[must_use]
93pub fn supports_candle_interval(interval: CandleInterval) -> bool {
94 match interval {
95 CandleInterval::Sec5 | CandleInterval::Sec15 | CandleInterval::Sec30 => false,
96 CandleInterval::Sec1
97 | CandleInterval::Min1
98 | CandleInterval::Min3
99 | CandleInterval::Min5
100 | CandleInterval::Min15
101 | CandleInterval::Min30
102 | CandleInterval::Hour1
103 | CandleInterval::Hour2
104 | CandleInterval::Hour4
105 | CandleInterval::Hour6
106 | CandleInterval::Hour8
107 | CandleInterval::Hour12
108 | CandleInterval::Day1
109 | CandleInterval::Day3
110 | CandleInterval::Week1
111 | CandleInterval::Month1 => true,
112 }
113}
114
115#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
121pub struct Binance<Server> {
122 server: PhantomData<Server>,
123}
124
125impl<Server> Connector for Binance<Server>
126where
127 Server: ExchangeServer,
128{
129 const ID: ExchangeId = Server::ID;
130 type Channel = BinanceChannel;
131 type Market = BinanceMarket;
132 type Subscriber = WebSocketSubscriber;
133 type SubValidator = WebSocketSubValidator;
134 type SubResponse = BinanceSubResponse;
135
136 fn url() -> Result<Url, url::ParseError> {
137 Url::parse(Server::websocket_url())
138 }
139
140 fn requests(exchange_subs: Vec<ExchangeSub<Self::Channel, Self::Market>>) -> Vec<WsMessage> {
141 let stream_names = exchange_subs
142 .into_iter()
143 .map(|sub| {
144 format!(
148 "{}{}",
149 sub.market.as_ref().to_lowercase(),
150 sub.channel.as_ref()
151 )
152 })
153 .collect::<Vec<String>>();
154
155 vec![WsMessage::text(
156 serde_json::json!({
157 "method": "SUBSCRIBE",
158 "params": stream_names,
159 "id": 1
160 })
161 .to_string(),
162 )]
163 }
164
165 fn expected_responses<InstrumentKey>(_: &Map<InstrumentKey>) -> usize {
166 1
167 }
168}
169
170impl<Instrument> StreamSelector<Instrument, PublicTrades> for BinanceSpot
177where
178 Instrument: InstrumentData,
179{
180 type SnapFetcher = NoInitialSnapshots;
181 type Stream =
182 BinanceWsStream<StatelessTransformer<Self, Instrument::Key, PublicTrades, BinanceTrade>>;
183}
184
185impl<Instrument> StreamSelector<Instrument, PublicTrades> for BinanceFuturesUsd
186where
187 Instrument: InstrumentData,
188{
189 type SnapFetcher = NoInitialSnapshots;
190 type Stream =
191 BinanceWsStream<StatelessTransformer<Self, Instrument::Key, PublicTrades, BinanceTrade>>;
192}
193
194impl<Instrument> StreamSelector<Instrument, OrderBooksL1> for BinanceSpot
195where
196 Instrument: InstrumentData,
197{
198 type SnapFetcher = NoInitialSnapshots;
199 type Stream = BinanceWsStream<
200 StatelessTransformer<Self, Instrument::Key, OrderBooksL1, BinanceOrderBookL1>,
201 >;
202}
203
204impl<Instrument> StreamSelector<Instrument, OrderBooksL1> for BinanceFuturesUsd
205where
206 Instrument: InstrumentData,
207{
208 type SnapFetcher = NoInitialSnapshots;
209 type Stream = BinanceWsStream<
210 StatelessTransformer<Self, Instrument::Key, OrderBooksL1, BinanceOrderBookL1>,
211 >;
212}
213
214impl<Instrument> StreamSelector<Instrument, Candles> for BinanceSpot
216where
217 Instrument: InstrumentData,
218{
219 type SnapFetcher = NoInitialSnapshots;
220 type Stream =
221 BinanceWsStream<StatelessTransformer<Self, Instrument::Key, Candles, BinanceKline>>;
222}
223
224impl<'de, Server> serde::Deserialize<'de> for Binance<Server>
225where
226 Server: ExchangeServer,
227{
228 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
229 where
230 D: serde::de::Deserializer<'de>,
231 {
232 let input = <String as serde::Deserialize>::deserialize(deserializer)?;
233
234 if input.as_str() == Self::ID.as_str() {
235 Ok(Self::default())
236 } else {
237 Err(serde::de::Error::invalid_value(
238 serde::de::Unexpected::Str(input.as_str()),
239 &Self::ID.as_str(),
240 ))
241 }
242 }
243}
244
245impl<Server> serde::Serialize for Binance<Server>
246where
247 Server: ExchangeServer,
248{
249 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
250 where
251 S: serde::ser::Serializer,
252 {
253 serializer.serialize_str(Self::ID.as_str())
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
265 fn supports_candle_interval_rejects_only_the_sub_minute_intervals_binance_lacks() {
266 for interval in CandleInterval::ALL {
267 let expected = !matches!(
268 interval,
269 CandleInterval::Sec5 | CandleInterval::Sec15 | CandleInterval::Sec30
270 );
271 assert_eq!(
272 supports_candle_interval(interval),
273 expected,
274 "interval {interval}"
275 );
276 }
277 }
278}