use tracing::instrument;
use crate::client::Client;
use crate::datatables::{
DataTablesColumn, DataTablesRequest, DataTablesResponse, fetch_limit,
impl_datatables_request_methods,
};
use crate::error::Result;
use crate::models::Trade;
pub(crate) const TRADES_PATH: &str = "/Trades/GetTrades";
#[derive(Clone, Debug)]
pub struct TradesRequest(pub(crate) DataTablesRequest);
impl_datatables_request_methods!(TradesRequest);
impl TradesRequest {
#[must_use]
pub fn new() -> Self {
Self(DataTablesRequest {
columns: trades_columns(),
..DataTablesRequest::default()
})
}
#[must_use]
pub fn with_trade_filters(mut self, filters: Vec<(String, String)>) -> Self {
self.0 = self.0.with_extra_values(filters);
self
}
pub(crate) fn to_pairs(&self) -> Vec<(String, String)> {
self.0.to_pairs()
}
}
impl Default for TradesRequest {
fn default() -> Self {
Self::new()
}
}
#[must_use]
pub fn trades_columns() -> Vec<DataTablesColumn> {
vec![
DataTablesColumn::new("FullTimeString24", "", true, false),
DataTablesColumn::new("FullTimeString24", "FullTimeString24", true, true),
DataTablesColumn::new("Ticker", "Ticker", true, true),
DataTablesColumn::new("Current", "Current", true, false),
DataTablesColumn::new("Trade", "Trade", true, false),
DataTablesColumn::new("Sector", "Sector", true, true),
DataTablesColumn::new("Industry", "Industry", true, true),
DataTablesColumn::new("Volume", "Sh", true, true),
DataTablesColumn::new("Dollars", "$$", true, true),
DataTablesColumn::new("DollarsMultiplier", "RS", true, true),
DataTablesColumn::new("CumulativeDistribution", "PCT", true, true),
DataTablesColumn::new("TradeRank", "R", true, true),
DataTablesColumn::new("RelativeSize", "RelativeSize", true, true),
DataTablesColumn::new(
"LastComparibleTradeDate",
"LastComparibleTradeDate",
true,
true,
),
DataTablesColumn::new(
"LastComparibleTradeDate",
"LastComparibleTradeDate",
true,
false,
),
]
}
impl Client {
#[instrument(skip_all)]
pub async fn get_trades(&self, request: &TradesRequest) -> Result<DataTablesResponse<Trade>> {
let body = self.post_form(TRADES_PATH, request.to_pairs()).await?;
Ok(serde_json::from_str(&body)?)
}
#[instrument(skip_all)]
pub async fn get_trades_limit(
&self,
request: &TradesRequest,
limit: usize,
) -> Result<Vec<Trade>> {
fetch_limit(self, TRADES_PATH, request.0.clone(), limit).await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::client::ClientConfig;
use crate::session::{
COOKIE_DOMAIN, Cookie, FORMS_AUTH_COOKIE_NAME, SESSION_COOKIE_NAME, Session,
};
fn test_session() -> Session {
Session::new(
vec![
Cookie::new(SESSION_COOKIE_NAME, "session-123", COOKIE_DOMAIN),
Cookie::new(FORMS_AUTH_COOKIE_NAME, "auth-456", COOKIE_DOMAIN),
],
"xsrf-789",
)
}
fn test_client(server: &mockito::Server) -> Client {
Client::with_config(
test_session(),
ClientConfig {
base_url: server.url(),
..ClientConfig::default()
},
)
.unwrap()
}
#[test]
fn trades_columns_returns_15_columns() {
let columns = trades_columns();
assert_eq!(columns.len(), 15);
}
#[test]
fn trades_columns_first_and_last_match_go_source() {
let columns = trades_columns();
assert_eq!(columns[0].data, "FullTimeString24");
assert_eq!(columns[0].name, "");
assert!(columns[0].searchable);
assert!(!columns[0].orderable);
assert_eq!(columns[14].data, "LastComparibleTradeDate");
assert_eq!(columns[14].name, "LastComparibleTradeDate");
assert!(columns[14].searchable);
assert!(!columns[14].orderable);
}
#[tokio::test]
async fn get_trades_returns_fixture_response() {
let mut server = mockito::Server::new_async().await;
let fixture = crate::test_support::read_fixture("trades_get_trades_response.json");
let mock = server
.mock("POST", TRADES_PATH)
.with_status(200)
.with_header("content-type", "application/json")
.with_body(&fixture)
.create_async()
.await;
let client = test_client(&server);
let response = client.get_trades(&TradesRequest::new()).await.unwrap();
assert_eq!(response.draw, 1);
assert_eq!(response.records_total, 465);
assert_eq!(response.records_filtered, 465);
assert_eq!(response.data.len(), 2);
assert_eq!(response.data[0].ticker.as_deref(), Some("AXP"));
assert_eq!(response.data[1].ticker.as_deref(), Some("MRVL"));
mock.assert_async().await;
}
#[tokio::test]
async fn get_trades_limit_respects_limit() {
let mut server = mockito::Server::new_async().await;
let fixture = crate::test_support::read_fixture("trades_get_trades_response.json");
server
.mock("POST", TRADES_PATH)
.with_status(200)
.with_header("content-type", "application/json")
.with_body(&fixture)
.create_async()
.await;
let client = test_client(&server);
let trades = client
.get_trades_limit(&TradesRequest::new(), 1)
.await
.unwrap();
assert_eq!(trades.len(), 1);
assert_eq!(trades[0].ticker.as_deref(), Some("AXP"));
}
}