use crate::error::{Error, Result};
use crate::http::HttpClient;
use crate::orderbook_reconstructor::OrderBookReconstructor;
use crate::types::{
CursorResponse, LighterGranularity, OrderBook, OrderbookDelta, ReconstructOptions,
ReconstructedOrderBook, TickData, Timestamp,
};
#[derive(Debug, Default)]
pub struct GetOrderBookParams {
pub timestamp: Option<Timestamp>,
pub depth: Option<i32>,
}
#[derive(Debug)]
pub struct OrderBookHistoryParams {
pub start: Timestamp,
pub end: Timestamp,
pub cursor: Option<String>,
pub limit: Option<i64>,
pub depth: Option<i32>,
pub granularity: Option<LighterGranularity>,
}
#[derive(Debug, Clone)]
pub struct OrderBookResource {
http: HttpClient,
prefix: String,
}
impl OrderBookResource {
pub(crate) fn new(http: HttpClient, prefix: &str) -> Self {
Self {
http,
prefix: prefix.to_string(),
}
}
pub async fn get(&self, symbol: &str, params: Option<GetOrderBookParams>) -> Result<OrderBook> {
let p = params.unwrap_or_default();
let mut qp = vec![];
if let Some(ts) = p.timestamp {
qp.push(("timestamp", ts.to_millis().to_string()));
}
if let Some(d) = p.depth {
qp.push(("depth", d.to_string()));
}
self.http
.get(&format!("{}/orderbook/{}", self.prefix, symbol), &qp)
.await
}
pub async fn history(
&self,
symbol: &str,
params: OrderBookHistoryParams,
) -> Result<CursorResponse<Vec<OrderBook>>> {
let mut qp = vec![
("start", params.start.to_millis().to_string()),
("end", params.end.to_millis().to_string()),
];
if let Some(c) = ¶ms.cursor {
qp.push(("cursor", c.clone()));
}
if let Some(l) = params.limit {
qp.push(("limit", l.to_string()));
}
if let Some(d) = params.depth {
qp.push(("depth", d.to_string()));
}
if let Some(g) = params.granularity {
qp.push(("granularity", g.as_str().to_string()));
}
let (data, next_cursor) = self
.http
.get_with_cursor(&format!("{}/orderbook/{}/history", self.prefix, symbol), &qp)
.await?;
Ok(CursorResponse { data, next_cursor })
}
pub async fn history_tick(
&self,
symbol: &str,
start: impl Into<Timestamp>,
end: impl Into<Timestamp>,
depth: Option<i32>,
) -> Result<TickData> {
let mut qp = vec![
("start", start.into().to_millis().to_string()),
("end", end.into().to_millis().to_string()),
("granularity", "tick".to_string()),
];
if let Some(d) = depth {
qp.push(("depth", d.to_string()));
}
let value: serde_json::Value = self
.http
.get(&format!("{}/orderbook/{}/history", self.prefix, symbol), &qp)
.await?;
let obj = value.as_object().ok_or_else(|| {
Error::InvalidParam(
"Tick-level orderbook data was not returned for this request. \
Check the symbol and time range, or use a different granularity."
.into(),
)
})?;
if !obj.contains_key("checkpoint") {
return Err(Error::InvalidParam(
"Tick-level orderbook data was not returned for this request. \
Check the symbol and time range, or use a different granularity."
.into(),
));
}
let checkpoint: OrderBook = serde_json::from_value(obj["checkpoint"].clone())
.map_err(|e| Error::Deserialize(format!("Failed to parse checkpoint: {e}")))?;
let deltas: Vec<OrderbookDelta> = obj
.get("deltas")
.and_then(|d| serde_json::from_value(d.clone()).ok())
.unwrap_or_default();
Ok(TickData {
checkpoint,
deltas,
})
}
pub async fn history_reconstructed(
&self,
symbol: &str,
start: impl Into<Timestamp>,
end: impl Into<Timestamp>,
depth: Option<i32>,
emit_all: bool,
) -> Result<Vec<ReconstructedOrderBook>> {
let tick_data = self.history_tick(symbol, start, end, depth).await?;
let mut reconstructor = OrderBookReconstructor::new();
let options = ReconstructOptions {
depth: depth.map(|d| d as usize),
emit_all,
};
Ok(reconstructor.reconstruct_all(&tick_data.checkpoint, &tick_data.deltas, Some(options)))
}
pub async fn collect_tick_history(
&self,
symbol: &str,
start: impl Into<Timestamp>,
end: impl Into<Timestamp>,
depth: Option<i32>,
) -> Result<Vec<ReconstructedOrderBook>> {
let start_ts = start.into().to_millis();
let end_ts = end.into().to_millis();
let depth_usize = depth.map(|d| d as usize);
let max_deltas_per_page = 1000;
let mut cursor = start_ts;
let mut reconstructor = OrderBookReconstructor::new();
let mut all_snapshots = Vec::new();
let mut is_first_page = true;
while cursor < end_ts {
let tick_data = self.history_tick(symbol, cursor, end_ts, depth).await?;
if tick_data.deltas.is_empty() {
if is_first_page {
reconstructor.initialize(&tick_data.checkpoint);
all_snapshots.push(reconstructor.get_snapshot(depth_usize));
}
break;
}
reconstructor.initialize(&tick_data.checkpoint);
let mut sorted_deltas: Vec<&OrderbookDelta> = tick_data.deltas.iter().collect();
sorted_deltas.sort_by_key(|d| d.sequence);
if is_first_page {
all_snapshots.push(reconstructor.get_snapshot(depth_usize));
}
for delta in &sorted_deltas {
reconstructor.apply_delta(delta);
all_snapshots.push(reconstructor.get_snapshot(depth_usize));
}
is_first_page = false;
let last_delta = sorted_deltas.last().unwrap();
cursor = last_delta.timestamp + 1;
if tick_data.deltas.len() < max_deltas_per_page {
break;
}
}
Ok(all_snapshots)
}
}