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.
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/CHANGELOG.md +10 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/PKG-INFO +27 -1
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/README.md +26 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/client.py +2 -0
- finbrain_python-0.2.2/src/finbrain/aio/endpoints/reddit_mentions.py +79 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/client.py +2 -0
- finbrain_python-0.2.2/src/finbrain/endpoints/reddit_mentions.py +123 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/plotting.py +247 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain_python.egg-info/PKG-INFO +27 -1
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain_python.egg-info/SOURCES.txt +3 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_integration.py +48 -0
- finbrain_python-0.2.2/tests/test_plotting.py +554 -0
- finbrain_python-0.2.2/tests/test_reddit_mentions.py +168 -0
- finbrain_python-0.2.1/tests/test_plotting.py +0 -134
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/.gitattributes +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/.github/workflows/ci.yml +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/.github/workflows/release.yml +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/.gitignore +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/LICENSE +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/RELEASE.md +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/examples/async_example.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/examples/transactions_plotting_example.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/pyproject.toml +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/setup.cfg +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/__init__.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/__init__.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/__init__.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/_utils.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/analyst_ratings.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/app_ratings.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/available.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/corporate_lobbying.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/house_trades.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/insider_transactions.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/linkedin_data.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/news.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/options.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/predictions.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/recent.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/screener.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/senate_trades.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/aio/endpoints/sentiments.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/__init__.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/_utils.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/analyst_ratings.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/app_ratings.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/available.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/corporate_lobbying.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/house_trades.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/insider_transactions.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/linkedin_data.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/news.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/options.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/predictions.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/recent.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/screener.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/senate_trades.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/endpoints/sentiments.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/exceptions.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain/py.typed +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain_python.egg-info/dependency_links.txt +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain_python.egg-info/requires.txt +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/src/finbrain_python.egg-info/top_level.txt +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/__init__.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/conftest.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_analyst_ratings.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_app_ratings.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_async_client.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_available.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_corporate_lobbying.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_envelope.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_house_trades.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_insider_transactions.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_linkedin_data.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_news.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_options.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_predictions.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_recent.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_screener.py +0 -0
- {finbrain_python-0.2.1 → finbrain_python-0.2.2}/tests/test_senate_trades.py +0 -0
- {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.
|
|
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.
|
|
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
|