kabukit 0.3.0__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.3.0 → kabukit-0.3.1}/PKG-INFO +1 -1
- {kabukit-0.3.0 → kabukit-0.3.1}/pyproject.toml +8 -4
- kabukit-0.3.1/src/kabukit/__init__.py +17 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/cli/get.py +56 -6
- 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.3.0 → kabukit-0.3.1}/src/kabukit/edinet/client.py +3 -7
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/edinet/concurrent.py +4 -2
- kabukit-0.3.1/src/kabukit/edinet/doc.py +46 -0
- kabukit-0.3.1/src/kabukit/jquants/__init__.py +4 -0
- kabukit-0.3.0/src/kabukit/__init__.py +0 -7
- kabukit-0.3.0/src/kabukit/edinet/__init__.py +0 -3
- kabukit-0.3.0/src/kabukit/edinet/doc.py +0 -32
- kabukit-0.3.0/src/kabukit/jquants/__init__.py +0 -3
- {kabukit-0.3.0 → kabukit-0.3.1}/LICENSE +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/README.md +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/analysis/__init__.py +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/analysis/indicators.py +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/analysis/preprocess.py +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/analysis/screener.py +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/analysis/visualization.py +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/cli/__init__.py +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/cli/app.py +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/cli/auth.py +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/core/__init__.py +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/core/base.py +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/core/client.py +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/core/info.py +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/core/prices.py +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/core/statements.py +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/jquants/client.py +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/jquants/concurrent.py +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/jquants/info.py +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/jquants/prices.py +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/jquants/schema.py +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/jquants/statements.py +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/py.typed +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/utils/__init__.py +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/utils/concurrent.py +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/utils/config.py +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/utils/date.py +0 -0
- {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/utils/params.py +0 -0
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "kabukit"
|
7
|
-
version = "0.3.
|
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" }
|
@@ -58,10 +58,14 @@ addopts = [
|
|
58
58
|
"--cov=kabukit",
|
59
59
|
"--cov-report=lcov:lcov.info",
|
60
60
|
"--doctest-modules",
|
61
|
-
"-m
|
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",
|
62
68
|
]
|
63
|
-
testpaths = ["tests/unit", "tests/integration"]
|
64
|
-
markers = ["integration: marks tests as integration tests"]
|
65
69
|
|
66
70
|
[tool.coverage.report]
|
67
71
|
exclude_lines = ["no cov", "raise NotImplementedError", "if TYPE_CHECKING:"]
|
@@ -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
|
+
]
|
@@ -1,11 +1,14 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from typing import Annotated, Any
|
3
|
+
from typing import TYPE_CHECKING, Annotated, Any
|
4
4
|
|
5
5
|
import typer
|
6
6
|
from async_typer import AsyncTyper # pyright: ignore[reportMissingTypeStubs]
|
7
7
|
from typer import Argument
|
8
8
|
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from kabukit.core.base import Base
|
11
|
+
|
9
12
|
app = AsyncTyper(
|
10
13
|
add_completion=False,
|
11
14
|
help="J-Quantsからデータを取得します。",
|
@@ -36,7 +39,7 @@ async def info(code: Code = None) -> None:
|
|
36
39
|
async def _fetch(
|
37
40
|
code: str | None,
|
38
41
|
target: str,
|
39
|
-
|
42
|
+
cls: type[Base],
|
40
43
|
fetch_func_name: str,
|
41
44
|
message: str,
|
42
45
|
**kwargs: Any,
|
@@ -56,7 +59,7 @@ async def _fetch(
|
|
56
59
|
|
57
60
|
df = await fetch_all(target, progress=tqdm.asyncio.tqdm, **kwargs)
|
58
61
|
typer.echo(df)
|
59
|
-
path =
|
62
|
+
path = cls(df).write()
|
60
63
|
typer.echo(f"全銘柄の{message}を '{path}' に保存しました。")
|
61
64
|
|
62
65
|
|
@@ -68,7 +71,7 @@ async def statements(code: Code = None) -> None:
|
|
68
71
|
await _fetch(
|
69
72
|
code=code,
|
70
73
|
target="statements",
|
71
|
-
|
74
|
+
cls=Statements,
|
72
75
|
fetch_func_name="get_statements",
|
73
76
|
message="財務情報",
|
74
77
|
)
|
@@ -82,16 +85,55 @@ async def prices(code: Code = None) -> None:
|
|
82
85
|
await _fetch(
|
83
86
|
code=code,
|
84
87
|
target="prices",
|
85
|
-
|
88
|
+
cls=Prices,
|
86
89
|
fetch_func_name="get_prices",
|
87
90
|
message="株価情報",
|
88
91
|
max_concurrency=8,
|
89
92
|
)
|
90
93
|
|
91
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
|
+
|
92
134
|
@app.async_command(name="all") # pyright: ignore[reportUnknownMemberType]
|
93
135
|
async def all_(code: Code = None) -> None:
|
94
|
-
"""
|
136
|
+
"""上場銘柄一覧、財務情報、株価、報告書を連続して取得します。"""
|
95
137
|
typer.echo("上場銘柄一覧を取得します。")
|
96
138
|
await info(code)
|
97
139
|
|
@@ -102,3 +144,11 @@ async def all_(code: Code = None) -> None:
|
|
102
144
|
typer.echo("---")
|
103
145
|
typer.echo("株価を取得します。")
|
104
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()
|
@@ -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)
|
@@ -83,13 +83,14 @@ async def fetch_list(
|
|
83
83
|
if limit is not None:
|
84
84
|
dates = dates[:limit]
|
85
85
|
|
86
|
-
|
86
|
+
df = await fetch(
|
87
87
|
"list",
|
88
88
|
dates,
|
89
89
|
max_concurrency=max_concurrency,
|
90
90
|
progress=progress,
|
91
91
|
callback=callback,
|
92
92
|
)
|
93
|
+
return df.sort("Date")
|
93
94
|
|
94
95
|
|
95
96
|
async def fetch_csv(
|
@@ -121,10 +122,11 @@ async def fetch_csv(
|
|
121
122
|
if limit is not None:
|
122
123
|
doc_ids = doc_ids[:limit]
|
123
124
|
|
124
|
-
|
125
|
+
df = await fetch(
|
125
126
|
"csv",
|
126
127
|
doc_ids,
|
127
128
|
max_concurrency=max_concurrency,
|
128
129
|
progress=progress,
|
129
130
|
callback=callback,
|
130
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
|
+
)
|
@@ -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
|
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
|