kabukit 0.2.1__tar.gz → 0.3.1__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.2.1 → kabukit-0.3.1}/PKG-INFO +12 -7
  2. kabukit-0.3.1/README.md +28 -0
  3. {kabukit-0.2.1 → kabukit-0.3.1}/pyproject.toml +22 -12
  4. kabukit-0.3.1/src/kabukit/__init__.py +17 -0
  5. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/cli/app.py +2 -1
  6. kabukit-0.3.1/src/kabukit/cli/get.py +154 -0
  7. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/core/base.py +8 -3
  8. kabukit-0.3.1/src/kabukit/core/list.py +12 -0
  9. kabukit-0.3.1/src/kabukit/core/reports.py +12 -0
  10. kabukit-0.3.1/src/kabukit/edinet/__init__.py +3 -0
  11. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/edinet/client.py +3 -7
  12. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/edinet/concurrent.py +6 -27
  13. kabukit-0.3.1/src/kabukit/edinet/doc.py +46 -0
  14. kabukit-0.3.1/src/kabukit/jquants/__init__.py +4 -0
  15. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/jquants/client.py +21 -6
  16. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/jquants/statements.py +1 -12
  17. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/utils/concurrent.py +4 -3
  18. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/utils/config.py +4 -7
  19. kabukit-0.3.1/src/kabukit/utils/date.py +27 -0
  20. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/utils/params.py +5 -5
  21. kabukit-0.2.1/README.md +0 -18
  22. kabukit-0.2.1/src/kabukit/__init__.py +0 -7
  23. kabukit-0.2.1/src/kabukit/edinet/__init__.py +0 -3
  24. kabukit-0.2.1/src/kabukit/edinet/doc.py +0 -32
  25. kabukit-0.2.1/src/kabukit/jquants/__init__.py +0 -3
  26. {kabukit-0.2.1 → kabukit-0.3.1}/LICENSE +0 -0
  27. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/analysis/__init__.py +0 -0
  28. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/analysis/indicators.py +0 -0
  29. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/analysis/preprocess.py +0 -0
  30. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/analysis/screener.py +0 -0
  31. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/analysis/visualization.py +0 -0
  32. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/cli/__init__.py +0 -0
  33. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/cli/auth.py +0 -0
  34. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/core/__init__.py +0 -0
  35. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/core/client.py +0 -0
  36. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/core/info.py +0 -0
  37. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/core/prices.py +0 -0
  38. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/core/statements.py +0 -0
  39. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/jquants/concurrent.py +0 -0
  40. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/jquants/info.py +0 -0
  41. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/jquants/prices.py +0 -0
  42. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/jquants/schema.py +0 -0
  43. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/py.typed +0 -0
  44. {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/utils/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: kabukit
3
- Version: 0.2.1
3
+ Version: 0.3.1
4
4
  Summary: A Python toolkit for Japanese financial market data, supporting J-Quants and EDINET APIs.
5
5
  Author: daizutabi
6
6
  Author-email: daizutabi <daizutabi@gmail.com>
@@ -27,21 +27,16 @@ License: MIT License
27
27
  SOFTWARE.
28
28
  Classifier: Development Status :: 4 - Beta
29
29
  Classifier: Programming Language :: Python
30
- Classifier: Programming Language :: Python :: 3.12
31
30
  Classifier: Programming Language :: Python :: 3.13
32
31
  Requires-Dist: altair>=5
33
32
  Requires-Dist: async-typer>=0.1
34
33
  Requires-Dist: holidays>=0.81
35
34
  Requires-Dist: httpx>=0.28.1
36
- Requires-Dist: marimo[lsp]>=0.16
37
35
  Requires-Dist: platformdirs>=4
38
36
  Requires-Dist: polars>=1
39
37
  Requires-Dist: python-dotenv>=1
40
38
  Requires-Dist: typer>=0.19
41
- Requires-Dist: vegafusion-python-embed>=1.6
42
- Requires-Dist: vegafusion>=2
43
- Requires-Dist: vl-convert-python>=1.8
44
- Requires-Python: >=3.12
39
+ Requires-Python: >=3.13
45
40
  Project-URL: Documentation, https://daizutabi.github.io/kabukit/
46
41
  Project-URL: Issues, https://github.com/daizutabi/kabukit/issues
47
42
  Project-URL: Source, https://github.com/daizutabi/kabukit
@@ -53,6 +48,9 @@ A Python toolkit for Japanese financial market data, supporting J-Quants and EDI
53
48
 
54
49
  [![PyPI Version][pypi-v-image]][pypi-v-link]
55
50
  [![Python Version][python-v-image]][python-v-link]
51
+ [![Build Status][GHAction-image]][GHAction-link]
52
+ [![Coverage Status][codecov-image]][codecov-link]
53
+ [![Documentation Status][docs-image]][docs-link]
56
54
 
57
55
  ## Installation
58
56
 
@@ -61,7 +59,14 @@ pip install kabukit
61
59
  ```
62
60
 
63
61
  <!-- Badges -->
62
+
64
63
  [pypi-v-image]: https://img.shields.io/pypi/v/kabukit.svg
65
64
  [pypi-v-link]: https://pypi.org/project/kabukit/
66
65
  [python-v-image]: https://img.shields.io/pypi/pyversions/kabukit.svg
67
66
  [python-v-link]: https://pypi.org/project/kabukit
67
+ [GHAction-image]: https://github.com/daizutabi/kabukit/actions/workflows/ci.yaml/badge.svg?branch=main&event=push
68
+ [GHAction-link]: https://github.com/daizutabi/kabukit/actions?query=event%3Apush+branch%3Amain
69
+ [codecov-image]: https://codecov.io/github/daizutabi/kabukit/graph/badge.svg?token=Yu6lAdVVnd
70
+ [codecov-link]: https://codecov.io/github/daizutabi/kabukit?branch=main
71
+ [docs-image]: https://img.shields.io/badge/docs-latest-blue.svg
72
+ [docs-link]: https://daizutabi.github.io/kabukit/
@@ -0,0 +1,28 @@
1
+ # kabukit
2
+
3
+ A Python toolkit for Japanese financial market data, supporting J-Quants and EDINET APIs.
4
+
5
+ [![PyPI Version][pypi-v-image]][pypi-v-link]
6
+ [![Python Version][python-v-image]][python-v-link]
7
+ [![Build Status][GHAction-image]][GHAction-link]
8
+ [![Coverage Status][codecov-image]][codecov-link]
9
+ [![Documentation Status][docs-image]][docs-link]
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ pip install kabukit
15
+ ```
16
+
17
+ <!-- Badges -->
18
+
19
+ [pypi-v-image]: https://img.shields.io/pypi/v/kabukit.svg
20
+ [pypi-v-link]: https://pypi.org/project/kabukit/
21
+ [python-v-image]: https://img.shields.io/pypi/pyversions/kabukit.svg
22
+ [python-v-link]: https://pypi.org/project/kabukit
23
+ [GHAction-image]: https://github.com/daizutabi/kabukit/actions/workflows/ci.yaml/badge.svg?branch=main&event=push
24
+ [GHAction-link]: https://github.com/daizutabi/kabukit/actions?query=event%3Apush+branch%3Amain
25
+ [codecov-image]: https://codecov.io/github/daizutabi/kabukit/graph/badge.svg?token=Yu6lAdVVnd
26
+ [codecov-link]: https://codecov.io/github/daizutabi/kabukit?branch=main
27
+ [docs-image]: https://img.shields.io/badge/docs-latest-blue.svg
28
+ [docs-link]: https://daizutabi.github.io/kabukit/
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "kabukit"
7
- version = "0.2.1"
7
+ version = "0.3.1"
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" }
@@ -12,23 +12,18 @@ authors = [{ name = "daizutabi", email = "daizutabi@gmail.com" }]
12
12
  classifiers = [
13
13
  "Development Status :: 4 - Beta",
14
14
  "Programming Language :: Python",
15
- "Programming Language :: Python :: 3.12",
16
15
  "Programming Language :: Python :: 3.13",
17
16
  ]
18
- requires-python = ">=3.12"
17
+ requires-python = ">=3.13"
19
18
  dependencies = [
20
19
  "altair>=5",
21
20
  "async-typer>=0.1",
22
21
  "holidays>=0.81",
23
22
  "httpx>=0.28.1",
24
- "marimo[lsp]>=0.16",
25
23
  "platformdirs>=4",
26
24
  "polars>=1",
27
25
  "python-dotenv>=1",
28
26
  "typer>=0.19",
29
- "vegafusion-python-embed>=1.6",
30
- "vegafusion>=2",
31
- "vl-convert-python>=1.8",
32
27
  ]
33
28
 
34
29
  [project.scripts]
@@ -42,20 +37,35 @@ Issues = "https://github.com/daizutabi/kabukit/issues"
42
37
  [dependency-groups]
43
38
  dev = [
44
39
  "basedpyright>=1.31.4",
45
- "numpy>=2.3.3", # polars 1.33 type hinting workaround,
40
+ "marimo[lsp]>=0.16",
41
+ "numpy>=2.3.3", # polars 1.33 type hinting workaround,
46
42
  "pytest-asyncio>=1.2.0",
47
43
  "pytest-clarity>=1.0.1",
48
44
  "pytest-cov>=7.0.0",
45
+ "pytest-mock>=3.15.1",
49
46
  "pytest-randomly>=4.0.1",
50
47
  "pytest-xdist>=3.8.0",
51
48
  "rich>=14.1.0",
52
49
  "tqdm>=4.67.1",
50
+ "vegafusion-python-embed>=1.6",
51
+ "vegafusion>=2",
52
+ "vl-convert-python>=1.8",
53
53
  ]
54
54
  docs = ["mkapi>=4.4", "mkdocs-marimo", "mkdocs-material"]
55
55
 
56
56
  [tool.pytest.ini_options]
57
- addopts = ["--cov=kabukit", "--cov-report=lcov:lcov.info", "--doctest-modules"]
58
- testpaths = ["src", "tests"]
57
+ addopts = [
58
+ "--cov=kabukit",
59
+ "--cov-report=lcov:lcov.info",
60
+ "--doctest-modules",
61
+ "-m",
62
+ "not integration and not validation",
63
+ ]
64
+ testpaths = ["tests/unit", "tests/integration", "tests/validation"]
65
+ markers = [
66
+ "integration: marks tests as integration tests",
67
+ "validation: marks tests as data validation tests",
68
+ ]
59
69
 
60
70
  [tool.coverage.report]
61
71
  exclude_lines = ["no cov", "raise NotImplementedError", "if TYPE_CHECKING:"]
@@ -63,7 +73,7 @@ skip_covered = true
63
73
 
64
74
  [tool.ruff]
65
75
  line-length = 88
66
- target-version = "py312"
76
+ target-version = "py313"
67
77
  include = ["src", "tests"]
68
78
 
69
79
  [tool.ruff.lint]
@@ -86,7 +96,7 @@ ignore = [
86
96
  ]
87
97
 
88
98
  [tool.ruff.lint.per-file-ignores]
89
- "tests/*" = ["ANN", "FBT", "S101", "S607"]
99
+ "tests/*" = ["ANN", "FBT", "S101", "S607", "PLC2401"]
90
100
  "schema.py" = ["E501"]
91
101
  "notebooks/*" = ["F704", "PLE1142"]
92
102
 
@@ -0,0 +1,17 @@
1
+ from .core.info import Info
2
+ from .core.list import List
3
+ from .core.prices import Prices
4
+ from .core.reports import Reports
5
+ from .core.statements import Statements
6
+ from .edinet.client import EdinetClient
7
+ from .jquants.client import JQuantsClient
8
+
9
+ __all__ = [
10
+ "EdinetClient",
11
+ "Info",
12
+ "JQuantsClient",
13
+ "List",
14
+ "Prices",
15
+ "Reports",
16
+ "Statements",
17
+ ]
@@ -5,13 +5,14 @@ from __future__ import annotations
5
5
  import typer
6
6
  from async_typer import AsyncTyper # pyright: ignore[reportMissingTypeStubs]
7
7
 
8
- from . import auth
8
+ from . import auth, get
9
9
 
10
10
  app = AsyncTyper(
11
11
  add_completion=False,
12
12
  help="J-Quants/EDINETデータツール",
13
13
  )
14
14
  app.add_typer(auth.app, name="auth")
15
+ app.add_typer(get.app, name="get")
15
16
 
16
17
 
17
18
  @app.command()
@@ -0,0 +1,154 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Annotated, Any
4
+
5
+ import typer
6
+ from async_typer import AsyncTyper # pyright: ignore[reportMissingTypeStubs]
7
+ from typer import Argument
8
+
9
+ if TYPE_CHECKING:
10
+ from kabukit.core.base import Base
11
+
12
+ app = AsyncTyper(
13
+ add_completion=False,
14
+ help="J-Quantsからデータを取得します。",
15
+ )
16
+
17
+ Code = Annotated[
18
+ str | None,
19
+ Argument(help="銘柄コード。指定しない場合は全銘柄の情報を取得します。"),
20
+ ]
21
+
22
+
23
+ @app.async_command() # pyright: ignore[reportUnknownMemberType]
24
+ async def info(code: Code = None) -> None:
25
+ """上場銘柄一覧を取得します。"""
26
+ from kabukit.core.info import Info
27
+ from kabukit.jquants.client import JQuantsClient
28
+
29
+ async with JQuantsClient() as client:
30
+ df = await client.get_info(code)
31
+
32
+ typer.echo(df)
33
+
34
+ if code is None:
35
+ path = Info(df).write()
36
+ typer.echo(f"全銘柄の情報を '{path}' に保存しました。")
37
+
38
+
39
+ async def _fetch(
40
+ code: str | None,
41
+ target: str,
42
+ cls: type[Base],
43
+ fetch_func_name: str,
44
+ message: str,
45
+ **kwargs: Any,
46
+ ) -> None:
47
+ """財務情報・株価情報を取得するための共通処理"""
48
+ from kabukit.jquants.client import JQuantsClient
49
+
50
+ if code is not None:
51
+ async with JQuantsClient() as client:
52
+ df = await getattr(client, fetch_func_name)(code)
53
+ typer.echo(df)
54
+ return
55
+
56
+ import tqdm.asyncio
57
+
58
+ from kabukit.jquants.concurrent import fetch_all
59
+
60
+ df = await fetch_all(target, progress=tqdm.asyncio.tqdm, **kwargs)
61
+ typer.echo(df)
62
+ path = cls(df).write()
63
+ typer.echo(f"全銘柄の{message}を '{path}' に保存しました。")
64
+
65
+
66
+ @app.async_command() # pyright: ignore[reportUnknownMemberType]
67
+ async def statements(code: Code = None) -> None:
68
+ """財務情報を取得します。"""
69
+ from kabukit.core.statements import Statements
70
+
71
+ await _fetch(
72
+ code=code,
73
+ target="statements",
74
+ cls=Statements,
75
+ fetch_func_name="get_statements",
76
+ message="財務情報",
77
+ )
78
+
79
+
80
+ @app.async_command() # pyright: ignore[reportUnknownMemberType]
81
+ async def prices(code: Code = None) -> None:
82
+ """株価を取得します。"""
83
+ from kabukit.core.prices import Prices
84
+
85
+ await _fetch(
86
+ code=code,
87
+ target="prices",
88
+ cls=Prices,
89
+ fetch_func_name="get_prices",
90
+ message="株価情報",
91
+ max_concurrency=8,
92
+ )
93
+
94
+
95
+ @app.async_command(name="list") # pyright: ignore[reportUnknownMemberType]
96
+ async def list_() -> None:
97
+ """報告書一覧を取得します。"""
98
+ import tqdm.asyncio
99
+
100
+ from kabukit.core.list import List
101
+ from kabukit.edinet.concurrent import fetch_list
102
+
103
+ df = await fetch_list(years=10, progress=tqdm.asyncio.tqdm)
104
+ typer.echo(df)
105
+ path = List(df).write()
106
+ typer.echo(f"報告書一覧を '{path}' に保存しました。")
107
+
108
+
109
+ @app.async_command() # pyright: ignore[reportUnknownMemberType]
110
+ async def reports() -> None:
111
+ """報告書を取得します。"""
112
+ import polars as pl
113
+ import tqdm.asyncio
114
+
115
+ from kabukit.core.list import List
116
+ from kabukit.core.reports import Reports
117
+ from kabukit.edinet.concurrent import fetch_csv
118
+
119
+ try:
120
+ df = List.read().data
121
+ except FileNotFoundError:
122
+ await list_()
123
+ df = List.read().data
124
+
125
+ lst = df.filter(pl.col("csvFlag"), pl.col("secCode").is_not_null())
126
+ doc_ids = lst["docID"].unique()
127
+
128
+ df = await fetch_csv(doc_ids, limit=1000, progress=tqdm.asyncio.tqdm)
129
+ typer.echo(df)
130
+ path = Reports(df).write()
131
+ typer.echo(f"報告書を '{path}' に保存しました。")
132
+
133
+
134
+ @app.async_command(name="all") # pyright: ignore[reportUnknownMemberType]
135
+ async def all_(code: Code = None) -> None:
136
+ """上場銘柄一覧、財務情報、株価、報告書を連続して取得します。"""
137
+ typer.echo("上場銘柄一覧を取得します。")
138
+ await info(code)
139
+
140
+ typer.echo("---")
141
+ typer.echo("財務情報を取得します。")
142
+ await statements(code)
143
+
144
+ typer.echo("---")
145
+ typer.echo("株価を取得します。")
146
+ await prices(code)
147
+
148
+ if code is None:
149
+ typer.echo("---")
150
+ typer.echo("報告書一覧を取得します。")
151
+ await list_()
152
+ typer.echo("---")
153
+ typer.echo("報告書を取得します。")
154
+ await reports()
@@ -36,10 +36,15 @@ class Base:
36
36
  def read(cls, path: str | None = None) -> Self:
37
37
  data_dir = cls.data_dir()
38
38
 
39
- if path is None:
40
- filename = sorted(data_dir.glob("*.parquet"))[-1]
41
- else:
39
+ if path:
42
40
  filename = data_dir / path
41
+ else:
42
+ filenames = sorted(data_dir.glob("*.parquet"))
43
+ if not filenames:
44
+ msg = f"No data found in {data_dir}"
45
+ raise FileNotFoundError(msg)
46
+
47
+ filename = filenames[-1]
43
48
 
44
49
  data = pl.read_parquet(filename)
45
50
  return cls(data)
@@ -0,0 +1,12 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from .base import Base
6
+
7
+ if TYPE_CHECKING:
8
+ from polars import DataFrame
9
+
10
+
11
+ class List(Base):
12
+ pass
@@ -0,0 +1,12 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from .base import Base
6
+
7
+ if TYPE_CHECKING:
8
+ from polars import DataFrame
9
+
10
+
11
+ class Reports(Base):
12
+ pass
@@ -0,0 +1,3 @@
1
+ from .concurrent import fetch, fetch_csv, fetch_list
2
+
3
+ __all__ = ["fetch", "fetch_csv", "fetch_list"]
@@ -6,14 +6,13 @@ import zipfile
6
6
  from enum import StrEnum
7
7
  from typing import TYPE_CHECKING
8
8
 
9
- import polars as pl
10
9
  from polars import DataFrame
11
10
 
12
11
  from kabukit.core.client import Client
13
12
  from kabukit.utils.config import load_dotenv
14
13
  from kabukit.utils.params import get_params
15
14
 
16
- from .doc import clean_csv, clean_list
15
+ from .doc import clean_csv, clean_list, read_csv
17
16
 
18
17
  if TYPE_CHECKING:
19
18
  import datetime
@@ -103,11 +102,8 @@ class EdinetClient(Client):
103
102
  for info in zf.infolist():
104
103
  if info.filename.endswith(".csv"):
105
104
  with zf.open(info) as f:
106
- return pl.read_csv(
107
- f.read(),
108
- separator="\t",
109
- encoding="utf-16-le",
110
- ).pipe(clean_csv, doc_id)
105
+ df = read_csv(f.read())
106
+ return clean_csv(df, doc_id)
111
107
 
112
108
  msg = "CSV is not available."
113
109
  raise ValueError(msg)
@@ -1,13 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
- import datetime
4
3
  from typing import TYPE_CHECKING
5
4
 
6
5
  from kabukit.utils import concurrent
6
+ from kabukit.utils.date import get_dates
7
7
 
8
8
  from .client import EdinetClient
9
9
 
10
10
  if TYPE_CHECKING:
11
+ import datetime
11
12
  from collections.abc import Iterable
12
13
 
13
14
  from polars import DataFrame
@@ -15,30 +16,6 @@ if TYPE_CHECKING:
15
16
  from kabukit.utils.concurrent import Callback, Progress
16
17
 
17
18
 
18
- def get_dates(days: int | None = None, years: int | None = None) -> list[datetime.date]:
19
- """過去days日またはyears年の日付リストを返す。
20
-
21
- Args:
22
- days (int | None): 過去days日の日付リストを取得する。
23
- years (int | None): 過去years年の日付リストを取得する。
24
- daysが指定されている場合は無視される。
25
- """
26
- end_date = datetime.date.today() # noqa: DTZ011
27
-
28
- if days is not None:
29
- start_date = end_date - datetime.timedelta(days=days)
30
- elif years is not None:
31
- start_date = end_date.replace(year=end_date.year - years)
32
- else:
33
- msg = "daysまたはyearsのいずれかを指定してください。"
34
- raise ValueError(msg)
35
-
36
- return [
37
- start_date + datetime.timedelta(days=i)
38
- for i in range(1, (end_date - start_date).days + 1)
39
- ]
40
-
41
-
42
19
  async def fetch(
43
20
  resource: str,
44
21
  args: Iterable[str | datetime.date],
@@ -106,13 +83,14 @@ async def fetch_list(
106
83
  if limit is not None:
107
84
  dates = dates[:limit]
108
85
 
109
- return await fetch(
86
+ df = await fetch(
110
87
  "list",
111
88
  dates,
112
89
  max_concurrency=max_concurrency,
113
90
  progress=progress,
114
91
  callback=callback,
115
92
  )
93
+ return df.sort("Date")
116
94
 
117
95
 
118
96
  async def fetch_csv(
@@ -144,10 +122,11 @@ async def fetch_csv(
144
122
  if limit is not None:
145
123
  doc_ids = doc_ids[:limit]
146
124
 
147
- return await fetch(
125
+ df = await fetch(
148
126
  "csv",
149
127
  doc_ids,
150
128
  max_concurrency=max_concurrency,
151
129
  progress=progress,
152
130
  callback=callback,
153
131
  )
132
+ return df.sort("docID")
@@ -0,0 +1,46 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ from typing import TYPE_CHECKING
5
+
6
+ import polars as pl
7
+
8
+ if TYPE_CHECKING:
9
+ from polars import DataFrame
10
+
11
+
12
+ def clean_list(df: DataFrame, date: str | datetime.date) -> DataFrame:
13
+ if isinstance(date, str):
14
+ date = datetime.datetime.strptime(date, "%Y-%m-%d").date() # noqa: DTZ007
15
+
16
+ null_columns = [c for c in df.columns if df[c].dtype == pl.Null]
17
+
18
+ return (
19
+ df.with_columns(
20
+ pl.col(null_columns).cast(pl.String),
21
+ )
22
+ .with_columns(
23
+ pl.lit(date).alias("Date"),
24
+ pl.col("^.+DateTime$").str.to_datetime("%Y-%m-%d %H:%M", strict=False),
25
+ pl.col("^period.+$").str.to_date("%Y-%m-%d", strict=False),
26
+ pl.col("^.+Flag$").cast(pl.Int8).cast(pl.Boolean),
27
+ pl.col("^.+Code$").cast(pl.String),
28
+ )
29
+ .select("Date", pl.exclude("Date"))
30
+ )
31
+
32
+
33
+ def read_csv(data: bytes) -> DataFrame:
34
+ return pl.read_csv(
35
+ data,
36
+ separator="\t",
37
+ encoding="utf-16-le",
38
+ infer_schema_length=None,
39
+ )
40
+
41
+
42
+ def clean_csv(df: DataFrame, doc_id: str) -> DataFrame:
43
+ return df.select(
44
+ pl.lit(doc_id).alias("docID"),
45
+ pl.all(),
46
+ )
@@ -0,0 +1,4 @@
1
+ from .concurrent import fetch, fetch_all
2
+ from .schema import rename
3
+
4
+ __all__ = ["fetch", "fetch_all", "rename"]
@@ -132,12 +132,15 @@ class JQuantsClient(Client):
132
132
  self,
133
133
  code: str | None = None,
134
134
  date: str | datetime.date | None = None,
135
+ *,
136
+ clean: bool = True,
135
137
  ) -> DataFrame:
136
138
  """銘柄情報を取得する。
137
139
 
138
140
  Args:
139
141
  code (str, optional): 情報を取得する銘柄のコード。
140
142
  date (str | datetime.date, optional): 情報を取得する日付。
143
+ clean (bool, optional): 取得したデータをクリーンアップするかどうか。
141
144
 
142
145
  Returns:
143
146
  銘柄情報を含むDataFrame。
@@ -148,7 +151,8 @@ class JQuantsClient(Client):
148
151
  params = get_params(code=code, date=date)
149
152
  url = "/listed/info"
150
153
  data = await self.get(url, params)
151
- return DataFrame(data["info"]).pipe(info.clean)
154
+ df = DataFrame(data["info"])
155
+ return info.clean(df) if clean else df
152
156
 
153
157
  async def iter_pages(
154
158
  self,
@@ -185,6 +189,8 @@ class JQuantsClient(Client):
185
189
  date: str | datetime.date | None = None,
186
190
  from_: str | datetime.date | None = None,
187
191
  to: str | datetime.date | None = None,
192
+ *,
193
+ clean: bool = True,
188
194
  ) -> DataFrame:
189
195
  """日々の株価四本値を取得する。
190
196
 
@@ -198,6 +204,7 @@ class JQuantsClient(Client):
198
204
  `date`とは併用不可。
199
205
  to (str | datetime.date, optional): 取得期間の終了日。
200
206
  `date`とは併用不可。
207
+ clean (bool, optional): 取得したデータをクリーンアップするかどうか。
201
208
 
202
209
  Returns:
203
210
  日々の株価四本値を含むDataFrame。
@@ -207,7 +214,7 @@ class JQuantsClient(Client):
207
214
  HTTPStatusError: APIリクエストが失敗した場合。
208
215
  """
209
216
  if not date and not code:
210
- return await self.get_latest_available_prices()
217
+ return await self.get_latest_available_prices(clean=clean)
211
218
 
212
219
  if date and (from_ or to):
213
220
  msg = "dateとfrom/toの両方を指定することはできません。"
@@ -224,15 +231,20 @@ class JQuantsClient(Client):
224
231
  if df.is_empty():
225
232
  return df
226
233
 
227
- return prices.clean(df)
234
+ return prices.clean(df) if clean else df
228
235
 
229
- async def get_latest_available_prices(self, num_days: int = 30) -> DataFrame:
236
+ async def get_latest_available_prices(
237
+ self,
238
+ num_days: int = 30,
239
+ *,
240
+ clean: bool = True,
241
+ ) -> DataFrame:
230
242
  """直近利用可能な日付の株価を取得する。"""
231
243
  today = datetime.date.today() # noqa: DTZ011
232
244
 
233
245
  for days in range(num_days):
234
246
  date = today - datetime.timedelta(days)
235
- df = await self.get_prices(date=date)
247
+ df = await self.get_prices(date=date, clean=clean)
236
248
 
237
249
  if not df.is_empty():
238
250
  return df
@@ -243,12 +255,15 @@ class JQuantsClient(Client):
243
255
  self,
244
256
  code: str | None = None,
245
257
  date: str | datetime.date | None = None,
258
+ *,
259
+ clean: bool = True,
246
260
  ) -> DataFrame:
247
261
  """四半期毎の決算短信サマリーおよび業績・配当の修正に関する開示情報を取得する。
248
262
 
249
263
  Args:
250
264
  code (str, optional): 財務情報を取得する銘柄のコード。
251
265
  date (str | datetime.date, optional): 財務情報を取得する日付。
266
+ clean (bool, optional): 取得したデータをクリーンアップするかどうか。
252
267
 
253
268
  Returns:
254
269
  財務情報を含むDataFrame。
@@ -271,7 +286,7 @@ class JQuantsClient(Client):
271
286
  if df.is_empty():
272
287
  return df
273
288
 
274
- return statements.clean(df)
289
+ return statements.clean(df) if clean else df
275
290
 
276
291
  async def get_announcement(self) -> DataFrame:
277
292
  """翌日発表予定の決算情報を取得する。
@@ -2,25 +2,14 @@ from __future__ import annotations
2
2
 
3
3
  import datetime
4
4
  from functools import cache
5
- from typing import TYPE_CHECKING, Any, Protocol
5
+ from typing import TYPE_CHECKING
6
6
 
7
7
  import holidays
8
8
  import polars as pl
9
9
 
10
10
  if TYPE_CHECKING:
11
- from collections.abc import AsyncIterable, AsyncIterator
12
-
13
11
  from polars import DataFrame
14
12
 
15
- class Progress(Protocol):
16
- def __call__[T](
17
- self,
18
- async_iterable: AsyncIterable[T],
19
- total: int | None = None,
20
- *args: Any,
21
- **kwargs: Any,
22
- ) -> AsyncIterator[T]: ...
23
-
24
13
 
25
14
  def clean(df: DataFrame) -> DataFrame:
26
15
  return (
@@ -23,13 +23,14 @@ if TYPE_CHECKING:
23
23
  from kabukit.core.client import Client
24
24
 
25
25
  class _Progress(Protocol):
26
- def __call__[T](
26
+ def __call__(
27
27
  self,
28
- aiterable: AsyncIterable[T],
28
+ aiterable: AsyncIterable[Any],
29
+ /,
29
30
  total: int | None = None,
30
31
  *args: Any,
31
32
  **kwargs: Any,
32
- ) -> AsyncIterator[T]: ...
33
+ ) -> AsyncIterator[Any]: ...
33
34
 
34
35
 
35
36
  MAX_CONCURRENCY = 12
@@ -1,13 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
- from functools import cache
4
3
  from pathlib import Path
5
4
 
6
5
  import dotenv
7
6
  from platformdirs import user_config_dir
8
7
 
9
8
 
10
- @cache
11
9
  def get_dotenv_path() -> Path:
12
10
  """Return the path to the .env file in the user config directory."""
13
11
  config_dir = Path(user_config_dir("kabukit"))
@@ -15,12 +13,11 @@ def get_dotenv_path() -> Path:
15
13
  return config_dir / ".env"
16
14
 
17
15
 
18
- @cache
19
- def load_dotenv() -> bool:
16
+ def set_key(key: str, value: str) -> tuple[bool | None, str, str]:
20
17
  dotenv_path = get_dotenv_path()
21
- return dotenv.load_dotenv(dotenv_path)
18
+ return dotenv.set_key(dotenv_path, key, value)
22
19
 
23
20
 
24
- def set_key(key: str, value: str) -> tuple[bool | None, str, str]:
21
+ def load_dotenv() -> bool:
25
22
  dotenv_path = get_dotenv_path()
26
- return dotenv.set_key(dotenv_path, key, value)
23
+ return dotenv.load_dotenv(dotenv_path)
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+
5
+
6
+ def get_dates(days: int | None = None, years: int | None = None) -> list[datetime.date]:
7
+ """過去days日またはyears年の日付リストを返す。
8
+
9
+ Args:
10
+ days (int | None): 過去days日の日付リストを取得する。
11
+ years (int | None): 過去years年の日付リストを取得する。
12
+ daysが指定されている場合は無視される。
13
+ """
14
+ end_date = datetime.date.today() # noqa: DTZ011
15
+
16
+ if days is not None:
17
+ start_date = end_date - datetime.timedelta(days=days)
18
+ elif years is not None:
19
+ start_date = end_date.replace(year=end_date.year - years)
20
+ else:
21
+ msg = "daysまたはyearsのいずれかを指定してください。"
22
+ raise ValueError(msg)
23
+
24
+ return [
25
+ start_date + datetime.timedelta(days=i)
26
+ for i in range(1, (end_date - start_date).days + 1)
27
+ ]
@@ -18,16 +18,16 @@ def iter_items(kwargs: dict[str, Any]) -> Iterator[tuple[str, Any]]:
18
18
  yield key, value
19
19
 
20
20
 
21
- def get_params(**kwargs: Any) -> dict[str, str]:
22
- params: dict[str, str] = {}
21
+ def get_params(**kwargs: Any) -> dict[str, Any]:
22
+ params: dict[str, Any] = {}
23
23
 
24
24
  for key, value in iter_items(kwargs):
25
25
  if isinstance(value, datetime.date):
26
26
  params[key] = date_to_str(value)
27
- elif not isinstance(value, str):
28
- params[key] = str(value)
29
- else:
27
+ elif isinstance(value, str | int | float | bool):
30
28
  params[key] = value
29
+ else:
30
+ params[key] = str(value)
31
31
 
32
32
  return params
33
33
 
kabukit-0.2.1/README.md DELETED
@@ -1,18 +0,0 @@
1
- # kabukit
2
-
3
- A Python toolkit for Japanese financial market data, supporting J-Quants and EDINET APIs.
4
-
5
- [![PyPI Version][pypi-v-image]][pypi-v-link]
6
- [![Python Version][python-v-image]][python-v-link]
7
-
8
- ## Installation
9
-
10
- ```bash
11
- pip install kabukit
12
- ```
13
-
14
- <!-- Badges -->
15
- [pypi-v-image]: https://img.shields.io/pypi/v/kabukit.svg
16
- [pypi-v-link]: https://pypi.org/project/kabukit/
17
- [python-v-image]: https://img.shields.io/pypi/pyversions/kabukit.svg
18
- [python-v-link]: https://pypi.org/project/kabukit
@@ -1,7 +0,0 @@
1
- from .core.info import Info
2
- from .core.prices import Prices
3
- from .core.statements import Statements
4
- from .edinet.client import EdinetClient
5
- from .jquants.client import JQuantsClient
6
-
7
- __all__ = ["EdinetClient", "Info", "JQuantsClient", "Prices", "Statements"]
@@ -1,3 +0,0 @@
1
- from .concurrent import fetch, fetch_list
2
-
3
- __all__ = ["fetch", "fetch_list"]
@@ -1,32 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import datetime
4
- from typing import TYPE_CHECKING
5
-
6
- import polars as pl
7
-
8
- if TYPE_CHECKING:
9
- from polars import DataFrame
10
-
11
-
12
- def clean_list(df: DataFrame, date: str | datetime.date) -> DataFrame:
13
- if isinstance(date, str):
14
- date = datetime.datetime.strptime(date, "%Y-%m-%d").date() # noqa: DTZ007
15
-
16
- return df.with_columns(
17
- pl.lit(date).alias("Date"),
18
- pl.col("submitDateTime").str.to_datetime("%Y-%m-%d %H:%M", strict=False),
19
- pl.col("^period.+$").str.to_date("%Y-%m-%d", strict=False),
20
- pl.col("^.+Flag$").cast(pl.Int8).cast(pl.Boolean),
21
- pl.col("^.+Code$").cast(pl.String),
22
- pl.col("opeDateTime")
23
- .cast(pl.String)
24
- .str.to_datetime("%Y-%m-%d %H:%M", strict=False),
25
- ).select("Date", pl.exclude("Date"))
26
-
27
-
28
- def clean_csv(df: DataFrame, doc_id: str) -> DataFrame:
29
- return df.select(
30
- pl.lit(doc_id).alias("docID"),
31
- pl.all(),
32
- )
@@ -1,3 +0,0 @@
1
- from .concurrent import fetch, fetch_all
2
-
3
- __all__ = ["fetch", "fetch_all"]
File without changes
File without changes
File without changes