use std::collections::HashMap;
use dbn::{MappingInterval, SType};
use reqwest::RequestBuilder;
use serde::Deserialize;
use typed_builder::TypedBuilder;
use crate::Symbols;
use super::DateRange;
pub struct SymbologyClient<'a> {
pub(crate) inner: &'a mut super::Client,
}
impl SymbologyClient<'_> {
pub async fn resolve(&mut self, params: &ResolveParams) -> crate::Result<Resolution> {
let mut form = vec![
("dataset", params.dataset.to_string()),
("stype_in", params.stype_in.to_string()),
("stype_out", params.stype_out.to_string()),
("symbols", params.symbols.to_api_string()),
];
params.date_range.add_to_form(&mut form);
Ok(self
.post("resolve")?
.form(&form)
.send()
.await?
.error_for_status()?
.json()
.await?)
}
fn post(&mut self, slug: &str) -> crate::Result<RequestBuilder> {
self.inner.post(&format!("symbology.{slug}"))
}
}
#[derive(Debug, Clone, TypedBuilder)]
pub struct ResolveParams {
#[builder(setter(transform = |dt: impl ToString| dt.to_string()))]
pub dataset: String,
#[builder(setter(into))]
pub symbols: Symbols,
#[builder(default = SType::RawSymbol)]
pub stype_in: SType,
#[builder(default = SType::InstrumentId)]
pub stype_out: SType,
#[builder(setter(into))]
pub date_range: DateRange,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Resolution {
#[serde(rename = "result")]
pub mappings: HashMap<String, Vec<MappingInterval>>,
pub partial: Vec<String>,
pub not_found: Vec<String>,
}
#[cfg(test)]
mod tests {
use reqwest::StatusCode;
use serde_json::json;
use time::macros::date;
use wiremock::{
matchers::{basic_auth, method, path},
Mock, MockServer, ResponseTemplate,
};
use super::*;
use crate::{
body_contains,
historical::{HistoricalGateway, API_VERSION},
HistoricalClient,
};
const API_KEY: &str = "test-API";
#[tokio::test]
async fn test_resolve() {
let mock_server = MockServer::start().await;
Mock::given(method("POST"))
.and(basic_auth(API_KEY, ""))
.and(path(format!("/v{API_VERSION}/symbology.resolve")))
.and(body_contains("dataset", "GLBX.MDP3"))
.and(body_contains("symbols", "ES.c.0%2CES.d.0"))
.and(body_contains("stype_in", "continuous"))
.and(body_contains("stype_out", "instrument_id"))
.and(body_contains("start_date", "2023-06-14"))
.and(body_contains("end_date", "2023-06-17"))
.respond_with(ResponseTemplate::new(StatusCode::OK).set_body_json(json!({
"result": {
"ES.c.0": [
{
"d0": "2023-06-14",
"d1": "2023-06-15",
"s": "10245"
},
{
"d0": "2023-06-15",
"d1": "2023-06-16",
"s": "10248"
}
]
},
"partial": [],
"not_found": ["ES.d.0"]
})))
.mount(&mock_server)
.await;
let mut target = HistoricalClient::with_url(
mock_server.uri(),
API_KEY.to_owned(),
HistoricalGateway::Bo1,
)
.unwrap();
let res = target
.symbology()
.resolve(
&ResolveParams::builder()
.dataset(dbn::datasets::GLBX_MDP3)
.symbols(vec!["ES.c.0", "ES.d.0"])
.stype_in(SType::Continuous)
.date_range((date!(2023 - 06 - 14), date!(2023 - 06 - 17)))
.build(),
)
.await
.unwrap();
assert_eq!(
*res.mappings.get("ES.c.0").unwrap(),
vec![
MappingInterval {
start_date: time::macros::date!(2023 - 06 - 14),
end_date: time::macros::date!(2023 - 06 - 15),
symbol: "10245".to_owned()
},
MappingInterval {
start_date: time::macros::date!(2023 - 06 - 15),
end_date: time::macros::date!(2023 - 06 - 16),
symbol: "10248".to_owned()
},
]
);
assert!(res.partial.is_empty());
assert_eq!(res.not_found, vec!["ES.d.0"]);
}
}