kabukit 0.3.1__tar.gz → 0.5.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.
Files changed (41) hide show
  1. {kabukit-0.3.1 → kabukit-0.5.0}/PKG-INFO +2 -3
  2. {kabukit-0.3.1 → kabukit-0.5.0}/pyproject.toml +4 -10
  3. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/cli/auth.py +2 -2
  4. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/core/base.py +12 -1
  5. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/core/client.py +1 -1
  6. kabukit-0.5.0/src/kabukit/core/prices.py +104 -0
  7. kabukit-0.5.0/src/kabukit/core/statements.py +24 -0
  8. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/jquants/client.py +7 -2
  9. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/jquants/concurrent.py +1 -1
  10. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/jquants/schema.py +6 -9
  11. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/jquants/statements.py +28 -15
  12. kabukit-0.3.1/src/kabukit/core/prices.py +0 -30
  13. kabukit-0.3.1/src/kabukit/core/statements.py +0 -7
  14. {kabukit-0.3.1 → kabukit-0.5.0}/LICENSE +0 -0
  15. {kabukit-0.3.1 → kabukit-0.5.0}/README.md +0 -0
  16. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/__init__.py +0 -0
  17. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/analysis/__init__.py +0 -0
  18. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/analysis/indicators.py +0 -0
  19. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/analysis/preprocess.py +0 -0
  20. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/analysis/screener.py +0 -0
  21. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/analysis/visualization.py +0 -0
  22. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/cli/__init__.py +0 -0
  23. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/cli/app.py +0 -0
  24. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/cli/get.py +0 -0
  25. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/core/__init__.py +0 -0
  26. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/core/info.py +0 -0
  27. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/core/list.py +0 -0
  28. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/core/reports.py +0 -0
  29. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/edinet/__init__.py +0 -0
  30. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/edinet/client.py +0 -0
  31. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/edinet/concurrent.py +0 -0
  32. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/edinet/doc.py +0 -0
  33. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/jquants/__init__.py +0 -0
  34. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/jquants/info.py +0 -0
  35. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/jquants/prices.py +0 -0
  36. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/py.typed +0 -0
  37. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/utils/__init__.py +0 -0
  38. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/utils/concurrent.py +0 -0
  39. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/utils/config.py +0 -0
  40. {kabukit-0.3.1 → kabukit-0.5.0}/src/kabukit/utils/date.py +0 -0
  41. {kabukit-0.3.1 → kabukit-0.5.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.1
3
+ Version: 0.5.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>
@@ -28,10 +28,9 @@ License: MIT License
28
28
  Classifier: Development Status :: 4 - Beta
29
29
  Classifier: Programming Language :: Python
30
30
  Classifier: Programming Language :: Python :: 3.13
31
- Requires-Dist: altair>=5
32
31
  Requires-Dist: async-typer>=0.1
33
32
  Requires-Dist: holidays>=0.81
34
- Requires-Dist: httpx>=0.28.1
33
+ Requires-Dist: httpx>=0.28
35
34
  Requires-Dist: platformdirs>=4
36
35
  Requires-Dist: polars>=1
37
36
  Requires-Dist: python-dotenv>=1
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "kabukit"
7
- version = "0.3.1"
7
+ version = "0.5.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" }
@@ -16,10 +16,9 @@ classifiers = [
16
16
  ]
17
17
  requires-python = ">=3.13"
18
18
  dependencies = [
19
- "altair>=5",
20
19
  "async-typer>=0.1",
21
20
  "holidays>=0.81",
22
- "httpx>=0.28.1",
21
+ "httpx>=0.28",
23
22
  "platformdirs>=4",
24
23
  "polars>=1",
25
24
  "python-dotenv>=1",
@@ -36,6 +35,7 @@ Issues = "https://github.com/daizutabi/kabukit/issues"
36
35
 
37
36
  [dependency-groups]
38
37
  dev = [
38
+ "altair>=5",
39
39
  "basedpyright>=1.31.4",
40
40
  "marimo[lsp]>=0.16",
41
41
  "numpy>=2.3.3", # polars 1.33 type hinting workaround,
@@ -54,13 +54,7 @@ dev = [
54
54
  docs = ["mkapi>=4.4", "mkdocs-marimo", "mkdocs-material"]
55
55
 
56
56
  [tool.pytest.ini_options]
57
- addopts = [
58
- "--cov=kabukit",
59
- "--cov-report=lcov:lcov.info",
60
- "--doctest-modules",
61
- "-m",
62
- "not integration and not validation",
63
- ]
57
+ addopts = ["--cov=kabukit", "--cov-report=lcov:lcov.info", "--doctest-modules"]
64
58
  testpaths = ["tests/unit", "tests/integration", "tests/validation"]
65
59
  markers = [
66
60
  "integration: marks tests as integration tests",
@@ -20,8 +20,8 @@ async def auth_jquants(mailaddress: str, password: str) -> None:
20
20
  async with JQuantsClient() as client:
21
21
  try:
22
22
  await client.auth(mailaddress, password, save=True)
23
- except HTTPStatusError as e:
24
- typer.echo(f"認証に失敗しました: {e}")
23
+ except HTTPStatusError:
24
+ typer.echo("認証に失敗しました。")
25
25
  raise Exit(1) from None
26
26
 
27
27
  typer.echo("J-Quantsのリフレッシュトークン・IDトークンを保存しました。")
@@ -8,9 +8,11 @@ import polars as pl
8
8
  from platformdirs import user_cache_dir
9
9
 
10
10
  if TYPE_CHECKING:
11
- from typing import Self
11
+ from collections.abc import Iterable
12
+ from typing import Any, Self
12
13
 
13
14
  from polars import DataFrame
15
+ from polars._typing import IntoExprColumn
14
16
 
15
17
 
16
18
  class Base:
@@ -48,3 +50,12 @@ class Base:
48
50
 
49
51
  data = pl.read_parquet(filename)
50
52
  return cls(data)
53
+
54
+ def filter(
55
+ self,
56
+ *predicates: IntoExprColumn | Iterable[IntoExprColumn] | bool | list[bool],
57
+ **constraints: Any,
58
+ ) -> Self:
59
+ """Filter the data with given predicates and constraints."""
60
+ data = self.data.filter(*predicates, **constraints)
61
+ return self.__class__(data)
@@ -12,7 +12,7 @@ class Client:
12
12
  client: AsyncClient
13
13
 
14
14
  def __init__(self, base_url: str = "") -> None:
15
- self.client = AsyncClient(base_url=base_url)
15
+ self.client = AsyncClient(base_url=base_url, timeout=20)
16
16
 
17
17
  async def aclose(self) -> None:
18
18
  """HTTPクライアントを閉じる。"""
@@ -0,0 +1,104 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import polars as pl
6
+
7
+ from .base import Base
8
+
9
+ if TYPE_CHECKING:
10
+ from datetime import timedelta
11
+ from typing import Self
12
+
13
+ from polars import Expr
14
+
15
+ from .statements import Statements
16
+
17
+
18
+ class Prices(Base):
19
+ def truncate(self, every: str | timedelta | Expr) -> Self:
20
+ data = (
21
+ self.data.group_by(pl.col("Date").dt.truncate(every), "Code")
22
+ .agg(
23
+ pl.col("Open").drop_nulls().first(),
24
+ pl.col("High").max(),
25
+ pl.col("Low").min(),
26
+ pl.col("Close").drop_nulls().last(),
27
+ pl.col("Volume").sum(),
28
+ pl.col("TurnoverValue").sum(),
29
+ )
30
+ .sort("Code", "Date")
31
+ )
32
+ return self.__class__(data)
33
+
34
+ def with_adjusted_shares(self, statements: Statements) -> Self:
35
+ """日次の調整済み株式数を計算し、列として追加する。
36
+
37
+ 決算短信で報告される株式数(例:発行済株式総数)は、四半期ごとなど
38
+ 特定の日付のデータです。一方で、株式分割や併合は日々発生し、株式数を
39
+ 変動させます。
40
+ このメソッドは、直近の決算で報告された株式数を、日々の調整係数
41
+ (`AdjustmentFactor`) を用いて補正し、日次ベースの時系列データとして
42
+ 提供します。これにより、日々の時価総額計算などが正確に行えるようになります。
43
+
44
+ 具体的には、`statements`から`IssuedShares`(発行済株式総数)と
45
+ `TreasuryShares`(自己株式数)を取得し、それぞれを調整します。
46
+ 計算結果は、元の列名との混同を避けるため、接頭辞`Adjusted`を付与した
47
+ 新しい列(`AdjustedIssuedShares`, `AdjustedTreasuryShares`)として
48
+ 追加されます。
49
+
50
+ .. note::
51
+ この計算は、決算発表間の株式数の変動が、株式分割・併合
52
+ (`AdjustmentFactor`)にのみ起因すると仮定しています。
53
+ 期中に行われる増資や自己株式取得など、`AdjustmentFactor`に
54
+ 反映されないイベントによる株式数の変動は考慮されません。
55
+
56
+ Args:
57
+ statements (Statements): 財務データを提供する`Statements`オブジェクト。
58
+
59
+ Returns:
60
+ Self: `AdjustedIssuedShares`および`AdjustedTreasuryShares`列が
61
+ 追加された、新しいPricesオブジェクト。
62
+ """
63
+ shares = statements.number_of_shares().rename({"Date": "ReportDate"})
64
+
65
+ adjusted = (
66
+ self.data.join_asof(
67
+ shares,
68
+ left_on="Date",
69
+ right_on="ReportDate",
70
+ by="Code",
71
+ check_sortedness=False,
72
+ )
73
+ .with_columns(
74
+ (1.0 / pl.col("AdjustmentFactor"))
75
+ .cum_prod()
76
+ .over("Code", "ReportDate")
77
+ .alias("CumulativeRatio"),
78
+ )
79
+ .select(
80
+ "Date",
81
+ "Code",
82
+ (pl.col("IssuedShares", "TreasuryShares") * pl.col("CumulativeRatio"))
83
+ .round(0)
84
+ .cast(pl.Int64)
85
+ .name.prefix("Adjusted"),
86
+ )
87
+ )
88
+
89
+ data = self.data.join(adjusted, on=["Date", "Code"], how="left")
90
+
91
+ return self.__class__(data)
92
+
93
+ # def with_yields(self, statements: Statements) -> Self:
94
+ # """各種利回り指標(収益利回り、純資産利回り、配当利回り)を計算し、列として追加する。
95
+
96
+ # Args:
97
+ # statements (Statements): 財務データを提供する`Statements`オブジェクト。
98
+
99
+ # Returns:
100
+ # Self: 各種利回り指標が追加された、新しいPricesオブジェクト。
101
+ # """
102
+ # prices_with_adjusted_shares = self.with_adjusted_shares(statements)
103
+ # data_with_yields = calculate_all_yields(prices_with_adjusted_shares, statements)
104
+ # return self.__class__(data_with_yields)
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import polars as pl
6
+
7
+ from .base import Base
8
+
9
+ if TYPE_CHECKING:
10
+ from polars import DataFrame
11
+
12
+
13
+ class Statements(Base):
14
+ def number_of_shares(self) -> DataFrame:
15
+ """発行済株式数を取得する。"""
16
+ return self.data.filter(
17
+ pl.col("IssuedShares").is_not_null(),
18
+ ).select(
19
+ "Date",
20
+ "Code",
21
+ "IssuedShares",
22
+ "TreasuryShares",
23
+ "AverageOutstandingShares",
24
+ )
@@ -257,6 +257,7 @@ class JQuantsClient(Client):
257
257
  date: str | datetime.date | None = None,
258
258
  *,
259
259
  clean: bool = True,
260
+ with_date: bool = True,
260
261
  ) -> DataFrame:
261
262
  """四半期毎の決算短信サマリーおよび業績・配当の修正に関する開示情報を取得する。
262
263
 
@@ -264,6 +265,8 @@ class JQuantsClient(Client):
264
265
  code (str, optional): 財務情報を取得する銘柄のコード。
265
266
  date (str | datetime.date, optional): 財務情報を取得する日付。
266
267
  clean (bool, optional): 取得したデータをクリーンアップするかどうか。
268
+ with_date (bool, optional): クリーンアップ後に営業日ベースで開示日の翌日を
269
+ 計算して`Date`列を追加するかどうか。
267
270
 
268
271
  Returns:
269
272
  財務情報を含むDataFrame。
@@ -283,10 +286,12 @@ class JQuantsClient(Client):
283
286
  dfs = [df async for df in self.iter_pages(url, params, name)]
284
287
  df = pl.concat(dfs)
285
288
 
286
- if df.is_empty():
289
+ if df.is_empty() or not clean:
287
290
  return df
288
291
 
289
- return statements.clean(df) if clean else df
292
+ df = statements.clean(df)
293
+
294
+ return statements.with_date(df) if with_date else df
290
295
 
291
296
  async def get_announcement(self) -> DataFrame:
292
297
  """翌日発表予定の決算情報を取得する。
@@ -23,7 +23,7 @@ async def fetch(
23
23
  progress: Progress | None = None,
24
24
  callback: Callback | None = None,
25
25
  ) -> DataFrame:
26
- """全銘柄の各種データを取得し、単一のDataFrameにまとめて返す。
26
+ """複数の銘柄の各種データを取得し、単一のDataFrameにまとめて返す。
27
27
 
28
28
  Args:
29
29
  resource (str): 取得するデータの種類。JQuantsClientのメソッド名から"get_"を
@@ -45,8 +45,9 @@ class PriceColumns(BaseColumns):
45
45
 
46
46
 
47
47
  class StatementColumns(BaseColumns):
48
- Date = "開示日"
49
- Time = "開示時刻"
48
+ Date = "日付"
49
+ DisclosedDate = "開示日"
50
+ DisclosedTime = "開示時刻"
50
51
  Code = "銘柄コード"
51
52
  DisclosureNumber = "開示番号"
52
53
  TypeOfDocument = "開示書類種別"
@@ -129,13 +130,10 @@ class StatementColumns(BaseColumns):
129
130
  ChangesInAccountingEstimates = "会計上の見積りの変更"
130
131
  RetrospectiveRestatement = "修正再表示"
131
132
 
132
- # NumberOfIssuedAndOutstandingSharesAtTheEndOfFiscalYearIncludingTreasuryStock
133
- NumberOfShares = "期末発行済株式数"
134
- # NumberOfTreasuryStockAtTheEndOfFiscalYear
135
- NumberOfTreasuryStock = "期末自己株式数"
136
- AverageNumberOfShares = "期中平均株式数"
133
+ IssuedShares = "期末発行済株式数" # 自己株式を含む (NumberOfIssuedAndOutstandingSharesAtTheEndOfFiscalYearIncludingTreasuryStock)
134
+ TreasuryShares = "期末自己株式数" # (NumberOfTreasuryStockAtTheEndOfFiscalYear)
135
+ AverageOutstandingShares = "期中平均株式数" # 自己株式を除く。EPSなどの計算に使用される (AverageNumberOfShares)
137
136
 
138
- """
139
137
  NonConsolidatedNetSales = "売上高_非連結"
140
138
  NonConsolidatedOperatingProfit = "営業利益_非連結"
141
139
  NonConsolidatedOrdinaryProfit = "経常利益_非連結"
@@ -170,7 +168,6 @@ class StatementColumns(BaseColumns):
170
168
  NextYearForecastNonConsolidatedOrdinaryProfit = "経常利益_予想_翌事業年度期末_非連結"
171
169
  NextYearForecastNonConsolidatedProfit = "当期純利益_予想_翌事業年度期末_非連結"
172
170
  NextYearForecastNonConsolidatedEarningsPerShare = "一株あたり当期純利益_予想_翌事業年度期末_非連結"
173
- """
174
171
 
175
172
 
176
173
  def rename(df: DataFrame, *, strict: bool = False) -> DataFrame:
@@ -13,19 +13,18 @@ if TYPE_CHECKING:
13
13
 
14
14
  def clean(df: DataFrame) -> DataFrame:
15
15
  return (
16
- df.select(pl.exclude(r"^.*\(REIT\)|.*NonConsolidated.*$"))
16
+ df.select(pl.exclude(r"^.*\(REIT\)$"))
17
17
  .rename(
18
18
  {
19
- "DisclosedDate": "Date",
20
- "DisclosedTime": "Time",
21
19
  "LocalCode": "Code",
22
- "NumberOfIssuedAndOutstandingSharesAtTheEndOfFiscalYearIncludingTreasuryStock": "NumberOfShares", # noqa: E501
23
- "NumberOfTreasuryStockAtTheEndOfFiscalYear": "NumberOfTreasuryStock",
20
+ "NumberOfIssuedAndOutstandingSharesAtTheEndOfFiscalYearIncludingTreasuryStock": "IssuedShares", # noqa: E501
21
+ "NumberOfTreasuryStockAtTheEndOfFiscalYear": "TreasuryShares",
22
+ "AverageNumberOfShares": "AverageOutstandingShares",
24
23
  },
25
24
  )
26
25
  .with_columns(
27
26
  pl.col("^.*Date$").str.to_date("%Y-%m-%d", strict=False),
28
- pl.col("Time").str.to_time("%H:%M:%S", strict=False),
27
+ pl.col("DisclosedTime").str.to_time("%H:%M:%S", strict=False),
29
28
  pl.col("TypeOfCurrentPeriod").cast(pl.Categorical),
30
29
  )
31
30
  .pipe(_cast_float)
@@ -45,10 +44,17 @@ def _cast_float(df: DataFrame) -> DataFrame:
45
44
  "Earnings",
46
45
  "Equity",
47
46
  "NetSales",
48
- "NumberOf",
49
47
  "PayoutRatio",
50
48
  "Profit",
51
49
  ]
50
+ ).with_columns(
51
+ pl.col(
52
+ "IssuedShares",
53
+ "TreasuryShares",
54
+ ).cast(pl.Int64, strict=False),
55
+ pl.col(
56
+ "AverageOutstandingShares",
57
+ ).cast(pl.Float64, strict=False),
52
58
  )
53
59
 
54
60
 
@@ -77,15 +83,22 @@ def get_holidays(year: int | None = None, n: int = 10) -> list[datetime.date]:
77
83
  return sorted(dates.keys())
78
84
 
79
85
 
80
- def update_effective_date(df: DataFrame, year: int | None = None) -> DataFrame:
81
- """開示日が休日や15時以降の場合、翌営業日に更新する。"""
82
- holidays = get_holidays(year=year)
86
+ def with_date(df: DataFrame, year: int | None = None) -> DataFrame:
87
+ """`Date`列を追加する。
83
88
 
84
- cond = pl.col("Time").is_null() | (pl.col("Time") > datetime.time(15, 0))
89
+ 開示日が休日のとき、あるいは、開示時刻が15時以降の場合、Dateを開示日の翌営業日に設定する。
90
+ """
91
+ is_after_hours = pl.col("DisclosedTime").is_null() | (
92
+ pl.col("DisclosedTime") > datetime.time(15, 0)
93
+ )
85
94
 
86
- return df.with_columns(
87
- pl.when(cond)
88
- .then(pl.col("Date").dt.add_business_days(1, holidays=holidays))
89
- .otherwise(pl.col("Date"))
95
+ holidays = get_holidays(year=year)
96
+
97
+ return df.select(
98
+ pl.when(is_after_hours)
99
+ .then(pl.col("DisclosedDate") + datetime.timedelta(days=1))
100
+ .otherwise(pl.col("DisclosedDate"))
101
+ .dt.add_business_days(0, holidays=holidays, roll="forward")
90
102
  .alias("Date"),
103
+ pl.all(),
91
104
  )
@@ -1,30 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import TYPE_CHECKING
4
-
5
- import polars as pl
6
-
7
- from .base import Base
8
-
9
- if TYPE_CHECKING:
10
- from datetime import timedelta
11
- from typing import Self
12
-
13
- from polars import Expr
14
-
15
-
16
- class Prices(Base):
17
- def truncate(self, every: str | timedelta | Expr) -> Self:
18
- data = (
19
- self.data.group_by(pl.col("Date").dt.truncate(every), "Code")
20
- .agg(
21
- pl.col("Open").drop_nulls().first(),
22
- pl.col("High").max(),
23
- pl.col("Low").min(),
24
- pl.col("Close").drop_nulls().last(),
25
- pl.col("Volume").sum(),
26
- pl.col("TurnoverValue").sum(),
27
- )
28
- .sort("Code", "Date")
29
- )
30
- return self.__class__(data)
@@ -1,7 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from .base import Base
4
-
5
-
6
- class Statements(Base):
7
- pass
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes