finbrain-python 0.2.1__tar.gz → 0.2.3__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 (81) hide show
  1. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/CHANGELOG.md +18 -0
  2. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/PKG-INFO +25 -1
  3. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/README.md +24 -0
  4. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/aio/client.py +2 -0
  5. finbrain_python-0.2.3/src/finbrain/aio/endpoints/reddit_mentions.py +48 -0
  6. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/screener.py +14 -0
  7. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/client.py +2 -0
  8. finbrain_python-0.2.3/src/finbrain/endpoints/reddit_mentions.py +73 -0
  9. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/endpoints/screener.py +14 -0
  10. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/plotting.py +247 -0
  11. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain_python.egg-info/PKG-INFO +25 -1
  12. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain_python.egg-info/SOURCES.txt +3 -0
  13. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/tests/test_integration.py +45 -0
  14. finbrain_python-0.2.3/tests/test_plotting.py +549 -0
  15. finbrain_python-0.2.3/tests/test_reddit_mentions.py +162 -0
  16. finbrain_python-0.2.1/tests/test_plotting.py +0 -134
  17. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/.gitattributes +0 -0
  18. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/.github/workflows/ci.yml +0 -0
  19. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/.github/workflows/release.yml +0 -0
  20. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/.gitignore +0 -0
  21. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/LICENSE +0 -0
  22. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/RELEASE.md +0 -0
  23. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/examples/async_example.py +0 -0
  24. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/examples/transactions_plotting_example.py +0 -0
  25. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/pyproject.toml +0 -0
  26. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/setup.cfg +0 -0
  27. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/__init__.py +0 -0
  28. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/aio/__init__.py +0 -0
  29. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/__init__.py +0 -0
  30. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/_utils.py +0 -0
  31. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/analyst_ratings.py +0 -0
  32. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/app_ratings.py +0 -0
  33. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/available.py +0 -0
  34. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/corporate_lobbying.py +0 -0
  35. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/house_trades.py +0 -0
  36. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/insider_transactions.py +0 -0
  37. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/linkedin_data.py +0 -0
  38. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/news.py +0 -0
  39. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/options.py +0 -0
  40. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/predictions.py +0 -0
  41. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/recent.py +0 -0
  42. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/senate_trades.py +0 -0
  43. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/sentiments.py +0 -0
  44. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/endpoints/__init__.py +0 -0
  45. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/endpoints/_utils.py +0 -0
  46. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/endpoints/analyst_ratings.py +0 -0
  47. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/endpoints/app_ratings.py +0 -0
  48. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/endpoints/available.py +0 -0
  49. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/endpoints/corporate_lobbying.py +0 -0
  50. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/endpoints/house_trades.py +0 -0
  51. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/endpoints/insider_transactions.py +0 -0
  52. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/endpoints/linkedin_data.py +0 -0
  53. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/endpoints/news.py +0 -0
  54. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/endpoints/options.py +0 -0
  55. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/endpoints/predictions.py +0 -0
  56. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/endpoints/recent.py +0 -0
  57. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/endpoints/senate_trades.py +0 -0
  58. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/endpoints/sentiments.py +0 -0
  59. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/exceptions.py +0 -0
  60. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain/py.typed +0 -0
  61. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain_python.egg-info/dependency_links.txt +0 -0
  62. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain_python.egg-info/requires.txt +0 -0
  63. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/src/finbrain_python.egg-info/top_level.txt +0 -0
  64. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/tests/__init__.py +0 -0
  65. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/tests/conftest.py +0 -0
  66. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/tests/test_analyst_ratings.py +0 -0
  67. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/tests/test_app_ratings.py +0 -0
  68. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/tests/test_async_client.py +0 -0
  69. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/tests/test_available.py +0 -0
  70. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/tests/test_corporate_lobbying.py +0 -0
  71. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/tests/test_envelope.py +0 -0
  72. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/tests/test_house_trades.py +0 -0
  73. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/tests/test_insider_transactions.py +0 -0
  74. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/tests/test_linkedin_data.py +0 -0
  75. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/tests/test_news.py +0 -0
  76. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/tests/test_options.py +0 -0
  77. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/tests/test_predictions.py +0 -0
  78. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/tests/test_recent.py +0 -0
  79. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/tests/test_screener.py +0 -0
  80. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/tests/test_senate_trades.py +0 -0
  81. {finbrain_python-0.2.1 → finbrain_python-0.2.3}/tests/test_sentiments.py +0 -0
@@ -5,6 +5,24 @@ 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.3] - 2026-03-17
9
+
10
+ ### Changed
11
+
12
+ - **Reddit Mentions Screener**: Moved from `fb.reddit_mentions.screener()` to `fb.screener.reddit_mentions()` for consistency with other screener endpoints
13
+ - **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)
14
+
15
+ ## [0.2.2] - 2026-03-17
16
+
17
+ ### Added
18
+
19
+ - **Reddit Mentions Endpoint**: `fb.reddit_mentions.ticker("TSLA")` — fetch per-subreddit mention counts with full timestamps (sampled every 4 hours) (`/reddit-mentions/{SYMBOL}`)
20
+ - **Reddit Mentions Screener**: `fb.screener.reddit_mentions()` — cross-ticker Reddit mentions with aggregated totals and per-subreddit breakdowns (`/screener/reddit-mentions`)
21
+ - **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
22
+ - **Async Reddit Mentions**: Full async support via `AsyncRedditMentionsAPI`
23
+ - **Reddit Mentions Tests**: Unit tests, integration tests, and functional plotting tests
24
+ - **Plotting Test Coverage**: Added functional tests for all 11 plotting methods
25
+
8
26
  ## [0.2.1] - 2026-03-12
9
27
 
10
28
  ### 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.3
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
@@ -99,6 +99,12 @@ fb.corporate_lobbying.ticker("AAPL",
99
99
  date_to="2025-06-30",
100
100
  as_dataframe=True)
101
101
 
102
+ # ---------- reddit mentions ----------
103
+ fb.reddit_mentions.ticker("TSLA",
104
+ date_from="2026-03-01",
105
+ date_to="2026-03-17",
106
+ as_dataframe=True)
107
+
102
108
  # ---------- insider transactions ----------
103
109
  fb.insider_transactions.ticker("AMZN", as_dataframe=True)
104
110
 
@@ -130,6 +136,7 @@ fb.news.ticker("AMZN", limit=20, as_dataframe=True)
130
136
  fb.screener.sentiment(market="S&P 500", as_dataframe=True)
131
137
  fb.screener.predictions_daily(limit=100, as_dataframe=True)
132
138
  fb.screener.insider_trading(limit=50)
139
+ fb.screener.reddit_mentions(limit=100, as_dataframe=True)
133
140
 
134
141
  # ---------- recent data ----------
135
142
  fb.recent.news(limit=100, as_dataframe=True)
@@ -244,6 +251,21 @@ fb.plot.corporate_lobbying("AAPL",
244
251
  price_data=price_df,
245
252
  date_from="2024-01-01",
246
253
  date_to="2025-06-30")
254
+
255
+ # Plot Reddit mentions (stacked bars per subreddit) on your price chart
256
+ fb.plot.reddit_mentions("TSLA",
257
+ price_data=price_df,
258
+ date_from="2026-03-01",
259
+ date_to="2026-03-17")
260
+ ```
261
+
262
+ ```python
263
+ # ---------- Reddit Mentions Screener Chart (no price data needed) ----------
264
+ # Stacked horizontal bar chart of top 15 most mentioned tickers
265
+ fb.plot.reddit_mentions_top(market="S&P 500")
266
+
267
+ # Customize the number of tickers shown
268
+ fb.plot.reddit_mentions_top(top_n=10, region="US")
247
269
  ```
248
270
 
249
271
  **Price Data Requirements:**
@@ -289,12 +311,14 @@ fb = FinBrainClient() # reads from FINBRAIN_API_KEY env var
289
311
  | House trades | `client.house_trades.ticker()` | `/congress/house/{SYMBOL}` |
290
312
  | Senate trades | `client.senate_trades.ticker()` | `/congress/senate/{SYMBOL}` |
291
313
  | Corporate lobbying | `client.corporate_lobbying.ticker()` | `/lobbying/{SYMBOL}` |
314
+ | Reddit mentions | `client.reddit_mentions.ticker()` | `/reddit-mentions/{SYMBOL}` |
292
315
  | Insider transactions | `client.insider_transactions.ticker()` | `/insider-trading/{SYMBOL}` |
293
316
  | LinkedIn | `client.linkedin_data.ticker()` | `/linkedin/{SYMBOL}` |
294
317
  | Options – Put/Call | `client.options.put_call()` | `/put-call-ratio/{SYMBOL}` |
295
318
  | Screener | `client.screener.sentiment()` | `/screener/sentiment` |
296
319
  | | `client.screener.predictions_daily()` | `/screener/predictions/daily` |
297
320
  | | `client.screener.insider_trading()` | `/screener/insider-trading` |
321
+ | | `client.screener.reddit_mentions()` | `/screener/reddit-mentions` |
298
322
  | | ... and 8 more screener methods | |
299
323
  | Recent | `client.recent.news()` | `/recent/news` |
300
324
  | | `client.recent.analyst_ratings()` | `/recent/analyst-ratings` |
@@ -73,6 +73,12 @@ 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
+
76
82
  # ---------- insider transactions ----------
77
83
  fb.insider_transactions.ticker("AMZN", as_dataframe=True)
78
84
 
@@ -104,6 +110,7 @@ fb.news.ticker("AMZN", limit=20, as_dataframe=True)
104
110
  fb.screener.sentiment(market="S&P 500", as_dataframe=True)
105
111
  fb.screener.predictions_daily(limit=100, as_dataframe=True)
106
112
  fb.screener.insider_trading(limit=50)
113
+ fb.screener.reddit_mentions(limit=100, as_dataframe=True)
107
114
 
108
115
  # ---------- recent data ----------
109
116
  fb.recent.news(limit=100, as_dataframe=True)
@@ -218,6 +225,21 @@ fb.plot.corporate_lobbying("AAPL",
218
225
  price_data=price_df,
219
226
  date_from="2024-01-01",
220
227
  date_to="2025-06-30")
228
+
229
+ # Plot Reddit mentions (stacked bars per subreddit) on your price chart
230
+ fb.plot.reddit_mentions("TSLA",
231
+ price_data=price_df,
232
+ date_from="2026-03-01",
233
+ date_to="2026-03-17")
234
+ ```
235
+
236
+ ```python
237
+ # ---------- Reddit Mentions Screener Chart (no price data needed) ----------
238
+ # Stacked horizontal bar chart of top 15 most mentioned tickers
239
+ fb.plot.reddit_mentions_top(market="S&P 500")
240
+
241
+ # Customize the number of tickers shown
242
+ fb.plot.reddit_mentions_top(top_n=10, region="US")
221
243
  ```
222
244
 
223
245
  **Price Data Requirements:**
@@ -263,12 +285,14 @@ fb = FinBrainClient() # reads from FINBRAIN_API_KEY env var
263
285
  | House trades | `client.house_trades.ticker()` | `/congress/house/{SYMBOL}` |
264
286
  | Senate trades | `client.senate_trades.ticker()` | `/congress/senate/{SYMBOL}` |
265
287
  | Corporate lobbying | `client.corporate_lobbying.ticker()` | `/lobbying/{SYMBOL}` |
288
+ | Reddit mentions | `client.reddit_mentions.ticker()` | `/reddit-mentions/{SYMBOL}` |
266
289
  | Insider transactions | `client.insider_transactions.ticker()` | `/insider-trading/{SYMBOL}` |
267
290
  | LinkedIn | `client.linkedin_data.ticker()` | `/linkedin/{SYMBOL}` |
268
291
  | Options – Put/Call | `client.options.put_call()` | `/put-call-ratio/{SYMBOL}` |
269
292
  | Screener | `client.screener.sentiment()` | `/screener/sentiment` |
270
293
  | | `client.screener.predictions_daily()` | `/screener/predictions/daily` |
271
294
  | | `client.screener.insider_trading()` | `/screener/insider-trading` |
295
+ | | `client.screener.reddit_mentions()` | `/screener/reddit-mentions` |
272
296
  | | ... and 8 more screener methods | |
273
297
  | Recent | `client.recent.news()` | `/recent/news` |
274
298
  | | `client.recent.analyst_ratings()` | `/recent/analyst-ratings` |
@@ -23,6 +23,7 @@ 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
26
27
 
27
28
 
28
29
  # Which status codes merit a retry
@@ -82,6 +83,7 @@ class AsyncFinBrainClient:
82
83
  self.screener = AsyncScreenerAPI(self)
83
84
  self.recent = AsyncRecentAPI(self)
84
85
  self.corporate_lobbying = AsyncCorporateLobbyingAPI(self)
86
+ self.reddit_mentions = AsyncRedditMentionsAPI(self)
85
87
 
86
88
  async def __aenter__(self) -> "AsyncFinBrainClient":
87
89
  """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 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,17 @@ 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)
@@ -24,6 +24,7 @@ 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
27
28
 
28
29
 
29
30
  # Which status codes merit a retry
@@ -77,6 +78,7 @@ class FinBrainClient:
77
78
  self.screener = ScreenerAPI(self)
78
79
  self.recent = RecentAPI(self)
79
80
  self.corporate_lobbying = CorporateLobbyingAPI(self)
81
+ self.reddit_mentions = RedditMentionsAPI(self)
80
82
 
81
83
  # ---------- private helpers ----------
82
84
  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 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,17 @@ 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)
@@ -907,6 +907,253 @@ class _PlotNamespace:
907
907
  return None
908
908
  return fig.to_json() if as_json else fig
909
909
 
910
+ # --------------------------------------------------------------------- #
911
+ # Reddit Mentions → bars on price chart #
912
+ # --------------------------------------------------------------------- #
913
+ def reddit_mentions(
914
+ self,
915
+ ticker: str,
916
+ price_data: pd.DataFrame,
917
+ *,
918
+ date_from: str | None = None,
919
+ date_to: str | None = None,
920
+ as_json: bool = False,
921
+ show: bool = True,
922
+ template: str = "plotly_dark",
923
+ **kwargs,
924
+ ):
925
+ """
926
+ Plot Reddit mention counts overlaid on a price chart.
927
+
928
+ This method requires user-provided historical price data, as FinBrain
929
+ does not currently offer a price history endpoint.
930
+
931
+ Parameters
932
+ ----------
933
+ ticker : str
934
+ Ticker symbol (e.g. ``"AAPL"``).
935
+ price_data : pandas.DataFrame
936
+ **User-provided** price history with a DatetimeIndex and a column
937
+ containing prices (e.g. ``"close"``, ``"Close"``, or ``"price"``).
938
+ The index must be timezone-naive or UTC.
939
+ date_from, date_to : str or None, optional
940
+ Date range for mentions in ``YYYY-MM-DD`` format.
941
+ as_json : bool, default False
942
+ If ``True``, return JSON string instead of Figure object.
943
+ show : bool, default True
944
+ If ``True`` and ``as_json=False``, display the figure immediately.
945
+ template : str, default "plotly_dark"
946
+ Plotly template name.
947
+ **kwargs
948
+ Additional arguments passed to
949
+ :meth:`FinBrainClient.reddit_mentions.ticker`.
950
+
951
+ Returns
952
+ -------
953
+ plotly.graph_objects.Figure or str or None
954
+ Figure object, JSON string, or None (when shown).
955
+
956
+ Raises
957
+ ------
958
+ ValueError
959
+ If ``price_data`` is empty or missing required price column.
960
+ """
961
+ # Validate price_data
962
+ if price_data.empty:
963
+ raise ValueError("price_data cannot be empty")
964
+
965
+ # Flatten MultiIndex columns if present (e.g., from yf.download())
966
+ if isinstance(price_data.columns, pd.MultiIndex):
967
+ price_data = price_data.copy()
968
+ price_data.columns = price_data.columns.get_level_values(0)
969
+
970
+ # Find price column (case-insensitive search)
971
+ price_col = None
972
+ for col in ["close", "Close", "price", "Price", "adj_close", "Adj Close"]:
973
+ if col in price_data.columns:
974
+ price_col = col
975
+ break
976
+ if price_col is None:
977
+ raise ValueError(
978
+ f"price_data must contain a price column (e.g. 'close', 'Close', 'price'). "
979
+ f"Found columns: {price_data.columns.tolist()}"
980
+ )
981
+
982
+ # Fetch Reddit mentions
983
+ mentions_df = self._fb.reddit_mentions.ticker(
984
+ ticker,
985
+ date_from=date_from,
986
+ date_to=date_to,
987
+ as_dataframe=True,
988
+ **kwargs,
989
+ )
990
+
991
+ # Normalize timezones
992
+ price_data_normalized = price_data.copy()
993
+ if price_data_normalized.index.tz is not None:
994
+ price_data_normalized.index = price_data_normalized.index.tz_localize(None)
995
+
996
+ fig = go.Figure(
997
+ layout=dict(
998
+ template=template,
999
+ title=f"Reddit Mentions · {ticker}",
1000
+ xaxis_title="Date",
1001
+ hovermode="x unified",
1002
+ )
1003
+ )
1004
+
1005
+ # Plot price line on primary y-axis
1006
+ fig.add_scatter(
1007
+ name="Price",
1008
+ x=price_data_normalized.index,
1009
+ y=price_data_normalized[price_col],
1010
+ mode="lines",
1011
+ line=dict(width=2, color="#02d2ff"),
1012
+ hovertemplate="<b>%{x|%Y-%m-%d %H:%M}</b><br>Price: $%{y:.2f}<extra></extra>",
1013
+ )
1014
+
1015
+ if not mentions_df.empty:
1016
+ mentions_normalized = mentions_df.copy()
1017
+ if mentions_normalized.index.tz is not None:
1018
+ mentions_normalized.index = mentions_normalized.index.tz_localize(None)
1019
+
1020
+ # Exclude _all (aggregate) — use individual subreddits for stacked bars
1021
+ per_sub = mentions_normalized[
1022
+ mentions_normalized["subreddit"] != "_all"
1023
+ ]
1024
+
1025
+ if not per_sub.empty:
1026
+ for subreddit in sorted(per_sub["subreddit"].unique()):
1027
+ sub_data = per_sub[per_sub["subreddit"] == subreddit]
1028
+ fig.add_bar(
1029
+ name=f"r/{subreddit}",
1030
+ x=sub_data.index,
1031
+ y=sub_data["mentions"],
1032
+ yaxis="y2",
1033
+ hovertemplate=(
1034
+ "<b>%{x|%Y-%m-%d %H:%M}</b><br>"
1035
+ f"r/{subreddit}: " + "%{y:,}<extra></extra>"
1036
+ ),
1037
+ )
1038
+
1039
+ fig.update_layout(
1040
+ barmode="stack",
1041
+ yaxis=dict(title="Price", showgrid=True),
1042
+ yaxis2=dict(
1043
+ title="Mentions",
1044
+ overlaying="y",
1045
+ side="right",
1046
+ showgrid=False,
1047
+ zeroline=False,
1048
+ rangemode="tozero",
1049
+ ),
1050
+ )
1051
+
1052
+ if show and not as_json:
1053
+ fig.show()
1054
+ return None
1055
+ return fig.to_json() if as_json else fig
1056
+
1057
+ # --------------------------------------------------------------------- #
1058
+ # Reddit Mentions Screener → stacked horizontal bars (top N tickers) #
1059
+ # --------------------------------------------------------------------- #
1060
+ def reddit_mentions_top(
1061
+ self,
1062
+ *,
1063
+ top_n: int = 15,
1064
+ market: str | None = None,
1065
+ region: str | None = None,
1066
+ limit: int | None = None,
1067
+ as_json: bool = False,
1068
+ show: bool = True,
1069
+ template: str = "plotly_dark",
1070
+ **kwargs,
1071
+ ):
1072
+ """
1073
+ Plot a stacked horizontal bar chart of the most mentioned tickers
1074
+ from the latest Reddit mentions screener snapshot.
1075
+
1076
+ Parameters
1077
+ ----------
1078
+ top_n : int, default 15
1079
+ Number of top-mentioned tickers to display.
1080
+ market : str or None, optional
1081
+ Filter by market name (e.g. ``"S&P 500"``).
1082
+ region : str or None, optional
1083
+ Filter by region (e.g. ``"US"``).
1084
+ limit : int or None, optional
1085
+ Maximum records to fetch from the screener API.
1086
+ as_json : bool, default False
1087
+ If ``True``, return JSON string instead of Figure object.
1088
+ show : bool, default True
1089
+ If ``True`` and ``as_json=False``, display the figure immediately.
1090
+ template : str, default "plotly_dark"
1091
+ Plotly template name.
1092
+ **kwargs
1093
+ Additional arguments passed to
1094
+ :meth:`FinBrainClient.screener.reddit_mentions`.
1095
+
1096
+ Returns
1097
+ -------
1098
+ plotly.graph_objects.Figure or str or None
1099
+ """
1100
+ data = self._fb.screener.reddit_mentions(
1101
+ market=market,
1102
+ region=region,
1103
+ limit=limit,
1104
+ **kwargs,
1105
+ )
1106
+
1107
+ rows = data if isinstance(data, list) else data.get("data", [])
1108
+ if not rows:
1109
+ raise ValueError("No screener data returned")
1110
+
1111
+ # Keep only the latest snapshot per ticker
1112
+ latest: dict[str, dict] = {}
1113
+ for row in rows:
1114
+ sym = row["symbol"]
1115
+ if sym not in latest or row["date"] > latest[sym]["date"]:
1116
+ latest[sym] = row
1117
+
1118
+ # Sort by totalMentions descending, take top N
1119
+ ranked = sorted(latest.values(), key=lambda r: r.get("totalMentions", 0), reverse=True)
1120
+ top = ranked[:top_n]
1121
+
1122
+ # Reverse so the highest-mentioned ticker is at the top of the chart
1123
+ top = list(reversed(top))
1124
+
1125
+ symbols = [r["symbol"] for r in top]
1126
+
1127
+ # Collect all subreddit names across top tickers
1128
+ all_subs: set[str] = set()
1129
+ for r in top:
1130
+ all_subs.update(r.get("subreddits", {}).keys())
1131
+
1132
+ fig = go.Figure(
1133
+ layout=dict(
1134
+ template=template,
1135
+ title=f"Reddit Mentions · Top {len(top)} Tickers",
1136
+ xaxis_title="Mentions",
1137
+ hovermode="y unified",
1138
+ barmode="stack",
1139
+ )
1140
+ )
1141
+
1142
+ for subreddit in sorted(all_subs):
1143
+ values = [r.get("subreddits", {}).get(subreddit, 0) for r in top]
1144
+ fig.add_bar(
1145
+ name=f"r/{subreddit}",
1146
+ y=symbols,
1147
+ x=values,
1148
+ orientation="h",
1149
+ hovertemplate=f"r/{subreddit}: " + "%{x:,}<extra></extra>",
1150
+ )
1151
+
1152
+ if show and not as_json:
1153
+ fig.show()
1154
+ return None
1155
+ return fig.to_json() if as_json else fig
1156
+
910
1157
  # --------------------------------------------------------------------- #
911
1158
  # Helper methods #
912
1159
  # --------------------------------------------------------------------- #
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: finbrain-python
3
- Version: 0.2.1
3
+ Version: 0.2.3
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
@@ -99,6 +99,12 @@ fb.corporate_lobbying.ticker("AAPL",
99
99
  date_to="2025-06-30",
100
100
  as_dataframe=True)
101
101
 
102
+ # ---------- reddit mentions ----------
103
+ fb.reddit_mentions.ticker("TSLA",
104
+ date_from="2026-03-01",
105
+ date_to="2026-03-17",
106
+ as_dataframe=True)
107
+
102
108
  # ---------- insider transactions ----------
103
109
  fb.insider_transactions.ticker("AMZN", as_dataframe=True)
104
110
 
@@ -130,6 +136,7 @@ fb.news.ticker("AMZN", limit=20, as_dataframe=True)
130
136
  fb.screener.sentiment(market="S&P 500", as_dataframe=True)
131
137
  fb.screener.predictions_daily(limit=100, as_dataframe=True)
132
138
  fb.screener.insider_trading(limit=50)
139
+ fb.screener.reddit_mentions(limit=100, as_dataframe=True)
133
140
 
134
141
  # ---------- recent data ----------
135
142
  fb.recent.news(limit=100, as_dataframe=True)
@@ -244,6 +251,21 @@ fb.plot.corporate_lobbying("AAPL",
244
251
  price_data=price_df,
245
252
  date_from="2024-01-01",
246
253
  date_to="2025-06-30")
254
+
255
+ # Plot Reddit mentions (stacked bars per subreddit) on your price chart
256
+ fb.plot.reddit_mentions("TSLA",
257
+ price_data=price_df,
258
+ date_from="2026-03-01",
259
+ date_to="2026-03-17")
260
+ ```
261
+
262
+ ```python
263
+ # ---------- Reddit Mentions Screener Chart (no price data needed) ----------
264
+ # Stacked horizontal bar chart of top 15 most mentioned tickers
265
+ fb.plot.reddit_mentions_top(market="S&P 500")
266
+
267
+ # Customize the number of tickers shown
268
+ fb.plot.reddit_mentions_top(top_n=10, region="US")
247
269
  ```
248
270
 
249
271
  **Price Data Requirements:**
@@ -289,12 +311,14 @@ fb = FinBrainClient() # reads from FINBRAIN_API_KEY env var
289
311
  | House trades | `client.house_trades.ticker()` | `/congress/house/{SYMBOL}` |
290
312
  | Senate trades | `client.senate_trades.ticker()` | `/congress/senate/{SYMBOL}` |
291
313
  | Corporate lobbying | `client.corporate_lobbying.ticker()` | `/lobbying/{SYMBOL}` |
314
+ | Reddit mentions | `client.reddit_mentions.ticker()` | `/reddit-mentions/{SYMBOL}` |
292
315
  | Insider transactions | `client.insider_transactions.ticker()` | `/insider-trading/{SYMBOL}` |
293
316
  | LinkedIn | `client.linkedin_data.ticker()` | `/linkedin/{SYMBOL}` |
294
317
  | Options – Put/Call | `client.options.put_call()` | `/put-call-ratio/{SYMBOL}` |
295
318
  | Screener | `client.screener.sentiment()` | `/screener/sentiment` |
296
319
  | | `client.screener.predictions_daily()` | `/screener/predictions/daily` |
297
320
  | | `client.screener.insider_trading()` | `/screener/insider-trading` |
321
+ | | `client.screener.reddit_mentions()` | `/screener/reddit-mentions` |
298
322
  | | ... and 8 more screener methods | |
299
323
  | Recent | `client.recent.news()` | `/recent/news` |
300
324
  | | `client.recent.analyst_ratings()` | `/recent/analyst-ratings` |