finbrain-python 0.2.1__tar.gz → 0.2.5__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 (90) hide show
  1. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/.github/workflows/ci.yml +1 -0
  2. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/.github/workflows/release.yml +2 -2
  3. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/CHANGELOG.md +52 -0
  4. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/PKG-INFO +62 -1
  5. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/README.md +59 -0
  6. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/RELEASE.md +1 -1
  7. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/pyproject.toml +9 -0
  8. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/aio/client.py +9 -3
  9. finbrain_python-0.2.5/src/finbrain/aio/endpoints/_utils.py +12 -0
  10. finbrain_python-0.2.5/src/finbrain/aio/endpoints/government_contracts.py +48 -0
  11. finbrain_python-0.2.5/src/finbrain/aio/endpoints/patent_filings.py +48 -0
  12. finbrain_python-0.2.5/src/finbrain/aio/endpoints/reddit_mentions.py +48 -0
  13. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/aio/endpoints/screener.py +38 -0
  14. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/client.py +22 -3
  15. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/endpoints/_utils.py +7 -1
  16. finbrain_python-0.2.5/src/finbrain/endpoints/government_contracts.py +73 -0
  17. finbrain_python-0.2.5/src/finbrain/endpoints/patent_filings.py +74 -0
  18. finbrain_python-0.2.5/src/finbrain/endpoints/reddit_mentions.py +73 -0
  19. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/endpoints/screener.py +38 -0
  20. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/exceptions.py +24 -0
  21. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/plotting.py +728 -110
  22. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain_python.egg-info/PKG-INFO +62 -1
  23. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain_python.egg-info/SOURCES.txt +12 -1
  24. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain_python.egg-info/requires.txt +2 -0
  25. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/tests/test_async_client.py +3 -0
  26. finbrain_python-0.2.5/tests/test_client.py +46 -0
  27. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/tests/test_envelope.py +43 -1
  28. finbrain_python-0.2.5/tests/test_government_contracts.py +214 -0
  29. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/tests/test_integration.py +127 -0
  30. finbrain_python-0.2.5/tests/test_patent_filings.py +207 -0
  31. finbrain_python-0.2.5/tests/test_plotting.py +689 -0
  32. finbrain_python-0.2.5/tests/test_reddit_mentions.py +162 -0
  33. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/tests/test_screener.py +72 -0
  34. finbrain_python-0.2.5/tests/test_utils.py +24 -0
  35. finbrain_python-0.2.1/src/finbrain/aio/endpoints/_utils.py +0 -37
  36. finbrain_python-0.2.1/tests/test_plotting.py +0 -134
  37. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/.gitattributes +0 -0
  38. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/.gitignore +0 -0
  39. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/LICENSE +0 -0
  40. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/examples/async_example.py +0 -0
  41. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/examples/transactions_plotting_example.py +0 -0
  42. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/setup.cfg +0 -0
  43. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/__init__.py +0 -0
  44. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/aio/__init__.py +0 -0
  45. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/aio/endpoints/__init__.py +0 -0
  46. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/aio/endpoints/analyst_ratings.py +0 -0
  47. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/aio/endpoints/app_ratings.py +0 -0
  48. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/aio/endpoints/available.py +0 -0
  49. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/aio/endpoints/corporate_lobbying.py +0 -0
  50. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/aio/endpoints/house_trades.py +0 -0
  51. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/aio/endpoints/insider_transactions.py +0 -0
  52. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/aio/endpoints/linkedin_data.py +0 -0
  53. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/aio/endpoints/news.py +0 -0
  54. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/aio/endpoints/options.py +0 -0
  55. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/aio/endpoints/predictions.py +0 -0
  56. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/aio/endpoints/recent.py +0 -0
  57. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/aio/endpoints/senate_trades.py +0 -0
  58. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/aio/endpoints/sentiments.py +0 -0
  59. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/endpoints/__init__.py +0 -0
  60. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/endpoints/analyst_ratings.py +0 -0
  61. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/endpoints/app_ratings.py +0 -0
  62. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/endpoints/available.py +0 -0
  63. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/endpoints/corporate_lobbying.py +0 -0
  64. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/endpoints/house_trades.py +0 -0
  65. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/endpoints/insider_transactions.py +0 -0
  66. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/endpoints/linkedin_data.py +0 -0
  67. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/endpoints/news.py +0 -0
  68. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/endpoints/options.py +0 -0
  69. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/endpoints/predictions.py +0 -0
  70. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/endpoints/recent.py +0 -0
  71. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/endpoints/senate_trades.py +0 -0
  72. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/endpoints/sentiments.py +0 -0
  73. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain/py.typed +0 -0
  74. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain_python.egg-info/dependency_links.txt +0 -0
  75. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/src/finbrain_python.egg-info/top_level.txt +0 -0
  76. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/tests/__init__.py +0 -0
  77. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/tests/conftest.py +0 -0
  78. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/tests/test_analyst_ratings.py +0 -0
  79. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/tests/test_app_ratings.py +0 -0
  80. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/tests/test_available.py +0 -0
  81. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/tests/test_corporate_lobbying.py +0 -0
  82. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/tests/test_house_trades.py +0 -0
  83. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/tests/test_insider_transactions.py +0 -0
  84. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/tests/test_linkedin_data.py +0 -0
  85. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/tests/test_news.py +0 -0
  86. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/tests/test_options.py +0 -0
  87. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/tests/test_predictions.py +0 -0
  88. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/tests/test_recent.py +0 -0
  89. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/tests/test_senate_trades.py +0 -0
  90. {finbrain_python-0.2.1 → finbrain_python-0.2.5}/tests/test_sentiments.py +0 -0
@@ -17,4 +17,5 @@ jobs:
17
17
  python-version: ${{ matrix.python }}
18
18
  - run: pip install -e .[dev]
19
19
  - run: ruff check .
20
+ - run: mypy
20
21
  - run: pytest -q
@@ -24,14 +24,14 @@ jobs:
24
24
 
25
25
  # ─────────── push to TestPyPI first ───────────
26
26
  - name: Upload to TestPyPI
27
- run: twine upload --repository-url https://test.pypi.org/legacy/ dist/*
27
+ run: twine upload --skip-existing --repository-url https://test.pypi.org/legacy/ dist/*
28
28
  env:
29
29
  TWINE_USERNAME: __token__
30
30
  TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }}
31
31
 
32
32
  # ─────────── then to production PyPI ───────────
33
33
  - name: Upload to PyPI
34
- run: twine upload dist/*
34
+ run: twine upload --skip-existing dist/*
35
35
  env:
36
36
  TWINE_USERNAME: __token__
37
37
  TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
@@ -5,6 +5,58 @@ 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.5] - 2026-06-18
9
+
10
+ ### Added
11
+
12
+ - **Patent Filings Endpoint**: `fb.patent_filings.ticker("AAPL")` — fetch USPTO granted patents mapped to a ticker, with grant date, title, type, claim counts, citations, assignee, inventors, and CPC classifications (`/patent-filings/{SYMBOL}`)
13
+ - **Patent Filings Screener**: `fb.screener.patent_filings()` — cross-ticker patent filings with summary stats (total patents, tickers, top CPC sections) (`/screener/patent-filings`)
14
+ - **Patent Filings Plotting**: `fb.plot.patent_filings()` — visualize patent grants as bars sized by claim count on a secondary y-axis overlaid on a price chart, with hover details showing title, type, and CPC section
15
+ - **Async Patent Filings**: Full async support via `AsyncPatentFilingsAPI`
16
+ - **Patent Filings Tests**: Unit tests (`tests/test_patent_filings.py`), screener tests, integration tests, and plotting tests
17
+ - **Sync Context Manager**: `FinBrainClient` now supports `with FinBrainClient(...) as fb:` and exposes `close()` to release the underlying HTTP session (parity with `AsyncFinBrainClient`)
18
+ - **Exception Types**: Dedicated `BadGateway` (502), `ServiceUnavailable` (503), and `GatewayTimeout` (504) exceptions for transient gateway errors
19
+ - **Analyst Ratings Plotting**: `fb.plot.analyst_ratings()` — overlay analyst rating actions and price targets on a price chart, with markers grouped and coloured by action category (upgrade/downgrade/initiate/maintain); fills the last remaining ticker-level dataset without a plot method
20
+
21
+ ### Fixed
22
+
23
+ - **Date parameter handling**: `datetime.datetime` values passed to `date_from`/`date_to` are now truncated to `YYYY-MM-DD` instead of leaking a full ISO timestamp into the API query
24
+
25
+ ### Changed
26
+
27
+ - **Retry coverage**: Transient gateway errors (502/503/504) are now retried alongside 500, with exponential back-off
28
+ - **Internal**: Async endpoint date helper re-exports the synchronous `to_datestr` to keep a single source of truth
29
+ - **Internal**: Plotting price-overlay methods now share `_resolve_price_column` and `_to_naive_index` helpers, removing ~250 lines of duplicated validation/timezone boilerplate across the chart methods
30
+ - **Tooling**: Added `mypy` static type checking (validating the published `py.typed` surface) to the dev extra and CI; added a sync-client endpoint-parity test; release uploads now use `--skip-existing` for idempotent re-runs
31
+
32
+ ## [0.2.4] - 2026-03-30
33
+
34
+ ### Added
35
+
36
+ - **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}`)
37
+ - **Government Contracts Screener**: `fb.screener.government_contracts()` — cross-ticker government contracts with summary stats (`/screener/government-contracts`)
38
+ - **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
39
+ - **Async Government Contracts**: Full async support via `AsyncGovernmentContractsAPI`
40
+ - **Government Contracts Tests**: Unit tests (`tests/test_government_contracts.py`), screener tests, integration tests, and plotting tests
41
+
42
+ ## [0.2.3] - 2026-03-17
43
+
44
+ ### Changed
45
+
46
+ - **Reddit Mentions Screener**: Moved from `fb.reddit_mentions.screener()` to `fb.screener.reddit_mentions()` for consistency with other screener endpoints
47
+ - **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)
48
+
49
+ ## [0.2.2] - 2026-03-17
50
+
51
+ ### Added
52
+
53
+ - **Reddit Mentions Endpoint**: `fb.reddit_mentions.ticker("TSLA")` — fetch per-subreddit mention counts with full timestamps (sampled every 4 hours) (`/reddit-mentions/{SYMBOL}`)
54
+ - **Reddit Mentions Screener**: `fb.screener.reddit_mentions()` — cross-ticker Reddit mentions with aggregated totals and per-subreddit breakdowns (`/screener/reddit-mentions`)
55
+ - **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
56
+ - **Async Reddit Mentions**: Full async support via `AsyncRedditMentionsAPI`
57
+ - **Reddit Mentions Tests**: Unit tests, integration tests, and functional plotting tests
58
+ - **Plotting Test Coverage**: Added functional tests for all 11 plotting methods
59
+
8
60
  ## [0.2.1] - 2026-03-12
9
61
 
10
62
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: finbrain-python
3
- Version: 0.2.1
3
+ Version: 0.2.5
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
@@ -22,6 +22,8 @@ Requires-Dist: httpx>=0.24; extra == "dev"
22
22
  Requires-Dist: build; extra == "dev"
23
23
  Requires-Dist: twine; extra == "dev"
24
24
  Requires-Dist: ruff; extra == "dev"
25
+ Requires-Dist: mypy; extra == "dev"
26
+ Requires-Dist: types-requests; extra == "dev"
25
27
  Dynamic: license-file
26
28
 
27
29
  # FinBrain Python SDK&nbsp;<!-- omit in toc -->
@@ -99,6 +101,26 @@ fb.corporate_lobbying.ticker("AAPL",
99
101
  date_to="2025-06-30",
100
102
  as_dataframe=True)
101
103
 
104
+ # ---------- reddit mentions ----------
105
+ fb.reddit_mentions.ticker("TSLA",
106
+ date_from="2026-03-01",
107
+ date_to="2026-03-17",
108
+ as_dataframe=True)
109
+
110
+ # ---------- government contracts ----------
111
+ fb.government_contracts.ticker("LMT",
112
+ date_from="2025-01-01",
113
+ date_to="2025-12-31",
114
+ limit=50,
115
+ as_dataframe=True)
116
+
117
+ # ---------- patent filings ----------
118
+ fb.patent_filings.ticker("AAPL",
119
+ date_from="2025-01-01",
120
+ date_to="2025-12-31",
121
+ limit=50,
122
+ as_dataframe=True)
123
+
102
124
  # ---------- insider transactions ----------
103
125
  fb.insider_transactions.ticker("AMZN", as_dataframe=True)
104
126
 
@@ -130,6 +152,9 @@ fb.news.ticker("AMZN", limit=20, as_dataframe=True)
130
152
  fb.screener.sentiment(market="S&P 500", as_dataframe=True)
131
153
  fb.screener.predictions_daily(limit=100, as_dataframe=True)
132
154
  fb.screener.insider_trading(limit=50)
155
+ fb.screener.reddit_mentions(limit=100, as_dataframe=True)
156
+ fb.screener.government_contracts(limit=100, as_dataframe=True)
157
+ fb.screener.patent_filings(limit=100, as_dataframe=True)
133
158
 
134
159
  # ---------- recent data ----------
135
160
  fb.recent.news(limit=100, as_dataframe=True)
@@ -244,6 +269,33 @@ fb.plot.corporate_lobbying("AAPL",
244
269
  price_data=price_df,
245
270
  date_from="2024-01-01",
246
271
  date_to="2025-06-30")
272
+
273
+ # Plot Reddit mentions (stacked bars per subreddit) on your price chart
274
+ fb.plot.reddit_mentions("TSLA",
275
+ price_data=price_df,
276
+ date_from="2026-03-01",
277
+ date_to="2026-03-17")
278
+
279
+ # Plot patent grants (bars sized by claim count) on your price chart
280
+ fb.plot.patent_filings("AAPL",
281
+ price_data=price_df,
282
+ date_from="2024-01-01",
283
+ date_to="2025-06-30")
284
+
285
+ # Plot analyst ratings & price targets (markers coloured by action) on your price chart
286
+ fb.plot.analyst_ratings("AAPL",
287
+ price_data=price_df,
288
+ date_from="2024-01-01",
289
+ date_to="2025-06-30")
290
+ ```
291
+
292
+ ```python
293
+ # ---------- Reddit Mentions Screener Chart (no price data needed) ----------
294
+ # Stacked horizontal bar chart of top 15 most mentioned tickers
295
+ fb.plot.reddit_mentions_top(market="S&P 500")
296
+
297
+ # Customize the number of tickers shown
298
+ fb.plot.reddit_mentions_top(top_n=10, region="US")
247
299
  ```
248
300
 
249
301
  **Price Data Requirements:**
@@ -289,12 +341,18 @@ fb = FinBrainClient() # reads from FINBRAIN_API_KEY env var
289
341
  | House trades | `client.house_trades.ticker()` | `/congress/house/{SYMBOL}` |
290
342
  | Senate trades | `client.senate_trades.ticker()` | `/congress/senate/{SYMBOL}` |
291
343
  | Corporate lobbying | `client.corporate_lobbying.ticker()` | `/lobbying/{SYMBOL}` |
344
+ | Reddit mentions | `client.reddit_mentions.ticker()` | `/reddit-mentions/{SYMBOL}` |
345
+ | Gov. contracts | `client.government_contracts.ticker()` | `/government-contracts/{SYMBOL}` |
346
+ | Patent filings | `client.patent_filings.ticker()` | `/patent-filings/{SYMBOL}` |
292
347
  | Insider transactions | `client.insider_transactions.ticker()` | `/insider-trading/{SYMBOL}` |
293
348
  | LinkedIn | `client.linkedin_data.ticker()` | `/linkedin/{SYMBOL}` |
294
349
  | Options – Put/Call | `client.options.put_call()` | `/put-call-ratio/{SYMBOL}` |
295
350
  | Screener | `client.screener.sentiment()` | `/screener/sentiment` |
296
351
  | | `client.screener.predictions_daily()` | `/screener/predictions/daily` |
297
352
  | | `client.screener.insider_trading()` | `/screener/insider-trading` |
353
+ | | `client.screener.reddit_mentions()` | `/screener/reddit-mentions` |
354
+ | | `client.screener.government_contracts()` | `/screener/government-contracts` |
355
+ | | `client.screener.patent_filings()` | `/screener/patent-filings` |
298
356
  | | ... and 8 more screener methods | |
299
357
  | Recent | `client.recent.news()` | `/recent/news` |
300
358
  | | `client.recent.analyst_ratings()` | `/recent/analyst-ratings` |
@@ -322,6 +380,9 @@ except BadRequest as exc:
322
380
  | 405 | `MethodNotAllowed` | HTTP method not supported on endpoint |
323
381
  | 429 | `RateLimitError` | Too many requests |
324
382
  | 500 | `ServerError` | FinBrain internal error |
383
+ | 502 | `BadGateway` | Invalid response from upstream server |
384
+ | 503 | `ServiceUnavailable` | Service temporarily unavailable |
385
+ | 504 | `GatewayTimeout` | Upstream server timed out |
325
386
 
326
387
  ---
327
388
 
@@ -73,6 +73,26 @@ fb.corporate_lobbying.ticker("AAPL",
73
73
  date_to="2025-06-30",
74
74
  as_dataframe=True)
75
75
 
76
+ # ---------- reddit mentions ----------
77
+ fb.reddit_mentions.ticker("TSLA",
78
+ date_from="2026-03-01",
79
+ date_to="2026-03-17",
80
+ as_dataframe=True)
81
+
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)
88
+
89
+ # ---------- patent filings ----------
90
+ fb.patent_filings.ticker("AAPL",
91
+ date_from="2025-01-01",
92
+ date_to="2025-12-31",
93
+ limit=50,
94
+ as_dataframe=True)
95
+
76
96
  # ---------- insider transactions ----------
77
97
  fb.insider_transactions.ticker("AMZN", as_dataframe=True)
78
98
 
@@ -104,6 +124,9 @@ fb.news.ticker("AMZN", limit=20, as_dataframe=True)
104
124
  fb.screener.sentiment(market="S&P 500", as_dataframe=True)
105
125
  fb.screener.predictions_daily(limit=100, as_dataframe=True)
106
126
  fb.screener.insider_trading(limit=50)
127
+ fb.screener.reddit_mentions(limit=100, as_dataframe=True)
128
+ fb.screener.government_contracts(limit=100, as_dataframe=True)
129
+ fb.screener.patent_filings(limit=100, as_dataframe=True)
107
130
 
108
131
  # ---------- recent data ----------
109
132
  fb.recent.news(limit=100, as_dataframe=True)
@@ -218,6 +241,33 @@ fb.plot.corporate_lobbying("AAPL",
218
241
  price_data=price_df,
219
242
  date_from="2024-01-01",
220
243
  date_to="2025-06-30")
244
+
245
+ # Plot Reddit mentions (stacked bars per subreddit) on your price chart
246
+ fb.plot.reddit_mentions("TSLA",
247
+ price_data=price_df,
248
+ date_from="2026-03-01",
249
+ date_to="2026-03-17")
250
+
251
+ # Plot patent grants (bars sized by claim count) on your price chart
252
+ fb.plot.patent_filings("AAPL",
253
+ price_data=price_df,
254
+ date_from="2024-01-01",
255
+ date_to="2025-06-30")
256
+
257
+ # Plot analyst ratings & price targets (markers coloured by action) on your price chart
258
+ fb.plot.analyst_ratings("AAPL",
259
+ price_data=price_df,
260
+ date_from="2024-01-01",
261
+ date_to="2025-06-30")
262
+ ```
263
+
264
+ ```python
265
+ # ---------- Reddit Mentions Screener Chart (no price data needed) ----------
266
+ # Stacked horizontal bar chart of top 15 most mentioned tickers
267
+ fb.plot.reddit_mentions_top(market="S&P 500")
268
+
269
+ # Customize the number of tickers shown
270
+ fb.plot.reddit_mentions_top(top_n=10, region="US")
221
271
  ```
222
272
 
223
273
  **Price Data Requirements:**
@@ -263,12 +313,18 @@ fb = FinBrainClient() # reads from FINBRAIN_API_KEY env var
263
313
  | House trades | `client.house_trades.ticker()` | `/congress/house/{SYMBOL}` |
264
314
  | Senate trades | `client.senate_trades.ticker()` | `/congress/senate/{SYMBOL}` |
265
315
  | Corporate lobbying | `client.corporate_lobbying.ticker()` | `/lobbying/{SYMBOL}` |
316
+ | Reddit mentions | `client.reddit_mentions.ticker()` | `/reddit-mentions/{SYMBOL}` |
317
+ | Gov. contracts | `client.government_contracts.ticker()` | `/government-contracts/{SYMBOL}` |
318
+ | Patent filings | `client.patent_filings.ticker()` | `/patent-filings/{SYMBOL}` |
266
319
  | Insider transactions | `client.insider_transactions.ticker()` | `/insider-trading/{SYMBOL}` |
267
320
  | LinkedIn | `client.linkedin_data.ticker()` | `/linkedin/{SYMBOL}` |
268
321
  | Options – Put/Call | `client.options.put_call()` | `/put-call-ratio/{SYMBOL}` |
269
322
  | Screener | `client.screener.sentiment()` | `/screener/sentiment` |
270
323
  | | `client.screener.predictions_daily()` | `/screener/predictions/daily` |
271
324
  | | `client.screener.insider_trading()` | `/screener/insider-trading` |
325
+ | | `client.screener.reddit_mentions()` | `/screener/reddit-mentions` |
326
+ | | `client.screener.government_contracts()` | `/screener/government-contracts` |
327
+ | | `client.screener.patent_filings()` | `/screener/patent-filings` |
272
328
  | | ... and 8 more screener methods | |
273
329
  | Recent | `client.recent.news()` | `/recent/news` |
274
330
  | | `client.recent.analyst_ratings()` | `/recent/analyst-ratings` |
@@ -296,6 +352,9 @@ except BadRequest as exc:
296
352
  | 405 | `MethodNotAllowed` | HTTP method not supported on endpoint |
297
353
  | 429 | `RateLimitError` | Too many requests |
298
354
  | 500 | `ServerError` | FinBrain internal error |
355
+ | 502 | `BadGateway` | Invalid response from upstream server |
356
+ | 503 | `ServiceUnavailable` | Service temporarily unavailable |
357
+ | 504 | `GatewayTimeout` | Upstream server timed out |
299
358
 
300
359
  ---
301
360
 
@@ -10,7 +10,7 @@ Starting from v0.1.6, we use **`v`-prefixed tags** (e.g., `v0.1.6`, `v0.2.0`, `v
10
10
 
11
11
  ### 1. Update CHANGELOG.md
12
12
 
13
- Edit `CHANGELOG.md` and move items from `[Unreleased]` to a new version section:
13
+ Edit `CHANGELOG.md` and add a new version section at the top, above the previous release:
14
14
 
15
15
  ```markdown
16
16
  ## [0.1.6] - 2025-01-15
@@ -45,9 +45,18 @@ dev = [
45
45
  "build",
46
46
  "twine",
47
47
  "ruff", # linter/formatter (optional)
48
+ "mypy", # static type checker (validates py.typed surface)
49
+ "types-requests", # stubs for requests (mypy import-untyped otherwise)
48
50
  ]
49
51
 
50
52
  [tool.pytest.ini_options]
51
53
  markers = [
52
54
  "integration: hits the real FinBrain v2 API (requires FINBRAIN_API_KEY)",
53
55
  ]
56
+
57
+ [tool.mypy]
58
+ # Code targets >=3.9 but uses `from __future__ import annotations` throughout,
59
+ # so checking against 3.10 (mypy's minimum target) is equivalent here.
60
+ python_version = "3.10"
61
+ files = ["src/finbrain"]
62
+ ignore_missing_imports = true # third-party libs (pandas, plotly, …) ship no stubs here
@@ -23,10 +23,13 @@ from .endpoints.news import AsyncNewsAPI
23
23
  from .endpoints.screener import AsyncScreenerAPI
24
24
  from .endpoints.recent import AsyncRecentAPI
25
25
  from .endpoints.corporate_lobbying import AsyncCorporateLobbyingAPI
26
+ from .endpoints.reddit_mentions import AsyncRedditMentionsAPI
27
+ from .endpoints.government_contracts import AsyncGovernmentContractsAPI
28
+ from .endpoints.patent_filings import AsyncPatentFilingsAPI
26
29
 
27
30
 
28
- # Which status codes merit a retry
29
- _RETRYABLE_STATUS = {500}
31
+ # Which status codes merit a retry (transient server / gateway errors)
32
+ _RETRYABLE_STATUS = {500, 502, 503, 504}
30
33
  # How long to wait between retries (2, 4, 8 … seconds)
31
34
  _BACKOFF_BASE = 2
32
35
 
@@ -82,6 +85,9 @@ class AsyncFinBrainClient:
82
85
  self.screener = AsyncScreenerAPI(self)
83
86
  self.recent = AsyncRecentAPI(self)
84
87
  self.corporate_lobbying = AsyncCorporateLobbyingAPI(self)
88
+ self.reddit_mentions = AsyncRedditMentionsAPI(self)
89
+ self.government_contracts = AsyncGovernmentContractsAPI(self)
90
+ self.patent_filings = AsyncPatentFilingsAPI(self)
85
91
 
86
92
  async def __aenter__(self) -> "AsyncFinBrainClient":
87
93
  """Context manager entry."""
@@ -160,7 +166,7 @@ class AsyncFinBrainClient:
160
166
 
161
167
  # ── Error path ───────────────────────────────────
162
168
  if resp.status_code in _RETRYABLE_STATUS and attempt < self.retries:
163
- # 500 – exponential back-off then retry
169
+ # Transient server/gateway error – exponential back-off then retry
164
170
  await asyncio.sleep(_BACKOFF_BASE**attempt)
165
171
  continue
166
172
 
@@ -0,0 +1,12 @@
1
+ """
2
+ Shared utility functions for async endpoint modules.
3
+
4
+ These helpers are identical to the synchronous ones, so they are re-exported
5
+ from :mod:`finbrain.endpoints._utils` to keep a single source of truth.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from ...endpoints._utils import to_datestr
11
+
12
+ __all__ = ["to_datestr"]
@@ -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 AsyncPatentFilingsAPI:
13
+ """Async wrapper for /patent-filings and /screener/patent-filings 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 USPTO granted patents 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"patent-filings/{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("patents", [])
42
+ df = pd.DataFrame(rows)
43
+ if not df.empty and "patentDate" in df.columns:
44
+ df["patentDate"] = pd.to_datetime(df["patentDate"])
45
+ df.set_index("patentDate", 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,41 @@ 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)
237
+
238
+ # ── patent filings ────────────────────────────────────────
239
+
240
+ async def patent_filings(
241
+ self,
242
+ *,
243
+ limit: int | None = None,
244
+ as_dataframe: bool = False,
245
+ ) -> List[Dict[str, Any]] | pd.DataFrame:
246
+ """Screen USPTO patent filings across all tickers (async)."""
247
+ params = self._build_params(limit=limit)
248
+ return await self._get("screener/patent-filings", params, as_dataframe)
@@ -24,10 +24,13 @@ from .endpoints.news import NewsAPI
24
24
  from .endpoints.screener import ScreenerAPI
25
25
  from .endpoints.recent import RecentAPI
26
26
  from .endpoints.corporate_lobbying import CorporateLobbyingAPI
27
+ from .endpoints.reddit_mentions import RedditMentionsAPI
28
+ from .endpoints.government_contracts import GovernmentContractsAPI
29
+ from .endpoints.patent_filings import PatentFilingsAPI
27
30
 
28
31
 
29
- # Which status codes merit a retry
30
- _RETRYABLE_STATUS = {500}
32
+ # Which status codes merit a retry (transient server / gateway errors)
33
+ _RETRYABLE_STATUS = {500, 502, 503, 504}
31
34
  # How long to wait between retries (2, 4, 8 … seconds)
32
35
  _BACKOFF_BASE = 2
33
36
 
@@ -77,6 +80,22 @@ class FinBrainClient:
77
80
  self.screener = ScreenerAPI(self)
78
81
  self.recent = RecentAPI(self)
79
82
  self.corporate_lobbying = CorporateLobbyingAPI(self)
83
+ self.reddit_mentions = RedditMentionsAPI(self)
84
+ self.government_contracts = GovernmentContractsAPI(self)
85
+ self.patent_filings = PatentFilingsAPI(self)
86
+
87
+ # ---------- lifecycle ----------
88
+ def __enter__(self) -> "FinBrainClient":
89
+ """Context manager entry."""
90
+ return self
91
+
92
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
93
+ """Context manager exit — close the underlying HTTP session."""
94
+ self.close()
95
+
96
+ def close(self) -> None:
97
+ """Close the underlying ``requests.Session``."""
98
+ self.session.close()
80
99
 
81
100
  # ---------- private helpers ----------
82
101
  def _request(
@@ -127,7 +146,7 @@ class FinBrainClient:
127
146
 
128
147
  # ── Error path ───────────────────────────────────
129
148
  if resp.status_code in _RETRYABLE_STATUS and attempt < self.retries:
130
- # 500 – exponential back-off then retry
149
+ # Transient server/gateway error – exponential back-off then retry
131
150
  time.sleep(_BACKOFF_BASE**attempt)
132
151
  continue
133
152
 
@@ -28,10 +28,16 @@ def to_datestr(value: _dt.date | str) -> str:
28
28
 
29
29
  Examples
30
30
  --------
31
- >>> from datetime import date
31
+ >>> from datetime import date, datetime
32
32
  >>> to_datestr(date(2025, 1, 15))
33
33
  '2025-01-15'
34
+ >>> to_datestr(datetime(2025, 1, 15, 9, 30))
35
+ '2025-01-15'
34
36
  >>> to_datestr("2025-01-15")
35
37
  '2025-01-15'
36
38
  """
39
+ # ``datetime`` subclasses ``date``; take the date part so the time
40
+ # component never leaks into the ``YYYY-MM-DD`` API parameter.
41
+ if isinstance(value, _dt.datetime):
42
+ return value.date().isoformat()
37
43
  return value.isoformat() if isinstance(value, _dt.date) else value