kabukit 0.7.1__tar.gz → 0.7.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.
Files changed (44) hide show
  1. {kabukit-0.7.1 → kabukit-0.7.3}/PKG-INFO +19 -3
  2. {kabukit-0.7.1 → kabukit-0.7.3}/pyproject.toml +36 -8
  3. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/core/base.py +2 -1
  4. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/edinet/client.py +13 -0
  5. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/edinet/doc.py +11 -2
  6. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/jquants/client.py +5 -5
  7. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/jquants/statements.py +2 -2
  8. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/utils/concurrent.py +1 -1
  9. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/utils/date.py +2 -1
  10. {kabukit-0.7.1 → kabukit-0.7.3}/LICENSE +0 -0
  11. {kabukit-0.7.1 → kabukit-0.7.3}/README.md +0 -0
  12. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/__init__.py +0 -0
  13. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/analysis/__init__.py +0 -0
  14. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/analysis/indicators.py +0 -0
  15. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/analysis/preprocess.py +0 -0
  16. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/analysis/screener.py +0 -0
  17. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/analysis/visualization/__init__.py +0 -0
  18. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/analysis/visualization/market.py +0 -0
  19. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/analysis/visualization/prices.py +0 -0
  20. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/cli/__init__.py +0 -0
  21. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/cli/app.py +0 -0
  22. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/cli/auth.py +0 -0
  23. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/cli/cache.py +0 -0
  24. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/cli/get.py +0 -0
  25. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/core/__init__.py +0 -0
  26. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/core/client.py +0 -0
  27. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/core/info.py +0 -0
  28. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/core/list.py +0 -0
  29. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/core/prices.py +0 -0
  30. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/core/reports.py +0 -0
  31. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/core/statements.py +0 -0
  32. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/edinet/__init__.py +0 -0
  33. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/edinet/concurrent.py +0 -0
  34. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/jquants/__init__.py +0 -0
  35. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/jquants/calendar.py +0 -0
  36. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/jquants/concurrent.py +0 -0
  37. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/jquants/info.py +0 -0
  38. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/jquants/prices.py +0 -0
  39. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/jquants/schema.py +0 -0
  40. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/jquants/topix.py +0 -0
  41. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/py.typed +0 -0
  42. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/utils/__init__.py +0 -0
  43. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/utils/config.py +0 -0
  44. {kabukit-0.7.1 → kabukit-0.7.3}/src/kabukit/utils/params.py +0 -0
@@ -1,7 +1,8 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: kabukit
3
- Version: 0.7.1
3
+ Version: 0.7.3
4
4
  Summary: A Python toolkit for Japanese financial market data, supporting J-Quants and EDINET APIs.
5
+ Keywords: J-Quants,EDINET,financial data,stock market,investment,toolkit,finance,trading,data analysis,polars
5
6
  Author: daizutabi
6
7
  Author-email: daizutabi <daizutabi@gmail.com>
7
8
  License: MIT License
@@ -27,17 +28,32 @@ License: MIT License
27
28
  SOFTWARE.
28
29
  Classifier: Development Status :: 4 - Beta
29
30
  Classifier: Programming Language :: Python
31
+ Classifier: Programming Language :: Python :: 3 :: Only
30
32
  Classifier: Programming Language :: Python :: 3.13
33
+ Classifier: Programming Language :: Python :: 3.14
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Operating System :: OS Independent
36
+ Classifier: Intended Audience :: Developers
37
+ Classifier: Intended Audience :: Financial and Insurance Industry
38
+ Classifier: Intended Audience :: Science/Research
39
+ Classifier: Topic :: Office/Business :: Financial
40
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
41
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
42
+ Classifier: Topic :: Utilities
31
43
  Requires-Dist: async-typer>=0.1.10
32
44
  Requires-Dist: httpx>=0.28.1
33
- Requires-Dist: platformdirs>=4.4.0
45
+ Requires-Dist: platformdirs>=4.5.0
34
46
  Requires-Dist: polars>=1.34.0
35
47
  Requires-Dist: python-dotenv>=1.1.1
36
- Requires-Dist: rich>=14.1.0
48
+ Requires-Dist: rich>=14.2.0
49
+ Requires-Dist: tenacity>=9.1.2
37
50
  Requires-Dist: tqdm>=4.67.1
38
51
  Requires-Dist: typer>=0.19.2
52
+ Requires-Dist: tzdata ; sys_platform == 'win32'
39
53
  Requires-Python: >=3.13
54
+ Project-URL: Changelog, https://github.com/daizutabi/kabukit/releases
40
55
  Project-URL: Documentation, https://daizutabi.github.io/kabukit/
56
+ Project-URL: Homepage, https://daizutabi.github.io/kabukit/
41
57
  Project-URL: Issues, https://github.com/daizutabi/kabukit/issues
42
58
  Project-URL: Source, https://github.com/daizutabi/kabukit
43
59
  Description-Content-Type: text/markdown
@@ -4,49 +4,77 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "kabukit"
7
- version = "0.7.1"
7
+ version = "0.7.3"
8
8
  description = "A Python toolkit for Japanese financial market data, supporting J-Quants and EDINET APIs."
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
11
+ keywords = [
12
+ "J-Quants",
13
+ "EDINET",
14
+ "financial data",
15
+ "stock market",
16
+ "investment",
17
+ "toolkit",
18
+ "finance",
19
+ "trading",
20
+ "data analysis",
21
+ "polars",
22
+ ]
11
23
  authors = [{ name = "daizutabi", email = "daizutabi@gmail.com" }]
12
24
  classifiers = [
13
25
  "Development Status :: 4 - Beta",
14
26
  "Programming Language :: Python",
27
+ "Programming Language :: Python :: 3 :: Only",
15
28
  "Programming Language :: Python :: 3.13",
29
+ "Programming Language :: Python :: 3.14",
30
+ "License :: OSI Approved :: MIT License",
31
+ "Operating System :: OS Independent",
32
+ "Intended Audience :: Developers",
33
+ "Intended Audience :: Financial and Insurance Industry",
34
+ "Intended Audience :: Science/Research",
35
+ "Topic :: Office/Business :: Financial",
36
+ "Topic :: Software Development :: Libraries :: Python Modules",
37
+ "Topic :: Scientific/Engineering :: Information Analysis",
38
+ "Topic :: Utilities",
16
39
  ]
17
40
  requires-python = ">=3.13"
18
41
  dependencies = [
19
42
  "async-typer>=0.1.10",
20
43
  "httpx>=0.28.1",
21
- "platformdirs>=4.4.0",
44
+ "platformdirs>=4.5.0",
22
45
  "polars>=1.34.0",
23
46
  "python-dotenv>=1.1.1",
24
- "rich>=14.1.0",
47
+ "rich>=14.2.0",
48
+ "tenacity>=9.1.2",
25
49
  "tqdm>=4.67.1",
26
50
  "typer>=0.19.2",
51
+ "tzdata; sys_platform == 'win32'",
27
52
  ]
28
53
 
29
54
  [project.scripts]
30
55
  kabu = "kabukit.cli.app:app"
31
56
 
32
57
  [project.urls]
58
+ Changelog = "https://github.com/daizutabi/kabukit/releases"
33
59
  Documentation = "https://daizutabi.github.io/kabukit/"
34
- Source = "https://github.com/daizutabi/kabukit"
60
+ Homepage = "https://daizutabi.github.io/kabukit/"
35
61
  Issues = "https://github.com/daizutabi/kabukit/issues"
62
+ Source = "https://github.com/daizutabi/kabukit"
36
63
 
37
64
  [dependency-groups]
38
65
  dev = [
39
- "altair>=5.5.0",
66
+ "altair @ git+https://github.com/vega/altair.git@main",
40
67
  "basedpyright>=1.31.6",
41
68
  "marimo[lsp]>=0.16.5",
42
- "numpy>=2.3.3", # polars 1.33 type hinting workaround,
69
+ "numpy>=2.3.3",
70
+ "pre-commit>=4.3.0",
43
71
  "pytest-asyncio>=1.2.0",
44
72
  "pytest-clarity>=1.0.1",
45
73
  "pytest-cov>=7.0.0",
46
74
  "pytest-mock>=3.15.1",
47
75
  "pytest-randomly>=4.0.1",
48
76
  "pytest-xdist>=3.8.0",
49
- "scipy",
77
+ "scipy>=1.16.2",
50
78
  "vegafusion-python-embed>=1.6.9",
51
79
  "vegafusion>=2.0.3",
52
80
  "vl-convert-python>=1.8.0",
@@ -97,7 +125,7 @@ ignore = [
97
125
  [tool.ruff.format]
98
126
  exclude = ["schema.py"]
99
127
 
100
- [tool.basedpyright]
128
+ [tool.pyright]
101
129
  include = ["src", "tests"]
102
130
  reportAny = false
103
131
  reportExplicitAny = false
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import datetime
4
4
  from typing import TYPE_CHECKING
5
+ from zoneinfo import ZoneInfo
5
6
 
6
7
  import polars as pl
7
8
 
@@ -30,7 +31,7 @@ class Base:
30
31
  def write(self) -> Path:
31
32
  data_dir = self.data_dir()
32
33
  data_dir.mkdir(parents=True, exist_ok=True)
33
- path = datetime.datetime.today().strftime("%Y%m%d") # noqa: DTZ002
34
+ path = datetime.datetime.now(ZoneInfo("Asia/Tokyo")).strftime("%Y%m%d")
34
35
  filename = data_dir / f"{path}.parquet"
35
36
  self.data.write_parquet(filename)
36
37
  return filename
@@ -6,7 +6,9 @@ import zipfile
6
6
  from enum import StrEnum
7
7
  from typing import TYPE_CHECKING
8
8
 
9
+ import httpx
9
10
  from polars import DataFrame
11
+ from tenacity import retry, retry_if_exception, stop_after_attempt, wait_exponential
10
12
 
11
13
  from kabukit.core.client import Client
12
14
  from kabukit.utils.config import load_dotenv
@@ -24,6 +26,11 @@ API_VERSION = "v2"
24
26
  BASE_URL = f"https://api.edinet-fsa.go.jp/api/{API_VERSION}"
25
27
 
26
28
 
29
+ def is_retryable(e: BaseException) -> bool:
30
+ """Return True if the exception is a retryable network error."""
31
+ return isinstance(e, (httpx.ConnectTimeout, httpx.ReadTimeout, httpx.ConnectError))
32
+
33
+
27
34
  class AuthKey(StrEnum):
28
35
  """Environment variable keys for EDINET authentication."""
29
36
 
@@ -43,6 +50,12 @@ class EdinetClient(Client):
43
50
  if api_key:
44
51
  self.client.params = {"Subscription-Key": api_key}
45
52
 
53
+ @retry(
54
+ reraise=True,
55
+ stop=stop_after_attempt(3),
56
+ wait=wait_exponential(multiplier=1, min=2, max=10),
57
+ retry=retry_if_exception(is_retryable),
58
+ )
46
59
  async def get(self, url: str, params: QueryParamTypes) -> Response:
47
60
  resp = await self.client.get(url, params=params)
48
61
  resp.raise_for_status()
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import datetime
4
4
  from typing import TYPE_CHECKING
5
+ from zoneinfo import ZoneInfo
5
6
 
6
7
  import polars as pl
7
8
 
@@ -11,7 +12,11 @@ if TYPE_CHECKING:
11
12
 
12
13
  def clean_list(df: DataFrame, date: str | datetime.date) -> DataFrame:
13
14
  if isinstance(date, str):
14
- date = datetime.datetime.strptime(date, "%Y-%m-%d").date() # noqa: DTZ007
15
+ date = (
16
+ datetime.datetime.strptime(date, "%Y-%m-%d")
17
+ .replace(tzinfo=ZoneInfo("Asia/Tokyo"))
18
+ .date()
19
+ )
15
20
 
16
21
  null_columns = [c for c in df.columns if df[c].dtype == pl.Null]
17
22
 
@@ -21,7 +26,11 @@ def clean_list(df: DataFrame, date: str | datetime.date) -> DataFrame:
21
26
  )
22
27
  .with_columns(
23
28
  pl.lit(date).alias("Date"),
24
- pl.col("^.+DateTime$").str.to_datetime("%Y-%m-%d %H:%M", strict=False),
29
+ pl.col("^.+DateTime$").str.to_datetime(
30
+ "%Y-%m-%d %H:%M",
31
+ strict=False,
32
+ time_zone="Asia/Tokyo",
33
+ ),
25
34
  pl.col("^period.+$").str.to_date("%Y-%m-%d", strict=False),
26
35
  pl.col("^.+Flag$").cast(pl.Int8).cast(pl.Boolean),
27
36
  pl.col("^.+Code$").cast(pl.String),
@@ -5,6 +5,7 @@ import datetime
5
5
  import os
6
6
  from enum import StrEnum
7
7
  from typing import TYPE_CHECKING, final
8
+ from zoneinfo import ZoneInfo
8
9
 
9
10
  import polars as pl
10
11
  from polars import DataFrame
@@ -32,10 +33,9 @@ class _CalendarCache:
32
33
  async def get_holidays(self, client: JQuantsClient) -> list[datetime.date]:
33
34
  async with self._lock:
34
35
  if self._holidays is None:
35
- calendar_df = await client.get_calendar()
36
- self._holidays = (
37
- calendar_df.filter(pl.col("IsHoliday")).get_column("Date").to_list()
38
- )
36
+ df = await client.get_calendar()
37
+ holidays = df.filter(pl.col("IsHoliday"))["Date"]
38
+ self._holidays = holidays.to_list()
39
39
  return self._holidays
40
40
 
41
41
 
@@ -261,7 +261,7 @@ class JQuantsClient(Client):
261
261
  clean: bool = True,
262
262
  ) -> DataFrame:
263
263
  """直近利用可能な日付の株価を取得する。"""
264
- today = datetime.date.today() # noqa: DTZ011
264
+ today = datetime.datetime.now(ZoneInfo("Asia/Tokyo")).date()
265
265
 
266
266
  for days in range(num_days):
267
267
  date = today - datetime.timedelta(days)
@@ -81,10 +81,10 @@ def _cast_bool(df: DataFrame) -> DataFrame:
81
81
  def with_date(df: DataFrame, holidays: list[datetime.date]) -> DataFrame:
82
82
  """`Date`列を追加する。
83
83
 
84
- 開示日が休日のとき、あるいは、開示時刻が15時以降の場合、Dateを開示日の翌営業日に設定する。
84
+ 開示日が休日のとき、あるいは、開示時刻が15時30分以降の場合、Dateを開示日の翌営業日に設定する。
85
85
  """
86
86
  is_after_hours = pl.col("DisclosedTime").is_null() | (
87
- pl.col("DisclosedTime") > datetime.time(15, 0)
87
+ pl.col("DisclosedTime") >= datetime.time(15, 30)
88
88
  )
89
89
 
90
90
  return df.select(
@@ -150,5 +150,5 @@ async def fetch(
150
150
  if callback:
151
151
  stream = (x if (r := callback(x)) is None else r async for x in stream)
152
152
 
153
- dfs = [df async for df in stream if not df.is_empty()]
153
+ dfs = [df async for df in stream if not df.is_empty()] # ty: ignore[not-iterable]
154
154
  return pl.concat(dfs) if dfs else pl.DataFrame()
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import datetime
4
+ from zoneinfo import ZoneInfo
4
5
 
5
6
 
6
7
  def get_dates(days: int | None = None, years: int | None = None) -> list[datetime.date]:
@@ -11,7 +12,7 @@ def get_dates(days: int | None = None, years: int | None = None) -> list[datetim
11
12
  years (int | None): 過去years年の日付リストを取得する。
12
13
  daysが指定されている場合は無視される。
13
14
  """
14
- end_date = datetime.date.today() # noqa: DTZ011
15
+ end_date = datetime.datetime.now(ZoneInfo("Asia/Tokyo")).date()
15
16
 
16
17
  if days is not None:
17
18
  start_date = end_date - datetime.timedelta(days=days)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes