Skip to content
Open
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
109 changes: 109 additions & 0 deletions eodhd/APIs/CongressionalTradesAPI.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# APIs/CongressionalTradesAPI.py

from .BaseAPI import BaseAPI


class CongressionalTradesAPI(BaseAPI):
"""
Wrapper for the Congressional Trades endpoint:

GET /api/congressional-trades

US Congress stock-trade disclosures filed under the STOCK Act, from the
official Senate EFD and House Clerk sources. Requires the All-in-One plan;
each request costs 10 API calls.

Optional filters (flat query keys):
symbol, chamber (senate/house), bioguide_id,
transaction_type (purchase/sale/exchange, comma-separated for multiple),
transaction_date_from, transaction_date_to,
disclosure_date_from, disclosure_date_to

Pagination:
page_offset -> page[offset] (default 0)
page_limit -> page[limit] (default 20, max 100)
"""

_CHAMBERS = ("senate", "house")
_TRANSACTION_TYPES = ("purchase", "sale", "exchange")

def get_congressional_trades(
self,
api_token: str,
symbol: str = None,
chamber: str = None,
bioguide_id: str = None,
transaction_type: str = None,
transaction_date_from: str = None,
transaction_date_to: str = None,
disclosure_date_from: str = None,
disclosure_date_to: str = None,
page_offset: int = None,
page_limit: int = None,
):
"""
Parameters
----------
api_token : str
Your EODHD API token.
symbol : str, optional
Filter by a single ticker symbol, e.g. "AAPL".
chamber : str, optional
Filter by chamber: "senate" or "house".
bioguide_id : str, optional
Filter by a member's Bioguide ID, e.g. "S000250".
transaction_type : str, optional
One or more of "purchase", "sale", "exchange", comma-separated.
transaction_date_from, transaction_date_to : str, optional
Transaction date range, YYYY-MM-DD, inclusive.
disclosure_date_from, disclosure_date_to : str, optional
Disclosure date range, YYYY-MM-DD, inclusive.
page_offset : int, optional
Pagination offset (records to skip). Default 0.
page_limit : int, optional
Records per page. Default 20, maximum 100.

Returns
-------
dict
JSON response with keys: data (array), meta (total, page), links (next).
"""
if chamber is not None:
chamber = str(chamber).lower()
if chamber not in self._CHAMBERS:
raise ValueError("chamber must be 'senate' or 'house'.")

if transaction_type is not None:
for value in str(transaction_type).split(","):
if value.strip().lower() not in self._TRANSACTION_TYPES:
raise ValueError(
"transaction_type values must be one of 'purchase', 'sale', 'exchange'."
)

querystring = ""
if symbol is not None:
querystring += f"&symbol={symbol}"
if chamber is not None:
querystring += f"&chamber={chamber}"
if bioguide_id is not None:
querystring += f"&bioguide_id={bioguide_id}"
if transaction_type is not None:
querystring += f"&transaction_type={transaction_type}"
if transaction_date_from is not None:
querystring += f"&transaction_date_from={transaction_date_from}"
if transaction_date_to is not None:
querystring += f"&transaction_date_to={transaction_date_to}"
if disclosure_date_from is not None:
querystring += f"&disclosure_date_from={disclosure_date_from}"
if disclosure_date_to is not None:
querystring += f"&disclosure_date_to={disclosure_date_to}"
if page_offset is not None:
querystring += f"&page[offset]={int(page_offset)}"
if page_limit is not None:
querystring += f"&page[limit]={int(page_limit)}"

return self._rest_get_method(
api_key=api_token,
endpoint="congressional-trades",
querystring=querystring,
)
1 change: 1 addition & 0 deletions eodhd/APIs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from .BulkFundamentalsAPI import BulkFundamentalsAPI
from .TreasuryAPI import TreasuryAPI
from .ExchangeDetailsV2API import ExchangeDetailsV2API
from .CongressionalTradesAPI import CongressionalTradesAPI

#Marketplace endpoints
from .MPIndexComponentsAPI import MPIndexComponentsAPI
Expand Down
46 changes: 46 additions & 0 deletions eodhd/apiclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
from eodhd.APIs import BulkFundamentalsAPI
from eodhd.APIs import TreasuryAPI
from eodhd.APIs import ExchangeDetailsV2API
from eodhd.APIs import CongressionalTradesAPI

#Marketplace endpoints
from eodhd.APIs import MPIndexComponentsAPI
Expand Down Expand Up @@ -1586,6 +1587,51 @@ def get_treasury_real_yield_rates(self, from_date=None, to_date=None):
api_call = TreasuryAPI(session=self._session, timeout=self._timeout)
return api_call.get_treasury_real_yield_rates(api_token=self._api_key, from_date=from_date, to_date=to_date)

# ── Congressional Trades ──────────────────────────────────────

def get_congressional_trades(
self,
symbol=None,
chamber=None,
bioguide_id=None,
transaction_type=None,
transaction_date_from=None,
transaction_date_to=None,
disclosure_date_from=None,
disclosure_date_to=None,
page_offset=None,
page_limit=None,
):
"""
Congressional Trades API
Endpoint: GET /api/congressional-trades

US Senate and House stock-trade disclosures filed under the STOCK Act.
Requires the All-in-One plan; each request costs 10 API calls.

Optional filters: symbol, chamber ("senate"/"house"), bioguide_id,
transaction_type ("purchase"/"sale"/"exchange", comma-separated for
multiple), transaction_date_from/to, disclosure_date_from/to.
Pagination: page_offset (default 0), page_limit (default 20, max 100).

Returns:
dict with keys: data, meta (total, page), links (next).
"""
api_call = CongressionalTradesAPI(session=self._session, timeout=self._timeout)
return api_call.get_congressional_trades(
api_token=self._api_key,
symbol=symbol,
chamber=chamber,
bioguide_id=bioguide_id,
transaction_type=transaction_type,
transaction_date_from=transaction_date_from,
transaction_date_to=transaction_date_to,
disclosure_date_from=disclosure_date_from,
disclosure_date_to=disclosure_date_to,
page_offset=page_offset,
page_limit=page_limit,
)

# ── Phase 2: Marketplace ──────────────────────────────────────

# --- InvestVerte ESG (6 methods) ---
Expand Down
84 changes: 84 additions & 0 deletions tests/test_congressional_trades.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"""Tests for CongressionalTradesAPI."""

import pytest
from unittest.mock import MagicMock

from eodhd.APIs.CongressionalTradesAPI import CongressionalTradesAPI


@pytest.fixture
def mock_session():
return MagicMock()


def _make_api(session):
return CongressionalTradesAPI(session=session)


def _mock_response(session, data=None):
resp = MagicMock()
resp.status_code = 200
resp.json.return_value = data or {
"data": [],
"meta": {"total": 0, "page": {"offset": 0, "limit": 20}},
"links": {"next": None},
}
session.get.return_value = resp


def test_basic_request(mock_session):
_mock_response(mock_session)
api = _make_api(mock_session)
result = api.get_congressional_trades(api_token="test1234567890123456")

call_url = mock_session.get.call_args[0][0]
assert "/congressional-trades" in call_url
assert "meta" in result


def test_filters(mock_session):
_mock_response(mock_session)
api = _make_api(mock_session)
api.get_congressional_trades(
api_token="test1234567890123456",
chamber="senate",
transaction_type="purchase,sale",
symbol="AAPL",
bioguide_id="S000250",
)

call_url = mock_session.get.call_args[0][0]
assert "&chamber=senate" in call_url
assert "&transaction_type=purchase,sale" in call_url
assert "&symbol=AAPL" in call_url
assert "&bioguide_id=S000250" in call_url


def test_date_range_and_pagination(mock_session):
_mock_response(mock_session)
api = _make_api(mock_session)
api.get_congressional_trades(
api_token="test1234567890123456",
transaction_date_from="2024-01-01",
transaction_date_to="2024-12-31",
page_offset=0,
page_limit=50,
)

call_url = mock_session.get.call_args[0][0]
assert "&transaction_date_from=2024-01-01" in call_url
assert "&transaction_date_to=2024-12-31" in call_url
assert "&page[offset]=0" in call_url
assert "&page[limit]=50" in call_url


def test_invalid_chamber_raises(mock_session):
api = _make_api(mock_session)
with pytest.raises(ValueError):
api.get_congressional_trades(api_token="test1234567890123456", chamber="duma")


def test_invalid_transaction_type_raises(mock_session):
api = _make_api(mock_session)
with pytest.raises(ValueError):
api.get_congressional_trades(api_token="test1234567890123456", transaction_type="bribe")