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.
- {kabukit-0.2.1 → kabukit-0.3.1}/PKG-INFO +12 -7
- kabukit-0.3.1/README.md +28 -0
- {kabukit-0.2.1 → kabukit-0.3.1}/pyproject.toml +22 -12
- kabukit-0.3.1/src/kabukit/__init__.py +17 -0
- {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/cli/app.py +2 -1
- kabukit-0.3.1/src/kabukit/cli/get.py +154 -0
- {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/core/base.py +8 -3
- kabukit-0.3.1/src/kabukit/core/list.py +12 -0
- kabukit-0.3.1/src/kabukit/core/reports.py +12 -0
- kabukit-0.3.1/src/kabukit/edinet/__init__.py +3 -0
- {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/edinet/client.py +3 -7
- {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/edinet/concurrent.py +6 -27
- kabukit-0.3.1/src/kabukit/edinet/doc.py +46 -0
- kabukit-0.3.1/src/kabukit/jquants/__init__.py +4 -0
- {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/jquants/client.py +21 -6
- {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/jquants/statements.py +1 -12
- {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/utils/concurrent.py +4 -3
- {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/utils/config.py +4 -7
- kabukit-0.3.1/src/kabukit/utils/date.py +27 -0
- {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/utils/params.py +5 -5
- kabukit-0.2.1/README.md +0 -18
- kabukit-0.2.1/src/kabukit/__init__.py +0 -7
- kabukit-0.2.1/src/kabukit/edinet/__init__.py +0 -3
- kabukit-0.2.1/src/kabukit/edinet/doc.py +0 -32
- kabukit-0.2.1/src/kabukit/jquants/__init__.py +0 -3
- {kabukit-0.2.1 → kabukit-0.3.1}/LICENSE +0 -0
- {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/analysis/__init__.py +0 -0
- {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/analysis/indicators.py +0 -0
- {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/analysis/preprocess.py +0 -0
- {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/analysis/screener.py +0 -0
- {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/analysis/visualization.py +0 -0
- {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/cli/__init__.py +0 -0
- {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/cli/auth.py +0 -0
- {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/core/__init__.py +0 -0
- {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/core/client.py +0 -0
- {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/core/info.py +0 -0
- {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/core/prices.py +0 -0
- {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/core/statements.py +0 -0
- {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/jquants/concurrent.py +0 -0
- {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/jquants/info.py +0 -0
- {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/jquants/prices.py +0 -0
- {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/jquants/schema.py +0 -0
- {kabukit-0.2.1 → kabukit-0.3.1}/src/kabukit/py.typed +0 -0
- {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.
|
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-
|
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/
|
kabukit-0.3.1/README.md
ADDED
@@ -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.
|
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.
|
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
|
-
"
|
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 = [
|
58
|
-
|
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 = "
|
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
|
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)
|
@@ -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
|
-
|
107
|
-
|
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
|
-
|
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
|
-
|
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
|
+
)
|
@@ -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
|
-
|
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(
|
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
|
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__
|
26
|
+
def __call__(
|
27
27
|
self,
|
28
|
-
aiterable: AsyncIterable[
|
28
|
+
aiterable: AsyncIterable[Any],
|
29
|
+
/,
|
29
30
|
total: int | None = None,
|
30
31
|
*args: Any,
|
31
32
|
**kwargs: Any,
|
32
|
-
) -> AsyncIterator[
|
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
|
-
|
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.
|
18
|
+
return dotenv.set_key(dotenv_path, key, value)
|
22
19
|
|
23
20
|
|
24
|
-
def
|
21
|
+
def load_dotenv() -> bool:
|
25
22
|
dotenv_path = get_dotenv_path()
|
26
|
-
return dotenv.
|
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,
|
22
|
-
params: dict[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
|
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,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
|
-
)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|