kabukit 0.6.0__tar.gz → 0.7.0__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.6.0 → kabukit-0.7.0}/PKG-INFO +4 -2
- {kabukit-0.6.0 → kabukit-0.7.0}/pyproject.toml +5 -4
- kabukit-0.7.0/src/kabukit/analysis/visualization/__init__.py +7 -0
- kabukit-0.7.0/src/kabukit/analysis/visualization/market.py +29 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/cli/app.py +2 -1
- kabukit-0.7.0/src/kabukit/cli/cache.py +61 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/cli/get.py +18 -3
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/core/base.py +4 -3
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/jquants/concurrent.py +2 -2
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/jquants/info.py +7 -3
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/utils/concurrent.py +28 -23
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/utils/config.py +5 -1
- {kabukit-0.6.0 → kabukit-0.7.0}/LICENSE +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/README.md +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/__init__.py +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/analysis/__init__.py +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/analysis/indicators.py +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/analysis/preprocess.py +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/analysis/screener.py +0 -0
- /kabukit-0.6.0/src/kabukit/analysis/visualization.py → /kabukit-0.7.0/src/kabukit/analysis/visualization/prices.py +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/cli/__init__.py +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/cli/auth.py +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/core/__init__.py +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/core/client.py +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/core/info.py +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/core/list.py +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/core/prices.py +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/core/reports.py +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/core/statements.py +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/edinet/__init__.py +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/edinet/client.py +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/edinet/concurrent.py +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/edinet/doc.py +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/jquants/__init__.py +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/jquants/client.py +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/jquants/prices.py +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/jquants/schema.py +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/jquants/statements.py +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/jquants/topix.py +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/py.typed +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/utils/__init__.py +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/utils/date.py +0 -0
- {kabukit-0.6.0 → kabukit-0.7.0}/src/kabukit/utils/params.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: kabukit
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.7.0
|
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>
|
@@ -29,11 +29,13 @@ Classifier: Development Status :: 4 - Beta
|
|
29
29
|
Classifier: Programming Language :: Python
|
30
30
|
Classifier: Programming Language :: Python :: 3.13
|
31
31
|
Requires-Dist: async-typer>=0.1.10
|
32
|
-
Requires-Dist: holidays>=0.
|
32
|
+
Requires-Dist: holidays>=0.82
|
33
33
|
Requires-Dist: httpx>=0.28.1
|
34
34
|
Requires-Dist: platformdirs>=4.4.0
|
35
35
|
Requires-Dist: polars>=1.34.0
|
36
36
|
Requires-Dist: python-dotenv>=1.1.1
|
37
|
+
Requires-Dist: rich>=14.1.0
|
38
|
+
Requires-Dist: tqdm>=4.67.1
|
37
39
|
Requires-Dist: typer>=0.19.2
|
38
40
|
Requires-Python: >=3.13
|
39
41
|
Project-URL: Documentation, 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.7.0"
|
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" }
|
@@ -17,11 +17,13 @@ classifiers = [
|
|
17
17
|
requires-python = ">=3.13"
|
18
18
|
dependencies = [
|
19
19
|
"async-typer>=0.1.10",
|
20
|
-
"holidays>=0.
|
20
|
+
"holidays>=0.82",
|
21
21
|
"httpx>=0.28.1",
|
22
22
|
"platformdirs>=4.4.0",
|
23
23
|
"polars>=1.34.0",
|
24
24
|
"python-dotenv>=1.1.1",
|
25
|
+
"rich>=14.1.0",
|
26
|
+
"tqdm>=4.67.1",
|
25
27
|
"typer>=0.19.2",
|
26
28
|
]
|
27
29
|
|
@@ -45,8 +47,7 @@ dev = [
|
|
45
47
|
"pytest-mock>=3.15.1",
|
46
48
|
"pytest-randomly>=4.0.1",
|
47
49
|
"pytest-xdist>=3.8.0",
|
48
|
-
"
|
49
|
-
"tqdm>=4.67.1",
|
50
|
+
"scipy",
|
50
51
|
"vegafusion-python-embed>=1.6.9",
|
51
52
|
"vegafusion>=2.0.3",
|
52
53
|
"vl-convert-python>=1.8.0",
|
@@ -0,0 +1,29 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import TYPE_CHECKING
|
4
|
+
|
5
|
+
import altair as alt
|
6
|
+
|
7
|
+
if TYPE_CHECKING:
|
8
|
+
from polars import DataFrame
|
9
|
+
|
10
|
+
# pyright: reportUnknownMemberType=false
|
11
|
+
|
12
|
+
|
13
|
+
def plot_topix_timeseries(df: DataFrame) -> alt.Chart:
|
14
|
+
"""TOPIXの時系列データを折れ線グラフでプロットする。"""
|
15
|
+
return (
|
16
|
+
alt.Chart(df, title="TOPIX 時系列チャート")
|
17
|
+
.mark_line()
|
18
|
+
.encode(
|
19
|
+
x=alt.X("Date:T", title="日付"),
|
20
|
+
y=alt.Y("Close:Q", title="終値", scale=alt.Scale(zero=False)),
|
21
|
+
tooltip=[
|
22
|
+
alt.Tooltip("Date:T", title="日付"),
|
23
|
+
alt.Tooltip("Open:Q", title="始値"),
|
24
|
+
alt.Tooltip("High:Q", title="高値"),
|
25
|
+
alt.Tooltip("Low:Q", title="安値"),
|
26
|
+
alt.Tooltip("Close:Q", title="終値"),
|
27
|
+
],
|
28
|
+
)
|
29
|
+
)
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
5
5
|
import typer
|
6
6
|
from async_typer import AsyncTyper # pyright: ignore[reportMissingTypeStubs]
|
7
7
|
|
8
|
-
from . import auth, get
|
8
|
+
from . import auth, cache, get
|
9
9
|
|
10
10
|
app = AsyncTyper(
|
11
11
|
add_completion=False,
|
@@ -13,6 +13,7 @@ app = AsyncTyper(
|
|
13
13
|
)
|
14
14
|
app.add_typer(auth.app, name="auth")
|
15
15
|
app.add_typer(get.app, name="get")
|
16
|
+
app.add_typer(cache.app, name="cache")
|
16
17
|
|
17
18
|
|
18
19
|
@app.command()
|
@@ -0,0 +1,61 @@
|
|
1
|
+
"""Cache management commands."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import shutil
|
6
|
+
from typing import TYPE_CHECKING
|
7
|
+
|
8
|
+
import typer
|
9
|
+
from rich.console import Console
|
10
|
+
from rich.tree import Tree
|
11
|
+
|
12
|
+
from kabukit.utils.config import get_cache_dir
|
13
|
+
|
14
|
+
if TYPE_CHECKING:
|
15
|
+
from pathlib import Path
|
16
|
+
|
17
|
+
app = typer.Typer(add_completion=False, help="キャッシュを管理します。")
|
18
|
+
|
19
|
+
|
20
|
+
def add_to_tree(tree: Tree, path: Path) -> None:
|
21
|
+
for p in sorted(path.iterdir()):
|
22
|
+
if p.is_dir():
|
23
|
+
branch = tree.add(p.name)
|
24
|
+
add_to_tree(branch, p)
|
25
|
+
else:
|
26
|
+
tree.add(p.name)
|
27
|
+
|
28
|
+
|
29
|
+
@app.command()
|
30
|
+
def tree() -> None:
|
31
|
+
"""キャッシュディレクトリのツリー構造を表示します。"""
|
32
|
+
cache_dir = get_cache_dir()
|
33
|
+
|
34
|
+
if not cache_dir.exists():
|
35
|
+
typer.echo(f"キャッシュディレクトリ '{cache_dir}' は存在しません。")
|
36
|
+
return
|
37
|
+
|
38
|
+
console = Console()
|
39
|
+
tree_view = Tree(str(cache_dir))
|
40
|
+
add_to_tree(tree_view, cache_dir)
|
41
|
+
console.print(tree_view)
|
42
|
+
|
43
|
+
|
44
|
+
@app.command()
|
45
|
+
def clean() -> None:
|
46
|
+
"""キャッシュディレクトリを削除します。"""
|
47
|
+
cache_dir = get_cache_dir()
|
48
|
+
|
49
|
+
if not cache_dir.exists():
|
50
|
+
typer.echo(f"キャッシュディレクトリ '{cache_dir}' は存在しません。")
|
51
|
+
return
|
52
|
+
|
53
|
+
try:
|
54
|
+
shutil.rmtree(cache_dir)
|
55
|
+
msg = f"キャッシュディレクトリ '{cache_dir}' を正常にクリーンアップしました。"
|
56
|
+
typer.echo(msg)
|
57
|
+
except OSError:
|
58
|
+
msg = f"キャッシュディレクトリ '{cache_dir}' のクリーンアップ中に"
|
59
|
+
msg += "エラーが発生しました。"
|
60
|
+
typer.secho(msg, fg=typer.colors.RED, bold=True)
|
61
|
+
raise typer.Exit(1) from None
|
@@ -57,7 +57,12 @@ async def _fetch(
|
|
57
57
|
|
58
58
|
from kabukit.jquants.concurrent import fetch_all
|
59
59
|
|
60
|
-
|
60
|
+
try:
|
61
|
+
df = await fetch_all(target, progress=tqdm.asyncio.tqdm, **kwargs)
|
62
|
+
except KeyboardInterrupt:
|
63
|
+
typer.echo("中断しました。")
|
64
|
+
raise typer.Exit(1) from None
|
65
|
+
|
61
66
|
typer.echo(df)
|
62
67
|
path = cls(df).write()
|
63
68
|
typer.echo(f"全銘柄の{message}を '{path}' に保存しました。")
|
@@ -100,7 +105,12 @@ async def list_() -> None:
|
|
100
105
|
from kabukit.core.list import List
|
101
106
|
from kabukit.edinet.concurrent import fetch_list
|
102
107
|
|
103
|
-
|
108
|
+
try:
|
109
|
+
df = await fetch_list(years=10, progress=tqdm.asyncio.tqdm)
|
110
|
+
except (KeyboardInterrupt, RuntimeError):
|
111
|
+
typer.echo("中断しました。")
|
112
|
+
raise typer.Exit(1) from None
|
113
|
+
|
104
114
|
typer.echo(df)
|
105
115
|
path = List(df).write()
|
106
116
|
typer.echo(f"報告書一覧を '{path}' に保存しました。")
|
@@ -125,7 +135,12 @@ async def reports() -> None:
|
|
125
135
|
lst = df.filter(pl.col("csvFlag"), pl.col("secCode").is_not_null())
|
126
136
|
doc_ids = lst["docID"].unique()
|
127
137
|
|
128
|
-
|
138
|
+
try:
|
139
|
+
df = await fetch_csv(doc_ids, limit=1000, progress=tqdm.asyncio.tqdm)
|
140
|
+
except (KeyboardInterrupt, RuntimeError):
|
141
|
+
typer.echo("中断しました。")
|
142
|
+
raise typer.Exit(1) from None
|
143
|
+
|
129
144
|
typer.echo(df)
|
130
145
|
path = Reports(df).write()
|
131
146
|
typer.echo(f"報告書を '{path}' に保存しました。")
|
@@ -1,14 +1,15 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import datetime
|
4
|
-
from pathlib import Path
|
5
4
|
from typing import TYPE_CHECKING
|
6
5
|
|
7
6
|
import polars as pl
|
8
|
-
|
7
|
+
|
8
|
+
from kabukit.utils.config import get_cache_dir
|
9
9
|
|
10
10
|
if TYPE_CHECKING:
|
11
11
|
from collections.abc import Iterable
|
12
|
+
from pathlib import Path
|
12
13
|
from typing import Any, Self
|
13
14
|
|
14
15
|
from polars import DataFrame
|
@@ -24,7 +25,7 @@ class Base:
|
|
24
25
|
@classmethod
|
25
26
|
def data_dir(cls) -> Path:
|
26
27
|
clsname = cls.__name__.lower()
|
27
|
-
return
|
28
|
+
return get_cache_dir() / clsname
|
28
29
|
|
29
30
|
def write(self) -> Path:
|
30
31
|
data_dir = self.data_dir()
|
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
|
|
5
5
|
from kabukit.utils import concurrent
|
6
6
|
|
7
7
|
from .client import JQuantsClient
|
8
|
-
from .info import
|
8
|
+
from .info import get_target_codes
|
9
9
|
|
10
10
|
if TYPE_CHECKING:
|
11
11
|
from collections.abc import Iterable
|
@@ -80,7 +80,7 @@ async def fetch_all(
|
|
80
80
|
すべての銘柄の財務情報を含む単一のDataFrame。
|
81
81
|
"""
|
82
82
|
|
83
|
-
codes = await
|
83
|
+
codes = await get_target_codes()
|
84
84
|
codes = codes[:limit]
|
85
85
|
|
86
86
|
return await fetch(
|
@@ -11,10 +11,13 @@ def clean(df: DataFrame) -> DataFrame:
|
|
11
11
|
).drop("^.+Code$", "CompanyNameEnglish")
|
12
12
|
|
13
13
|
|
14
|
-
async def
|
15
|
-
"""
|
14
|
+
async def get_target_codes() -> list[str]:
|
15
|
+
"""分析対象となる銘柄コードのリストを返す。
|
16
16
|
|
17
|
-
|
17
|
+
以下の条件を満たす銘柄は対象外とする。
|
18
|
+
- 市場: TOKYO PRO MARKET
|
19
|
+
- 業種: その他 -- (投資信託など)
|
20
|
+
- 優先株式
|
18
21
|
"""
|
19
22
|
from .client import JQuantsClient
|
20
23
|
|
@@ -25,6 +28,7 @@ async def get_codes() -> list[str]:
|
|
25
28
|
info.filter(
|
26
29
|
pl.col("MarketCodeName") != "TOKYO PRO MARKET",
|
27
30
|
pl.col("Sector17CodeName") != "その他",
|
31
|
+
~pl.col("CompanyName").str.contains("優先株式"),
|
28
32
|
)
|
29
33
|
.get_column("Code")
|
30
34
|
.to_list()
|
@@ -1,7 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import asyncio
|
4
|
-
|
4
|
+
import contextlib
|
5
5
|
from typing import TYPE_CHECKING, Any, Protocol
|
6
6
|
|
7
7
|
import polars as pl
|
@@ -48,10 +48,17 @@ async def collect[R](
|
|
48
48
|
async with semaphore:
|
49
49
|
return await awaitable
|
50
50
|
|
51
|
-
|
51
|
+
tasks = {asyncio.create_task(run(awaitable)) for awaitable in awaitables}
|
52
52
|
|
53
|
-
|
54
|
-
|
53
|
+
try:
|
54
|
+
async for future in asyncio.as_completed(tasks):
|
55
|
+
with contextlib.suppress(asyncio.CancelledError):
|
56
|
+
yield await future
|
57
|
+
finally:
|
58
|
+
for task in tasks:
|
59
|
+
task.cancel()
|
60
|
+
if tasks:
|
61
|
+
await asyncio.gather(*tasks, return_exceptions=True)
|
55
62
|
|
56
63
|
|
57
64
|
async def collect_fn[T, R](
|
@@ -92,19 +99,16 @@ type Callback = Callable[[DataFrame], DataFrame | None]
|
|
92
99
|
type Progress = type[progress_bar[Any] | tqdm[Any]] | _Progress
|
93
100
|
|
94
101
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
async def __aiter__(self) -> AsyncIterator[DataFrame]:
|
103
|
-
async with self.cls() as client:
|
104
|
-
fn = getattr(client, f"get_{self.resource}")
|
102
|
+
async def get_stream(
|
103
|
+
client: Client,
|
104
|
+
resource: str,
|
105
|
+
args: list[Any],
|
106
|
+
max_concurrency: int | None = None,
|
107
|
+
) -> AsyncIterator[DataFrame]:
|
108
|
+
fn = getattr(client, f"get_{resource}")
|
105
109
|
|
106
|
-
|
107
|
-
|
110
|
+
async for df in collect_fn(fn, args, max_concurrency):
|
111
|
+
yield df
|
108
112
|
|
109
113
|
|
110
114
|
async def fetch(
|
@@ -137,13 +141,14 @@ async def fetch(
|
|
137
141
|
すべての情報を含む単一のDataFrame。
|
138
142
|
"""
|
139
143
|
args = list(args)
|
140
|
-
|
144
|
+
async with cls() as client:
|
145
|
+
stream = get_stream(client, resource, args, max_concurrency)
|
141
146
|
|
142
|
-
|
143
|
-
|
147
|
+
if progress:
|
148
|
+
stream = progress(stream, total=len(args))
|
144
149
|
|
145
|
-
|
146
|
-
|
150
|
+
if callback:
|
151
|
+
stream = (x if (r := callback(x)) is None else r async for x in stream)
|
147
152
|
|
148
|
-
|
149
|
-
|
153
|
+
dfs = [df async for df in stream if not df.is_empty()]
|
154
|
+
return pl.concat(dfs) if dfs else pl.DataFrame()
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
3
3
|
from pathlib import Path
|
4
4
|
|
5
5
|
import dotenv
|
6
|
-
from platformdirs import user_config_dir
|
6
|
+
from platformdirs import user_cache_dir, user_config_dir
|
7
7
|
|
8
8
|
|
9
9
|
def get_dotenv_path() -> Path:
|
@@ -21,3 +21,7 @@ def set_key(key: str, value: str) -> tuple[bool | None, str, str]:
|
|
21
21
|
def load_dotenv() -> bool:
|
22
22
|
dotenv_path = get_dotenv_path()
|
23
23
|
return dotenv.load_dotenv(dotenv_path)
|
24
|
+
|
25
|
+
|
26
|
+
def get_cache_dir() -> Path:
|
27
|
+
return Path(user_cache_dir("kabukit"))
|
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
|
File without changes
|
File without changes
|
File without changes
|