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.
Files changed (43) hide show
  1. {kabukit-0.3.0 → kabukit-0.3.1}/PKG-INFO +1 -1
  2. {kabukit-0.3.0 → kabukit-0.3.1}/pyproject.toml +8 -4
  3. kabukit-0.3.1/src/kabukit/__init__.py +17 -0
  4. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/cli/get.py +56 -6
  5. kabukit-0.3.1/src/kabukit/core/list.py +12 -0
  6. kabukit-0.3.1/src/kabukit/core/reports.py +12 -0
  7. kabukit-0.3.1/src/kabukit/edinet/__init__.py +3 -0
  8. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/edinet/client.py +3 -7
  9. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/edinet/concurrent.py +4 -2
  10. kabukit-0.3.1/src/kabukit/edinet/doc.py +46 -0
  11. kabukit-0.3.1/src/kabukit/jquants/__init__.py +4 -0
  12. kabukit-0.3.0/src/kabukit/__init__.py +0 -7
  13. kabukit-0.3.0/src/kabukit/edinet/__init__.py +0 -3
  14. kabukit-0.3.0/src/kabukit/edinet/doc.py +0 -32
  15. kabukit-0.3.0/src/kabukit/jquants/__init__.py +0 -3
  16. {kabukit-0.3.0 → kabukit-0.3.1}/LICENSE +0 -0
  17. {kabukit-0.3.0 → kabukit-0.3.1}/README.md +0 -0
  18. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/analysis/__init__.py +0 -0
  19. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/analysis/indicators.py +0 -0
  20. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/analysis/preprocess.py +0 -0
  21. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/analysis/screener.py +0 -0
  22. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/analysis/visualization.py +0 -0
  23. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/cli/__init__.py +0 -0
  24. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/cli/app.py +0 -0
  25. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/cli/auth.py +0 -0
  26. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/core/__init__.py +0 -0
  27. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/core/base.py +0 -0
  28. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/core/client.py +0 -0
  29. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/core/info.py +0 -0
  30. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/core/prices.py +0 -0
  31. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/core/statements.py +0 -0
  32. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/jquants/client.py +0 -0
  33. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/jquants/concurrent.py +0 -0
  34. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/jquants/info.py +0 -0
  35. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/jquants/prices.py +0 -0
  36. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/jquants/schema.py +0 -0
  37. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/jquants/statements.py +0 -0
  38. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/py.typed +0 -0
  39. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/utils/__init__.py +0 -0
  40. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/utils/concurrent.py +0 -0
  41. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/utils/config.py +0 -0
  42. {kabukit-0.3.0 → kabukit-0.3.1}/src/kabukit/utils/date.py +0 -0
  43. {kabukit-0.3.0 → kabukit-0.3.1}/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.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>
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "kabukit"
7
- version = "0.3.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" }
@@ -58,10 +58,14 @@ addopts = [
58
58
  "--cov=kabukit",
59
59
  "--cov-report=lcov:lcov.info",
60
60
  "--doctest-modules",
61
- "-m not integration",
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
- writer_cls: type,
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 = writer_cls(df).write()
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
- writer_cls=Statements,
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
- writer_cls=Prices,
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()
@@ -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)
@@ -83,13 +83,14 @@ async def fetch_list(
83
83
  if limit is not None:
84
84
  dates = dates[:limit]
85
85
 
86
- return await fetch(
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
- return await fetch(
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
+ )
@@ -0,0 +1,4 @@
1
+ from .concurrent import fetch, fetch_all
2
+ from .schema import rename
3
+
4
+ __all__ = ["fetch", "fetch_all", "rename"]
@@ -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
File without changes
File without changes