finbrain-python 0.2.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/CHANGELOG.md +10 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/PKG-INFO +15 -2
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/README.md +14 -1
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/aio/client.py +2 -0
- finbrain_python-0.2.1/src/finbrain/aio/endpoints/corporate_lobbying.py +48 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/client.py +2 -0
- finbrain_python-0.2.1/src/finbrain/endpoints/corporate_lobbying.py +72 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/plotting.py +156 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain_python.egg-info/PKG-INFO +15 -2
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain_python.egg-info/SOURCES.txt +3 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/tests/test_async_client.py +1 -0
- finbrain_python-0.2.1/tests/test_corporate_lobbying.py +104 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/tests/test_integration.py +27 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/.gitattributes +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/.github/workflows/ci.yml +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/.github/workflows/release.yml +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/.gitignore +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/LICENSE +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/RELEASE.md +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/examples/async_example.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/examples/transactions_plotting_example.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/pyproject.toml +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/setup.cfg +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/__init__.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/aio/__init__.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/aio/endpoints/__init__.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/aio/endpoints/_utils.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/aio/endpoints/analyst_ratings.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/aio/endpoints/app_ratings.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/aio/endpoints/available.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/aio/endpoints/house_trades.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/aio/endpoints/insider_transactions.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/aio/endpoints/linkedin_data.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/aio/endpoints/news.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/aio/endpoints/options.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/aio/endpoints/predictions.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/aio/endpoints/recent.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/aio/endpoints/screener.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/aio/endpoints/senate_trades.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/aio/endpoints/sentiments.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/endpoints/__init__.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/endpoints/_utils.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/endpoints/analyst_ratings.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/endpoints/app_ratings.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/endpoints/available.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/endpoints/house_trades.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/endpoints/insider_transactions.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/endpoints/linkedin_data.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/endpoints/news.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/endpoints/options.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/endpoints/predictions.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/endpoints/recent.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/endpoints/screener.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/endpoints/senate_trades.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/endpoints/sentiments.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/exceptions.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/py.typed +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain_python.egg-info/dependency_links.txt +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain_python.egg-info/requires.txt +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain_python.egg-info/top_level.txt +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/tests/__init__.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/tests/conftest.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/tests/test_analyst_ratings.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/tests/test_app_ratings.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/tests/test_available.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/tests/test_envelope.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/tests/test_house_trades.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/tests/test_insider_transactions.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/tests/test_linkedin_data.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/tests/test_news.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/tests/test_options.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/tests/test_plotting.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/tests/test_predictions.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/tests/test_recent.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/tests/test_screener.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/tests/test_senate_trades.py +0 -0
- {finbrain_python-0.2.0 → finbrain_python-0.2.1}/tests/test_sentiments.py +0 -0
|
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.2.1] - 2026-03-12
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Corporate Lobbying Endpoint**: `fb.corporate_lobbying.ticker("AAPL")` — fetch corporate lobbying filings with income, expenses, registrant details, issue codes, and government entities (`/lobbying/{SYMBOL}`)
|
|
13
|
+
- **Corporate Lobbying Plotting**: `fb.plot.corporate_lobbying()` — visualize lobbying spend (income + expenses) as bars on a secondary y-axis overlaid on a price chart, with hover details showing registrant, quarter, income, and expenses
|
|
14
|
+
- **Async Corporate Lobbying**: Full async support via `AsyncCorporateLobbyingAPI`
|
|
15
|
+
- **Corporate Lobbying Tests**: Unit tests (`tests/test_corporate_lobbying.py`) and integration tests
|
|
16
|
+
|
|
8
17
|
## [0.2.0] - 2026-03-09
|
|
9
18
|
|
|
10
19
|
### BREAKING CHANGES
|
|
@@ -128,6 +137,7 @@ Previous releases...
|
|
|
128
137
|
|
|
129
138
|
Previous releases...
|
|
130
139
|
|
|
140
|
+
[0.2.1]: https://github.com/ahmetsbilgin/finbrain-python/compare/v0.2.0...v0.2.1
|
|
131
141
|
[0.2.0]: https://github.com/ahmetsbilgin/finbrain-python/compare/v0.1.8...v0.2.0
|
|
132
142
|
[0.1.8]: https://github.com/ahmetsbilgin/finbrain-python/compare/v0.1.7...v0.1.8
|
|
133
143
|
[0.1.7]: https://github.com/ahmetsbilgin/finbrain-python/compare/v0.1.6...v0.1.7
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: finbrain-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Official Python client for the FinBrain API
|
|
5
5
|
Author-email: Ahmet Salim Bilgin <ahmet@finbrain.tech>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -93,6 +93,12 @@ fb.senate_trades.ticker("META",
|
|
|
93
93
|
date_to="2025-06-30",
|
|
94
94
|
as_dataframe=True)
|
|
95
95
|
|
|
96
|
+
# ---------- corporate lobbying ----------
|
|
97
|
+
fb.corporate_lobbying.ticker("AAPL",
|
|
98
|
+
date_from="2024-01-01",
|
|
99
|
+
date_to="2025-06-30",
|
|
100
|
+
as_dataframe=True)
|
|
101
|
+
|
|
96
102
|
# ---------- insider transactions ----------
|
|
97
103
|
fb.insider_transactions.ticker("AMZN", as_dataframe=True)
|
|
98
104
|
|
|
@@ -205,7 +211,7 @@ fb.plot.sentiments("AMZN",
|
|
|
205
211
|
date_from="2025-01-01",
|
|
206
212
|
date_to="2025-06-30")
|
|
207
213
|
|
|
208
|
-
# ---------- Insider Transactions, House & Senate Trades (requires user price data) ----------
|
|
214
|
+
# ---------- Insider Transactions, House & Senate Trades, Corporate Lobbying (requires user price data) ----------
|
|
209
215
|
# These plots overlay transaction markers on a price chart.
|
|
210
216
|
# Since FinBrain doesn't provide historical prices, you must provide your own:
|
|
211
217
|
|
|
@@ -232,6 +238,12 @@ fb.plot.senate_trades("META",
|
|
|
232
238
|
price_data=price_df,
|
|
233
239
|
date_from="2025-01-01",
|
|
234
240
|
date_to="2025-06-30")
|
|
241
|
+
|
|
242
|
+
# Plot corporate lobbying spend on your price chart
|
|
243
|
+
fb.plot.corporate_lobbying("AAPL",
|
|
244
|
+
price_data=price_df,
|
|
245
|
+
date_from="2024-01-01",
|
|
246
|
+
date_to="2025-06-30")
|
|
235
247
|
```
|
|
236
248
|
|
|
237
249
|
**Price Data Requirements:**
|
|
@@ -276,6 +288,7 @@ fb = FinBrainClient() # reads from FINBRAIN_API_KEY env var
|
|
|
276
288
|
| Analyst ratings | `client.analyst_ratings.ticker()` | `/analyst-ratings/{SYMBOL}` |
|
|
277
289
|
| House trades | `client.house_trades.ticker()` | `/congress/house/{SYMBOL}` |
|
|
278
290
|
| Senate trades | `client.senate_trades.ticker()` | `/congress/senate/{SYMBOL}` |
|
|
291
|
+
| Corporate lobbying | `client.corporate_lobbying.ticker()` | `/lobbying/{SYMBOL}` |
|
|
279
292
|
| Insider transactions | `client.insider_transactions.ticker()` | `/insider-trading/{SYMBOL}` |
|
|
280
293
|
| LinkedIn | `client.linkedin_data.ticker()` | `/linkedin/{SYMBOL}` |
|
|
281
294
|
| Options – Put/Call | `client.options.put_call()` | `/put-call-ratio/{SYMBOL}` |
|
|
@@ -67,6 +67,12 @@ fb.senate_trades.ticker("META",
|
|
|
67
67
|
date_to="2025-06-30",
|
|
68
68
|
as_dataframe=True)
|
|
69
69
|
|
|
70
|
+
# ---------- corporate lobbying ----------
|
|
71
|
+
fb.corporate_lobbying.ticker("AAPL",
|
|
72
|
+
date_from="2024-01-01",
|
|
73
|
+
date_to="2025-06-30",
|
|
74
|
+
as_dataframe=True)
|
|
75
|
+
|
|
70
76
|
# ---------- insider transactions ----------
|
|
71
77
|
fb.insider_transactions.ticker("AMZN", as_dataframe=True)
|
|
72
78
|
|
|
@@ -179,7 +185,7 @@ fb.plot.sentiments("AMZN",
|
|
|
179
185
|
date_from="2025-01-01",
|
|
180
186
|
date_to="2025-06-30")
|
|
181
187
|
|
|
182
|
-
# ---------- Insider Transactions, House & Senate Trades (requires user price data) ----------
|
|
188
|
+
# ---------- Insider Transactions, House & Senate Trades, Corporate Lobbying (requires user price data) ----------
|
|
183
189
|
# These plots overlay transaction markers on a price chart.
|
|
184
190
|
# Since FinBrain doesn't provide historical prices, you must provide your own:
|
|
185
191
|
|
|
@@ -206,6 +212,12 @@ fb.plot.senate_trades("META",
|
|
|
206
212
|
price_data=price_df,
|
|
207
213
|
date_from="2025-01-01",
|
|
208
214
|
date_to="2025-06-30")
|
|
215
|
+
|
|
216
|
+
# Plot corporate lobbying spend on your price chart
|
|
217
|
+
fb.plot.corporate_lobbying("AAPL",
|
|
218
|
+
price_data=price_df,
|
|
219
|
+
date_from="2024-01-01",
|
|
220
|
+
date_to="2025-06-30")
|
|
209
221
|
```
|
|
210
222
|
|
|
211
223
|
**Price Data Requirements:**
|
|
@@ -250,6 +262,7 @@ fb = FinBrainClient() # reads from FINBRAIN_API_KEY env var
|
|
|
250
262
|
| Analyst ratings | `client.analyst_ratings.ticker()` | `/analyst-ratings/{SYMBOL}` |
|
|
251
263
|
| House trades | `client.house_trades.ticker()` | `/congress/house/{SYMBOL}` |
|
|
252
264
|
| Senate trades | `client.senate_trades.ticker()` | `/congress/senate/{SYMBOL}` |
|
|
265
|
+
| Corporate lobbying | `client.corporate_lobbying.ticker()` | `/lobbying/{SYMBOL}` |
|
|
253
266
|
| Insider transactions | `client.insider_transactions.ticker()` | `/insider-trading/{SYMBOL}` |
|
|
254
267
|
| LinkedIn | `client.linkedin_data.ticker()` | `/linkedin/{SYMBOL}` |
|
|
255
268
|
| Options – Put/Call | `client.options.put_call()` | `/put-call-ratio/{SYMBOL}` |
|
|
@@ -22,6 +22,7 @@ from .endpoints.options import AsyncOptionsAPI
|
|
|
22
22
|
from .endpoints.news import AsyncNewsAPI
|
|
23
23
|
from .endpoints.screener import AsyncScreenerAPI
|
|
24
24
|
from .endpoints.recent import AsyncRecentAPI
|
|
25
|
+
from .endpoints.corporate_lobbying import AsyncCorporateLobbyingAPI
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
# Which status codes merit a retry
|
|
@@ -80,6 +81,7 @@ class AsyncFinBrainClient:
|
|
|
80
81
|
self.news = AsyncNewsAPI(self)
|
|
81
82
|
self.screener = AsyncScreenerAPI(self)
|
|
82
83
|
self.recent = AsyncRecentAPI(self)
|
|
84
|
+
self.corporate_lobbying = AsyncCorporateLobbyingAPI(self)
|
|
83
85
|
|
|
84
86
|
async def __aenter__(self) -> "AsyncFinBrainClient":
|
|
85
87
|
"""Context manager entry."""
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import datetime as _dt
|
|
4
|
+
from typing import TYPE_CHECKING, Dict, Any
|
|
5
|
+
|
|
6
|
+
from ._utils import to_datestr
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from ..client import AsyncFinBrainClient
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AsyncCorporateLobbyingAPI:
|
|
13
|
+
"""Async wrapper for /lobbying endpoints."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, client: "AsyncFinBrainClient") -> None:
|
|
16
|
+
self._c = client
|
|
17
|
+
|
|
18
|
+
async def ticker(
|
|
19
|
+
self,
|
|
20
|
+
symbol: str,
|
|
21
|
+
*,
|
|
22
|
+
date_from: _dt.date | str | None = None,
|
|
23
|
+
date_to: _dt.date | str | None = None,
|
|
24
|
+
limit: int | None = None,
|
|
25
|
+
as_dataframe: bool = False,
|
|
26
|
+
) -> Dict[str, Any] | pd.DataFrame:
|
|
27
|
+
"""Fetch corporate lobbying filings for a symbol (async)."""
|
|
28
|
+
params: Dict[str, str] = {}
|
|
29
|
+
if date_from:
|
|
30
|
+
params["startDate"] = to_datestr(date_from)
|
|
31
|
+
if date_to:
|
|
32
|
+
params["endDate"] = to_datestr(date_to)
|
|
33
|
+
if limit is not None:
|
|
34
|
+
params["limit"] = str(limit)
|
|
35
|
+
|
|
36
|
+
path = f"lobbying/{symbol.upper()}"
|
|
37
|
+
|
|
38
|
+
data: Dict[str, Any] = await self._c._request("GET", path, params=params)
|
|
39
|
+
|
|
40
|
+
if as_dataframe:
|
|
41
|
+
rows = data.get("filings", [])
|
|
42
|
+
df = pd.DataFrame(rows)
|
|
43
|
+
if not df.empty and "date" in df.columns:
|
|
44
|
+
df["date"] = pd.to_datetime(df["date"])
|
|
45
|
+
df.set_index("date", inplace=True)
|
|
46
|
+
return df
|
|
47
|
+
|
|
48
|
+
return data
|
|
@@ -23,6 +23,7 @@ from .endpoints.options import OptionsAPI
|
|
|
23
23
|
from .endpoints.news import NewsAPI
|
|
24
24
|
from .endpoints.screener import ScreenerAPI
|
|
25
25
|
from .endpoints.recent import RecentAPI
|
|
26
|
+
from .endpoints.corporate_lobbying import CorporateLobbyingAPI
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
# Which status codes merit a retry
|
|
@@ -75,6 +76,7 @@ class FinBrainClient:
|
|
|
75
76
|
self.news = NewsAPI(self)
|
|
76
77
|
self.screener = ScreenerAPI(self)
|
|
77
78
|
self.recent = RecentAPI(self)
|
|
79
|
+
self.corporate_lobbying = CorporateLobbyingAPI(self)
|
|
78
80
|
|
|
79
81
|
# ---------- private helpers ----------
|
|
80
82
|
def _request(
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import datetime as _dt
|
|
4
|
+
from typing import TYPE_CHECKING, Dict, Any, List
|
|
5
|
+
|
|
6
|
+
from ._utils import to_datestr
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING: # imported only by type-checkers
|
|
9
|
+
from ..client import FinBrainClient
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CorporateLobbyingAPI:
|
|
13
|
+
"""
|
|
14
|
+
Endpoint
|
|
15
|
+
--------
|
|
16
|
+
``/lobbying/<TICKER>`` - corporate lobbying filings for the selected ticker.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# ------------------------------------------------------------------ #
|
|
20
|
+
def __init__(self, client: "FinBrainClient") -> None:
|
|
21
|
+
self._c = client # reference to the parent client
|
|
22
|
+
|
|
23
|
+
# ------------------------------------------------------------------ #
|
|
24
|
+
def ticker(
|
|
25
|
+
self,
|
|
26
|
+
symbol: str,
|
|
27
|
+
*,
|
|
28
|
+
date_from: _dt.date | str | None = None,
|
|
29
|
+
date_to: _dt.date | str | None = None,
|
|
30
|
+
limit: int | None = None,
|
|
31
|
+
as_dataframe: bool = False,
|
|
32
|
+
) -> Dict[str, Any] | pd.DataFrame:
|
|
33
|
+
"""
|
|
34
|
+
Fetch corporate lobbying filings for *symbol*.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
symbol :
|
|
39
|
+
Ticker symbol; auto-upper-cased.
|
|
40
|
+
date_from, date_to :
|
|
41
|
+
Optional ISO dates (``YYYY-MM-DD``) bounding the returned rows.
|
|
42
|
+
limit :
|
|
43
|
+
Maximum number of records to return.
|
|
44
|
+
as_dataframe :
|
|
45
|
+
If *True*, return a **pandas.DataFrame** indexed by ``date``;
|
|
46
|
+
otherwise return the raw JSON dict.
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
dict | pandas.DataFrame
|
|
51
|
+
"""
|
|
52
|
+
params: Dict[str, str] = {}
|
|
53
|
+
if date_from:
|
|
54
|
+
params["startDate"] = to_datestr(date_from)
|
|
55
|
+
if date_to:
|
|
56
|
+
params["endDate"] = to_datestr(date_to)
|
|
57
|
+
if limit is not None:
|
|
58
|
+
params["limit"] = str(limit)
|
|
59
|
+
|
|
60
|
+
path = f"lobbying/{symbol.upper()}"
|
|
61
|
+
|
|
62
|
+
data: Dict[str, Any] = self._c._request("GET", path, params=params)
|
|
63
|
+
|
|
64
|
+
if as_dataframe:
|
|
65
|
+
rows: List[Dict[str, Any]] = data.get("filings", [])
|
|
66
|
+
df = pd.DataFrame(rows)
|
|
67
|
+
if not df.empty and "date" in df.columns:
|
|
68
|
+
df["date"] = pd.to_datetime(df["date"])
|
|
69
|
+
df.set_index("date", inplace=True)
|
|
70
|
+
return df
|
|
71
|
+
|
|
72
|
+
return data
|
|
@@ -751,6 +751,162 @@ class _PlotNamespace:
|
|
|
751
751
|
return None
|
|
752
752
|
return fig.to_json() if as_json else fig
|
|
753
753
|
|
|
754
|
+
# --------------------------------------------------------------------- #
|
|
755
|
+
# Corporate Lobbying → bars on price chart #
|
|
756
|
+
# --------------------------------------------------------------------- #
|
|
757
|
+
def corporate_lobbying(
|
|
758
|
+
self,
|
|
759
|
+
ticker: str,
|
|
760
|
+
price_data: pd.DataFrame,
|
|
761
|
+
*,
|
|
762
|
+
date_from: str | None = None,
|
|
763
|
+
date_to: str | None = None,
|
|
764
|
+
as_json: bool = False,
|
|
765
|
+
show: bool = True,
|
|
766
|
+
template: str = "plotly_dark",
|
|
767
|
+
**kwargs,
|
|
768
|
+
):
|
|
769
|
+
"""
|
|
770
|
+
Plot corporate lobbying spend overlaid on a price chart.
|
|
771
|
+
|
|
772
|
+
This method requires user-provided historical price data, as FinBrain
|
|
773
|
+
does not currently offer a price history endpoint.
|
|
774
|
+
|
|
775
|
+
Parameters
|
|
776
|
+
----------
|
|
777
|
+
ticker : str
|
|
778
|
+
Ticker symbol (e.g. ``"AAPL"``).
|
|
779
|
+
price_data : pandas.DataFrame
|
|
780
|
+
**User-provided** price history with a DatetimeIndex and a column
|
|
781
|
+
containing prices (e.g. ``"close"``, ``"Close"``, or ``"price"``).
|
|
782
|
+
The index must be timezone-naive or UTC.
|
|
783
|
+
date_from, date_to : str or None, optional
|
|
784
|
+
Date range for filings in ``YYYY-MM-DD`` format.
|
|
785
|
+
as_json : bool, default False
|
|
786
|
+
If ``True``, return JSON string instead of Figure object.
|
|
787
|
+
show : bool, default True
|
|
788
|
+
If ``True`` and ``as_json=False``, display the figure immediately.
|
|
789
|
+
template : str, default "plotly_dark"
|
|
790
|
+
Plotly template name.
|
|
791
|
+
**kwargs
|
|
792
|
+
Additional arguments passed to
|
|
793
|
+
:meth:`FinBrainClient.corporate_lobbying.ticker`.
|
|
794
|
+
|
|
795
|
+
Returns
|
|
796
|
+
-------
|
|
797
|
+
plotly.graph_objects.Figure or str or None
|
|
798
|
+
Figure object, JSON string, or None (when shown).
|
|
799
|
+
|
|
800
|
+
Raises
|
|
801
|
+
------
|
|
802
|
+
ValueError
|
|
803
|
+
If ``price_data`` is empty or missing required price column.
|
|
804
|
+
"""
|
|
805
|
+
# Validate price_data
|
|
806
|
+
if price_data.empty:
|
|
807
|
+
raise ValueError("price_data cannot be empty")
|
|
808
|
+
|
|
809
|
+
# Flatten MultiIndex columns if present (e.g., from yf.download())
|
|
810
|
+
if isinstance(price_data.columns, pd.MultiIndex):
|
|
811
|
+
price_data = price_data.copy()
|
|
812
|
+
price_data.columns = price_data.columns.get_level_values(0)
|
|
813
|
+
|
|
814
|
+
# Find price column (case-insensitive search)
|
|
815
|
+
price_col = None
|
|
816
|
+
for col in ["close", "Close", "price", "Price", "adj_close", "Adj Close"]:
|
|
817
|
+
if col in price_data.columns:
|
|
818
|
+
price_col = col
|
|
819
|
+
break
|
|
820
|
+
if price_col is None:
|
|
821
|
+
raise ValueError(
|
|
822
|
+
f"price_data must contain a price column (e.g. 'close', 'Close', 'price'). "
|
|
823
|
+
f"Found columns: {price_data.columns.tolist()}"
|
|
824
|
+
)
|
|
825
|
+
|
|
826
|
+
# Fetch lobbying filings
|
|
827
|
+
filings_df = self._fb.corporate_lobbying.ticker(
|
|
828
|
+
ticker,
|
|
829
|
+
date_from=date_from,
|
|
830
|
+
date_to=date_to,
|
|
831
|
+
as_dataframe=True,
|
|
832
|
+
**kwargs,
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
# Normalize timezones
|
|
836
|
+
price_data_normalized = price_data.copy()
|
|
837
|
+
if price_data_normalized.index.tz is not None:
|
|
838
|
+
price_data_normalized.index = price_data_normalized.index.tz_localize(None)
|
|
839
|
+
|
|
840
|
+
fig = go.Figure(
|
|
841
|
+
layout=dict(
|
|
842
|
+
template=template,
|
|
843
|
+
title=f"Corporate Lobbying · {ticker}",
|
|
844
|
+
xaxis_title="Date",
|
|
845
|
+
hovermode="x unified",
|
|
846
|
+
)
|
|
847
|
+
)
|
|
848
|
+
|
|
849
|
+
# Plot price line on primary y-axis
|
|
850
|
+
fig.add_scatter(
|
|
851
|
+
name="Price",
|
|
852
|
+
x=price_data_normalized.index,
|
|
853
|
+
y=price_data_normalized[price_col],
|
|
854
|
+
mode="lines",
|
|
855
|
+
line=dict(width=2, color="#02d2ff"),
|
|
856
|
+
hovertemplate="<b>%{x|%Y-%m-%d}</b><br>Price: $%{y:.2f}<extra></extra>",
|
|
857
|
+
)
|
|
858
|
+
|
|
859
|
+
if not filings_df.empty:
|
|
860
|
+
filings_normalized = filings_df.copy()
|
|
861
|
+
if filings_normalized.index.tz is not None:
|
|
862
|
+
filings_normalized.index = filings_normalized.index.tz_localize(None)
|
|
863
|
+
|
|
864
|
+
# Compute total spend per filing (income + expenses)
|
|
865
|
+
spend = filings_normalized.get("income", 0) + filings_normalized.get(
|
|
866
|
+
"expenses", 0
|
|
867
|
+
)
|
|
868
|
+
|
|
869
|
+
hover_text = []
|
|
870
|
+
for _, row in filings_normalized.iterrows():
|
|
871
|
+
registrant = row.get("registrantName", "N/A")
|
|
872
|
+
quarter = row.get("quarter", "")
|
|
873
|
+
income = row.get("income", 0)
|
|
874
|
+
expenses = row.get("expenses", 0)
|
|
875
|
+
hover_text.append(
|
|
876
|
+
f"Registrant: {registrant}<br>"
|
|
877
|
+
f"Quarter: {quarter}<br>"
|
|
878
|
+
f"Income: ${income:,.0f}<br>"
|
|
879
|
+
f"Expenses: ${expenses:,.0f}"
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
# Plot lobbying spend as bars on secondary y-axis
|
|
883
|
+
fig.add_bar(
|
|
884
|
+
name="Lobbying Spend",
|
|
885
|
+
x=filings_normalized.index,
|
|
886
|
+
y=spend,
|
|
887
|
+
marker_color="rgba(249,200,14,0.6)",
|
|
888
|
+
yaxis="y2",
|
|
889
|
+
hovertext=hover_text,
|
|
890
|
+
hovertemplate="<b>%{x|%Y-%m-%d}</b><br>%{hovertext}<extra></extra>",
|
|
891
|
+
)
|
|
892
|
+
|
|
893
|
+
fig.update_layout(
|
|
894
|
+
yaxis=dict(title="Price", showgrid=True),
|
|
895
|
+
yaxis2=dict(
|
|
896
|
+
title="Lobbying Spend ($)",
|
|
897
|
+
overlaying="y",
|
|
898
|
+
side="right",
|
|
899
|
+
showgrid=False,
|
|
900
|
+
zeroline=False,
|
|
901
|
+
rangemode="tozero",
|
|
902
|
+
),
|
|
903
|
+
)
|
|
904
|
+
|
|
905
|
+
if show and not as_json:
|
|
906
|
+
fig.show()
|
|
907
|
+
return None
|
|
908
|
+
return fig.to_json() if as_json else fig
|
|
909
|
+
|
|
754
910
|
# --------------------------------------------------------------------- #
|
|
755
911
|
# Helper methods #
|
|
756
912
|
# --------------------------------------------------------------------- #
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: finbrain-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Official Python client for the FinBrain API
|
|
5
5
|
Author-email: Ahmet Salim Bilgin <ahmet@finbrain.tech>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -93,6 +93,12 @@ fb.senate_trades.ticker("META",
|
|
|
93
93
|
date_to="2025-06-30",
|
|
94
94
|
as_dataframe=True)
|
|
95
95
|
|
|
96
|
+
# ---------- corporate lobbying ----------
|
|
97
|
+
fb.corporate_lobbying.ticker("AAPL",
|
|
98
|
+
date_from="2024-01-01",
|
|
99
|
+
date_to="2025-06-30",
|
|
100
|
+
as_dataframe=True)
|
|
101
|
+
|
|
96
102
|
# ---------- insider transactions ----------
|
|
97
103
|
fb.insider_transactions.ticker("AMZN", as_dataframe=True)
|
|
98
104
|
|
|
@@ -205,7 +211,7 @@ fb.plot.sentiments("AMZN",
|
|
|
205
211
|
date_from="2025-01-01",
|
|
206
212
|
date_to="2025-06-30")
|
|
207
213
|
|
|
208
|
-
# ---------- Insider Transactions, House & Senate Trades (requires user price data) ----------
|
|
214
|
+
# ---------- Insider Transactions, House & Senate Trades, Corporate Lobbying (requires user price data) ----------
|
|
209
215
|
# These plots overlay transaction markers on a price chart.
|
|
210
216
|
# Since FinBrain doesn't provide historical prices, you must provide your own:
|
|
211
217
|
|
|
@@ -232,6 +238,12 @@ fb.plot.senate_trades("META",
|
|
|
232
238
|
price_data=price_df,
|
|
233
239
|
date_from="2025-01-01",
|
|
234
240
|
date_to="2025-06-30")
|
|
241
|
+
|
|
242
|
+
# Plot corporate lobbying spend on your price chart
|
|
243
|
+
fb.plot.corporate_lobbying("AAPL",
|
|
244
|
+
price_data=price_df,
|
|
245
|
+
date_from="2024-01-01",
|
|
246
|
+
date_to="2025-06-30")
|
|
235
247
|
```
|
|
236
248
|
|
|
237
249
|
**Price Data Requirements:**
|
|
@@ -276,6 +288,7 @@ fb = FinBrainClient() # reads from FINBRAIN_API_KEY env var
|
|
|
276
288
|
| Analyst ratings | `client.analyst_ratings.ticker()` | `/analyst-ratings/{SYMBOL}` |
|
|
277
289
|
| House trades | `client.house_trades.ticker()` | `/congress/house/{SYMBOL}` |
|
|
278
290
|
| Senate trades | `client.senate_trades.ticker()` | `/congress/senate/{SYMBOL}` |
|
|
291
|
+
| Corporate lobbying | `client.corporate_lobbying.ticker()` | `/lobbying/{SYMBOL}` |
|
|
279
292
|
| Insider transactions | `client.insider_transactions.ticker()` | `/insider-trading/{SYMBOL}` |
|
|
280
293
|
| LinkedIn | `client.linkedin_data.ticker()` | `/linkedin/{SYMBOL}` |
|
|
281
294
|
| Options – Put/Call | `client.options.put_call()` | `/put-call-ratio/{SYMBOL}` |
|
|
@@ -21,6 +21,7 @@ src/finbrain/aio/endpoints/_utils.py
|
|
|
21
21
|
src/finbrain/aio/endpoints/analyst_ratings.py
|
|
22
22
|
src/finbrain/aio/endpoints/app_ratings.py
|
|
23
23
|
src/finbrain/aio/endpoints/available.py
|
|
24
|
+
src/finbrain/aio/endpoints/corporate_lobbying.py
|
|
24
25
|
src/finbrain/aio/endpoints/house_trades.py
|
|
25
26
|
src/finbrain/aio/endpoints/insider_transactions.py
|
|
26
27
|
src/finbrain/aio/endpoints/linkedin_data.py
|
|
@@ -36,6 +37,7 @@ src/finbrain/endpoints/_utils.py
|
|
|
36
37
|
src/finbrain/endpoints/analyst_ratings.py
|
|
37
38
|
src/finbrain/endpoints/app_ratings.py
|
|
38
39
|
src/finbrain/endpoints/available.py
|
|
40
|
+
src/finbrain/endpoints/corporate_lobbying.py
|
|
39
41
|
src/finbrain/endpoints/house_trades.py
|
|
40
42
|
src/finbrain/endpoints/insider_transactions.py
|
|
41
43
|
src/finbrain/endpoints/linkedin_data.py
|
|
@@ -57,6 +59,7 @@ tests/test_analyst_ratings.py
|
|
|
57
59
|
tests/test_app_ratings.py
|
|
58
60
|
tests/test_async_client.py
|
|
59
61
|
tests/test_available.py
|
|
62
|
+
tests/test_corporate_lobbying.py
|
|
60
63
|
tests/test_envelope.py
|
|
61
64
|
tests/test_house_trades.py
|
|
62
65
|
tests/test_insider_transactions.py
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# tests/test_corporate_lobbying.py
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from .conftest import stub_json, wrap_v2
|
|
6
|
+
from finbrain.exceptions import BadRequest
|
|
7
|
+
|
|
8
|
+
TICKER = "AAPL"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# ─────────── raw JSON branch ────────────────────────────────────────────
|
|
12
|
+
def test_corporate_lobbying_raw_ok(client, _activate_responses):
|
|
13
|
+
path = f"lobbying/{TICKER}"
|
|
14
|
+
payload = wrap_v2({
|
|
15
|
+
"symbol": "AAPL",
|
|
16
|
+
"name": "Apple Inc.",
|
|
17
|
+
"filings": [
|
|
18
|
+
{
|
|
19
|
+
"date": "2024-03-15",
|
|
20
|
+
"filingUuid": "abc-123",
|
|
21
|
+
"filingYear": 2024,
|
|
22
|
+
"quarter": "Q1",
|
|
23
|
+
"clientName": "Apple Inc.",
|
|
24
|
+
"registrantName": "Lobbying Firm LLC",
|
|
25
|
+
"income": 50000,
|
|
26
|
+
"expenses": 75000,
|
|
27
|
+
"issueCodes": ["TAX", "ITC"],
|
|
28
|
+
"governmentEntities": ["Congress", "Senate"],
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
stub_json(_activate_responses, "GET", path, payload)
|
|
34
|
+
|
|
35
|
+
data = client.corporate_lobbying.ticker(symbol=TICKER)
|
|
36
|
+
assert data["symbol"] == TICKER
|
|
37
|
+
assert isinstance(data["filings"], list)
|
|
38
|
+
assert data["filings"][0]["registrantName"] == "Lobbying Firm LLC"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ─────────── DataFrame branch ───────────────────────────────────────────
|
|
42
|
+
def test_corporate_lobbying_dataframe_ok(client, _activate_responses):
|
|
43
|
+
path = f"lobbying/{TICKER}"
|
|
44
|
+
payload = wrap_v2({
|
|
45
|
+
"symbol": "AAPL",
|
|
46
|
+
"name": "Apple Inc.",
|
|
47
|
+
"filings": [
|
|
48
|
+
{
|
|
49
|
+
"date": "2024-03-15",
|
|
50
|
+
"filingUuid": "abc-123",
|
|
51
|
+
"filingYear": 2024,
|
|
52
|
+
"quarter": "Q1",
|
|
53
|
+
"clientName": "Apple Inc.",
|
|
54
|
+
"registrantName": "Lobbying Firm LLC",
|
|
55
|
+
"income": 50000,
|
|
56
|
+
"expenses": 75000,
|
|
57
|
+
"issueCodes": ["TAX", "ITC"],
|
|
58
|
+
"governmentEntities": ["Congress", "Senate"],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"date": "2024-06-20",
|
|
62
|
+
"filingUuid": "def-456",
|
|
63
|
+
"filingYear": 2024,
|
|
64
|
+
"quarter": "Q2",
|
|
65
|
+
"clientName": "Apple Inc.",
|
|
66
|
+
"registrantName": "Another Firm Inc.",
|
|
67
|
+
"income": 30000,
|
|
68
|
+
"expenses": 45000,
|
|
69
|
+
"issueCodes": ["ENV"],
|
|
70
|
+
"governmentEntities": ["Congress"],
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
stub_json(_activate_responses, "GET", path, payload)
|
|
76
|
+
|
|
77
|
+
df = client.corporate_lobbying.ticker(symbol=TICKER, as_dataframe=True)
|
|
78
|
+
|
|
79
|
+
assert isinstance(df, pd.DataFrame)
|
|
80
|
+
assert len(df) == 2
|
|
81
|
+
assert "registrantName" in df.columns
|
|
82
|
+
assert "income" in df.columns
|
|
83
|
+
assert "expenses" in df.columns
|
|
84
|
+
assert df.index.name == "date"
|
|
85
|
+
assert pd.api.types.is_datetime64_any_dtype(df.index)
|
|
86
|
+
assert pd.Timestamp("2024-03-15") in df.index
|
|
87
|
+
assert pd.Timestamp("2024-06-20") in df.index
|
|
88
|
+
assert df.loc["2024-03-15", "registrantName"] == "Lobbying Firm LLC"
|
|
89
|
+
assert df.loc["2024-06-20", "income"] == 30000
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# ─────────── error mapping ──────────────────────────────────────────────
|
|
93
|
+
def test_corporate_lobbying_bad_request(client, _activate_responses):
|
|
94
|
+
path = f"lobbying/{TICKER}"
|
|
95
|
+
stub_json(
|
|
96
|
+
_activate_responses,
|
|
97
|
+
"GET",
|
|
98
|
+
path,
|
|
99
|
+
{"success": False, "error": {"code": "VALIDATION_ERROR", "message": "bad"}},
|
|
100
|
+
status=400,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
with pytest.raises(BadRequest):
|
|
104
|
+
client.corporate_lobbying.ticker(symbol=TICKER)
|
|
@@ -261,6 +261,33 @@ class TestSenateTrades:
|
|
|
261
261
|
assert "politician" in df.columns
|
|
262
262
|
|
|
263
263
|
|
|
264
|
+
# =====================================================================
|
|
265
|
+
# Corporate Lobbying
|
|
266
|
+
# =====================================================================
|
|
267
|
+
class TestCorporateLobbying:
|
|
268
|
+
def test_raw(self, fb):
|
|
269
|
+
data = fb.corporate_lobbying.ticker(TICKER)
|
|
270
|
+
assert isinstance(data, dict)
|
|
271
|
+
assert "filings" in data
|
|
272
|
+
filings = data["filings"]
|
|
273
|
+
assert isinstance(filings, list)
|
|
274
|
+
if len(filings) > 0:
|
|
275
|
+
assert "date" in filings[0]
|
|
276
|
+
assert "registrantName" in filings[0]
|
|
277
|
+
assert "income" in filings[0]
|
|
278
|
+
assert "expenses" in filings[0]
|
|
279
|
+
|
|
280
|
+
def test_dataframe(self, fb):
|
|
281
|
+
df = fb.corporate_lobbying.ticker(TICKER, as_dataframe=True)
|
|
282
|
+
assert isinstance(df, pd.DataFrame)
|
|
283
|
+
if len(df) > 0:
|
|
284
|
+
assert df.index.name == "date"
|
|
285
|
+
assert pd.api.types.is_datetime64_any_dtype(df.index)
|
|
286
|
+
assert "registrantName" in df.columns
|
|
287
|
+
assert "income" in df.columns
|
|
288
|
+
assert "expenses" in df.columns
|
|
289
|
+
|
|
290
|
+
|
|
264
291
|
# =====================================================================
|
|
265
292
|
# LinkedIn Data
|
|
266
293
|
# =====================================================================
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/aio/endpoints/analyst_ratings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/aio/endpoints/insider_transactions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain/endpoints/insider_transactions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{finbrain_python-0.2.0 → finbrain_python-0.2.1}/src/finbrain_python.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|