quantflow 0.3.2__tar.gz → 0.3.3__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.3.2 → quantflow-0.3.3}/PKG-INFO +1 -1
- {quantflow-0.3.2 → quantflow-0.3.3}/pyproject.toml +11 -13
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/__init__.py +1 -1
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/cli/commands/crypto.py +31 -3
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/cli/commands/stocks.py +14 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/data/deribit.py +48 -13
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/data/fed.py +3 -8
- quantflow-0.3.3/quantflow/data/fiscal_data.py +42 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/data/fmp.py +37 -28
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/utils/dates.py +9 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/LICENSE +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/cli/__init__.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/cli/app.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/cli/commands/__init__.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/cli/commands/base.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/cli/commands/fred.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/cli/commands/vault.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/cli/script.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/cli/settings.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/data/__init__.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/data/fred.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/data/vault.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/options/__init__.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/options/bs.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/options/calibration.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/options/inputs.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/options/pricer.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/options/surface.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/py.typed +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/sp/__init__.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/sp/base.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/sp/bns.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/sp/cir.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/sp/copula.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/sp/dsp.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/sp/heston.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/sp/jump_diffusion.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/sp/ou.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/sp/poisson.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/sp/weiner.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/ta/__init__.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/ta/base.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/ta/ohlc.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/ta/paths.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/utils/__init__.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/utils/bins.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/utils/distributions.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/utils/functions.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/utils/interest_rates.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/utils/marginal.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/utils/numbers.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/utils/plot.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/utils/transforms.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/quantflow/utils/types.py +0 -0
- {quantflow-0.3.2 → quantflow-0.3.3}/readme.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "quantflow"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.3"
|
|
4
4
|
description = "quantitative analysis"
|
|
5
5
|
authors = ["Luca <luca@quantmind.com>"]
|
|
6
6
|
license = "BSD-3-Clause"
|
|
@@ -17,21 +17,21 @@ scipy = "^1.14.1"
|
|
|
17
17
|
pydantic = "^2.0.2"
|
|
18
18
|
ccy = { version = "^1.7.1" }
|
|
19
19
|
python-dotenv = "^1.0.1"
|
|
20
|
-
polars = {version = "^1.11.0", extras=["pandas", "pyarrow"]}
|
|
20
|
+
polars = { version = "^1.11.0", extras = ["pandas", "pyarrow"] }
|
|
21
21
|
asciichartpy = { version = "^1.5.25", optional = true }
|
|
22
22
|
prompt-toolkit = { version = "^3.0.43", optional = true }
|
|
23
|
-
aio-fluid = {version = "^1.2.1", extras=["http"], optional = true}
|
|
24
|
-
rich = {version = "^13.9.4", optional = true}
|
|
25
|
-
click = {version = "^8.1.7", optional = true}
|
|
26
|
-
holidays = {version = "^0.63", optional = true}
|
|
27
|
-
async-cache = {version = "^1.1.1", optional = true}
|
|
23
|
+
aio-fluid = { version = "^1.2.1", extras = ["http"], optional = true }
|
|
24
|
+
rich = { version = "^13.9.4", optional = true }
|
|
25
|
+
click = { version = "^8.1.7", optional = true }
|
|
26
|
+
holidays = { version = "^0.63", optional = true }
|
|
27
|
+
async-cache = { version = "^1.1.1", optional = true }
|
|
28
28
|
|
|
29
29
|
[tool.poetry.group.dev.dependencies]
|
|
30
30
|
black = "^25.1.0"
|
|
31
31
|
pytest-cov = "^6.0.0"
|
|
32
32
|
mypy = "^1.14.1"
|
|
33
33
|
ghp-import = "^2.0.2"
|
|
34
|
-
ruff = "^0.11.
|
|
34
|
+
ruff = "^0.11.12"
|
|
35
35
|
pytest-asyncio = "^0.26.0"
|
|
36
36
|
isort = "^6.0.1"
|
|
37
37
|
|
|
@@ -44,7 +44,7 @@ cli = [
|
|
|
44
44
|
"prompt-toolkit",
|
|
45
45
|
"rich",
|
|
46
46
|
"click",
|
|
47
|
-
"holidays"
|
|
47
|
+
"holidays",
|
|
48
48
|
]
|
|
49
49
|
|
|
50
50
|
[tool.poetry.group.book]
|
|
@@ -74,9 +74,7 @@ formats = "ipynb,myst"
|
|
|
74
74
|
|
|
75
75
|
[tool.pytest.ini_options]
|
|
76
76
|
asyncio_mode = "auto"
|
|
77
|
-
testpaths = [
|
|
78
|
-
"quantflow_tests"
|
|
79
|
-
]
|
|
77
|
+
testpaths = ["quantflow_tests"]
|
|
80
78
|
|
|
81
79
|
[tool.isort]
|
|
82
80
|
profile = "black"
|
|
@@ -102,7 +100,7 @@ module = [
|
|
|
102
100
|
"IPython.*",
|
|
103
101
|
"pandas.*",
|
|
104
102
|
"plotly.*",
|
|
105
|
-
"scipy.*"
|
|
103
|
+
"scipy.*",
|
|
106
104
|
]
|
|
107
105
|
ignore_missing_imports = true
|
|
108
106
|
disallow_untyped_defs = false
|
|
@@ -8,7 +8,7 @@ from asciichartpy import plot
|
|
|
8
8
|
from cache import AsyncTTL
|
|
9
9
|
from ccy.cli.console import df_to_rich
|
|
10
10
|
|
|
11
|
-
from quantflow.data.deribit import Deribit
|
|
11
|
+
from quantflow.data.deribit import Deribit, InstrumentKind
|
|
12
12
|
from quantflow.options.surface import VolSurface
|
|
13
13
|
from quantflow.utils.numbers import round_to_step
|
|
14
14
|
|
|
@@ -24,13 +24,34 @@ def crypto() -> None:
|
|
|
24
24
|
ctx.set_as_section()
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
@crypto.command()
|
|
28
|
+
@click.argument("currency")
|
|
29
|
+
@click.option(
|
|
30
|
+
"-k",
|
|
31
|
+
"--kind",
|
|
32
|
+
type=click.Choice(list(InstrumentKind)),
|
|
33
|
+
default=InstrumentKind.spot.value,
|
|
34
|
+
)
|
|
35
|
+
def instruments(currency: str, kind: str) -> None:
|
|
36
|
+
"""Provides information about instruments
|
|
37
|
+
|
|
38
|
+
Instruments for given cryptocurrency from Deribit API"""
|
|
39
|
+
ctx = QuantContext.current()
|
|
40
|
+
data = asyncio.run(get_instruments(ctx, currency, kind))
|
|
41
|
+
df = pd.DataFrame(data)
|
|
42
|
+
ctx.qf.print(df_to_rich(df))
|
|
43
|
+
|
|
44
|
+
|
|
27
45
|
@crypto.command()
|
|
28
46
|
@click.argument("currency")
|
|
29
47
|
@options.length
|
|
30
48
|
@options.height
|
|
31
49
|
@options.chart
|
|
32
50
|
def volatility(currency: str, length: int, height: int, chart: bool) -> None:
|
|
33
|
-
"""Provides information about historical volatility
|
|
51
|
+
"""Provides information about historical volatility
|
|
52
|
+
|
|
53
|
+
Historical volatility for given cryptocurrency from Deribit API
|
|
54
|
+
"""
|
|
34
55
|
ctx = QuantContext.current()
|
|
35
56
|
df = asyncio.run(get_volatility(ctx, currency))
|
|
36
57
|
df["volatility"] = df["volatility"].map(lambda p: round_to_step(p, "0.01"))
|
|
@@ -109,9 +130,16 @@ def prices(symbol: str, height: int, length: int, chart: bool, frequency: str) -
|
|
|
109
130
|
)
|
|
110
131
|
|
|
111
132
|
|
|
133
|
+
async def get_instruments(ctx: QuantContext, currency: str, kind: str) -> list[dict]:
|
|
134
|
+
async with Deribit() as client:
|
|
135
|
+
return await client.get_instruments(
|
|
136
|
+
currency=currency, kind=InstrumentKind(kind)
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
112
140
|
async def get_volatility(ctx: QuantContext, currency: str) -> pd.DataFrame:
|
|
113
141
|
async with Deribit() as client:
|
|
114
|
-
return await client.get_volatility(
|
|
142
|
+
return await client.get_volatility(currency)
|
|
115
143
|
|
|
116
144
|
|
|
117
145
|
@AsyncTTL(time_to_live=10)
|
|
@@ -24,6 +24,15 @@ def stocks() -> None:
|
|
|
24
24
|
ctx.set_as_section()
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
@stocks.command()
|
|
28
|
+
def indices() -> None:
|
|
29
|
+
"""Search companies"""
|
|
30
|
+
ctx = QuantContext.current()
|
|
31
|
+
data = asyncio.run(get_indices(ctx))
|
|
32
|
+
df = pd.DataFrame(data)
|
|
33
|
+
ctx.qf.print(df_to_rich(df))
|
|
34
|
+
|
|
35
|
+
|
|
27
36
|
@stocks.command()
|
|
28
37
|
@click.argument("symbol")
|
|
29
38
|
def profile(symbol: str) -> None:
|
|
@@ -78,6 +87,11 @@ def sectors(period: str) -> None:
|
|
|
78
87
|
ctx.qf.print(df_to_rich(df))
|
|
79
88
|
|
|
80
89
|
|
|
90
|
+
async def get_indices(ctx: QuantContext) -> list[dict]:
|
|
91
|
+
async with ctx.fmp() as cli:
|
|
92
|
+
return await cli.indices()
|
|
93
|
+
|
|
94
|
+
|
|
81
95
|
async def get_prices(ctx: QuantContext, symbol: str, frequency: str) -> pd.DataFrame:
|
|
82
96
|
async with ctx.fmp() as cli:
|
|
83
97
|
return await cli.prices(symbol, frequency)
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
from dataclasses import dataclass
|
|
1
3
|
from datetime import datetime, timezone
|
|
2
4
|
from decimal import Decimal
|
|
3
5
|
from typing import Any, cast
|
|
4
6
|
|
|
5
7
|
import pandas as pd
|
|
6
8
|
from dateutil.parser import parse
|
|
9
|
+
from fluid.utils.data import compact_dict
|
|
7
10
|
from fluid.utils.http_client import AioHttpClient, HttpResponse, HttpResponseError
|
|
8
11
|
|
|
9
12
|
from quantflow.options.surface import VolSecurityType, VolSurfaceLoader
|
|
@@ -14,47 +17,79 @@ def parse_maturity(v: str) -> datetime:
|
|
|
14
17
|
return parse(v).replace(tzinfo=timezone.utc, hour=8)
|
|
15
18
|
|
|
16
19
|
|
|
20
|
+
class InstrumentKind(enum.StrEnum):
|
|
21
|
+
"""Instrument kind for Deribit API."""
|
|
22
|
+
|
|
23
|
+
future = enum.auto()
|
|
24
|
+
option = enum.auto()
|
|
25
|
+
spot = enum.auto()
|
|
26
|
+
future_combo = enum.auto()
|
|
27
|
+
option_combo = enum.auto()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
17
31
|
class Deribit(AioHttpClient):
|
|
18
32
|
"""Deribit API client
|
|
19
33
|
|
|
20
|
-
Fetch market and static data from `Deribit`_.
|
|
34
|
+
Fetch market and static data from `Deribit`_ API.
|
|
21
35
|
|
|
22
36
|
.. _Deribit: https://docs.deribit.com/
|
|
23
37
|
"""
|
|
24
38
|
|
|
25
|
-
url = "https://www.deribit.com/api/v2"
|
|
39
|
+
url: str = "https://www.deribit.com/api/v2"
|
|
26
40
|
|
|
27
|
-
async def get_book_summary_by_instrument(
|
|
28
|
-
|
|
41
|
+
async def get_book_summary_by_instrument(
|
|
42
|
+
self,
|
|
43
|
+
instrument_name: str,
|
|
44
|
+
**kw: Any,
|
|
45
|
+
) -> list[dict]:
|
|
46
|
+
"""Get the book summary for a given instrument."""
|
|
47
|
+
kw.update(params=dict(instrument_name=instrument_name), callback=self.to_result)
|
|
29
48
|
return cast(
|
|
30
49
|
list[dict],
|
|
31
50
|
await self.get_path("public/get_book_summary_by_instrument", **kw),
|
|
32
51
|
)
|
|
33
52
|
|
|
34
|
-
async def get_book_summary_by_currency(
|
|
35
|
-
kw
|
|
53
|
+
async def get_book_summary_by_currency(
|
|
54
|
+
self, currency: str, kind: InstrumentKind | None = None, **kw: Any
|
|
55
|
+
) -> list[dict]:
|
|
56
|
+
"""Get the book summary for a given currency."""
|
|
57
|
+
kw.update(
|
|
58
|
+
params=compact_dict(currency=currency, kind=kind), callback=self.to_result
|
|
59
|
+
)
|
|
36
60
|
return cast(
|
|
37
61
|
list[dict], await self.get_path("public/get_book_summary_by_currency", **kw)
|
|
38
62
|
)
|
|
39
63
|
|
|
40
|
-
async def get_instruments(
|
|
41
|
-
|
|
64
|
+
async def get_instruments(
|
|
65
|
+
self,
|
|
66
|
+
currency: str,
|
|
67
|
+
kind: InstrumentKind | None = None,
|
|
68
|
+
expired: bool | None = None,
|
|
69
|
+
**kw: Any,
|
|
70
|
+
) -> list[dict]:
|
|
71
|
+
"""Get the list of instruments for a given currency."""
|
|
72
|
+
kw.update(
|
|
73
|
+
params=compact_dict(currency=currency, kind=kind, expired=expired),
|
|
74
|
+
callback=self.to_result,
|
|
75
|
+
)
|
|
42
76
|
return cast(list[dict], await self.get_path("public/get_instruments", **kw))
|
|
43
77
|
|
|
44
|
-
async def get_volatility(self, **kw: Any) -> pd.DataFrame:
|
|
45
|
-
|
|
78
|
+
async def get_volatility(self, currency: str, **kw: Any) -> pd.DataFrame:
|
|
79
|
+
"""Provides information about historical volatility for given cryptocurrency"""
|
|
80
|
+
kw.update(params=dict(currency=currency), callback=self.to_df)
|
|
46
81
|
return await self.get_path("public/get_historical_volatility", **kw)
|
|
47
82
|
|
|
48
83
|
async def volatility_surface_loader(self, currency: str) -> VolSurfaceLoader:
|
|
49
84
|
"""Create a :class:`.VolSurfaceLoader` for a given crypto-currency"""
|
|
50
85
|
loader = VolSurfaceLoader()
|
|
51
86
|
futures = await self.get_book_summary_by_currency(
|
|
52
|
-
|
|
87
|
+
currency=currency, kind=InstrumentKind.future
|
|
53
88
|
)
|
|
54
89
|
options = await self.get_book_summary_by_currency(
|
|
55
|
-
|
|
90
|
+
currency=currency, kind=InstrumentKind.option
|
|
56
91
|
)
|
|
57
|
-
instruments = await self.get_instruments(
|
|
92
|
+
instruments = await self.get_instruments(currency=currency)
|
|
58
93
|
instrument_map = {i["instrument_name"]: i for i in instruments}
|
|
59
94
|
min_tick_size = Decimal("inf")
|
|
60
95
|
for future in futures:
|
|
@@ -6,12 +6,7 @@ import numpy as np
|
|
|
6
6
|
import pandas as pd
|
|
7
7
|
from fluid.utils.http_client import AioHttpClient
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
"https://www.federalreserve.gov/datadownload/Output.aspx?"
|
|
11
|
-
"rel=H15&series=bf17364827e38702b42a58cf8eaa3f78&lastobs=&"
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
maturities = [
|
|
9
|
+
MATURITIES = (
|
|
15
10
|
"month_1",
|
|
16
11
|
"month_3",
|
|
17
12
|
"month_6",
|
|
@@ -23,7 +18,7 @@ maturities = [
|
|
|
23
18
|
"year_10",
|
|
24
19
|
"year_20",
|
|
25
20
|
"year_30",
|
|
26
|
-
|
|
21
|
+
)
|
|
27
22
|
|
|
28
23
|
|
|
29
24
|
@dataclass
|
|
@@ -52,7 +47,7 @@ class FederalReserve(AioHttpClient):
|
|
|
52
47
|
params.update(series="bf17364827e38702b42a58cf8eaa3f78", rel="H15")
|
|
53
48
|
data = await self._get_text(params)
|
|
54
49
|
df = pd.read_csv(data, header=5, index_col=None, parse_dates=True)
|
|
55
|
-
df.columns =
|
|
50
|
+
df.columns = list(("date",) + MATURITIES) # type: ignore
|
|
56
51
|
df = df.set_index("date").replace("ND", np.nan)
|
|
57
52
|
return df.dropna(axis=0, how="all").reset_index()
|
|
58
53
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from datetime import date, timedelta
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from fluid.utils.http_client import AioHttpClient
|
|
6
|
+
|
|
7
|
+
from quantflow.utils.dates import as_date
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class FiscalData(AioHttpClient):
|
|
12
|
+
"""Fiscal Data API client.
|
|
13
|
+
|
|
14
|
+
THis class is used to fetch data from the
|
|
15
|
+
[fiscal data api](https://fiscaldata.treasury.gov/api-documentation/)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
url: str = "https://api.fiscaldata.treasury.gov/services/api/fiscal_service"
|
|
19
|
+
|
|
20
|
+
async def securities(self, record_date: date | None = None) -> pd.DataFrame:
|
|
21
|
+
"""Get treasury constant maturities rates"""
|
|
22
|
+
rd = as_date(record_date)
|
|
23
|
+
pm = rd.replace(day=1) - timedelta(days=1)
|
|
24
|
+
params = {"filter": f"record_date:eq:{pm.isoformat()}"}
|
|
25
|
+
data = await self.get_all("/v1/debt/mspd/mspd_table_3_market", params)
|
|
26
|
+
return pd.DataFrame(data)
|
|
27
|
+
|
|
28
|
+
async def get_all(self, path: str, params: dict[str, str]) -> list:
|
|
29
|
+
"""Get all data from the API"""
|
|
30
|
+
next_url: str | None = f"{self.url}{path}"
|
|
31
|
+
full_data = []
|
|
32
|
+
while next_url:
|
|
33
|
+
payload = await self.get(next_url, params=params)
|
|
34
|
+
full_data.extend(payload["data"])
|
|
35
|
+
if links := payload.get("links"):
|
|
36
|
+
if next_path := links.get("next"):
|
|
37
|
+
next_url = f"{self.url}{next_path}"
|
|
38
|
+
else:
|
|
39
|
+
next_url = None
|
|
40
|
+
else:
|
|
41
|
+
next_url = None
|
|
42
|
+
return full_data
|
|
@@ -20,10 +20,10 @@ class FMP(AioHttpClient):
|
|
|
20
20
|
|
|
21
21
|
Fetch market and financial data from `Financial Modeling Prep`_.
|
|
22
22
|
|
|
23
|
-
.. _Financial Modeling Prep: https://financialmodelingprep.com/developer/docs/
|
|
23
|
+
.. _Financial Modeling Prep: https://site.financialmodelingprep.com/developer/docs/stable
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
|
-
url: str = "https://financialmodelingprep.com/
|
|
26
|
+
url: str = "https://financialmodelingprep.com/stable"
|
|
27
27
|
key: str = field(default_factory=lambda: os.environ.get("FMP_API_KEY", ""))
|
|
28
28
|
|
|
29
29
|
class freq(StrEnum):
|
|
@@ -37,22 +37,28 @@ class FMP(AioHttpClient):
|
|
|
37
37
|
four_hour = "4hour"
|
|
38
38
|
daily = ""
|
|
39
39
|
|
|
40
|
+
async def market_risk_premium(self) -> list[dict]:
|
|
41
|
+
"""Market risk premium"""
|
|
42
|
+
return await self.get_path("market-risk-premium")
|
|
43
|
+
|
|
40
44
|
async def stocks(self, **kw: Any) -> list[dict]:
|
|
41
|
-
return await self.get_path("
|
|
45
|
+
return await self.get_path("stock-list", **kw)
|
|
42
46
|
|
|
43
47
|
async def etfs(self, **kw: Any) -> list[dict]:
|
|
44
|
-
return await self.get_path("
|
|
48
|
+
return await self.get_path("etf-list", **kw)
|
|
45
49
|
|
|
46
50
|
async def indices(self, **kw: Any) -> list[dict]:
|
|
47
|
-
|
|
51
|
+
"""Retrieve a comprehensive list of stock market indexes
|
|
52
|
+
across global exchanges"""
|
|
53
|
+
return await self.get_path("index-list", **kw)
|
|
48
54
|
|
|
49
55
|
async def profile(self, *tickers: str, **kw: Any) -> list[dict]:
|
|
50
56
|
"""Company profile - minute"""
|
|
51
|
-
return await self.get_path(f"
|
|
57
|
+
return await self.get_path(f"profile/{self.join(*tickers)}", **kw)
|
|
52
58
|
|
|
53
59
|
async def quote(self, *tickers: str, **kw: Any) -> list[dict]:
|
|
54
60
|
"""Company quote - real time"""
|
|
55
|
-
return await self.get_path(f"
|
|
61
|
+
return await self.get_path(f"quote/{self.join(*tickers)}", **kw)
|
|
56
62
|
|
|
57
63
|
# calendars
|
|
58
64
|
|
|
@@ -68,28 +74,28 @@ class FMP(AioHttpClient):
|
|
|
68
74
|
if not to_date:
|
|
69
75
|
to_date = date.today() + timedelta(days=7)
|
|
70
76
|
params = {"from": isoformat(from_date), "to": isoformat(to_date)}
|
|
71
|
-
return await self.get_path("
|
|
77
|
+
return await self.get_path("stock_dividend_calendar", params=params, **kw)
|
|
72
78
|
|
|
73
79
|
# Executives
|
|
74
80
|
|
|
75
81
|
async def executives(self, ticker: str, **kw: Any) -> list[dict]:
|
|
76
82
|
"""Company quote - real time"""
|
|
77
|
-
return await self.get_path(f"
|
|
83
|
+
return await self.get_path(f"key-executives/{ticker}", **kw)
|
|
78
84
|
|
|
79
85
|
async def insider_trading(self, ticker: str, **kw: Any) -> list[dict]:
|
|
80
86
|
"""Company Insider Trading"""
|
|
81
87
|
return await self.get_path(
|
|
82
|
-
"
|
|
88
|
+
"insider-trading", **self.params(dict(symbol=ticker), **kw)
|
|
83
89
|
)
|
|
84
90
|
|
|
85
91
|
# Rating
|
|
86
92
|
|
|
87
93
|
async def rating(self, ticker: str, **kw: Any) -> list[dict]:
|
|
88
94
|
"""Company rating - real time"""
|
|
89
|
-
return await self.get_path(f"
|
|
95
|
+
return await self.get_path(f"rating/{ticker}", **kw)
|
|
90
96
|
|
|
91
97
|
async def etf_holders(self, ticker: str, **kw: Any) -> list[dict]:
|
|
92
|
-
return await self.get_path(f"
|
|
98
|
+
return await self.get_path(f"etf-holder/{ticker}", **kw)
|
|
93
99
|
|
|
94
100
|
async def ratios(
|
|
95
101
|
self,
|
|
@@ -102,7 +108,7 @@ class FMP(AioHttpClient):
|
|
|
102
108
|
the trailing 12 months"""
|
|
103
109
|
path = "ratios" if period else "ratios-ttm"
|
|
104
110
|
return await self.get_path(
|
|
105
|
-
f"
|
|
111
|
+
f"{path}/{ticker}",
|
|
106
112
|
**self.params(compact_dict(period=period, limit=limit), **kw),
|
|
107
113
|
)
|
|
108
114
|
|
|
@@ -110,14 +116,14 @@ class FMP(AioHttpClient):
|
|
|
110
116
|
"""Stock peers based on sector, exchange and market cap"""
|
|
111
117
|
kwargs = self.params(**kw)
|
|
112
118
|
kwargs["params"]["symbol"] = self.join(*tickers)
|
|
113
|
-
return await self.get_path("
|
|
119
|
+
return await self.get_path("stock_peers", **kwargs)
|
|
114
120
|
|
|
115
121
|
async def news(self, *tickers: str, **kw: Any) -> list[dict]:
|
|
116
122
|
"""Company quote - real time"""
|
|
117
123
|
kwargs = self.params(**kw)
|
|
118
124
|
if tickers:
|
|
119
125
|
kwargs["params"]["tickers"] = self.join(*tickers)
|
|
120
|
-
return await self.get_path("
|
|
126
|
+
return await self.get_path("stock_news", **kwargs)
|
|
121
127
|
|
|
122
128
|
async def search(
|
|
123
129
|
self,
|
|
@@ -125,10 +131,10 @@ class FMP(AioHttpClient):
|
|
|
125
131
|
*,
|
|
126
132
|
exchange: str | None = None,
|
|
127
133
|
limit: int | None = None,
|
|
128
|
-
|
|
134
|
+
symbol: bool = False,
|
|
129
135
|
**kw: Any,
|
|
130
136
|
) -> list[dict]:
|
|
131
|
-
path = "
|
|
137
|
+
path = "search-symbol" if symbol else "search-name"
|
|
132
138
|
return await self.get_path(
|
|
133
139
|
path,
|
|
134
140
|
**self.params(
|
|
@@ -137,15 +143,20 @@ class FMP(AioHttpClient):
|
|
|
137
143
|
)
|
|
138
144
|
|
|
139
145
|
async def prices(
|
|
140
|
-
self,
|
|
146
|
+
self,
|
|
147
|
+
symbol: str,
|
|
148
|
+
frequency: str = "",
|
|
149
|
+
to_date: bool = False,
|
|
150
|
+
**kw: Any,
|
|
141
151
|
) -> pd.DataFrame:
|
|
142
152
|
"""Historical prices, daily if frequency is not provided"""
|
|
143
|
-
|
|
144
|
-
"historical-price-full
|
|
153
|
+
path = (
|
|
154
|
+
"historical-price-eod/full"
|
|
145
155
|
if not frequency
|
|
146
156
|
else f"historical-chart/{frequency}"
|
|
147
157
|
)
|
|
148
|
-
|
|
158
|
+
kw.update(params=dict(symbol=symbol))
|
|
159
|
+
data = await self.get_path(path, **kw)
|
|
149
160
|
if isinstance(data, dict):
|
|
150
161
|
data = data.get("historical", [])
|
|
151
162
|
df = pd.DataFrame(data)
|
|
@@ -164,25 +175,23 @@ class FMP(AioHttpClient):
|
|
|
164
175
|
**kw: Any,
|
|
165
176
|
) -> dict | list[dict]:
|
|
166
177
|
if not from_date:
|
|
167
|
-
data = await self.get_path("
|
|
178
|
+
data = await self.get_path("sectors-performance", params=params, **kw)
|
|
168
179
|
return {d["sector"]: Decimal(d["changesPercentage"][:-1]) for d in data}
|
|
169
180
|
else:
|
|
170
181
|
params = params.copy() if params is not None else {}
|
|
171
182
|
params.update(compact_dict({"from": from_date, "to": to_date}))
|
|
172
183
|
data = await self.get_path(
|
|
173
|
-
"
|
|
184
|
+
"historical-sectors-performance", params=params, **kw
|
|
174
185
|
)
|
|
175
186
|
ts = [dict(nice_sector_performance(d)) for d in data]
|
|
176
187
|
return summary_sector_performance(ts) if summary else ts
|
|
177
188
|
|
|
178
189
|
async def sector_pe(self, **kw: Any) -> list[dict]:
|
|
179
|
-
return cast(
|
|
180
|
-
list[dict], await self.get_path("v4/sector_price_earning_ratio", **kw)
|
|
181
|
-
)
|
|
190
|
+
return cast(list[dict], await self.get_path("sector_price_earning_ratio", **kw))
|
|
182
191
|
|
|
183
192
|
# forex
|
|
184
193
|
async def forex_list(self) -> list[dict]:
|
|
185
|
-
return await self.get_path("
|
|
194
|
+
return await self.get_path("symbol/available-forex-currency-pairs")
|
|
186
195
|
|
|
187
196
|
def historical_frequencies(self) -> dict:
|
|
188
197
|
return {
|
|
@@ -201,7 +210,7 @@ class FMP(AioHttpClient):
|
|
|
201
210
|
|
|
202
211
|
# Crypto
|
|
203
212
|
async def crypto_list(self) -> list[dict]:
|
|
204
|
-
return await self.get_path("
|
|
213
|
+
return await self.get_path("symbol/available-cryptocurrencies")
|
|
205
214
|
|
|
206
215
|
# Internals
|
|
207
216
|
async def get_path(self, path: str, **kw: Any) -> list[dict]:
|
|
@@ -22,3 +22,12 @@ def isoformat(date: str | date) -> str:
|
|
|
22
22
|
|
|
23
23
|
def start_of_day(dt: date | None = None) -> datetime:
|
|
24
24
|
return as_utc(dt).replace(hour=0, minute=0, second=0, microsecond=0)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def as_date(dt: date | None = None) -> date:
|
|
28
|
+
if dt is None:
|
|
29
|
+
return date.today()
|
|
30
|
+
elif isinstance(dt, datetime):
|
|
31
|
+
return dt.date()
|
|
32
|
+
else:
|
|
33
|
+
return dt
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|