finbrain-python 0.1.3__tar.gz → 0.1.4__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 (44) hide show
  1. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/PKG-INFO +55 -18
  2. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/README.md +52 -17
  3. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/pyproject.toml +2 -0
  4. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/src/finbrain/__init__.py +4 -3
  5. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/src/finbrain/client.py +4 -0
  6. finbrain_python-0.1.4/src/finbrain/plotting.py +504 -0
  7. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/src/finbrain_python.egg-info/PKG-INFO +55 -18
  8. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/src/finbrain_python.egg-info/SOURCES.txt +1 -3
  9. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/src/finbrain_python.egg-info/requires.txt +2 -0
  10. finbrain_python-0.1.3/cli.py +0 -0
  11. finbrain_python-0.1.3/models.py +0 -0
  12. finbrain_python-0.1.3/utils.py +0 -0
  13. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/.gitattributes +0 -0
  14. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/.github/workflows/ci.yml +0 -0
  15. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/.github/workflows/release.yml +0 -0
  16. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/.gitignore +0 -0
  17. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/LICENSE +0 -0
  18. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/setup.cfg +0 -0
  19. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/src/finbrain/aio/__init__.py +0 -0
  20. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/src/finbrain/aio/client.py +0 -0
  21. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/src/finbrain/endpoints/__init__.py +0 -0
  22. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/src/finbrain/endpoints/analyst_ratings.py +0 -0
  23. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/src/finbrain/endpoints/app_ratings.py +0 -0
  24. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/src/finbrain/endpoints/available.py +0 -0
  25. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/src/finbrain/endpoints/house_trades.py +0 -0
  26. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/src/finbrain/endpoints/insider_transactions.py +0 -0
  27. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/src/finbrain/endpoints/linkedin_data.py +0 -0
  28. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/src/finbrain/endpoints/options.py +0 -0
  29. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/src/finbrain/endpoints/predictions.py +0 -0
  30. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/src/finbrain/endpoints/sentiments.py +0 -0
  31. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/src/finbrain/exceptions.py +0 -0
  32. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/src/finbrain_python.egg-info/dependency_links.txt +0 -0
  33. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/src/finbrain_python.egg-info/top_level.txt +0 -0
  34. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/tests/__init__.py +0 -0
  35. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/tests/conftest.py +0 -0
  36. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/tests/test_analyst_ratings.py +0 -0
  37. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/tests/test_app_ratings.py +0 -0
  38. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/tests/test_available.py +0 -0
  39. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/tests/test_house_trades.py +0 -0
  40. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/tests/test_insider_transactions.py +0 -0
  41. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/tests/test_linkedin_data.py +0 -0
  42. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/tests/test_options.py +0 -0
  43. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/tests/test_predictions.py +0 -0
  44. {finbrain_python-0.1.3 → finbrain_python-0.1.4}/tests/test_sentiments.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: finbrain-python
3
- Version: 0.1.3
3
+ Version: 0.1.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
@@ -10,6 +10,8 @@ License-File: LICENSE
10
10
  Requires-Dist: requests>=2.31
11
11
  Requires-Dist: typer[all]>=0.12
12
12
  Requires-Dist: pandas>=2.2
13
+ Requires-Dist: plotly>=6.0
14
+ Requires-Dist: numpy>=1.22.4
13
15
  Provides-Extra: dev
14
16
  Requires-Dist: pytest; extra == "dev"
15
17
  Requires-Dist: responses; extra == "dev"
@@ -27,7 +29,7 @@ Dynamic: license-file
27
29
  **Official Python client** for the [FinBrain API](https://docs.finbrain.tech).
28
30
  Fetch deep-learning price predictions, sentiment scores, insider trades, LinkedIn metrics, options data and more — with a single import.
29
31
 
30
- *Python ≥ 3.9 • requests & pandas • asyncio optional.*
32
+ *Python ≥ 3.9 • requests, pandas, numpy & plotly • asyncio optional.*
31
33
 
32
34
  ---
33
35
 
@@ -60,19 +62,19 @@ fb.available.tickers("daily", as_dataframe=True)
60
62
  # ---------- app ratings ----------
61
63
  fb.app_ratings.ticker("S&P 500", "AMZN",
62
64
  date_from="2025-01-01",
63
- date_to="2025-05-31",
65
+ date_to="2025-06-30",
64
66
  as_dataframe=True)
65
67
 
66
68
  # ---------- analyst ratings ----------
67
69
  fb.analyst_ratings.ticker("S&P 500", "AMZN",
68
70
  date_from="2025-01-01",
69
- date_to="2025-05-31",
71
+ date_to="2025-06-30",
70
72
  as_dataframe=True)
71
73
 
72
74
  # ---------- house trades ----------
73
75
  fb.house_trades.ticker("S&P 500", "AMZN",
74
76
  date_from="2025-01-01",
75
- date_to="2025-05-31",
77
+ date_to="2025-06-30",
76
78
  as_dataframe=True)
77
79
 
78
80
  # ---------- insider transactions ----------
@@ -81,13 +83,13 @@ fb.insider_transactions.ticker("S&P 500", "AMZN", as_dataframe=True)
81
83
  # ---------- LinkedIn metrics ----------
82
84
  fb.linkedin_data.ticker("S&P 500", "AMZN",
83
85
  date_from="2025-01-01",
84
- date_to="2025-05-31",
86
+ date_to="2025-06-30",
85
87
  as_dataframe=True)
86
88
 
87
89
  # ---------- options put/call ----------
88
90
  fb.options.put_call("S&P 500", "AMZN",
89
91
  date_from="2025-01-01",
90
- date_to="2025-05-31",
92
+ date_to="2025-06-30",
91
93
  as_dataframe=True)
92
94
 
93
95
  # ---------- price predictions ----------
@@ -97,10 +99,43 @@ fb.predictions.ticker("AMZN", as_dataframe=True) # single ticker
97
99
  # ---------- news sentiment ----------
98
100
  fb.sentiments.ticker("S&P 500", "AMZN",
99
101
  date_from="2025-01-01",
100
- date_to="2025-05-31",
102
+ date_to="2025-06-30",
101
103
  as_dataframe=True)
102
104
  ```
103
105
 
106
+ ## 📈 Plotting
107
+
108
+ Plot helpers in a nutshell
109
+
110
+ - `show` – defaults to True, so the chart appears immediately.
111
+
112
+ - `as_json=True` – skips display and returns the figure as a Plotly-JSON string, ready to embed elsewhere.
113
+
114
+ ```
115
+ # ---------- App Ratings Chart - Apple App Store or Google Play Store ----------
116
+ fb.plot.app_ratings("S&P 500", "AMZN",
117
+ store="app", # "play" for Google Play Store
118
+ date_from="2025-01-01",
119
+ date_to="2025-06-30")
120
+
121
+ # ---------- LinkedIn Metrics Chart ----------
122
+ fb.plot.linkedin("S&P 500", "AMZN",
123
+ date_from="2025-01-01", date_to="2025-06-30")
124
+
125
+ # ---------- Put-Call Ratio Chart ----------
126
+ fb.plot.options("S&P 500", "AMZN",
127
+ kind="put_call",
128
+ date_from="2025-01-01", date_to="2025-06-30")
129
+
130
+ # ---------- Predictions Chart ----------
131
+ fb.plot.predictions("AMZN")
132
+
133
+ # ---------- Sentiments Chart ----------
134
+ fb.plot.sentiments("S&P 500", "AMZN",
135
+ date_from="2025-01-01",
136
+ date_to="2025-06-30")
137
+ ```
138
+
104
139
  ## 🔑 Authentication
105
140
 
106
141
  To call the API you need an **API key**, obtained by purchasing a **FinBrain API subscription**.
@@ -115,20 +150,20 @@ from finbrain import FinBrainClient
115
150
  fb = FinBrainClient(api_key="YOUR_KEY")
116
151
  ```
117
152
 
118
- ### Async(currently under development)
153
+ ### Async (currently under development)
119
154
 
120
155
  ```
121
156
  import asyncio, os
122
157
  from finbrain.aio import FinBrainAsyncClient async
123
- def main():
124
- async with FinBrainAsyncClient(api_key=os.getenv("FINBRAIN_API_KEY")) as fb:
125
- data = await fb.sentiments.ticker("sp500", "AMZN")
158
+ def main():
159
+ async with FinBrainAsyncClient(api_key=os.getenv("FINBRAIN_API_KEY")) as fb:
160
+ data = await fb.sentiments.ticker("S&P 500", "AMZN")
126
161
  print(list(data["sentimentAnalysis"].items())[:3])
127
162
 
128
- asyncio.run(main())`
163
+ asyncio.run(main())
129
164
  ```
130
165
 
131
- ### CLI(currently under development)
166
+ ### CLI (currently under development)
132
167
 
133
168
  ```
134
169
  export FINBRAIN_API_KEY=your_key
@@ -163,7 +198,7 @@ from finbrain.exceptions import BadRequest
163
198
  try:
164
199
  fb.predictions.ticker("MSFT", prediction_type="weekly")
165
200
  except BadRequest as exc:
166
- print("Invalid parameters:", exc)`
201
+ print("Invalid parameters:", exc)
167
202
  ```
168
203
 
169
204
  | HTTP status | Exception class | Meaning |
@@ -184,7 +219,7 @@ except BadRequest as exc:
184
219
  - Version auto-generated from Git tags (setuptools-scm)
185
220
 
186
221
  ```
187
- git tag -a v0.2.0 -m "Add options.chain endpoint"
222
+ git tag -a 0.2.0 -m "Add options.chain endpoint"
188
223
  git push --tags # GitHub Actions builds & uploads to PyPI
189
224
  ```
190
225
 
@@ -193,11 +228,13 @@ git push --tags # GitHub Actions builds & uploads to PyPI
193
228
  ## 🧑‍💻 Development
194
229
 
195
230
  ```
196
- git clone https://github.com/finbrain-tech/finbrain-python cd finbrain-python
231
+ git clone https://github.com/finbrain-tech/finbrain-python
232
+ cd finbrain-python
197
233
  python -m venv .venv && source .venv/bin/activate
198
234
  pip install -e .[dev]
199
235
 
200
- ruff check . # lint / format pytest -q # unit tests (mocked)
236
+ ruff check . # lint / format
237
+ pytest -q # unit tests (mocked)
201
238
  ```
202
239
 
203
240
  ### Live integration test(currently under development)
@@ -7,7 +7,7 @@
7
7
  **Official Python client** for the [FinBrain API](https://docs.finbrain.tech).
8
8
  Fetch deep-learning price predictions, sentiment scores, insider trades, LinkedIn metrics, options data and more — with a single import.
9
9
 
10
- *Python ≥ 3.9 • requests & pandas • asyncio optional.*
10
+ *Python ≥ 3.9 • requests, pandas, numpy & plotly • asyncio optional.*
11
11
 
12
12
  ---
13
13
 
@@ -40,19 +40,19 @@ fb.available.tickers("daily", as_dataframe=True)
40
40
  # ---------- app ratings ----------
41
41
  fb.app_ratings.ticker("S&P 500", "AMZN",
42
42
  date_from="2025-01-01",
43
- date_to="2025-05-31",
43
+ date_to="2025-06-30",
44
44
  as_dataframe=True)
45
45
 
46
46
  # ---------- analyst ratings ----------
47
47
  fb.analyst_ratings.ticker("S&P 500", "AMZN",
48
48
  date_from="2025-01-01",
49
- date_to="2025-05-31",
49
+ date_to="2025-06-30",
50
50
  as_dataframe=True)
51
51
 
52
52
  # ---------- house trades ----------
53
53
  fb.house_trades.ticker("S&P 500", "AMZN",
54
54
  date_from="2025-01-01",
55
- date_to="2025-05-31",
55
+ date_to="2025-06-30",
56
56
  as_dataframe=True)
57
57
 
58
58
  # ---------- insider transactions ----------
@@ -61,13 +61,13 @@ fb.insider_transactions.ticker("S&P 500", "AMZN", as_dataframe=True)
61
61
  # ---------- LinkedIn metrics ----------
62
62
  fb.linkedin_data.ticker("S&P 500", "AMZN",
63
63
  date_from="2025-01-01",
64
- date_to="2025-05-31",
64
+ date_to="2025-06-30",
65
65
  as_dataframe=True)
66
66
 
67
67
  # ---------- options put/call ----------
68
68
  fb.options.put_call("S&P 500", "AMZN",
69
69
  date_from="2025-01-01",
70
- date_to="2025-05-31",
70
+ date_to="2025-06-30",
71
71
  as_dataframe=True)
72
72
 
73
73
  # ---------- price predictions ----------
@@ -77,10 +77,43 @@ fb.predictions.ticker("AMZN", as_dataframe=True) # single ticker
77
77
  # ---------- news sentiment ----------
78
78
  fb.sentiments.ticker("S&P 500", "AMZN",
79
79
  date_from="2025-01-01",
80
- date_to="2025-05-31",
80
+ date_to="2025-06-30",
81
81
  as_dataframe=True)
82
82
  ```
83
83
 
84
+ ## 📈 Plotting
85
+
86
+ Plot helpers in a nutshell
87
+
88
+ - `show` – defaults to True, so the chart appears immediately.
89
+
90
+ - `as_json=True` – skips display and returns the figure as a Plotly-JSON string, ready to embed elsewhere.
91
+
92
+ ```
93
+ # ---------- App Ratings Chart - Apple App Store or Google Play Store ----------
94
+ fb.plot.app_ratings("S&P 500", "AMZN",
95
+ store="app", # "play" for Google Play Store
96
+ date_from="2025-01-01",
97
+ date_to="2025-06-30")
98
+
99
+ # ---------- LinkedIn Metrics Chart ----------
100
+ fb.plot.linkedin("S&P 500", "AMZN",
101
+ date_from="2025-01-01", date_to="2025-06-30")
102
+
103
+ # ---------- Put-Call Ratio Chart ----------
104
+ fb.plot.options("S&P 500", "AMZN",
105
+ kind="put_call",
106
+ date_from="2025-01-01", date_to="2025-06-30")
107
+
108
+ # ---------- Predictions Chart ----------
109
+ fb.plot.predictions("AMZN")
110
+
111
+ # ---------- Sentiments Chart ----------
112
+ fb.plot.sentiments("S&P 500", "AMZN",
113
+ date_from="2025-01-01",
114
+ date_to="2025-06-30")
115
+ ```
116
+
84
117
  ## 🔑 Authentication
85
118
 
86
119
  To call the API you need an **API key**, obtained by purchasing a **FinBrain API subscription**.
@@ -95,20 +128,20 @@ from finbrain import FinBrainClient
95
128
  fb = FinBrainClient(api_key="YOUR_KEY")
96
129
  ```
97
130
 
98
- ### Async(currently under development)
131
+ ### Async (currently under development)
99
132
 
100
133
  ```
101
134
  import asyncio, os
102
135
  from finbrain.aio import FinBrainAsyncClient async
103
- def main():
104
- async with FinBrainAsyncClient(api_key=os.getenv("FINBRAIN_API_KEY")) as fb:
105
- data = await fb.sentiments.ticker("sp500", "AMZN")
136
+ def main():
137
+ async with FinBrainAsyncClient(api_key=os.getenv("FINBRAIN_API_KEY")) as fb:
138
+ data = await fb.sentiments.ticker("S&P 500", "AMZN")
106
139
  print(list(data["sentimentAnalysis"].items())[:3])
107
140
 
108
- asyncio.run(main())`
141
+ asyncio.run(main())
109
142
  ```
110
143
 
111
- ### CLI(currently under development)
144
+ ### CLI (currently under development)
112
145
 
113
146
  ```
114
147
  export FINBRAIN_API_KEY=your_key
@@ -143,7 +176,7 @@ from finbrain.exceptions import BadRequest
143
176
  try:
144
177
  fb.predictions.ticker("MSFT", prediction_type="weekly")
145
178
  except BadRequest as exc:
146
- print("Invalid parameters:", exc)`
179
+ print("Invalid parameters:", exc)
147
180
  ```
148
181
 
149
182
  | HTTP status | Exception class | Meaning |
@@ -164,7 +197,7 @@ except BadRequest as exc:
164
197
  - Version auto-generated from Git tags (setuptools-scm)
165
198
 
166
199
  ```
167
- git tag -a v0.2.0 -m "Add options.chain endpoint"
200
+ git tag -a 0.2.0 -m "Add options.chain endpoint"
168
201
  git push --tags # GitHub Actions builds & uploads to PyPI
169
202
  ```
170
203
 
@@ -173,11 +206,13 @@ git push --tags # GitHub Actions builds & uploads to PyPI
173
206
  ## 🧑‍💻 Development
174
207
 
175
208
  ```
176
- git clone https://github.com/finbrain-tech/finbrain-python cd finbrain-python
209
+ git clone https://github.com/finbrain-tech/finbrain-python
210
+ cd finbrain-python
177
211
  python -m venv .venv && source .venv/bin/activate
178
212
  pip install -e .[dev]
179
213
 
180
- ruff check . # lint / format pytest -q # unit tests (mocked)
214
+ ruff check . # lint / format
215
+ pytest -q # unit tests (mocked)
181
216
  ```
182
217
 
183
218
  ### Live integration test(currently under development)
@@ -18,6 +18,8 @@ dependencies = [
18
18
  "requests>=2.31",
19
19
  "typer[all]>=0.12",
20
20
  "pandas>=2.2",
21
+ "plotly>=6.0",
22
+ "numpy>=1.22.4",
21
23
  ]
22
24
 
23
25
  [tool.setuptools.packages.find]
@@ -1,11 +1,12 @@
1
1
  """FinBrain Python SDK."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from importlib.metadata import PackageNotFoundError, version as _v
5
6
 
6
- try: # installed (wheel / sdist / -e)
7
- __version__ = _v("finbrain-python") # ← distribution name on PyPI
8
- except PackageNotFoundError: # fresh git clone, no install
7
+ try: # installed (wheel / sdist / -e)
8
+ __version__ = _v("finbrain-python") # ← distribution name on PyPI
9
+ except PackageNotFoundError: # fresh git clone, no install
9
10
  __version__ = "0.0.0.dev0"
10
11
 
11
12
  from .client import FinBrainClient
@@ -6,6 +6,7 @@ from typing import Any, Dict, Optional
6
6
  import requests
7
7
  from urllib.parse import urljoin
8
8
 
9
+ from .plotting import _PlotNamespace
9
10
  from .exceptions import http_error_to_exception, InvalidResponse
10
11
  from . import __version__
11
12
 
@@ -52,6 +53,9 @@ class FinBrainClient:
52
53
  self.timeout = timeout
53
54
  self.retries = retries
54
55
 
56
+ # expose plotting under .plot
57
+ self.plot = _PlotNamespace(self)
58
+
55
59
  # wire endpoint helpers
56
60
  self.available = AvailableAPI(self)
57
61
  self.predictions = PredictionsAPI(self)
@@ -0,0 +1,504 @@
1
+ # src/finbrain/plotting.py
2
+ from __future__ import annotations
3
+ from typing import Union, TYPE_CHECKING
4
+ import numpy as np
5
+ import pandas as pd
6
+ import plotly.graph_objects as go
7
+
8
+ if TYPE_CHECKING: # imported only by static-type tools
9
+ from .client import FinBrainClient
10
+
11
+
12
+ class _PlotNamespace:
13
+ """
14
+ Internal helper that hangs off FinBrainClient as `client.plot`.
15
+ Each public method should return either a Plotly Figure or a JSON string.
16
+ """
17
+
18
+ def __init__(self, parent: "FinBrainClient"):
19
+ self._fb = parent # keep a reference to the main client
20
+
21
+ # ────────────────────────────────────────────────────────────────────────────
22
+ # App-ratings plot • bars = counts • lines = scores
23
+ # ────────────────────────────────────────────────────────────────────────────
24
+ def app_ratings(
25
+ self,
26
+ market: str,
27
+ ticker: str,
28
+ *,
29
+ store: str = "play",
30
+ date_from: str | None = None,
31
+ date_to: str | None = None,
32
+ as_json: bool = False,
33
+ show: bool = True,
34
+ template: str = "plotly_dark",
35
+ **kwargs,
36
+ ):
37
+ """
38
+ Plot ratings for a single mobile store (Google Play **or** Apple App Store).
39
+
40
+ Bars → ratings count • primary y-axis (auto-scaled)
41
+ Line → average score • secondary y-axis (auto-scaled within 0-5)
42
+
43
+ Parameters
44
+ ----------
45
+ store : {'play', 'app'}, default 'play'
46
+ Which store to visualise.
47
+ Other args/kwargs identical to the other plotting wrappers.
48
+ """
49
+ # 1) pull data
50
+ df = self._fb.app_ratings.ticker(
51
+ market,
52
+ ticker,
53
+ date_from=date_from,
54
+ date_to=date_to,
55
+ as_dataframe=True,
56
+ **kwargs,
57
+ )
58
+
59
+ # 2) pick columns & colours
60
+ s = store.lower()
61
+ if s in ("play", "playstore", "google"):
62
+ count_col, score_col = "playStoreRatingsCount", "playStoreScore"
63
+ count_name, score_name = "Play Store Ratings Count", "Play Store Score"
64
+ count_color, score_color = "rgba(0,190,0,0.65)", "#02d2ff"
65
+ elif s in ("app", "appstore", "apple"):
66
+ count_col, score_col = "appStoreRatingsCount", "appStoreScore"
67
+ count_name, score_name = "App Store Ratings Count", "App Store Score"
68
+ count_color, score_color = "rgba(0,190,0,0.65)", "#02d2ff"
69
+ else:
70
+ raise ValueError("store must be 'play' or 'app'")
71
+
72
+ # 3) dynamic axis ranges
73
+ max_cnt = float(df[count_col].max())
74
+ min_cnt = float(df[count_col].min())
75
+
76
+ # raw span; fall back to max_cnt when all bars are equal
77
+ span = max_cnt - min_cnt
78
+ pad = (span if span else max_cnt) * 0.10 # 10 % of the data spread
79
+
80
+ cnt_lower = max(0.0, min_cnt - pad)
81
+ cnt_upper = max_cnt + pad
82
+
83
+ # scores (secondary axis) – same as before
84
+ score_min, score_max = float(df[score_col].min()), float(df[score_col].max())
85
+ pad = 0.25
86
+ score_lower = max(0, score_min - pad)
87
+ score_upper = min(5, score_max + pad)
88
+
89
+ # 4) build figure
90
+ fig = go.Figure(
91
+ layout=dict(
92
+ template=template,
93
+ title=f"{score_name.split()[0]} · {ticker}",
94
+ hovermode="x unified",
95
+ )
96
+ )
97
+
98
+ fig.add_bar(
99
+ name=count_name, x=df.index, y=df[count_col], marker_color=count_color
100
+ )
101
+ fig.add_scatter(
102
+ name=score_name,
103
+ x=df.index,
104
+ y=df[score_col],
105
+ mode="lines",
106
+ line=dict(width=2, color=score_color),
107
+ yaxis="y2",
108
+ )
109
+
110
+ fig.update_layout(
111
+ xaxis_title="Date",
112
+ yaxis=dict(
113
+ title="Ratings Count",
114
+ range=[cnt_lower, cnt_upper],
115
+ fixedrange=True,
116
+ showgrid=True,
117
+ ),
118
+ yaxis2=dict(
119
+ title="Score",
120
+ overlaying="y",
121
+ side="right",
122
+ range=[score_lower, score_upper],
123
+ fixedrange=True,
124
+ showgrid=False,
125
+ zeroline=False,
126
+ ),
127
+ )
128
+
129
+ # 5) show / return
130
+ if show and not as_json:
131
+ fig.show()
132
+ return None
133
+ return fig.to_json() if as_json else fig
134
+
135
+ # ────────────────────────────────────────────────────────────────────────────
136
+ # LinkedIn plot • bars = employeeCount • line = followersCount
137
+ # ────────────────────────────────────────────────────────────────────────────
138
+ def linkedin(
139
+ self,
140
+ market: str,
141
+ ticker: str,
142
+ *,
143
+ date_from: str | None = None,
144
+ date_to: str | None = None,
145
+ as_json: bool = False,
146
+ show: bool = True,
147
+ template: str = "plotly_dark",
148
+ **kwargs,
149
+ ):
150
+ """
151
+ Plot LinkedIn company metrics.
152
+
153
+ * **Bars** → ``employeeCount`` (primary y-axis)
154
+ * **Line** → ``followersCount`` (secondary y-axis)
155
+ """
156
+ df = self._fb.linkedin_data.ticker(
157
+ market,
158
+ ticker,
159
+ date_from=date_from,
160
+ date_to=date_to,
161
+ as_dataframe=True,
162
+ **kwargs,
163
+ )
164
+
165
+ fig = go.Figure(
166
+ layout=dict(
167
+ template=template,
168
+ title=f"LinkedIn Metrics · {ticker}",
169
+ hovermode="x unified",
170
+ )
171
+ )
172
+
173
+ # employees (bars)
174
+ fig.add_bar(
175
+ name="Employees",
176
+ x=df.index,
177
+ y=df["employeeCount"],
178
+ marker_color="rgba(0,190,0,0.6)",
179
+ )
180
+
181
+ # followers (line on secondary axis)
182
+ fig.add_scatter(
183
+ name="Followers",
184
+ x=df.index,
185
+ y=df["followersCount"],
186
+ mode="lines",
187
+ line=dict(width=2, color="#f9c80e"),
188
+ yaxis="y2",
189
+ )
190
+
191
+ fig.update_layout(
192
+ xaxis_title="Date",
193
+ yaxis=dict(title="Employee Count", showgrid=True),
194
+ yaxis2=dict(
195
+ title="Follower Count",
196
+ overlaying="y",
197
+ side="right",
198
+ showgrid=False,
199
+ zeroline=False,
200
+ ),
201
+ )
202
+
203
+ if show and not as_json:
204
+ fig.show()
205
+ return None
206
+ return fig.to_json() if as_json else fig
207
+
208
+ # --------------------------------------------------------------------- #
209
+ # Sentiment → green/red bar #
210
+ # --------------------------------------------------------------------- #
211
+ def sentiments(
212
+ self,
213
+ market: str,
214
+ ticker: str,
215
+ *,
216
+ date_from: str | None = None,
217
+ date_to: str | None = None,
218
+ as_json: bool = False,
219
+ show: bool = True,
220
+ template: str = "plotly_dark",
221
+ **kw,
222
+ ) -> Union[go.Figure, str]:
223
+ """
224
+ Visualise FinBrain news-sentiment scores for a single ticker.
225
+
226
+ A green bar represents a non-negative score (bullish news); a red
227
+ bar represents a negative score (bearish news). Bars are plotted on
228
+ the primary y-axis, with dates on the x-axis.
229
+
230
+ Parameters
231
+ ----------
232
+ market : str
233
+ Market identifier (e.g. ``"S&P 500"``).
234
+ ticker : str
235
+ Ticker symbol (e.g. ``"AMZN"``).
236
+ date_from, date_to : str or None, optional
237
+ Inclusive date range in ``YYYY-MM-DD`` format. If omitted,
238
+ FinBrain returns its full available range.
239
+ as_json : bool, default ``False``
240
+ • ``False`` → return a :class:`plotly.graph_objects.Figure`.
241
+ • ``True`` → return ``figure.to_json()`` (``str``).
242
+ show : bool, default ``True``
243
+ If ``True`` *and* ``as_json=False``, immediately display the
244
+ figure via :meth:`plotly.graph_objects.Figure.show`. When
245
+ ``as_json=True`` this flag is ignored.
246
+ template : str, default ``"plotly_dark"``
247
+ Name of a built-in Plotly template.
248
+ **kwargs
249
+ Passed straight through to
250
+ :meth:`FinBrainClient.sentiments.ticker`.
251
+
252
+ Returns
253
+ -------
254
+ plotly.graph_objects.Figure or str or None
255
+ *Figure*: when ``as_json=False`` **and** ``show=False``
256
+ *str* : JSON representation when ``as_json=True``
257
+ *None* : when ``show=True`` and the figure is already shown.
258
+
259
+ Examples
260
+ --------
261
+ >>> fb.plot.sentiments("S&P 500", "AMZN",
262
+ ... date_from="2025-01-01",
263
+ ... date_to="2025-05-31")
264
+ """
265
+ df: pd.DataFrame = self._fb.sentiments.ticker(
266
+ market,
267
+ ticker,
268
+ date_from=date_from,
269
+ date_to=date_to,
270
+ as_dataframe=True,
271
+ **kw,
272
+ )
273
+
274
+ # 2) build colour array: green for ≥0, red for <0
275
+ colors = np.where(
276
+ df["sentiment"] >= 0, "rgba(0,190,0,0.8)", "rgba(190,0,0,0.8)"
277
+ )
278
+
279
+ # 3) bar chart (index on x-axis, sentiment on y-axis)
280
+ fig = go.Figure(
281
+ data=[
282
+ go.Bar(
283
+ x=df.index,
284
+ y=df["sentiment"],
285
+ marker_color=colors,
286
+ hovertemplate="<b>%{x|%Y-%m-%d}</b><br>Sentiment: %{y:.3f}<extra></extra>",
287
+ )
288
+ ],
289
+ layout=dict(
290
+ template=template,
291
+ title=f"News Sentiment · {ticker}",
292
+ xaxis_title="Date",
293
+ yaxis_title="Sentiment Score",
294
+ hovermode="x unified",
295
+ ),
296
+ )
297
+
298
+ if show and not as_json: # don't “show” raw JSON
299
+ fig.show()
300
+ return None # <- silences the echo
301
+
302
+ return fig.to_json() if as_json else fig
303
+
304
+ # --------------------------------------------------------------------- #
305
+ # Put/Call ratios → stacked bars + ratio line #
306
+ # --------------------------------------------------------------------- #
307
+ def options(
308
+ self,
309
+ market: str,
310
+ ticker: str,
311
+ *,
312
+ kind: str = "put_call",
313
+ date_from=None,
314
+ date_to=None,
315
+ as_json=False,
316
+ show=True,
317
+ template="plotly_dark",
318
+ **kw,
319
+ ):
320
+ """
321
+ Plot options-market activity for a given ticker.
322
+
323
+ Currently implemented ``kind`` values
324
+ --------------------------------------
325
+ ``"put_call"`` (default)
326
+ *Stacked* bars of ``callCount`` (green, bottom) and
327
+ ``putCount`` (red, top) plus a yellow line for the ``ratio``
328
+ on a secondary y-axis.
329
+
330
+ Additional kinds can be added in future without changing the
331
+ public API—just extend the internal ``elif`` block.
332
+
333
+ Parameters
334
+ ----------
335
+ market, ticker : str
336
+ Market identifier and ticker symbol.
337
+ kind : {'put_call', ...}, default ``"put_call"``
338
+ Which visualisation to render. Unknown values raise
339
+ :class:`ValueError`.
340
+ date_from, date_to, as_json, show, template, **kwargs
341
+ Same semantics as :pymeth:`~_PlotNamespace.sentiments`.
342
+
343
+ Returns
344
+ -------
345
+ plotly.graph_objects.Figure or str or None
346
+ As described for :pymeth:`~_PlotNamespace.sentiments`.
347
+
348
+ Examples
349
+ --------
350
+ >>> fb.plot.options("S&P 500", "AMZN",
351
+ ... kind="put_call",
352
+ ... date_from="2025-01-01",
353
+ ... date_to="2025-05-31")
354
+ """
355
+ if kind == "put_call":
356
+ df = self._fb.options.put_call(
357
+ market,
358
+ ticker,
359
+ date_from=date_from,
360
+ date_to=date_to,
361
+ as_dataframe=True,
362
+ **kw,
363
+ )
364
+ fig = self._plot_put_call(df, ticker, template) # helper below
365
+
366
+ if show and not as_json:
367
+ fig.show()
368
+ return None # <- silences the echo
369
+
370
+ return fig.to_json() if as_json else fig
371
+
372
+ # --------------------------------------------------------------------- #
373
+ # Predictions → price + CI band #
374
+ # --------------------------------------------------------------------- #
375
+ def predictions(
376
+ self,
377
+ ticker: str,
378
+ *,
379
+ prediction_type: str = "daily",
380
+ as_json=False,
381
+ show=True,
382
+ template="plotly_dark",
383
+ **kw,
384
+ ):
385
+ """
386
+ Plot FinBrain price predictions with confidence intervals.
387
+
388
+ The figure shows the predicted price (solid line) and a shaded
389
+ confidence band between the upper and lower bounds.
390
+
391
+ Parameters
392
+ ----------
393
+ ticker : str
394
+ Ticker symbol.
395
+ prediction_type : {'daily', 'monthly'}, default ``"daily"``
396
+ Granularity of the prediction data requested from FinBrain.
397
+ as_json, show, template, **kwargs
398
+ Same semantics as :pymeth:`~_PlotNamespace.sentiments`.
399
+
400
+ Returns
401
+ -------
402
+ plotly.graph_objects.Figure or str or None
403
+ As described for :pymeth:`~_PlotNamespace.sentiments`.
404
+
405
+ Examples
406
+ --------
407
+ >>> fb.plot.predictions("AMZN", prediction_type="monthly")
408
+ """
409
+ df = self._fb.predictions.ticker(
410
+ ticker, prediction_type=prediction_type, as_dataframe=True, **kw
411
+ )
412
+
413
+ fig = go.Figure(
414
+ layout=dict(
415
+ template=template,
416
+ title=f"Price Prediction · {ticker}",
417
+ xaxis_title="Date",
418
+ yaxis_title="Price",
419
+ hovermode="x unified",
420
+ )
421
+ )
422
+
423
+ # add the three lines
424
+ fig.add_scatter(x=df.index, y=df["main"], mode="lines", name="Predicted")
425
+ fig.add_scatter(
426
+ x=df.index,
427
+ y=df["upper"],
428
+ mode="lines",
429
+ name="Upper CI",
430
+ line=dict(width=0),
431
+ showlegend=False,
432
+ )
433
+ fig.add_scatter(
434
+ x=df.index,
435
+ y=df["lower"],
436
+ mode="lines",
437
+ name="Lower CI",
438
+ line=dict(width=0),
439
+ fill="tonexty",
440
+ fillcolor="rgba(2,210,255,0.2)",
441
+ showlegend=False,
442
+ )
443
+
444
+ if show and not as_json:
445
+ fig.show()
446
+ return None # <- silences the echo
447
+
448
+ return fig.to_json() if as_json else fig
449
+
450
+ # --------------------------------------------------------------------- #
451
+ # TODO: insider_transactions, house_trades, analyst_ratings ... #
452
+ # Add more methods here following the same pattern. #
453
+ # --------------------------------------------------------------------- #
454
+
455
+ @staticmethod
456
+ def _plot_put_call(df, ticker, template):
457
+ fig = go.Figure(
458
+ layout=dict(
459
+ template=template,
460
+ title=f"Options Activity · {ticker}",
461
+ hovermode="x unified",
462
+ barmode="stack",
463
+ )
464
+ )
465
+
466
+ # Calls (green) - added first so it sits *below* in the stack
467
+ fig.add_bar(
468
+ name="Calls",
469
+ x=df.index,
470
+ y=df["callCount"],
471
+ marker_color="rgba(0,190,0,0.6)",
472
+ )
473
+ # Puts (red) - added second so it appears *on top* of Calls
474
+ fig.add_bar(
475
+ name="Puts", x=df.index, y=df["putCount"], marker_color="rgba(190,0,0,0.6)"
476
+ )
477
+ # Put/Call ratio line (secondary axis)
478
+ fig.add_scatter(
479
+ name="Put/Call Ratio",
480
+ x=df.index,
481
+ y=df["ratio"],
482
+ mode="lines",
483
+ line=dict(width=2, color="#F9C80E"),
484
+ yaxis="y2",
485
+ )
486
+
487
+ # axes & layout tweaks
488
+ fig.update_layout(
489
+ xaxis_title="Date",
490
+ yaxis=dict(
491
+ title="Volume",
492
+ showgrid=True,
493
+ ),
494
+ yaxis2=dict(
495
+ title="Ratio",
496
+ overlaying="y",
497
+ side="right",
498
+ rangemode="tozero",
499
+ showgrid=False,
500
+ zeroline=False,
501
+ ),
502
+ )
503
+
504
+ return fig
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: finbrain-python
3
- Version: 0.1.3
3
+ Version: 0.1.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
@@ -10,6 +10,8 @@ License-File: LICENSE
10
10
  Requires-Dist: requests>=2.31
11
11
  Requires-Dist: typer[all]>=0.12
12
12
  Requires-Dist: pandas>=2.2
13
+ Requires-Dist: plotly>=6.0
14
+ Requires-Dist: numpy>=1.22.4
13
15
  Provides-Extra: dev
14
16
  Requires-Dist: pytest; extra == "dev"
15
17
  Requires-Dist: responses; extra == "dev"
@@ -27,7 +29,7 @@ Dynamic: license-file
27
29
  **Official Python client** for the [FinBrain API](https://docs.finbrain.tech).
28
30
  Fetch deep-learning price predictions, sentiment scores, insider trades, LinkedIn metrics, options data and more — with a single import.
29
31
 
30
- *Python ≥ 3.9 • requests & pandas • asyncio optional.*
32
+ *Python ≥ 3.9 • requests, pandas, numpy & plotly • asyncio optional.*
31
33
 
32
34
  ---
33
35
 
@@ -60,19 +62,19 @@ fb.available.tickers("daily", as_dataframe=True)
60
62
  # ---------- app ratings ----------
61
63
  fb.app_ratings.ticker("S&P 500", "AMZN",
62
64
  date_from="2025-01-01",
63
- date_to="2025-05-31",
65
+ date_to="2025-06-30",
64
66
  as_dataframe=True)
65
67
 
66
68
  # ---------- analyst ratings ----------
67
69
  fb.analyst_ratings.ticker("S&P 500", "AMZN",
68
70
  date_from="2025-01-01",
69
- date_to="2025-05-31",
71
+ date_to="2025-06-30",
70
72
  as_dataframe=True)
71
73
 
72
74
  # ---------- house trades ----------
73
75
  fb.house_trades.ticker("S&P 500", "AMZN",
74
76
  date_from="2025-01-01",
75
- date_to="2025-05-31",
77
+ date_to="2025-06-30",
76
78
  as_dataframe=True)
77
79
 
78
80
  # ---------- insider transactions ----------
@@ -81,13 +83,13 @@ fb.insider_transactions.ticker("S&P 500", "AMZN", as_dataframe=True)
81
83
  # ---------- LinkedIn metrics ----------
82
84
  fb.linkedin_data.ticker("S&P 500", "AMZN",
83
85
  date_from="2025-01-01",
84
- date_to="2025-05-31",
86
+ date_to="2025-06-30",
85
87
  as_dataframe=True)
86
88
 
87
89
  # ---------- options put/call ----------
88
90
  fb.options.put_call("S&P 500", "AMZN",
89
91
  date_from="2025-01-01",
90
- date_to="2025-05-31",
92
+ date_to="2025-06-30",
91
93
  as_dataframe=True)
92
94
 
93
95
  # ---------- price predictions ----------
@@ -97,10 +99,43 @@ fb.predictions.ticker("AMZN", as_dataframe=True) # single ticker
97
99
  # ---------- news sentiment ----------
98
100
  fb.sentiments.ticker("S&P 500", "AMZN",
99
101
  date_from="2025-01-01",
100
- date_to="2025-05-31",
102
+ date_to="2025-06-30",
101
103
  as_dataframe=True)
102
104
  ```
103
105
 
106
+ ## 📈 Plotting
107
+
108
+ Plot helpers in a nutshell
109
+
110
+ - `show` – defaults to True, so the chart appears immediately.
111
+
112
+ - `as_json=True` – skips display and returns the figure as a Plotly-JSON string, ready to embed elsewhere.
113
+
114
+ ```
115
+ # ---------- App Ratings Chart - Apple App Store or Google Play Store ----------
116
+ fb.plot.app_ratings("S&P 500", "AMZN",
117
+ store="app", # "play" for Google Play Store
118
+ date_from="2025-01-01",
119
+ date_to="2025-06-30")
120
+
121
+ # ---------- LinkedIn Metrics Chart ----------
122
+ fb.plot.linkedin("S&P 500", "AMZN",
123
+ date_from="2025-01-01", date_to="2025-06-30")
124
+
125
+ # ---------- Put-Call Ratio Chart ----------
126
+ fb.plot.options("S&P 500", "AMZN",
127
+ kind="put_call",
128
+ date_from="2025-01-01", date_to="2025-06-30")
129
+
130
+ # ---------- Predictions Chart ----------
131
+ fb.plot.predictions("AMZN")
132
+
133
+ # ---------- Sentiments Chart ----------
134
+ fb.plot.sentiments("S&P 500", "AMZN",
135
+ date_from="2025-01-01",
136
+ date_to="2025-06-30")
137
+ ```
138
+
104
139
  ## 🔑 Authentication
105
140
 
106
141
  To call the API you need an **API key**, obtained by purchasing a **FinBrain API subscription**.
@@ -115,20 +150,20 @@ from finbrain import FinBrainClient
115
150
  fb = FinBrainClient(api_key="YOUR_KEY")
116
151
  ```
117
152
 
118
- ### Async(currently under development)
153
+ ### Async (currently under development)
119
154
 
120
155
  ```
121
156
  import asyncio, os
122
157
  from finbrain.aio import FinBrainAsyncClient async
123
- def main():
124
- async with FinBrainAsyncClient(api_key=os.getenv("FINBRAIN_API_KEY")) as fb:
125
- data = await fb.sentiments.ticker("sp500", "AMZN")
158
+ def main():
159
+ async with FinBrainAsyncClient(api_key=os.getenv("FINBRAIN_API_KEY")) as fb:
160
+ data = await fb.sentiments.ticker("S&P 500", "AMZN")
126
161
  print(list(data["sentimentAnalysis"].items())[:3])
127
162
 
128
- asyncio.run(main())`
163
+ asyncio.run(main())
129
164
  ```
130
165
 
131
- ### CLI(currently under development)
166
+ ### CLI (currently under development)
132
167
 
133
168
  ```
134
169
  export FINBRAIN_API_KEY=your_key
@@ -163,7 +198,7 @@ from finbrain.exceptions import BadRequest
163
198
  try:
164
199
  fb.predictions.ticker("MSFT", prediction_type="weekly")
165
200
  except BadRequest as exc:
166
- print("Invalid parameters:", exc)`
201
+ print("Invalid parameters:", exc)
167
202
  ```
168
203
 
169
204
  | HTTP status | Exception class | Meaning |
@@ -184,7 +219,7 @@ except BadRequest as exc:
184
219
  - Version auto-generated from Git tags (setuptools-scm)
185
220
 
186
221
  ```
187
- git tag -a v0.2.0 -m "Add options.chain endpoint"
222
+ git tag -a 0.2.0 -m "Add options.chain endpoint"
188
223
  git push --tags # GitHub Actions builds & uploads to PyPI
189
224
  ```
190
225
 
@@ -193,11 +228,13 @@ git push --tags # GitHub Actions builds & uploads to PyPI
193
228
  ## 🧑‍💻 Development
194
229
 
195
230
  ```
196
- git clone https://github.com/finbrain-tech/finbrain-python cd finbrain-python
231
+ git clone https://github.com/finbrain-tech/finbrain-python
232
+ cd finbrain-python
197
233
  python -m venv .venv && source .venv/bin/activate
198
234
  pip install -e .[dev]
199
235
 
200
- ruff check . # lint / format pytest -q # unit tests (mocked)
236
+ ruff check . # lint / format
237
+ pytest -q # unit tests (mocked)
201
238
  ```
202
239
 
203
240
  ### Live integration test(currently under development)
@@ -2,15 +2,13 @@
2
2
  .gitignore
3
3
  LICENSE
4
4
  README.md
5
- cli.py
6
- models.py
7
5
  pyproject.toml
8
- utils.py
9
6
  .github/workflows/ci.yml
10
7
  .github/workflows/release.yml
11
8
  src/finbrain/__init__.py
12
9
  src/finbrain/client.py
13
10
  src/finbrain/exceptions.py
11
+ src/finbrain/plotting.py
14
12
  src/finbrain/aio/__init__.py
15
13
  src/finbrain/aio/client.py
16
14
  src/finbrain/endpoints/__init__.py
@@ -1,6 +1,8 @@
1
1
  requests>=2.31
2
2
  typer[all]>=0.12
3
3
  pandas>=2.2
4
+ plotly>=6.0
5
+ numpy>=1.22.4
4
6
 
5
7
  [dev]
6
8
  pytest
File without changes
File without changes
File without changes
File without changes