Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions crates/fmp-py/python/fmp/_native/insider_trading/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,15 @@ class BeneficialOwnershipAcquisition:
@property
def accepted_date(self) -> datetime.date: ...
@property
def cusip(self) -> builtins.str: ...
def cusip(self) -> typing.Optional[builtins.str]: ...
@property
def name_of_reporting_person(self) -> builtins.str: ...
@property
def citizenship_or_place_of_organization(self) -> builtins.str: ...
def citizenship_or_place_of_organization(self) -> typing.Optional[builtins.str]: ...
@property
def sole_voting_power(self) -> builtins.str: ...
@property
def shared_voting_power(self) -> builtins.str: ...
def shared_voting_power(self) -> typing.Optional[builtins.str]: ...
@property
def sole_dispositive_power(self) -> builtins.str: ...
@property
Expand All @@ -73,11 +73,11 @@ class BeneficialOwnershipAcquisition:
symbol: builtins.str,
filing_date: datetime.date,
accepted_date: datetime.date,
cusip: builtins.str,
cusip: typing.Optional[builtins.str],
name_of_reporting_person: builtins.str,
citizenship_or_place_of_organization: builtins.str,
citizenship_or_place_of_organization: typing.Optional[builtins.str],
sole_voting_power: builtins.str,
shared_voting_power: builtins.str,
shared_voting_power: typing.Optional[builtins.str],
sole_dispositive_power: builtins.str,
shared_dispositive_power: builtins.str,
amount_beneficially_owned: builtins.str,
Expand Down Expand Up @@ -151,17 +151,17 @@ class InsiderTrade:
@property
def company_cik(self) -> builtins.str: ...
@property
def transaction_type(self) -> builtins.str: ...
def transaction_type(self) -> typing.Optional[builtins.str]: ...
@property
def securities_owned(self) -> builtins.float: ...
def securities_owned(self) -> typing.Optional[builtins.float]: ...
@property
def reporting_name(self) -> builtins.str: ...
@property
def type_of_owner(self) -> builtins.str: ...
@property
def acquisition_or_disposition(self) -> builtins.str: ...
@property
def direct_or_indirect(self) -> builtins.str: ...
def direct_or_indirect(self) -> typing.Optional[builtins.str]: ...
@property
def form_type(self) -> builtins.str: ...
@property
Expand All @@ -181,12 +181,12 @@ class InsiderTrade:
transaction_date: datetime.date,
reporting_cik: builtins.str,
company_cik: builtins.str,
transaction_type: builtins.str,
securities_owned: builtins.float,
transaction_type: typing.Optional[builtins.str],
securities_owned: typing.Optional[builtins.float],
reporting_name: builtins.str,
type_of_owner: builtins.str,
acquisition_or_disposition: builtins.str,
direct_or_indirect: builtins.str,
direct_or_indirect: typing.Optional[builtins.str],
form_type: builtins.str,
securities_transacted: builtins.float,
price: builtins.float,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,19 @@ class HolderIndustryBreakdown:
@property
def investor_name(self) -> builtins.str: ...
@property
def industry_title(self) -> builtins.str: ...
def industry_title(self) -> typing.Optional[builtins.str]: ...
@property
def weight(self) -> builtins.float: ...
@property
def last_weight(self) -> builtins.float: ...
@property
def change_in_weight(self) -> builtins.float: ...
@property
def change_in_weight_percentage(self) -> builtins.float: ...
def change_in_weight_percentage(self) -> typing.Optional[builtins.float]: ...
@property
def performance(self) -> builtins.float: ...
@property
def performance_percentage(self) -> builtins.float: ...
def performance_percentage(self) -> typing.Optional[builtins.float]: ...
@property
def last_performance(self) -> builtins.float: ...
@property
Expand All @@ -92,13 +92,13 @@ class HolderIndustryBreakdown:
date: datetime.date,
cik: builtins.str,
investor_name: builtins.str,
industry_title: builtins.str,
industry_title: typing.Optional[builtins.str],
weight: builtins.float,
last_weight: builtins.float,
change_in_weight: builtins.float,
change_in_weight_percentage: builtins.float,
change_in_weight_percentage: typing.Optional[builtins.float],
performance: builtins.float,
performance_percentage: builtins.float,
performance_percentage: typing.Optional[builtins.float],
last_performance: builtins.float,
change_in_performance: builtins.float,
) -> HolderIndustryBreakdown: ...
Expand Down Expand Up @@ -472,7 +472,7 @@ class InstitutionalHolding:
@property
def security_cusip(self) -> builtins.str: ...
@property
def symbol(self) -> builtins.str: ...
def symbol(self) -> typing.Optional[builtins.str]: ...
@property
def name_of_issuer(self) -> builtins.str: ...
@property
Expand All @@ -498,7 +498,7 @@ class InstitutionalHolding:
accepted_date: datetime.date,
cik: builtins.str,
security_cusip: builtins.str,
symbol: builtins.str,
symbol: typing.Optional[builtins.str],
name_of_issuer: builtins.str,
shares: builtins.float,
title_of_class: builtins.str,
Expand Down
30 changes: 15 additions & 15 deletions crates/fmp-py/src/models/insider_trading.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,17 @@ pub(crate) struct InsiderTrade {
#[pyo3(get)]
pub company_cik: String,
#[pyo3(get)]
pub transaction_type: String,
pub transaction_type: Option<String>,
#[pyo3(get)]
pub securities_owned: f64,
pub securities_owned: Option<f64>,
#[pyo3(get)]
pub reporting_name: String,
#[pyo3(get)]
pub type_of_owner: String,
#[pyo3(get)]
pub acquisition_or_disposition: String,
#[pyo3(get)]
pub direct_or_indirect: String,
pub direct_or_indirect: Option<String>,
#[pyo3(get)]
pub form_type: String,
#[pyo3(get)]
Expand All @@ -56,12 +56,12 @@ impl InsiderTrade {
transaction_date: ::chrono::NaiveDate,
reporting_cik: String,
company_cik: String,
transaction_type: String,
securities_owned: f64,
transaction_type: Option<String>,
securities_owned: Option<f64>,
reporting_name: String,
type_of_owner: String,
acquisition_or_disposition: String,
direct_or_indirect: String,
direct_or_indirect: Option<String>,
form_type: String,
securities_transacted: f64,
price: f64,
Expand Down Expand Up @@ -323,7 +323,7 @@ impl From<libfmp::responses::insider_trading::InsiderTrade> for InsiderTrade {
transaction_date: value.transaction_date.into_inner(),
reporting_cik: value.reporting_cik.into_inner(),
company_cik: value.company_cik.into_inner(),
transaction_type: value.transaction_type.into_inner(),
transaction_type: value.transaction_type.map(|value| value.into_inner()),
securities_owned: value.securities_owned,
reporting_name: value.reporting_name,
type_of_owner: value.type_of_owner,
Expand Down Expand Up @@ -814,15 +814,15 @@ pub(crate) struct BeneficialOwnershipAcquisition {
#[pyo3(get)]
pub accepted_date: ::chrono::NaiveDate,
#[pyo3(get)]
pub cusip: String,
pub cusip: Option<String>,
#[pyo3(get)]
pub name_of_reporting_person: String,
#[pyo3(get)]
pub citizenship_or_place_of_organization: String,
pub citizenship_or_place_of_organization: Option<String>,
#[pyo3(get)]
pub sole_voting_power: String,
#[pyo3(get)]
pub shared_voting_power: String,
pub shared_voting_power: Option<String>,
#[pyo3(get)]
pub sole_dispositive_power: String,
#[pyo3(get)]
Expand All @@ -849,11 +849,11 @@ impl BeneficialOwnershipAcquisition {
symbol: String,
filing_date: ::chrono::NaiveDate,
accepted_date: ::chrono::NaiveDate,
cusip: String,
cusip: Option<String>,
name_of_reporting_person: String,
citizenship_or_place_of_organization: String,
citizenship_or_place_of_organization: Option<String>,
sole_voting_power: String,
shared_voting_power: String,
shared_voting_power: Option<String>,
sole_dispositive_power: String,
shared_dispositive_power: String,
amount_beneficially_owned: String,
Expand Down Expand Up @@ -1122,11 +1122,11 @@ impl From<libfmp::responses::insider_trading::BeneficialOwnershipAcquisition>
symbol: value.symbol.into_inner(),
filing_date: value.filing_date.into_inner(),
accepted_date: value.accepted_date.into_inner(),
cusip: value.cusip.into_inner(),
cusip: value.cusip.map(|value| value.into_inner()),
name_of_reporting_person: value.name_of_reporting_person,
citizenship_or_place_of_organization: value.citizenship_or_place_of_organization,
sole_voting_power: value.sole_voting_power.into_inner(),
shared_voting_power: value.shared_voting_power.into_inner(),
shared_voting_power: value.shared_voting_power.map(|value| value.into_inner()),
sole_dispositive_power: value.sole_dispositive_power.into_inner(),
shared_dispositive_power: value.shared_dispositive_power.into_inner(),
amount_beneficially_owned: value.amount_beneficially_owned.into_inner(),
Expand Down
18 changes: 9 additions & 9 deletions crates/fmp-py/src/models/institutional_ownership.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ pub(crate) struct InstitutionalHolding {
#[pyo3(get)]
pub security_cusip: String,
#[pyo3(get)]
pub symbol: String,
pub symbol: Option<String>,
#[pyo3(get)]
pub name_of_issuer: String,
#[pyo3(get)]
Expand Down Expand Up @@ -245,7 +245,7 @@ impl InstitutionalHolding {
accepted_date: ::chrono::NaiveDate,
cik: String,
security_cusip: String,
symbol: String,
symbol: Option<String>,
name_of_issuer: String,
shares: f64,
title_of_class: String,
Expand Down Expand Up @@ -462,7 +462,7 @@ impl From<libfmp::responses::institutional_ownership::InstitutionalHolding>
accepted_date: value.accepted_date.into_inner(),
cik: value.cik.into_inner(),
security_cusip: value.security_cusip.into_inner(),
symbol: value.symbol.into_inner(),
symbol: value.symbol.map(|value| value.into_inner()),
name_of_issuer: value.name_of_issuer,
shares: value.shares,
title_of_class: value.title_of_class,
Expand Down Expand Up @@ -2105,19 +2105,19 @@ pub(crate) struct HolderIndustryBreakdown {
#[pyo3(get)]
pub investor_name: String,
#[pyo3(get)]
pub industry_title: String,
pub industry_title: Option<String>,
#[pyo3(get)]
pub weight: f64,
#[pyo3(get)]
pub last_weight: f64,
#[pyo3(get)]
pub change_in_weight: f64,
#[pyo3(get)]
pub change_in_weight_percentage: f64,
pub change_in_weight_percentage: Option<f64>,
#[pyo3(get)]
pub performance: f64,
#[pyo3(get)]
pub performance_percentage: f64,
pub performance_percentage: Option<f64>,
#[pyo3(get)]
pub last_performance: f64,
#[pyo3(get)]
Expand All @@ -2135,13 +2135,13 @@ impl HolderIndustryBreakdown {
date: ::chrono::NaiveDate,
cik: String,
investor_name: String,
industry_title: String,
industry_title: Option<String>,
weight: f64,
last_weight: f64,
change_in_weight: f64,
change_in_weight_percentage: f64,
change_in_weight_percentage: Option<f64>,
performance: f64,
performance_percentage: f64,
performance_percentage: Option<f64>,
last_performance: f64,
change_in_performance: f64,
) -> Self {
Expand Down
13 changes: 13 additions & 0 deletions crates/fmp-py/tests/test_insider_trading_a.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@ def test_latest_trades_with_every_filter(client: Any, fixture_server: FixtureSer
assert_trmk_award(rows[0])


def test_latest_trades_decode_omitted_members_as_none(client: Any, fixture_server: FixtureServer) -> None:
"""Issue #368: a null or empty ``transactionType`` and null owned or direct members decode as ``None``."""
body = load_fixture("latest_insider_trades.json")
body[0].update(transactionType="", securitiesOwned=None, directOrIndirect=None)
fixture_server.route("/insider-trading/latest", body)
rows = client.insider_trading.latest_trades()

assert rows[0].transaction_type is None
assert rows[0].securities_owned is None
assert rows[0].direct_or_indirect is None
assert rows[0].symbol == "TRMK"


def test_latest_trades_accepts_a_datetime_date(client: Any, fixture_server: FixtureServer) -> None:
"""``latest_trades`` formats a ``datetime.date`` as ``YYYY-MM-DD`` on the wire."""
fixture_server.route("/insider-trading/latest", load_fixture("latest_insider_trades.json"))
Expand Down
15 changes: 15 additions & 0 deletions crates/fmp-py/tests/test_insider_trading_b.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,21 @@ def test_beneficial_ownership_acquisitions_with_limit(client: Any, fixture_serve
assert row.url.startswith("https://www.sec.gov/Archives/edgar/data/320193/")


def test_beneficial_ownership_acquisitions_decode_null_members_as_none(
client: Any, fixture_server: FixtureServer
) -> None:
"""Issue #368: a null ``cusip``, citizenship, or shared voting power decodes as ``None``."""
body = load_fixture("beneficial_ownership_acquisitions.json")
body[0].update(cusip=None, citizenshipOrPlaceOfOrganization=None, sharedVotingPower=None)
fixture_server.route("/acquisition-of-beneficial-ownership", body)
rows = client.insider_trading.beneficial_ownership_acquisitions("AAPL")

assert rows[0].cusip is None
assert rows[0].citizenship_or_place_of_organization is None
assert rows[0].shared_voting_power is None
assert rows[0].sole_voting_power == "0"


def test_beneficial_ownership_acquisitions_with_a_spaced_symbol_and_the_u32_limit(
client: Any, fixture_server: FixtureServer
) -> None:
Expand Down
11 changes: 11 additions & 0 deletions crates/fmp-py/tests/test_institutional_ownership_a.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,17 @@ def test_extract_encodes_cik_year_then_quarter(client: Any, fixture_server: Fixt
assert row.accepted_date == datetime.date(2023, 11, 13)


def test_extract_decodes_a_null_symbol_as_none(client: Any, fixture_server: FixtureServer) -> None:
"""Issue #368: a holding whose ``symbol`` is null decodes as ``None``."""
body = load_fixture("institutional_ownership_extract.json")
body[0]["symbol"] = None
fixture_server.route("/institutional-ownership/extract", body)
rows = client.institutional_ownership.extract(HOLDER_CIK, YEAR_2023, Q3)

assert rows[0].symbol is None
assert rows[0].security_cusip == "674215207"


def test_extract_accepts_keyword_arguments(client: Any, fixture_server: FixtureServer) -> None:
"""``extract`` takes its required arguments by keyword as well as by position."""
fixture_server.route("/institutional-ownership/extract", load_fixture("institutional_ownership_extract.json"))
Expand Down
14 changes: 14 additions & 0 deletions crates/fmp-py/tests/test_institutional_ownership_b.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,20 @@ def test_holder_industry_breakdown_encodes_cik_year_then_quarter(client: Any, fi
assert row.performance_percentage == pytest.approx(-178.2938)


def test_holder_industry_breakdown_decodes_null_members_as_none(client: Any, fixture_server: FixtureServer) -> None:
"""Issue #368: null industry title and percentages decode as ``None``; an empty title stays ``""``."""
body = load_fixture("holder_industry_breakdown.json")
body[0].update(industryTitle=None, changeInWeightPercentage=None, performancePercentage=None)
body.append({**body[0], "industryTitle": ""})
fixture_server.route("/institutional-ownership/holder-industry-breakdown", body)
rows = client.institutional_ownership.holder_industry_breakdown(BERKSHIRE_CIK, YEAR_2023, Q3)

assert rows[0].industry_title is None
assert rows[0].change_in_weight_percentage is None
assert rows[0].performance_percentage is None
assert rows[1].industry_title == ""


def test_positions_summary_encodes_a_spaced_symbol(client: Any, fixture_server: FixtureServer) -> None:
"""``positions_summary`` form-encodes the ticker before the year and quarter and decodes the row."""
fixture_server.route(
Expand Down
4 changes: 3 additions & 1 deletion crates/fmp-py/tests/typecheck_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,7 @@ def check_insider_trading_contract(client: FmpClient) -> None:
)
filed_on: datetime.date = trades[0].filing_date
price: float = trades[0].price
owned: float | None = trades[0].securities_owned
latest: list[InsiderTrade] = client.insider_trading.latest_trades(date=datetime.date(2026, 1, 27), page=0)
traded_on: datetime.date = latest[0].transaction_date
types: list[InsiderTransactionType] = client.insider_trading.transaction_types()
Expand All @@ -489,7 +490,8 @@ def check_insider_trading_contract(client: FmpClient) -> None:
)
accepted_on: datetime.date = filings[0].accepted_date
percent: str = filings[0].percent_of_class
_ = (filed_on, price, traded_on, code, ratio, accepted_on, percent)
cusip: str | None = filings[0].cusip
_ = (filed_on, price, owned, traded_on, code, ratio, accepted_on, percent, cusip)


def check_fundraising_contract(client: FmpClient) -> None:
Expand Down
Loading
Loading