quantflow 0.2.7__tar.gz → 0.2.9__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.2.7 → quantflow-0.2.9}/PKG-INFO +15 -8
- {quantflow-0.2.7 → quantflow-0.2.9}/pyproject.toml +14 -13
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/__init__.py +1 -1
- quantflow-0.2.7/quantflow/cli/__init__.py → quantflow-0.2.9/quantflow/cli/app.py +10 -69
- quantflow-0.2.9/quantflow/cli/commands.py +102 -0
- quantflow-0.2.9/quantflow/cli/script.py +14 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/data/fmp.py +25 -6
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/options/pricer.py +2 -2
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/sp/ou.py +7 -2
- quantflow-0.2.9/quantflow/utils/__init__.py +0 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/utils/bins.py +8 -6
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/utils/paths.py +1 -1
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/utils/types.py +5 -4
- {quantflow-0.2.7 → quantflow-0.2.9}/readme.md +6 -1
- quantflow-0.2.7/quantflow/data/client.py +0 -96
- {quantflow-0.2.7 → quantflow-0.2.9}/LICENSE +0 -0
- {quantflow-0.2.7/quantflow/data → quantflow-0.2.9/quantflow/cli}/__init__.py +0 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/cli/settings.py +0 -0
- {quantflow-0.2.7/quantflow/options → quantflow-0.2.9/quantflow/data}/__init__.py +0 -0
- {quantflow-0.2.7/quantflow/sp → quantflow-0.2.9/quantflow/options}/__init__.py +0 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/options/bs.py +0 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/options/calibration.py +0 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/options/inputs.py +0 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/options/surface.py +0 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/py.typed +0 -0
- {quantflow-0.2.7/quantflow/utils → quantflow-0.2.9/quantflow/sp}/__init__.py +0 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/sp/base.py +0 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/sp/bns.py +0 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/sp/cir.py +0 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/sp/copula.py +0 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/sp/dsp.py +0 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/sp/heston.py +0 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/sp/jump_diffusion.py +0 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/sp/poisson.py +0 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/sp/weiner.py +0 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/utils/dates.py +0 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/utils/df.py +0 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/utils/distributions.py +0 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/utils/functions.py +0 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/utils/interest_rates.py +0 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/utils/marginal.py +0 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/utils/numbers.py +0 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/utils/plot.py +0 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/utils/transforms.py +0 -0
- {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/utils/volatility.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: quantflow
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.9
|
|
4
4
|
Summary: quantitative analysis
|
|
5
5
|
License: BSD-3-Clause
|
|
6
6
|
Author: Luca
|
|
@@ -11,15 +11,17 @@ Classifier: Programming Language :: Python :: 3
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Provides-Extra: cli
|
|
14
15
|
Provides-Extra: data
|
|
15
|
-
Requires-Dist:
|
|
16
|
-
Requires-Dist:
|
|
17
|
-
Requires-Dist:
|
|
18
|
-
Requires-Dist:
|
|
19
|
-
Requires-Dist: polars[pandas,pyarrow] (
|
|
20
|
-
Requires-Dist: prompt-toolkit (>=3.0.43,<4.0.0)
|
|
16
|
+
Requires-Dist: aio-fluid[http] (>=1.2.1,<2.0.0) ; extra == "data"
|
|
17
|
+
Requires-Dist: asciichartpy (>=1.5.25,<2.0.0) ; extra == "cli"
|
|
18
|
+
Requires-Dist: ccy (==1.6.0)
|
|
19
|
+
Requires-Dist: click (>=8.1.7,<9.0.0) ; extra == "cli"
|
|
20
|
+
Requires-Dist: polars[pandas,pyarrow] (>=1.11.0,<2.0.0)
|
|
21
|
+
Requires-Dist: prompt-toolkit (>=3.0.43,<4.0.0) ; extra == "cli"
|
|
21
22
|
Requires-Dist: pydantic (>=2.0.2,<3.0.0)
|
|
22
23
|
Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
|
|
24
|
+
Requires-Dist: rich (>=13.9.4,<14.0.0) ; extra == "cli"
|
|
23
25
|
Requires-Dist: scipy (>=1.14.1,<2.0.0)
|
|
24
26
|
Project-URL: Documentation, https://quantmind.github.io/quantflow/
|
|
25
27
|
Project-URL: Homepage, https://github.com/quantmind/quantflow
|
|
@@ -56,6 +58,11 @@ pip install quantflow
|
|
|
56
58
|
|
|
57
59
|
## Command line tools
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
The command line tools are available when installing with the extra `cli` and `data` dependencies.
|
|
60
62
|
|
|
63
|
+
```bash
|
|
64
|
+
pip install quantflow[cli,data]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
It is possible to use the command line tool `qf` to download data and run pricing and calibration scripts.
|
|
61
68
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "quantflow"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.9"
|
|
4
4
|
description = "quantitative analysis"
|
|
5
5
|
authors = ["Luca <luca@quantmind.com>"]
|
|
6
6
|
license = "BSD-3-Clause"
|
|
@@ -14,26 +14,28 @@ Documentation = "https://quantmind.github.io/quantflow/"
|
|
|
14
14
|
[tool.poetry.dependencies]
|
|
15
15
|
python = ">=3.11"
|
|
16
16
|
scipy = "^1.14.1"
|
|
17
|
-
aiohttp = {version = "^3.8.1", optional = true}
|
|
18
17
|
pydantic = "^2.0.2"
|
|
19
|
-
ccy = {version="1.6.0"
|
|
20
|
-
asciichart = "^0.1"
|
|
18
|
+
ccy = {version="1.6.0"}
|
|
21
19
|
python-dotenv = "^1.0.1"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
polars = {version = "^1.11.0", extras=["pandas", "pyarrow"]}
|
|
21
|
+
asciichartpy = { version = "^1.5.25", optional = true }
|
|
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}
|
|
25
26
|
|
|
26
27
|
[tool.poetry.group.dev.dependencies]
|
|
27
28
|
black = "^24.1.1"
|
|
28
|
-
pytest-cov = "^
|
|
29
|
+
pytest-cov = "^6.0.0"
|
|
29
30
|
mypy = "^1.13.0"
|
|
30
31
|
ghp-import = "^2.0.2"
|
|
31
|
-
ruff = "^0.
|
|
32
|
-
pytest-asyncio = "^0.
|
|
32
|
+
ruff = "^0.8.1"
|
|
33
|
+
pytest-asyncio = "^0.25.0"
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
[tool.poetry.extras]
|
|
36
|
-
data = ["
|
|
37
|
+
data = ["aio-fluid"]
|
|
38
|
+
cli = ["asciichartpy", "prompt-toolkit", "rich", "click"]
|
|
37
39
|
|
|
38
40
|
[tool.poetry.group.book]
|
|
39
41
|
optional = true
|
|
@@ -48,7 +50,7 @@ sympy = "^1.12"
|
|
|
48
50
|
ipywidgets = "^8.0.7"
|
|
49
51
|
|
|
50
52
|
[tool.poetry.scripts]
|
|
51
|
-
qf = "quantflow.cli:main"
|
|
53
|
+
qf = "quantflow.cli.script:main"
|
|
52
54
|
|
|
53
55
|
[build-system]
|
|
54
56
|
requires = ["poetry-core>=1.0.0"]
|
|
@@ -68,7 +70,6 @@ profile = "black"
|
|
|
68
70
|
|
|
69
71
|
[tool.ruff]
|
|
70
72
|
lint.select = ["E", "F"]
|
|
71
|
-
extend-exclude = ["fluid_apps/db/migrations"]
|
|
72
73
|
line-length = 88
|
|
73
74
|
|
|
74
75
|
[tool.hatch.version]
|
|
@@ -1,25 +1,15 @@
|
|
|
1
|
-
import asyncio
|
|
2
1
|
import os
|
|
3
2
|
from dataclasses import dataclass, field
|
|
4
3
|
from typing import Any
|
|
5
4
|
|
|
6
5
|
import click
|
|
7
|
-
import dotenv
|
|
8
|
-
import pandas as pd
|
|
9
|
-
from asciichartpy import plot
|
|
10
|
-
from ccy.cli.console import df_to_rich
|
|
11
6
|
from prompt_toolkit import PromptSession
|
|
12
7
|
from prompt_toolkit.history import FileHistory
|
|
13
8
|
from rich.console import Console
|
|
14
9
|
from rich.text import Text
|
|
15
10
|
|
|
16
|
-
from quantflow.data.fmp import FMP
|
|
17
11
|
|
|
18
|
-
from . import settings
|
|
19
|
-
|
|
20
|
-
dotenv.load_dotenv()
|
|
21
|
-
|
|
22
|
-
FREQUENCIES = tuple(FMP().historical_frequencies())
|
|
12
|
+
from . import settings, commands
|
|
23
13
|
|
|
24
14
|
|
|
25
15
|
@click.group()
|
|
@@ -27,60 +17,14 @@ def qf() -> None:
|
|
|
27
17
|
pass
|
|
28
18
|
|
|
29
19
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
data = asyncio.run(get_profile(symbol))[0]
|
|
35
|
-
main.print(data.pop("description"))
|
|
36
|
-
df = pd.DataFrame(data.items(), columns=["Key", "Value"])
|
|
37
|
-
main.print(df_to_rich(df))
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
@qf.command()
|
|
41
|
-
@click.argument("symbol")
|
|
42
|
-
@click.option(
|
|
43
|
-
"-h",
|
|
44
|
-
"--height",
|
|
45
|
-
type=int,
|
|
46
|
-
default=20,
|
|
47
|
-
show_default=True,
|
|
48
|
-
help="Chart height",
|
|
49
|
-
)
|
|
50
|
-
@click.option(
|
|
51
|
-
"-l",
|
|
52
|
-
"--length",
|
|
53
|
-
type=int,
|
|
54
|
-
default=100,
|
|
55
|
-
show_default=True,
|
|
56
|
-
help="Number of data points",
|
|
57
|
-
)
|
|
58
|
-
@click.option(
|
|
59
|
-
"-f",
|
|
60
|
-
"--frequency",
|
|
61
|
-
type=click.Choice(FREQUENCIES),
|
|
62
|
-
default="",
|
|
63
|
-
help="Number of data points",
|
|
64
|
-
)
|
|
65
|
-
def chart(symbol: str, height: int, length: int, frequency: str) -> None:
|
|
66
|
-
"""Symbol chart"""
|
|
67
|
-
df = asyncio.run(get_prices(symbol, frequency))
|
|
68
|
-
data = list(reversed(df["close"].tolist()[:length]))
|
|
69
|
-
print(plot(data, {"height": height}))
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
async def get_prices(symbol: str, frequency: str) -> pd.DataFrame:
|
|
73
|
-
async with FMP() as cli:
|
|
74
|
-
return await cli.prices(symbol, frequency)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
async def get_profile(symbol: str) -> list[dict]:
|
|
78
|
-
async with FMP() as cli:
|
|
79
|
-
return await cli.profile(symbol)
|
|
20
|
+
qf.add_command(commands.exit)
|
|
21
|
+
qf.add_command(commands.profile)
|
|
22
|
+
qf.add_command(commands.search)
|
|
23
|
+
qf.add_command(commands.chart)
|
|
80
24
|
|
|
81
25
|
|
|
82
26
|
@dataclass
|
|
83
|
-
class
|
|
27
|
+
class QfApp:
|
|
84
28
|
console: Console = field(default_factory=Console)
|
|
85
29
|
|
|
86
30
|
def __call__(self) -> None:
|
|
@@ -116,16 +60,13 @@ class App:
|
|
|
116
60
|
if not text:
|
|
117
61
|
return
|
|
118
62
|
elif text == "help":
|
|
119
|
-
return qf.main(["--help"], standalone_mode=False)
|
|
120
|
-
elif text == "exit":
|
|
121
|
-
raise click.Abort()
|
|
63
|
+
return qf.main(["--help"], standalone_mode=False, obj=self)
|
|
122
64
|
|
|
123
65
|
try:
|
|
124
|
-
qf.main(text.split(), standalone_mode=False)
|
|
66
|
+
qf.main(text.split(), standalone_mode=False, obj=self)
|
|
125
67
|
except click.exceptions.MissingParameter as e:
|
|
126
68
|
self.error(e)
|
|
127
69
|
except click.exceptions.NoSuchOption as e:
|
|
128
70
|
self.error(e)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
main = App()
|
|
71
|
+
except click.exceptions.UsageError as e:
|
|
72
|
+
self.error(e)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
import asyncio
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
from asciichartpy import plot
|
|
8
|
+
from ccy.cli.console import df_to_rich
|
|
9
|
+
from quantflow.data.fmp import FMP
|
|
10
|
+
|
|
11
|
+
FREQUENCIES = tuple(FMP().historical_frequencies())
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from quantflow.cli.app import QfApp
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def from_context(ctx: click.Context) -> QfApp:
|
|
18
|
+
return ctx.obj # type: ignore
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@click.command()
|
|
22
|
+
def exit() -> None:
|
|
23
|
+
"""Exit the program"""
|
|
24
|
+
raise click.Abort()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@click.command()
|
|
28
|
+
@click.argument("symbol")
|
|
29
|
+
@click.pass_context
|
|
30
|
+
def profile(ctx: click.Context, symbol: str) -> None:
|
|
31
|
+
"""Company profile"""
|
|
32
|
+
app = from_context(ctx)
|
|
33
|
+
data = asyncio.run(get_profile(symbol))
|
|
34
|
+
if not data:
|
|
35
|
+
raise click.UsageError(f"Company {symbol} not found - try searching")
|
|
36
|
+
else:
|
|
37
|
+
d = data[0]
|
|
38
|
+
app.print(d.pop("description") or "")
|
|
39
|
+
df = pd.DataFrame(d.items(), columns=["Key", "Value"])
|
|
40
|
+
app.print(df_to_rich(df))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@click.command()
|
|
44
|
+
@click.argument("text")
|
|
45
|
+
@click.pass_context
|
|
46
|
+
def search(ctx: click.Context, text: str) -> None:
|
|
47
|
+
"""Search companies"""
|
|
48
|
+
app = from_context(ctx)
|
|
49
|
+
data = asyncio.run(search_company(text))
|
|
50
|
+
df = pd.DataFrame(data, columns=["symbol", "name", "currency", "stockExchange"])
|
|
51
|
+
app.print(df_to_rich(df))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@click.command()
|
|
55
|
+
@click.argument("symbol")
|
|
56
|
+
@click.option(
|
|
57
|
+
"-h",
|
|
58
|
+
"--height",
|
|
59
|
+
type=int,
|
|
60
|
+
default=20,
|
|
61
|
+
show_default=True,
|
|
62
|
+
help="Chart height",
|
|
63
|
+
)
|
|
64
|
+
@click.option(
|
|
65
|
+
"-l",
|
|
66
|
+
"--length",
|
|
67
|
+
type=int,
|
|
68
|
+
default=100,
|
|
69
|
+
show_default=True,
|
|
70
|
+
help="Number of data points",
|
|
71
|
+
)
|
|
72
|
+
@click.option(
|
|
73
|
+
"-f",
|
|
74
|
+
"--frequency",
|
|
75
|
+
type=click.Choice(FREQUENCIES),
|
|
76
|
+
default="",
|
|
77
|
+
help="Frequency of data - if not provided it is daily",
|
|
78
|
+
)
|
|
79
|
+
def chart(symbol: str, height: int, length: int, frequency: str) -> None:
|
|
80
|
+
"""Symbol chart"""
|
|
81
|
+
df = asyncio.run(get_prices(symbol, frequency))
|
|
82
|
+
if df.empty:
|
|
83
|
+
raise click.UsageError(
|
|
84
|
+
f"No data for {symbol} - are you sure the symbol exists?"
|
|
85
|
+
)
|
|
86
|
+
data = list(reversed(df["close"].tolist()[:length]))
|
|
87
|
+
print(plot(data, {"height": height}))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
async def get_prices(symbol: str, frequency: str) -> pd.DataFrame:
|
|
91
|
+
async with FMP() as cli:
|
|
92
|
+
return await cli.prices(symbol, frequency)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
async def get_profile(symbol: str) -> list[dict]:
|
|
96
|
+
async with FMP() as cli:
|
|
97
|
+
return await cli.profile(symbol)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
async def search_company(text: str) -> list[dict]:
|
|
101
|
+
async with FMP() as cli:
|
|
102
|
+
return await cli.search(text)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import dotenv
|
|
2
|
+
|
|
3
|
+
dotenv.load_dotenv()
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from .app import QfApp
|
|
7
|
+
except ImportError:
|
|
8
|
+
raise ImportError(
|
|
9
|
+
"Cannot run qf command line, "
|
|
10
|
+
"quantflow needs to be installed with cli & data extras, "
|
|
11
|
+
"pip install quantflow[cli, data]"
|
|
12
|
+
) from None
|
|
13
|
+
|
|
14
|
+
main = QfApp()
|
|
@@ -2,18 +2,30 @@ import os
|
|
|
2
2
|
from dataclasses import dataclass, field
|
|
3
3
|
from datetime import date, timedelta
|
|
4
4
|
from typing import Any, cast
|
|
5
|
-
|
|
5
|
+
from fluid.utils.http_client import AioHttpClient
|
|
6
|
+
from fluid.utils.data import compact_dict
|
|
6
7
|
import pandas as pd
|
|
8
|
+
from enum import StrEnum
|
|
7
9
|
|
|
8
|
-
from
|
|
9
|
-
from .client import HttpClient, compact
|
|
10
|
+
from quantflow.utils.dates import isoformat
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
@dataclass
|
|
13
|
-
class FMP(
|
|
14
|
+
class FMP(AioHttpClient):
|
|
14
15
|
url: str = "https://financialmodelingprep.com/api"
|
|
15
16
|
key: str = field(default_factory=lambda: os.environ.get("FMP_API_KEY", ""))
|
|
16
17
|
|
|
18
|
+
class freq(StrEnum):
|
|
19
|
+
"""FMP historical frequencies"""
|
|
20
|
+
|
|
21
|
+
one_min = "1min"
|
|
22
|
+
five_min = "5min"
|
|
23
|
+
fifteen_min = "15min"
|
|
24
|
+
thirty_min = "30min"
|
|
25
|
+
one_hour = "1hour"
|
|
26
|
+
four_hour = "4hour"
|
|
27
|
+
daily = ""
|
|
28
|
+
|
|
17
29
|
async def stocks(self, **kw: Any) -> list[dict]:
|
|
18
30
|
return await self.get_path("v3/stock/list", **kw)
|
|
19
31
|
|
|
@@ -80,7 +92,7 @@ class FMP(HttpClient):
|
|
|
80
92
|
path = "ratios" if period else "ratios-ttm"
|
|
81
93
|
return await self.get_path(
|
|
82
94
|
f"v3/{path}/{ticker}",
|
|
83
|
-
**self.params(
|
|
95
|
+
**self.params(compact_dict(period=period, limit=limit), **kw),
|
|
84
96
|
)
|
|
85
97
|
|
|
86
98
|
async def peers(self, *tickers: str, **kw: Any) -> list[dict]:
|
|
@@ -108,12 +120,15 @@ class FMP(HttpClient):
|
|
|
108
120
|
path = "v3/search-ticker" if ticker else "v3/search"
|
|
109
121
|
return await self.get_path(
|
|
110
122
|
path,
|
|
111
|
-
**self.params(
|
|
123
|
+
**self.params(
|
|
124
|
+
compact_dict(query=query, exchange=exchange, limit=limit), **kw
|
|
125
|
+
),
|
|
112
126
|
)
|
|
113
127
|
|
|
114
128
|
async def prices(
|
|
115
129
|
self, ticker: str, frequency: str = "", to_date: bool = False, **kw: Any
|
|
116
130
|
) -> pd.DataFrame:
|
|
131
|
+
"""Historical prices, daily if frequency is not provided"""
|
|
117
132
|
base = (
|
|
118
133
|
"historical-price-full/"
|
|
119
134
|
if not frequency
|
|
@@ -146,6 +161,10 @@ class FMP(HttpClient):
|
|
|
146
161
|
one_year = 525600
|
|
147
162
|
return {k: v / one_year for k, v in self.historical_frequencies().items()}
|
|
148
163
|
|
|
164
|
+
# Crypto
|
|
165
|
+
async def crypto_list(self) -> list[dict]:
|
|
166
|
+
return await self.get_path("v3/symbol/available-cryptocurrencies")
|
|
167
|
+
|
|
149
168
|
# Internals
|
|
150
169
|
async def get_path(self, path: str, **kw: Any) -> list[dict]:
|
|
151
170
|
result = await self.get(f"{self.url}/{path}", **self.params(**kw))
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
|
-
from typing import Any, Generic, NamedTuple, TypeVar
|
|
4
|
+
from typing import Any, Generic, NamedTuple, TypeVar, cast
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
import pandas as pd
|
|
@@ -156,7 +156,7 @@ class OptionPricer(Generic[M]):
|
|
|
156
156
|
moneyness_ttm = np.linspace(-max_moneyness_ttm, max_moneyness_ttm, support)
|
|
157
157
|
implied = np.zeros((len(ttm), len(moneyness_ttm)))
|
|
158
158
|
for i, t in enumerate(ttm):
|
|
159
|
-
maturity = self.maturity(t)
|
|
159
|
+
maturity = self.maturity(cast(float, t))
|
|
160
160
|
implied[i, :] = maturity.interp(moneyness_ttm * np.sqrt(t)).implied_vols
|
|
161
161
|
properties: dict = dict(
|
|
162
162
|
xaxis_title="moneyness_ttm",
|
|
@@ -9,7 +9,7 @@ from scipy.stats import gamma, norm
|
|
|
9
9
|
|
|
10
10
|
from ..utils.distributions import Exponential
|
|
11
11
|
from ..utils.paths import Paths
|
|
12
|
-
from ..utils.types import FloatArrayLike, Vector
|
|
12
|
+
from ..utils.types import FloatArrayLike, Vector, Float
|
|
13
13
|
from .base import Im, IntensityProcess
|
|
14
14
|
from .poisson import CompoundPoissonProcess, D
|
|
15
15
|
from .weiner import WeinerProcess
|
|
@@ -139,7 +139,12 @@ class GammaOU(NGOU[Exponential]):
|
|
|
139
139
|
return Paths(t=time_horizon, data=paths)
|
|
140
140
|
|
|
141
141
|
def _advance(
|
|
142
|
-
self,
|
|
142
|
+
self,
|
|
143
|
+
i: int,
|
|
144
|
+
pp: np.ndarray,
|
|
145
|
+
dt: Float,
|
|
146
|
+
arrival: Float = 0,
|
|
147
|
+
jump: Float = 0,
|
|
143
148
|
) -> int:
|
|
144
149
|
x = pp[i - 1]
|
|
145
150
|
kappa = self.kappa
|
|
File without changes
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import Sequence, cast, Any
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
from pandas import DataFrame
|
|
@@ -13,9 +13,9 @@ def pdf(
|
|
|
13
13
|
symmetric: float | None = None,
|
|
14
14
|
precision: int = 6,
|
|
15
15
|
) -> DataFrame:
|
|
16
|
-
max_value = np.max(data)
|
|
17
|
-
min_value = np.min(data)
|
|
18
|
-
domain = max(abs(data)) if symmetric is not None else max_value - min_value
|
|
16
|
+
max_value = cast(float, np.max(data))
|
|
17
|
+
min_value = cast(float, np.min(data))
|
|
18
|
+
domain: float = max(abs(data)) if symmetric is not None else max_value - min_value # type: ignore
|
|
19
19
|
if num_bins is None:
|
|
20
20
|
if not delta:
|
|
21
21
|
num_bins = 50
|
|
@@ -39,7 +39,9 @@ def pdf(
|
|
|
39
39
|
return DataFrame(dict(pdf=pdf), index=x[1:-1])
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
def event_density(
|
|
42
|
+
def event_density(
|
|
43
|
+
df: DataFrame, columns: Sequence[str], num: int = 10
|
|
44
|
+
) -> dict[str, Any]:
|
|
43
45
|
"""Calculate the probability density of the number of events
|
|
44
46
|
in the dataframe columns
|
|
45
47
|
"""
|
|
@@ -48,5 +50,5 @@ def event_density(df: DataFrame, columns: Sequence, num: int = 10) -> Dict:
|
|
|
48
50
|
for col in columns:
|
|
49
51
|
counts, _ = np.histogram(df[col], bins=bins)
|
|
50
52
|
counts = counts / np.sum(counts)
|
|
51
|
-
data[col] = counts[:num]
|
|
53
|
+
data[col] = counts[:num] # type: ignore
|
|
52
54
|
return data
|
|
@@ -51,7 +51,7 @@ class Paths(BaseModel, arbitrary_types_allowed=True):
|
|
|
51
51
|
@property
|
|
52
52
|
def ys(self) -> list[list[float]]:
|
|
53
53
|
"""Paths as list of list (for visualization tools)"""
|
|
54
|
-
return self.data.transpose().tolist()
|
|
54
|
+
return self.data.transpose().tolist() # type: ignore
|
|
55
55
|
|
|
56
56
|
def mean(self) -> FloatArray:
|
|
57
57
|
"""Mean of paths"""
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
from decimal import Decimal
|
|
2
|
-
from typing import Optional, Union
|
|
2
|
+
from typing import Optional, Union, Any
|
|
3
3
|
|
|
4
4
|
import pandas as pd
|
|
5
5
|
import numpy as np
|
|
6
6
|
import numpy.typing as npt
|
|
7
7
|
|
|
8
8
|
Number = Decimal
|
|
9
|
-
|
|
9
|
+
Float = float | np.floating[Any]
|
|
10
|
+
Numbers = Union[int, Float, np.number]
|
|
10
11
|
NumberType = Union[float, int, str, Number]
|
|
11
12
|
Vector = Union[int, float, complex, np.ndarray, pd.Series]
|
|
12
|
-
FloatArray = npt.NDArray[np.
|
|
13
|
-
IntArray = npt.NDArray[np.
|
|
13
|
+
FloatArray = npt.NDArray[np.floating[Any]]
|
|
14
|
+
IntArray = npt.NDArray[np.signedinteger[Any]]
|
|
14
15
|
FloatArrayLike = FloatArray | float
|
|
15
16
|
|
|
16
17
|
|
|
@@ -28,5 +28,10 @@ pip install quantflow
|
|
|
28
28
|
|
|
29
29
|
## Command line tools
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
The command line tools are available when installing with the extra `cli` and `data` dependencies.
|
|
32
32
|
|
|
33
|
+
```bash
|
|
34
|
+
pip install quantflow[cli,data]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
It is possible to use the command line tool `qf` to download data and run pricing and calibration scripts.
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import os
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
from typing import Any, Self
|
|
5
|
-
|
|
6
|
-
from aiohttp import ClientResponse, ClientSession
|
|
7
|
-
from aiohttp.client_exceptions import ContentTypeError
|
|
8
|
-
|
|
9
|
-
ResponseType = dict | list
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def compact(**kwargs: Any) -> dict:
|
|
13
|
-
return {k: v for k, v in kwargs.items() if v}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class HttpResponseError(RuntimeError):
|
|
17
|
-
def __init__(self, response: ClientResponse, data: ResponseType) -> None:
|
|
18
|
-
self.response = response
|
|
19
|
-
self.data: dict[str, Any] = data if isinstance(data, dict) else {"data": data}
|
|
20
|
-
self.data["request_url"] = str(response.url)
|
|
21
|
-
self.data["request_method"] = response.method
|
|
22
|
-
self.data["response_status"] = response.status
|
|
23
|
-
|
|
24
|
-
@property
|
|
25
|
-
def status(self) -> int:
|
|
26
|
-
return self.response.status
|
|
27
|
-
|
|
28
|
-
def __str__(self) -> str:
|
|
29
|
-
return json.dumps(self.data, indent=4)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@dataclass
|
|
33
|
-
class HttpClient:
|
|
34
|
-
session: ClientSession | None = None
|
|
35
|
-
user_agent: str = os.getenv("HTTP_USER_AGENT", "quantflow/data")
|
|
36
|
-
content_type: str = "application/json"
|
|
37
|
-
session_owner: bool = False
|
|
38
|
-
ResponseError: type[HttpResponseError] = HttpResponseError
|
|
39
|
-
ok_status: frozenset = frozenset((200, 201))
|
|
40
|
-
|
|
41
|
-
def new_session(self, **kwargs: Any) -> ClientSession:
|
|
42
|
-
return ClientSession(**kwargs)
|
|
43
|
-
|
|
44
|
-
def get_session(self) -> ClientSession:
|
|
45
|
-
if not self.session:
|
|
46
|
-
self.session_owner = True
|
|
47
|
-
self.session = ClientSession()
|
|
48
|
-
return self.session
|
|
49
|
-
|
|
50
|
-
async def close(self) -> None:
|
|
51
|
-
if self.session and self.session_owner:
|
|
52
|
-
await self.session.close()
|
|
53
|
-
self.session = None
|
|
54
|
-
|
|
55
|
-
async def __aenter__(self) -> Self:
|
|
56
|
-
return self
|
|
57
|
-
|
|
58
|
-
async def __aexit__(self, *args: Any) -> None:
|
|
59
|
-
await self.close()
|
|
60
|
-
|
|
61
|
-
async def get(self, url: str, **kwargs: Any) -> ResponseType:
|
|
62
|
-
return await self.request("GET", url, **kwargs)
|
|
63
|
-
|
|
64
|
-
async def request(
|
|
65
|
-
self,
|
|
66
|
-
method: str,
|
|
67
|
-
url: str,
|
|
68
|
-
*,
|
|
69
|
-
headers: dict | None = None,
|
|
70
|
-
**kw: Any,
|
|
71
|
-
) -> ResponseType:
|
|
72
|
-
session = self.get_session()
|
|
73
|
-
_headers = self.default_headers()
|
|
74
|
-
_headers.update(headers or ())
|
|
75
|
-
response = await session.request(method, url, headers=_headers, **kw)
|
|
76
|
-
if response.status in self.ok_status:
|
|
77
|
-
return await self.response_data(response)
|
|
78
|
-
elif response.status == 204:
|
|
79
|
-
return {}
|
|
80
|
-
else:
|
|
81
|
-
data = await self.response_error(response)
|
|
82
|
-
raise self.ResponseError(response, data)
|
|
83
|
-
|
|
84
|
-
def default_headers(self) -> dict[str, str]:
|
|
85
|
-
return {"user-agent": self.user_agent, "accept": self.content_type}
|
|
86
|
-
|
|
87
|
-
@classmethod
|
|
88
|
-
async def response_error(cls, response: ClientResponse) -> ResponseType:
|
|
89
|
-
try:
|
|
90
|
-
return await cls.response_data(response)
|
|
91
|
-
except ContentTypeError:
|
|
92
|
-
return dict(message=await response.text())
|
|
93
|
-
|
|
94
|
-
@classmethod
|
|
95
|
-
async def response_data(cls, response: ClientResponse) -> ResponseType:
|
|
96
|
-
return await response.json()
|
|
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
|