finbrain-python 0.2.2__tar.gz → 0.2.4__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.
Files changed (85) hide show
  1. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/CHANGELOG.md +21 -3
  2. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/PKG-INFO +14 -6
  3. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/README.md +13 -5
  4. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/aio/client.py +2 -0
  5. finbrain_python-0.2.4/src/finbrain/aio/endpoints/government_contracts.py +48 -0
  6. finbrain_python-0.2.4/src/finbrain/aio/endpoints/reddit_mentions.py +48 -0
  7. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/aio/endpoints/screener.py +26 -0
  8. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/client.py +2 -0
  9. finbrain_python-0.2.4/src/finbrain/endpoints/government_contracts.py +73 -0
  10. finbrain_python-0.2.4/src/finbrain/endpoints/reddit_mentions.py +73 -0
  11. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/endpoints/screener.py +26 -0
  12. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/plotting.py +158 -4
  13. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain_python.egg-info/PKG-INFO +14 -6
  14. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain_python.egg-info/SOURCES.txt +3 -0
  15. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/tests/test_async_client.py +2 -0
  16. finbrain_python-0.2.4/tests/test_government_contracts.py +214 -0
  17. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/tests/test_integration.py +46 -8
  18. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/tests/test_plotting.py +77 -37
  19. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/tests/test_reddit_mentions.py +6 -12
  20. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/tests/test_screener.py +36 -0
  21. finbrain_python-0.2.2/src/finbrain/aio/endpoints/reddit_mentions.py +0 -79
  22. finbrain_python-0.2.2/src/finbrain/endpoints/reddit_mentions.py +0 -123
  23. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/.gitattributes +0 -0
  24. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/.github/workflows/ci.yml +0 -0
  25. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/.github/workflows/release.yml +0 -0
  26. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/.gitignore +0 -0
  27. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/LICENSE +0 -0
  28. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/RELEASE.md +0 -0
  29. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/examples/async_example.py +0 -0
  30. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/examples/transactions_plotting_example.py +0 -0
  31. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/pyproject.toml +0 -0
  32. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/setup.cfg +0 -0
  33. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/__init__.py +0 -0
  34. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/aio/__init__.py +0 -0
  35. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/aio/endpoints/__init__.py +0 -0
  36. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/aio/endpoints/_utils.py +0 -0
  37. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/aio/endpoints/analyst_ratings.py +0 -0
  38. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/aio/endpoints/app_ratings.py +0 -0
  39. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/aio/endpoints/available.py +0 -0
  40. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/aio/endpoints/corporate_lobbying.py +0 -0
  41. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/aio/endpoints/house_trades.py +0 -0
  42. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/aio/endpoints/insider_transactions.py +0 -0
  43. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/aio/endpoints/linkedin_data.py +0 -0
  44. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/aio/endpoints/news.py +0 -0
  45. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/aio/endpoints/options.py +0 -0
  46. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/aio/endpoints/predictions.py +0 -0
  47. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/aio/endpoints/recent.py +0 -0
  48. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/aio/endpoints/senate_trades.py +0 -0
  49. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/aio/endpoints/sentiments.py +0 -0
  50. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/endpoints/__init__.py +0 -0
  51. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/endpoints/_utils.py +0 -0
  52. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/endpoints/analyst_ratings.py +0 -0
  53. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/endpoints/app_ratings.py +0 -0
  54. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/endpoints/available.py +0 -0
  55. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/endpoints/corporate_lobbying.py +0 -0
  56. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/endpoints/house_trades.py +0 -0
  57. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/endpoints/insider_transactions.py +0 -0
  58. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/endpoints/linkedin_data.py +0 -0
  59. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/endpoints/news.py +0 -0
  60. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/endpoints/options.py +0 -0
  61. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/endpoints/predictions.py +0 -0
  62. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/endpoints/recent.py +0 -0
  63. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/endpoints/senate_trades.py +0 -0
  64. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/endpoints/sentiments.py +0 -0
  65. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/exceptions.py +0 -0
  66. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain/py.typed +0 -0
  67. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain_python.egg-info/dependency_links.txt +0 -0
  68. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain_python.egg-info/requires.txt +0 -0
  69. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/src/finbrain_python.egg-info/top_level.txt +0 -0
  70. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/tests/__init__.py +0 -0
  71. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/tests/conftest.py +0 -0
  72. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/tests/test_analyst_ratings.py +0 -0
  73. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/tests/test_app_ratings.py +0 -0
  74. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/tests/test_available.py +0 -0
  75. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/tests/test_corporate_lobbying.py +0 -0
  76. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/tests/test_envelope.py +0 -0
  77. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/tests/test_house_trades.py +0 -0
  78. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/tests/test_insider_transactions.py +0 -0
  79. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/tests/test_linkedin_data.py +0 -0
  80. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/tests/test_news.py +0 -0
  81. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/tests/test_options.py +0 -0
  82. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/tests/test_predictions.py +0 -0
  83. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/tests/test_recent.py +0 -0
  84. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/tests/test_senate_trades.py +0 -0
  85. {finbrain_python-0.2.2 → finbrain_python-0.2.4}/tests/test_sentiments.py +0 -0
@@ -5,15 +5,33 @@ 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.4] - 2026-03-30
9
+
10
+ ### Added
11
+
12
+ - **Government Contracts Endpoint**: `fb.government_contracts.ticker("LMT")` — fetch U.S. government contract awards with award amounts, agencies, NAICS codes, and descriptions (`/government-contracts/{SYMBOL}`)
13
+ - **Government Contracts Screener**: `fb.screener.government_contracts()` — cross-ticker government contracts with summary stats (`/screener/government-contracts`)
14
+ - **Government Contracts Plotting**: `fb.plot.government_contracts()` — visualize contract award amounts as bars on a secondary y-axis overlaid on a price chart, with hover details showing agency, NAICS description, and contract description
15
+ - **Async Government Contracts**: Full async support via `AsyncGovernmentContractsAPI`
16
+ - **Government Contracts Tests**: Unit tests (`tests/test_government_contracts.py`), screener tests, integration tests, and plotting tests
17
+
18
+ ## [0.2.3] - 2026-03-17
19
+
20
+ ### Changed
21
+
22
+ - **Reddit Mentions Screener**: Moved from `fb.reddit_mentions.screener()` to `fb.screener.reddit_mentions()` for consistency with other screener endpoints
23
+ - **Plotting rename**: `fb.plot.reddit_mentions_screener()` renamed to `fb.plot.reddit_mentions_top()` to better reflect the chart's purpose (top N most mentioned tickers)
24
+
8
25
  ## [0.2.2] - 2026-03-17
9
26
 
10
27
  ### Added
11
28
 
12
29
  - **Reddit Mentions Endpoint**: `fb.reddit_mentions.ticker("TSLA")` — fetch per-subreddit mention counts with full timestamps (sampled every 4 hours) (`/reddit-mentions/{SYMBOL}`)
13
- - **Reddit Mentions Screener**: `fb.reddit_mentions.screener()` — cross-ticker Reddit mentions with aggregated totals and per-subreddit breakdowns (`/screener/reddit-mentions`)
14
- - **Reddit Mentions Plotting**: `fb.plot.reddit_mentions()` — stacked bars per subreddit overlaid on a price chart; `fb.plot.reddit_mentions_screener()` — horizontal stacked bar chart of top N most mentioned tickers from the latest screener snapshot
30
+ - **Reddit Mentions Screener**: `fb.screener.reddit_mentions()` — cross-ticker Reddit mentions with aggregated totals and per-subreddit breakdowns (`/screener/reddit-mentions`)
31
+ - **Reddit Mentions Plotting**: `fb.plot.reddit_mentions()` — stacked bars per subreddit overlaid on a price chart; `fb.plot.reddit_mentions_top()` — horizontal stacked bar chart of top N most mentioned tickers
15
32
  - **Async Reddit Mentions**: Full async support via `AsyncRedditMentionsAPI`
16
- - **Reddit Mentions Tests**: Unit tests (`tests/test_reddit_mentions.py`) covering ticker, screener, DataFrame, and error handling
33
+ - **Reddit Mentions Tests**: Unit tests, integration tests, and functional plotting tests
34
+ - **Plotting Test Coverage**: Added functional tests for all 11 plotting methods
17
35
 
18
36
  ## [0.2.1] - 2026-03-12
19
37
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: finbrain-python
3
- Version: 0.2.2
3
+ Version: 0.2.4
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
@@ -105,8 +105,12 @@ fb.reddit_mentions.ticker("TSLA",
105
105
  date_to="2026-03-17",
106
106
  as_dataframe=True)
107
107
 
108
- # screener cross-ticker Reddit mentions
109
- fb.reddit_mentions.screener(market="S&P 500", limit=100, as_dataframe=True)
108
+ # ---------- government contracts ----------
109
+ fb.government_contracts.ticker("LMT",
110
+ date_from="2025-01-01",
111
+ date_to="2025-12-31",
112
+ limit=50,
113
+ as_dataframe=True)
110
114
 
111
115
  # ---------- insider transactions ----------
112
116
  fb.insider_transactions.ticker("AMZN", as_dataframe=True)
@@ -139,6 +143,8 @@ fb.news.ticker("AMZN", limit=20, as_dataframe=True)
139
143
  fb.screener.sentiment(market="S&P 500", as_dataframe=True)
140
144
  fb.screener.predictions_daily(limit=100, as_dataframe=True)
141
145
  fb.screener.insider_trading(limit=50)
146
+ fb.screener.reddit_mentions(limit=100, as_dataframe=True)
147
+ fb.screener.government_contracts(limit=100, as_dataframe=True)
142
148
 
143
149
  # ---------- recent data ----------
144
150
  fb.recent.news(limit=100, as_dataframe=True)
@@ -264,10 +270,10 @@ fb.plot.reddit_mentions("TSLA",
264
270
  ```python
265
271
  # ---------- Reddit Mentions Screener Chart (no price data needed) ----------
266
272
  # Stacked horizontal bar chart of top 15 most mentioned tickers
267
- fb.plot.reddit_mentions_screener(market="S&P 500")
273
+ fb.plot.reddit_mentions_top(market="S&P 500")
268
274
 
269
275
  # Customize the number of tickers shown
270
- fb.plot.reddit_mentions_screener(top_n=10, region="US")
276
+ fb.plot.reddit_mentions_top(top_n=10, region="US")
271
277
  ```
272
278
 
273
279
  **Price Data Requirements:**
@@ -314,13 +320,15 @@ fb = FinBrainClient() # reads from FINBRAIN_API_KEY env var
314
320
  | Senate trades | `client.senate_trades.ticker()` | `/congress/senate/{SYMBOL}` |
315
321
  | Corporate lobbying | `client.corporate_lobbying.ticker()` | `/lobbying/{SYMBOL}` |
316
322
  | Reddit mentions | `client.reddit_mentions.ticker()` | `/reddit-mentions/{SYMBOL}` |
317
- | | `client.reddit_mentions.screener()` | `/screener/reddit-mentions` |
323
+ | Gov. contracts | `client.government_contracts.ticker()` | `/government-contracts/{SYMBOL}` |
318
324
  | Insider transactions | `client.insider_transactions.ticker()` | `/insider-trading/{SYMBOL}` |
319
325
  | LinkedIn | `client.linkedin_data.ticker()` | `/linkedin/{SYMBOL}` |
320
326
  | Options – Put/Call | `client.options.put_call()` | `/put-call-ratio/{SYMBOL}` |
321
327
  | Screener | `client.screener.sentiment()` | `/screener/sentiment` |
322
328
  | | `client.screener.predictions_daily()` | `/screener/predictions/daily` |
323
329
  | | `client.screener.insider_trading()` | `/screener/insider-trading` |
330
+ | | `client.screener.reddit_mentions()` | `/screener/reddit-mentions` |
331
+ | | `client.screener.government_contracts()` | `/screener/government-contracts` |
324
332
  | | ... and 8 more screener methods | |
325
333
  | Recent | `client.recent.news()` | `/recent/news` |
326
334
  | | `client.recent.analyst_ratings()` | `/recent/analyst-ratings` |
@@ -79,8 +79,12 @@ fb.reddit_mentions.ticker("TSLA",
79
79
  date_to="2026-03-17",
80
80
  as_dataframe=True)
81
81
 
82
- # screener cross-ticker Reddit mentions
83
- fb.reddit_mentions.screener(market="S&P 500", limit=100, as_dataframe=True)
82
+ # ---------- government contracts ----------
83
+ fb.government_contracts.ticker("LMT",
84
+ date_from="2025-01-01",
85
+ date_to="2025-12-31",
86
+ limit=50,
87
+ as_dataframe=True)
84
88
 
85
89
  # ---------- insider transactions ----------
86
90
  fb.insider_transactions.ticker("AMZN", as_dataframe=True)
@@ -113,6 +117,8 @@ fb.news.ticker("AMZN", limit=20, as_dataframe=True)
113
117
  fb.screener.sentiment(market="S&P 500", as_dataframe=True)
114
118
  fb.screener.predictions_daily(limit=100, as_dataframe=True)
115
119
  fb.screener.insider_trading(limit=50)
120
+ fb.screener.reddit_mentions(limit=100, as_dataframe=True)
121
+ fb.screener.government_contracts(limit=100, as_dataframe=True)
116
122
 
117
123
  # ---------- recent data ----------
118
124
  fb.recent.news(limit=100, as_dataframe=True)
@@ -238,10 +244,10 @@ fb.plot.reddit_mentions("TSLA",
238
244
  ```python
239
245
  # ---------- Reddit Mentions Screener Chart (no price data needed) ----------
240
246
  # Stacked horizontal bar chart of top 15 most mentioned tickers
241
- fb.plot.reddit_mentions_screener(market="S&P 500")
247
+ fb.plot.reddit_mentions_top(market="S&P 500")
242
248
 
243
249
  # Customize the number of tickers shown
244
- fb.plot.reddit_mentions_screener(top_n=10, region="US")
250
+ fb.plot.reddit_mentions_top(top_n=10, region="US")
245
251
  ```
246
252
 
247
253
  **Price Data Requirements:**
@@ -288,13 +294,15 @@ fb = FinBrainClient() # reads from FINBRAIN_API_KEY env var
288
294
  | Senate trades | `client.senate_trades.ticker()` | `/congress/senate/{SYMBOL}` |
289
295
  | Corporate lobbying | `client.corporate_lobbying.ticker()` | `/lobbying/{SYMBOL}` |
290
296
  | Reddit mentions | `client.reddit_mentions.ticker()` | `/reddit-mentions/{SYMBOL}` |
291
- | | `client.reddit_mentions.screener()` | `/screener/reddit-mentions` |
297
+ | Gov. contracts | `client.government_contracts.ticker()` | `/government-contracts/{SYMBOL}` |
292
298
  | Insider transactions | `client.insider_transactions.ticker()` | `/insider-trading/{SYMBOL}` |
293
299
  | LinkedIn | `client.linkedin_data.ticker()` | `/linkedin/{SYMBOL}` |
294
300
  | Options – Put/Call | `client.options.put_call()` | `/put-call-ratio/{SYMBOL}` |
295
301
  | Screener | `client.screener.sentiment()` | `/screener/sentiment` |
296
302
  | | `client.screener.predictions_daily()` | `/screener/predictions/daily` |
297
303
  | | `client.screener.insider_trading()` | `/screener/insider-trading` |
304
+ | | `client.screener.reddit_mentions()` | `/screener/reddit-mentions` |
305
+ | | `client.screener.government_contracts()` | `/screener/government-contracts` |
298
306
  | | ... and 8 more screener methods | |
299
307
  | Recent | `client.recent.news()` | `/recent/news` |
300
308
  | | `client.recent.analyst_ratings()` | `/recent/analyst-ratings` |
@@ -24,6 +24,7 @@ from .endpoints.screener import AsyncScreenerAPI
24
24
  from .endpoints.recent import AsyncRecentAPI
25
25
  from .endpoints.corporate_lobbying import AsyncCorporateLobbyingAPI
26
26
  from .endpoints.reddit_mentions import AsyncRedditMentionsAPI
27
+ from .endpoints.government_contracts import AsyncGovernmentContractsAPI
27
28
 
28
29
 
29
30
  # Which status codes merit a retry
@@ -84,6 +85,7 @@ class AsyncFinBrainClient:
84
85
  self.recent = AsyncRecentAPI(self)
85
86
  self.corporate_lobbying = AsyncCorporateLobbyingAPI(self)
86
87
  self.reddit_mentions = AsyncRedditMentionsAPI(self)
88
+ self.government_contracts = AsyncGovernmentContractsAPI(self)
87
89
 
88
90
  async def __aenter__(self) -> "AsyncFinBrainClient":
89
91
  """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, List
5
+
6
+ from ._utils import to_datestr
7
+
8
+ if TYPE_CHECKING:
9
+ from ..client import AsyncFinBrainClient
10
+
11
+
12
+ class AsyncGovernmentContractsAPI:
13
+ """Async wrapper for /government-contracts and /screener/government-contracts 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 government contract awards 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"government-contracts/{symbol.upper()}"
37
+
38
+ data: Dict[str, Any] = await self._c._request("GET", path, params=params)
39
+
40
+ if as_dataframe:
41
+ rows: List[Dict[str, Any]] = data.get("contracts", [])
42
+ df = pd.DataFrame(rows)
43
+ if not df.empty and "startDate" in df.columns:
44
+ df["startDate"] = pd.to_datetime(df["startDate"])
45
+ df.set_index("startDate", inplace=True)
46
+ return df
47
+
48
+ return data
@@ -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, List
5
+
6
+ from ._utils import to_datestr
7
+
8
+ if TYPE_CHECKING:
9
+ from ..client import AsyncFinBrainClient
10
+
11
+
12
+ class AsyncRedditMentionsAPI:
13
+ """Async wrapper for /reddit-mentions and /screener/reddit-mentions 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 Reddit mention counts for a symbol across subreddits (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"reddit-mentions/{symbol.upper()}"
37
+
38
+ data: Dict[str, Any] = await self._c._request("GET", path, params=params)
39
+
40
+ if as_dataframe:
41
+ rows: List[Dict[str, Any]] = data.get("data", [])
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
@@ -208,3 +208,29 @@ class AsyncScreenerAPI:
208
208
  """Screen monthly (12-month) predictions across tickers."""
209
209
  params = self._build_params(limit=limit, market=market, region=region)
210
210
  return await self._get("screener/predictions/monthly", params, as_dataframe)
211
+
212
+ # ── reddit mentions ────────────────────────────────────────
213
+
214
+ async def reddit_mentions(
215
+ self,
216
+ *,
217
+ limit: int | None = None,
218
+ market: str | None = None,
219
+ region: str | None = None,
220
+ as_dataframe: bool = False,
221
+ ) -> List[Dict[str, Any]] | pd.DataFrame:
222
+ """Screen Reddit mention counts across tickers (async)."""
223
+ params = self._build_params(limit=limit, market=market, region=region)
224
+ return await self._get("screener/reddit-mentions", params, as_dataframe)
225
+
226
+ # ── government contracts ──────────────────────────────────
227
+
228
+ async def government_contracts(
229
+ self,
230
+ *,
231
+ limit: int | None = None,
232
+ as_dataframe: bool = False,
233
+ ) -> List[Dict[str, Any]] | pd.DataFrame:
234
+ """Screen government contracts across all tickers (async)."""
235
+ params = self._build_params(limit=limit)
236
+ return await self._get("screener/government-contracts", params, as_dataframe)
@@ -25,6 +25,7 @@ from .endpoints.screener import ScreenerAPI
25
25
  from .endpoints.recent import RecentAPI
26
26
  from .endpoints.corporate_lobbying import CorporateLobbyingAPI
27
27
  from .endpoints.reddit_mentions import RedditMentionsAPI
28
+ from .endpoints.government_contracts import GovernmentContractsAPI
28
29
 
29
30
 
30
31
  # Which status codes merit a retry
@@ -79,6 +80,7 @@ class FinBrainClient:
79
80
  self.recent = RecentAPI(self)
80
81
  self.corporate_lobbying = CorporateLobbyingAPI(self)
81
82
  self.reddit_mentions = RedditMentionsAPI(self)
83
+ self.government_contracts = GovernmentContractsAPI(self)
82
84
 
83
85
  # ---------- private helpers ----------
84
86
  def _request(
@@ -0,0 +1,73 @@
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 GovernmentContractsAPI:
13
+ """
14
+ Endpoints
15
+ ---------
16
+ ``/government-contracts/<TICKER>`` - U.S. government contract awards.
17
+ ``/screener/government-contracts`` - cross-ticker government contracts screener.
18
+ """
19
+
20
+ # ------------------------------------------------------------------ #
21
+ def __init__(self, client: "FinBrainClient") -> None:
22
+ self._c = client # reference to the parent client
23
+
24
+ # ------------------------------------------------------------------ #
25
+ def ticker(
26
+ self,
27
+ symbol: str,
28
+ *,
29
+ date_from: _dt.date | str | None = None,
30
+ date_to: _dt.date | str | None = None,
31
+ limit: int | None = None,
32
+ as_dataframe: bool = False,
33
+ ) -> Dict[str, Any] | pd.DataFrame:
34
+ """
35
+ Fetch government contract awards for *symbol*.
36
+
37
+ Parameters
38
+ ----------
39
+ symbol :
40
+ Ticker symbol; auto-upper-cased.
41
+ date_from, date_to :
42
+ Optional ISO dates (``YYYY-MM-DD``) bounding the returned rows.
43
+ limit :
44
+ Maximum number of records to return (1-500).
45
+ as_dataframe :
46
+ If *True*, return a **pandas.DataFrame** indexed by ``startDate``;
47
+ otherwise return the raw JSON dict.
48
+
49
+ Returns
50
+ -------
51
+ dict | pandas.DataFrame
52
+ """
53
+ params: Dict[str, str] = {}
54
+ if date_from:
55
+ params["startDate"] = to_datestr(date_from)
56
+ if date_to:
57
+ params["endDate"] = to_datestr(date_to)
58
+ if limit is not None:
59
+ params["limit"] = str(limit)
60
+
61
+ path = f"government-contracts/{symbol.upper()}"
62
+
63
+ data: Dict[str, Any] = self._c._request("GET", path, params=params)
64
+
65
+ if as_dataframe:
66
+ rows: List[Dict[str, Any]] = data.get("contracts", [])
67
+ df = pd.DataFrame(rows)
68
+ if not df.empty and "startDate" in df.columns:
69
+ df["startDate"] = pd.to_datetime(df["startDate"])
70
+ df.set_index("startDate", inplace=True)
71
+ return df
72
+
73
+ return data
@@ -0,0 +1,73 @@
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 RedditMentionsAPI:
13
+ """
14
+ Endpoints
15
+ ---------
16
+ ``/reddit-mentions/<TICKER>`` - Reddit mention counts per subreddit.
17
+ ``/screener/reddit-mentions`` - cross-ticker Reddit mentions screener.
18
+ """
19
+
20
+ # ------------------------------------------------------------------ #
21
+ def __init__(self, client: "FinBrainClient") -> None:
22
+ self._c = client # reference to the parent client
23
+
24
+ # ------------------------------------------------------------------ #
25
+ def ticker(
26
+ self,
27
+ symbol: str,
28
+ *,
29
+ date_from: _dt.date | str | None = None,
30
+ date_to: _dt.date | str | None = None,
31
+ limit: int | None = None,
32
+ as_dataframe: bool = False,
33
+ ) -> Dict[str, Any] | pd.DataFrame:
34
+ """
35
+ Fetch Reddit mention counts for *symbol* across subreddits.
36
+
37
+ Parameters
38
+ ----------
39
+ symbol :
40
+ Ticker symbol; auto-upper-cased.
41
+ date_from, date_to :
42
+ Optional ISO dates (``YYYY-MM-DD``) bounding the returned rows.
43
+ limit :
44
+ Maximum number of records to return (1-500).
45
+ as_dataframe :
46
+ If *True*, return a **pandas.DataFrame** indexed by ``date``;
47
+ otherwise return the raw JSON dict.
48
+
49
+ Returns
50
+ -------
51
+ dict | pandas.DataFrame
52
+ """
53
+ params: Dict[str, str] = {}
54
+ if date_from:
55
+ params["startDate"] = to_datestr(date_from)
56
+ if date_to:
57
+ params["endDate"] = to_datestr(date_to)
58
+ if limit is not None:
59
+ params["limit"] = str(limit)
60
+
61
+ path = f"reddit-mentions/{symbol.upper()}"
62
+
63
+ data: Dict[str, Any] = self._c._request("GET", path, params=params)
64
+
65
+ if as_dataframe:
66
+ rows: List[Dict[str, Any]] = data.get("data", [])
67
+ df = pd.DataFrame(rows)
68
+ if not df.empty and "date" in df.columns:
69
+ df["date"] = pd.to_datetime(df["date"])
70
+ df.set_index("date", inplace=True)
71
+ return df
72
+
73
+ return data
@@ -213,3 +213,29 @@ class ScreenerAPI:
213
213
  """Screen monthly (12-month) predictions across tickers."""
214
214
  params = self._build_params(limit=limit, market=market, region=region)
215
215
  return self._get("screener/predictions/monthly", params, as_dataframe)
216
+
217
+ # ── reddit mentions ────────────────────────────────────────
218
+
219
+ def reddit_mentions(
220
+ self,
221
+ *,
222
+ limit: int | None = None,
223
+ market: str | None = None,
224
+ region: str | None = None,
225
+ as_dataframe: bool = False,
226
+ ) -> List[Dict[str, Any]] | pd.DataFrame:
227
+ """Screen Reddit mention counts across tickers."""
228
+ params = self._build_params(limit=limit, market=market, region=region)
229
+ return self._get("screener/reddit-mentions", params, as_dataframe)
230
+
231
+ # ── government contracts ──────────────────────────────────
232
+
233
+ def government_contracts(
234
+ self,
235
+ *,
236
+ limit: int | None = None,
237
+ as_dataframe: bool = False,
238
+ ) -> List[Dict[str, Any]] | pd.DataFrame:
239
+ """Screen government contracts across all tickers."""
240
+ params = self._build_params(limit=limit)
241
+ return self._get("screener/government-contracts", params, as_dataframe)