quantflow 0.4.3__tar.gz → 0.4.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.
- {quantflow-0.4.3 → quantflow-0.4.4}/LICENSE +1 -1
- {quantflow-0.4.3 → quantflow-0.4.4}/PKG-INFO +4 -6
- {quantflow-0.4.3 → quantflow-0.4.4}/pyproject.toml +48 -23
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/__init__.py +1 -1
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/cli/commands/stocks.py +1 -1
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/data/deribit.py +43 -14
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/data/fmp.py +109 -44
- quantflow-0.4.4/quantflow/options/bs.py +132 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/sp/base.py +22 -16
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/sp/cir.py +11 -15
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/sp/dsp.py +3 -3
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/sp/heston.py +69 -44
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/sp/jump_diffusion.py +33 -24
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/sp/ou.py +5 -8
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/sp/poisson.py +71 -43
- quantflow-0.4.4/quantflow/ta/__init__.py +5 -0
- quantflow-0.4.4/quantflow/ta/ewma.py +131 -0
- quantflow-0.4.4/quantflow/ta/kalman.py +72 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/ta/paths.py +53 -41
- quantflow-0.4.4/quantflow/ta/supersmoother.py +116 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/utils/dates.py +6 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/utils/distributions.py +34 -33
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/utils/plot.py +5 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/readme.md +1 -4
- quantflow-0.4.3/quantflow/options/bs.py +0 -122
- quantflow-0.4.3/quantflow/utils/__init__.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/cli/__init__.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/cli/app.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/cli/commands/__init__.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/cli/commands/base.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/cli/commands/crypto.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/cli/commands/fred.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/cli/commands/vault.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/cli/script.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/cli/settings.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/data/__init__.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/data/fed.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/data/fiscal_data.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/data/fred.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/data/vault.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/options/__init__.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/options/calibration.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/options/inputs.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/options/pricer.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/options/surface.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/py.typed +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/sp/__init__.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/sp/bns.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/sp/copula.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/sp/weiner.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/ta/base.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/ta/ohlc.py +0 -0
- {quantflow-0.4.3/quantflow/ta → quantflow-0.4.4/quantflow/utils}/__init__.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/utils/bins.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/utils/functions.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/utils/interest_rates.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/utils/marginal.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/utils/numbers.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/utils/transforms.py +0 -0
- {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/utils/types.py +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quantflow
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.4
|
|
4
4
|
Summary: quantitative analysis
|
|
5
5
|
License-Expression: BSD-3-Clause
|
|
6
6
|
License-File: LICENSE
|
|
7
7
|
Author: Luca Sbardella
|
|
8
8
|
Author-email: luca@quantmind.com
|
|
9
|
-
Requires-Python: >=3.11,<
|
|
9
|
+
Requires-Python: >=3.11,<3.15
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -26,6 +26,7 @@ Requires-Dist: pydantic (>=2.0.2)
|
|
|
26
26
|
Requires-Dist: python-dotenv (>=1.0.1)
|
|
27
27
|
Requires-Dist: rich (>=13.9.4) ; extra == "cli"
|
|
28
28
|
Requires-Dist: scipy (>=1.14.1)
|
|
29
|
+
Requires-Dist: statsmodels (>=0.14.6,<0.15.0)
|
|
29
30
|
Project-URL: Documentation, https://quantmind.github.io/quantflow/
|
|
30
31
|
Project-URL: Homepage, https://github.com/quantmind/quantflow
|
|
31
32
|
Project-URL: Repository, https://github.com/quantmind/quantflow
|
|
@@ -41,7 +42,7 @@ Description-Content-Type: text/markdown
|
|
|
41
42
|
|
|
42
43
|
Quantitative analysis and pricing tools.
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+

|
|
45
46
|
|
|
46
47
|
## Installation
|
|
47
48
|
|
|
@@ -49,9 +50,6 @@ Documentation is available as [quantflow jupyter book](https://quantflow.quantmi
|
|
|
49
50
|
pip install quantflow
|
|
50
51
|
```
|
|
51
52
|
|
|
52
|
-

|
|
53
|
-
|
|
54
|
-
|
|
55
53
|
## Modules
|
|
56
54
|
|
|
57
55
|
* [quantflow.cli](https://github.com/quantmind/quantflow/tree/main/quantflow/cli) command line client (requires `quantflow[cli,data]`)
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "quantflow"
|
|
3
|
-
version = "0.4.
|
|
3
|
+
version = "0.4.4"
|
|
4
4
|
description = "quantitative analysis"
|
|
5
|
-
authors = [{ name = "Luca Sbardella", email = "luca@quantmind.com" }]
|
|
5
|
+
authors = [ { name = "Luca Sbardella", email = "luca@quantmind.com" } ]
|
|
6
6
|
license = "BSD-3-Clause"
|
|
7
7
|
readme = "readme.md"
|
|
8
|
-
requires-python = ">=3.11,<
|
|
8
|
+
requires-python = ">=3.11,<3.15"
|
|
9
9
|
dependencies = [
|
|
10
10
|
"scipy>=1.14.1",
|
|
11
11
|
"pydantic>=2.0.2",
|
|
12
12
|
"ccy>=1.7.1",
|
|
13
13
|
"python-dotenv>=1.0.1",
|
|
14
14
|
"polars[pandas,pyarrow]>=1.11.0",
|
|
15
|
+
"statsmodels (>=0.14.6,<0.15.0)",
|
|
15
16
|
]
|
|
16
17
|
|
|
17
18
|
[project.urls]
|
|
@@ -20,7 +21,7 @@ Repository = "https://github.com/quantmind/quantflow"
|
|
|
20
21
|
Documentation = "https://quantmind.github.io/quantflow/"
|
|
21
22
|
|
|
22
23
|
[project.optional-dependencies]
|
|
23
|
-
data = ["aio-fluid[http]>=1.2.1"]
|
|
24
|
+
data = [ "aio-fluid[http]>=1.2.1" ]
|
|
24
25
|
cli = [
|
|
25
26
|
"asciichartpy>=1.5.25",
|
|
26
27
|
"async-cache>=1.1.1",
|
|
@@ -33,49 +34,73 @@ cli = [
|
|
|
33
34
|
[project.scripts]
|
|
34
35
|
qf = "quantflow.cli.script:main"
|
|
35
36
|
|
|
36
|
-
|
|
37
37
|
[tool.poetry.group.dev.dependencies]
|
|
38
|
-
black = "^
|
|
39
|
-
pytest-cov = "^6.0.0"
|
|
40
|
-
mypy = "^1.14.1"
|
|
38
|
+
black = "^26.1.0"
|
|
41
39
|
ghp-import = "^2.0.2"
|
|
42
|
-
|
|
40
|
+
isort = "^8.0.0"
|
|
41
|
+
mypy = "^1.14.1"
|
|
43
42
|
pytest-asyncio = "^1.0.0"
|
|
44
|
-
|
|
43
|
+
pytest-cov = "^7.0.0"
|
|
44
|
+
ruff = "^0.15.4"
|
|
45
45
|
types-python-dateutil = "^2.9.0.20251115"
|
|
46
46
|
|
|
47
|
+
[tool.poetry.group.docs]
|
|
48
|
+
optional = true
|
|
49
|
+
|
|
50
|
+
[tool.poetry.group.docs.dependencies]
|
|
51
|
+
mkdocs-material = "^9.7.0"
|
|
52
|
+
mkdocs-macros-plugin = "^1.3.7"
|
|
53
|
+
mkdocs-redirects = "^1.2.1"
|
|
54
|
+
mkdocstrings = { version = "1.0.0", extras = [ "python" ] }
|
|
55
|
+
griffe-pydantic = "^1.1.0"
|
|
56
|
+
griffe-typingdoc = "^0.2.7"
|
|
57
|
+
|
|
47
58
|
[tool.poetry.group.book]
|
|
48
59
|
optional = true
|
|
49
60
|
|
|
61
|
+
[tool.poetry.group.ml]
|
|
62
|
+
optional = true
|
|
63
|
+
|
|
50
64
|
[tool.poetry.group.book.dependencies]
|
|
51
|
-
|
|
52
|
-
|
|
65
|
+
altair = "^6.0.0"
|
|
66
|
+
autodocsumm = "^0.2.14"
|
|
67
|
+
duckdb = "^1.4.4"
|
|
68
|
+
fastapi = "^0.129.0"
|
|
69
|
+
google-genai = "^1.61.0"
|
|
70
|
+
marimo = "^0.19.7"
|
|
71
|
+
mcp = "^1.26.0"
|
|
72
|
+
openai = "^2.16.0"
|
|
53
73
|
plotly = "^6.2.0"
|
|
54
|
-
|
|
74
|
+
pydantic-ai-slim = "^1.51.0"
|
|
55
75
|
sympy = "^1.12"
|
|
56
|
-
ipywidgets = "^8.0.7"
|
|
57
|
-
sphinx-autodoc-typehints = "2.3.0"
|
|
58
|
-
sphinx-autosummary-accessors = "^2023.4.0"
|
|
59
|
-
sphinx-copybutton = "^0.5.2"
|
|
60
|
-
autodocsumm = "^0.2.14"
|
|
61
76
|
|
|
77
|
+
[tool.poetry.group.ml.dependencies]
|
|
78
|
+
torch = { version = "^2.10.0", source = "pytorch" }
|
|
79
|
+
|
|
80
|
+
[[tool.poetry.source]]
|
|
81
|
+
name = "pytorch"
|
|
82
|
+
url = "https://download.pytorch.org/whl/cu126"
|
|
83
|
+
priority = "explicit"
|
|
62
84
|
|
|
63
85
|
[build-system]
|
|
64
|
-
requires = ["poetry-core>=1.0.0"]
|
|
86
|
+
requires = [ "poetry-core>=1.0.0" ]
|
|
65
87
|
build-backend = "poetry.core.masonry.api"
|
|
66
88
|
|
|
67
|
-
[tool.jupytext]
|
|
68
|
-
|
|
89
|
+
[tool.jupytext.formats]
|
|
90
|
+
"notebooks/" = "ipynb,py:percent"
|
|
91
|
+
|
|
92
|
+
[tool.marimo.display]
|
|
93
|
+
theme = "dark"
|
|
69
94
|
|
|
70
95
|
[tool.pytest.ini_options]
|
|
71
96
|
asyncio_mode = "auto"
|
|
72
|
-
testpaths = ["quantflow_tests"]
|
|
97
|
+
testpaths = [ "quantflow_tests" ]
|
|
73
98
|
|
|
74
99
|
[tool.isort]
|
|
75
100
|
profile = "black"
|
|
76
101
|
|
|
77
102
|
[tool.ruff]
|
|
78
|
-
lint.select = ["E", "F"]
|
|
103
|
+
lint.select = [ "E", "F" ]
|
|
79
104
|
line-length = 88
|
|
80
105
|
|
|
81
106
|
[tool.hatch.version]
|
|
@@ -94,7 +94,7 @@ async def get_indices(ctx: QuantContext) -> list[dict]:
|
|
|
94
94
|
|
|
95
95
|
async def get_prices(ctx: QuantContext, symbol: str, frequency: str) -> pd.DataFrame:
|
|
96
96
|
async with ctx.fmp() as cli:
|
|
97
|
-
return await cli.prices(symbol, frequency)
|
|
97
|
+
return await cli.prices(symbol, frequency=frequency)
|
|
98
98
|
|
|
99
99
|
|
|
100
100
|
async def get_profile(ctx: QuantContext, symbol: str) -> list[dict]:
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import enum
|
|
2
4
|
from dataclasses import dataclass
|
|
3
5
|
from datetime import datetime, timezone
|
|
@@ -8,6 +10,7 @@ import pandas as pd
|
|
|
8
10
|
from dateutil.parser import parse
|
|
9
11
|
from fluid.utils.data import compact_dict
|
|
10
12
|
from fluid.utils.http_client import AioHttpClient, HttpResponse, HttpResponseError
|
|
13
|
+
from typing_extensions import Annotated, Doc
|
|
11
14
|
|
|
12
15
|
from quantflow.options.inputs import OptionType
|
|
13
16
|
from quantflow.options.surface import VolSecurityType, VolSurfaceLoader
|
|
@@ -37,16 +40,20 @@ class InstrumentKind(enum.StrEnum):
|
|
|
37
40
|
class Deribit(AioHttpClient):
|
|
38
41
|
"""Deribit API client
|
|
39
42
|
|
|
40
|
-
|
|
43
|
+
## Example
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from quantflow.data.deribit import Deribit
|
|
41
47
|
|
|
42
|
-
|
|
48
|
+
deribit = Deribit()
|
|
49
|
+
```
|
|
43
50
|
"""
|
|
44
51
|
|
|
45
52
|
url: str = "https://www.deribit.com/api/v2"
|
|
46
53
|
|
|
47
54
|
async def get_book_summary_by_instrument(
|
|
48
55
|
self,
|
|
49
|
-
instrument_name: str,
|
|
56
|
+
instrument_name: Annotated[str, Doc("Instrument name")],
|
|
50
57
|
**kw: Any,
|
|
51
58
|
) -> list[dict]:
|
|
52
59
|
"""Get the book summary for a given instrument."""
|
|
@@ -57,7 +64,10 @@ class Deribit(AioHttpClient):
|
|
|
57
64
|
)
|
|
58
65
|
|
|
59
66
|
async def get_book_summary_by_currency(
|
|
60
|
-
self,
|
|
67
|
+
self,
|
|
68
|
+
currency: Annotated[str, Doc("Currency")],
|
|
69
|
+
kind: Annotated[InstrumentKind | None, Doc("Optional instrument kind")] = None,
|
|
70
|
+
**kw: Any,
|
|
61
71
|
) -> list[dict]:
|
|
62
72
|
"""Get the book summary for a given currency."""
|
|
63
73
|
kw.update(
|
|
@@ -69,9 +79,9 @@ class Deribit(AioHttpClient):
|
|
|
69
79
|
|
|
70
80
|
async def get_instruments(
|
|
71
81
|
self,
|
|
72
|
-
currency: str,
|
|
73
|
-
kind: InstrumentKind | None = None,
|
|
74
|
-
expired: bool | None = None,
|
|
82
|
+
currency: Annotated[str, Doc("Currency")],
|
|
83
|
+
kind: Annotated[InstrumentKind | None, Doc("Optional instrument kind")] = None,
|
|
84
|
+
expired: Annotated[bool | None, Doc("Include expired instruments")] = None,
|
|
75
85
|
**kw: Any,
|
|
76
86
|
) -> list[dict]:
|
|
77
87
|
"""Get the list of instruments for a given currency."""
|
|
@@ -81,17 +91,26 @@ class Deribit(AioHttpClient):
|
|
|
81
91
|
)
|
|
82
92
|
return cast(list[dict], await self.get_path("public/get_instruments", **kw))
|
|
83
93
|
|
|
84
|
-
async def get_volatility(
|
|
94
|
+
async def get_volatility(
|
|
95
|
+
self,
|
|
96
|
+
currency: Annotated[str, Doc("Currency")],
|
|
97
|
+
**kw: Any,
|
|
98
|
+
) -> pd.DataFrame:
|
|
85
99
|
"""Provides information about historical volatility for given cryptocurrency"""
|
|
86
100
|
kw.update(params=dict(currency=currency), callback=self.to_df)
|
|
87
101
|
return await self.get_path("public/get_historical_volatility", **kw)
|
|
88
102
|
|
|
89
103
|
async def volatility_surface_loader(
|
|
90
104
|
self,
|
|
91
|
-
currency: str,
|
|
105
|
+
currency: Annotated[str, Doc("Currency")],
|
|
92
106
|
*,
|
|
93
|
-
exclude_open_interest:
|
|
94
|
-
|
|
107
|
+
exclude_open_interest: Annotated[
|
|
108
|
+
Number | None,
|
|
109
|
+
Doc("Exclude options with open interest below this threshold"),
|
|
110
|
+
] = None,
|
|
111
|
+
exclude_volume: Annotated[
|
|
112
|
+
Number | None, Doc("Exclude options with volume below this threshold")
|
|
113
|
+
] = None,
|
|
95
114
|
) -> VolSurfaceLoader:
|
|
96
115
|
"""Create a :class:`.VolSurfaceLoader` for a given crypto-currency"""
|
|
97
116
|
loader = VolSurfaceLoader(
|
|
@@ -170,16 +189,26 @@ class Deribit(AioHttpClient):
|
|
|
170
189
|
|
|
171
190
|
# Internal methods
|
|
172
191
|
|
|
173
|
-
async def get_path(
|
|
192
|
+
async def get_path(
|
|
193
|
+
self,
|
|
194
|
+
path: Annotated[str, Doc("API path")],
|
|
195
|
+
**kw: Any,
|
|
196
|
+
) -> dict:
|
|
174
197
|
return await self.get(f"{self.url}/{path}", **kw)
|
|
175
198
|
|
|
176
|
-
async def to_result(
|
|
199
|
+
async def to_result(
|
|
200
|
+
self,
|
|
201
|
+
response: Annotated[HttpResponse, Doc("HTTP response object")],
|
|
202
|
+
) -> list[dict]:
|
|
177
203
|
data = await response.json()
|
|
178
204
|
if "error" in data:
|
|
179
205
|
raise HttpResponseError(response, data["error"])
|
|
180
206
|
return cast(list[dict], data["result"])
|
|
181
207
|
|
|
182
|
-
async def to_df(
|
|
208
|
+
async def to_df(
|
|
209
|
+
self,
|
|
210
|
+
response: Annotated[HttpResponse, Doc("HTTP response object")],
|
|
211
|
+
) -> pd.DataFrame:
|
|
183
212
|
data = await self.to_result(response)
|
|
184
213
|
df = pd.DataFrame(data, columns=["timestamp", "volatility"])
|
|
185
214
|
df["timestamp"] = pd.to_datetime(df["timestamp"], unit="ms")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from dataclasses import dataclass, field
|
|
3
|
-
from datetime import date
|
|
3
|
+
from datetime import date
|
|
4
4
|
from decimal import Decimal
|
|
5
5
|
from enum import StrEnum
|
|
6
6
|
from typing import Any, Iterator, cast
|
|
@@ -9,19 +9,15 @@ import inflection
|
|
|
9
9
|
import pandas as pd
|
|
10
10
|
from fluid.utils.data import compact_dict
|
|
11
11
|
from fluid.utils.http_client import AioHttpClient
|
|
12
|
+
from typing_extensions import Annotated, Doc
|
|
12
13
|
|
|
13
|
-
from quantflow.utils.dates import
|
|
14
|
+
from quantflow.utils.dates import to_date_iso
|
|
14
15
|
from quantflow.utils.numbers import to_decimal
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
@dataclass
|
|
18
19
|
class FMP(AioHttpClient):
|
|
19
|
-
"""Financial Modeling Prep API client
|
|
20
|
-
|
|
21
|
-
Fetch market and financial data from `Financial Modeling Prep`_.
|
|
22
|
-
|
|
23
|
-
.. _Financial Modeling Prep: https://site.financialmodelingprep.com/developer/docs/stable
|
|
24
|
-
"""
|
|
20
|
+
"""Financial Modeling Prep API client"""
|
|
25
21
|
|
|
26
22
|
url: str = "https://financialmodelingprep.com/stable"
|
|
27
23
|
key: str = field(default_factory=lambda: os.environ.get("FMP_API_KEY", ""))
|
|
@@ -52,11 +48,19 @@ class FMP(AioHttpClient):
|
|
|
52
48
|
across global exchanges"""
|
|
53
49
|
return await self.get_path("index-list", **kw)
|
|
54
50
|
|
|
55
|
-
async def profile(
|
|
51
|
+
async def profile(
|
|
52
|
+
self,
|
|
53
|
+
*tickers: Annotated[str, Doc("One or more ticker symbols")],
|
|
54
|
+
**kw: Any,
|
|
55
|
+
) -> list[dict]:
|
|
56
56
|
"""Company profile - minute"""
|
|
57
57
|
return await self.get_path(f"profile/{self.join(*tickers)}", **kw)
|
|
58
58
|
|
|
59
|
-
async def quote(
|
|
59
|
+
async def quote(
|
|
60
|
+
self,
|
|
61
|
+
*tickers: Annotated[str, Doc("One or more ticker symbols")],
|
|
62
|
+
**kw: Any,
|
|
63
|
+
) -> list[dict]:
|
|
60
64
|
"""Company quote - real time"""
|
|
61
65
|
return await self.get_path(f"quote/{self.join(*tickers)}", **kw)
|
|
62
66
|
|
|
@@ -64,25 +68,35 @@ class FMP(AioHttpClient):
|
|
|
64
68
|
|
|
65
69
|
async def dividends(
|
|
66
70
|
self,
|
|
67
|
-
from_date:
|
|
68
|
-
|
|
71
|
+
from_date: Annotated[
|
|
72
|
+
str | date | None, Doc("Start date for dividend calendar")
|
|
73
|
+
] = None,
|
|
74
|
+
to_date: Annotated[
|
|
75
|
+
str | date | None, Doc("End date for dividend calendar")
|
|
76
|
+
] = None,
|
|
69
77
|
**kw: Any,
|
|
70
78
|
) -> list[dict]:
|
|
71
79
|
"""Dividend calendar"""
|
|
72
|
-
|
|
73
|
-
from_date
|
|
74
|
-
|
|
75
|
-
to_date = date.today() + timedelta(days=7)
|
|
76
|
-
params = {"from": isoformat(from_date), "to": isoformat(to_date)}
|
|
80
|
+
params = compact_dict(
|
|
81
|
+
{"from": to_date_iso(from_date), "to": to_date_iso(to_date)},
|
|
82
|
+
)
|
|
77
83
|
return await self.get_path("dividends-calendar", params=params, **kw)
|
|
78
84
|
|
|
79
85
|
# Executives
|
|
80
86
|
|
|
81
|
-
async def executives(
|
|
87
|
+
async def executives(
|
|
88
|
+
self,
|
|
89
|
+
ticker: Annotated[str, Doc("Ticker symbol")],
|
|
90
|
+
**kw: Any,
|
|
91
|
+
) -> list[dict]:
|
|
82
92
|
"""Company quote - real time"""
|
|
83
93
|
return await self.get_path(f"key-executives/{ticker}", **kw)
|
|
84
94
|
|
|
85
|
-
async def insider_trading(
|
|
95
|
+
async def insider_trading(
|
|
96
|
+
self,
|
|
97
|
+
ticker: Annotated[str, Doc("Ticker symbol")],
|
|
98
|
+
**kw: Any,
|
|
99
|
+
) -> list[dict]:
|
|
86
100
|
"""Company Insider Trading"""
|
|
87
101
|
return await self.get_path(
|
|
88
102
|
"insider-trading", **self.params(dict(symbol=ticker), **kw)
|
|
@@ -90,18 +104,28 @@ class FMP(AioHttpClient):
|
|
|
90
104
|
|
|
91
105
|
# Rating
|
|
92
106
|
|
|
93
|
-
async def rating(
|
|
107
|
+
async def rating(
|
|
108
|
+
self,
|
|
109
|
+
ticker: Annotated[str, Doc("Ticker symbol")],
|
|
110
|
+
**kw: Any,
|
|
111
|
+
) -> list[dict]:
|
|
94
112
|
"""Company rating - real time"""
|
|
95
113
|
return await self.get_path(f"rating/{ticker}", **kw)
|
|
96
114
|
|
|
97
|
-
async def etf_holders(
|
|
115
|
+
async def etf_holders(
|
|
116
|
+
self,
|
|
117
|
+
ticker: Annotated[str, Doc("Ticker symbol")],
|
|
118
|
+
**kw: Any,
|
|
119
|
+
) -> list[dict]:
|
|
98
120
|
return await self.get_path(f"etf-holder/{ticker}", **kw)
|
|
99
121
|
|
|
100
122
|
async def ratios(
|
|
101
123
|
self,
|
|
102
|
-
ticker: str,
|
|
103
|
-
period:
|
|
104
|
-
|
|
124
|
+
ticker: Annotated[str, Doc("Ticker symbol")],
|
|
125
|
+
period: Annotated[
|
|
126
|
+
str | None, Doc("Reporting period (e.g., 'annual', 'quarter')")
|
|
127
|
+
] = None,
|
|
128
|
+
limit: Annotated[int | None, Doc("Maximum number of results")] = None,
|
|
105
129
|
**kw: Any,
|
|
106
130
|
) -> list[dict]:
|
|
107
131
|
"""Company financial ratios - if period not provided it is for
|
|
@@ -112,13 +136,21 @@ class FMP(AioHttpClient):
|
|
|
112
136
|
**self.params(compact_dict(period=period, limit=limit), **kw),
|
|
113
137
|
)
|
|
114
138
|
|
|
115
|
-
async def peers(
|
|
139
|
+
async def peers(
|
|
140
|
+
self,
|
|
141
|
+
*tickers: Annotated[str, Doc("One or more ticker symbols")],
|
|
142
|
+
**kw: Any,
|
|
143
|
+
) -> list[dict]:
|
|
116
144
|
"""Stock peers based on sector, exchange and market cap"""
|
|
117
145
|
kwargs = self.params(**kw)
|
|
118
146
|
kwargs["params"]["symbol"] = self.join(*tickers)
|
|
119
147
|
return await self.get_path("stock_peers", **kwargs)
|
|
120
148
|
|
|
121
|
-
async def news(
|
|
149
|
+
async def news(
|
|
150
|
+
self,
|
|
151
|
+
*tickers: Annotated[str, Doc("One or more ticker symbols")],
|
|
152
|
+
**kw: Any,
|
|
153
|
+
) -> list[dict]:
|
|
122
154
|
"""Company quote - real time"""
|
|
123
155
|
kwargs = self.params(**kw)
|
|
124
156
|
if tickers:
|
|
@@ -127,11 +159,11 @@ class FMP(AioHttpClient):
|
|
|
127
159
|
|
|
128
160
|
async def search(
|
|
129
161
|
self,
|
|
130
|
-
query: str,
|
|
162
|
+
query: Annotated[str, Doc("Search query string")],
|
|
131
163
|
*,
|
|
132
|
-
exchange: str | None = None,
|
|
133
|
-
limit: int | None = None,
|
|
134
|
-
symbol: bool = False,
|
|
164
|
+
exchange: Annotated[str | None, Doc("Filter by exchange")] = None,
|
|
165
|
+
limit: Annotated[int | None, Doc("Maximum number of results")] = None,
|
|
166
|
+
symbol: Annotated[bool, Doc("Search by symbol instead of name")] = False,
|
|
135
167
|
**kw: Any,
|
|
136
168
|
) -> list[dict]:
|
|
137
169
|
path = "search-symbol" if symbol else "search-name"
|
|
@@ -144,10 +176,20 @@ class FMP(AioHttpClient):
|
|
|
144
176
|
|
|
145
177
|
async def prices(
|
|
146
178
|
self,
|
|
147
|
-
symbol: str,
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
179
|
+
symbol: Annotated[str, Doc("Ticker symbol")],
|
|
180
|
+
*,
|
|
181
|
+
frequency: Annotated[
|
|
182
|
+
str | None, Doc("Price frequency (e.g., '1min', '5min', 'daily')")
|
|
183
|
+
] = None,
|
|
184
|
+
from_date: Annotated[
|
|
185
|
+
str | date | None, Doc("From date for historical prices")
|
|
186
|
+
] = None,
|
|
187
|
+
to_date: Annotated[
|
|
188
|
+
str | date | None, Doc("To date for historical prices")
|
|
189
|
+
] = None,
|
|
190
|
+
convert_to_date: Annotated[
|
|
191
|
+
bool, Doc("Convert date column to datetime type")
|
|
192
|
+
] = False,
|
|
151
193
|
) -> pd.DataFrame:
|
|
152
194
|
"""Historical prices, daily if frequency is not provided"""
|
|
153
195
|
path = (
|
|
@@ -155,12 +197,18 @@ class FMP(AioHttpClient):
|
|
|
155
197
|
if not frequency
|
|
156
198
|
else f"historical-chart/{frequency}"
|
|
157
199
|
)
|
|
158
|
-
|
|
159
|
-
|
|
200
|
+
data = await self.get_path(
|
|
201
|
+
path,
|
|
202
|
+
params=compact_dict(
|
|
203
|
+
{"from": to_date_iso(from_date), "to": to_date_iso(to_date)},
|
|
204
|
+
frequency=frequency,
|
|
205
|
+
symbol=symbol,
|
|
206
|
+
),
|
|
207
|
+
)
|
|
160
208
|
if isinstance(data, dict):
|
|
161
209
|
data = data.get("historical", [])
|
|
162
210
|
df = pd.DataFrame(data)
|
|
163
|
-
if
|
|
211
|
+
if convert_to_date and "date" in df.columns:
|
|
164
212
|
df["date"] = pd.to_datetime(df["date"])
|
|
165
213
|
return df
|
|
166
214
|
|
|
@@ -168,10 +216,16 @@ class FMP(AioHttpClient):
|
|
|
168
216
|
async def sector_performance(
|
|
169
217
|
self,
|
|
170
218
|
*,
|
|
171
|
-
from_date:
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
219
|
+
from_date: Annotated[
|
|
220
|
+
date | None, Doc("Start date for historical sector performance")
|
|
221
|
+
] = None,
|
|
222
|
+
to_date: Annotated[
|
|
223
|
+
date | None, Doc("End date for historical sector performance")
|
|
224
|
+
] = None,
|
|
225
|
+
summary: Annotated[
|
|
226
|
+
bool, Doc("Return summary instead of daily performance")
|
|
227
|
+
] = False,
|
|
228
|
+
params: Annotated[dict | None, Doc("Additional query parameters")] = None,
|
|
175
229
|
**kw: Any,
|
|
176
230
|
) -> dict | list[dict]:
|
|
177
231
|
if not from_date:
|
|
@@ -213,17 +267,28 @@ class FMP(AioHttpClient):
|
|
|
213
267
|
return await self.get_path("symbol/available-cryptocurrencies")
|
|
214
268
|
|
|
215
269
|
# Internals
|
|
216
|
-
async def get_path(
|
|
270
|
+
async def get_path(
|
|
271
|
+
self,
|
|
272
|
+
path: Annotated[str, Doc("API endpoint path")],
|
|
273
|
+
**kw: Any,
|
|
274
|
+
) -> list[dict]:
|
|
217
275
|
result = await self.get(f"{self.url}/{path}", **self.params(**kw))
|
|
218
276
|
return cast(list[dict], result)
|
|
219
277
|
|
|
220
|
-
def join(
|
|
278
|
+
def join(
|
|
279
|
+
self,
|
|
280
|
+
*tickers: Annotated[str, Doc("One or more ticker symbols")],
|
|
281
|
+
) -> str:
|
|
221
282
|
value = ",".join(tickers)
|
|
222
283
|
if not value:
|
|
223
284
|
raise TypeError("at least one ticker must be provided")
|
|
224
285
|
return value
|
|
225
286
|
|
|
226
|
-
def params(
|
|
287
|
+
def params(
|
|
288
|
+
self,
|
|
289
|
+
params: Annotated[dict | None, Doc("Query parameters dictionary")] = None,
|
|
290
|
+
**kw: Any,
|
|
291
|
+
) -> dict:
|
|
227
292
|
params = params.copy() if params is not None else {}
|
|
228
293
|
params["apikey"] = self.key
|
|
229
294
|
return {"params": params, **kw}
|