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.
Files changed (45) hide show
  1. {quantflow-0.2.7 → quantflow-0.2.9}/PKG-INFO +15 -8
  2. {quantflow-0.2.7 → quantflow-0.2.9}/pyproject.toml +14 -13
  3. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/__init__.py +1 -1
  4. quantflow-0.2.7/quantflow/cli/__init__.py → quantflow-0.2.9/quantflow/cli/app.py +10 -69
  5. quantflow-0.2.9/quantflow/cli/commands.py +102 -0
  6. quantflow-0.2.9/quantflow/cli/script.py +14 -0
  7. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/data/fmp.py +25 -6
  8. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/options/pricer.py +2 -2
  9. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/sp/ou.py +7 -2
  10. quantflow-0.2.9/quantflow/utils/__init__.py +0 -0
  11. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/utils/bins.py +8 -6
  12. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/utils/paths.py +1 -1
  13. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/utils/types.py +5 -4
  14. {quantflow-0.2.7 → quantflow-0.2.9}/readme.md +6 -1
  15. quantflow-0.2.7/quantflow/data/client.py +0 -96
  16. {quantflow-0.2.7 → quantflow-0.2.9}/LICENSE +0 -0
  17. {quantflow-0.2.7/quantflow/data → quantflow-0.2.9/quantflow/cli}/__init__.py +0 -0
  18. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/cli/settings.py +0 -0
  19. {quantflow-0.2.7/quantflow/options → quantflow-0.2.9/quantflow/data}/__init__.py +0 -0
  20. {quantflow-0.2.7/quantflow/sp → quantflow-0.2.9/quantflow/options}/__init__.py +0 -0
  21. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/options/bs.py +0 -0
  22. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/options/calibration.py +0 -0
  23. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/options/inputs.py +0 -0
  24. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/options/surface.py +0 -0
  25. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/py.typed +0 -0
  26. {quantflow-0.2.7/quantflow/utils → quantflow-0.2.9/quantflow/sp}/__init__.py +0 -0
  27. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/sp/base.py +0 -0
  28. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/sp/bns.py +0 -0
  29. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/sp/cir.py +0 -0
  30. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/sp/copula.py +0 -0
  31. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/sp/dsp.py +0 -0
  32. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/sp/heston.py +0 -0
  33. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/sp/jump_diffusion.py +0 -0
  34. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/sp/poisson.py +0 -0
  35. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/sp/weiner.py +0 -0
  36. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/utils/dates.py +0 -0
  37. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/utils/df.py +0 -0
  38. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/utils/distributions.py +0 -0
  39. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/utils/functions.py +0 -0
  40. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/utils/interest_rates.py +0 -0
  41. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/utils/marginal.py +0 -0
  42. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/utils/numbers.py +0 -0
  43. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/utils/plot.py +0 -0
  44. {quantflow-0.2.7 → quantflow-0.2.9}/quantflow/utils/transforms.py +0 -0
  45. {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.7
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: aiohttp (>=3.8.1,<4.0.0) ; extra == "data"
16
- Requires-Dist: asciichart (>=0.1,<0.2)
17
- Requires-Dist: asciichartpy (>=1.5.25,<2.0.0)
18
- Requires-Dist: ccy[cli] (==1.6.0)
19
- Requires-Dist: polars[pandas,pyarrow] (==1.11.0)
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
- When installing with the extra `data` dependencies, it is possible to use the command line tool `qf`
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.7"
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", extras=["cli"]}
20
- asciichart = "^0.1"
18
+ ccy = {version="1.6.0"}
21
19
  python-dotenv = "^1.0.1"
22
- asciichartpy = "^1.5.25"
23
- prompt-toolkit = "^3.0.43"
24
- polars = {version = "1.11.0", extras=["pandas", "pyarrow"]}
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 = "^5.0.0"
29
+ pytest-cov = "^6.0.0"
29
30
  mypy = "^1.13.0"
30
31
  ghp-import = "^2.0.2"
31
- ruff = "^0.7.1"
32
- pytest-asyncio = "^0.24.0"
32
+ ruff = "^0.8.1"
33
+ pytest-asyncio = "^0.25.0"
33
34
 
34
35
 
35
36
  [tool.poetry.extras]
36
- data = ["aiohttp"]
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,3 +1,3 @@
1
1
  """Quantitative analysis and pricing"""
2
2
 
3
- __version__ = "0.2.7"
3
+ __version__ = "0.2.9"
@@ -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
- @qf.command()
31
- @click.argument("symbol")
32
- def profile(symbol: str) -> None:
33
- """Company profile"""
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 App:
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 ..utils.dates import isoformat
9
- from .client import HttpClient, compact
10
+ from quantflow.utils.dates import isoformat
10
11
 
11
12
 
12
13
  @dataclass
13
- class FMP(HttpClient):
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(compact(period=period, limit=limit), **kw),
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(compact(query=query, exchange=exchange, limit=limit), **kw),
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, i: int, pp: np.ndarray, dt: float, arrival: float = 0, jump: float = 0
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 Dict, Sequence
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(df: DataFrame, columns: Sequence, num: int = 10) -> Dict:
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
- Numbers = Union[int, float, np.number]
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.float64]
13
- IntArray = npt.NDArray[np.int_]
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
- When installing with the extra `data` dependencies, it is possible to use the command line tool `qf`
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