-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(data_feeder): fetching binance stream
- Loading branch information
Showing
7 changed files
with
132 additions
and
128 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
use futures::{Stream, StreamExt}; | ||
use std::{ | ||
pin::Pin, | ||
task::{Context, Poll}, | ||
}; | ||
use thiserror::Error; | ||
use ticker::{BinanceResponse, Ticker}; | ||
use tokio::net::TcpStream; | ||
use tokio_tungstenite::{connect_async, MaybeTlsStream, WebSocketStream}; | ||
|
||
use super::DataFeeder; | ||
|
||
mod ticker; | ||
|
||
#[derive(Error, Debug)] | ||
pub(crate) enum BinanceDataFeederError { | ||
/// Error connecting to the WebSocket. | ||
#[error("error connecting to WebSocket")] | ||
Connection(#[from] tokio_tungstenite::tungstenite::Error), | ||
/// Error decoding the message. | ||
#[error("error decoding message")] | ||
Decode(#[from] serde_json::Error), | ||
} | ||
|
||
/// This structure controls the interaction with the Binance WebSocket API. | ||
pub(crate) struct BinanceDataFeeder { | ||
/// The URL of the Binance WebSocket API. | ||
url: String, | ||
/// The WebSocket stream. | ||
inner: WebSocketStream<MaybeTlsStream<TcpStream>>, | ||
} | ||
|
||
impl BinanceDataFeeder { | ||
/// Creates a new BinanceDataFeeder instance. | ||
pub(crate) async fn new(symbols: Vec<String>) -> Result<Self, BinanceDataFeederError> { | ||
let query = symbols | ||
.iter() | ||
.map(|symbol| format!("{}@ticker", symbol)) | ||
.collect::<Vec<String>>() | ||
.join("/"); | ||
|
||
let url = format!("wss://stream.binance.com/stream?streams={}", query); | ||
let (client, _) = connect_async(url.to_string()).await?; | ||
|
||
Ok(Self { url, inner: client }) | ||
} | ||
} | ||
|
||
/// We implement the Stream trait for the BinanceDataFeeder struct | ||
/// in order to encode the messages received from the WebSocket into our Ticker struct. | ||
impl Stream for BinanceDataFeeder { | ||
type Item = Result<Ticker, BinanceDataFeederError>; | ||
|
||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { | ||
let this = self.get_mut(); | ||
|
||
match this.inner.poll_next_unpin(cx) { | ||
Poll::Ready(Some(Ok(msg))) => { | ||
let msg = msg.into_text()?; | ||
let resp: BinanceResponse = serde_json::from_str(&msg)?; | ||
Poll::Ready(Some(Ok(resp.data))) | ||
} | ||
Poll::Ready(Some(Err(e))) => { | ||
// we should handle reconnections here | ||
Poll::Ready(Some(Err(BinanceDataFeederError::Connection(e)))) | ||
} | ||
Poll::Ready(None) => Poll::Ready(None), | ||
Poll::Pending => Poll::Pending, | ||
} | ||
} | ||
} | ||
|
||
impl DataFeeder for BinanceDataFeeder { | ||
type Item = Ticker; | ||
|
||
/// Converts the Binance feeder into a stream of `Ticker` items. | ||
fn into_stream( | ||
self, | ||
) -> Pin<Box<dyn Stream<Item = Result<Self::Item, BinanceDataFeederError>> + Send>> { | ||
Box::pin(self) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use futures::StreamExt; | ||
|
||
#[tokio::test] | ||
async fn can_connect() { | ||
let symbols = vec!["btcusdt".to_string(), "ethusdt".to_string()]; | ||
let mut feeder = BinanceDataFeeder::new(symbols).await.unwrap(); | ||
let msg = feeder.next().await.unwrap().unwrap(); | ||
assert!(msg.symbol == "BTCUSDT" || msg.symbol == "ETHUSDT"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
use std::pin::Pin; | ||
|
||
use binance::BinanceDataFeederError; | ||
use futures::stream::Stream; | ||
|
||
pub(crate) mod binance; | ||
|
||
/// This trait must be implemented by any data feeder that wants to be used by the Oracle. | ||
/// | ||
/// It takes care of feeding off-chain data to the Oracle. | ||
pub(crate) trait DataFeeder { | ||
/// The Item type that the stream will return. | ||
type Item; | ||
|
||
/// Converts the data feeder into a stream of items. | ||
fn into_stream( | ||
self, | ||
) -> Pin<Box<dyn Stream<Item = Result<Self::Item, BinanceDataFeederError>> + Send>>; | ||
} |