finbrain-python 0.1.1__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/__init__.py +12 -0
- finbrain/aio/__init__.py +0 -0
- finbrain/aio/client.py +0 -0
- finbrain/client.py +112 -0
- finbrain/endpoints/__init__.py +0 -0
- finbrain/endpoints/analyst_ratings.py +83 -0
- finbrain/endpoints/app_ratings.py +102 -0
- finbrain/endpoints/available.py +83 -0
- finbrain/endpoints/house_trades.py +82 -0
- finbrain/endpoints/insider_transactions.py +68 -0
- finbrain/endpoints/linkedin_data.py +85 -0
- finbrain/endpoints/options.py +86 -0
- finbrain/endpoints/predictions.py +130 -0
- finbrain/endpoints/sentiments.py +108 -0
- finbrain/exceptions.py +140 -0
- finbrain_python-0.1.1.dist-info/METADATA +238 -0
- finbrain_python-0.1.1.dist-info/RECORD +20 -0
- finbrain_python-0.1.1.dist-info/WHEEL +5 -0
- finbrain_python-0.1.1.dist-info/licenses/LICENSE +21 -0
- finbrain_python-0.1.1.dist-info/top_level.txt +1 -0
finbrain/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""FinBrain Python SDK."""
|
|
2
|
+
|
|
3
|
+
from importlib import metadata as _meta
|
|
4
|
+
|
|
5
|
+
try: # installed from a wheel / sdist
|
|
6
|
+
__version__: str = _meta.version(__name__)
|
|
7
|
+
except _meta.PackageNotFoundError: # running from a Git checkout
|
|
8
|
+
__version__ = "0.0.0.dev0"
|
|
9
|
+
|
|
10
|
+
from .client import FinBrainClient
|
|
11
|
+
|
|
12
|
+
__all__ = ["FinBrainClient", "__version__"]
|
finbrain/aio/__init__.py
ADDED
|
File without changes
|
finbrain/aio/client.py
ADDED
|
File without changes
|
finbrain/client.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import time
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
import requests
|
|
7
|
+
from urllib.parse import urljoin
|
|
8
|
+
|
|
9
|
+
from .exceptions import http_error_to_exception, InvalidResponse
|
|
10
|
+
from . import __version__
|
|
11
|
+
|
|
12
|
+
from .endpoints.available import AvailableAPI
|
|
13
|
+
from .endpoints.predictions import PredictionsAPI
|
|
14
|
+
from .endpoints.sentiments import SentimentsAPI
|
|
15
|
+
from .endpoints.app_ratings import AppRatingsAPI
|
|
16
|
+
from .endpoints.analyst_ratings import AnalystRatingsAPI
|
|
17
|
+
from .endpoints.house_trades import HouseTradesAPI
|
|
18
|
+
from .endpoints.insider_transactions import InsiderTransactionsAPI
|
|
19
|
+
from .endpoints.linkedin_data import LinkedInDataAPI
|
|
20
|
+
from .endpoints.options import OptionsAPI
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Which status codes merit a retry
|
|
24
|
+
_RETRYABLE_STATUS = {500}
|
|
25
|
+
# How long to wait between retries (2, 4, 8 … seconds)
|
|
26
|
+
_BACKOFF_BASE = 2
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class FinBrainClient:
|
|
30
|
+
"""
|
|
31
|
+
Thin wrapper around the FinBrain REST API.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
DEFAULT_BASE_URL = "https://api.finbrain.tech/v1/"
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
api_key: Optional[str] = None,
|
|
39
|
+
base_url: str | None = None,
|
|
40
|
+
timeout: float = 10,
|
|
41
|
+
retries: int = 3,
|
|
42
|
+
):
|
|
43
|
+
self.api_key = api_key or os.getenv("FINBRAIN_API_KEY")
|
|
44
|
+
if not self.api_key:
|
|
45
|
+
raise ValueError("FinBrain API key missing")
|
|
46
|
+
self.base_url = base_url or self.DEFAULT_BASE_URL
|
|
47
|
+
|
|
48
|
+
self.session = requests.Session()
|
|
49
|
+
self.session.headers["User-Agent"] = f"finbrain-python/{__version__}"
|
|
50
|
+
# optional: mount urllib3 Retry adapter here
|
|
51
|
+
|
|
52
|
+
self.timeout = timeout
|
|
53
|
+
self.retries = retries
|
|
54
|
+
|
|
55
|
+
# wire endpoint helpers
|
|
56
|
+
self.available = AvailableAPI(self)
|
|
57
|
+
self.predictions = PredictionsAPI(self)
|
|
58
|
+
self.sentiments = SentimentsAPI(self)
|
|
59
|
+
self.app_ratings = AppRatingsAPI(self)
|
|
60
|
+
self.analyst_ratings = AnalystRatingsAPI(self)
|
|
61
|
+
self.house_trades = HouseTradesAPI(self)
|
|
62
|
+
self.insider_transactions = InsiderTransactionsAPI(self)
|
|
63
|
+
self.linkedin_data = LinkedInDataAPI(self)
|
|
64
|
+
self.options = OptionsAPI(self)
|
|
65
|
+
|
|
66
|
+
# ---------- private helpers ----------
|
|
67
|
+
def _request(
|
|
68
|
+
self,
|
|
69
|
+
method: str,
|
|
70
|
+
path: str,
|
|
71
|
+
params: Optional[Dict[str, Any]] = None,
|
|
72
|
+
) -> Any:
|
|
73
|
+
"""Perform a single HTTP request with auth token and retries.
|
|
74
|
+
|
|
75
|
+
Raises
|
|
76
|
+
------
|
|
77
|
+
FinBrainError
|
|
78
|
+
Mapped from HTTP status via ``http_error_to_exception``.
|
|
79
|
+
InvalidResponse
|
|
80
|
+
If the body is not valid JSON.
|
|
81
|
+
"""
|
|
82
|
+
params = params.copy() if params else {}
|
|
83
|
+
params["token"] = self.api_key # FinBrain authentication
|
|
84
|
+
url = urljoin(self.base_url, path)
|
|
85
|
+
|
|
86
|
+
for attempt in range(self.retries + 1):
|
|
87
|
+
try:
|
|
88
|
+
resp = self.session.request(
|
|
89
|
+
method, url, params=params, timeout=self.timeout
|
|
90
|
+
)
|
|
91
|
+
except requests.RequestException as exc:
|
|
92
|
+
# Network problem → retry if budget allows, else wrap into FinBrainError
|
|
93
|
+
if attempt == self.retries:
|
|
94
|
+
raise InvalidResponse(f"Network error: {exc}") from exc
|
|
95
|
+
time.sleep(_BACKOFF_BASE**attempt)
|
|
96
|
+
continue
|
|
97
|
+
|
|
98
|
+
# ── Happy path ────────────────────────────────────
|
|
99
|
+
if resp.ok: # 2xx / 3xx
|
|
100
|
+
try:
|
|
101
|
+
return resp.json()
|
|
102
|
+
except ValueError as exc:
|
|
103
|
+
raise InvalidResponse("Response body is not valid JSON") from exc
|
|
104
|
+
|
|
105
|
+
# ── Error path ───────────────────────────────────
|
|
106
|
+
if resp.status_code in _RETRYABLE_STATUS and attempt < self.retries:
|
|
107
|
+
# 500 – exponential back-off then retry
|
|
108
|
+
time.sleep(_BACKOFF_BASE**attempt)
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
# No more retries → raise the mapped FinBrainError
|
|
112
|
+
raise http_error_to_exception(resp)
|
|
File without changes
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import datetime as _dt
|
|
4
|
+
from urllib.parse import quote
|
|
5
|
+
from typing import TYPE_CHECKING, Dict, Any, List
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING: # imported only by type-checkers
|
|
8
|
+
from ..client import FinBrainClient
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AnalystRatingsAPI:
|
|
12
|
+
"""
|
|
13
|
+
Endpoint: ``/analystratings/<MARKET>/<TICKER>``
|
|
14
|
+
|
|
15
|
+
Retrieve broker/analyst rating actions for a single ticker.
|
|
16
|
+
Market names may contain spaces (``"S&P 500"``, ``"HK Hang Seng"``...);
|
|
17
|
+
they are URL-encoded automatically.
|
|
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
|
+
market: str,
|
|
28
|
+
symbol: str,
|
|
29
|
+
*,
|
|
30
|
+
date_from: _dt.date | str | None = None,
|
|
31
|
+
date_to: _dt.date | str | None = None,
|
|
32
|
+
as_dataframe: bool = False,
|
|
33
|
+
) -> Dict[str, Any] | pd.DataFrame:
|
|
34
|
+
"""
|
|
35
|
+
Analyst ratings for *symbol* in *market*.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
market :
|
|
40
|
+
Market name **exactly as FinBrain lists it**
|
|
41
|
+
(e.g. ``"S&P 500"``, ``"Germany DAX"``, ``"HK Hang Seng"``).
|
|
42
|
+
Spaces and special characters are accepted; they are URL-encoded
|
|
43
|
+
automatically.
|
|
44
|
+
symbol :
|
|
45
|
+
Ticker symbol (case-insensitive; converted to upper-case).
|
|
46
|
+
date_from, date_to :
|
|
47
|
+
Optional ISO dates ``YYYY-MM-DD`` limiting the range.
|
|
48
|
+
as_dataframe :
|
|
49
|
+
If *True*, return a **pandas.DataFrame** indexed by ``date``;
|
|
50
|
+
otherwise return the raw JSON dict.
|
|
51
|
+
|
|
52
|
+
Returns
|
|
53
|
+
-------
|
|
54
|
+
dict | pandas.DataFrame
|
|
55
|
+
"""
|
|
56
|
+
params: Dict[str, str] = {}
|
|
57
|
+
|
|
58
|
+
if date_from:
|
|
59
|
+
params["dateFrom"] = _to_datestr(date_from)
|
|
60
|
+
if date_to:
|
|
61
|
+
params["dateTo"] = _to_datestr(date_to)
|
|
62
|
+
|
|
63
|
+
market_slug = quote(market, safe="")
|
|
64
|
+
path = f"analystratings/{market_slug}/{symbol.upper()}"
|
|
65
|
+
data: Dict[str, Any] = self._c._request("GET", path, params=params)
|
|
66
|
+
|
|
67
|
+
if as_dataframe:
|
|
68
|
+
rows: List[Dict[str, Any]] = data.get("analystRatings", [])
|
|
69
|
+
df = pd.DataFrame(rows)
|
|
70
|
+
if not df.empty and "date" in df.columns:
|
|
71
|
+
df["date"] = pd.to_datetime(df["date"])
|
|
72
|
+
df.set_index("date", inplace=True)
|
|
73
|
+
return df
|
|
74
|
+
|
|
75
|
+
return data
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# ---------------------------------------------------------------------- #
|
|
79
|
+
# helper #
|
|
80
|
+
# ---------------------------------------------------------------------- #
|
|
81
|
+
def _to_datestr(value: _dt.date | str) -> str:
|
|
82
|
+
"""Convert ``datetime.date`` → ``YYYY-MM-DD``; pass strings through untouched."""
|
|
83
|
+
return value.isoformat() if isinstance(value, _dt.date) else value
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import datetime as _dt
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from urllib.parse import quote
|
|
6
|
+
from typing import TYPE_CHECKING, Dict, Any, List
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING: # imported only by static-type tools
|
|
9
|
+
from ..client import FinBrainClient
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AppRatingsAPI:
|
|
13
|
+
"""
|
|
14
|
+
Mobile-app rating analytics for a single ticker.
|
|
15
|
+
|
|
16
|
+
Example
|
|
17
|
+
-------
|
|
18
|
+
>>> fb.app_ratings.ticker(
|
|
19
|
+
... market="S&P 500",
|
|
20
|
+
... symbol="AMZN",
|
|
21
|
+
... date_from="2024-01-01",
|
|
22
|
+
... date_to="2024-02-02",
|
|
23
|
+
... )["appRatings"][:2]
|
|
24
|
+
[
|
|
25
|
+
{
|
|
26
|
+
"playStoreScore": 3.75,
|
|
27
|
+
"playStoreRatingsCount": 567996,
|
|
28
|
+
"appStoreScore": 4.07,
|
|
29
|
+
"appStoreRatingsCount": 88533,
|
|
30
|
+
"playStoreInstallCount": null,
|
|
31
|
+
"date": "2024-02-02"
|
|
32
|
+
},
|
|
33
|
+
...
|
|
34
|
+
]
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# ------------------------------------------------------------------ #
|
|
38
|
+
def __init__(self, client: "FinBrainClient") -> None:
|
|
39
|
+
self._c = client
|
|
40
|
+
|
|
41
|
+
# ------------------------------------------------------------------ #
|
|
42
|
+
def ticker(
|
|
43
|
+
self,
|
|
44
|
+
market: str,
|
|
45
|
+
symbol: str,
|
|
46
|
+
*,
|
|
47
|
+
date_from: _dt.date | str | None = None,
|
|
48
|
+
date_to: _dt.date | str | None = None,
|
|
49
|
+
as_dataframe: bool = False,
|
|
50
|
+
) -> Dict[str, Any] | pd.DataFrame:
|
|
51
|
+
"""
|
|
52
|
+
Fetch mobile-app ratings for *symbol* in *market*.
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
market :
|
|
57
|
+
Market name **exactly as FinBrain lists it**
|
|
58
|
+
(e.g. ``"S&P 500"``, ``"Germany DAX"``, ``"HK Hang Seng"``).
|
|
59
|
+
Spaces and special characters are accepted; they are URL-encoded
|
|
60
|
+
automatically.
|
|
61
|
+
symbol :
|
|
62
|
+
Ticker symbol, upper-cased before the request.
|
|
63
|
+
date_from, date_to :
|
|
64
|
+
Optional ISO dates (``YYYY-MM-DD``) to bound the range.
|
|
65
|
+
as_dataframe :
|
|
66
|
+
If *True*, return a **pandas.DataFrame** indexed by ``date``;
|
|
67
|
+
otherwise return the raw JSON dict.
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
dict | pandas.DataFrame
|
|
72
|
+
"""
|
|
73
|
+
params: Dict[str, str] = {}
|
|
74
|
+
|
|
75
|
+
if date_from:
|
|
76
|
+
params["dateFrom"] = _to_datestr(date_from)
|
|
77
|
+
if date_to:
|
|
78
|
+
params["dateTo"] = _to_datestr(date_to)
|
|
79
|
+
|
|
80
|
+
market_slug = quote(market, safe="")
|
|
81
|
+
path = f"appratings/{market_slug}/{symbol.upper()}"
|
|
82
|
+
data = self._c._request("GET", path, params=params)
|
|
83
|
+
|
|
84
|
+
if as_dataframe:
|
|
85
|
+
rows: List[Dict[str, Any]] = data.get("appRatings", [])
|
|
86
|
+
df = pd.DataFrame(rows)
|
|
87
|
+
if not df.empty and "date" in df.columns:
|
|
88
|
+
df["date"] = pd.to_datetime(df["date"])
|
|
89
|
+
df.set_index("date", inplace=True)
|
|
90
|
+
return df
|
|
91
|
+
|
|
92
|
+
return data
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# ---------------------------------------------------------------------- #
|
|
96
|
+
# helper #
|
|
97
|
+
# ---------------------------------------------------------------------- #
|
|
98
|
+
def _to_datestr(value: _dt.date | str) -> str:
|
|
99
|
+
"""Convert :pyclass:`~datetime.date` → ``YYYY-MM-DD`` but pass strings."""
|
|
100
|
+
if isinstance(value, _dt.date):
|
|
101
|
+
return value.isoformat()
|
|
102
|
+
return value
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# src/finbrain/endpoints/available.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from typing import TYPE_CHECKING, Literal, List, Dict, Any
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING: # imported only by type-checkers (mypy, pyright…)
|
|
7
|
+
from ..client import FinBrainClient
|
|
8
|
+
|
|
9
|
+
_PType = Literal["daily", "monthly"]
|
|
10
|
+
_ALLOWED: set[str] = {"daily", "monthly"}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AvailableAPI:
|
|
14
|
+
"""
|
|
15
|
+
Wrapper for FinBrain's **/available** endpoints
|
|
16
|
+
-----------------------------------------------
|
|
17
|
+
|
|
18
|
+
• ``/available/markets`` → list supported indices
|
|
19
|
+
• ``/available/tickers/<TYPE>`` → list tickers for that *TYPE*
|
|
20
|
+
|
|
21
|
+
The docs call the path segment “TYPE”; it might be a market name
|
|
22
|
+
(``sp500`` / ``nasdaq``) or something else. We don't guess—caller passes it.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
# ------------------------------------------------------------
|
|
26
|
+
def __init__(self, client: "FinBrainClient") -> None:
|
|
27
|
+
self._c = client # reference to the parent client
|
|
28
|
+
|
|
29
|
+
# ------------------------------------------------------------
|
|
30
|
+
def markets(self) -> List[str]:
|
|
31
|
+
"""
|
|
32
|
+
Return every market index string FinBrain supports.
|
|
33
|
+
|
|
34
|
+
Example
|
|
35
|
+
-------
|
|
36
|
+
>>> fb.available.markets()
|
|
37
|
+
['S&P 500', 'NASDAQ', ...]
|
|
38
|
+
"""
|
|
39
|
+
data = self._c._request("GET", "available/markets")
|
|
40
|
+
|
|
41
|
+
if isinstance(data, List):
|
|
42
|
+
return data
|
|
43
|
+
|
|
44
|
+
return data.get("availableMarkets", [])
|
|
45
|
+
|
|
46
|
+
# ------------------------------------------------------------
|
|
47
|
+
def tickers(
|
|
48
|
+
self,
|
|
49
|
+
prediction_type: _PType,
|
|
50
|
+
*,
|
|
51
|
+
as_dataframe: bool = False,
|
|
52
|
+
) -> List[Dict[str, Any]] | pd.DataFrame:
|
|
53
|
+
"""
|
|
54
|
+
List all tickers for which **FinBrain has predictions** of the given type.
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
prediction_type :
|
|
59
|
+
Either ``"daily"`` (10-day horizon predictions) or
|
|
60
|
+
``"monthly"`` (12-month horizon predictions). Case-insensitive.
|
|
61
|
+
as_dataframe :
|
|
62
|
+
If *True*, return a ``pd.DataFrame``;
|
|
63
|
+
otherwise return the raw list of dicts.
|
|
64
|
+
|
|
65
|
+
Returns
|
|
66
|
+
-------
|
|
67
|
+
list[dict] | pandas.DataFrame
|
|
68
|
+
Each row / dict contains at least::
|
|
69
|
+
|
|
70
|
+
{
|
|
71
|
+
"ticker": "AAPL",
|
|
72
|
+
"name": "Apple Inc.",
|
|
73
|
+
"market": "S&P 500"
|
|
74
|
+
}
|
|
75
|
+
"""
|
|
76
|
+
prediction_type = prediction_type.lower()
|
|
77
|
+
if prediction_type not in _ALLOWED:
|
|
78
|
+
raise ValueError("prediction_type must be 'daily' or 'monthly'")
|
|
79
|
+
|
|
80
|
+
path = f"available/tickers/{prediction_type}"
|
|
81
|
+
data: List[Dict[str, Any]] = self._c._request("GET", path)
|
|
82
|
+
|
|
83
|
+
return pd.DataFrame(data) if as_dataframe else data
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from urllib.parse import quote
|
|
4
|
+
import datetime as _dt
|
|
5
|
+
from typing import TYPE_CHECKING, Dict, Any
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING: # imported only by type-checkers
|
|
8
|
+
from ..client import FinBrainClient
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class HouseTradesAPI:
|
|
12
|
+
"""
|
|
13
|
+
Endpoint
|
|
14
|
+
--------
|
|
15
|
+
``/housetrades/<MARKET>/<TICKER>`` - trading activity of U.S. House
|
|
16
|
+
Representatives for the selected ticker.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# ------------------------------------------------------------------ #
|
|
20
|
+
def __init__(self, client: "FinBrainClient") -> None:
|
|
21
|
+
self._c = client # reference to the parent client
|
|
22
|
+
|
|
23
|
+
# ------------------------------------------------------------------ #
|
|
24
|
+
def ticker(
|
|
25
|
+
self,
|
|
26
|
+
market: str,
|
|
27
|
+
symbol: str,
|
|
28
|
+
*,
|
|
29
|
+
date_from: _dt.date | str | None = None,
|
|
30
|
+
date_to: _dt.date | str | None = None,
|
|
31
|
+
as_dataframe: bool = False,
|
|
32
|
+
) -> Dict[str, Any] | pd.DataFrame:
|
|
33
|
+
"""
|
|
34
|
+
Fetch House-member trades for *symbol* in *market*.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
market :
|
|
39
|
+
Market name **exactly as FinBrain lists it**
|
|
40
|
+
(e.g. ``"S&P 500"``, ``"Germany DAX"``, ``"HK Hang Seng"``).
|
|
41
|
+
Spaces and special characters are accepted; they are URL-encoded
|
|
42
|
+
automatically.
|
|
43
|
+
symbol :
|
|
44
|
+
Ticker symbol; auto-upper-cased.
|
|
45
|
+
date_from, date_to :
|
|
46
|
+
Optional ISO dates (``YYYY-MM-DD``) bounding the returned rows.
|
|
47
|
+
as_dataframe :
|
|
48
|
+
If *True*, return a **pandas.DataFrame** indexed by ``date``;
|
|
49
|
+
otherwise return the raw JSON dict.
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
dict | pandas.DataFrame
|
|
54
|
+
"""
|
|
55
|
+
params: Dict[str, str] = {}
|
|
56
|
+
if date_from:
|
|
57
|
+
params["dateFrom"] = _to_datestr(date_from)
|
|
58
|
+
if date_to:
|
|
59
|
+
params["dateTo"] = _to_datestr(date_to)
|
|
60
|
+
|
|
61
|
+
market_slug = quote(market, safe="")
|
|
62
|
+
path = f"housetrades/{market_slug}/{symbol.upper()}"
|
|
63
|
+
|
|
64
|
+
data: Dict[str, Any] = self._c._request("GET", path, params=params)
|
|
65
|
+
|
|
66
|
+
if as_dataframe:
|
|
67
|
+
rows = data.get("houseTrades", [])
|
|
68
|
+
df = pd.DataFrame(rows)
|
|
69
|
+
if not df.empty and "date" in df.columns:
|
|
70
|
+
df["date"] = pd.to_datetime(df["date"])
|
|
71
|
+
df.set_index("date", inplace=True)
|
|
72
|
+
return df
|
|
73
|
+
|
|
74
|
+
return data
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# ---------------------------------------------------------------------- #
|
|
78
|
+
# helper #
|
|
79
|
+
# ---------------------------------------------------------------------- #
|
|
80
|
+
def _to_datestr(value: _dt.date | str) -> str:
|
|
81
|
+
"""Convert ``datetime.date`` → ``YYYY-MM-DD``; leave strings untouched."""
|
|
82
|
+
return value.isoformat() if isinstance(value, _dt.date) else value
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from urllib.parse import quote
|
|
4
|
+
from typing import TYPE_CHECKING, Dict, Any
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING: # imported only by static-type tools
|
|
7
|
+
from ..client import FinBrainClient
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class InsiderTransactionsAPI:
|
|
11
|
+
"""
|
|
12
|
+
Endpoint
|
|
13
|
+
--------
|
|
14
|
+
``/insidertransactions/<MARKET>/<TICKER>`` - recent Form-4 insider trades
|
|
15
|
+
for the requested ticker.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
# ------------------------------------------------------------------ #
|
|
19
|
+
def __init__(self, client: "FinBrainClient") -> None:
|
|
20
|
+
self._c = client
|
|
21
|
+
|
|
22
|
+
# ------------------------------------------------------------------ #
|
|
23
|
+
def ticker(
|
|
24
|
+
self,
|
|
25
|
+
market: str,
|
|
26
|
+
symbol: str,
|
|
27
|
+
*,
|
|
28
|
+
as_dataframe: bool = False,
|
|
29
|
+
) -> Dict[str, Any] | pd.DataFrame:
|
|
30
|
+
"""
|
|
31
|
+
Insider transactions for *symbol* in *market*.
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
market :
|
|
36
|
+
Market name **exactly as FinBrain lists it**
|
|
37
|
+
(e.g. ``"S&P 500"``, ``"Germany DAX"``, ``"HK Hang Seng"``).
|
|
38
|
+
Spaces and special characters are accepted; they are URL-encoded
|
|
39
|
+
automatically.
|
|
40
|
+
symbol :
|
|
41
|
+
Ticker symbol; converted to upper-case.
|
|
42
|
+
as_dataframe :
|
|
43
|
+
If *True*, return a **pandas.DataFrame** indexed by ``date``;
|
|
44
|
+
otherwise return the raw JSON dict.
|
|
45
|
+
|
|
46
|
+
Returns
|
|
47
|
+
-------
|
|
48
|
+
dict | pandas.DataFrame
|
|
49
|
+
"""
|
|
50
|
+
market_slug = quote(market, safe="")
|
|
51
|
+
path = f"insidertransactions/{market_slug}/{symbol.upper()}"
|
|
52
|
+
data: Dict[str, Any] = self._c._request("GET", path)
|
|
53
|
+
|
|
54
|
+
# --- DataFrame conversion ---
|
|
55
|
+
if as_dataframe:
|
|
56
|
+
rows = data.get("insiderTransactions", [])
|
|
57
|
+
df = pd.DataFrame(rows)
|
|
58
|
+
if not df.empty and "date" in df.columns:
|
|
59
|
+
# examples show dates like "Mar 08 '24" – let pandas parse flexibly
|
|
60
|
+
_fmt = "%b %d '%y" # e.g. Mar 08 '24
|
|
61
|
+
dt = pd.to_datetime(df["date"], format=_fmt, errors="coerce")
|
|
62
|
+
if dt.isna().any(): # fallback if format ever changes
|
|
63
|
+
dt = pd.to_datetime(df["date"], errors="coerce", dayfirst=False)
|
|
64
|
+
df["date"] = dt
|
|
65
|
+
df.set_index("date", inplace=True)
|
|
66
|
+
return df
|
|
67
|
+
|
|
68
|
+
return data
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from urllib.parse import quote
|
|
4
|
+
import datetime as _dt
|
|
5
|
+
from typing import TYPE_CHECKING, Dict, Any, List
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING: # imported only by static type-checkers
|
|
8
|
+
from ..client import FinBrainClient
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LinkedInDataAPI:
|
|
12
|
+
"""
|
|
13
|
+
Endpoint
|
|
14
|
+
--------
|
|
15
|
+
``/linkedindata/<MARKET>/<TICKER>`` - LinkedIn follower / employee-count
|
|
16
|
+
metrics for a single ticker.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# ------------------------------------------------------------------ #
|
|
20
|
+
def __init__(self, client: "FinBrainClient") -> None:
|
|
21
|
+
self._c = client # reference to the parent client
|
|
22
|
+
|
|
23
|
+
# ------------------------------------------------------------------ #
|
|
24
|
+
def ticker(
|
|
25
|
+
self,
|
|
26
|
+
market: str,
|
|
27
|
+
symbol: str,
|
|
28
|
+
*,
|
|
29
|
+
date_from: _dt.date | str | None = None,
|
|
30
|
+
date_to: _dt.date | str | None = None,
|
|
31
|
+
as_dataframe: bool = False,
|
|
32
|
+
) -> Dict[str, Any] | pd.DataFrame:
|
|
33
|
+
"""
|
|
34
|
+
LinkedIn follower- and employee-count metrics for a single ticker.
|
|
35
|
+
|
|
36
|
+
Market names may contain spaces (“S&P 500”, “Germany DAX”, “HK Hang Seng”);
|
|
37
|
+
they are URL-encoded automatically.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
market :
|
|
42
|
+
Market name **exactly as FinBrain lists it**
|
|
43
|
+
(e.g. ``"S&P 500"``, ``"Germany DAX"``, ``"HK Hang Seng"``).
|
|
44
|
+
Spaces and special characters are accepted; they are URL-encoded
|
|
45
|
+
automatically.
|
|
46
|
+
symbol :
|
|
47
|
+
Stock symbol; auto-upper-cased.
|
|
48
|
+
date_from, date_to :
|
|
49
|
+
Optional ``YYYY-MM-DD`` bounds.
|
|
50
|
+
as_dataframe :
|
|
51
|
+
If *True*, return a **pandas.DataFrame** indexed by ``date``;
|
|
52
|
+
otherwise return the raw JSON dict.
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
dict | pandas.DataFrame
|
|
57
|
+
"""
|
|
58
|
+
params: Dict[str, str] = {}
|
|
59
|
+
if date_from:
|
|
60
|
+
params["dateFrom"] = _to_datestr(date_from)
|
|
61
|
+
if date_to:
|
|
62
|
+
params["dateTo"] = _to_datestr(date_to)
|
|
63
|
+
|
|
64
|
+
market_slug = quote(market, safe="")
|
|
65
|
+
path = f"linkedindata/{market_slug}/{symbol.upper()}"
|
|
66
|
+
|
|
67
|
+
data: Dict[str, Any] = self._c._request("GET", path, params=params)
|
|
68
|
+
|
|
69
|
+
if as_dataframe:
|
|
70
|
+
rows: List[Dict[str, Any]] = data.get("linkedinData", [])
|
|
71
|
+
df = pd.DataFrame(rows)
|
|
72
|
+
if not df.empty and "date" in df.columns:
|
|
73
|
+
df["date"] = pd.to_datetime(df["date"])
|
|
74
|
+
df.set_index("date", inplace=True)
|
|
75
|
+
return df
|
|
76
|
+
|
|
77
|
+
return data
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ---------------------------------------------------------------------- #
|
|
81
|
+
# helper #
|
|
82
|
+
# ---------------------------------------------------------------------- #
|
|
83
|
+
def _to_datestr(value: _dt.date | str) -> str:
|
|
84
|
+
"""Convert :class:`datetime.date` → ``YYYY-MM-DD``; leave strings untouched."""
|
|
85
|
+
return value.isoformat() if isinstance(value, _dt.date) else value
|