kabukit 0.1.0__py3-none-any.whl → 0.2.0__py3-none-any.whl

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/jquants/client.py CHANGED
@@ -1,324 +1,324 @@
1
- """This module provides a client for the J-Quants API.
2
-
3
- It handles authentication and provides methods to interact with
4
- the API endpoints.
5
- """
6
-
7
1
  from __future__ import annotations
8
2
 
9
3
  import datetime
10
4
  import os
11
5
  from enum import StrEnum
12
- from functools import cached_property
13
- from pathlib import Path
14
6
  from typing import TYPE_CHECKING
15
7
 
16
8
  import polars as pl
17
- from dotenv import load_dotenv, set_key
18
- from httpx import Client
19
- from platformdirs import user_config_dir
20
9
  from polars import DataFrame
21
10
 
11
+ from kabukit.core.client import Client
12
+ from kabukit.utils.config import load_dotenv, set_key
13
+ from kabukit.utils.params import get_params
14
+
15
+ from . import info, prices, statements
16
+
22
17
  if TYPE_CHECKING:
23
- from collections.abc import Iterator
24
- from typing import Any
18
+ from collections.abc import AsyncIterator
19
+ from typing import Any, Self
25
20
 
26
21
  from httpx import HTTPStatusError # noqa: F401
27
22
  from httpx._types import QueryParamTypes
28
23
 
29
24
  API_VERSION = "v1"
30
-
31
-
32
- class AuthenticationError(Exception):
33
- """Custom exception for authentication failures."""
25
+ BASE_URL = f"https://api.jquants.com/{API_VERSION}"
34
26
 
35
27
 
36
28
  class AuthKey(StrEnum):
37
- """Environment variable keys for J-Quants authentication."""
29
+ """J-Quants認証のための環境変数キー。"""
38
30
 
39
31
  REFRESH_TOKEN = "JQUANTS_REFRESH_TOKEN" # noqa: S105
40
32
  ID_TOKEN = "JQUANTS_ID_TOKEN" # noqa: S105
41
33
 
42
34
 
43
- class JQuantsClient:
44
- """A client for interacting with the J-Quants API.
35
+ class JQuantsClient(Client):
36
+ """J-Quants APIと対話するためのクライアント。
45
37
 
46
- This client manages API authentication tokens (refresh and ID)
47
- and provides methods to access various J-Quants API
48
- endpoints. Tokens are loaded from and saved to a file in the
49
- user's standard config directory.
38
+ API認証トークン(リフレッシュトークンおよびIDトークン)を管理し、
39
+ 各種J-Quants APIエンドポイントへアクセスするメソッドを提供する。
40
+ トークンは設定ファイルから読み込まれ、またファイルに保存される。
50
41
 
51
42
  Attributes:
52
- client: An httpx.Client instance for making API requests.
53
- refresh_token: The refresh token for authentication.
54
- id_token: The ID token for API requests.
43
+ client: APIリクエストを行うための `AsyncClient` インスタンス。
55
44
  """
56
45
 
57
- client: Client
58
- refresh_token: str | None
59
- id_token: str | None
46
+ def __init__(self, id_token: str | None = None) -> None:
47
+ super().__init__(BASE_URL)
48
+ self.set_id_token(id_token)
60
49
 
61
- def __init__(self) -> None:
62
- """Initializes the JQuantsClient.
50
+ def set_id_token(self, id_token: str | None = None) -> None:
51
+ """IDトークンをヘッダーに設定する。
63
52
 
64
- It sets up the httpx client, determines the config path,
65
- loads authentication tokens, and sets the auth header if an
66
- ID token is present.
53
+ Args:
54
+ id_token (str | None, optional): 設定するIDトークン。
55
+ Noneの場合、環境変数から読み込む。
67
56
  """
68
- self.client = Client(base_url=f"https://api.jquants.com/{API_VERSION}")
69
- self._setup_config_path()
70
- self._load_tokens()
71
- self.set_header()
72
-
73
- @cached_property
74
- def dotenv_path(self) -> Path:
75
- """Returns the path to the .env file in the user config directory."""
76
- config_dir = Path(user_config_dir("kabukit"))
77
- config_dir.mkdir(parents=True, exist_ok=True)
78
- return config_dir / ".env"
79
-
80
- def _setup_config_path(self) -> None:
81
- """Determines the config path and creates the directory."""
82
- # Accessing dotenv_path property will create the directory if it doesn't exist
83
- _ = self.dotenv_path
84
-
85
- def _load_tokens(self) -> None:
86
- """Loads tokens from the .env file."""
87
- load_dotenv(self.dotenv_path)
88
- self.refresh_token = os.environ.get(AuthKey.REFRESH_TOKEN)
89
- self.id_token = os.environ.get(AuthKey.ID_TOKEN)
90
-
91
- def set_header(self) -> None:
92
- """Sets the Authorization header if an ID token is available."""
93
- if self.id_token:
94
- self.client.headers["Authorization"] = f"Bearer {self.id_token}"
95
- # Clear header if no ID token is available
96
- elif "Authorization" in self.client.headers:
97
- del self.client.headers["Authorization"]
98
-
99
- def auth(self, mailaddress: str, password: str) -> None:
100
- """Authenticates, saves tokens, and sets the auth header.
101
57
 
102
- Args:
103
- mailaddress: The user's email address.
104
- password: The user's password.
58
+ if id_token is None:
59
+ load_dotenv()
60
+ id_token = os.environ.get(AuthKey.ID_TOKEN)
105
61
 
106
- Raises:
107
- HTTPStatusError: If any API request fails.
108
- """
109
- self.refresh_token = self.get_refresh_token(mailaddress, password)
110
- self.id_token = self.get_id_token(self.refresh_token)
111
- set_key(self.dotenv_path, AuthKey.REFRESH_TOKEN, self.refresh_token)
112
- set_key(self.dotenv_path, AuthKey.ID_TOKEN, self.id_token)
113
- self.set_header()
62
+ if id_token:
63
+ self.client.headers["Authorization"] = f"Bearer {id_token}"
114
64
 
115
- def post(self, url: str, json: Any | None = None) -> Any:
116
- """Sends a POST request to the specified URL.
65
+ async def post(self, url: str, json: Any | None = None) -> Any:
66
+ """指定されたURLにPOSTリクエストを送信する。
117
67
 
118
68
  Args:
119
- url: The URL path for the POST request.
120
- json: The JSON payload for the request body.
69
+ url: POSTリクエストのURLパス。
70
+ json: リクエストボディのJSONペイロード。
121
71
 
122
72
  Returns:
123
- The JSON response from the API.
73
+ APIからのJSONレスポンス。
124
74
 
125
75
  Raises:
126
- AuthenticationError: If no ID token is available.
127
- HTTPStatusError: If the API request fails.
76
+ HTTPStatusError: APIリクエストが失敗した場合。
128
77
  """
129
- if not self.id_token:
130
- msg = "ID token is not available. Please authenticate first."
131
- raise AuthenticationError(msg)
132
-
133
- resp = self.client.post(url, json=json)
78
+ resp = await self.client.post(url, json=json)
134
79
  resp.raise_for_status()
135
80
  return resp.json()
136
81
 
137
- def get_refresh_token(self, mailaddress: str, password: str) -> str:
138
- """Gets a new refresh token from the API.
82
+ async def get(self, url: str, params: QueryParamTypes | None = None) -> Any:
83
+ """指定されたURLにGETリクエストを送信する。
139
84
 
140
85
  Args:
141
- mailaddress: The user's email address.
142
- password: The user's password.
86
+ url (str): GETリクエストのURLパス。
87
+ params (QueryParamTypes | None, optional): リクエストのクエリパラメータ。
143
88
 
144
89
  Returns:
145
- The new refresh token.
90
+ APIからのJSONレスポンス。
146
91
 
147
92
  Raises:
148
- httpx.HTTPStatusError: If the API request fails.
93
+ HTTPStatusError: APIリクエストが失敗した場合。
149
94
  """
150
- json_data = {"mailaddress": mailaddress, "password": password}
151
- return self.post("/token/auth_user", json=json_data)["refreshToken"]
95
+ resp = await self.client.get(url, params=params)
96
+ resp.raise_for_status()
97
+ return resp.json()
152
98
 
153
- def get_id_token(self, refresh_token: str) -> str:
154
- """Gets a new ID token from the API.
99
+ async def auth(
100
+ self,
101
+ mailaddress: str,
102
+ password: str,
103
+ *,
104
+ save: bool = False,
105
+ ) -> Self:
106
+ """認証を行い、トークンを保存する。
155
107
 
156
108
  Args:
157
- refresh_token: The refresh token to use.
158
-
159
- Returns:
160
- The new ID token.
109
+ mailaddress (str): J-Quantsに登録したメールアドレス。
110
+ password (str): J-Quantsのパスワード。
111
+ save (bool, optional): トークンを環境変数に保存するかどうか。
161
112
 
162
113
  Raises:
163
- HTTPStatusError: If the API request fails.
114
+ HTTPStatusError: APIリクエストが失敗した場合。
164
115
  """
165
- url = f"/token/auth_refresh?refreshtoken={refresh_token}"
166
- return self.post(url)["idToken"]
167
-
168
- def get(self, url: str, params: QueryParamTypes | None = None) -> Any:
169
- """Sends a GET request to the specified URL.
116
+ json_data = {"mailaddress": mailaddress, "password": password}
117
+ data = await self.post("/token/auth_user", json=json_data)
118
+ refresh_token = data["refreshToken"]
170
119
 
171
- Args:
172
- url: The URL path for the GET request.
173
- params: The query parameters for the request.
120
+ url = f"/token/auth_refresh?refreshtoken={refresh_token}"
121
+ data = await self.post(url)
122
+ id_token = data["idToken"]
174
123
 
175
- Returns:
176
- The JSON response from the API.
124
+ if save:
125
+ set_key(AuthKey.REFRESH_TOKEN, refresh_token)
126
+ set_key(AuthKey.ID_TOKEN, id_token)
177
127
 
178
- Raises:
179
- AuthenticationError: If no ID token is available.
180
- HTTPStatusError: If the API request fails.
181
- """
182
- if not self.id_token:
183
- msg = "ID token is not available. Please authenticate first."
184
- raise AuthenticationError(msg)
185
-
186
- resp = self.client.get(url, params=params)
187
- resp.raise_for_status()
188
- return resp.json()
128
+ self.set_id_token(id_token)
129
+ return self
189
130
 
190
- def get_listed_info(
131
+ async def get_info(
191
132
  self,
192
133
  code: str | None = None,
193
134
  date: str | datetime.date | None = None,
194
135
  ) -> DataFrame:
195
- """Gets listed info (e.g., stock details) from the API.
136
+ """銘柄情報を取得する。
196
137
 
197
138
  Args:
198
- code: Optional. The stock code to filter by.
199
- date: Optional. The date to filter by (YYYY-MM-DD format
200
- or datetime.date object).
139
+ code (str, optional): 情報を取得する銘柄のコード。
140
+ date (str | datetime.date, optional): 情報を取得する日付。
201
141
 
202
142
  Returns:
203
- A Polars DataFrame containing the listed info.
143
+ 銘柄情報を含むPolars DataFrame
204
144
 
205
145
  Raises:
206
- AuthenticationError: If no ID token is available.
207
- HTTPStatusError: If the API request fails.
146
+ HTTPStatusError: APIリクエストが失敗した場合。
208
147
  """
209
- params = params_code_date(code, date)
148
+ params = get_params(code=code, date=date)
210
149
  url = "/listed/info"
211
- data = self.get(url, params)
212
- df = DataFrame(data["info"])
213
- return df.with_columns(pl.col("Date").str.to_date())
150
+ data = await self.get(url, params)
151
+ return DataFrame(data["info"]).pipe(info.clean)
214
152
 
215
- def iter_pagaes(
153
+ async def iter_pages(
216
154
  self,
217
155
  url: str,
218
156
  params: dict[str, Any] | None,
219
157
  name: str,
220
- ) -> Iterator[DataFrame]:
221
- """Iterates through paginated API responses.
158
+ ) -> AsyncIterator[DataFrame]:
159
+ """ページ分割されたAPIレスポンスを反復処理する。
222
160
 
223
161
  Args:
224
- url: The base URL for the API endpoint.
225
- params: Optional. Dictionary of query parameters.
226
- name: The key in the JSON response containing the list of items.
162
+ url (str): APIエンドポイントのベースURL
163
+ params (dict[str, Any]): クエリパラメータの辞書。
164
+ name (str): アイテムのリストを含むJSONレスポンスのキー。
227
165
 
228
166
  Yields:
229
- A Polars DataFrame for each page of data.
167
+ データの各ページに対応するPolars DataFrame
230
168
 
231
169
  Raises:
232
- AuthenticationError: If no ID token is available.
233
- HTTPStatusError: If the API request fails.
170
+ HTTPStatusError: APIリクエストが失敗した場合。
234
171
  """
235
172
  params = params or {}
236
173
 
237
174
  while True:
238
- data = self.get(url, params)
175
+ data = await self.get(url, params)
239
176
  yield DataFrame(data[name])
240
177
  if "pagination_key" in data:
241
178
  params["pagination_key"] = data["pagination_key"]
242
179
  else:
243
180
  break
244
181
 
245
- def get_prices(
182
+ async def get_prices(
246
183
  self,
247
184
  code: str | None = None,
248
185
  date: str | datetime.date | None = None,
249
186
  from_: str | datetime.date | None = None,
250
187
  to: str | datetime.date | None = None,
251
188
  ) -> DataFrame:
252
- """Gets daily stock prices from the API.
189
+ """日々の株価四本値を取得する。
190
+
191
+ 株価は分割・併合を考慮した調整済み株価(小数点第2位四捨五入)と調整前の株価を取得できる。
253
192
 
254
193
  Args:
255
- code: Optional. The stock code to filter by.
256
- date: Optional. The specific date for which to retrieve prices.
257
- Cannot be used with `from_` or `to`.
258
- from_: Optional. The start date for a price range.
259
- Requires `to` if `date` is not specified.
260
- to: Optional. The end date for a price range.
261
- Requires `from_` if `date` is not specified.
194
+ code (str, optional): 株価を取得する銘柄のコード。
195
+ date (str | datetime.date, optional): 株価を取得する日付。
196
+ `from_`または`to`とは併用不可。
197
+ from_ (str | datetime.date, optional): 取得期間の開始日。
198
+ `date`とは併用不可。
199
+ to (str | datetime.date, optional): 取得期間の終了日。
200
+ `date`とは併用不可。
262
201
 
263
202
  Returns:
264
- A Polars DataFrame containing daily stock prices.
203
+ 日々の株価四本値を含むPolars DataFrame
265
204
 
266
205
  Raises:
267
- ValueError: If both `date` and `from_`/`to` are specified.
268
- AuthenticationError: If no ID token is available.
269
- HTTPStatusError: If the API request fails.
206
+ ValueError: `date`と`from_`/`to`の両方が指定された場合。
207
+ HTTPStatusError: APIリクエストが失敗した場合。
270
208
  """
271
- params = params_code_date(code, date)
209
+ if not date and not code:
210
+ return await self.get_latest_available_prices()
272
211
 
273
212
  if date and (from_ or to):
274
- msg = "Cannot specify both date and from/to parameters."
213
+ msg = "datefrom/toの両方を指定することはできません。"
275
214
  raise ValueError(msg)
276
215
 
277
- if not date and from_:
278
- params["from"] = date_to_str(from_)
279
- if not date and to:
280
- params["to"] = date_to_str(to)
216
+ params = get_params(code=code, date=date, from_=from_, to=to)
281
217
 
282
218
  url = "/prices/daily_quotes"
283
219
  name = "daily_quotes"
284
220
 
285
- df = pl.concat(self.iter_pagaes(url, params, name))
221
+ dfs = [df async for df in self.iter_pages(url, params, name)]
222
+ df = pl.concat(dfs)
223
+
286
224
  if df.is_empty():
287
225
  return df
288
226
 
289
- return df.with_columns(pl.col("Date").str.to_date())
227
+ return prices.clean(df)
290
228
 
229
+ async def get_latest_available_prices(self, num_days: int = 30) -> DataFrame:
230
+ """直近利用可能な日付の株価を取得する。"""
231
+ today = datetime.date.today() # noqa: DTZ011
291
232
 
292
- def params_code_date(
293
- code: str | None,
294
- date: str | datetime.date | None,
295
- ) -> dict[str, str]:
296
- """Constructs a dictionary of parameters for code and date filtering.
233
+ for days in range(num_days):
234
+ date = today - datetime.timedelta(days)
235
+ df = await self.get_prices(date=date)
297
236
 
298
- Args:
299
- code: Optional. The stock code.
300
- date: Optional. The date (string or datetime.date object).
237
+ if not df.is_empty():
238
+ return df
301
239
 
302
- Returns:
303
- A dictionary containing 'code' and/or 'date' parameters.
304
- """
305
- params: dict[str, str] = {}
306
- if code:
307
- params["code"] = code
308
- if date:
309
- params["date"] = date_to_str(date)
310
- return params
240
+ return DataFrame()
241
+
242
+ async def get_statements(
243
+ self,
244
+ code: str | None = None,
245
+ date: str | datetime.date | None = None,
246
+ ) -> DataFrame:
247
+ """四半期毎の決算短信サマリーおよび業績・配当の修正に関する開示情報を取得する。
248
+
249
+ Args:
250
+ code (str, optional): 財務情報を取得する銘柄のコード。
251
+ date (str | datetime.date, optional): 財務情報を取得する日付。
252
+
253
+ Returns:
254
+ 財務情報を含むDataFrame。
255
+
256
+ Raises:
257
+ ValueError: `code`と`date`が両方とも指定されない場合。
258
+ HTTPStatusError: APIリクエストが失敗した場合。
259
+ """
260
+ if not code and not date:
261
+ msg = "codeまたはdateのどちらかを指定する必要があります。"
262
+ raise ValueError(msg)
311
263
 
264
+ params = get_params(code=code, date=date)
265
+ url = "/fins/statements"
266
+ name = "statements"
312
267
 
313
- def date_to_str(date: str | datetime.date) -> str:
314
- """Converts a date object or string to a YYYY-MM-DD string.
268
+ dfs = [df async for df in self.iter_pages(url, params, name)]
269
+ df = pl.concat(dfs)
315
270
 
316
- Args:
317
- date: The date to convert (string or datetime.date object).
271
+ if df.is_empty():
272
+ return df
318
273
 
319
- Returns:
320
- The date as a YYYY-MM-DD string.
321
- """
322
- if isinstance(date, datetime.date):
323
- return date.strftime("%Y-%m-%d")
324
- return date
274
+ return statements.clean(df)
275
+
276
+ async def get_announcement(self) -> DataFrame:
277
+ """翌日発表予定の決算情報を取得する。
278
+
279
+ Returns:
280
+ 開示情報を含むPolars DataFrame。
281
+
282
+ Raises:
283
+ HTTPStatusError: APIリクエストが失敗した場合。
284
+ """
285
+ url = "fins/announcement"
286
+ name = "announcement"
287
+
288
+ dfs = [df async for df in self.iter_pages(url, {}, name)]
289
+ df = pl.concat(dfs)
290
+ if df.is_empty():
291
+ return df
292
+
293
+ return df.with_columns(pl.col("Date").str.to_date("%Y-%m-%d", strict=False))
294
+
295
+ async def get_trades_spec(
296
+ self,
297
+ section: str | None = None,
298
+ from_: str | datetime.date | None = None,
299
+ to: str | datetime.date | None = None,
300
+ ) -> DataFrame:
301
+ """投資部門別の情報を取得する。
302
+
303
+ Args:
304
+ section: 絞り込み対象のセクション。
305
+ from_: 取得期間の開始日。
306
+ to: 取得期間の終了日。
307
+
308
+ Returns:
309
+ 投資部門別の情報を含むPolars DataFrame。
310
+
311
+ Raises:
312
+ HTTPStatusError: APIリクエストが失敗した場合。
313
+ """
314
+ params = get_params(section=section, from_=from_, to=to)
315
+
316
+ url = "/markets/trades_spec"
317
+ name = "trades_spec"
318
+
319
+ dfs = [df async for df in self.iter_pages(url, params, name)]
320
+ df = pl.concat(dfs)
321
+ if df.is_empty():
322
+ return df
323
+
324
+ return df.with_columns(pl.col("^.*Date$").str.to_date("%Y-%m-%d", strict=False))
@@ -0,0 +1,91 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from kabukit.utils import concurrent
6
+
7
+ from .client import JQuantsClient
8
+ from .info import get_codes
9
+
10
+ if TYPE_CHECKING:
11
+ from collections.abc import Iterable
12
+
13
+ from polars import DataFrame
14
+
15
+ from kabukit.utils.concurrent import Callback, Progress
16
+
17
+
18
+ async def fetch(
19
+ resource: str,
20
+ codes: Iterable[str],
21
+ /,
22
+ max_concurrency: int | None = None,
23
+ progress: Progress | None = None,
24
+ callback: Callback | None = None,
25
+ ) -> DataFrame:
26
+ """全銘柄の各種データを取得し、単一のDataFrameにまとめて返す。
27
+
28
+ Args:
29
+ resource (str): 取得するデータの種類。JQuantsClientのメソッド名から"get_"を
30
+ 除いたものを指定する。
31
+ codes (Iterable[str]): 取得対象の銘柄コードのリスト。
32
+ max_concurrency (int | None, optional): 同時に実行するリクエストの最大数。
33
+ 指定しないときはデフォルト値が使用される。
34
+ progress (Progress | None, optional): 進捗表示のための関数。
35
+ tqdm, marimoなどのライブラリを使用できる。
36
+ 指定しないときは進捗表示は行われない。
37
+ callback (Callback | None, optional): 各DataFrameに対して適用する
38
+ コールバック関数。指定しないときはそのままのDataFrameが使用される。
39
+
40
+ Returns:
41
+ DataFrame:
42
+ すべての銘柄の財務情報を含む単一のDataFrame。
43
+ """
44
+ return await concurrent.fetch(
45
+ JQuantsClient,
46
+ resource,
47
+ codes,
48
+ max_concurrency=max_concurrency,
49
+ progress=progress,
50
+ callback=callback,
51
+ )
52
+
53
+
54
+ async def fetch_all(
55
+ resource: str,
56
+ /,
57
+ limit: int | None = None,
58
+ max_concurrency: int | None = None,
59
+ progress: Progress | None = None,
60
+ callback: Callback | None = None,
61
+ ) -> DataFrame:
62
+ """全銘柄の各種データを取得し、単一のDataFrameにまとめて返す。
63
+
64
+ Args:
65
+ resource (str): 取得するデータの種類。JQuantsClientのメソッド名から"get_"を
66
+ 除いたものを指定する。
67
+ limit (int | None, optional): 取得する銘柄数の上限。
68
+ 指定しないときはすべての銘柄が対象となる。
69
+ max_concurrency (int | None, optional): 同時に実行するリクエストの最大数。
70
+ 指定しないときはデフォルト値が使用される。
71
+ progress (Progress | None, optional): 進捗表示のための関数。
72
+ tqdm, marimoなどのライブラリを使用できる。
73
+ 指定しないときは進捗表示は行われない。
74
+ callback (Callback | None, optional): 各DataFrameに対して適用する
75
+ コールバック関数。指定しないときはそのままのDataFrameが使用される。
76
+
77
+ Returns:
78
+ DataFrame:
79
+ すべての銘柄の財務情報を含む単一のDataFrame。
80
+ """
81
+
82
+ codes = await get_codes()
83
+ codes = codes[:limit]
84
+
85
+ return await fetch(
86
+ resource,
87
+ codes,
88
+ max_concurrency=max_concurrency,
89
+ progress=progress,
90
+ callback=callback,
91
+ )
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ import polars as pl
4
+ from polars import DataFrame
5
+
6
+
7
+ def clean(df: DataFrame) -> DataFrame:
8
+ return df.with_columns(
9
+ pl.col("Date").str.to_date("%Y-%m-%d"),
10
+ pl.col("^.*CodeName$", "ScaleCategory").cast(pl.Categorical),
11
+ ).drop("^.+Code$", "CompanyNameEnglish")
12
+
13
+
14
+ async def get_codes() -> list[str]:
15
+ """銘柄コードのリストを返す。
16
+
17
+ 市場「TOKYO PRO MARKET」と業種「その他」を除外した銘柄を対象とする。
18
+ """
19
+ from .client import JQuantsClient
20
+
21
+ async with JQuantsClient() as client:
22
+ info = await client.get_info()
23
+
24
+ return (
25
+ info.filter(
26
+ pl.col("MarketCodeName") != "TOKYO PRO MARKET",
27
+ pl.col("Sector17CodeName") != "その他",
28
+ )
29
+ .get_column("Code")
30
+ .to_list()
31
+ )