finbrain-python 0.2.2__py3-none-any.whl → 0.2.4__py3-none-any.whl
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/aio/client.py +2 -0
- finbrain/aio/endpoints/government_contracts.py +48 -0
- finbrain/aio/endpoints/reddit_mentions.py +0 -31
- finbrain/aio/endpoints/screener.py +26 -0
- finbrain/client.py +2 -0
- finbrain/endpoints/government_contracts.py +73 -0
- finbrain/endpoints/reddit_mentions.py +0 -50
- finbrain/endpoints/screener.py +26 -0
- finbrain/plotting.py +158 -4
- {finbrain_python-0.2.2.dist-info → finbrain_python-0.2.4.dist-info}/METADATA +14 -6
- {finbrain_python-0.2.2.dist-info → finbrain_python-0.2.4.dist-info}/RECORD +14 -12
- {finbrain_python-0.2.2.dist-info → finbrain_python-0.2.4.dist-info}/WHEEL +0 -0
- {finbrain_python-0.2.2.dist-info → finbrain_python-0.2.4.dist-info}/licenses/LICENSE +0 -0
- {finbrain_python-0.2.2.dist-info → finbrain_python-0.2.4.dist-info}/top_level.txt +0 -0
finbrain/aio/client.py
CHANGED
|
@@ -24,6 +24,7 @@ from .endpoints.screener import AsyncScreenerAPI
|
|
|
24
24
|
from .endpoints.recent import AsyncRecentAPI
|
|
25
25
|
from .endpoints.corporate_lobbying import AsyncCorporateLobbyingAPI
|
|
26
26
|
from .endpoints.reddit_mentions import AsyncRedditMentionsAPI
|
|
27
|
+
from .endpoints.government_contracts import AsyncGovernmentContractsAPI
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
# Which status codes merit a retry
|
|
@@ -84,6 +85,7 @@ class AsyncFinBrainClient:
|
|
|
84
85
|
self.recent = AsyncRecentAPI(self)
|
|
85
86
|
self.corporate_lobbying = AsyncCorporateLobbyingAPI(self)
|
|
86
87
|
self.reddit_mentions = AsyncRedditMentionsAPI(self)
|
|
88
|
+
self.government_contracts = AsyncGovernmentContractsAPI(self)
|
|
87
89
|
|
|
88
90
|
async def __aenter__(self) -> "AsyncFinBrainClient":
|
|
89
91
|
"""Context manager entry."""
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import datetime as _dt
|
|
4
|
+
from typing import TYPE_CHECKING, Dict, Any, List
|
|
5
|
+
|
|
6
|
+
from ._utils import to_datestr
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from ..client import AsyncFinBrainClient
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AsyncGovernmentContractsAPI:
|
|
13
|
+
"""Async wrapper for /government-contracts and /screener/government-contracts endpoints."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, client: "AsyncFinBrainClient") -> None:
|
|
16
|
+
self._c = client
|
|
17
|
+
|
|
18
|
+
async def ticker(
|
|
19
|
+
self,
|
|
20
|
+
symbol: str,
|
|
21
|
+
*,
|
|
22
|
+
date_from: _dt.date | str | None = None,
|
|
23
|
+
date_to: _dt.date | str | None = None,
|
|
24
|
+
limit: int | None = None,
|
|
25
|
+
as_dataframe: bool = False,
|
|
26
|
+
) -> Dict[str, Any] | pd.DataFrame:
|
|
27
|
+
"""Fetch government contract awards for a symbol (async)."""
|
|
28
|
+
params: Dict[str, str] = {}
|
|
29
|
+
if date_from:
|
|
30
|
+
params["startDate"] = to_datestr(date_from)
|
|
31
|
+
if date_to:
|
|
32
|
+
params["endDate"] = to_datestr(date_to)
|
|
33
|
+
if limit is not None:
|
|
34
|
+
params["limit"] = str(limit)
|
|
35
|
+
|
|
36
|
+
path = f"government-contracts/{symbol.upper()}"
|
|
37
|
+
|
|
38
|
+
data: Dict[str, Any] = await self._c._request("GET", path, params=params)
|
|
39
|
+
|
|
40
|
+
if as_dataframe:
|
|
41
|
+
rows: List[Dict[str, Any]] = data.get("contracts", [])
|
|
42
|
+
df = pd.DataFrame(rows)
|
|
43
|
+
if not df.empty and "startDate" in df.columns:
|
|
44
|
+
df["startDate"] = pd.to_datetime(df["startDate"])
|
|
45
|
+
df.set_index("startDate", inplace=True)
|
|
46
|
+
return df
|
|
47
|
+
|
|
48
|
+
return data
|
|
@@ -46,34 +46,3 @@ class AsyncRedditMentionsAPI:
|
|
|
46
46
|
return df
|
|
47
47
|
|
|
48
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
|
|
@@ -208,3 +208,29 @@ class AsyncScreenerAPI:
|
|
|
208
208
|
"""Screen monthly (12-month) predictions across tickers."""
|
|
209
209
|
params = self._build_params(limit=limit, market=market, region=region)
|
|
210
210
|
return await self._get("screener/predictions/monthly", params, as_dataframe)
|
|
211
|
+
|
|
212
|
+
# ── reddit mentions ────────────────────────────────────────
|
|
213
|
+
|
|
214
|
+
async def reddit_mentions(
|
|
215
|
+
self,
|
|
216
|
+
*,
|
|
217
|
+
limit: int | None = None,
|
|
218
|
+
market: str | None = None,
|
|
219
|
+
region: str | None = None,
|
|
220
|
+
as_dataframe: bool = False,
|
|
221
|
+
) -> List[Dict[str, Any]] | pd.DataFrame:
|
|
222
|
+
"""Screen Reddit mention counts across tickers (async)."""
|
|
223
|
+
params = self._build_params(limit=limit, market=market, region=region)
|
|
224
|
+
return await self._get("screener/reddit-mentions", params, as_dataframe)
|
|
225
|
+
|
|
226
|
+
# ── government contracts ──────────────────────────────────
|
|
227
|
+
|
|
228
|
+
async def government_contracts(
|
|
229
|
+
self,
|
|
230
|
+
*,
|
|
231
|
+
limit: int | None = None,
|
|
232
|
+
as_dataframe: bool = False,
|
|
233
|
+
) -> List[Dict[str, Any]] | pd.DataFrame:
|
|
234
|
+
"""Screen government contracts across all tickers (async)."""
|
|
235
|
+
params = self._build_params(limit=limit)
|
|
236
|
+
return await self._get("screener/government-contracts", params, as_dataframe)
|
finbrain/client.py
CHANGED
|
@@ -25,6 +25,7 @@ from .endpoints.screener import ScreenerAPI
|
|
|
25
25
|
from .endpoints.recent import RecentAPI
|
|
26
26
|
from .endpoints.corporate_lobbying import CorporateLobbyingAPI
|
|
27
27
|
from .endpoints.reddit_mentions import RedditMentionsAPI
|
|
28
|
+
from .endpoints.government_contracts import GovernmentContractsAPI
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
# Which status codes merit a retry
|
|
@@ -79,6 +80,7 @@ class FinBrainClient:
|
|
|
79
80
|
self.recent = RecentAPI(self)
|
|
80
81
|
self.corporate_lobbying = CorporateLobbyingAPI(self)
|
|
81
82
|
self.reddit_mentions = RedditMentionsAPI(self)
|
|
83
|
+
self.government_contracts = GovernmentContractsAPI(self)
|
|
82
84
|
|
|
83
85
|
# ---------- private helpers ----------
|
|
84
86
|
def _request(
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import datetime as _dt
|
|
4
|
+
from typing import TYPE_CHECKING, Dict, Any, List
|
|
5
|
+
|
|
6
|
+
from ._utils import to_datestr
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING: # imported only by type-checkers
|
|
9
|
+
from ..client import FinBrainClient
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GovernmentContractsAPI:
|
|
13
|
+
"""
|
|
14
|
+
Endpoints
|
|
15
|
+
---------
|
|
16
|
+
``/government-contracts/<TICKER>`` - U.S. government contract awards.
|
|
17
|
+
``/screener/government-contracts`` - cross-ticker government contracts 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 government contract awards for *symbol*.
|
|
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 ``startDate``;
|
|
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"government-contracts/{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("contracts", [])
|
|
67
|
+
df = pd.DataFrame(rows)
|
|
68
|
+
if not df.empty and "startDate" in df.columns:
|
|
69
|
+
df["startDate"] = pd.to_datetime(df["startDate"])
|
|
70
|
+
df.set_index("startDate", inplace=True)
|
|
71
|
+
return df
|
|
72
|
+
|
|
73
|
+
return data
|
|
@@ -71,53 +71,3 @@ class RedditMentionsAPI:
|
|
|
71
71
|
return df
|
|
72
72
|
|
|
73
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
|
finbrain/endpoints/screener.py
CHANGED
|
@@ -213,3 +213,29 @@ 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)
|
|
230
|
+
|
|
231
|
+
# ── government contracts ──────────────────────────────────
|
|
232
|
+
|
|
233
|
+
def government_contracts(
|
|
234
|
+
self,
|
|
235
|
+
*,
|
|
236
|
+
limit: int | None = None,
|
|
237
|
+
as_dataframe: bool = False,
|
|
238
|
+
) -> List[Dict[str, Any]] | pd.DataFrame:
|
|
239
|
+
"""Screen government contracts across all tickers."""
|
|
240
|
+
params = self._build_params(limit=limit)
|
|
241
|
+
return self._get("screener/government-contracts", params, as_dataframe)
|
finbrain/plotting.py
CHANGED
|
@@ -1057,7 +1057,7 @@ class _PlotNamespace:
|
|
|
1057
1057
|
# --------------------------------------------------------------------- #
|
|
1058
1058
|
# Reddit Mentions Screener → stacked horizontal bars (top N tickers) #
|
|
1059
1059
|
# --------------------------------------------------------------------- #
|
|
1060
|
-
def
|
|
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
|
|
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
|
|
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
|
|
|
@@ -1154,6 +1154,160 @@ class _PlotNamespace:
|
|
|
1154
1154
|
return None
|
|
1155
1155
|
return fig.to_json() if as_json else fig
|
|
1156
1156
|
|
|
1157
|
+
# --------------------------------------------------------------------- #
|
|
1158
|
+
# Government Contracts → bars on price chart #
|
|
1159
|
+
# --------------------------------------------------------------------- #
|
|
1160
|
+
def government_contracts(
|
|
1161
|
+
self,
|
|
1162
|
+
ticker: str,
|
|
1163
|
+
price_data: pd.DataFrame,
|
|
1164
|
+
*,
|
|
1165
|
+
date_from: str | None = None,
|
|
1166
|
+
date_to: str | None = None,
|
|
1167
|
+
as_json: bool = False,
|
|
1168
|
+
show: bool = True,
|
|
1169
|
+
template: str = "plotly_dark",
|
|
1170
|
+
**kwargs,
|
|
1171
|
+
):
|
|
1172
|
+
"""
|
|
1173
|
+
Plot government contract awards overlaid on a price chart.
|
|
1174
|
+
|
|
1175
|
+
This method requires user-provided historical price data, as FinBrain
|
|
1176
|
+
does not currently offer a price history endpoint.
|
|
1177
|
+
|
|
1178
|
+
Parameters
|
|
1179
|
+
----------
|
|
1180
|
+
ticker : str
|
|
1181
|
+
Ticker symbol (e.g. ``"LMT"``).
|
|
1182
|
+
price_data : pandas.DataFrame
|
|
1183
|
+
**User-provided** price history with a DatetimeIndex and a column
|
|
1184
|
+
containing prices (e.g. ``"close"``, ``"Close"``, or ``"price"``).
|
|
1185
|
+
The index must be timezone-naive or UTC.
|
|
1186
|
+
date_from, date_to : str or None, optional
|
|
1187
|
+
Date range for contracts in ``YYYY-MM-DD`` format.
|
|
1188
|
+
as_json : bool, default False
|
|
1189
|
+
If ``True``, return JSON string instead of Figure object.
|
|
1190
|
+
show : bool, default True
|
|
1191
|
+
If ``True`` and ``as_json=False``, display the figure immediately.
|
|
1192
|
+
template : str, default "plotly_dark"
|
|
1193
|
+
Plotly template name.
|
|
1194
|
+
**kwargs
|
|
1195
|
+
Additional arguments passed to
|
|
1196
|
+
:meth:`FinBrainClient.government_contracts.ticker`.
|
|
1197
|
+
|
|
1198
|
+
Returns
|
|
1199
|
+
-------
|
|
1200
|
+
plotly.graph_objects.Figure or str or None
|
|
1201
|
+
Figure object, JSON string, or None (when shown).
|
|
1202
|
+
|
|
1203
|
+
Raises
|
|
1204
|
+
------
|
|
1205
|
+
ValueError
|
|
1206
|
+
If ``price_data`` is empty or missing required price column.
|
|
1207
|
+
"""
|
|
1208
|
+
# Validate price_data
|
|
1209
|
+
if price_data.empty:
|
|
1210
|
+
raise ValueError("price_data cannot be empty")
|
|
1211
|
+
|
|
1212
|
+
# Flatten MultiIndex columns if present (e.g., from yf.download())
|
|
1213
|
+
if isinstance(price_data.columns, pd.MultiIndex):
|
|
1214
|
+
price_data = price_data.copy()
|
|
1215
|
+
price_data.columns = price_data.columns.get_level_values(0)
|
|
1216
|
+
|
|
1217
|
+
# Find price column (case-insensitive search)
|
|
1218
|
+
price_col = None
|
|
1219
|
+
for col in ["close", "Close", "price", "Price", "adj_close", "Adj Close"]:
|
|
1220
|
+
if col in price_data.columns:
|
|
1221
|
+
price_col = col
|
|
1222
|
+
break
|
|
1223
|
+
if price_col is None:
|
|
1224
|
+
raise ValueError(
|
|
1225
|
+
f"price_data must contain a price column (e.g. 'close', 'Close', 'price'). "
|
|
1226
|
+
f"Found columns: {price_data.columns.tolist()}"
|
|
1227
|
+
)
|
|
1228
|
+
|
|
1229
|
+
# Fetch government contracts
|
|
1230
|
+
contracts_df = self._fb.government_contracts.ticker(
|
|
1231
|
+
ticker,
|
|
1232
|
+
date_from=date_from,
|
|
1233
|
+
date_to=date_to,
|
|
1234
|
+
as_dataframe=True,
|
|
1235
|
+
**kwargs,
|
|
1236
|
+
)
|
|
1237
|
+
|
|
1238
|
+
# Normalize timezones
|
|
1239
|
+
price_data_normalized = price_data.copy()
|
|
1240
|
+
if price_data_normalized.index.tz is not None:
|
|
1241
|
+
price_data_normalized.index = price_data_normalized.index.tz_localize(None)
|
|
1242
|
+
|
|
1243
|
+
fig = go.Figure(
|
|
1244
|
+
layout=dict(
|
|
1245
|
+
template=template,
|
|
1246
|
+
title=f"Government Contracts · {ticker}",
|
|
1247
|
+
xaxis_title="Date",
|
|
1248
|
+
hovermode="x unified",
|
|
1249
|
+
)
|
|
1250
|
+
)
|
|
1251
|
+
|
|
1252
|
+
# Plot price line on primary y-axis
|
|
1253
|
+
fig.add_scatter(
|
|
1254
|
+
name="Price",
|
|
1255
|
+
x=price_data_normalized.index,
|
|
1256
|
+
y=price_data_normalized[price_col],
|
|
1257
|
+
mode="lines",
|
|
1258
|
+
line=dict(width=2, color="#02d2ff"),
|
|
1259
|
+
hovertemplate="<b>%{x|%Y-%m-%d}</b><br>Price: $%{y:.2f}<extra></extra>",
|
|
1260
|
+
)
|
|
1261
|
+
|
|
1262
|
+
if not contracts_df.empty:
|
|
1263
|
+
contracts_normalized = contracts_df.copy()
|
|
1264
|
+
if contracts_normalized.index.tz is not None:
|
|
1265
|
+
contracts_normalized.index = contracts_normalized.index.tz_localize(None)
|
|
1266
|
+
|
|
1267
|
+
amounts = contracts_normalized.get("awardAmount", pd.Series(dtype=float))
|
|
1268
|
+
|
|
1269
|
+
hover_text = []
|
|
1270
|
+
for _, row in contracts_normalized.iterrows():
|
|
1271
|
+
agency = row.get("awardingAgency", "N/A")
|
|
1272
|
+
desc = row.get("description", "")
|
|
1273
|
+
if len(str(desc)) > 80:
|
|
1274
|
+
desc = str(desc)[:80] + "…"
|
|
1275
|
+
naics = row.get("naicsDescription", "")
|
|
1276
|
+
amount = row.get("awardAmount", 0)
|
|
1277
|
+
hover_text.append(
|
|
1278
|
+
f"Agency: {agency}<br>"
|
|
1279
|
+
f"Amount: ${amount:,.0f}<br>"
|
|
1280
|
+
f"NAICS: {naics}<br>"
|
|
1281
|
+
f"Desc: {desc}"
|
|
1282
|
+
)
|
|
1283
|
+
|
|
1284
|
+
fig.add_bar(
|
|
1285
|
+
name="Contract Award",
|
|
1286
|
+
x=contracts_normalized.index,
|
|
1287
|
+
y=amounts,
|
|
1288
|
+
marker_color="rgba(249,200,14,0.6)",
|
|
1289
|
+
yaxis="y2",
|
|
1290
|
+
hovertext=hover_text,
|
|
1291
|
+
hovertemplate="<b>%{x|%Y-%m-%d}</b><br>%{hovertext}<extra></extra>",
|
|
1292
|
+
)
|
|
1293
|
+
|
|
1294
|
+
fig.update_layout(
|
|
1295
|
+
yaxis=dict(title="Price", showgrid=True),
|
|
1296
|
+
yaxis2=dict(
|
|
1297
|
+
title="Award Amount ($)",
|
|
1298
|
+
overlaying="y",
|
|
1299
|
+
side="right",
|
|
1300
|
+
showgrid=False,
|
|
1301
|
+
zeroline=False,
|
|
1302
|
+
rangemode="tozero",
|
|
1303
|
+
),
|
|
1304
|
+
)
|
|
1305
|
+
|
|
1306
|
+
if show and not as_json:
|
|
1307
|
+
fig.show()
|
|
1308
|
+
return None
|
|
1309
|
+
return fig.to_json() if as_json else fig
|
|
1310
|
+
|
|
1157
1311
|
# --------------------------------------------------------------------- #
|
|
1158
1312
|
# Helper methods #
|
|
1159
1313
|
# --------------------------------------------------------------------- #
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: finbrain-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
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,8 +105,12 @@ fb.reddit_mentions.ticker("TSLA",
|
|
|
105
105
|
date_to="2026-03-17",
|
|
106
106
|
as_dataframe=True)
|
|
107
107
|
|
|
108
|
-
#
|
|
109
|
-
fb.
|
|
108
|
+
# ---------- government contracts ----------
|
|
109
|
+
fb.government_contracts.ticker("LMT",
|
|
110
|
+
date_from="2025-01-01",
|
|
111
|
+
date_to="2025-12-31",
|
|
112
|
+
limit=50,
|
|
113
|
+
as_dataframe=True)
|
|
110
114
|
|
|
111
115
|
# ---------- insider transactions ----------
|
|
112
116
|
fb.insider_transactions.ticker("AMZN", as_dataframe=True)
|
|
@@ -139,6 +143,8 @@ fb.news.ticker("AMZN", limit=20, as_dataframe=True)
|
|
|
139
143
|
fb.screener.sentiment(market="S&P 500", as_dataframe=True)
|
|
140
144
|
fb.screener.predictions_daily(limit=100, as_dataframe=True)
|
|
141
145
|
fb.screener.insider_trading(limit=50)
|
|
146
|
+
fb.screener.reddit_mentions(limit=100, as_dataframe=True)
|
|
147
|
+
fb.screener.government_contracts(limit=100, as_dataframe=True)
|
|
142
148
|
|
|
143
149
|
# ---------- recent data ----------
|
|
144
150
|
fb.recent.news(limit=100, as_dataframe=True)
|
|
@@ -264,10 +270,10 @@ fb.plot.reddit_mentions("TSLA",
|
|
|
264
270
|
```python
|
|
265
271
|
# ---------- Reddit Mentions Screener Chart (no price data needed) ----------
|
|
266
272
|
# Stacked horizontal bar chart of top 15 most mentioned tickers
|
|
267
|
-
fb.plot.
|
|
273
|
+
fb.plot.reddit_mentions_top(market="S&P 500")
|
|
268
274
|
|
|
269
275
|
# Customize the number of tickers shown
|
|
270
|
-
fb.plot.
|
|
276
|
+
fb.plot.reddit_mentions_top(top_n=10, region="US")
|
|
271
277
|
```
|
|
272
278
|
|
|
273
279
|
**Price Data Requirements:**
|
|
@@ -314,13 +320,15 @@ fb = FinBrainClient() # reads from FINBRAIN_API_KEY env var
|
|
|
314
320
|
| Senate trades | `client.senate_trades.ticker()` | `/congress/senate/{SYMBOL}` |
|
|
315
321
|
| Corporate lobbying | `client.corporate_lobbying.ticker()` | `/lobbying/{SYMBOL}` |
|
|
316
322
|
| Reddit mentions | `client.reddit_mentions.ticker()` | `/reddit-mentions/{SYMBOL}` |
|
|
317
|
-
|
|
|
323
|
+
| Gov. contracts | `client.government_contracts.ticker()` | `/government-contracts/{SYMBOL}` |
|
|
318
324
|
| Insider transactions | `client.insider_transactions.ticker()` | `/insider-trading/{SYMBOL}` |
|
|
319
325
|
| LinkedIn | `client.linkedin_data.ticker()` | `/linkedin/{SYMBOL}` |
|
|
320
326
|
| Options – Put/Call | `client.options.put_call()` | `/put-call-ratio/{SYMBOL}` |
|
|
321
327
|
| Screener | `client.screener.sentiment()` | `/screener/sentiment` |
|
|
322
328
|
| | `client.screener.predictions_daily()` | `/screener/predictions/daily` |
|
|
323
329
|
| | `client.screener.insider_trading()` | `/screener/insider-trading` |
|
|
330
|
+
| | `client.screener.reddit_mentions()` | `/screener/reddit-mentions` |
|
|
331
|
+
| | `client.screener.government_contracts()` | `/screener/government-contracts` |
|
|
324
332
|
| | ... and 8 more screener methods | |
|
|
325
333
|
| Recent | `client.recent.news()` | `/recent/news` |
|
|
326
334
|
| | `client.recent.analyst_ratings()` | `/recent/analyst-ratings` |
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
finbrain/__init__.py,sha256=ULIgwHkWbj5sT0udC3SlTROhZCgm2y9BtPox-4joNn4,416
|
|
2
|
-
finbrain/client.py,sha256=
|
|
2
|
+
finbrain/client.py,sha256=7ytNARm22V9ePOIPv6hq-UGvphSC9fsIuxQqTfL313I,5252
|
|
3
3
|
finbrain/exceptions.py,sha256=ZkuhWfaMAqoEfhrN_6V2XPq0eY7vPCINM9S__tfgqYI,6249
|
|
4
|
-
finbrain/plotting.py,sha256=
|
|
4
|
+
finbrain/plotting.py,sha256=ZDswlMAGfQzjCYNmKmcfpkljs9FYl4kOKJyy9vl1z2w,53596
|
|
5
5
|
finbrain/py.typed,sha256=INFR1qO3jdMrtVPs0N0SAVKVbjKV80HQPoPshwGEpGo,27
|
|
6
6
|
finbrain/aio/__init__.py,sha256=eJra9SqvLMkq5zoZfG6WFw7kd1jGpf9l-ovuPMeWvdw,125
|
|
7
|
-
finbrain/aio/client.py,sha256
|
|
7
|
+
finbrain/aio/client.py,sha256=aiYDmukVDoCw9MmrPdxMyLOoPlR77il_nZO9CvP3ZHk,7171
|
|
8
8
|
finbrain/aio/endpoints/__init__.py,sha256=zdNSU1jzvnK6JnHxrvb3ye9KFCkA86xDTu9bBzmwrEQ,48
|
|
9
9
|
finbrain/aio/endpoints/_utils.py,sha256=4ElYWGCcJ7t_lx02e49NvFHMt2g6DTcnwx3Mvwf3KyY,908
|
|
10
10
|
finbrain/aio/endpoints/analyst_ratings.py,sha256=IYG-PU8HDOZoyyMcb_2KRxhiMVicYyaU_TOCVZPWroo,1451
|
|
11
11
|
finbrain/aio/endpoints/app_ratings.py,sha256=QrJoLc7roTPRfInAEMwlr9vtxzruFJulGdB6msz7Eg0,2177
|
|
12
12
|
finbrain/aio/endpoints/available.py,sha256=rQuffHowNF7FrVV5Ds-KE5iFjdz0I_vD7Qk2_zY2WLo,2315
|
|
13
13
|
finbrain/aio/endpoints/corporate_lobbying.py,sha256=4rILB9-W91n6v2UwOnlVeH-ulk4D8onamzdEJf--wko,1429
|
|
14
|
+
finbrain/aio/endpoints/government_contracts.py,sha256=-jKXMHWrdG-4PszBSTQpK-i4SHpVZufPDBABMDB56Gk,1540
|
|
14
15
|
finbrain/aio/endpoints/house_trades.py,sha256=HQVFnCI_rn9mdYWUTiU10iRoFxYdIeR7AhmqKrItndU,1427
|
|
15
16
|
finbrain/aio/endpoints/insider_transactions.py,sha256=EBqHdvapYWvI53PM_sQy0caN2rbl7Qul4OmqzItMUR0,1437
|
|
16
17
|
finbrain/aio/endpoints/linkedin_data.py,sha256=ucnkyNc1waiFHLxas1onPhxh9Y7ekcI_gOfUJ5gu1hI,1469
|
|
@@ -18,8 +19,8 @@ finbrain/aio/endpoints/news.py,sha256=NJkcpoP8ngNsJdCzQab8yaXJzNe5ypYstzq3-mEHfq
|
|
|
18
19
|
finbrain/aio/endpoints/options.py,sha256=k1XL3ktoFL2Exw6UE2XfU-EcS1Xw8BPXQzABLYXmfxk,1442
|
|
19
20
|
finbrain/aio/endpoints/predictions.py,sha256=BrUSmqy0v7ngXyoE_UHgqdght8qtJ8IRxAhk_s9UeR0,1285
|
|
20
21
|
finbrain/aio/endpoints/recent.py,sha256=TSaAwf5w86O1plSmm4eg_gKSjSap2Z8rd2i7FAvgm90,2302
|
|
21
|
-
finbrain/aio/endpoints/reddit_mentions.py,sha256=
|
|
22
|
-
finbrain/aio/endpoints/screener.py,sha256=
|
|
22
|
+
finbrain/aio/endpoints/reddit_mentions.py,sha256=P9ToZRaRN521RJ9W2J8cBCySUsNT47ROe-8TTdVtlqA,1508
|
|
23
|
+
finbrain/aio/endpoints/screener.py,sha256=MYkIIZ6ocyx9khkxYjJnMQeg098-uZpdoSVbkUUj8Z4,9751
|
|
23
24
|
finbrain/aio/endpoints/senate_trades.py,sha256=nhZd8R7Tclxzn-J4-ZWZ5onvBBd8pVOtiLfhKFtFBHw,1431
|
|
24
25
|
finbrain/aio/endpoints/sentiments.py,sha256=BXFT0jr5j0MnB65MXGxZK36BJLXMTuggkFF9A9eenEQ,1532
|
|
25
26
|
finbrain/endpoints/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -28,6 +29,7 @@ finbrain/endpoints/analyst_ratings.py,sha256=6q_tecgnbzXn2wLSWqHNUa9MbLcybTD7xEB
|
|
|
28
29
|
finbrain/endpoints/app_ratings.py,sha256=mbeKiJ87nNibnz9AT2kmH4fVFA4goEcld910abm3tFs,3597
|
|
29
30
|
finbrain/endpoints/available.py,sha256=3cXjDK1qE38RHAfQ2C14oD7l7dR8mhDtUcva3LzlK5s,3911
|
|
30
31
|
finbrain/endpoints/corporate_lobbying.py,sha256=K4jFcgx2_PqxFSlSm2E0rxuunl7FjWEwk_S4nfjsMuw,2213
|
|
32
|
+
finbrain/endpoints/government_contracts.py,sha256=jb96byPNK32AsEQWgCHMU7kAue9BFC539TH10JGfY7I,2343
|
|
31
33
|
finbrain/endpoints/house_trades.py,sha256=x_C5Xc-Cj9CKdR2014XcP6G_zeXSWChwR8bKbkLzuGM,2235
|
|
32
34
|
finbrain/endpoints/insider_transactions.py,sha256=TU6EsSHIeams4OLJ9s8BpNuXeBnWPHghlp4enqkEFe8,2244
|
|
33
35
|
finbrain/endpoints/linkedin_data.py,sha256=zcNboQ-Ejt_7Ncu3p9WCd-PsjEBFbojVaJZdkbaGT8Q,2215
|
|
@@ -35,12 +37,12 @@ finbrain/endpoints/news.py,sha256=q4aaEiunrUbl1FF0JZuX9__cZWxm2r1-B9x-c8g4Ia8,19
|
|
|
35
37
|
finbrain/endpoints/options.py,sha256=_TYWPcJ-gmPTQRg_kEVQl6Oj6fAirtZJ8QrfqFA2cMU,2611
|
|
36
38
|
finbrain/endpoints/predictions.py,sha256=og1RY68p4O71smogrx-M5FyGk8FVclBGP18BvttzbZk,2026
|
|
37
39
|
finbrain/endpoints/recent.py,sha256=7D36oVRJKF5hkR42y6M6CiEMYksuFleZou1v86SbEfA,3004
|
|
38
|
-
finbrain/endpoints/reddit_mentions.py,sha256=
|
|
39
|
-
finbrain/endpoints/screener.py,sha256=
|
|
40
|
+
finbrain/endpoints/reddit_mentions.py,sha256=WiePVAyUbA0VpWOd22M2YTlDWOQITp8qUBraI29y6d4,2305
|
|
41
|
+
finbrain/endpoints/screener.py,sha256=jBPUvmfj0Zfj5aoDLBtfXlYIR0UynIG613duSkck3NY,9665
|
|
40
42
|
finbrain/endpoints/senate_trades.py,sha256=K6r_-Wt1HOs0dDXngHdj6Eo_UUJXAy5yE4EmmQbGaiU,2226
|
|
41
43
|
finbrain/endpoints/sentiments.py,sha256=sGGPaxzGGFvMOaYv3VSCmnG7H9v7OVlX7Vsndct8jhA,2606
|
|
42
|
-
finbrain_python-0.2.
|
|
43
|
-
finbrain_python-0.2.
|
|
44
|
-
finbrain_python-0.2.
|
|
45
|
-
finbrain_python-0.2.
|
|
46
|
-
finbrain_python-0.2.
|
|
44
|
+
finbrain_python-0.2.4.dist-info/licenses/LICENSE,sha256=x71LjIUPbK7Y8YBulH_AXzEIX7BK90dgFL2zA6LBT-A,1069
|
|
45
|
+
finbrain_python-0.2.4.dist-info/METADATA,sha256=qLUp9VxSkq5FRsYFAgpI5rLxClH-uUfcy2auc8bNLno,15402
|
|
46
|
+
finbrain_python-0.2.4.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
47
|
+
finbrain_python-0.2.4.dist-info/top_level.txt,sha256=dCiQtTx3z2TtO_yQI6q3yODcdb-pABB-iEReJegVAv0,9
|
|
48
|
+
finbrain_python-0.2.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|