use crate::{
Client, Result,
endpoints::{
EndpointSpec, QueryEncoder, QueryParameters,
metadata::{EndpointBounds, EndpointMetadata, GeographicAvailability},
},
query::{Quarter, Year},
responses::institutional_ownership::{
Form13fFilingDate, HolderIndustryBreakdown, HolderPerformanceSummary,
InstitutionalHolderAnalytics, InstitutionalHolding, InstitutionalIndustrySummary,
InstitutionalOwnershipFiling, InstitutionalPositionSummary,
},
types::{Cik, Limit, Page, Ticker},
};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct LatestInstitutionalOwnershipFilingsQuery {
page: Option<Page>,
limit: Option<Limit>,
}
impl LatestInstitutionalOwnershipFilingsQuery {
pub const fn new() -> Self {
Self {
page: None,
limit: None,
}
}
pub const fn with_page(mut self, page: Page) -> Self {
self.page = Some(page);
self
}
pub const fn with_limit(mut self, limit: Limit) -> Self {
self.limit = Some(limit);
self
}
pub const fn page(&self) -> Option<Page> {
self.page
}
pub const fn limit(&self) -> Option<Limit> {
self.limit
}
}
impl QueryParameters for LatestInstitutionalOwnershipFilingsQuery {
fn encode(&self, encoder: &mut QueryEncoder<'_>) {
encoder.optional("page", self.page);
encoder.optional("limit", self.limit);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InstitutionalOwnershipExtractQuery {
cik: Cik,
year: Year,
quarter: Quarter,
}
impl InstitutionalOwnershipExtractQuery {
pub const fn new(cik: Cik, year: Year, quarter: Quarter) -> Self {
Self { cik, year, quarter }
}
pub const fn cik(&self) -> &Cik {
&self.cik
}
pub const fn year(&self) -> Year {
self.year
}
pub const fn quarter(&self) -> Quarter {
self.quarter
}
}
impl QueryParameters for InstitutionalOwnershipExtractQuery {
fn encode(&self, encoder: &mut QueryEncoder<'_>) {
encoder.required("cik", &self.cik);
encoder.required("year", self.year);
encoder.required("quarter", self.quarter);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Form13fFilingDatesQuery {
cik: Cik,
}
impl Form13fFilingDatesQuery {
pub const fn new(cik: Cik) -> Self {
Self { cik }
}
pub const fn cik(&self) -> &Cik {
&self.cik
}
}
impl From<Cik> for Form13fFilingDatesQuery {
fn from(cik: Cik) -> Self {
Self::new(cik)
}
}
impl From<&Cik> for Form13fFilingDatesQuery {
fn from(cik: &Cik) -> Self {
Self::new(cik.clone())
}
}
impl QueryParameters for Form13fFilingDatesQuery {
fn encode(&self, encoder: &mut QueryEncoder<'_>) {
encoder.required("cik", &self.cik);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InstitutionalHolderAnalyticsQuery {
symbol: Ticker,
year: Year,
quarter: Quarter,
page: Option<Page>,
limit: Option<Limit>,
}
impl InstitutionalHolderAnalyticsQuery {
pub fn new(symbol: Ticker, year: Year, quarter: Quarter) -> Self {
Self {
symbol,
year,
quarter,
page: None,
limit: None,
}
}
pub const fn with_page(mut self, page: Page) -> Self {
self.page = Some(page);
self
}
pub const fn with_limit(mut self, limit: Limit) -> Self {
self.limit = Some(limit);
self
}
pub const fn symbol(&self) -> &Ticker {
&self.symbol
}
pub const fn year(&self) -> Year {
self.year
}
pub const fn quarter(&self) -> Quarter {
self.quarter
}
pub const fn page(&self) -> Option<Page> {
self.page
}
pub const fn limit(&self) -> Option<Limit> {
self.limit
}
}
impl QueryParameters for InstitutionalHolderAnalyticsQuery {
fn encode(&self, encoder: &mut QueryEncoder<'_>) {
encoder.required("symbol", &self.symbol);
encoder.required("year", self.year);
encoder.required("quarter", self.quarter);
encoder.optional("page", self.page);
encoder.optional("limit", self.limit);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HolderPerformanceSummaryQuery {
cik: Cik,
page: Option<Page>,
}
impl HolderPerformanceSummaryQuery {
pub const fn new(cik: Cik) -> Self {
Self { cik, page: None }
}
pub const fn with_page(mut self, page: Page) -> Self {
self.page = Some(page);
self
}
pub const fn cik(&self) -> &Cik {
&self.cik
}
pub const fn page(&self) -> Option<Page> {
self.page
}
}
impl From<Cik> for HolderPerformanceSummaryQuery {
fn from(cik: Cik) -> Self {
Self::new(cik)
}
}
impl From<&Cik> for HolderPerformanceSummaryQuery {
fn from(cik: &Cik) -> Self {
Self::new(cik.clone())
}
}
impl QueryParameters for HolderPerformanceSummaryQuery {
fn encode(&self, encoder: &mut QueryEncoder<'_>) {
encoder.required("cik", &self.cik);
encoder.optional("page", self.page);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HolderIndustryBreakdownQuery {
cik: Cik,
year: Year,
quarter: Quarter,
}
impl HolderIndustryBreakdownQuery {
pub const fn new(cik: Cik, year: Year, quarter: Quarter) -> Self {
Self { cik, year, quarter }
}
pub const fn cik(&self) -> &Cik {
&self.cik
}
pub const fn year(&self) -> Year {
self.year
}
pub const fn quarter(&self) -> Quarter {
self.quarter
}
}
impl QueryParameters for HolderIndustryBreakdownQuery {
fn encode(&self, encoder: &mut QueryEncoder<'_>) {
encoder.required("cik", &self.cik);
encoder.required("year", self.year);
encoder.required("quarter", self.quarter);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InstitutionalPositionsSummaryQuery {
symbol: Ticker,
year: Year,
quarter: Quarter,
}
impl InstitutionalPositionsSummaryQuery {
pub fn new(symbol: Ticker, year: Year, quarter: Quarter) -> Self {
Self {
symbol,
year,
quarter,
}
}
pub const fn symbol(&self) -> &Ticker {
&self.symbol
}
pub const fn year(&self) -> Year {
self.year
}
pub const fn quarter(&self) -> Quarter {
self.quarter
}
}
impl QueryParameters for InstitutionalPositionsSummaryQuery {
fn encode(&self, encoder: &mut QueryEncoder<'_>) {
encoder.required("symbol", &self.symbol);
encoder.required("year", self.year);
encoder.required("quarter", self.quarter);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InstitutionalIndustrySummaryQuery {
year: Year,
quarter: Quarter,
}
impl InstitutionalIndustrySummaryQuery {
pub const fn new(year: Year, quarter: Quarter) -> Self {
Self { year, quarter }
}
pub const fn year(&self) -> Year {
self.year
}
pub const fn quarter(&self) -> Quarter {
self.quarter
}
}
impl QueryParameters for InstitutionalIndustrySummaryQuery {
fn encode(&self, encoder: &mut QueryEncoder<'_>) {
encoder.required("year", self.year);
encoder.required("quarter", self.quarter);
}
}
const US_ONLY: EndpointMetadata =
EndpointMetadata::new().with_geography(GeographicAvailability::UsOnly);
const LATEST_FILINGS_METADATA: EndpointMetadata =
US_ONLY.with_bounds(EndpointBounds::new().with_page(100));
pub fn latest_institutional_ownership_filings(
query: LatestInstitutionalOwnershipFilingsQuery,
) -> EndpointSpec<LatestInstitutionalOwnershipFilingsQuery, Vec<InstitutionalOwnershipFiling>> {
EndpointSpec::get(
"institutional-ownership/latest",
"institutional-ownership/latest",
query,
)
.with_metadata(LATEST_FILINGS_METADATA)
}
pub fn institutional_ownership_extract(
query: InstitutionalOwnershipExtractQuery,
) -> EndpointSpec<InstitutionalOwnershipExtractQuery, Vec<InstitutionalHolding>> {
EndpointSpec::get(
"institutional-ownership/extract",
"institutional-ownership/extract",
query,
)
.with_metadata(US_ONLY)
}
pub fn form_13f_filing_dates(
query: Form13fFilingDatesQuery,
) -> EndpointSpec<Form13fFilingDatesQuery, Vec<Form13fFilingDate>> {
EndpointSpec::get(
"institutional-ownership/dates",
"institutional-ownership/dates",
query,
)
.with_metadata(US_ONLY)
}
pub fn institutional_holder_analytics(
query: InstitutionalHolderAnalyticsQuery,
) -> EndpointSpec<InstitutionalHolderAnalyticsQuery, Vec<InstitutionalHolderAnalytics>> {
EndpointSpec::get(
"institutional-ownership/extract-analytics/holder",
"institutional-ownership/extract-analytics/holder",
query,
)
.with_metadata(US_ONLY)
}
pub fn holder_performance_summary(
query: HolderPerformanceSummaryQuery,
) -> EndpointSpec<HolderPerformanceSummaryQuery, Vec<HolderPerformanceSummary>> {
EndpointSpec::get(
"institutional-ownership/holder-performance-summary",
"institutional-ownership/holder-performance-summary",
query,
)
.with_metadata(US_ONLY)
}
pub fn holder_industry_breakdown(
query: HolderIndustryBreakdownQuery,
) -> EndpointSpec<HolderIndustryBreakdownQuery, Vec<HolderIndustryBreakdown>> {
EndpointSpec::get(
"institutional-ownership/holder-industry-breakdown",
"institutional-ownership/holder-industry-breakdown",
query,
)
.with_metadata(US_ONLY)
}
pub fn institutional_positions_summary(
query: InstitutionalPositionsSummaryQuery,
) -> EndpointSpec<InstitutionalPositionsSummaryQuery, Vec<InstitutionalPositionSummary>> {
EndpointSpec::get(
"institutional-ownership/symbol-positions-summary",
"institutional-ownership/symbol-positions-summary",
query,
)
.with_metadata(US_ONLY)
}
pub fn institutional_industry_summary(
query: InstitutionalIndustrySummaryQuery,
) -> EndpointSpec<InstitutionalIndustrySummaryQuery, Vec<InstitutionalIndustrySummary>> {
EndpointSpec::get(
"institutional-ownership/industry-summary",
"institutional-ownership/industry-summary",
query,
)
.with_metadata(US_ONLY)
}
impl Client {
pub async fn latest_institutional_ownership_filings(
&self,
query: impl Into<LatestInstitutionalOwnershipFilingsQuery>,
) -> Result<Vec<InstitutionalOwnershipFiling>> {
self.execute(&latest_institutional_ownership_filings(query.into()))
.await
}
pub async fn institutional_ownership_extract(
&self,
query: impl Into<InstitutionalOwnershipExtractQuery>,
) -> Result<Vec<InstitutionalHolding>> {
self.execute(&institutional_ownership_extract(query.into()))
.await
}
pub async fn form_13f_filing_dates(
&self,
query: impl Into<Form13fFilingDatesQuery>,
) -> Result<Vec<Form13fFilingDate>> {
self.execute(&form_13f_filing_dates(query.into())).await
}
pub async fn institutional_holder_analytics(
&self,
query: impl Into<InstitutionalHolderAnalyticsQuery>,
) -> Result<Vec<InstitutionalHolderAnalytics>> {
self.execute(&institutional_holder_analytics(query.into()))
.await
}
pub async fn holder_performance_summary(
&self,
query: impl Into<HolderPerformanceSummaryQuery>,
) -> Result<Vec<HolderPerformanceSummary>> {
self.execute(&holder_performance_summary(query.into()))
.await
}
pub async fn holder_industry_breakdown(
&self,
query: impl Into<HolderIndustryBreakdownQuery>,
) -> Result<Vec<HolderIndustryBreakdown>> {
self.execute(&holder_industry_breakdown(query.into())).await
}
pub async fn institutional_positions_summary(
&self,
query: impl Into<InstitutionalPositionsSummaryQuery>,
) -> Result<Vec<InstitutionalPositionSummary>> {
self.execute(&institutional_positions_summary(query.into()))
.await
}
pub async fn institutional_industry_summary(
&self,
query: impl Into<InstitutionalIndustrySummaryQuery>,
) -> Result<Vec<InstitutionalIndustrySummary>> {
self.execute(&institutional_industry_summary(query.into()))
.await
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pairs(query: &impl QueryParameters) -> Vec<(String, String)> {
let mut pairs = Vec::new();
query.encode(&mut QueryEncoder::new(&mut |name, value| {
pairs.push((name.to_owned(), value.to_owned()));
}));
pairs
}
#[test]
fn latest_query_omits_absent_values_and_encodes_page_then_limit() {
assert!(pairs(&LatestInstitutionalOwnershipFilingsQuery::new()).is_empty());
let query = LatestInstitutionalOwnershipFilingsQuery::new()
.with_page(Page(0))
.with_limit(Limit(100));
assert_eq!(
pairs(&query),
[
("page".to_owned(), "0".to_owned()),
("limit".to_owned(), "100".to_owned()),
]
);
}
#[test]
fn extract_query_encodes_cik_year_then_quarter() {
let query = InstitutionalOwnershipExtractQuery::new(
Cik::new("0001388838").unwrap(),
Year(2023),
Quarter::Q3,
);
assert_eq!(
pairs(&query),
[
("cik".to_owned(), "0001388838".to_owned()),
("year".to_owned(), "2023".to_owned()),
("quarter".to_owned(), "3".to_owned()),
]
);
}
#[test]
fn dates_query_encodes_only_leading_zero_cik() {
let query = Form13fFilingDatesQuery::new(Cik::new("0001067983").unwrap());
assert_eq!(pairs(&query), [("cik".to_owned(), "0001067983".to_owned())]);
}
#[test]
fn holder_analytics_encodes_symbol_year_quarter_page_then_limit() {
let query = InstitutionalHolderAnalyticsQuery::new(
Ticker::new("AAPL").unwrap(),
Year(2023),
Quarter::Q3,
)
.with_page(Page(0))
.with_limit(Limit(10));
assert_eq!(
pairs(&query),
[
("symbol".to_owned(), "AAPL".to_owned()),
("year".to_owned(), "2023".to_owned()),
("quarter".to_owned(), "3".to_owned()),
("page".to_owned(), "0".to_owned()),
("limit".to_owned(), "10".to_owned()),
]
);
}
#[test]
fn holder_summary_queries_encode_required_values_in_exact_order() {
let cik = Cik::new("0001067983").unwrap();
assert_eq!(
pairs(&HolderPerformanceSummaryQuery::new(cik.clone()).with_page(Page(0))),
[
("cik".to_owned(), "0001067983".to_owned()),
("page".to_owned(), "0".to_owned()),
]
);
assert_eq!(
pairs(&HolderIndustryBreakdownQuery::new(
cik,
Year(2023),
Quarter::Q3,
)),
[
("cik".to_owned(), "0001067983".to_owned()),
("year".to_owned(), "2023".to_owned()),
("quarter".to_owned(), "3".to_owned()),
]
);
}
#[test]
fn position_and_industry_summaries_encode_required_values_in_exact_order() {
assert_eq!(
pairs(&InstitutionalPositionsSummaryQuery::new(
Ticker::new("AAPL").unwrap(),
Year(2023),
Quarter::Q3,
)),
[
("symbol".to_owned(), "AAPL".to_owned()),
("year".to_owned(), "2023".to_owned()),
("quarter".to_owned(), "3".to_owned()),
]
);
assert_eq!(
pairs(&InstitutionalIndustrySummaryQuery::new(
Year(2023),
Quarter::Q3,
)),
[
("year".to_owned(), "2023".to_owned()),
("quarter".to_owned(), "3".to_owned()),
]
);
}
}