use serde::{Deserialize, Serialize};
use crate::adapters::common::encode_path_segment;
use crate::error::Result;
use super::build_client;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct InsiderTrade {
pub symbol: Option<String>,
#[serde(rename = "filingDate")]
pub filing_date: Option<String>,
#[serde(rename = "transactionDate")]
pub transaction_date: Option<String>,
#[serde(rename = "reportingCik")]
pub reporting_cik: Option<String>,
#[serde(rename = "reportingName")]
pub reporting_name: Option<String>,
#[serde(rename = "transactionType")]
pub transaction_type: Option<String>,
#[serde(rename = "securitiesTransacted")]
pub securities_transacted: Option<f64>,
pub price: Option<f64>,
#[serde(rename = "securitiesOwned")]
pub securities_owned: Option<f64>,
#[serde(rename = "typeOfOwner")]
pub type_of_owner: Option<String>,
pub link: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct CikMapping {
#[serde(rename = "reportingCik")]
pub reporting_cik: Option<String>,
#[serde(rename = "reportingName")]
pub reporting_name: Option<String>,
#[serde(rename = "companyCik")]
pub company_cik: Option<String>,
#[serde(rename = "companyName")]
pub company_name: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct FailToDeliver {
pub symbol: Option<String>,
pub date: Option<String>,
pub quantity: Option<f64>,
pub price: Option<f64>,
pub name: Option<String>,
pub description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct CongressionalTrade {
pub symbol: Option<String>,
#[serde(rename = "transactionDate")]
pub transaction_date: Option<String>,
#[serde(rename = "disclosureDate")]
pub disclosure_date: Option<String>,
#[serde(rename = "firstName")]
pub first_name: Option<String>,
#[serde(rename = "lastName")]
pub last_name: Option<String>,
pub office: Option<String>,
pub district: Option<String>,
#[serde(rename = "type")]
pub trade_type: Option<String>,
pub amount: Option<String>,
#[serde(rename = "assetDescription")]
pub asset_description: Option<String>,
pub link: Option<String>,
}
pub async fn insider_trading(symbol: &str, limit: u32) -> Result<Vec<InsiderTrade>> {
let client = build_client()?;
let limit_str = limit.to_string();
client
.get(
"/api/v4/insider-trading",
&[("symbol", symbol), ("limit", &limit_str)],
)
.await
}
pub async fn insider_trading_rss(limit: u32) -> Result<Vec<InsiderTrade>> {
let client = build_client()?;
let limit_str = limit.to_string();
client
.get("/api/v4/insider-trading-rss-feed", &[("limit", &limit_str)])
.await
}
pub async fn cik_mapper(name: &str) -> Result<Vec<CikMapping>> {
let client = build_client()?;
client
.get("/api/v4/mapper-cik-name", &[("name", name)])
.await
}
pub async fn cik_mapper_by_company(name: &str) -> Result<Vec<CikMapping>> {
let client = build_client()?;
let path = format!("/api/v4/mapper-cik-company/{}", encode_path_segment(name));
client.get(&path, &[]).await
}
pub async fn fail_to_deliver(symbol: &str) -> Result<Vec<FailToDeliver>> {
let client = build_client()?;
client
.get("/api/v4/fail_to_deliver", &[("symbol", symbol)])
.await
}
pub async fn congressional_trading(symbol: &str) -> Result<Vec<CongressionalTrade>> {
let client = build_client()?;
client
.get("/api/v4/senate-trading", &[("symbol", symbol)])
.await
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_insider_trading_mock() {
let mut server = mockito::Server::new_async().await;
let _mock = server
.mock("GET", "/api/v4/insider-trading")
.match_query(mockito::Matcher::AllOf(vec![
mockito::Matcher::UrlEncoded("apikey".into(), "test-key".into()),
mockito::Matcher::UrlEncoded("symbol".into(), "AAPL".into()),
mockito::Matcher::UrlEncoded("limit".into(), "10".into()),
]))
.with_status(200)
.with_body(
serde_json::json!([
{
"symbol": "AAPL",
"filingDate": "2024-01-15",
"transactionDate": "2024-01-12",
"reportingCik": "0001234567",
"reportingName": "Cook Timothy D",
"transactionType": "S-Sale",
"securitiesTransacted": 50000.0,
"price": 185.50,
"securitiesOwned": 3200000.0,
"typeOfOwner": "officer"
}
])
.to_string(),
)
.create_async()
.await;
let client = super::super::build_test_client(&server.url()).unwrap();
let resp: Vec<InsiderTrade> = client
.get(
"/api/v4/insider-trading",
&[("symbol", "AAPL"), ("limit", "10")],
)
.await
.unwrap();
assert_eq!(resp.len(), 1);
assert_eq!(resp[0].reporting_name.as_deref(), Some("Cook Timothy D"));
assert!((resp[0].price.unwrap() - 185.50).abs() < 0.01);
}
#[tokio::test]
async fn test_congressional_trading_mock() {
let mut server = mockito::Server::new_async().await;
let _mock = server
.mock("GET", "/api/v4/senate-trading")
.match_query(mockito::Matcher::AllOf(vec![
mockito::Matcher::UrlEncoded("apikey".into(), "test-key".into()),
mockito::Matcher::UrlEncoded("symbol".into(), "AAPL".into()),
]))
.with_status(200)
.with_body(
serde_json::json!([
{
"symbol": "AAPL",
"transactionDate": "2024-01-10",
"disclosureDate": "2024-01-20",
"firstName": "John",
"lastName": "Doe",
"office": "Senate",
"type": "Purchase",
"amount": "$1,001 - $15,000"
}
])
.to_string(),
)
.create_async()
.await;
let client = super::super::build_test_client(&server.url()).unwrap();
let resp: Vec<CongressionalTrade> = client
.get("/api/v4/senate-trading", &[("symbol", "AAPL")])
.await
.unwrap();
assert_eq!(resp.len(), 1);
assert_eq!(resp[0].last_name.as_deref(), Some("Doe"));
}
}