use crate::adapters::polygon::client::PathRule;
use crate::adapters::polygon::models::{AggBarDTO, AggregateResponseDTO};
use crate::adapters::polygon::{build_client, invalid_page};
use crate::{PageCursor, Provider, ProviderPage, Result, StockBar, StockBarsRequest};
fn bar_to_canonical(row: AggBarDTO) -> Result<StockBar> {
if chrono::DateTime::from_timestamp_millis(row.timestamp).is_none()
|| [row.open, row.high, row.low, row.close, row.volume]
.iter()
.any(|v| !v.is_finite())
|| row.volume < 0.0
|| row.vwap.is_some_and(|v| !v.is_finite())
{
return Err(invalid_page("bar"));
}
Ok(StockBar {
timestamp_ms: row.timestamp,
open: row.open,
high: row.high,
low: row.low,
close: row.close,
volume: row.volume,
transactions: row.transactions,
vwap: row.vwap,
})
}
pub(crate) async fn fetch_stock_bars_page(
request: &StockBarsRequest,
cursor: Option<&PageCursor>,
) -> Result<ProviderPage<StockBar>> {
let client = build_client()?;
let adjusted = request.split_adjusted();
let adjustment = adjusted.to_string();
let limit = request.limit.to_string();
let params = [
("adjusted", adjustment.as_str()),
("sort", request.sort_param()),
("limit", limit.as_str()),
];
let path = format!(
"/v2/aggs/ticker/{}/range/1/{}/{}/{}",
crate::adapters::common::encode_path_segment(&request.symbol),
request.timespan,
request.from,
request.to
);
let (body, current): (AggregateResponseDTO, _) = client
.page(&path, ¶ms, PathRule::AggregateWindow, cursor)
.await?;
if body
.ticker
.as_ref()
.is_some_and(|symbol| !symbol.eq_ignore_ascii_case(&request.symbol))
|| body.adjusted.is_some_and(|value| value != adjusted)
{
return Err(invalid_page("ticker/adjusted"));
}
let rows = match body.results {
Some(rows) => rows,
None if body.results_count == Some(0) => Vec::new(),
None => return Err(invalid_page("results")),
};
if body.results_count.is_some_and(|count| count != rows.len()) {
return Err(invalid_page("resultsCount"));
}
let items = rows
.into_iter()
.map(bar_to_canonical)
.collect::<Result<Vec<_>>>()?;
let next = client.continuation(
body.next_url,
&path,
¶ms,
PathRule::AggregateWindow,
¤t,
)?;
Ok(ProviderPage {
items,
next,
provider_id: Provider::Polygon,
request_id: body.request_id,
results_count: body.results_count,
query_count: body.query_count,
reported_symbol: body.ticker,
adjusted: body.adjusted,
})
}