finbrain-python 0.2.2__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 (82) hide show
  1. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/CHANGELOG.md +11 -3
  2. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/PKG-INFO +5 -7
  3. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/README.md +4 -6
  4. finbrain_python-0.2.3/src/finbrain/aio/endpoints/reddit_mentions.py +48 -0
  5. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/screener.py +14 -0
  6. finbrain_python-0.2.3/src/finbrain/endpoints/reddit_mentions.py +73 -0
  7. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/endpoints/screener.py +14 -0
  8. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/plotting.py +4 -4
  9. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain_python.egg-info/PKG-INFO +5 -7
  10. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/tests/test_integration.py +5 -8
  11. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/tests/test_plotting.py +32 -37
  12. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/tests/test_reddit_mentions.py +6 -12
  13. finbrain_python-0.2.2/src/finbrain/aio/endpoints/reddit_mentions.py +0 -79
  14. finbrain_python-0.2.2/src/finbrain/endpoints/reddit_mentions.py +0 -123
  15. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/.gitattributes +0 -0
  16. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/.github/workflows/ci.yml +0 -0
  17. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/.github/workflows/release.yml +0 -0
  18. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/.gitignore +0 -0
  19. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/LICENSE +0 -0
  20. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/RELEASE.md +0 -0
  21. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/examples/async_example.py +0 -0
  22. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/examples/transactions_plotting_example.py +0 -0
  23. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/pyproject.toml +0 -0
  24. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/setup.cfg +0 -0
  25. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/__init__.py +0 -0
  26. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/aio/__init__.py +0 -0
  27. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/aio/client.py +0 -0
  28. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/__init__.py +0 -0
  29. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/_utils.py +0 -0
  30. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/analyst_ratings.py +0 -0
  31. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/app_ratings.py +0 -0
  32. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/available.py +0 -0
  33. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/corporate_lobbying.py +0 -0
  34. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/house_trades.py +0 -0
  35. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/insider_transactions.py +0 -0
  36. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/linkedin_data.py +0 -0
  37. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/news.py +0 -0
  38. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/options.py +0 -0
  39. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/predictions.py +0 -0
  40. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/recent.py +0 -0
  41. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/senate_trades.py +0 -0
  42. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/aio/endpoints/sentiments.py +0 -0
  43. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/client.py +0 -0
  44. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/endpoints/__init__.py +0 -0
  45. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/endpoints/_utils.py +0 -0
  46. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/endpoints/analyst_ratings.py +0 -0
  47. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/endpoints/app_ratings.py +0 -0
  48. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/endpoints/available.py +0 -0
  49. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/endpoints/corporate_lobbying.py +0 -0
  50. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/endpoints/house_trades.py +0 -0
  51. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/endpoints/insider_transactions.py +0 -0
  52. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/endpoints/linkedin_data.py +0 -0
  53. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/endpoints/news.py +0 -0
  54. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/endpoints/options.py +0 -0
  55. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/endpoints/predictions.py +0 -0
  56. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/endpoints/recent.py +0 -0
  57. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/endpoints/senate_trades.py +0 -0
  58. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/endpoints/sentiments.py +0 -0
  59. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/exceptions.py +0 -0
  60. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain/py.typed +0 -0
  61. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain_python.egg-info/SOURCES.txt +0 -0
  62. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain_python.egg-info/dependency_links.txt +0 -0
  63. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain_python.egg-info/requires.txt +0 -0
  64. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/src/finbrain_python.egg-info/top_level.txt +0 -0
  65. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/tests/__init__.py +0 -0
  66. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/tests/conftest.py +0 -0
  67. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/tests/test_analyst_ratings.py +0 -0
  68. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/tests/test_app_ratings.py +0 -0
  69. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/tests/test_async_client.py +0 -0
  70. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/tests/test_available.py +0 -0
  71. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/tests/test_corporate_lobbying.py +0 -0
  72. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/tests/test_envelope.py +0 -0
  73. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/tests/test_house_trades.py +0 -0
  74. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/tests/test_insider_transactions.py +0 -0
  75. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/tests/test_linkedin_data.py +0 -0
  76. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/tests/test_news.py +0 -0
  77. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/tests/test_options.py +0 -0
  78. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/tests/test_predictions.py +0 -0
  79. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/tests/test_recent.py +0 -0
  80. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/tests/test_screener.py +0 -0
  81. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/tests/test_senate_trades.py +0 -0
  82. {finbrain_python-0.2.2 → finbrain_python-0.2.3}/tests/test_sentiments.py +0 -0
@@ -5,15 +5,23 @@ 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
+
8
15
  ## [0.2.2] - 2026-03-17
9
16
 
10
17
  ### Added
11
18
 
12
19
  - **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
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
15
22
  - **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
23
+ - **Reddit Mentions Tests**: Unit tests, integration tests, and functional plotting tests
24
+ - **Plotting Test Coverage**: Added functional tests for all 11 plotting methods
17
25
 
18
26
  ## [0.2.1] - 2026-03-12
19
27
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: finbrain-python
3
- Version: 0.2.2
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
@@ -105,9 +105,6 @@ fb.reddit_mentions.ticker("TSLA",
105
105
  date_to="2026-03-17",
106
106
  as_dataframe=True)
107
107
 
108
- # screener — cross-ticker Reddit mentions
109
- fb.reddit_mentions.screener(market="S&P 500", limit=100, as_dataframe=True)
110
-
111
108
  # ---------- insider transactions ----------
112
109
  fb.insider_transactions.ticker("AMZN", as_dataframe=True)
113
110
 
@@ -139,6 +136,7 @@ fb.news.ticker("AMZN", limit=20, as_dataframe=True)
139
136
  fb.screener.sentiment(market="S&P 500", as_dataframe=True)
140
137
  fb.screener.predictions_daily(limit=100, as_dataframe=True)
141
138
  fb.screener.insider_trading(limit=50)
139
+ fb.screener.reddit_mentions(limit=100, as_dataframe=True)
142
140
 
143
141
  # ---------- recent data ----------
144
142
  fb.recent.news(limit=100, as_dataframe=True)
@@ -264,10 +262,10 @@ fb.plot.reddit_mentions("TSLA",
264
262
  ```python
265
263
  # ---------- Reddit Mentions Screener Chart (no price data needed) ----------
266
264
  # Stacked horizontal bar chart of top 15 most mentioned tickers
267
- fb.plot.reddit_mentions_screener(market="S&P 500")
265
+ fb.plot.reddit_mentions_top(market="S&P 500")
268
266
 
269
267
  # Customize the number of tickers shown
270
- fb.plot.reddit_mentions_screener(top_n=10, region="US")
268
+ fb.plot.reddit_mentions_top(top_n=10, region="US")
271
269
  ```
272
270
 
273
271
  **Price Data Requirements:**
@@ -314,13 +312,13 @@ fb = FinBrainClient() # reads from FINBRAIN_API_KEY env var
314
312
  | Senate trades | `client.senate_trades.ticker()` | `/congress/senate/{SYMBOL}` |
315
313
  | Corporate lobbying | `client.corporate_lobbying.ticker()` | `/lobbying/{SYMBOL}` |
316
314
  | Reddit mentions | `client.reddit_mentions.ticker()` | `/reddit-mentions/{SYMBOL}` |
317
- | | `client.reddit_mentions.screener()` | `/screener/reddit-mentions` |
318
315
  | Insider transactions | `client.insider_transactions.ticker()` | `/insider-trading/{SYMBOL}` |
319
316
  | LinkedIn | `client.linkedin_data.ticker()` | `/linkedin/{SYMBOL}` |
320
317
  | Options – Put/Call | `client.options.put_call()` | `/put-call-ratio/{SYMBOL}` |
321
318
  | Screener | `client.screener.sentiment()` | `/screener/sentiment` |
322
319
  | | `client.screener.predictions_daily()` | `/screener/predictions/daily` |
323
320
  | | `client.screener.insider_trading()` | `/screener/insider-trading` |
321
+ | | `client.screener.reddit_mentions()` | `/screener/reddit-mentions` |
324
322
  | | ... and 8 more screener methods | |
325
323
  | Recent | `client.recent.news()` | `/recent/news` |
326
324
  | | `client.recent.analyst_ratings()` | `/recent/analyst-ratings` |
@@ -79,9 +79,6 @@ fb.reddit_mentions.ticker("TSLA",
79
79
  date_to="2026-03-17",
80
80
  as_dataframe=True)
81
81
 
82
- # screener — cross-ticker Reddit mentions
83
- fb.reddit_mentions.screener(market="S&P 500", limit=100, as_dataframe=True)
84
-
85
82
  # ---------- insider transactions ----------
86
83
  fb.insider_transactions.ticker("AMZN", as_dataframe=True)
87
84
 
@@ -113,6 +110,7 @@ fb.news.ticker("AMZN", limit=20, as_dataframe=True)
113
110
  fb.screener.sentiment(market="S&P 500", as_dataframe=True)
114
111
  fb.screener.predictions_daily(limit=100, as_dataframe=True)
115
112
  fb.screener.insider_trading(limit=50)
113
+ fb.screener.reddit_mentions(limit=100, as_dataframe=True)
116
114
 
117
115
  # ---------- recent data ----------
118
116
  fb.recent.news(limit=100, as_dataframe=True)
@@ -238,10 +236,10 @@ fb.plot.reddit_mentions("TSLA",
238
236
  ```python
239
237
  # ---------- Reddit Mentions Screener Chart (no price data needed) ----------
240
238
  # Stacked horizontal bar chart of top 15 most mentioned tickers
241
- fb.plot.reddit_mentions_screener(market="S&P 500")
239
+ fb.plot.reddit_mentions_top(market="S&P 500")
242
240
 
243
241
  # Customize the number of tickers shown
244
- fb.plot.reddit_mentions_screener(top_n=10, region="US")
242
+ fb.plot.reddit_mentions_top(top_n=10, region="US")
245
243
  ```
246
244
 
247
245
  **Price Data Requirements:**
@@ -288,13 +286,13 @@ fb = FinBrainClient() # reads from FINBRAIN_API_KEY env var
288
286
  | Senate trades | `client.senate_trades.ticker()` | `/congress/senate/{SYMBOL}` |
289
287
  | Corporate lobbying | `client.corporate_lobbying.ticker()` | `/lobbying/{SYMBOL}` |
290
288
  | Reddit mentions | `client.reddit_mentions.ticker()` | `/reddit-mentions/{SYMBOL}` |
291
- | | `client.reddit_mentions.screener()` | `/screener/reddit-mentions` |
292
289
  | Insider transactions | `client.insider_transactions.ticker()` | `/insider-trading/{SYMBOL}` |
293
290
  | LinkedIn | `client.linkedin_data.ticker()` | `/linkedin/{SYMBOL}` |
294
291
  | Options – Put/Call | `client.options.put_call()` | `/put-call-ratio/{SYMBOL}` |
295
292
  | Screener | `client.screener.sentiment()` | `/screener/sentiment` |
296
293
  | | `client.screener.predictions_daily()` | `/screener/predictions/daily` |
297
294
  | | `client.screener.insider_trading()` | `/screener/insider-trading` |
295
+ | | `client.screener.reddit_mentions()` | `/screener/reddit-mentions` |
298
296
  | | ... and 8 more screener methods | |
299
297
  | Recent | `client.recent.news()` | `/recent/news` |
300
298
  | | `client.recent.analyst_ratings()` | `/recent/analyst-ratings` |
@@ -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)
@@ -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)
@@ -1057,7 +1057,7 @@ class _PlotNamespace:
1057
1057
  # --------------------------------------------------------------------- #
1058
1058
  # Reddit Mentions Screener → stacked horizontal bars (top N tickers) #
1059
1059
  # --------------------------------------------------------------------- #
1060
- def reddit_mentions_screener(
1060
+ def reddit_mentions_top(
1061
1061
  self,
1062
1062
  *,
1063
1063
  top_n: int = 15,
@@ -1091,20 +1091,20 @@ class _PlotNamespace:
1091
1091
  Plotly template name.
1092
1092
  **kwargs
1093
1093
  Additional arguments passed to
1094
- :meth:`FinBrainClient.reddit_mentions.screener`.
1094
+ :meth:`FinBrainClient.screener.reddit_mentions`.
1095
1095
 
1096
1096
  Returns
1097
1097
  -------
1098
1098
  plotly.graph_objects.Figure or str or None
1099
1099
  """
1100
- data = self._fb.reddit_mentions.screener(
1100
+ data = self._fb.screener.reddit_mentions(
1101
1101
  market=market,
1102
1102
  region=region,
1103
1103
  limit=limit,
1104
1104
  **kwargs,
1105
1105
  )
1106
1106
 
1107
- rows = data.get("data", [])
1107
+ rows = data if isinstance(data, list) else data.get("data", [])
1108
1108
  if not rows:
1109
1109
  raise ValueError("No screener data returned")
1110
1110
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: finbrain-python
3
- Version: 0.2.2
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
@@ -105,9 +105,6 @@ fb.reddit_mentions.ticker("TSLA",
105
105
  date_to="2026-03-17",
106
106
  as_dataframe=True)
107
107
 
108
- # screener — cross-ticker Reddit mentions
109
- fb.reddit_mentions.screener(market="S&P 500", limit=100, as_dataframe=True)
110
-
111
108
  # ---------- insider transactions ----------
112
109
  fb.insider_transactions.ticker("AMZN", as_dataframe=True)
113
110
 
@@ -139,6 +136,7 @@ fb.news.ticker("AMZN", limit=20, as_dataframe=True)
139
136
  fb.screener.sentiment(market="S&P 500", as_dataframe=True)
140
137
  fb.screener.predictions_daily(limit=100, as_dataframe=True)
141
138
  fb.screener.insider_trading(limit=50)
139
+ fb.screener.reddit_mentions(limit=100, as_dataframe=True)
142
140
 
143
141
  # ---------- recent data ----------
144
142
  fb.recent.news(limit=100, as_dataframe=True)
@@ -264,10 +262,10 @@ fb.plot.reddit_mentions("TSLA",
264
262
  ```python
265
263
  # ---------- Reddit Mentions Screener Chart (no price data needed) ----------
266
264
  # Stacked horizontal bar chart of top 15 most mentioned tickers
267
- fb.plot.reddit_mentions_screener(market="S&P 500")
265
+ fb.plot.reddit_mentions_top(market="S&P 500")
268
266
 
269
267
  # Customize the number of tickers shown
270
- fb.plot.reddit_mentions_screener(top_n=10, region="US")
268
+ fb.plot.reddit_mentions_top(top_n=10, region="US")
271
269
  ```
272
270
 
273
271
  **Price Data Requirements:**
@@ -314,13 +312,13 @@ fb = FinBrainClient() # reads from FINBRAIN_API_KEY env var
314
312
  | Senate trades | `client.senate_trades.ticker()` | `/congress/senate/{SYMBOL}` |
315
313
  | Corporate lobbying | `client.corporate_lobbying.ticker()` | `/lobbying/{SYMBOL}` |
316
314
  | Reddit mentions | `client.reddit_mentions.ticker()` | `/reddit-mentions/{SYMBOL}` |
317
- | | `client.reddit_mentions.screener()` | `/screener/reddit-mentions` |
318
315
  | Insider transactions | `client.insider_transactions.ticker()` | `/insider-trading/{SYMBOL}` |
319
316
  | LinkedIn | `client.linkedin_data.ticker()` | `/linkedin/{SYMBOL}` |
320
317
  | Options – Put/Call | `client.options.put_call()` | `/put-call-ratio/{SYMBOL}` |
321
318
  | Screener | `client.screener.sentiment()` | `/screener/sentiment` |
322
319
  | | `client.screener.predictions_daily()` | `/screener/predictions/daily` |
323
320
  | | `client.screener.insider_trading()` | `/screener/insider-trading` |
321
+ | | `client.screener.reddit_mentions()` | `/screener/reddit-mentions` |
324
322
  | | ... and 8 more screener methods | |
325
323
  | Recent | `client.recent.news()` | `/recent/news` |
326
324
  | | `client.recent.analyst_ratings()` | `/recent/analyst-ratings` |
@@ -319,19 +319,16 @@ class TestRedditMentions:
319
319
  assert "_all" in df["subreddit"].values
320
320
 
321
321
  def test_screener_raw(self, fb):
322
- data = fb.reddit_mentions.screener()
323
- assert isinstance(data, dict)
324
- assert "data" in data
325
- rows = data["data"]
326
- assert isinstance(rows, list)
327
- assert len(rows) > 0
328
- row = rows[0]
322
+ data = fb.screener.reddit_mentions()
323
+ assert isinstance(data, list)
324
+ assert len(data) > 0
325
+ row = data[0]
329
326
  assert "symbol" in row
330
327
  assert "totalMentions" in row
331
328
  assert "subreddits" in row
332
329
 
333
330
  def test_screener_dataframe(self, fb):
334
- df = fb.reddit_mentions.screener(as_dataframe=True)
331
+ df = fb.screener.reddit_mentions(as_dataframe=True)
335
332
  assert isinstance(df, pd.DataFrame)
336
333
  assert len(df) > 0
337
334
 
@@ -152,19 +152,17 @@ class MockRedditMentionsClient:
152
152
  }).set_index("date")
153
153
  return df
154
154
 
155
+ class screener:
155
156
  @staticmethod
156
- def screener(*args, **kwargs):
157
- return {
158
- "data": [
159
- {"symbol": "TSLA", "name": "Tesla", "date": "2024-01-01T08:00:00Z",
160
- "totalMentions": 120, "subreddits": {"wallstreetbets": 85, "stocks": 12}},
161
- {"symbol": "AAPL", "name": "Apple", "date": "2024-01-01T08:00:00Z",
162
- "totalMentions": 45, "subreddits": {"wallstreetbets": 30, "stocks": 8}},
163
- {"symbol": "NVDA", "name": "NVIDIA", "date": "2024-01-01T08:00:00Z",
164
- "totalMentions": 80, "subreddits": {"wallstreetbets": 60, "stocks": 5}},
165
- ],
166
- "summary": {},
167
- }
157
+ def reddit_mentions(*args, **kwargs):
158
+ return [
159
+ {"symbol": "TSLA", "name": "Tesla", "date": "2024-01-01T08:00:00Z",
160
+ "totalMentions": 120, "subreddits": {"wallstreetbets": 85, "stocks": 12}},
161
+ {"symbol": "AAPL", "name": "Apple", "date": "2024-01-01T08:00:00Z",
162
+ "totalMentions": 45, "subreddits": {"wallstreetbets": 30, "stocks": 8}},
163
+ {"symbol": "NVDA", "name": "NVIDIA", "date": "2024-01-01T08:00:00Z",
164
+ "totalMentions": 80, "subreddits": {"wallstreetbets": 60, "stocks": 5}},
165
+ ]
168
166
 
169
167
 
170
168
  # ═══════════════════════════════════════════════════════════════════════════
@@ -480,11 +478,11 @@ def test_reddit_mentions_as_json():
480
478
  assert isinstance(result, str)
481
479
 
482
480
 
483
- # ── reddit_mentions_screener ──────────────────────────────────────────────
481
+ # ── reddit_mentions_top ──────────────────────────────────────────────
484
482
 
485
- def test_reddit_mentions_screener_plot():
483
+ def test_reddit_mentions_top_plot():
486
484
  plot = _PlotNamespace(MockRedditMentionsClient())
487
- fig = plot.reddit_mentions_screener(show=False)
485
+ fig = plot.reddit_mentions_top(show=False)
488
486
 
489
487
  assert isinstance(fig, go.Figure)
490
488
  # 2 subreddits → 2 bar traces (stocks, wallstreetbets)
@@ -499,9 +497,9 @@ def test_reddit_mentions_screener_plot():
499
497
  assert symbols[0] == "AAPL" # lowest mentions
500
498
 
501
499
 
502
- def test_reddit_mentions_screener_top_n():
500
+ def test_reddit_mentions_top_top_n():
503
501
  plot = _PlotNamespace(MockRedditMentionsClient())
504
- fig = plot.reddit_mentions_screener(top_n=2, show=False)
502
+ fig = plot.reddit_mentions_top(top_n=2, show=False)
505
503
 
506
504
  assert isinstance(fig, go.Figure)
507
505
  # Only top 2 tickers
@@ -512,43 +510,40 @@ def test_reddit_mentions_screener_top_n():
512
510
  assert "AAPL" not in symbols
513
511
 
514
512
 
515
- def test_reddit_mentions_screener_empty_data():
513
+ def test_reddit_mentions_top_empty_data():
516
514
  class EmptyScreenerClient:
517
- class reddit_mentions:
515
+ class screener:
518
516
  @staticmethod
519
- def screener(*args, **kwargs):
520
- return {"data": [], "summary": {}}
517
+ def reddit_mentions(*args, **kwargs):
518
+ return []
521
519
 
522
520
  plot = _PlotNamespace(EmptyScreenerClient())
523
521
  with pytest.raises(ValueError, match="No screener data returned"):
524
- plot.reddit_mentions_screener(show=False)
522
+ plot.reddit_mentions_top(show=False)
525
523
 
526
524
 
527
- def test_reddit_mentions_screener_as_json():
525
+ def test_reddit_mentions_top_as_json():
528
526
  plot = _PlotNamespace(MockRedditMentionsClient())
529
- result = plot.reddit_mentions_screener(show=False, as_json=True)
527
+ result = plot.reddit_mentions_top(show=False, as_json=True)
530
528
  assert isinstance(result, str)
531
529
 
532
530
 
533
- def test_reddit_mentions_screener_latest_snapshot():
531
+ def test_reddit_mentions_top_latest_snapshot():
534
532
  """Verify that only the latest snapshot per ticker is used."""
535
533
 
536
534
  class MultiSnapshotClient:
537
- class reddit_mentions:
535
+ class screener:
538
536
  @staticmethod
539
- def screener(*args, **kwargs):
540
- return {
541
- "data": [
542
- {"symbol": "TSLA", "date": "2024-01-01T04:00:00Z",
543
- "totalMentions": 50, "subreddits": {"wsb": 30}},
544
- {"symbol": "TSLA", "date": "2024-01-01T08:00:00Z",
545
- "totalMentions": 120, "subreddits": {"wsb": 85}},
546
- ],
547
- "summary": {},
548
- }
537
+ def reddit_mentions(*args, **kwargs):
538
+ return [
539
+ {"symbol": "TSLA", "date": "2024-01-01T04:00:00Z",
540
+ "totalMentions": 50, "subreddits": {"wsb": 30}},
541
+ {"symbol": "TSLA", "date": "2024-01-01T08:00:00Z",
542
+ "totalMentions": 120, "subreddits": {"wsb": 85}},
543
+ ]
549
544
 
550
545
  plot = _PlotNamespace(MultiSnapshotClient())
551
- fig = plot.reddit_mentions_screener(show=False)
546
+ fig = plot.reddit_mentions_top(show=False)
552
547
 
553
548
  # Should use the 08:00 snapshot (120 mentions), not 04:00 (50)
554
549
  assert fig.data[0].x == (85,) # wsb=85 from the latest snapshot
@@ -117,11 +117,10 @@ def test_reddit_mentions_screener_raw_ok(client, _activate_responses):
117
117
 
118
118
  stub_json(_activate_responses, "GET", path, payload)
119
119
 
120
- data = client.reddit_mentions.screener()
121
- assert isinstance(data["data"], list)
122
- assert data["data"][0]["symbol"] == "TSLA"
123
- assert data["data"][0]["totalMentions"] == 120
124
- assert data["summary"]["totalTickers"] == 1
120
+ data = client.screener.reddit_mentions()
121
+ assert isinstance(data, list)
122
+ assert data[0]["symbol"] == "TSLA"
123
+ assert data[0]["totalMentions"] == 120
125
124
 
126
125
 
127
126
  # ─────────── screener DataFrame ─────────────────────────────────────────
@@ -155,14 +154,9 @@ def test_reddit_mentions_screener_dataframe_ok(client, _activate_responses):
155
154
 
156
155
  stub_json(_activate_responses, "GET", path, payload)
157
156
 
158
- df = client.reddit_mentions.screener(as_dataframe=True)
157
+ df = client.screener.reddit_mentions(as_dataframe=True)
159
158
 
160
159
  assert isinstance(df, pd.DataFrame)
161
160
  assert len(df) == 2
162
- assert df.index.name == "date"
163
- assert pd.api.types.is_datetime64_any_dtype(df.index)
164
- # Verify timestamps preserve time component (UTC-aware from ISO Z suffix)
165
- assert pd.Timestamp("2026-03-17 08:00:00", tz="UTC") in df.index
166
- assert pd.Timestamp("2026-03-17 12:00:00", tz="UTC") in df.index
161
+ assert df.index.name == "symbol"
167
162
  assert "totalMentions" in df.columns
168
- assert "symbol" in df.columns
@@ -1,79 +0,0 @@
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
@@ -1,123 +0,0 @@
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
File without changes