finbrain-python 0.2.1__tar.gz → 0.2.2__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.2}/CHANGELOG.md +10 -0
  2. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/PKG-INFO +27 -1
  3. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/README.md +26 -0
  4. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/client.py +2 -0
  5. finbrain_python-0.2.2/src/finbrain/aio/endpoints/reddit_mentions.py +79 -0
  6. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/client.py +2 -0
  7. finbrain_python-0.2.2/src/finbrain/endpoints/reddit_mentions.py +123 -0
  8. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/plotting.py +247 -0
  9. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain_python.egg-info/PKG-INFO +27 -1
  10. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain_python.egg-info/SOURCES.txt +3 -0
  11. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_integration.py +48 -0
  12. finbrain_python-0.2.2/tests/test_plotting.py +554 -0
  13. finbrain_python-0.2.2/tests/test_reddit_mentions.py +168 -0
  14. finbrain_python-0.2.1/tests/test_plotting.py +0 -134
  15. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/.gitattributes +0 -0
  16. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/.github/workflows/ci.yml +0 -0
  17. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/.github/workflows/release.yml +0 -0
  18. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/.gitignore +0 -0
  19. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/LICENSE +0 -0
  20. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/RELEASE.md +0 -0
  21. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/examples/async_example.py +0 -0
  22. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/examples/transactions_plotting_example.py +0 -0
  23. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/pyproject.toml +0 -0
  24. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/setup.cfg +0 -0
  25. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/__init__.py +0 -0
  26. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/__init__.py +0 -0
  27. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/__init__.py +0 -0
  28. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/_utils.py +0 -0
  29. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/analyst_ratings.py +0 -0
  30. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/app_ratings.py +0 -0
  31. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/available.py +0 -0
  32. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/corporate_lobbying.py +0 -0
  33. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/house_trades.py +0 -0
  34. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/insider_transactions.py +0 -0
  35. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/linkedin_data.py +0 -0
  36. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/news.py +0 -0
  37. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/options.py +0 -0
  38. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/predictions.py +0 -0
  39. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/recent.py +0 -0
  40. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/screener.py +0 -0
  41. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/senate_trades.py +0 -0
  42. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/sentiments.py +0 -0
  43. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/__init__.py +0 -0
  44. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/_utils.py +0 -0
  45. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/analyst_ratings.py +0 -0
  46. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/app_ratings.py +0 -0
  47. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/available.py +0 -0
  48. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/corporate_lobbying.py +0 -0
  49. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/house_trades.py +0 -0
  50. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/insider_transactions.py +0 -0
  51. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/linkedin_data.py +0 -0
  52. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/news.py +0 -0
  53. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/options.py +0 -0
  54. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/predictions.py +0 -0
  55. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/recent.py +0 -0
  56. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/screener.py +0 -0
  57. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/senate_trades.py +0 -0
  58. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/sentiments.py +0 -0
  59. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/exceptions.py +0 -0
  60. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/py.typed +0 -0
  61. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain_python.egg-info/dependency_links.txt +0 -0
  62. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain_python.egg-info/requires.txt +0 -0
  63. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain_python.egg-info/top_level.txt +0 -0
  64. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/__init__.py +0 -0
  65. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/conftest.py +0 -0
  66. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_analyst_ratings.py +0 -0
  67. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_app_ratings.py +0 -0
  68. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_async_client.py +0 -0
  69. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_available.py +0 -0
  70. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_corporate_lobbying.py +0 -0
  71. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_envelope.py +0 -0
  72. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_house_trades.py +0 -0
  73. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_insider_transactions.py +0 -0
  74. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_linkedin_data.py +0 -0
  75. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_news.py +0 -0
  76. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_options.py +0 -0
  77. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_predictions.py +0 -0
  78. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_recent.py +0 -0
  79. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_screener.py +0 -0
  80. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_senate_trades.py +0 -0
  81. {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_sentiments.py +0 -0
@@ -5,6 +5,16 @@ 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.2] - 2026-03-17
9
+
10
+ ### Added
11
+
12
+ - **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
15
+ - **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
17
+
8
18
  ## [0.2.1] - 2026-03-12
9
19
 
10
20
  ### 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.2
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,15 @@ 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
+
108
+ # screener — cross-ticker Reddit mentions
109
+ fb.reddit_mentions.screener(market="S&P 500", limit=100, as_dataframe=True)
110
+
102
111
  # ---------- insider transactions ----------
103
112
  fb.insider_transactions.ticker("AMZN", as_dataframe=True)
104
113
 
@@ -244,6 +253,21 @@ fb.plot.corporate_lobbying("AAPL",
244
253
  price_data=price_df,
245
254
  date_from="2024-01-01",
246
255
  date_to="2025-06-30")
256
+
257
+ # Plot Reddit mentions (stacked bars per subreddit) on your price chart
258
+ fb.plot.reddit_mentions("TSLA",
259
+ price_data=price_df,
260
+ date_from="2026-03-01",
261
+ date_to="2026-03-17")
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_screener(market="S&P 500")
268
+
269
+ # Customize the number of tickers shown
270
+ fb.plot.reddit_mentions_screener(top_n=10, region="US")
247
271
  ```
248
272
 
249
273
  **Price Data Requirements:**
@@ -289,6 +313,8 @@ fb = FinBrainClient() # reads from FINBRAIN_API_KEY env var
289
313
  | House trades | `client.house_trades.ticker()` | `/congress/house/{SYMBOL}` |
290
314
  | Senate trades | `client.senate_trades.ticker()` | `/congress/senate/{SYMBOL}` |
291
315
  | Corporate lobbying | `client.corporate_lobbying.ticker()` | `/lobbying/{SYMBOL}` |
316
+ | Reddit mentions | `client.reddit_mentions.ticker()` | `/reddit-mentions/{SYMBOL}` |
317
+ | | `client.reddit_mentions.screener()` | `/screener/reddit-mentions` |
292
318
  | Insider transactions | `client.insider_transactions.ticker()` | `/insider-trading/{SYMBOL}` |
293
319
  | LinkedIn | `client.linkedin_data.ticker()` | `/linkedin/{SYMBOL}` |
294
320
  | Options – Put/Call | `client.options.put_call()` | `/put-call-ratio/{SYMBOL}` |
@@ -73,6 +73,15 @@ 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
+ # screener — cross-ticker Reddit mentions
83
+ fb.reddit_mentions.screener(market="S&P 500", limit=100, as_dataframe=True)
84
+
76
85
  # ---------- insider transactions ----------
77
86
  fb.insider_transactions.ticker("AMZN", as_dataframe=True)
78
87
 
@@ -218,6 +227,21 @@ fb.plot.corporate_lobbying("AAPL",
218
227
  price_data=price_df,
219
228
  date_from="2024-01-01",
220
229
  date_to="2025-06-30")
230
+
231
+ # Plot Reddit mentions (stacked bars per subreddit) on your price chart
232
+ fb.plot.reddit_mentions("TSLA",
233
+ price_data=price_df,
234
+ date_from="2026-03-01",
235
+ date_to="2026-03-17")
236
+ ```
237
+
238
+ ```python
239
+ # ---------- Reddit Mentions Screener Chart (no price data needed) ----------
240
+ # Stacked horizontal bar chart of top 15 most mentioned tickers
241
+ fb.plot.reddit_mentions_screener(market="S&P 500")
242
+
243
+ # Customize the number of tickers shown
244
+ fb.plot.reddit_mentions_screener(top_n=10, region="US")
221
245
  ```
222
246
 
223
247
  **Price Data Requirements:**
@@ -263,6 +287,8 @@ fb = FinBrainClient() # reads from FINBRAIN_API_KEY env var
263
287
  | House trades | `client.house_trades.ticker()` | `/congress/house/{SYMBOL}` |
264
288
  | Senate trades | `client.senate_trades.ticker()` | `/congress/senate/{SYMBOL}` |
265
289
  | Corporate lobbying | `client.corporate_lobbying.ticker()` | `/lobbying/{SYMBOL}` |
290
+ | Reddit mentions | `client.reddit_mentions.ticker()` | `/reddit-mentions/{SYMBOL}` |
291
+ | | `client.reddit_mentions.screener()` | `/screener/reddit-mentions` |
266
292
  | Insider transactions | `client.insider_transactions.ticker()` | `/insider-trading/{SYMBOL}` |
267
293
  | LinkedIn | `client.linkedin_data.ticker()` | `/linkedin/{SYMBOL}` |
268
294
  | Options – Put/Call | `client.options.put_call()` | `/put-call-ratio/{SYMBOL}` |
@@ -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,79 @@
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
49
+
50
+ async def screener(
51
+ self,
52
+ *,
53
+ limit: int | None = None,
54
+ market: str | None = None,
55
+ region: str | None = None,
56
+ as_dataframe: bool = False,
57
+ ) -> Dict[str, Any] | pd.DataFrame:
58
+ """Get recent Reddit mention counts across all tickers (async)."""
59
+ params: Dict[str, str] = {}
60
+ if limit is not None:
61
+ params["limit"] = str(limit)
62
+ if market:
63
+ params["market"] = market
64
+ if region:
65
+ params["region"] = region
66
+
67
+ data: Dict[str, Any] = await self._c._request(
68
+ "GET", "screener/reddit-mentions", params=params
69
+ )
70
+
71
+ if as_dataframe:
72
+ rows: List[Dict[str, Any]] = data.get("data", [])
73
+ df = pd.DataFrame(rows)
74
+ if not df.empty and "date" in df.columns:
75
+ df["date"] = pd.to_datetime(df["date"])
76
+ df.set_index("date", inplace=True)
77
+ return df
78
+
79
+ return data
@@ -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,123 @@
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
74
+
75
+ # ------------------------------------------------------------------ #
76
+ def screener(
77
+ self,
78
+ *,
79
+ limit: int | None = None,
80
+ market: str | None = None,
81
+ region: str | None = None,
82
+ as_dataframe: bool = False,
83
+ ) -> Dict[str, Any] | pd.DataFrame:
84
+ """
85
+ Get recent Reddit mention counts across all tickers.
86
+
87
+ Parameters
88
+ ----------
89
+ limit :
90
+ Maximum number of records to return (1-20000).
91
+ market :
92
+ Filter by market name (e.g. ``"S&P 500"``, ``"NASDAQ"``).
93
+ region :
94
+ Filter by region (e.g. ``"US"``, ``"Europe"``).
95
+ as_dataframe :
96
+ If *True*, return a **pandas.DataFrame** indexed by ``date``;
97
+ otherwise return the raw JSON dict.
98
+
99
+ Returns
100
+ -------
101
+ dict | pandas.DataFrame
102
+ """
103
+ params: Dict[str, str] = {}
104
+ if limit is not None:
105
+ params["limit"] = str(limit)
106
+ if market:
107
+ params["market"] = market
108
+ if region:
109
+ params["region"] = region
110
+
111
+ data: Dict[str, Any] = self._c._request(
112
+ "GET", "screener/reddit-mentions", params=params
113
+ )
114
+
115
+ if as_dataframe:
116
+ rows: List[Dict[str, Any]] = data.get("data", [])
117
+ df = pd.DataFrame(rows)
118
+ if not df.empty and "date" in df.columns:
119
+ df["date"] = pd.to_datetime(df["date"])
120
+ df.set_index("date", inplace=True)
121
+ return df
122
+
123
+ return data
@@ -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_screener(
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.reddit_mentions.screener`.
1095
+
1096
+ Returns
1097
+ -------
1098
+ plotly.graph_objects.Figure or str or None
1099
+ """
1100
+ data = self._fb.reddit_mentions.screener(
1101
+ market=market,
1102
+ region=region,
1103
+ limit=limit,
1104
+ **kwargs,
1105
+ )
1106
+
1107
+ rows = 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.2
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,15 @@ 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
+
108
+ # screener — cross-ticker Reddit mentions
109
+ fb.reddit_mentions.screener(market="S&P 500", limit=100, as_dataframe=True)
110
+
102
111
  # ---------- insider transactions ----------
103
112
  fb.insider_transactions.ticker("AMZN", as_dataframe=True)
104
113
 
@@ -244,6 +253,21 @@ fb.plot.corporate_lobbying("AAPL",
244
253
  price_data=price_df,
245
254
  date_from="2024-01-01",
246
255
  date_to="2025-06-30")
256
+
257
+ # Plot Reddit mentions (stacked bars per subreddit) on your price chart
258
+ fb.plot.reddit_mentions("TSLA",
259
+ price_data=price_df,
260
+ date_from="2026-03-01",
261
+ date_to="2026-03-17")
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_screener(market="S&P 500")
268
+
269
+ # Customize the number of tickers shown
270
+ fb.plot.reddit_mentions_screener(top_n=10, region="US")
247
271
  ```
248
272
 
249
273
  **Price Data Requirements:**
@@ -289,6 +313,8 @@ fb = FinBrainClient() # reads from FINBRAIN_API_KEY env var
289
313
  | House trades | `client.house_trades.ticker()` | `/congress/house/{SYMBOL}` |
290
314
  | Senate trades | `client.senate_trades.ticker()` | `/congress/senate/{SYMBOL}` |
291
315
  | Corporate lobbying | `client.corporate_lobbying.ticker()` | `/lobbying/{SYMBOL}` |
316
+ | Reddit mentions | `client.reddit_mentions.ticker()` | `/reddit-mentions/{SYMBOL}` |
317
+ | | `client.reddit_mentions.screener()` | `/screener/reddit-mentions` |
292
318
  | Insider transactions | `client.insider_transactions.ticker()` | `/insider-trading/{SYMBOL}` |
293
319
  | LinkedIn | `client.linkedin_data.ticker()` | `/linkedin/{SYMBOL}` |
294
320
  | Options – Put/Call | `client.options.put_call()` | `/put-call-ratio/{SYMBOL}` |
@@ -29,6 +29,7 @@ src/finbrain/aio/endpoints/news.py
29
29
  src/finbrain/aio/endpoints/options.py
30
30
  src/finbrain/aio/endpoints/predictions.py
31
31
  src/finbrain/aio/endpoints/recent.py
32
+ src/finbrain/aio/endpoints/reddit_mentions.py
32
33
  src/finbrain/aio/endpoints/screener.py
33
34
  src/finbrain/aio/endpoints/senate_trades.py
34
35
  src/finbrain/aio/endpoints/sentiments.py
@@ -45,6 +46,7 @@ src/finbrain/endpoints/news.py
45
46
  src/finbrain/endpoints/options.py
46
47
  src/finbrain/endpoints/predictions.py
47
48
  src/finbrain/endpoints/recent.py
49
+ src/finbrain/endpoints/reddit_mentions.py
48
50
  src/finbrain/endpoints/screener.py
49
51
  src/finbrain/endpoints/senate_trades.py
50
52
  src/finbrain/endpoints/sentiments.py
@@ -70,6 +72,7 @@ tests/test_options.py
70
72
  tests/test_plotting.py
71
73
  tests/test_predictions.py
72
74
  tests/test_recent.py
75
+ tests/test_reddit_mentions.py
73
76
  tests/test_screener.py
74
77
  tests/test_senate_trades.py
75
78
  tests/test_sentiments.py