use crate::adapters::common::crypto_chart::{CryptoExchange, crypto_chart};
use crate::constants::{Interval, TimeRange};
use crate::error::Result;
use crate::models::chart::{Candle, Chart};
use crate::providers::{Operation, Provider};
use super::client::MAX_KLINES;
use super::models::Kline;
use super::symbols;
const EXCHANGE: CryptoExchange = CryptoExchange {
provider: Provider::Binance,
code: "BINANCE",
name: "Binance",
};
const MAX_PAGES: u32 = 10;
pub(super) fn interval_code(interval: Interval) -> Option<&'static str> {
Some(match interval {
Interval::OneMinute => "1m",
Interval::TwoMinutes => return None,
Interval::FiveMinutes => "5m",
Interval::FifteenMinutes => "15m",
Interval::ThirtyMinutes => "30m",
Interval::OneHour => "1h",
Interval::NinetyMinutes => return None,
Interval::OneDay => "1d",
Interval::FiveDays => return None,
Interval::OneWeek => "1w",
Interval::OneMonth => "1M",
Interval::ThreeMonths => return None,
})
}
pub(super) fn to_candles(klines: Vec<Kline>) -> Vec<Candle> {
klines
.into_iter()
.map(|k| Candle {
timestamp: k.open_time / 1_000,
open: k.open,
high: k.high,
low: k.low,
close: k.close,
volume: k.volume as i64,
adj_close: Some(k.close),
provider_id: Some(Provider::Binance),
})
.collect()
}
pub(super) fn build_chart(
symbol: &str,
market: &str,
candles: Vec<Candle>,
interval: Option<Interval>,
range: Option<TimeRange>,
) -> Chart {
let quote_currency = symbols::split_pair(market).map(|(_, quote)| quote.to_string());
crypto_chart(&EXCHANGE, symbol, quote_currency, candles, interval, range)
}
fn market_for(symbol: &str, operation: Operation) -> Result<String> {
symbols::parse_market(symbol).ok_or_else(|| operation.not_supported(Provider::Binance))
}
async fn collect_klines(
market: &str,
code: &str,
interval: Interval,
start_ms: i64,
end_ms: i64,
) -> Result<Vec<Kline>> {
let client = super::client()?;
let step_ms = interval.duration_secs() * 1_000;
let mut cursor = start_ms;
let mut out: Vec<Kline> = Vec::new();
for _ in 0..MAX_PAGES {
let page = client
.klines(market, code, cursor, end_ms, MAX_KLINES)
.await?;
let Some(last) = page.last() else { break };
cursor = last.open_time + step_ms;
let full_page = page.len() as u32 == MAX_KLINES;
out.extend(page);
if !full_page || cursor > end_ms {
break;
}
}
Ok(out)
}
pub(crate) async fn fetch_chart_response(
symbol: &str,
interval: Interval,
range: TimeRange,
) -> Result<Chart> {
let market = market_for(symbol, Operation::Chart)?;
let code =
interval_code(interval).ok_or_else(|| Operation::Chart.not_supported(Provider::Binance))?;
let end_ms = chrono::Utc::now().timestamp_millis();
let start_ms = end_ms - range.approx_duration_secs() * 1_000;
let klines = collect_klines(&market, code, interval, start_ms, end_ms).await?;
Ok(build_chart(
symbol,
&market,
to_candles(klines),
Some(interval),
Some(range),
))
}
pub(crate) async fn fetch_chart_range_response(
symbol: &str,
interval: Interval,
start: i64,
end: i64,
) -> Result<Chart> {
let market = market_for(symbol, Operation::ChartRange)?;
let code = interval_code(interval)
.ok_or_else(|| Operation::ChartRange.not_supported(Provider::Binance))?;
let klines = collect_klines(&market, code, interval, start * 1_000, end * 1_000).await?;
Ok(build_chart(
symbol,
&market,
to_candles(klines),
Some(interval),
None,
))
}