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.
Files changed (60) hide show
  1. {quantflow-0.4.3 → quantflow-0.4.4}/LICENSE +1 -1
  2. {quantflow-0.4.3 → quantflow-0.4.4}/PKG-INFO +4 -6
  3. {quantflow-0.4.3 → quantflow-0.4.4}/pyproject.toml +48 -23
  4. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/__init__.py +1 -1
  5. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/cli/commands/stocks.py +1 -1
  6. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/data/deribit.py +43 -14
  7. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/data/fmp.py +109 -44
  8. quantflow-0.4.4/quantflow/options/bs.py +132 -0
  9. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/sp/base.py +22 -16
  10. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/sp/cir.py +11 -15
  11. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/sp/dsp.py +3 -3
  12. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/sp/heston.py +69 -44
  13. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/sp/jump_diffusion.py +33 -24
  14. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/sp/ou.py +5 -8
  15. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/sp/poisson.py +71 -43
  16. quantflow-0.4.4/quantflow/ta/__init__.py +5 -0
  17. quantflow-0.4.4/quantflow/ta/ewma.py +131 -0
  18. quantflow-0.4.4/quantflow/ta/kalman.py +72 -0
  19. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/ta/paths.py +53 -41
  20. quantflow-0.4.4/quantflow/ta/supersmoother.py +116 -0
  21. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/utils/dates.py +6 -0
  22. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/utils/distributions.py +34 -33
  23. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/utils/plot.py +5 -0
  24. {quantflow-0.4.3 → quantflow-0.4.4}/readme.md +1 -4
  25. quantflow-0.4.3/quantflow/options/bs.py +0 -122
  26. quantflow-0.4.3/quantflow/utils/__init__.py +0 -0
  27. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/cli/__init__.py +0 -0
  28. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/cli/app.py +0 -0
  29. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/cli/commands/__init__.py +0 -0
  30. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/cli/commands/base.py +0 -0
  31. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/cli/commands/crypto.py +0 -0
  32. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/cli/commands/fred.py +0 -0
  33. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/cli/commands/vault.py +0 -0
  34. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/cli/script.py +0 -0
  35. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/cli/settings.py +0 -0
  36. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/data/__init__.py +0 -0
  37. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/data/fed.py +0 -0
  38. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/data/fiscal_data.py +0 -0
  39. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/data/fred.py +0 -0
  40. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/data/vault.py +0 -0
  41. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/options/__init__.py +0 -0
  42. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/options/calibration.py +0 -0
  43. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/options/inputs.py +0 -0
  44. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/options/pricer.py +0 -0
  45. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/options/surface.py +0 -0
  46. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/py.typed +0 -0
  47. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/sp/__init__.py +0 -0
  48. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/sp/bns.py +0 -0
  49. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/sp/copula.py +0 -0
  50. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/sp/weiner.py +0 -0
  51. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/ta/base.py +0 -0
  52. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/ta/ohlc.py +0 -0
  53. {quantflow-0.4.3/quantflow/ta → quantflow-0.4.4/quantflow/utils}/__init__.py +0 -0
  54. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/utils/bins.py +0 -0
  55. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/utils/functions.py +0 -0
  56. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/utils/interest_rates.py +0 -0
  57. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/utils/marginal.py +0 -0
  58. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/utils/numbers.py +0 -0
  59. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/utils/transforms.py +0 -0
  60. {quantflow-0.4.3 → quantflow-0.4.4}/quantflow/utils/types.py +0 -0
@@ -1,4 +1,4 @@
1
- Copyright (c) 2023-2025 Quantmind
1
+ Copyright (c) 2023-2026 Quantmind
2
2
 
3
3
  Redistribution and use in source and binary forms, with or without modification,
4
4
  are permitted provided that the following conditions are met:
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quantflow
3
- Version: 0.4.3
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,<4.0
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
- Documentation is available as [quantflow jupyter book](https://quantflow.quantmind.com).
45
+ ![btcvol](https://github.com/quantmind/quantflow/assets/144320/88ed85d1-c3c5-489c-ac07-21b036593214)
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
- ![btcvol](https://github.com/quantmind/quantflow/assets/144320/88ed85d1-c3c5-489c-ac07-21b036593214)
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"
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,<4.0"
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 = "^25.1.0"
39
- pytest-cov = "^6.0.0"
40
- mypy = "^1.14.1"
38
+ black = "^26.1.0"
41
39
  ghp-import = "^2.0.2"
42
- ruff = "^0.12.2"
40
+ isort = "^8.0.0"
41
+ mypy = "^1.14.1"
43
42
  pytest-asyncio = "^1.0.0"
44
- isort = "^6.0.1"
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
- jupyter-book = "^1.0.0"
52
- jupytext = "^1.13.8"
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
- jupyterlab = "^4.0.2"
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
- formats = "ipynb,myst"
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]
@@ -1,3 +1,3 @@
1
1
  """Quantitative analysis and pricing"""
2
2
 
3
- __version__ = "0.4.3"
3
+ __version__ = "0.4.4"
@@ -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
- Fetch market and static data from `Deribit`_ API.
43
+ ## Example
44
+
45
+ ```python
46
+ from quantflow.data.deribit import Deribit
41
47
 
42
- .. _Deribit: https://docs.deribit.com/
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, currency: str, kind: InstrumentKind | None = None, **kw: Any
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(self, currency: str, **kw: Any) -> pd.DataFrame:
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: Number | None = None,
94
- exclude_volume: Number | None = None,
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(self, path: str, **kw: Any) -> dict:
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(self, response: HttpResponse) -> list[dict]:
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(self, response: HttpResponse) -> pd.DataFrame:
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, timedelta
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 isoformat
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(self, *tickers: str, **kw: Any) -> list[dict]:
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(self, *tickers: str, **kw: Any) -> list[dict]:
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: str | date = "",
68
- to_date: str | date = "",
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
- if not from_date:
73
- from_date = date.today()
74
- if not to_date:
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(self, ticker: str, **kw: Any) -> list[dict]:
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(self, ticker: str, **kw: Any) -> list[dict]:
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(self, ticker: str, **kw: Any) -> list[dict]:
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(self, ticker: str, **kw: Any) -> list[dict]:
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: str | None = None,
104
- limit: int | None = None,
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(self, *tickers: str, **kw: Any) -> list[dict]:
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(self, *tickers: str, **kw: Any) -> list[dict]:
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
- frequency: str = "",
149
- to_date: bool = False,
150
- **kw: Any,
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
- kw.update(params=dict(symbol=symbol))
159
- data = await self.get_path(path, **kw)
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 to_date and "date" in df.columns:
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: date | None = None,
172
- to_date: date | None = None,
173
- summary: bool = False,
174
- params: dict | None = None,
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(self, path: str, **kw: Any) -> list[dict]:
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(self, *tickers: str) -> str:
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(self, params: dict | None = None, **kw: Any) -> dict:
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}