diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f3586c1..06d646f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,7 +17,7 @@ jobs: - name: Setup python uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version: "3.13" cache: "poetry" - name: Install dependencies run: poetry install @@ -29,7 +29,7 @@ jobs: fail-fast: false matrix: os: [ "ubuntu-latest" ] - python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12" ] + python-version: [ "3.10", "3.11", "3.12", "3.13" ] runs-on: ${{ matrix.os }} steps: #---------------------------------------------- diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a47cf37..dbe600f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -16,7 +16,7 @@ jobs: - name: Setup python uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version: "3.13" cache: 'poetry' - name: Install dependencies run: poetry install diff --git a/Makefile b/Makefile index 4787092..3ea7fcd 100644 --- a/Makefile +++ b/Makefile @@ -3,14 +3,14 @@ lint: poetry run isort . poetry run black . --check - poetry run pflake8 . + poetry run flake8 . poetry run mypy . .PHONY: lint-fix lint-fix: poetry run isort . poetry run black . - poetry run pflake8 . + poetry run flake8 . poetry run mypy . .PHONY: test diff --git a/README.md b/README.md index 4250774..115e15c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![PyPI version](https://badge.fury.io/py/jquants-api-client.svg)](https://badge.fury.io/py/jquants-api-client) -個人投資家向けデータ API 配信サービス「 [J-Quants API](https://jpx-jquants.com/#jquants-api) 」の Python クライアントライブラリです。 +個人投資家向けデータ API 配信サービス「 [J-Quants API](https://jpx-jquants.com/) 」の Python クライアントライブラリです。 J-Quants や API 仕様についての詳細を知りたい方は [公式ウェブサイト](https://jpx-jquants.com/) をご参照ください。 現在、J-Quants API は有償版サービスとして提供されています。 @@ -16,57 +16,146 @@ pip install jquants-api-client ### J-Quants API の利用 -To use J-Quants API, you need to "Applications for J-Quants API" from [J-Quants API Web site](https://jpx-jquants.com/?lang=en) and to select a plan. +To use J-Quants API, you need to "Applications for J-Quants API" from [J-Quants API Web site](https://jpx-jquants.com/) and to select a plan. J-Quants API を利用するためには[J-Quants API の Web サイト](https://jpx-jquants.com/) から「J-Quants API 申し込み」及び利用プランの選択が必要になります。 jquants-api-client-python を使用するためには「J-Quants API ログインページで使用するメールアドレスおよびパスワード」または「J-Quants API メニューページから取得したリフレッシュトークン」が必要になります。必要に応じて下記の Web サイトより取得してください。 -[J-Quants API ログインページ](https://jpx-jquants.com/auth/signin/) +[J-Quants API ログインページ](https://jpx-jquants.com/login) -### サンプルコード +### サンプルコード (V2) + +V2 API では API キーによる認証を使用します。API キーは [J-Quants API ダッシュボード](https://jpx-jquants.com/dashboard/api-keys) から取得できます。 ```python from datetime import datetime from dateutil import tz import jquantsapi -my_mail_address:str = "*****" -my_password: str = "*****" -cli = jquantsapi.Client(mail_address=my_mail_address, password=my_password) -df = cli.get_price_range( +my_api_key: str = "*****" +cli = jquantsapi.ClientV2(api_key=my_api_key) +df = cli.get_eq_bars_daily_range( start_dt=datetime(2022, 7, 25, tzinfo=tz.gettz("Asia/Tokyo")), end_dt=datetime(2022, 7, 26, tzinfo=tz.gettz("Asia/Tokyo")), ) print(df) ``` +環境変数 `JQUANTS_API_KEY` を設定している場合は、引数を省略できます。 + +```python +import jquantsapi + +cli = jquantsapi.ClientV2() # 環境変数 JQUANTS_API_KEY を使用 +``` + API レスポンスが Dataframe の形式で取得できます。 ```shell - Code Date ... AdjustmentClose AdjustmentVolume -0 13010 2022-07-25 ... 3630.0 8100.0 -1 13050 2022-07-25 ... 2023.0 54410.0 -2 13060 2022-07-25 ... 2001.0 943830.0 -3 13080 2022-07-25 ... 1977.5 121300.0 -4 13090 2022-07-25 ... 43300.0 391.0 -... ... ... ... ... ... -4189 99930 2022-07-26 ... 1426.0 5600.0 -4190 99940 2022-07-26 ... 2605.0 7300.0 -4191 99950 2022-07-26 ... 404.0 13000.0 -4192 99960 2022-07-26 ... 1255.0 4000.0 -4193 99970 2022-07-26 ... 825.0 133600.0 + Code Date ... AdjC AdjVo +0 13010 2022-07-25 ... 3630.0 8100.0 +1 13050 2022-07-25 ... 2023.0 54410.0 +2 13060 2022-07-25 ... 2001.0 943830.0 +3 13080 2022-07-25 ... 1977.5 121300.0 +4 13090 2022-07-25 ... 43300.0 391.0 +... ... ... ... ... ... +4189 99930 2022-07-26 ... 1426.0 5600.0 +4190 99940 2022-07-26 ... 2605.0 7300.0 +4191 99950 2022-07-26 ... 404.0 13000.0 +4192 99960 2022-07-26 ... 1255.0 4000.0 +4193 99970 2022-07-26 ... 825.0 133600.0 [8388 rows x 14 columns] ``` より具体的な使用例は [サンプルノートブック(/examples)](examples) をご参照ください。 -## 対応 API +## 対応 API (V2) + +`ClientV2` クラスで利用可能な V2 API エンドポイントです。 + +### ラッパー群 + +------------------ Free plan or higher is required ------------------ + +- get_eq_master - 上場銘柄一覧 +- get_eq_bars_daily - 株価日足 +- get_fin_summary - 決算サマリー +- get_eq_earnings_cal - 決算発表日 + +------------------ Light plan or higher is required ------------------ + +- get_idx_bars_daily - 指数日足 +- get_idx_bars_daily_topix - TOPIX日足 +- get_mkt_calendar - 営業日カレンダー +- get_bulk_list - バルクデータ一覧 +- get_bulk - バルクデータ取得 + +------------------ Standard plan or higher is required ------------------ + +- get_mkt_short_ratio - 空売り比率 +- get_mkt_short_sale_report - 空売り報告 +- get_mkt_margin_interest - 週次信用取引残高 +- get_mkt_margin_alert - 信用規制情報 +- get_drv_bars_daily_fut - 先物日足 +- get_drv_bars_daily_opt - オプション日足 +- get_drv_bars_daily_opt_225 - 日経225オプション日足 + +------------------ Premium plan or higher is required ------------------ + +- get_mkt_breakdown - 売買内訳 +- get_eq_bars_daily_am - 株価午前終値 +- get_eq_investor_types - 投資部門別売買状況 +- get_fin_details - 財務詳細 +- get_fin_dividend - 配当情報 + +------------------ Minute Bar Addon is required ------------------ -### ラッパー群  +- get_eq_bars_minute - 分足 +- get_eq_bars_5minute - 5分足(分足から算出) +- get_eq_bars_15minute - 15分足(分足から算出) + +### ユーティリティ群 + +業種や市場区分一覧などを返します。 -J-Quants API の各 API エンドポイントに対応しています。 +- get_market_segments - 市場区分一覧 +- get_17_sectors - 17業種一覧 +- get_33_sectors - 33業種一覧 + +日付範囲を指定して一括でデータ取得して、取得したデータを結合して返すユーティリティです。 + +------------------ Free plan or higher is required ------------------ + +- get_list - 銘柄一覧(セクター情報付き) +- get_eq_bars_daily_range - 株価日足(範囲指定) +- get_fin_summary_range - 決算サマリー(範囲指定) + +------------------ Standard plan or higher is required ------------------ + +- get_mkt_short_ratio_range - 空売り比率(範囲指定) +- get_mkt_short_sale_report_range - 空売り報告(範囲指定) +- get_mkt_margin_interest_range - 信用取引残高(範囲指定) +- get_mkt_margin_alert_range - 信用規制情報(範囲指定) +- get_drv_bars_daily_fut_range - 先物日足(範囲指定) +- get_drv_bars_daily_opt_range - オプション日足(範囲指定) +- get_drv_bars_daily_opt_225_range - 日経225オプション日足(範囲指定) + +------------------ Premium plan or higher is required ------------------ + +- get_mkt_breakdown_range - 売買内訳(範囲指定) +- get_fin_details_range - 財務詳細(範囲指定) + +## 対応 API (V1) - Deprecated + +> **⚠️ 非推奨**: `Client` クラス (V1) は非推奨となりました。今後は `ClientV2` をご利用ください。 +> V1 API は将来のバージョンで削除される予定です。 + +
+V1 API 一覧(クリックで展開) + +### ラッパー群 ------------------ Free plan or higher is required ------------------ @@ -102,14 +191,10 @@ J-Quants API の各 API エンドポイントに対応しています。 ### ユーティリティ群 -業種や市場区分一覧などを返します。 - - get_market_segments - get_17_sectors - get_33_sectors -日付範囲を指定して一括でデータ取得して、取得したデータを結合して返すようなユーティリティが用意されています。 - ------------------ Free plan or higher is required ------------------ - get_list @@ -132,8 +217,48 @@ J-Quants API の各 API エンドポイントに対応しています。 - get_derivatives_futures_range - get_derivatives_options_range +
+ +## レートリミット + +J-Quants API には、サービスの安定稼働を目的としてレートリミット(利用頻度の制限)が設けられています。 +プランごとのレートリミットの詳細については、[公式ドキュメント](https://jpx-jquants.com/spec/rate-limits) をご参照ください。 + +### 注意事項 + +サフィックスが `_range` で終わるメソッド(例: `get_eq_bars_daily_range`、`get_fin_summary_range` など)は、指定された日付範囲に対して並列処理で繰り返し API リクエストを行います。 +そのため、広い日付範囲を指定した場合や、短時間に複数回実行した場合、レートリミットに達する可能性があります。 + +レートリミットを超過すると、API は HTTP ステータスコード `429 Too Many Requests` を返します。 +エラーが発生した場合は、一定時間待機してから再試行するか、より狭い日付範囲で分割してリクエストすることをご検討ください。 + ## 設定 +### V2 (ClientV2) + +API キーは設定ファイルおよび環境変数を使用して指定することも可能です。 +設定は下記の順に読み込まれ、設定項目が重複している場合は後に読み込まれた値で上書きされます。 + +1. `/content/drive/MyDrive/drive_ws/secret/jquants-api.toml` (Google Colab のみ) +2. `${HOME}/.jquants-api/jquants-api.toml` +3. `jquants-api.toml` +4. `os.environ["JQUANTS_API_CLIENT_CONFIG_FILE"]` +5. `${JQUANTS_API_KEY}` + +#### 設定ファイル例 + +`jquants-api.toml` は下記のように設定します。 + +```toml +[jquants-api-client] +api_key = "*****" +``` + +### V1 (Client) - Deprecated + +
+V1 設定方法(クリックで展開) + 認証用のメールアドレス/パスワードおよびリフレッシュトークンは設定ファイルおよび環境変数を使用して指定することも可能です。 設定は下記の順に読み込まれ、設定項目が重複している場合は後に読み込まれた値で上書きされます。 @@ -143,7 +268,7 @@ J-Quants API の各 API エンドポイントに対応しています。 4. `os.environ["JQUANTS_API_CLIENT_CONFIG_FILE"]` 5. `${JQUANTS_API_MAIL_ADDRESS}`, `${JQUANTS_API_PASSWORD}`, `${JQUANTS_API_REFRESH_TOKEN}` -### 設定ファイル例 +#### 設定ファイル例 `jquants-api.toml` は下記のように設定します。 @@ -154,9 +279,11 @@ password = "*****" refresh_token = "*****" ``` +
+ ## 動作確認 -Google Colab および Python 3.11 で動作確認を行っています。 +Google Colab および Python 3.13 で動作確認を行っています。 J-Quants API は有償版で継続開発されているため、本ライブラリも今後仕様が変更となる可能性があります。 Python の EOL を迎えたバージョンはサポート対象外となります。 Please note we only support Python supported versions. Unsupported versions (after EOL) are not supported. diff --git a/examples/20260119-007-jquants-api-v2-starter.ipynb b/examples/20260119-007-jquants-api-v2-starter.ipynb new file mode 100644 index 0000000..27a7758 --- /dev/null +++ b/examples/20260119-007-jquants-api-v2-starter.ipynb @@ -0,0 +1,446 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# J-Quants API V2 スタートガイド\n", + "\n", + "本ノートブックでは、J-Quants API V2 の Python クライアントライブラリ `jquants-api-client` の使用方法について解説します。\n", + "\n", + "## 本ノートブックの内容\n", + "\n", + "- J-Quants API V2 の認証方法(API キーの発行・設定)\n", + "- `ClientV2` クラスを使用したデータ取得の実行例\n", + "- 株価データの可視化\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 1. 環境構築\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# jquants-api-client のインストール\n", + "%pip install jquants-api-client\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 2. J-Quants API V2 の認証方法\n", + "\n", + "J-Quants API V2 では、API キー(x-api-key)による認証方式を採用しています。\n", + "\n", + "## 2.1 API キーの発行手順\n", + "\n", + "1. [J-Quants API サービス登録ページ](https://jpx-jquants.com/auth/signup) よりサービスへ登録します。\n", + "2. [ログインページ](https://jpx-jquants.com/auth/signin) から登録したメールアドレス及びパスワードでログインします。\n", + "3. [API キー管理ページ](https://jpx-jquants.com/dashboard/api-keys) にアクセスし、API キーを発行します。\n", + "4. 発行された API キーをコピーして安全な場所に保管してください。\n", + "\n", + "※ API キーは第三者に漏洩しないよう厳重に管理してください。\n", + "\n", + "## 2.2 API キーの設定方法\n", + "\n", + "API キーは以下のいずれかの方法で設定できます。\n", + "\n", + "### 方法 1: 引数で直接指定\n", + "\n", + "```python\n", + "cli = jquantsapi.ClientV2(api_key=\"your-api-key\")\n", + "```\n", + "\n", + "### 方法 2: 環境変数で指定\n", + "\n", + "```bash\n", + "export JQUANTS_API_KEY=\"your-api-key\"\n", + "```\n", + "\n", + "```python\n", + "cli = jquantsapi.ClientV2() # 環境変数から自動取得\n", + "```\n", + "\n", + "### 方法 3: 設定ファイル(jquants-api.toml)で指定\n", + "\n", + "`~/.jquants-api/jquants-api.toml` または `./jquants-api.toml` に以下の内容を記載します。\n", + "\n", + "```toml\n", + "[jquants-api-client]\n", + "api_key = \"your-api-key\"\n", + "```\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Google Colab を使用する場合は、Google Drive をマウントして設定ファイルを読み込むことも可能です\n", + "from google.colab import drive\n", + "drive.mount('/content/drive')\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import plotly.express as px\n", + "import plotly.graph_objects as go\n", + "from plotly.subplots import make_subplots\n", + "from datetime import datetime, timedelta\n", + "import warnings\n", + "\n", + "import jquantsapi\n", + "\n", + "# pandas データフレームの表示設定\n", + "pd.set_option(\"display.max_columns\", None)\n", + "pd.set_option(\"display.max_rows\", 40)\n", + "pd.set_option(\"display.max_colwidth\", 80)\n", + "\n", + "warnings.simplefilter('ignore')\n", + "\n", + "# ClientV2 の初期化\n", + "# 設定ファイルまたは環境変数に API キーが設定されている場合は引数不要\n", + "cli = jquantsapi.ClientV2()\n", + "\n", + "# 引数で直接指定する場合は以下のように記述します\n", + "# cli = jquantsapi.ClientV2(api_key=\"your_api_key\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 3. 上場銘柄一覧 API\n", + "\n", + "東京証券取引所に上場している銘柄の情報を取得します。\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 上場銘柄一覧を取得\n", + "df_master = cli.get_eq_master()\n", + "\n", + "# データフレームの情報を確認\n", + "df_master.info()\n", + "\n", + "print(\"\\n\")\n", + "\n", + "# データフレームを表示\n", + "df_master\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# セクター情報付きの銘柄一覧を取得\n", + "df_list = cli.get_list()\n", + "df_list\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3.1 市場区分別・業種別の銘柄数\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 市場区分別の銘柄数\n", + "df_market = df_list.groupby(\"MktNmEn\").size().to_frame(\n", + " \"Count\").reset_index().sort_values(by=\"Count\", ascending=False)\n", + "px.bar(df_market, x=\"MktNmEn\", y=\"Count\", title=\"市場区分別の銘柄数\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# ETF/REIT 等を除外した 17 業種別の銘柄数\n", + "df = df_list[df_list[\"S17\"] != \"99\"]\n", + "df_sector = df.groupby(\"S17NmEn\").size().to_frame(\n", + " \"Count\").reset_index().sort_values(by=\"Count\", ascending=False)\n", + "px.bar(df_sector, x=\"S17NmEn\", y=\"Count\", title=\"17業種別の銘柄数\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 4. 株価日足 API\n", + "\n", + "株価データを取得します。調整済み株価(株式分割・併合を考慮)と調整前の株価の両方を取得できます。\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 特定銘柄の株価を取得(例: JPX 8697)\n", + "df_price = cli.get_eq_bars_daily(code=\"86970\")\n", + "\n", + "# データフレームの情報を確認\n", + "df_price.info()\n", + "\n", + "print(\"\\n\")\n", + "\n", + "# データフレームを表示\n", + "df_price\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 日付範囲を指定して全銘柄の株価を取得\n", + "d_from = datetime.now() - timedelta(days=30)\n", + "d_to = datetime.now() - timedelta(days=1)\n", + "\n", + "df_prices = cli.get_eq_bars_daily_range(\n", + " start_dt=d_from.strftime(format=\"%Y%m%d\"),\n", + " end_dt=d_to.strftime(format=\"%Y%m%d\")\n", + ")\n", + "df_prices\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4.1 株価チャートの描画\n", + "\n", + "取得した株価データをローソク足チャートとして可視化します。\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# JPX(8697)の株価を取得\n", + "df_jpx = cli.get_eq_bars_daily(code=\"86970\")\n", + "\n", + "# グラフに第2軸を設定\n", + "fig = make_subplots(specs=[[{\"secondary_y\": True}]])\n", + "\n", + "# ローソク足を描画\n", + "fig.add_trace(\n", + " go.Candlestick(\n", + " x=df_jpx.Date,\n", + " open=df_jpx.AdjO,\n", + " high=df_jpx.AdjH,\n", + " low=df_jpx.AdjL,\n", + " close=df_jpx.AdjC,\n", + " name=\"OHLC\"\n", + " )\n", + ")\n", + "\n", + "# 25日移動平均線\n", + "fig.add_trace(\n", + " go.Scatter(\n", + " x=df_jpx.Date,\n", + " y=df_jpx.AdjC.rolling(25).mean(),\n", + " name=\"25日移動平均線\",\n", + " line=dict(color=\"#FF6B6B\")\n", + " )\n", + ")\n", + "\n", + "# 75日移動平均線\n", + "fig.add_trace(\n", + " go.Scatter(\n", + " x=df_jpx.Date,\n", + " y=df_jpx.AdjC.rolling(75).mean(),\n", + " name=\"75日移動平均線\",\n", + " line=dict(color=\"#4ECDC4\")\n", + " )\n", + ")\n", + "\n", + "# 出来高を第2軸に設定\n", + "fig.add_trace(\n", + " go.Bar(\n", + " x=df_jpx.Date,\n", + " y=df_jpx.AdjVo,\n", + " name=\"出来高\",\n", + " marker_color=\"rgba(128, 128, 128, 0.3)\"\n", + " ),\n", + " secondary_y=True\n", + ")\n", + "\n", + "fig.update_layout(\n", + " title_text=\"JPX(8697)株価チャート\",\n", + " xaxis_title=\"日付\",\n", + " yaxis_title=\"株価(円)\",\n", + " yaxis2_title=\"出来高\"\n", + ")\n", + "fig.show()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 5. 決算情報 API\n", + "\n", + "上場会社の四半期及び通期の決算短信に係る情報を取得します。\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 特定銘柄の決算情報を取得\n", + "df_fins = cli.get_fin_summary(code=\"86970\")\n", + "\n", + "# データフレームの情報を確認\n", + "df_fins.info()\n", + "\n", + "print(\"\\n\")\n", + "\n", + "# データフレームを表示\n", + "df_fins\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5.1 キャッシュを使用した範囲取得\n", + "\n", + "`get_fin_summary_range()` メソッドでは、`cache_dir` 引数を指定することで、取得したデータをローカルにキャッシュできます。\n", + "2回目以降の実行ではキャッシュが利用されるため、API 呼び出し回数を削減できます。\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from datetime import datetime, timedelta\n", + "\n", + "# キャッシュディレクトリを指定(例: ホームディレクトリ配下)\n", + "cache_dir = os.path.expanduser(\"~/.jquants/cache\")\n", + "\n", + "# 日付範囲を指定して決算情報を取得(キャッシュ有効)\n", + "d_from = datetime.now() - timedelta(days=30)\n", + "d_to = datetime.now() - timedelta(days=1)\n", + "\n", + "df_fins_range = cli.get_fin_summary_range(\n", + " start_dt=d_from.strftime(\"%Y%m%d\"),\n", + " end_dt=d_to.strftime(\"%Y%m%d\"),\n", + " cache_dir=cache_dir # キャッシュを有効化\n", + ")\n", + "\n", + "print(f\"取得件数: {len(df_fins_range)}\")\n", + "print(f\"キャッシュ保存先: {cache_dir}\")\n", + "df_fins_range.head()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 6. 指数 API(Light プラン以上)\n", + "\n", + "TOPIX 等の指数データを取得します。\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# TOPIX 指数を取得\n", + "df_topix = cli.get_idx_bars_daily_topix()\n", + "\n", + "df_topix.info()\n", + "\n", + "print(\"\\n\")\n", + "\n", + "df_topix\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# TOPIX 指数の推移をプロット\n", + "fig = px.line(\n", + " df_topix,\n", + " x=\"Date\",\n", + " y=\"C\",\n", + " title=\"TOPIX 指数の推移\"\n", + ")\n", + "fig.update_layout(\n", + " xaxis_title=\"日付\",\n", + " yaxis_title=\"TOPIX\"\n", + ")\n", + "fig.show()\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.14" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/README.md b/examples/README.md index c5aaeb4..319b61a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -6,6 +6,16 @@ jquants-api-client-python の使用例集です。データの取得方法や分 Google Colab での使用例です。実際に実行することも可能となっていますのでご活用ください。 +### V2 API (ClientV2) + +| ファイル | 内容 | Colab で開く | +| :--------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------- | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| [20260119-007-jquants-api-v2-starter.ipynb](20260119-007-jquants-api-v2-starter.ipynb) | J-Quants API V2 の使い方や ClientV2 を使った基本的なデータ取得方法を解説します。 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/J-Quants/jquants-api-client-python/blob/master/examples/20260119-007-jquants-api-v2-starter.ipynb) | + +### V1 API (Client) - Deprecated + +> **⚠️ 非推奨**: 以下のノートブックは `Client` クラス (V1) を使用しています。V1 API は非推奨となりました。新規の開発には上記の V2 API (ClientV2) をご利用ください。 + | ファイル | 内容 | Colab で開く | | :--------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------- | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | | [20220825-000-write-refresh_token.ipynb](20220825-000-write-refresh_token.ipynb) | リフレッシュトークンを Google Drive のファイルに書き込みます。 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/J-Quants/jquants-api-client-python/blob/master/examples/20220825-000-write-refresh_token.ipynb) | diff --git a/jquantsapi/__init__.py b/jquantsapi/__init__.py index 3e490ba..7b23914 100644 --- a/jquantsapi/__init__.py +++ b/jquantsapi/__init__.py @@ -2,4 +2,5 @@ __version__ = "0.0.0" from .client import Client -from .enums import MARKET_API_SECTIONS +from .client_v2 import ClientV2 +from .enums import MARKET_API_SECTIONS, BulkEndpoint diff --git a/jquantsapi/apis/__init__.py b/jquantsapi/apis/__init__.py new file mode 100644 index 0000000..5b9124f --- /dev/null +++ b/jquantsapi/apis/__init__.py @@ -0,0 +1,9 @@ +""" +API ごとの実装クラス群をまとめるパッケージ。 + +- 共通の抽象クラスは `BaseApi` +- v1 向けの実装は `jquantsapi.apis.v1` +- v2 向けの実装は `jquantsapi.apis.v2` +""" + +from .base import BaseApi # noqa: F401 diff --git a/jquantsapi/apis/base.py b/jquantsapi/apis/base.py new file mode 100644 index 0000000..ca53463 --- /dev/null +++ b/jquantsapi/apis/base.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import Any, Protocol + +import pandas as pd # type: ignore + + +class SupportsRequest(Protocol): + """ + Api クラスが利用するクライアント側の最小インタフェース。 + + - v1 の `Client` + - v2 の `ClientV2` + などがこの Protocol を満たす想定。 + """ + + JQUANTS_API_BASE: str + RAW_ENCODING: str + + def _get( + self, url: str, params: dict[str, Any] | None = None + ): # pragma: no cover - Protocol 定義のみ + ... + + +class BaseApi(ABC): + """ + 各エンドポイント単位の API 実装のための抽象クラス。 + + v1 / v2 の実装で共通の execute インタフェースを提供する。 + """ + + #: 論理名 (例: "listed_info") + name: str + #: バージョン識別子 (例: "v1", "v2") + version: str + + @abstractmethod + def execute(self, client: SupportsRequest, **params: Any) -> pd.DataFrame: + """ + 実際に API を実行し、結果を DataFrame で返す。 + + Args: + client: HTTP リクエストなどを実行するクライアント + **params: API ごとのパラメータ + """ diff --git a/jquantsapi/apis/v1/__init__.py b/jquantsapi/apis/v1/__init__.py new file mode 100644 index 0000000..447e585 --- /dev/null +++ b/jquantsapi/apis/v1/__init__.py @@ -0,0 +1,3 @@ +""" +J-Quants API v1 向けの各種 Api 実装。 +""" diff --git a/jquantsapi/apis/v1/derivatives.py b/jquantsapi/apis/v1/derivatives.py new file mode 100644 index 0000000..693513e --- /dev/null +++ b/jquantsapi/apis/v1/derivatives.py @@ -0,0 +1,146 @@ +from __future__ import annotations + +import json +from typing import Any + +import pandas as pd # type: ignore + +from jquantsapi import constants +from jquantsapi.apis.base import BaseApi, SupportsRequest + + +class DerivativesFuturesApiV1(BaseApi): + """ + v1 の先物四本値 API (`/derivatives/futures`) のラッパークラス。 + """ + + name = "derivatives_futures" + version = "v1" + + def execute( + self, + client: SupportsRequest, + *, + date_yyyymmdd: str = "", + category: str = "", + contract_flag: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + # 元の _get_derivatives_futures_raw の実装を統合 + url = f"{client.JQUANTS_API_BASE}/derivatives/futures" # type: ignore[attr-defined] + params = { + "category": category, + "date": date_yyyymmdd, + "contract_flag": contract_flag, + } + + ret = client._get(url, params) # type: ignore[attr-defined] + ret.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + j = ret.text + d: dict[str, Any] = json.loads(j) + data = d["futures"] + while "pagination_key" in d: + params["pagination_key"] = d["pagination_key"] + ret = client._get(url, params) # type: ignore[attr-defined] + ret.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + j = ret.text + d = json.loads(j) + data += d["futures"] + + df = pd.DataFrame.from_dict(data) + cols = constants.DERIVATIVES_FUTURES_COLUMNS + if len(df) == 0: + return pd.DataFrame([], columns=cols) + df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d") + df.sort_values(["Code"], inplace=True) + return df[cols] + + +class DerivativesOptionsApiV1(BaseApi): + """ + v1 のオプション四本値 API (`/derivatives/options`) のラッパークラス。 + """ + + name = "derivatives_options" + version = "v1" + + def execute( + self, + client: SupportsRequest, + *, + date_yyyymmdd: str = "", + category: str = "", + contract_flag: str = "", + code: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + # 元の _get_derivatives_options_raw の実装を統合 + url = f"{client.JQUANTS_API_BASE}/derivatives/options" # type: ignore[attr-defined] + params = { + "category": category, + "date": date_yyyymmdd, + "contract_flag": contract_flag, + "code": code, + } + + ret = client._get(url, params) # type: ignore[attr-defined] + ret.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + j = ret.text + d: dict[str, Any] = json.loads(j) + data = d["options"] + while "pagination_key" in d: + params["pagination_key"] = d["pagination_key"] + ret = client._get(url, params) # type: ignore[attr-defined] + ret.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + j = ret.text + d = json.loads(j) + data += d["options"] + + df = pd.DataFrame.from_dict(data) + cols = constants.DERIVATIVES_OPTIONS_COLUMNS + if len(df) == 0: + return pd.DataFrame([], columns=cols) + df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d") + df.sort_values(["Code"], inplace=True) + return df[cols] + + +class OptionIndexOptionApiV1(BaseApi): + """ + v1 のオプション指数四本値 API (`/option/index_option`) のラッパークラス。 + """ + + name = "option_index_option" + version = "v1" + + def execute( + self, + client: SupportsRequest, + *, + date_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + # 元の _get_option_index_option_raw の実装を統合 + url = f"{client.JQUANTS_API_BASE}/option/index_option" # type: ignore[attr-defined] + params = {"date": date_yyyymmdd} + + ret = client._get(url, params) # type: ignore[attr-defined] + ret.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + j = ret.text + d: dict[str, Any] = json.loads(j) + data = d["index_option"] + while "pagination_key" in d: + params["pagination_key"] = d["pagination_key"] + ret = client._get(url, params) # type: ignore[attr-defined] + ret.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + j = ret.text + d = json.loads(j) + data += d["index_option"] + + df = pd.DataFrame.from_dict(data) + cols = constants.OPTION_INDEX_OPTION_COLUMNS + if len(df) == 0: + return pd.DataFrame([], columns=cols) + df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d") + df.sort_values(["Code"], inplace=True) + return df[cols] diff --git a/jquantsapi/apis/v1/fins.py b/jquantsapi/apis/v1/fins.py new file mode 100644 index 0000000..4e43e7f --- /dev/null +++ b/jquantsapi/apis/v1/fins.py @@ -0,0 +1,227 @@ +from __future__ import annotations + +import json +from typing import Any + +import pandas as pd # type: ignore + +from jquantsapi import constants +from jquantsapi.apis.base import BaseApi, SupportsRequest + + +class FinsStatementsApiV1(BaseApi): + """ + v1 の財務情報 API (`/fins/statements`) のラッパークラス。 + """ + + name = "fins_statements" + version = "v1" + + def execute( + self, + client: SupportsRequest, + *, + code: str = "", + date_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + `/fins/statements` を実行し、財務情報を DataFrame で返す。 + """ + url = f"{client.JQUANTS_API_BASE}/fins/statements" # type: ignore[attr-defined] + params: dict[str, Any] = {"code": code, "date": date_yyyymmdd} + + data: list[dict[str, Any]] = [] + pagination_key: str = "" + while True: + req_params = dict(params) + if pagination_key != "": + req_params["pagination_key"] = pagination_key + + resp = client._get(url, req_params) # type: ignore[arg-type] + resp.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + d: dict[str, Any] = json.loads(resp.text) + page = d.get("statements", []) + if isinstance(page, list): + data.extend(page) + + pagination_key = d.get("pagination_key", "") + if not pagination_key: + break + df = pd.DataFrame.from_dict(data) + cols = constants.FINS_STATEMENTS_COLUMNS + if len(df) == 0: + return pd.DataFrame([], columns=cols) + df["DisclosedDate"] = pd.to_datetime(df["DisclosedDate"], format="%Y-%m-%d") + df["CurrentPeriodStartDate"] = pd.to_datetime( + df["CurrentPeriodStartDate"], format="%Y-%m-%d" + ) + df["CurrentPeriodEndDate"] = pd.to_datetime( + df["CurrentPeriodEndDate"], format="%Y-%m-%d" + ) + df["CurrentFiscalYearStartDate"] = pd.to_datetime( + df["CurrentFiscalYearStartDate"], format="%Y-%m-%d" + ) + df["CurrentFiscalYearEndDate"] = pd.to_datetime( + df["CurrentFiscalYearEndDate"], format="%Y-%m-%d" + ) + df["NextFiscalYearStartDate"] = pd.to_datetime( + df["NextFiscalYearStartDate"], format="%Y-%m-%d" + ) + df["NextFiscalYearEndDate"] = pd.to_datetime( + df["NextFiscalYearEndDate"], format="%Y-%m-%d" + ) + df.sort_values(["DisclosedDate", "DisclosedTime", "LocalCode"], inplace=True) + return df[cols] + + +class FinsFsDetailsApiV1(BaseApi): + """ + v1 の財務諸表(BS/PL) API (`/fins/fs_details`) のラッパークラス。 + """ + + name = "fins_fs_details" + version = "v1" + + def execute( + self, + client: SupportsRequest, + *, + code: str = "", + date_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + `/fins/fs_details` を実行し、財務諸表(BS/PL)を DataFrame で返す。 + """ + url = f"{client.JQUANTS_API_BASE}/fins/fs_details" # type: ignore[attr-defined] + params: dict[str, Any] = {"code": code, "date": date_yyyymmdd} + + data: list[dict[str, Any]] = [] + pagination_key: str = "" + while True: + req_params = dict(params) + if pagination_key != "": + req_params["pagination_key"] = pagination_key + + resp = client._get(url, req_params) # type: ignore[arg-type] + resp.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + d: dict[str, Any] = json.loads(resp.text) + page = d.get("fs_details", []) + if isinstance(page, list): + data.extend(page) + + pagination_key = d.get("pagination_key", "") + if not pagination_key: + break + df = pd.json_normalize(data=data) + cols = constants.FINS_FS_DETAILS_COLUMNS + if len(df) == 0: + return pd.DataFrame([], columns=cols) + df["DisclosedDate"] = pd.to_datetime(df["DisclosedDate"], format="%Y-%m-%d") + df.sort_values(["DisclosedDate", "DisclosedTime", "LocalCode"], inplace=True) + return df + + +class FinsDividendApiV1(BaseApi): + """ + v1 の配当金情報 API (`/fins/dividend`) のラッパークラス。 + """ + + name = "fins_dividend" + version = "v1" + + def execute( + self, + client: SupportsRequest, + *, + code: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + date_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + `/fins/dividend` を実行し、配当金情報を DataFrame で返す。 + """ + url = f"{client.JQUANTS_API_BASE}/fins/dividend" # type: ignore[attr-defined] + params: dict[str, Any] = {"code": code} + if date_yyyymmdd != "": + params["date"] = date_yyyymmdd + else: + if from_yyyymmdd != "": + params["from"] = from_yyyymmdd + if to_yyyymmdd != "": + params["to"] = to_yyyymmdd + + data: list[dict[str, Any]] = [] + pagination_key: str = "" + while True: + req_params = dict(params) + if pagination_key != "": + req_params["pagination_key"] = pagination_key + + resp = client._get(url, req_params) # type: ignore[arg-type] + resp.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + d: dict[str, Any] = json.loads(resp.text) + page = d.get("dividend", []) + if isinstance(page, list): + data.extend(page) + + pagination_key = d.get("pagination_key", "") + if not pagination_key: + break + df = pd.DataFrame.from_dict(data) + cols = constants.FINS_DIVIDEND_COLUMNS + if len(df) == 0: + return pd.DataFrame([], columns=cols) + df["AnnouncementDate"] = pd.to_datetime( + df["AnnouncementDate"], format="%Y-%m-%d" + ) + df.sort_values(["Code"], inplace=True) + return df[cols] + + +class FinsAnnouncementApiV1(BaseApi): + """ + v1 の決算発表予定 API (`/fins/announcement`) のラッパークラス。 + """ + + name = "fins_announcement" + version = "v1" + + def execute( + self, + client: SupportsRequest, + **kwargs: Any, + ) -> pd.DataFrame: + """ + `/fins/announcement` を実行し、決算発表予定データを DataFrame で返す。 + """ + url = f"{client.JQUANTS_API_BASE}/fins/announcement" # type: ignore[attr-defined] + params: dict[str, Any] = {} + + data: list[dict[str, Any]] = [] + pagination_key: str = "" + while True: + req_params = dict(params) + if pagination_key != "": + req_params["pagination_key"] = pagination_key + + resp = client._get(url, req_params) # type: ignore[arg-type] + resp.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + d: dict[str, Any] = json.loads(resp.text) + page = d.get("announcement", []) + if isinstance(page, list): + data.extend(page) + + pagination_key = d.get("pagination_key", "") + if not pagination_key: + break + df = pd.DataFrame.from_dict(data) + cols = constants.FINS_ANNOUNCEMENT_COLUMNS + if len(df) == 0: + return pd.DataFrame([], columns=cols) + df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d") + df.sort_values(["Date", "Code"], inplace=True) + return df[cols] diff --git a/jquantsapi/apis/v1/indices.py b/jquantsapi/apis/v1/indices.py new file mode 100644 index 0000000..66fe01e --- /dev/null +++ b/jquantsapi/apis/v1/indices.py @@ -0,0 +1,106 @@ +from __future__ import annotations + +import json +from typing import Any + +import pandas as pd # type: ignore + +from jquantsapi import constants +from jquantsapi.apis.base import BaseApi, SupportsRequest + + +class IndicesApiV1(BaseApi): + """ + v1 の指数四本値 API (`/indices`) のラッパークラス。 + """ + + name = "indices" + version = "v1" + + def execute( + self, + client: SupportsRequest, + *, + code: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + date_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + # 元の _get_indices_raw の実装を統合 + url = f"{client.JQUANTS_API_BASE}/indices" # type: ignore[attr-defined] + params = {"code": code} + if date_yyyymmdd != "": + params["date"] = date_yyyymmdd + else: + if from_yyyymmdd != "": + params["from"] = from_yyyymmdd + if to_yyyymmdd != "": + params["to"] = to_yyyymmdd + + ret = client._get(url, params) # type: ignore[attr-defined] + ret.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + j = ret.text + d: dict[str, Any] = json.loads(j) + data = d["indices"] + while "pagination_key" in d: + params["pagination_key"] = d["pagination_key"] + ret = client._get(url, params) # type: ignore[attr-defined] + ret.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + j = ret.text + d = json.loads(j) + data += d["indices"] + + df = pd.DataFrame.from_dict(data) + cols = constants.INDICES_COLUMNS + if len(df) == 0: + return pd.DataFrame([], columns=cols) + df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d") + df.sort_values(["Code", "Date"], inplace=True) + return df[cols] + + +class IndicesTopixApiV1(BaseApi): + """ + v1 の TOPIX 指数四本値 API (`/indices/topix`) のラッパークラス。 + """ + + name = "indices_topix" + version = "v1" + + def execute( + self, + client: SupportsRequest, + *, + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + # 元の _get_indices_topix_raw の実装を統合 + url = f"{client.JQUANTS_API_BASE}/indices/topix" # type: ignore[attr-defined] + params = {} + if from_yyyymmdd != "": + params["from"] = from_yyyymmdd + if to_yyyymmdd != "": + params["to"] = to_yyyymmdd + + ret = client._get(url, params) # type: ignore[attr-defined] + ret.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + j = ret.text + d: dict[str, Any] = json.loads(j) + data = d["topix"] + while "pagination_key" in d: + params["pagination_key"] = d["pagination_key"] + ret = client._get(url, params) # type: ignore[attr-defined] + ret.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + j = ret.text + d = json.loads(j) + data += d["topix"] + + df = pd.DataFrame.from_dict(data) + cols = constants.INDICES_TOPIX_COLUMNS + if len(df) == 0: + return pd.DataFrame([], columns=cols) + df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d") + df.sort_values(["Date"], inplace=True) + return df[cols] diff --git a/jquantsapi/apis/v1/listed.py b/jquantsapi/apis/v1/listed.py new file mode 100644 index 0000000..513e839 --- /dev/null +++ b/jquantsapi/apis/v1/listed.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +import json +from typing import Any + +import pandas as pd # type: ignore + +from jquantsapi import constants +from jquantsapi.apis.base import BaseApi, SupportsRequest + + +class ListedInfoApiV1(BaseApi): + """ + v1 の上場銘柄一覧 API (`/listed/info`) のラッパークラス。 + """ + + name = "listed_info" + version = "v1" + + def execute( + self, + client: SupportsRequest, + *, + code: str = "", + date_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + v1 `/listed/info` を実行し、上場銘柄情報を DataFrame で返す。 + + Args: + client: v1 `Client` インスタンスを想定 + code: 銘柄コード (任意) + date_yyyymmdd: 基準日 (YYYYMMDD or YYYY-MM-DD, 任意) + """ + url = f"{client.JQUANTS_API_BASE}/listed/info" + + # ページングしながら全件取得 + all_info: list[dict[str, Any]] = [] + base_params: dict[str, Any] = {} + if code != "": + base_params["code"] = code + if date_yyyymmdd != "": + base_params["date"] = date_yyyymmdd + + pagination_key = "" + while True: + params = dict(base_params) + if pagination_key != "": + params["pagination_key"] = pagination_key + + resp = client._get(url, params) # type: ignore[arg-type] + resp.encoding = client.RAW_ENCODING + payload = json.loads(resp.text) + + data = payload.get("info", []) + if isinstance(data, list): + all_info.extend(data) + + pagination_key = payload.get("pagination_key", "") + if not pagination_key: + break + + df = pd.DataFrame.from_dict(all_info) + + standard_premium_flag = "MarginCode" in df.columns + if standard_premium_flag: + cols = constants.LISTED_INFO_STANDARD_PREMIUM_COLUMNS + else: + cols = constants.LISTED_INFO_COLUMNS + if len(df) == 0: + return pd.DataFrame([], columns=cols) + df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d") + df.sort_values("Code", inplace=True) + return df[cols].reset_index(drop=True) diff --git a/jquantsapi/apis/v1/markets.py b/jquantsapi/apis/v1/markets.py new file mode 100644 index 0000000..c9093e2 --- /dev/null +++ b/jquantsapi/apis/v1/markets.py @@ -0,0 +1,391 @@ +from __future__ import annotations + +import json +from typing import Any, Union + +import pandas as pd # type: ignore + +from jquantsapi import constants, enums +from jquantsapi.apis.base import BaseApi, SupportsRequest + + +class MarketsTradesSpecApiV1(BaseApi): + """ + v1 の投資部門別売買状況 API (`/markets/trades_spec`) のラッパークラス。 + """ + + name = "markets_trades_spec" + version = "v1" + + def execute( + self, + client: SupportsRequest, + *, + section: Union[str, enums.MARKET_API_SECTIONS] = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + `/markets/trades_spec` を実行し、投資部門別売買状況を DataFrame で返す。 + + Args: + client: v1 `Client` インスタンスを想定 + section: section name (e.g. "TSEPrime" or MARKET_API_SECTIONS.TSEPrime) + from_yyyymmdd: starting point of data period (e.g. 20210901 or 2021-09-01) + to_yyyymmdd: end point of data period (e.g. 20210907 or 2021-09-07) + """ + # 元の _get_markets_trades_spec_raw の実装を統合 + url = f"{client.JQUANTS_API_BASE}/markets/trades_spec" # type: ignore[attr-defined] + params = {} + if section != "": + params["section"] = section + if from_yyyymmdd != "": + params["from"] = from_yyyymmdd + if to_yyyymmdd != "": + params["to"] = to_yyyymmdd + + ret = client._get(url, params) # type: ignore[attr-defined] + ret.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + j = ret.text + d: dict[str, Any] = json.loads(j) + data: list[dict[str, Any]] = d["trades_spec"] + while "pagination_key" in d: + params["pagination_key"] = d["pagination_key"] + ret = client._get(url, params) # type: ignore[attr-defined] + ret.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + j = ret.text + d = json.loads(j) + data += d["trades_spec"] + df = pd.DataFrame.from_dict(data) + cols = constants.MARKETS_TRADES_SPEC + if len(df) == 0: + return pd.DataFrame([], columns=cols) + df["PublishedDate"] = pd.to_datetime(df["PublishedDate"], format="%Y-%m-%d") + df["StartDate"] = pd.to_datetime(df["StartDate"], format="%Y-%m-%d") + df["EndDate"] = pd.to_datetime(df["EndDate"], format="%Y-%m-%d") + df.sort_values(["PublishedDate", "Section"], inplace=True) + return df[cols] + + +class MarketsWeeklyMarginInterestApiV1(BaseApi): + """ + v1 の信用取引週末残高 API (`/markets/weekly_margin_interest`) のラッパークラス。 + """ + + name = "markets_weekly_margin_interest" + version = "v1" + + def execute( + self, + client: SupportsRequest, + *, + code: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + date_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + `/markets/weekly_margin_interest` を実行し、信用取引週末残高を DataFrame で返す。 + """ + # 元の _get_markets_weekly_margin_interest_raw の実装を統合 + url = f"{client.JQUANTS_API_BASE}/markets/weekly_margin_interest" # type: ignore[attr-defined] + params = {"code": code} + if date_yyyymmdd != "": + params["date"] = date_yyyymmdd + else: + if from_yyyymmdd != "": + params["from"] = from_yyyymmdd + if to_yyyymmdd != "": + params["to"] = to_yyyymmdd + + ret = client._get(url, params) # type: ignore[attr-defined] + ret.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + j = ret.text + d: dict[str, Any] = json.loads(j) + data: list[dict[str, Any]] = d["weekly_margin_interest"] + while "pagination_key" in d: + params["pagination_key"] = d["pagination_key"] + ret = client._get(url, params) # type: ignore[attr-defined] + ret.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + j = ret.text + d = json.loads(j) + data += d["weekly_margin_interest"] + df = pd.DataFrame.from_dict(data) + cols = constants.MARKETS_WEEKLY_MARGIN_INTEREST + if len(df) == 0: + return pd.DataFrame([], columns=cols) + df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d") + df.sort_values(["Date", "Code"], inplace=True) + return df[cols] + + +class MarketsTradingCalendarApiV1(BaseApi): + """ + v1 の取引カレンダー API (`/markets/trading_calendar`) のラッパークラス。 + """ + + name = "markets_trading_calendar" + version = "v1" + + def execute( + self, + client: SupportsRequest, + *, + holiday_division: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + `/markets/trading_calendar` を実行し、取引カレンダーデータを DataFrame で返す。 + """ + # 元の _get_markets_trading_calendar_raw の実装を統合 + url = f"{client.JQUANTS_API_BASE}/markets/trading_calendar" # type: ignore[attr-defined] + params = {} + if holiday_division != "": + params["holidaydivision"] = holiday_division + if from_yyyymmdd != "": + params["from"] = from_yyyymmdd + if to_yyyymmdd != "": + params["to"] = to_yyyymmdd + + ret = client._get(url, params) # type: ignore[attr-defined] + ret.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + j = ret.text + d: dict[str, Any] = json.loads(j) + df = pd.DataFrame.from_dict(d["trading_calendar"]) + cols = constants.MARKETS_TRADING_CALENDAR + if len(df) == 0: + return pd.DataFrame([], columns=cols) + df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d") + df.sort_values(["Date"], inplace=True) + return df[cols] + + +class MarketsShortSellingApiV1(BaseApi): + """ + v1 の業種別空売り比率 API (`/markets/short_selling`) のラッパークラス。 + """ + + name = "markets_short_selling" + version = "v1" + + def execute( + self, + client: SupportsRequest, + *, + sector_33_code: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + date_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + `/markets/short_selling` を実行し、業種別空売り比率データを DataFrame で返す。 + """ + # 元の _get_markets_short_selling_raw の実装を統合 + url = f"{client.JQUANTS_API_BASE}/markets/short_selling" # type: ignore[attr-defined] + params = {"sector33code": sector_33_code} + if date_yyyymmdd != "": + params["date"] = date_yyyymmdd + else: + if from_yyyymmdd != "": + params["from"] = from_yyyymmdd + if to_yyyymmdd != "": + params["to"] = to_yyyymmdd + + ret = client._get(url, params) # type: ignore[attr-defined] + ret.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + j = ret.text + d: dict[str, Any] = json.loads(j) + data: list[dict[str, Any]] = d["short_selling"] + while "pagination_key" in d: + params["pagination_key"] = d["pagination_key"] + ret = client._get(url, params) # type: ignore[attr-defined] + ret.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + j = ret.text + d = json.loads(j) + data += d["short_selling"] + df = pd.DataFrame.from_dict(data) + cols = constants.MARKET_SHORT_SELLING_COLUMNS + if len(df) == 0: + return pd.DataFrame([], columns=cols) + df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d") + df.sort_values(["Date", "Sector33Code"], inplace=True) + return df[cols] + + +class MarketsBreakdownApiV1(BaseApi): + """ + v1 の売買内訳 API (`/markets/breakdown`) のラッパークラス。 + """ + + name = "markets_breakdown" + version = "v1" + + def execute( + self, + client: SupportsRequest, + *, + code: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + date_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + `/markets/breakdown` を実行し、売買内訳データを DataFrame で返す。 + """ + # 元の _get_markets_breakdown_raw の実装を統合 + url = f"{client.JQUANTS_API_BASE}/markets/breakdown" # type: ignore[attr-defined] + params = {"code": code} + if date_yyyymmdd != "": + params["date"] = date_yyyymmdd + else: + if from_yyyymmdd != "": + params["from"] = from_yyyymmdd + if to_yyyymmdd != "": + params["to"] = to_yyyymmdd + + ret = client._get(url, params) # type: ignore[attr-defined] + ret.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + j = ret.text + d: dict[str, Any] = json.loads(j) + data: list[dict[str, Any]] = d["breakdown"] + while "pagination_key" in d: + params["pagination_key"] = d["pagination_key"] + ret = client._get(url, params) # type: ignore[attr-defined] + ret.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + j = ret.text + d = json.loads(j) + data += d["breakdown"] + df = pd.DataFrame.from_dict(data) + cols = constants.MARKETS_BREAKDOWN_COLUMNS + if len(df) == 0: + return pd.DataFrame([], columns=cols) + df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d") + df.sort_values(["Code"], inplace=True) + return df[cols] + + +class MarketsShortSellingPositionsApiV1(BaseApi): + """ + v1 の空売り残高報告 API (`/markets/short_selling_positions`) のラッパークラス。 + """ + + name = "markets_short_selling_positions" + version = "v1" + + def execute( + self, + client: SupportsRequest, + *, + code: str = "", + disclosed_date: str = "", + disclosed_date_from: str = "", + disclosed_date_to: str = "", + calculated_date: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + `/markets/short_selling_positions` を実行し、空売り残高報告データを DataFrame で返す。 + """ + # 元の _get_markets_short_selling_positions_raw の実装を統合 + url = f"{client.JQUANTS_API_BASE}/markets/short_selling_positions" # type: ignore[attr-defined] + params = {} + if code != "": + params["code"] = code + if disclosed_date != "": + params["disclosed_date"] = disclosed_date + if disclosed_date_from != "": + params["disclosed_date_from"] = disclosed_date_from + if disclosed_date_to != "": + params["disclosed_date_to"] = disclosed_date_to + if calculated_date != "": + params["calculated_date"] = calculated_date + + ret = client._get(url, params) # type: ignore[attr-defined] + ret.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + j = ret.text + d: dict[str, Any] = json.loads(j) + data: list[dict[str, Any]] = d["short_selling_positions"] + while "pagination_key" in d: + params["pagination_key"] = d["pagination_key"] + ret = client._get(url, params) # type: ignore[attr-defined] + ret.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + j = ret.text + d = json.loads(j) + data += d["short_selling_positions"] + df = pd.DataFrame.from_dict(data) + cols = constants.SHORT_SELLING_POSITIONS_COLUMNS + if len(df) == 0: + return pd.DataFrame([], columns=cols) + df["DisclosedDate"] = pd.to_datetime( + df["DisclosedDate"], format="%Y-%m-%d", errors="coerce" + ) + df["CalculatedDate"] = pd.to_datetime( + df["CalculatedDate"], format="%Y-%m-%d", errors="coerce" + ) + df["CalculationInPreviousReportingDate"] = pd.to_datetime( + df["CalculationInPreviousReportingDate"], format="%Y-%m-%d", errors="coerce" + ) + df.sort_values(["DisclosedDate", "CalculatedDate", "Code"], inplace=True) + return df[cols] + + +class MarketsDailyMarginInterestApiV1(BaseApi): + """ + v1 の日々公表信用取引残高 API (`/markets/daily_margin_interest`) のラッパークラス。 + """ + + name = "markets_daily_margin_interest" + version = "v1" + + def execute( + self, + client: SupportsRequest, + *, + code: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + date_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + `/markets/daily_margin_interest` を実行し、日々公表信用取引残高を DataFrame で返す。 + """ + # 元の _get_markets_daily_margin_interest_raw の実装を統合 + url = f"{client.JQUANTS_API_BASE}/markets/daily_margin_interest" # type: ignore[attr-defined] + params = {} + if code != "": + params["code"] = code + if date_yyyymmdd != "": + params["date"] = date_yyyymmdd + else: + if from_yyyymmdd != "": + params["from"] = from_yyyymmdd + if to_yyyymmdd != "": + params["to"] = to_yyyymmdd + + ret = client._get(url, params) # type: ignore[attr-defined] + ret.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + j = ret.text + d: dict[str, Any] = json.loads(j) + data: list[dict[str, Any]] = d["daily_margin_interest"] + while "pagination_key" in d: + params["pagination_key"] = d["pagination_key"] + ret = client._get(url, params) # type: ignore[attr-defined] + ret.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + j = ret.text + d = json.loads(j) + data += d["daily_margin_interest"] + df = pd.json_normalize(data=data) + cols = constants.DAILY_MARGIN_INTEREST_COLUMNS + if len(df) == 0: + return pd.DataFrame([], columns=cols) + df["PublishedDate"] = pd.to_datetime(df["PublishedDate"], format="%Y-%m-%d") + df["ApplicationDate"] = pd.to_datetime(df["ApplicationDate"], format="%Y-%m-%d") + df.sort_values(["Code", "PublishedDate"], inplace=True) + return df[cols] diff --git a/jquantsapi/apis/v1/prices.py b/jquantsapi/apis/v1/prices.py new file mode 100644 index 0000000..3bfe4b5 --- /dev/null +++ b/jquantsapi/apis/v1/prices.py @@ -0,0 +1,127 @@ +from __future__ import annotations + +import json +from typing import Any + +import pandas as pd # type: ignore + +from jquantsapi import constants +from jquantsapi.apis.base import BaseApi, SupportsRequest + + +class PricesDailyQuotesApiV1(BaseApi): + """ + v1 の株価四本値 API (`/prices/daily_quotes`) のラッパークラス。 + """ + + name = "prices_daily_quotes" + version = "v1" + + def execute( + self, + client: SupportsRequest, + *, + code: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + date_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + `/prices/daily_quotes` を実行し、株価情報を DataFrame で返す。 + + Args: + client: v1 `Client` インスタンスを想定 + code: 銘柄コード + from_yyyymmdd: 取得開始日 + to_yyyymmdd: 取得終了日 + date_yyyymmdd: 取得日 + """ + url = f"{client.JQUANTS_API_BASE}/prices/daily_quotes" # type: ignore[attr-defined] + params: dict[str, Any] = {"code": code} + if date_yyyymmdd != "": + params["date"] = date_yyyymmdd + else: + if from_yyyymmdd != "": + params["from"] = from_yyyymmdd + if to_yyyymmdd != "": + params["to"] = to_yyyymmdd + + data: list[dict[str, Any]] = [] + pagination_key: str = "" + while True: + req_params = dict(params) + if pagination_key != "": + req_params["pagination_key"] = pagination_key + + resp = client._get(url, req_params) # type: ignore[arg-type] + resp.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + d: dict[str, Any] = json.loads(resp.text) + page = d.get("daily_quotes", []) + if isinstance(page, list): + data.extend(page) + + pagination_key = d.get("pagination_key", "") + if not pagination_key: + break + + df = pd.DataFrame.from_dict(data) + + premium_flag = "MorningClose" in df.columns + if premium_flag: + cols = constants.PRICES_DAILY_QUOTES_PREMIUM_COLUMNS + else: + cols = constants.PRICES_DAILY_QUOTES_COLUMNS + if len(df) == 0: + return pd.DataFrame([], columns=cols) + + df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d") + df.sort_values(["Code", "Date"], inplace=True) + return df[cols] + + +class PricesPricesAmApiV1(BaseApi): + """ + v1 の前場四本値 API (`/prices/prices_am`) のラッパークラス。 + """ + + name = "prices_prices_am" + version = "v1" + + def execute( + self, + client: SupportsRequest, + *, + code: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + `/prices/prices_am` を実行し、前場四本値を DataFrame で返す。 + + Args: + client: v1 `Client` インスタンスを想定 + code: issue code (e.g. 27800 or 2780) + """ + url = f"{client.JQUANTS_API_BASE}/prices/prices_am" # type: ignore[attr-defined] + params: dict[str, Any] = {"code": code} + + resp = client._get(url, params) # type: ignore[arg-type] + resp.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + d: dict[str, Any] = json.loads(resp.text) + if d.get("message"): + return d["message"] # type: ignore[return-value] + data: list[dict[str, Any]] = d.get("prices_am", []) + while "pagination_key" in d: + req_params = dict(params) + req_params["pagination_key"] = d["pagination_key"] + resp = client._get(url, req_params) # type: ignore[arg-type] + resp.encoding = client.RAW_ENCODING # type: ignore[attr-defined] + d = json.loads(resp.text) + data += d.get("prices_am", []) + df = pd.DataFrame.from_dict(data) + cols = constants.PRICES_PRICES_AM_COLUMNS + if len(df) == 0: + return pd.DataFrame([], columns=cols) + df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d") + df.sort_values(["Code"], inplace=True) + return df[cols] diff --git a/jquantsapi/apis/v2/__init__.py b/jquantsapi/apis/v2/__init__.py new file mode 100644 index 0000000..99500d1 --- /dev/null +++ b/jquantsapi/apis/v2/__init__.py @@ -0,0 +1,3 @@ +""" +v2 用 API クラス +""" diff --git a/jquantsapi/apis/v2/bulk.py b/jquantsapi/apis/v2/bulk.py new file mode 100644 index 0000000..56cfdee --- /dev/null +++ b/jquantsapi/apis/v2/bulk.py @@ -0,0 +1,95 @@ +from __future__ import annotations + +from typing import Any, Union + +import pandas as pd # type: ignore + +from jquantsapi import constants +from jquantsapi.apis.base import BaseApi, SupportsRequest +from jquantsapi.enums import BulkEndpoint + + +class BulkListApiV2(BaseApi): + """ + v2 の Bulk List API (`/bulk/list`) のラッパークラス。 + + 指定したエンドポイントで取得可能なデータ一覧を取得します。 + """ + + name = "bulk_list" + version = "v2" + + def execute( + self, + client: SupportsRequest, + *, + endpoint: Union[str, BulkEndpoint] = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + v2 `/bulk/list` を実行し、取得可能なデータ一覧を DataFrame で返す。 + + Args: + client: v2 `ClientV2` インスタンスを想定 + endpoint: 取得したいデータのエンドポイント (例: "/equities/master") + """ + url = f"{client.JQUANTS_API_BASE}/bulk/list" + + # BulkEndpointの場合はvalue(str)を取得 + endpoint_str = ( + endpoint.value if isinstance(endpoint, BulkEndpoint) else endpoint + ) + + params: dict[str, Any] = {"endpoint": endpoint_str} + + resp = client._get(url, params) # type: ignore[arg-type] + payload = resp.json() + + data = payload.get("data", []) + cols = constants.BULK_LIST_COLUMNS_V2 + + if not data: + return pd.DataFrame(columns=cols) + + df = pd.DataFrame.from_records(data) + if "LastModified" in df.columns: + df["LastModified"] = pd.to_datetime(df["LastModified"], errors="coerce") + + return df[cols].reset_index(drop=True) + + +class BulkGetApiV2(BaseApi): + """ + v2 の Bulk Get API (`/bulk/get`) のラッパークラス。 + + 指定したキーのデータをダウンロードするためのURLを取得します。 + """ + + name = "bulk_get" + version = "v2" + + def execute( + self, + client: SupportsRequest, + *, + key: str = "", + **kwargs: Any, + ) -> str: + """ + v2 `/bulk/get` を実行し、ダウンロードURLを取得する。 + + Args: + client: v2 `ClientV2` インスタンスを想定 + key: BulkListで取得したKey + + Returns: + str: ダウンロードURL + """ + url = f"{client.JQUANTS_API_BASE}/bulk/get" + + params: dict[str, Any] = {"key": key} + + resp = client._get(url, params) # type: ignore[arg-type] + payload = resp.json() + + return payload.get("url", "") diff --git a/jquantsapi/apis/v2/derivatives.py b/jquantsapi/apis/v2/derivatives.py new file mode 100644 index 0000000..98f865f --- /dev/null +++ b/jquantsapi/apis/v2/derivatives.py @@ -0,0 +1,138 @@ +from __future__ import annotations + +from typing import Any + +import pandas as pd # type: ignore + +from jquantsapi.apis.base import BaseApi, SupportsRequest + + +class DrvBarsDailyFutApiV2(BaseApi): + """ + v2 の先物四本値 API (`/derivatives/bars/daily/futures`) のラッパークラス。 + + v1 `/derivatives/futures` に対応する先物四本値エンドポイント。 + """ + + name = "drv-bars-daily-fut" + version = "v2" + + def execute( + self, + client: SupportsRequest, + *, + date_yyyymmdd: str = "", + category: str = "", + contract_flag: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + params: dict[str, Any] = {"date": date_yyyymmdd} + if category: + params["category"] = category + if contract_flag: + params["contract_flag"] = contract_flag + + data = client._get_paginated( # type: ignore[attr-defined] + "/derivatives/bars/daily/futures", params=params + ) + if not data: + return pd.DataFrame() + + df = pd.DataFrame.from_records(data) + if "Date" in df.columns: + df["Date"] = pd.to_datetime(df["Date"], errors="coerce") + sort_cols: list[str] = [] + if "Code" in df.columns: + sort_cols.append("Code") + if "Date" in df.columns: + sort_cols.append("Date") + if sort_cols: + df.sort_values(sort_cols, inplace=True) + return df.reset_index(drop=True) + + +class DrvBarsDailyOptApiV2(BaseApi): + """ + v2 のオプション四本値 API (`/derivatives/bars/daily/options`) のラッパークラス。 + + v1 `/derivatives/options` に対応するオプション四本値エンドポイント。 + """ + + name = "drv-bars-daily-opt" + version = "v2" + + def execute( + self, + client: SupportsRequest, + *, + date_yyyymmdd: str = "", + category: str = "", + contract_flag: str = "", + code: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + params: dict[str, Any] = {"date": date_yyyymmdd} + if category: + params["category"] = category + if contract_flag: + params["contract_flag"] = contract_flag + if code: + params["code"] = code + + data = client._get_paginated( # type: ignore[attr-defined] + "/derivatives/bars/daily/options", + params=params, + ) + if not data: + return pd.DataFrame() + + df = pd.DataFrame.from_records(data) + if "Date" in df.columns: + df["Date"] = pd.to_datetime(df["Date"], errors="coerce") + sort_cols: list[str] = [] + if "Code" in df.columns: + sort_cols.append("Code") + if "Date" in df.columns: + sort_cols.append("Date") + if sort_cols: + df.sort_values(sort_cols, inplace=True) + return df.reset_index(drop=True) + + +class DrvBarsDailyOpt225ApiV2(BaseApi): + """ + v2 の日経225オプション四本値 API (`/derivatives/bars/daily/options/225`) のラッパークラス。 + + v1 `/option/index_option` に対応する日経225オプション四本値エンドポイント。 + """ + + name = "drv-bars-daily-opt-225" + version = "v2" + + def execute( + self, + client: SupportsRequest, + *, + date_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + params: dict[str, Any] = {"date": date_yyyymmdd} + + data = client._get_paginated( # type: ignore[attr-defined] + "/derivatives/bars/daily/options/225", + params=params, + ) + if not data: + return pd.DataFrame() + + df = pd.DataFrame.from_records(data) + if "Date" in df.columns: + df["Date"] = pd.to_datetime(df["Date"], errors="coerce") + sort_cols: list[str] = [] + if "Code" in df.columns: + sort_cols.append("Code") + if "Date" in df.columns: + sort_cols.append("Date") + if sort_cols: + df.sort_values(sort_cols, inplace=True) + return df.reset_index(drop=True) diff --git a/jquantsapi/apis/v2/equities.py b/jquantsapi/apis/v2/equities.py new file mode 100644 index 0000000..7d9417f --- /dev/null +++ b/jquantsapi/apis/v2/equities.py @@ -0,0 +1,295 @@ +from __future__ import annotations + +from typing import Any + +import pandas as pd # type: ignore + +from jquantsapi import constants +from jquantsapi.apis.base import BaseApi, SupportsRequest + + +class EqMasterApiV2(BaseApi): + """ + v2 の銘柄マスタ API (`/equities/master`) のラッパークラス。 + + 上場銘柄一覧 (v2) を取得します。 + """ + + name = "eq_master" + version = "v2" + + def execute( + self, client: Any, code: str = "", date: str = "", **kwargs: Any + ) -> pd.DataFrame: + params: dict[str, str] = {} + if code: + params["code"] = code + if date: + params["date"] = date + + # ClientV2 の _get_paginated を利用する + data: list[dict[str, Any]] = client._get_paginated( # type: ignore[attr-defined] + "/equities/master", params=params, data_key="data" + ) + cols = constants.EQ_MASTER_COLUMNS_V2 + if not data: + return pd.DataFrame(columns=cols) + + df = pd.DataFrame.from_records(data) + if "Date" in df.columns: + df["Date"] = pd.to_datetime(df["Date"], errors="coerce") + if "Code" in df.columns: + df.sort_values("Code", inplace=True) + + return df[cols].reset_index(drop=True) + + +class EqBarsDailyApiV2(BaseApi): + """ + v2 の株価四本値 API (`/equities/bars/daily`) のラッパークラス。 + """ + + name = "eq_bars_daily" + version = "v2" + + def execute( + self, + client: SupportsRequest, + *, + code: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + date_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + v2 `/equities/bars/daily` を実行し、株価四本値を DataFrame で返す。 + + Args: + client: v2 `ClientV2` インスタンスを想定 + code: 銘柄コード + from_yyyymmdd: 取得開始日 + to_yyyymmdd: 取得終了日 + date_yyyymmdd: 取得日 + """ + params: dict[str, Any] = {} + if code: + params["code"] = code + if date_yyyymmdd: + params["date"] = date_yyyymmdd + else: + if from_yyyymmdd: + params["from"] = from_yyyymmdd + if to_yyyymmdd: + params["to"] = to_yyyymmdd + + all_data = client._get_paginated( # type: ignore[attr-defined] + "/equities/bars/daily", + params=params, + ) + + if not all_data: + return pd.DataFrame() + + df = pd.DataFrame.from_records(all_data) + if "Date" in df.columns: + df["Date"] = pd.to_datetime(df["Date"], errors="coerce") + + sort_cols = [c for c in ["Code", "Date"] if c in df.columns] + if sort_cols: + df.sort_values(sort_cols, inplace=True) + + return df.reset_index(drop=True) + + +class EqBarsDailyAmApiV2(BaseApi): + """ + v2 の前場四本値 API (`/equities/bars/daily/am`) のラッパークラス。 + """ + + name = "eq_bars_daily_am" + version = "v2" + + def execute( + self, + client: SupportsRequest, + *, + code: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + v2 `/equities/bars/daily/am` を実行し、前場四本値を DataFrame で返す。 + + Args: + client: v2 `ClientV2` インスタンスを想定 + code: 銘柄コード (5桁 or 4桁)。空文字の場合は全銘柄。 + """ + params: dict[str, Any] = {} + if code: + params["code"] = code + + all_data = client._get_paginated( # type: ignore[attr-defined] + "/equities/bars/daily/am", + params=params, + ) + + if not all_data: + return pd.DataFrame() + + df = pd.DataFrame.from_records(all_data) + if "Date" in df.columns: + df["Date"] = pd.to_datetime(df["Date"], errors="coerce") + if "Code" in df.columns: + df.sort_values(["Code", "Date"], inplace=True) + + # v1 `/prices/prices_am` と同様に、前場四本値に対応する列のみを返す + cols = constants.PRICES_PRICES_AM_COLUMNS_V2 + return df[cols].reset_index(drop=True) + + +class EqBarsMinuteApiV2(BaseApi): + """ + v2 の分足 API (`/equities/bars/minute`) のラッパークラス。 + """ + + name = "eq_bars_minute" + version = "v2" + + def execute( + self, + client: SupportsRequest, + *, + code: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + date_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + v2 `/equities/bars/minute` を実行し、分足を DataFrame で返す。 + + Args: + client: v2 `ClientV2` インスタンスを想定 + code: 銘柄コード (5桁 or 4桁) + from_yyyymmdd: 取得開始日 + to_yyyymmdd: 取得終了日 + date_yyyymmdd: 取得日 + """ + params: dict[str, Any] = {} + if code: + params["code"] = code + if date_yyyymmdd: + params["date"] = date_yyyymmdd + else: + if from_yyyymmdd: + params["from"] = from_yyyymmdd + if to_yyyymmdd: + params["to"] = to_yyyymmdd + + all_data = client._get_paginated( # type: ignore[attr-defined] + "/equities/bars/minute", + params=params, + ) + + cols = constants.EQ_BARS_MINUTE_COLUMNS_V2 + if not all_data: + return pd.DataFrame(columns=cols) + + df = pd.DataFrame.from_records(all_data) + if "Date" in df.columns: + df["Date"] = pd.to_datetime(df["Date"], errors="coerce") + + sort_cols = [c for c in ["Code", "Date", "Time"] if c in df.columns] + if sort_cols: + df.sort_values(sort_cols, inplace=True) + + return df[cols].reset_index(drop=True) + + +class EqInvestorTypesApiV2(BaseApi): + """ + v2 の投資部門別売買状況 API (`/equities/investor-types`) のラッパークラス。 + """ + + name = "eq_investor_types" + version = "v2" + + def execute( + self, + client: SupportsRequest, + *, + section: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + v2 `/equities/investor-types` を実行し、投資部門別売買状況を DataFrame で返す。 + + Args: + client: v2 `ClientV2` インスタンスを想定 + section: 市場区分 (例: "TSEPrime") + from_yyyymmdd: 期間開始日 + to_yyyymmdd: 期間終了日 + """ + params: dict[str, Any] = {} + if section: + params["section"] = section + if from_yyyymmdd: + params["from"] = from_yyyymmdd + if to_yyyymmdd: + params["to"] = to_yyyymmdd + + all_data = client._get_paginated( # type: ignore[attr-defined] + "/equities/investor-types", + params=params, + ) + + if not all_data: + return pd.DataFrame() + + df = pd.DataFrame.from_records(all_data) + if "PubDate" in df.columns: + df["PubDate"] = pd.to_datetime(df["PubDate"], errors="coerce") + sort_cols = [c for c in ["PubDate", "Section"] if c in df.columns] + if sort_cols: + df.sort_values(sort_cols, inplace=True) + + # v1 `/markets/trades_spec` と同様に、定義済みカラムの順序で返す + cols = constants.EQ_INVESTOR_TYPES_COLUMNS_V2 + return df[cols].reset_index(drop=True) + + +class EqEarningsCalApiV2(BaseApi): + """ + v2 の決算発表予定日 API (`/equities/earnings-calendar`) のラッパークラス。 + """ + + name = "eq_earnings_cal" + version = "v2" + + def execute( + self, + client: SupportsRequest, + **kwargs: Any, + ) -> pd.DataFrame: + """ + `/equities/earnings-calendar` を実行し、決算発表予定データを DataFrame で返す。 + """ + params: dict[str, Any] = {} + + all_data = client._get_paginated( # type: ignore[attr-defined] + "/equities/earnings-calendar", + params=params, + ) + + if not all_data: + return pd.DataFrame() + + df = pd.DataFrame.from_records(all_data) + if "Date" in df.columns: + df["Date"] = pd.to_datetime(df["Date"], errors="coerce") + sort_cols = [c for c in ["Date", "Code"] if c in df.columns] + if sort_cols: + df.sort_values(sort_cols, inplace=True) + return df.reset_index(drop=True) diff --git a/jquantsapi/apis/v2/fins.py b/jquantsapi/apis/v2/fins.py new file mode 100644 index 0000000..efa9559 --- /dev/null +++ b/jquantsapi/apis/v2/fins.py @@ -0,0 +1,153 @@ +from __future__ import annotations + +from typing import Any + +import pandas as pd # type: ignore + +from jquantsapi import constants +from jquantsapi.apis.base import BaseApi, SupportsRequest + + +class FinSummaryApiV2(BaseApi): + """ + v2 の財務情報サマリ API (`/fins/summary`) のラッパークラス。 + """ + + name = "fin_summary" + version = "v2" + + def execute( + self, + client: SupportsRequest, + *, + code: str = "", + date_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + `/fins/summary` を実行し、財務情報サマリを DataFrame で返す。 + """ + params: dict[str, Any] = {} + if code: + params["code"] = code + if date_yyyymmdd: + params["date"] = date_yyyymmdd + + all_data = client._get_paginated( # type: ignore[attr-defined] + "/fins/summary", + params=params, + ) + + if not all_data: + return pd.DataFrame() + + df = pd.DataFrame.from_records(all_data) + for col in ( + "DiscDate", + "CurPerSt", + "CurPerEn", + "CurFYSt", + "CurFYEn", + "NxtFYSt", + "NxtFYEn", + ): + if col in df.columns: + df[col] = pd.to_datetime(df[col], errors="coerce") + sort_cols = [c for c in ["DiscDate", "DiscTime", "Code"] if c in df.columns] + if sort_cols: + df.sort_values(sort_cols, inplace=True) + + # v1 `/fins/statements` と同様に、定義済みカラムの順序で返す + cols = constants.FIN_SUMMARY_COLUMNS_V2 + return df[cols].reset_index(drop=True) + + +class FinDetailsApiV2(BaseApi): + """ + v2 の財務諸表詳細 API (`/fins/details`) のラッパークラス。 + """ + + name = "fin_details" + version = "v2" + + def execute( + self, + client: SupportsRequest, + *, + code: str = "", + date_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + `/fins/details` を実行し、財務諸表詳細を DataFrame で返す。 + """ + params: dict[str, Any] = {} + if code: + params["code"] = code + if date_yyyymmdd: + params["date"] = date_yyyymmdd + + all_data = client._get_paginated( # type: ignore[attr-defined] + "/fins/details", + params=params, + ) + + if not all_data: + return pd.DataFrame() + + df = pd.DataFrame.from_records(all_data) + if "DiscDate" in df.columns: + df["DiscDate"] = pd.to_datetime(df["DiscDate"], errors="coerce") + sort_cols = [c for c in ["DiscDate", "DiscTime", "Code"] if c in df.columns] + if sort_cols: + df.sort_values(sort_cols, inplace=True) + return df.reset_index(drop=True) + + +class FinDividendApiV2(BaseApi): + """ + v2 の配当金情報 API (`/fins/dividend`) のラッパークラス。 + """ + + name = "fin_dividend" + version = "v2" + + def execute( + self, + client: SupportsRequest, + *, + code: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + date_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + `/fins/dividend` を実行し、配当金情報を DataFrame で返す。 + """ + params: dict[str, Any] = {} + if code: + params["code"] = code + if date_yyyymmdd: + params["date"] = date_yyyymmdd + else: + if from_yyyymmdd: + params["from"] = from_yyyymmdd + if to_yyyymmdd: + params["to"] = to_yyyymmdd + + all_data = client._get_paginated( # type: ignore[attr-defined] + "/fins/dividend", + params=params, + ) + + if not all_data: + return pd.DataFrame() + + df = pd.DataFrame.from_records(all_data) + if "PubDate" in df.columns: + df["PubDate"] = pd.to_datetime(df["PubDate"], errors="coerce") + sort_cols = [c for c in ["PubDate", "Code"] if c in df.columns] + if sort_cols: + df.sort_values(sort_cols, inplace=True) + return df.reset_index(drop=True) diff --git a/jquantsapi/apis/v2/indices.py b/jquantsapi/apis/v2/indices.py new file mode 100644 index 0000000..f147c06 --- /dev/null +++ b/jquantsapi/apis/v2/indices.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +from typing import Any + +import pandas as pd # type: ignore + +from jquantsapi.apis.base import BaseApi, SupportsRequest + + +class IdxBarsDailyApiV2(BaseApi): + """ + v2 の指数四本値 API (`/indices/bars/daily`) のラッパークラス。 + + v1 `/indices` に対応する指数四本値エンドポイント。 + """ + + name = "idx-bars-daily" + version = "v2" + + def execute( + self, + client: SupportsRequest, + *, + code: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + date_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + params: dict[str, Any] = {} + if code: + params["code"] = code + # v1 と同様: date があれば date 優先、なければ from/to + if date_yyyymmdd: + params["date"] = date_yyyymmdd + else: + if from_yyyymmdd: + params["from"] = from_yyyymmdd + if to_yyyymmdd: + params["to"] = to_yyyymmdd + + data = client._get_paginated("/indices/bars/daily", params=params) # type: ignore[attr-defined] + if not data: + return pd.DataFrame() + + df = pd.DataFrame.from_records(data) + if "Date" in df.columns: + df["Date"] = pd.to_datetime(df["Date"], errors="coerce") + sort_cols: list[str] = [] + if "Code" in df.columns: + sort_cols.append("Code") + sort_cols.append("Date") + df.sort_values(sort_cols, inplace=True) + return df.reset_index(drop=True) + + +class IdxBarsDailyTopixApiV2(BaseApi): + """ + v2 の TOPIX 指数四本値 API (`/indices/bars/daily/topix`) のラッパークラス。 + + v1 `/indices/topix` に対応する TOPIX 指数四本値エンドポイント。 + """ + + name = "idx-bars-daily-topix" + version = "v2" + + def execute( + self, + client: SupportsRequest, + *, + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + params: dict[str, Any] = {} + if from_yyyymmdd: + params["from"] = from_yyyymmdd + if to_yyyymmdd: + params["to"] = to_yyyymmdd + + data = client._get_paginated("/indices/bars/daily/topix", params=params) # type: ignore[attr-defined] + if not data: + return pd.DataFrame() + + df = pd.DataFrame.from_records(data) + if "Date" in df.columns: + df["Date"] = pd.to_datetime(df["Date"], errors="coerce") + df.sort_values("Date", inplace=True) + return df.reset_index(drop=True) diff --git a/jquantsapi/apis/v2/markets.py b/jquantsapi/apis/v2/markets.py new file mode 100644 index 0000000..a5bd1e4 --- /dev/null +++ b/jquantsapi/apis/v2/markets.py @@ -0,0 +1,301 @@ +from __future__ import annotations + +from typing import Any + +import pandas as pd # type: ignore + +from jquantsapi import constants +from jquantsapi.apis.base import BaseApi, SupportsRequest + + +class MktShortRatioApiV2(BaseApi): + """ + v2 の業種別空売り比率 API (`/markets/short-ratio`) のラッパークラス。 + """ + + name = "mkt_short_ratio" + version = "v2" + + def execute( + self, + client: SupportsRequest, + *, + sector_33_code: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + date_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + `/markets/short-ratio` を実行し、業種別空売り比率データを DataFrame で返す。 + """ + params: dict[str, Any] = {} + if sector_33_code: + params["s33"] = sector_33_code + if date_yyyymmdd: + params["date"] = date_yyyymmdd + else: + if from_yyyymmdd: + params["from"] = from_yyyymmdd + if to_yyyymmdd: + params["to"] = to_yyyymmdd + + all_data = client._get_paginated( # type: ignore[attr-defined] + "/markets/short-ratio", + params=params, + ) + + if not all_data: + return pd.DataFrame() + + df = pd.DataFrame.from_records(all_data) + if "Date" in df.columns: + df["Date"] = pd.to_datetime(df["Date"], errors="coerce") + sort_cols = [c for c in ["Date", "S33"] if c in df.columns] + if sort_cols: + df.sort_values(sort_cols, inplace=True) + + # v1 `/markets/short_selling` と同様に、定義済みカラムの順序で返す + cols = constants.MKT_SHORT_RATIO_COLUMNS_V2 + return df[cols].reset_index(drop=True) + + +class MktShortSaleReportApiV2(BaseApi): + """ + v2 の空売り残高報告 API (`/markets/short-sale-report`) のラッパークラス。 + """ + + name = "mkt_short_sale_report" + version = "v2" + + def execute( + self, + client: SupportsRequest, + *, + code: str = "", + disclosed_date: str = "", + disclosed_date_from: str = "", + disclosed_date_to: str = "", + calculated_date: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + `/markets/short-sale-report` を実行し、空売り残高報告データを DataFrame で返す。 + """ + params: dict[str, Any] = {} + if code: + params["code"] = code + if disclosed_date: + params["disc_date"] = disclosed_date + if disclosed_date_from: + params["disc_date_from"] = disclosed_date_from + if disclosed_date_to: + params["disc_date_to"] = disclosed_date_to + if calculated_date: + params["calc_date"] = calculated_date + + all_data = client._get_paginated( # type: ignore[attr-defined] + "/markets/short-sale-report", + params=params, + ) + + if not all_data: + return pd.DataFrame() + + df = pd.DataFrame.from_records(all_data) + for col in ("DiscDate", "CalcDate", "PrevRptDate"): + if col in df.columns: + df[col] = pd.to_datetime(df[col], errors="coerce") + sort_cols = [c for c in ["DiscDate", "CalcDate", "Code"] if c in df.columns] + if sort_cols: + df.sort_values(sort_cols, inplace=True) + return df.reset_index(drop=True) + + +class MktMarginInterestApiV2(BaseApi): + """ + v2 の信用取引週末残高 API (`/markets/margin-interest`) のラッパークラス。 + """ + + name = "mkt_margin_interest" + version = "v2" + + def execute( + self, + client: SupportsRequest, + *, + code: str = "", + date_yyyymmdd: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + `/markets/margin-interest` を実行し、信用取引週末残高を DataFrame で返す。 + """ + params: dict[str, Any] = {} + if code: + params["code"] = code + if date_yyyymmdd: + params["date"] = date_yyyymmdd + else: + if from_yyyymmdd: + params["from"] = from_yyyymmdd + if to_yyyymmdd: + params["to"] = to_yyyymmdd + + data = client._get_paginated( # type: ignore[attr-defined] + "/markets/margin-interest", + params=params, + ) + if not data: + return pd.DataFrame() + + df = pd.DataFrame.from_records(data) + if "Date" in df.columns: + df["Date"] = pd.to_datetime(df["Date"], errors="coerce") + sort_cols = [c for c in ["Date", "Code"] if c in df.columns] + if sort_cols: + df.sort_values(sort_cols, inplace=True) + return df.reset_index(drop=True) + + +class MktBreakdownApiV2(BaseApi): + """ + v2 の売買内訳 API (`/markets/breakdown`) のラッパークラス。 + """ + + name = "mkt_breakdown" + version = "v2" + + def execute( + self, + client: SupportsRequest, + *, + code: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + date_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + `/markets/breakdown` を実行し、売買内訳データを DataFrame で返す。 + """ + params: dict[str, Any] = {} + if code: + params["code"] = code + if date_yyyymmdd: + params["date"] = date_yyyymmdd + else: + if from_yyyymmdd: + params["from"] = from_yyyymmdd + if to_yyyymmdd: + params["to"] = to_yyyymmdd + + data = client._get_paginated( # type: ignore[attr-defined] + "/markets/breakdown", + params=params, + ) + if not data: + return pd.DataFrame() + + df = pd.DataFrame.from_records(data) + if "Date" in df.columns: + df["Date"] = pd.to_datetime(df["Date"], errors="coerce") + sort_cols = [c for c in ["Code", "Date"] if c in df.columns] + if sort_cols: + df.sort_values(sort_cols, inplace=True) + + # v1 `/markets/breakdown` と同様に、定義済みカラムの順序で返す + cols = constants.MKT_BREAKDOWN_COLUMNS_V2 + return df[cols].reset_index(drop=True) + + +class MktMarginAlertApiV2(BaseApi): + """ + v2 の日々公表信用取引残高 API (`/markets/margin-alert`) のラッパークラス。 + """ + + name = "mkt_margin_alert" + version = "v2" + + def execute( + self, + client: SupportsRequest, + *, + code: str = "", + date_yyyymmdd: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + `/markets/margin-alert` を実行し、日々公表信用取引残高を DataFrame で返す。 + """ + params: dict[str, Any] = {} + if code: + params["code"] = code + if date_yyyymmdd: + params["date"] = date_yyyymmdd + else: + if from_yyyymmdd: + params["from"] = from_yyyymmdd + if to_yyyymmdd: + params["to"] = to_yyyymmdd + + data = client._get_paginated( # type: ignore[attr-defined] + "/markets/margin-alert", + params=params, + ) + if not data: + return pd.DataFrame() + + df = pd.DataFrame.from_records(data) + if "Date" in df.columns: + df["Date"] = pd.to_datetime(df["Date"], errors="coerce") + sort_cols = [c for c in ["Date", "Code"] if c in df.columns] + if sort_cols: + df.sort_values(sort_cols, inplace=True) + return df.reset_index(drop=True) + + +class MktCalendarApiV2(BaseApi): + """ + v2 の取引カレンダー API (`/markets/calendar`) のラッパークラス。 + """ + + name = "markets_trading_calendar" + version = "v2" + + def execute( + self, + client: SupportsRequest, + *, + holiday_division: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + **kwargs: Any, + ) -> pd.DataFrame: + """ + `/markets/calendar` を実行し、取引カレンダーデータを DataFrame で返す。 + """ + params: dict[str, Any] = {} + if holiday_division: + params["hol_div"] = holiday_division + if from_yyyymmdd: + params["from"] = from_yyyymmdd + if to_yyyymmdd: + params["to"] = to_yyyymmdd + + data = client._get_paginated( # type: ignore[attr-defined] + "/markets/calendar", + params=params, + ) + if not data: + return pd.DataFrame() + + df = pd.DataFrame.from_records(data) + if "Date" in df.columns: + df["Date"] = pd.to_datetime(df["Date"], errors="coerce") + df.sort_values("Date", inplace=True) + return df.reset_index(drop=True) diff --git a/jquantsapi/client.py b/jquantsapi/client.py index 9f4231f..7b5ddce 100644 --- a/jquantsapi/client.py +++ b/jquantsapi/client.py @@ -1,11 +1,10 @@ -import json import os import platform import sys from concurrent.futures import ThreadPoolExecutor, as_completed from datetime import datetime from pathlib import Path -from typing import Any, List, Mapping, Optional, Union +from typing import Any, Mapping, Optional, Union import pandas as pd # type: ignore import requests @@ -20,12 +19,40 @@ from urllib3.util import Retry from jquantsapi import __version__, constants, enums +from jquantsapi.apis.v1.derivatives import ( + DerivativesFuturesApiV1, + DerivativesOptionsApiV1, + OptionIndexOptionApiV1, +) +from jquantsapi.apis.v1.fins import ( + FinsAnnouncementApiV1, + FinsDividendApiV1, + FinsFsDetailsApiV1, + FinsStatementsApiV1, +) +from jquantsapi.apis.v1.indices import IndicesApiV1, IndicesTopixApiV1 +from jquantsapi.apis.v1.listed import ListedInfoApiV1 +from jquantsapi.apis.v1.markets import ( + MarketsBreakdownApiV1, + MarketsDailyMarginInterestApiV1, + MarketsShortSellingApiV1, + MarketsShortSellingPositionsApiV1, + MarketsTradesSpecApiV1, + MarketsTradingCalendarApiV1, + MarketsWeeklyMarginInterestApiV1, +) +from jquantsapi.apis.v1.prices import PricesDailyQuotesApiV1, PricesPricesAmApiV1 if sys.version_info >= (3, 11): import tomllib else: import tomli as tomllib +if sys.version_info >= (3, 13): + from warnings import deprecated # type: ignore[attr-defined] +else: + from typing_extensions import deprecated + DatetimeLike = Union[datetime, pd.Timestamp, str] _Data = Union[str, Mapping[str, Any]] @@ -35,10 +62,16 @@ class TokenAuthRefreshBadRequestException(Exception): pass +@deprecated( + "Client (V1) is deprecated and will be removed in a future version. Please use ClientV2 instead." +) class Client: """ J-Quants API からデータを取得する ref. https://jpx.gitbook.io/j-quants-ja/ + + .. deprecated:: + This class is deprecated. Use :class:`ClientV2` instead. """ JQUANTS_API_BASE = "https://api.jquants.com/v1" @@ -85,6 +118,28 @@ def __init__( self._id_token_expire = pd.Timestamp.utcnow() self._session: Optional[requests.Session] = None + # API 実装 (v1) + self._listed_info_api = ListedInfoApiV1() + self._prices_daily_quotes_api = PricesDailyQuotesApiV1() + self._prices_prices_am_api = PricesPricesAmApiV1() + self._markets_trades_spec_api = MarketsTradesSpecApiV1() + self._markets_weekly_margin_interest_api = MarketsWeeklyMarginInterestApiV1() + self._indices_api = IndicesApiV1() + self._indices_topix_api = IndicesTopixApiV1() + self._derivatives_futures_api = DerivativesFuturesApiV1() + self._derivatives_options_api = DerivativesOptionsApiV1() + self._option_index_option_api = OptionIndexOptionApiV1() + self._markets_weekly_margin_interest_api = MarketsWeeklyMarginInterestApiV1() + self._markets_short_selling_api = MarketsShortSellingApiV1() + self._markets_breakdown_api = MarketsBreakdownApiV1() + self._markets_short_selling_positions_api = MarketsShortSellingPositionsApiV1() + self._markets_daily_margin_interest_api = MarketsDailyMarginInterestApiV1() + self._markets_trading_calendar_api = MarketsTradingCalendarApiV1() + self._fins_statements_api = FinsStatementsApiV1() + self._fins_fs_details_api = FinsFsDetailsApiV1() + self._fins_dividend_api = FinsDividendApiV1() + self._fins_announcement_api = FinsAnnouncementApiV1() + if ((self._mail_address == "") or (self._password == "")) and ( self._refresh_token == "" ): @@ -175,8 +230,8 @@ def _base_headers(self) -> dict: def _request_session( self, - status_forcelist: Optional[List[int]] = None, - allowed_methods: Optional[List[str]] = None, + status_forcelist: Optional[list[int]] = None, + allowed_methods: Optional[list[str]] = None, ) -> requests.Session: """ requests の session 取得 @@ -355,33 +410,6 @@ def get_id_token(self, refresh_token: Optional[str] = None) -> str: self._id_token_expire = pd.Timestamp.utcnow() + pd.Timedelta(23, unit="hour") return self._id_token - # /listed - def _get_listed_info_raw( - self, code: str = "", date_yyyymmdd: str = "", pagination_key: str = "" - ) -> str: - """ - Get listed companies raw API returns - - Args: - code: Issue code (Optional) - date: YYYYMMDD or YYYY-MM-DD (Optional) - pagination_key: ページングキー - - Returns: - str: listed companies raw json string - """ - url = f"{self.JQUANTS_API_BASE}/listed/info" - params = {} - if code != "": - params["code"] = code - if date_yyyymmdd != "": - params["date"] = date_yyyymmdd - if pagination_key != "": - params["pagination_key"] = pagination_key - ret = self._get(url, params) - ret.encoding = self.RAW_ENCODING - return ret.text - def get_listed_info(self, code: str = "", date_yyyymmdd: str = "") -> pd.DataFrame: """ Get listed companies @@ -393,29 +421,11 @@ def get_listed_info(self, code: str = "", date_yyyymmdd: str = "") -> pd.DataFra Returns: pd.DataFrame: listed companies (sorted by Code) """ - j = self._get_listed_info_raw(code=code, date_yyyymmdd=date_yyyymmdd) - d = json.loads(j) - data = d["info"] - while "pagination_key" in d: - j = self._get_listed_info_raw( - code=code, - date_yyyymmdd=date_yyyymmdd, - pagination_key=d["pagination_key"], - ) - d = json.loads(j) - data += d["info"] - df = pd.DataFrame.from_dict(data) - - standard_premium_flag = "MarginCode" in df.columns - if standard_premium_flag: - cols = constants.LISTED_INFO_STANDARD_PREMIUM_COLUMNS - else: - cols = constants.LISTED_INFO_COLUMNS - if len(df) == 0: - return pd.DataFrame([], columns=cols) - df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d") - df.sort_values("Code", inplace=True) - return df[cols] + return self._listed_info_api.execute( + self, + code=code, + date_yyyymmdd=date_yyyymmdd, + ) @staticmethod def get_market_segments() -> pd.DataFrame: @@ -493,45 +503,6 @@ def get_list(self, code: str = "", date_yyyymmdd: str = "") -> pd.DataFrame: df_list.sort_values("Code", inplace=True) return df_list - # /prices - def _get_prices_daily_quotes_raw( - self, - code: str = "", - from_yyyymmdd: str = "", - to_yyyymmdd: str = "", - date_yyyymmdd: str = "", - pagination_key: str = "", - ) -> str: - """ - get daily quotes raw API returns - - Args: - code: 銘柄コード - from_yyyymmdd: 取得開始日 - to_yyyymmdd: 取得終了日 - date_yyyymmdd: 取得日 - pagination_key: ページングキー - - Returns: - str: daily quotes - """ - url = f"{self.JQUANTS_API_BASE}/prices/daily_quotes" - params = { - "code": code, - } - if date_yyyymmdd != "": - params["date"] = date_yyyymmdd - else: - if from_yyyymmdd != "": - params["from"] = from_yyyymmdd - if to_yyyymmdd != "": - params["to"] = to_yyyymmdd - if pagination_key != "": - params["pagination_key"] = pagination_key - ret = self._get(url, params) - ret.encoding = self.RAW_ENCODING - return ret.text - def get_prices_daily_quotes( self, code: str = "", @@ -551,35 +522,13 @@ def get_prices_daily_quotes( Returns: pd.DataFrame: 株価情報 (Code, Date列でソートされています) """ - j = self._get_prices_daily_quotes_raw( + return self._prices_daily_quotes_api.execute( + self, code=code, from_yyyymmdd=from_yyyymmdd, to_yyyymmdd=to_yyyymmdd, date_yyyymmdd=date_yyyymmdd, ) - d = json.loads(j) - data = d["daily_quotes"] - while "pagination_key" in d: - j = self._get_prices_daily_quotes_raw( - code=code, - from_yyyymmdd=from_yyyymmdd, - to_yyyymmdd=to_yyyymmdd, - date_yyyymmdd=date_yyyymmdd, - pagination_key=d["pagination_key"], - ) - d = json.loads(j) - data += d["daily_quotes"] - df = pd.DataFrame.from_dict(data) - premium_flag = "MorningClose" in df.columns - if premium_flag: - cols = constants.PRICES_DAILY_QUOTES_PREMIUM_COLUMNS - else: - cols = constants.PRICES_DAILY_QUOTES_COLUMNS - if len(df) == 0: - return pd.DataFrame([], columns=cols) - df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d") - df.sort_values(["Code", "Date"], inplace=True) - return df[cols] def get_price_range( self, @@ -612,33 +561,6 @@ def get_price_range( buff.append(df) return pd.concat(buff).sort_values(["Code", "Date"]) - def _get_prices_prices_am_raw( - self, - code: str = "", - pagination_key: str = "", - ) -> str: - """ - get the morning session's high, low, opening, and closing prices for individual stocks raw API returns - - Args: - code: issue code (e.g. 27800 or 2780) - If a 4-character issue code is specified, only the data of common stock will be obtained - for the issue on which both common and preferred stocks are listed. - pagination_key: ページングキー - - Returns: - str: the morning session's OHLC data - """ - url = f"{self.JQUANTS_API_BASE}/prices/prices_am" - params = { - "code": code, - } - if pagination_key != "": - params["pagination_key"] = pagination_key - ret = self._get(url, params) - ret.encoding = self.RAW_ENCODING - return ret.text - def get_prices_prices_am( self, code: str = "", @@ -652,62 +574,9 @@ def get_prices_prices_am( for the issue on which both common and preferred stocks are listed. Returns: pd.DataFrame: the morning session's OHLC data """ - j = self._get_prices_prices_am_raw( - code=code, - ) - d = json.loads(j) - if d.get("message"): - return d["message"] - data = d["prices_am"] - while "pagination_key" in d: - j = self._get_prices_prices_am_raw( - code=code, - pagination_key=d["pagination_key"], - ) - d = json.loads(j) - data += d["prices_am"] - df = pd.DataFrame.from_dict(data) - cols = constants.PRICES_PRICES_AM_COLUMNS - if len(df) == 0: - return pd.DataFrame([], columns=cols) - df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d") - df.sort_values(["Code"], inplace=True) - return df[cols] + return self._prices_prices_am_api.execute(self, code=code) # /markets - def _get_markets_trades_spec_raw( - self, - section: Union[str, enums.MARKET_API_SECTIONS] = "", - from_yyyymmdd: str = "", - to_yyyymmdd: str = "", - pagination_key: str = "", - ) -> str: - """ - Weekly Trading by Type of Investors raw API returns - - Args: - section: section name (e.g. "TSEPrime" or MARKET_API_SECTIONS.TSEPrime) - from_yyyymmdd: starting point of data period (e.g. 20210901 or 2021-09-01) - to_yyyymmdd: end point of data period (e.g. 20210907 or 2021-09-07) - pagination_key: ページングキー - - Returns: - str: Weekly Trading by Type of Investors - """ - url = f"{self.JQUANTS_API_BASE}/markets/trades_spec" - params = {} - if section != "": - params["section"] = section - if from_yyyymmdd != "": - params["from"] = from_yyyymmdd - if to_yyyymmdd != "": - params["to"] = to_yyyymmdd - if pagination_key != "": - params["pagination_key"] = pagination_key - ret = self._get(url, params) - ret.encoding = self.RAW_ENCODING - return ret.text - def get_markets_trades_spec( self, section: Union[str, enums.MARKET_API_SECTIONS] = "", @@ -724,68 +593,12 @@ def get_markets_trades_spec( Returns: pd.DataFrame: Weekly Trading by Type of Investors (Sorted by "PublishedDate" and "Section" columns) """ - j = self._get_markets_trades_spec_raw( - section=section, from_yyyymmdd=from_yyyymmdd, to_yyyymmdd=to_yyyymmdd + return self._markets_trades_spec_api.execute( + self, + section=section, + from_yyyymmdd=from_yyyymmdd, + to_yyyymmdd=to_yyyymmdd, ) - d = json.loads(j) - data = d["trades_spec"] - while "pagination_key" in d: - j = self._get_markets_trades_spec_raw( - section=section, - from_yyyymmdd=from_yyyymmdd, - to_yyyymmdd=to_yyyymmdd, - pagination_key=d["pagination_key"], - ) - d = json.loads(j) - data += d["trades_spec"] - df = pd.DataFrame.from_dict(data) - cols = constants.MARKETS_TRADES_SPEC - if len(df) == 0: - return pd.DataFrame([], columns=cols) - df["PublishedDate"] = pd.to_datetime(df["PublishedDate"], format="%Y-%m-%d") - df["StartDate"] = pd.to_datetime(df["StartDate"], format="%Y-%m-%d") - df["EndDate"] = pd.to_datetime(df["EndDate"], format="%Y-%m-%d") - df.sort_values(["PublishedDate", "Section"], inplace=True) - return df[cols] - - def _get_markets_weekly_margin_interest_raw( - self, - code: str = "", - from_yyyymmdd: str = "", - to_yyyymmdd: str = "", - date_yyyymmdd: str = "", - pagination_key: str = "", - ) -> str: - """ - get weekly margin interest raw API returns - - Args: - code: issue code (e.g. 27800 or 2780) - If a 4-character issue code is specified, only the data of common stock will be obtained - for the issue on which both common and preferred stocks are listed. - from_yyyymmdd: starting point of data period (e.g. 20210901 or 2021-09-01) - to_yyyymmdd: end point of data period (e.g. 20210907 or 2021-09-07) - date_yyyymmdd: date of data (e.g. 20210907 or 2021-09-07) - pagination_key: ページングキー - Returns: - str: weekly margin interest - """ - url = f"{self.JQUANTS_API_BASE}/markets/weekly_margin_interest" - params = { - "code": code, - } - if date_yyyymmdd != "": - params["date"] = date_yyyymmdd - else: - if from_yyyymmdd != "": - params["from"] = from_yyyymmdd - if to_yyyymmdd != "": - params["to"] = to_yyyymmdd - if pagination_key != "": - params["pagination_key"] = pagination_key - ret = self._get(url, params) - ret.encoding = self.RAW_ENCODING - return ret.text def get_markets_weekly_margin_interest( self, @@ -807,31 +620,13 @@ def get_markets_weekly_margin_interest( Returns: pd.DataFrame: weekly margin interest (Sorted by "Date" and "Code" columns) """ - j = self._get_markets_weekly_margin_interest_raw( + return self._markets_weekly_margin_interest_api.execute( + self, code=code, from_yyyymmdd=from_yyyymmdd, to_yyyymmdd=to_yyyymmdd, date_yyyymmdd=date_yyyymmdd, ) - d = json.loads(j) - data = d["weekly_margin_interest"] - while "pagination_key" in d: - j = self._get_markets_weekly_margin_interest_raw( - code=code, - from_yyyymmdd=from_yyyymmdd, - to_yyyymmdd=to_yyyymmdd, - date_yyyymmdd=date_yyyymmdd, - pagination_key=d["pagination_key"], - ) - d = json.loads(j) - data += d["weekly_margin_interest"] - df = pd.DataFrame.from_dict(data) - cols = constants.MARKETS_WEEKLY_MARGIN_INTEREST - if len(df) == 0: - return pd.DataFrame([], columns=cols) - df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d") - df.sort_values(["Date", "Code"], inplace=True) - return df[cols] def get_weekly_margin_range( self, @@ -865,43 +660,6 @@ def get_weekly_margin_range( buff.append(df) return pd.concat(buff).sort_values(["Code", "Date"]) - def _get_markets_short_selling_raw( - self, - sector_33_code: str = "", - from_yyyymmdd: str = "", - to_yyyymmdd: str = "", - date_yyyymmdd: str = "", - pagination_key: str = "", - ) -> str: - """ - get daily short sale ratios and trading value by industry (sector) raw API returns - - Args: - sector_33_code: 33-sector code (e.g. 0050 or 8050) - from_yyyymmdd: starting point of data period (e.g. 20210901 or 2021-09-01) - to_yyyymmdd: end point of data period (e.g. 20210907 or 2021-09-07) - date_yyyymmdd: date of data (e.g. 20210907 or 2021-09-07) - pagination_key: ページングキー - Returns: - str: daily short sale ratios and trading value by industry - """ - url = f"{self.JQUANTS_API_BASE}/markets/short_selling" - params = { - "sector33code": sector_33_code, - } - if date_yyyymmdd != "": - params["date"] = date_yyyymmdd - else: - if from_yyyymmdd != "": - params["from"] = from_yyyymmdd - if to_yyyymmdd != "": - params["to"] = to_yyyymmdd - if pagination_key != "": - params["pagination_key"] = date_yyyymmdd - ret = self._get(url, params) - ret.encoding = self.RAW_ENCODING - return ret.text - def get_markets_short_selling( self, sector_33_code: str = "", @@ -921,33 +679,14 @@ def get_markets_short_selling( pd.DataFrame: daily short sale ratios and trading value by industry (Sorted by "Date" and "Sector33Code" columns) """ - j = self._get_markets_short_selling_raw( + return self._markets_short_selling_api.execute( + self, sector_33_code=sector_33_code, from_yyyymmdd=from_yyyymmdd, to_yyyymmdd=to_yyyymmdd, date_yyyymmdd=date_yyyymmdd, ) - d = json.loads(j) - data = d["short_selling"] - while "pagination_key" in d: - j = self._get_markets_short_selling_raw( - sector_33_code=sector_33_code, - from_yyyymmdd=from_yyyymmdd, - to_yyyymmdd=to_yyyymmdd, - date_yyyymmdd=date_yyyymmdd, - pagination_key=d["pagination_key"], - ) - d = json.loads(j) - data += d["short_selling"] - df = pd.DataFrame.from_dict(data) - cols = constants.MARKET_SHORT_SELLING_COLUMNS - if len(df) == 0: - return pd.DataFrame([], columns=cols) - df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d") - df.sort_values(["Date", "Sector33Code"], inplace=True) - return df[cols] - def get_short_selling_range( self, start_dt: DatetimeLike = "20170101", @@ -979,45 +718,6 @@ def get_short_selling_range( buff.append(df) return pd.concat(buff).sort_values(["Sector33Code", "Date"]) - def _get_markets_breakdown_raw( - self, - code: str = "", - from_yyyymmdd: str = "", - to_yyyymmdd: str = "", - date_yyyymmdd: str = "", - pagination_key: str = "", - ) -> str: - """ - get detail breakdown trading data raw API returns - - Args: - code: issue code (e.g. 27800 or 2780) - If a 4-character issue code is specified, only the data of common stock will be obtained - for the issue on which both common and preferred stocks are listed. - from_yyyymmdd: starting point of data period (e.g. 20210901 or 2021-09-01) - to_yyyymmdd: end point of data period (e.g. 20210907 or 2021-09-07) - date_yyyymmdd: date of data (e.g. 20210907 or 2021-09-07) - pagination_key: ページングキー - Returns: - str: detail breakdown trading data - """ - url = f"{self.JQUANTS_API_BASE}/markets/breakdown" - params = { - "code": code, - } - if date_yyyymmdd != "": - params["date"] = date_yyyymmdd - else: - if from_yyyymmdd != "": - params["from"] = from_yyyymmdd - if to_yyyymmdd != "": - params["to"] = to_yyyymmdd - if pagination_key != "": - params["pagination_key"] = pagination_key - ret = self._get(url, params) - ret.encoding = self.RAW_ENCODING - return ret.text - def get_markets_breakdown( self, code: str = "", @@ -1038,31 +738,13 @@ def get_markets_breakdown( Returns: pd.DataFrame: detail breakdown trading data (Sorted by "Code") """ - j = self._get_markets_breakdown_raw( + return self._markets_breakdown_api.execute( + self, code=code, from_yyyymmdd=from_yyyymmdd, to_yyyymmdd=to_yyyymmdd, date_yyyymmdd=date_yyyymmdd, ) - d = json.loads(j) - data = d["breakdown"] - while "pagination_key" in d: - j = self._get_markets_breakdown_raw( - code=code, - from_yyyymmdd=from_yyyymmdd, - to_yyyymmdd=to_yyyymmdd, - date_yyyymmdd=date_yyyymmdd, - pagination_key=d["pagination_key"], - ) - d = json.loads(j) - data += d["breakdown"] - df = pd.DataFrame.from_dict(data) - cols = constants.MARKETS_BREAKDOWN_COLUMNS - if len(df) == 0: - return pd.DataFrame([], columns=cols) - df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d") - df.sort_values(["Code"], inplace=True) - return df[cols] def get_breakdown_range( self, @@ -1097,43 +779,6 @@ def get_breakdown_range( # /indices - def _get_indices_raw( - self, - code: str = "", - from_yyyymmdd: str = "", - to_yyyymmdd: str = "", - date_yyyymmdd: str = "", - pagination_key: str = "", - ) -> str: - """ - Indices Daily OHLC raw API returns - - Args: - code: 指数コード - from_yyyymmdd: 取得開始日 - to_yyyymmdd: 取得終了日 - date_yyyymmdd: 取得日 - pagination_key: ページングキー - Returns: - str: Indices Daily OHLC - """ - url = f"{self.JQUANTS_API_BASE}/indices" - params = { - "code": code, - } - if date_yyyymmdd != "": - params["date"] = date_yyyymmdd - else: - if from_yyyymmdd != "": - params["from"] = from_yyyymmdd - if to_yyyymmdd != "": - params["to"] = to_yyyymmdd - if pagination_key != "": - params["pagination_key"] = pagination_key - ret = self._get(url, params) - ret.encoding = self.RAW_ENCODING - return ret.text - def get_indices( self, code: str = "", @@ -1152,59 +797,13 @@ def get_indices( Returns: pd.DataFrame: Indices Daily OHLC (Sorted by "Code", "Date" column) """ - j = self._get_indices_raw( + return self._indices_api.execute( + self, code=code, from_yyyymmdd=from_yyyymmdd, to_yyyymmdd=to_yyyymmdd, date_yyyymmdd=date_yyyymmdd, ) - d = json.loads(j) - data = d["indices"] - while "pagination_key" in d: - j = self._get_indices_raw( - code=code, - from_yyyymmdd=from_yyyymmdd, - to_yyyymmdd=to_yyyymmdd, - date_yyyymmdd=date_yyyymmdd, - pagination_key=d["pagination_key"], - ) - d = json.loads(j) - data += d["indices"] - df = pd.DataFrame.from_dict(data) - cols = constants.INDICES_COLUMNS - if len(df) == 0: - return pd.DataFrame([], columns=cols) - df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d") - df.sort_values(["Code", "Date"], inplace=True) - return df[cols] - - def _get_indices_topix_raw( - self, - from_yyyymmdd: str = "", - to_yyyymmdd: str = "", - pagination_key: str = "", - ) -> str: - """ - TOPIX Daily OHLC raw API returns - - Args: - from_yyyymmdd: starting point of data period (e.g. 20210901 or 2021-09-01) - to_yyyymmdd: end point of data period (e.g. 20210907 or 2021-09-07) - pagination_key: ページングキー - Returns: - str: TOPIX Daily OHLC - """ - url = f"{self.JQUANTS_API_BASE}/indices/topix" - params = {} - if from_yyyymmdd != "": - params["from"] = from_yyyymmdd - if to_yyyymmdd != "": - params["to"] = to_yyyymmdd - if pagination_key != "": - params["pagination_key"] = pagination_key - ret = self._get(url, params) - ret.encoding = self.RAW_ENCODING - return ret.text def get_indices_topix( self, @@ -1220,53 +819,11 @@ def get_indices_topix( Returns: pd.DataFrame: TOPIX Daily OHLC (Sorted by "Date" column) """ - j = self._get_indices_topix_raw( - from_yyyymmdd=from_yyyymmdd, to_yyyymmdd=to_yyyymmdd + return self._indices_topix_api.execute( + self, + from_yyyymmdd=from_yyyymmdd, + to_yyyymmdd=to_yyyymmdd, ) - d = json.loads(j) - data = d["topix"] - while "pagination_key" in d: - j = self._get_indices_topix_raw( - from_yyyymmdd=from_yyyymmdd, - to_yyyymmdd=to_yyyymmdd, - pagination_key=d["pagination_key"], - ) - d = json.loads(j) - data += d["topix"] - df = pd.DataFrame.from_dict(data) - cols = constants.INDICES_TOPIX_COLUMNS - if len(df) == 0: - return pd.DataFrame([], columns=cols) - df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d") - df.sort_values(["Date"], inplace=True) - return df[cols] - - # /fins - def _get_fins_statements_raw( - self, code: str = "", date_yyyymmdd: str = "", pagination_key: str = "" - ) -> str: - """ - get fins statements raw API return - - Args: - code: 銘柄コード - date_yyyymmdd: 日付(YYYYMMDD or YYYY-MM-DD) - pagination_key: ページングキー - - Returns: - str: fins statements - """ - url = f"{self.JQUANTS_API_BASE}/fins/statements" - params = { - "code": code, - "date": date_yyyymmdd, - } - if pagination_key != "": - params["pagination_key"] = pagination_key - ret = self._get(url, params) - ret.encoding = self.RAW_ENCODING - - return ret.text def get_fins_statements( self, code: str = "", date_yyyymmdd: str = "" @@ -1281,42 +838,11 @@ def get_fins_statements( Returns: pd.DataFrame: 財務情報 (DisclosedDate, DisclosedTime, 及びLocalCode列でソートされています) """ - j = self._get_fins_statements_raw(code=code, date_yyyymmdd=date_yyyymmdd) - d = json.loads(j) - data = d["statements"] - while "pagination_key" in d: - j = self._get_fins_statements_raw( - code=code, - date_yyyymmdd=date_yyyymmdd, - pagination_key=d["pagination_key"], - ) - d = json.loads(j) - data += d["statements"] - df = pd.DataFrame.from_dict(data) - cols = constants.FINS_STATEMENTS_COLUMNS - if len(df) == 0: - return pd.DataFrame([], columns=cols) - df["DisclosedDate"] = pd.to_datetime(df["DisclosedDate"], format="%Y-%m-%d") - df["CurrentPeriodStartDate"] = pd.to_datetime( - df["CurrentPeriodStartDate"], format="%Y-%m-%d" - ) - df["CurrentPeriodEndDate"] = pd.to_datetime( - df["CurrentPeriodEndDate"], format="%Y-%m-%d" - ) - df["CurrentFiscalYearStartDate"] = pd.to_datetime( - df["CurrentFiscalYearStartDate"], format="%Y-%m-%d" - ) - df["CurrentFiscalYearEndDate"] = pd.to_datetime( - df["CurrentFiscalYearEndDate"], format="%Y-%m-%d" - ) - df["NextFiscalYearStartDate"] = pd.to_datetime( - df["NextFiscalYearStartDate"], format="%Y-%m-%d" - ) - df["NextFiscalYearEndDate"] = pd.to_datetime( - df["NextFiscalYearEndDate"], format="%Y-%m-%d" + return self._fins_statements_api.execute( + self, + code=code, + date_yyyymmdd=date_yyyymmdd, ) - df.sort_values(["DisclosedDate", "DisclosedTime", "LocalCode"], inplace=True) - return df[cols] def get_statements_range( self, @@ -1394,32 +920,6 @@ def get_statements_range( ["DisclosedDate", "DisclosedTime", "LocalCode"] ) - def _get_fins_fs_details_raw( - self, code: str = "", date_yyyymmdd: str = "", pagination_key: str = "" - ) -> str: - """ - get fins fs_details raw API return - - Args: - code: 銘柄コード - date_yyyymmdd: 開示日(YYYYMMDD or YYYY-MM-DD) - pagination_key: ページングキー - - Returns: - str: fins fs_details - """ - url = f"{self.JQUANTS_API_BASE}/fins/fs_details" - params = { - "code": code, - "date": date_yyyymmdd, - } - if pagination_key != "": - params["pagination_key"] = pagination_key - ret = self._get(url, params) - ret.encoding = self.RAW_ENCODING - - return ret.text - def get_fins_fs_details( self, code: str = "", date_yyyymmdd: str = "" ) -> pd.DataFrame: @@ -1433,24 +933,11 @@ def get_fins_fs_details( Returns: pd.DataFrame: 財務諸表(BS/PL) (DisclosedDate, DisclosedTime, 及びLocalCode列でソートされています) """ - j = self._get_fins_fs_details_raw(code=code, date_yyyymmdd=date_yyyymmdd) - d = json.loads(j) - data = d["fs_details"] - while "pagination_key" in d: - j = self._get_fins_fs_details_raw( - code=code, - date_yyyymmdd=date_yyyymmdd, - pagination_key=d["pagination_key"], - ) - d = json.loads(j) - data += d["fs_details"] - df = pd.json_normalize(data=data) - cols = constants.FINS_FS_DETAILS_COLUMNS - if len(df) == 0: - return pd.DataFrame([], columns=cols) - df["DisclosedDate"] = pd.to_datetime(df["DisclosedDate"], format="%Y-%m-%d") - df.sort_values(["DisclosedDate", "DisclosedTime", "LocalCode"], inplace=True) - return df + return self._fins_fs_details_api.execute( + self, + code=code, + date_yyyymmdd=date_yyyymmdd, + ) def get_fs_details_range( self, @@ -1510,45 +997,6 @@ def get_fs_details_range( ["DisclosedDate", "DisclosedTime", "LocalCode"] ) - def _get_fins_dividend_raw( - self, - code: str = "", - from_yyyymmdd: str = "", - to_yyyymmdd: str = "", - date_yyyymmdd: str = "", - pagination_key: str = "", - ) -> str: - """ - get information on dividends (determined and forecast) per share of listed companies etc.. raw API returns - - Args: - code: issue code (e.g. 27800 or 2780) - If a 4-character issue code is specified, only the data of common stock will be obtained - for the issue on which both common and preferred stocks are listed. - from_yyyymmdd: starting point of data period (e.g. 20210901 or 2021-09-01) - to_yyyymmdd: end point of data period (e.g. 20210907 or 2021-09-07) - date_yyyymmdd: date of data (e.g. 20210907 or 2021-09-07) - pagination_key: ページングキー - Returns: - str: information on dividends data - """ - url = f"{self.JQUANTS_API_BASE}/fins/dividend" - params = { - "code": code, - } - if date_yyyymmdd != "": - params["date"] = date_yyyymmdd - else: - if from_yyyymmdd != "": - params["from"] = from_yyyymmdd - if to_yyyymmdd != "": - params["to"] = to_yyyymmdd - if pagination_key != "": - params["pagination_key"] = pagination_key - ret = self._get(url, params) - ret.encoding = self.RAW_ENCODING - return ret.text - def get_fins_dividend( self, code: str = "", @@ -1569,33 +1017,13 @@ def get_fins_dividend( Returns: pd.DataFrame: information on dividends data (Sorted by "Code") """ - j = self._get_fins_dividend_raw( + return self._fins_dividend_api.execute( + self, code=code, from_yyyymmdd=from_yyyymmdd, to_yyyymmdd=to_yyyymmdd, date_yyyymmdd=date_yyyymmdd, ) - d = json.loads(j) - data = d["dividend"] - while "pagination_key" in d: - j = self._get_fins_dividend_raw( - code=code, - from_yyyymmdd=from_yyyymmdd, - to_yyyymmdd=to_yyyymmdd, - date_yyyymmdd=date_yyyymmdd, - pagination_key=d["pagination_key"], - ) - d = json.loads(j) - data += d["dividend"] - df = pd.DataFrame.from_dict(data) - cols = constants.FINS_DIVIDEND_COLUMNS - if len(df) == 0: - return pd.DataFrame([], columns=cols) - df["AnnouncementDate"] = pd.to_datetime( - df["AnnouncementDate"], format="%Y-%m-%d" - ) - df.sort_values(["Code"], inplace=True) - return df[cols] def get_dividend_range( self, @@ -1630,27 +1058,6 @@ def get_dividend_range( ["AnnouncementDate", "AnnouncementTime", "Code"] ) - def _get_fins_announcement_raw( - self, - pagination_key: str = "", - ) -> str: - """ - get fin announcement raw API returns - - Args: - pagination_key: ページングキー - - Returns: - str: Schedule of financial announcement - """ - url = f"{self.JQUANTS_API_BASE}/fins/announcement" - params = {} - if pagination_key != "": - params["pagination_key"] = pagination_key - ret = self._get(url, params) - ret.encoding = self.RAW_ENCODING - return ret.text - def get_fins_announcement(self) -> pd.DataFrame: """ get fin announcement @@ -1661,45 +1068,7 @@ def get_fins_announcement(self) -> pd.DataFrame: Returns: pd.DataFrame: Schedule of financial announcement """ - j = self._get_fins_announcement_raw() - d = json.loads(j) - data = d["announcement"] - while "pagination_key" in d: - j = self._get_fins_announcement_raw(pagination_key=d["pagination_key"]) - d = json.loads(j) - data += d["announcement"] - df = pd.DataFrame.from_dict(data) - cols = constants.FINS_ANNOUNCEMENT_COLUMNS - if len(df) == 0: - return pd.DataFrame([], columns=cols) - df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d") - df.sort_values(["Date", "Code"], inplace=True) - return df[cols] - - # /option - def _get_option_index_option_raw( - self, - date_yyyymmdd, - pagination_key: str = "", - ) -> str: - """ - get information on the OHLC etc. of Nikkei 225 raw API returns - - Args: - date_yyyymmdd: date of data (e.g. 20210907 or 2021-09-07) - pagination_key: ページングキー - Returns: - str: Nikkei 225 Options' OHLC etc. - """ - url = f"{self.JQUANTS_API_BASE}/option/index_option" - params = { - "date": date_yyyymmdd, - } - if pagination_key != "": - params["pagination_key"] = pagination_key - ret = self._get(url, params) - ret.encoding = self.RAW_ENCODING - return ret.text + return self._fins_announcement_api.execute(self) def get_option_index_option( self, @@ -1714,25 +1083,10 @@ def get_option_index_option( pd.DataFrame: Nikkei 225 Options' OHLC etc. (Sorted by "Code") """ - j = self._get_option_index_option_raw( + return self._option_index_option_api.execute( + self, date_yyyymmdd=date_yyyymmdd, ) - d = json.loads(j) - data = d["index_option"] - while "pagination_key" in d: - j = self._get_option_index_option_raw( - date_yyyymmdd=date_yyyymmdd, - pagination_key=d["pagination_key"], - ) - d = json.loads(j) - data += d["index_option"] - df = pd.DataFrame.from_dict(data) - cols = constants.OPTION_INDEX_OPTION_COLUMNS - if len(df) == 0: - return pd.DataFrame([], columns=cols) - df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d") - df.sort_values(["Code"], inplace=True) - return df[cols] def get_index_option_range( self, @@ -1765,36 +1119,6 @@ def get_index_option_range( buff.append(df) return pd.concat(buff).sort_values(["Code", "Date"]) - # /trading_calendar - def _get_markets_trading_calendar_raw( - self, - holiday_division: str = "", - from_yyyymmdd: str = "", - to_yyyymmdd: str = "", - ) -> str: - """ - get trading calendar raw API returns - - Args: - holiday_division: 休日区分 - from_yyyymmdd: 取得開始日 - to_yyyymmdd: 取得終了日 - - Returns: - str: trading calendar - """ - url = f"{self.JQUANTS_API_BASE}/markets/trading_calendar" - params = {} - if holiday_division != "": - params["holidaydivision"] = holiday_division - if from_yyyymmdd != "": - params["from"] = from_yyyymmdd - if to_yyyymmdd != "": - params["to"] = to_yyyymmdd - ret = self._get(url, params) - ret.encoding = self.RAW_ENCODING - return ret.text - def get_markets_trading_calendar( self, holiday_division: str = "", @@ -1812,48 +1136,12 @@ def get_markets_trading_calendar( Returns: pd.DataFrame: 取り引きカレンダー (Date列でソートされています) """ - j = self._get_markets_trading_calendar_raw( + return self._markets_trading_calendar_api.execute( + self, holiday_division=holiday_division, from_yyyymmdd=from_yyyymmdd, to_yyyymmdd=to_yyyymmdd, ) - d = json.loads(j) - df = pd.DataFrame.from_dict(d["trading_calendar"]) - cols = constants.MARKETS_TRADING_CALENDAR - if len(df) == 0: - return pd.DataFrame([], columns=cols) - df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d") - df.sort_values(["Date"], inplace=True) - return df[cols] - - # /derivatives - def _get_derivatives_futures_raw( - self, - date_yyyymmdd, - category, - contract_flag, - pagination_key: str = "", - ) -> str: - """ - get information on the OHLC etc. of futures raw API returns - - Args: - date_yyyymmdd: date of data (e.g. 20210907 or 2021-09-07) - pagination_key: ページングキー - Returns: - str: Futures' OHLC etc. - """ - url = f"{self.JQUANTS_API_BASE}/derivatives/futures" - params = { - "category": category, - "date": date_yyyymmdd, - "contract_flag": contract_flag, - } - if pagination_key != "": - params["pagination_key"] = pagination_key - ret = self._get(url, params) - ret.encoding = self.RAW_ENCODING - return ret.text def get_derivatives_futures( self, @@ -1870,29 +1158,12 @@ def get_derivatives_futures( pd.DataFrame: Futures' OHLC etc. (Sorted by "Code") """ - j = self._get_derivatives_futures_raw( - category=category, + return self._derivatives_futures_api.execute( + self, date_yyyymmdd=date_yyyymmdd, + category=category, contract_flag=contract_flag, ) - d = json.loads(j) - data = d["futures"] - while "pagination_key" in d: - j = self._get_derivatives_futures_raw( - category=category, - date_yyyymmdd=date_yyyymmdd, - contract_flag=contract_flag, - pagination_key=d["pagination_key"], - ) - d = json.loads(j) - data += d["futures"] - df = pd.DataFrame.from_dict(data) - cols = constants.DERIVATIVES_FUTURES_COLUMNS - if len(df) == 0: - return pd.DataFrame([], columns=cols) - df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d") - df.sort_values(["Code"], inplace=True) - return df[cols] def get_derivatives_futures_range( self, @@ -1930,36 +1201,6 @@ def get_derivatives_futures_range( buff.append(df) return pd.concat(buff).sort_values(["Code", "Date"]) - def _get_derivatives_options_raw( - self, - date_yyyymmdd, - category, - contract_flag, - code, - pagination_key: str = "", - ) -> str: - """ - get information on the OHLC etc. of options raw API returns - - Args: - date_yyyymmdd: date of data (e.g. 20210907 or 2021-09-07) - pagination_key: ページングキー - Returns: - str: Options' OHLC etc. - """ - url = f"{self.JQUANTS_API_BASE}/derivatives/options" - params = { - "category": category, - "date": date_yyyymmdd, - "contract_flag": contract_flag, - "code": code, - } - if pagination_key != "": - params["pagination_key"] = pagination_key - ret = self._get(url, params) - ret.encoding = self.RAW_ENCODING - return ret.text - def get_derivatives_options( self, date_yyyymmdd: str, @@ -1976,31 +1217,13 @@ def get_derivatives_options( pd.DataFrame: Futures' OHLC etc. (Sorted by "Code") """ - j = self._get_derivatives_options_raw( - category=category, + return self._derivatives_options_api.execute( + self, date_yyyymmdd=date_yyyymmdd, + category=category, contract_flag=contract_flag, code=code, ) - d = json.loads(j) - data = d["options"] - while "pagination_key" in d: - j = self._get_derivatives_options_raw( - category=category, - date_yyyymmdd=date_yyyymmdd, - contract_flag=contract_flag, - code=code, - pagination_key=d["pagination_key"], - ) - d = json.loads(j) - data += d["options"] - df = pd.DataFrame.from_dict(data) - cols = constants.DERIVATIVES_OPTIONS_COLUMNS - if len(df) == 0: - return pd.DataFrame([], columns=cols) - df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d") - df.sort_values(["Code"], inplace=True) - return df[cols] def get_derivatives_options_range( self, @@ -2040,49 +1263,6 @@ def get_derivatives_options_range( buff.append(df) return pd.concat(buff).sort_values(["Code", "Date"]) - def _get_markets_short_selling_positions_raw( - self, - code: str = "", - disclosed_date: str = "", - disclosed_date_from: str = "", - disclosed_date_to: str = "", - calculated_date: str = "", - pagination_key: str = "", - ) -> str: - """ - get short selling positions raw API returns - - Args: - code: issue code (e.g. 27800 or 2780) - If a 4-character issue code is specified, only the data of common stock - will be obtained for the issue on which both common and preferred stocks - are listed. - disclosed_date: disclosed date (e.g. 20240301 or 2024-03-01) - disclosed_date_from: disclosed date from (e.g. 20240301 or 2024-03-01) - disclosed_date_to: disclosed date to (e.g. 20240301 or 2024-03-01) - calculated_date: calculated date (e.g. 20240301 or 2024-03-01) - pagination_key: pagination key - Returns: - str: short selling positions - """ - url = f"{self.JQUANTS_API_BASE}/markets/short_selling_positions" - params = {} - if code != "": - params["code"] = code - if disclosed_date != "": - params["disclosed_date"] = disclosed_date - if disclosed_date_from != "": - params["disclosed_date_from"] = disclosed_date_from - if disclosed_date_to != "": - params["disclosed_date_to"] = disclosed_date_to - if calculated_date != "": - params["calculated_date"] = calculated_date - if pagination_key != "": - params["pagination_key"] = pagination_key - ret = self._get(url, params) - ret.encoding = self.RAW_ENCODING - return ret.text - def get_markets_short_selling_positions( self, code: str = "", @@ -2107,41 +1287,14 @@ def get_markets_short_selling_positions( pd.DataFrame: short selling positions (Sorted by "DisclosedDate", "CalculatedDate", and "Code" columns) """ - j = self._get_markets_short_selling_positions_raw( + return self._markets_short_selling_positions_api.execute( + self, code=code, disclosed_date=disclosed_date, disclosed_date_from=disclosed_date_from, disclosed_date_to=disclosed_date_to, calculated_date=calculated_date, ) - d = json.loads(j) - data = d["short_selling_positions"] - while "pagination_key" in d: - j = self._get_markets_short_selling_positions_raw( - code=code, - disclosed_date=disclosed_date, - disclosed_date_from=disclosed_date_from, - disclosed_date_to=disclosed_date_to, - calculated_date=calculated_date, - pagination_key=d["pagination_key"], - ) - d = json.loads(j) - data += d["short_selling_positions"] - df = pd.DataFrame.from_dict(data) - cols = constants.SHORT_SELLING_POSITIONS_COLUMNS - if len(df) == 0: - return pd.DataFrame([], columns=cols) - df["DisclosedDate"] = pd.to_datetime( - df["DisclosedDate"], format="%Y-%m-%d", errors="coerce" - ) - df["CalculatedDate"] = pd.to_datetime( - df["CalculatedDate"], format="%Y-%m-%d", errors="coerce" - ) - df["CalculationInPreviousReportingDate"] = pd.to_datetime( - df["CalculationInPreviousReportingDate"], format="%Y-%m-%d", errors="coerce" - ) - df.sort_values(["DisclosedDate", "CalculatedDate", "Code"], inplace=True) - return df[cols] def get_markets_short_selling_positions_range( self, @@ -2176,47 +1329,6 @@ def get_markets_short_selling_positions_range( buff.append(df) return pd.concat(buff).sort_values(["DisclosedDate", "CalculatedDate", "Code"]) - # /markets/daily_margin_interest - def _get_markets_daily_margin_interest_raw( - self, - code: str = "", - from_yyyymmdd: str = "", - to_yyyymmdd: str = "", - date_yyyymmdd: str = "", - pagination_key: str = "", - ) -> str: - """ - get daily margin interest raw API returns - - Args: - code: issue code (e.g. 27800 or 2780) - If a 4-character issue code is specified, only the data of common stock - will be obtained for the issue on which both common and preferred stocks - are listed. - from_yyyymmdd: starting point of data period (e.g. 20210901 or 2021-09-01) - to_yyyymmdd: end point of data period (e.g. 20210907 or 2021-09-07) - date_yyyymmdd: date of data (e.g. 20210907 or 2021-09-07) - pagination_key: pagination key - Returns: - str: daily margin interest - """ - url = f"{self.JQUANTS_API_BASE}/markets/daily_margin_interest" - params = {} - if code != "": - params["code"] = code - if date_yyyymmdd != "": - params["date"] = date_yyyymmdd - else: - if from_yyyymmdd != "": - params["from"] = from_yyyymmdd - if to_yyyymmdd != "": - params["to"] = to_yyyymmdd - if pagination_key != "": - params["pagination_key"] = pagination_key - ret = self._get(url, params) - ret.encoding = self.RAW_ENCODING - return ret.text - def get_markets_daily_margin_interest( self, code: str = "", @@ -2238,32 +1350,13 @@ def get_markets_daily_margin_interest( Returns: pd.DataFrame: daily margin interest (Sorted by "Code" and "PublishedDate" columns) """ - j = self._get_markets_daily_margin_interest_raw( + return self._markets_daily_margin_interest_api.execute( + self, code=code, from_yyyymmdd=from_yyyymmdd, to_yyyymmdd=to_yyyymmdd, date_yyyymmdd=date_yyyymmdd, ) - d = json.loads(j) - data = d["daily_margin_interest"] - while "pagination_key" in d: - j = self._get_markets_daily_margin_interest_raw( - code=code, - from_yyyymmdd=from_yyyymmdd, - to_yyyymmdd=to_yyyymmdd, - date_yyyymmdd=date_yyyymmdd, - pagination_key=d["pagination_key"], - ) - d = json.loads(j) - data += d["daily_margin_interest"] - df = pd.json_normalize(data=data) - cols = constants.DAILY_MARGIN_INTEREST_COLUMNS - if len(df) == 0: - return pd.DataFrame([], columns=cols) - df["PublishedDate"] = pd.to_datetime(df["PublishedDate"], format="%Y-%m-%d") - df["ApplicationDate"] = pd.to_datetime(df["ApplicationDate"], format="%Y-%m-%d") - df.sort_values(["Code", "PublishedDate"], inplace=True) - return df[cols] def get_daily_margin_interest_range( self, diff --git a/jquantsapi/client_v2.py b/jquantsapi/client_v2.py new file mode 100644 index 0000000..5cf9348 --- /dev/null +++ b/jquantsapi/client_v2.py @@ -0,0 +1,1342 @@ +import os +import platform +import sys +from concurrent.futures import ThreadPoolExecutor, as_completed +from datetime import datetime +from pathlib import Path +from typing import Any, Optional, Union + +import pandas as pd # type: ignore +import requests +from requests.adapters import HTTPAdapter +from urllib3.util import Retry + +if sys.version_info >= (3, 11): + import tomllib +else: + import tomli as tomllib + +from jquantsapi import __version__, constants +from jquantsapi.apis.v2.bulk import BulkGetApiV2, BulkListApiV2 +from jquantsapi.apis.v2.derivatives import ( + DrvBarsDailyFutApiV2, + DrvBarsDailyOpt225ApiV2, + DrvBarsDailyOptApiV2, +) +from jquantsapi.apis.v2.equities import ( + EqBarsDailyAmApiV2, + EqBarsDailyApiV2, + EqBarsMinuteApiV2, + EqEarningsCalApiV2, + EqInvestorTypesApiV2, + EqMasterApiV2, +) +from jquantsapi.apis.v2.fins import FinDetailsApiV2, FinDividendApiV2, FinSummaryApiV2 +from jquantsapi.apis.v2.indices import IdxBarsDailyApiV2, IdxBarsDailyTopixApiV2 +from jquantsapi.apis.v2.markets import ( + MktBreakdownApiV2, + MktCalendarApiV2, + MktMarginAlertApiV2, + MktMarginInterestApiV2, + MktShortRatioApiV2, + MktShortSaleReportApiV2, +) +from jquantsapi.enums import BulkEndpoint + +DatetimeLike = Union[datetime, pd.Timestamp, str] + + +class ClientV2: + """ + J-Quants API v2 用のクライアント + + - 認証方式: x-api-key (APIキー) + - ダッシュボードで発行した API キーを `api_key` 引数、もしくは + 環境変数 `JQUANTS_API_KEY` で指定してください。 + - v1 版 `Client` と同様に、各エンドポイントの結果を pandas.DataFrame で返しますが、 + v2 ではレスポンスのフィールド名が変更されているため、列名は v1 と異なります。 + """ + + JQUANTS_API_BASE = "https://api.jquants.com/v2" + USER_AGENT = "jqapi-python-v2" + USER_AGENT_VERSION = __version__ + RAW_ENCODING = "utf-8" + MAX_WORKERS = 5 + + def __init__(self, api_key: Optional[str] = None) -> None: + """ + Args: + api_key: J-Quants API v2 の API キー + 未指定の場合は設定ファイルまたは環境変数から取得します。 + + 設定の読み込み順序(後のものが優先): + 1. /content/drive/MyDrive/drive_ws/secret/jquants-api.toml (Google Colab のみ) + 2. ${HOME}/.jquants-api/jquants-api.toml + 3. jquants-api.toml (カレントディレクトリ) + 4. os.environ["JQUANTS_API_CLIENT_CONFIG_FILE"] で指定されたファイル + 5. 環境変数 JQUANTS_API_KEY + """ + config = self._load_config() + + if api_key is not None: + self._api_key = api_key + else: + self._api_key = config.get("api_key", "") + + if not self._api_key: + raise ValueError( + "api_key is required. Set it via argument, config file, or JQUANTS_API_KEY env var." + ) + + self._session: Optional[requests.Session] = None + + # API 実装 (v2) + self._eq_master_api = EqMasterApiV2() + self._eq_bars_daily_api = EqBarsDailyApiV2() + self._eq_bars_daily_am_api = EqBarsDailyAmApiV2() + self._eq_bars_minute_api = EqBarsMinuteApiV2() + self._eq_investor_types_api = EqInvestorTypesApiV2() + self._fin_summary_api = FinSummaryApiV2() + self._fin_details_api = FinDetailsApiV2() + self._fin_dividend_api = FinDividendApiV2() + self._eq_earnings_cal_api = EqEarningsCalApiV2() + self._mkt_short_ratio_api = MktShortRatioApiV2() + self._mkt_margin_interest_api = MktMarginInterestApiV2() + self._mkt_breakdown_api = MktBreakdownApiV2() + self._mkt_calendar_api = MktCalendarApiV2() + self._mkt_short_sale_report_api = MktShortSaleReportApiV2() + self._mkt_margin_alert_api = MktMarginAlertApiV2() + self._idx_bars_daily_api = IdxBarsDailyApiV2() + self._idx_bars_daily_topix_api = IdxBarsDailyTopixApiV2() + self._drv_bars_daily_fut_api = DrvBarsDailyFutApiV2() + self._drv_bars_daily_opt_api = DrvBarsDailyOptApiV2() + self._drv_bars_daily_opt_225_api = DrvBarsDailyOpt225ApiV2() + self._bulk_list_api = BulkListApiV2() + self._bulk_get_api = BulkGetApiV2() + + # ------------------------------------------------------------------ + # 内部ユーティリティ + # ------------------------------------------------------------------ + def _is_colab(self) -> bool: + """ + Return True if running in colab + """ + return "google.colab" in sys.modules + + def _load_config(self) -> dict: + """ + load config from files and environment variables + + Args: + N/A + Returns: + dict: configurations + """ + config: dict = {} + + # colab config + if self._is_colab(): + colab_config_path = ( + "/content/drive/MyDrive/drive_ws/secret/jquants-api.toml" + ) + config = {**config, **self._read_config(colab_config_path)} + + # user default config + user_config_path = f"{Path.home()}/.jquants-api/jquants-api.toml" + config = {**config, **self._read_config(user_config_path)} + + # current dir config + current_config_path = "jquants-api.toml" + config = {**config, **self._read_config(current_config_path)} + + # env specified config + if "JQUANTS_API_CLIENT_CONFIG_FILE" in os.environ: + env_config_path = os.environ["JQUANTS_API_CLIENT_CONFIG_FILE"] + config = {**config, **self._read_config(env_config_path)} + + # env var (highest priority) + config["api_key"] = os.environ.get("JQUANTS_API_KEY", config.get("api_key", "")) + + return config + + def _read_config(self, config_path: str) -> dict: + """ + read config from a toml file + + Params: + config_path: a path to a toml file + """ + if not os.path.isfile(config_path): + return {} + + with open(config_path, mode="rb") as f: + ret = tomllib.load(f) + + if "jquants-api-client" not in ret: + return {} + + return ret["jquants-api-client"] + + def _request_session( + self, + status_forcelist: Optional[list[int]] = None, + allowed_methods: Optional[list[str]] = None, + ) -> requests.Session: + """ + requests の session を取得し、リトライ設定を行う + """ + if status_forcelist is None: + status_forcelist = [429, 500, 502, 503, 504] + if allowed_methods is None: + allowed_methods = ["HEAD", "GET", "OPTIONS"] + + if self._session is None: + retry_strategy = Retry( + total=3, + status_forcelist=status_forcelist, + allowed_methods=allowed_methods, + ) + adapter = HTTPAdapter( + pool_connections=self.MAX_WORKERS + 10, + pool_maxsize=self.MAX_WORKERS + 10, + max_retries=retry_strategy, + ) + self._session = requests.Session() + self._session.mount("https://", adapter) + + return self._session + + def _base_headers(self) -> dict[str, str]: + """ + J-Quants API v2 にアクセスする際の共通ヘッダを生成 + """ + return { + "x-api-key": self._api_key, + "User-Agent": f"{self.USER_AGENT}/{self.USER_AGENT_VERSION} " + f"p/{platform.python_version()}", + } + + def _get( + self, url: str, params: Optional[dict[str, Any]] = None + ) -> requests.Response: + """ + GET リクエスト用ラッパー + """ + session = self._request_session() + headers = self._base_headers() + resp = session.get(url, params=params, headers=headers, timeout=30) + resp.raise_for_status() + return resp + + def _get_paginated( + self, + path: str, + params: Optional[dict[str, Any]] = None, + data_key: str = "data", + ) -> list[dict[str, Any]]: + """ + pagination_key に対応した共通 GET ヘルパー + + Args: + path: ベースURLからのパス (例: \"/equities/master\") + params: クエリパラメータ + data_key: レスポンス中のデータ配列キー (デフォルト: \"data\") + Returns: + List[Dict[str, Any]]: 連結済みのデータ配列 + """ + url = f"{self.JQUANTS_API_BASE}{path}" + all_data: list[dict[str, Any]] = [] + query: dict[str, Any] = dict(params or {}) + + while True: + resp = self._get(url, params=query) + payload = resp.json() + batch = payload.get(data_key, []) + if isinstance(batch, list): + all_data.extend(batch) + + pagination_key = payload.get("pagination_key") + if not pagination_key: + break + query["pagination_key"] = pagination_key + + return all_data + + # ------------------------------------------------------------------ + # eq-master (/equities/master) + # ------------------------------------------------------------------ + def get_eq_master( + self, + code: str = "", + date: str = "", + ) -> pd.DataFrame: + """ + eq-master: 上場銘柄一覧 (v2: /equities/master) + + Args: + code: 5桁の銘柄コード (例: 27800)。4桁指定も可能。 + date: 基準日 (YYYYMMDD or YYYY-MM-DD) + Returns: + pd.DataFrame: 上場銘柄情報 + """ + return self._eq_master_api.execute(self, code=code, date=date) + + # ------------------------------------------------------------------ + # ユーティリティ: 業種・市場区分マスタ (v1 と同様のローカル定義) + # ------------------------------------------------------------------ + def get_market_segments(self) -> pd.DataFrame: + """ + 市場区分コードと名称 (V2 カラム名) + """ + df = pd.DataFrame( + constants.MARKET_SEGMENT_DATA, columns=constants.MARKET_SEGMENT_COLUMNS_V2 + ) + df.sort_values(constants.MARKET_SEGMENT_COLUMNS_V2[0], inplace=True) + return df + + def get_17_sectors(self) -> pd.DataFrame: + """ + 17 業種コードと名称 (V2 カラム名) + """ + df = pd.DataFrame( + constants.SECTOR_17_DATA, columns=constants.SECTOR_17_COLUMNS_V2 + ) + df.sort_values(constants.SECTOR_17_COLUMNS_V2[0], inplace=True) + return df + + def get_33_sectors(self) -> pd.DataFrame: + """ + 33 業種コードと名称 (V2 カラム名) + """ + df = pd.DataFrame( + constants.SECTOR_33_DATA, columns=constants.SECTOR_33_COLUMNS_V2 + ) + df.sort_values(constants.SECTOR_33_COLUMNS_V2[0], inplace=True) + return df + + # ------------------------------------------------------------------ + # get_list (v1 と同名のユーティリティ, eq-master ベース) + # ------------------------------------------------------------------ + def get_list(self, code: str = "", date_yyyymmdd: str = "") -> pd.DataFrame: + """ + 上場銘柄一覧 (業種・市場区分の英語名を付与したユーティリティ) + + v2 の eq-master を利用し、v2 フィールド名で返却します。 + + Args: + code: 銘柄コード (任意) + date_yyyymmdd: 基準日 (YYYYMMDD or YYYY-MM-DD, 任意) + Returns: + pd.DataFrame: 上場銘柄情報 (v2 フィールド名) + """ + df_list = self.get_eq_master(code=code, date=date_yyyymmdd) + if df_list.empty: + return pd.DataFrame([], columns=constants.EQ_MASTER_COLUMNS_V2) + + # 17/33 業種 & 市場区分の英語名を付与 + df_17_sectors = self.get_17_sectors()[["S17", "S17NmEn"]] + df_33_sectors = self.get_33_sectors()[["S33", "S33NmEn"]] + df_segments = self.get_market_segments()[["Mkt", "MktNmEn"]] + + df_list = pd.merge(df_list, df_17_sectors, how="left", on=["S17"]) + df_list = pd.merge(df_list, df_33_sectors, how="left", on=["S33"]) + df_list = pd.merge(df_list, df_segments, how="left", on=["Mkt"]) + + df_list.sort_values("Code", inplace=True) + return df_list + + # ------------------------------------------------------------------ + # eq-bars-daily (/equities/bars/daily) + # ------------------------------------------------------------------ + def get_eq_bars_daily( + self, + code: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + date_yyyymmdd: str = "", + ) -> pd.DataFrame: + """ + eq-bars-daily: 株価四本値 (v2: /equities/bars/daily) + + Args: + code: 銘柄コード (5桁 or 4桁) + from_yyyymmdd: 期間開始日 (YYYYMMDD or YYYY-MM-DD) + to_yyyymmdd: 期間終了日 (YYYYMMDD or YYYY-MM-DD) + date_yyyymmdd: 特定日付 (YYYYMMDD or YYYY-MM-DD) + Returns: + pd.DataFrame: 株価データ (v2のフィールド名で返却) + """ + return self._eq_bars_daily_api.execute( + self, + code=code, + from_yyyymmdd=from_yyyymmdd, + to_yyyymmdd=to_yyyymmdd, + date_yyyymmdd=date_yyyymmdd, + ) + + def get_eq_bars_daily_range( + self, + start_dt: DatetimeLike = "20170101", + end_dt: DatetimeLike = datetime.now(), + ) -> pd.DataFrame: + """ + 全銘柄の株価四本値を日付範囲指定して取得 (v2: /equities/bars/daily) + + Args: + start_dt: 取得開始日 + end_dt: 取得終了日 + Returns: + pd.DataFrame: 株価データ (Code, Date 列でソート) + """ + buff: list[pd.DataFrame] = [] + dates = pd.date_range(start_dt, end_dt, freq="D") + with ThreadPoolExecutor(max_workers=self.MAX_WORKERS) as executor: + futures = [ + executor.submit( + self.get_eq_bars_daily, date_yyyymmdd=s.strftime("%Y-%m-%d") + ) + for s in dates + ] + for future in as_completed(futures): + df = future.result() + if not df.empty: + buff.append(df) + if not buff: + return pd.DataFrame() + return pd.concat(buff).sort_values(["Code", "Date"]).reset_index(drop=True) + + # ------------------------------------------------------------------ + # eq-bars-daily-am (/equities/bars/daily/am) + # ------------------------------------------------------------------ + def get_eq_bars_daily_am(self, code: str = "") -> pd.DataFrame: + """ + eq-bars-daily-am: 前場四本値 (v2: /equities/bars/daily/am) + + Args: + code: 銘柄コード (5桁 or 4桁)。空文字の場合は全銘柄。 + Returns: + pd.DataFrame: 前場の株価データ + """ + return self._eq_bars_daily_am_api.execute(self, code=code) + + # ------------------------------------------------------------------ + # eq-bars-minute (/equities/bars/minute) + # ------------------------------------------------------------------ + def get_eq_bars_minute( + self, + code: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + date_yyyymmdd: str = "", + ) -> pd.DataFrame: + """ + eq-bars-minute: 分足 (v2: /equities/bars/minute) + + Args: + code: 銘柄コード (5桁 or 4桁) + from_yyyymmdd: 期間開始日 (YYYYMMDD or YYYY-MM-DD) + to_yyyymmdd: 期間終了日 (YYYYMMDD or YYYY-MM-DD) + date_yyyymmdd: 特定日付 (YYYYMMDD or YYYY-MM-DD) + Returns: + pd.DataFrame: 1分足データ (v2のフィールド名で返却) + """ + return self._eq_bars_minute_api.execute( + self, + code=code, + from_yyyymmdd=from_yyyymmdd, + to_yyyymmdd=to_yyyymmdd, + date_yyyymmdd=date_yyyymmdd, + ) + + def _aggregate_bars_n_minute( + self, + df: pd.DataFrame, + n: int = 5, + ) -> pd.DataFrame: + """ + 1分足データをn分足に集約する (private) + + Args: + df: 1分足データ (Date, Time, Code, O, H, L, C, Vo, Va を含む) + n: 集約する分数 (デフォルト: 5) + Returns: + pd.DataFrame: n分足データ + """ + if df.empty: + return df.copy() + + df = df.copy() + + # DateTime列を作成 (Date + Time) + df["DateTime"] = pd.to_datetime( + df["Date"].astype(str) + " " + df["Time"].astype(str), + errors="coerce", + ) + + # n分間隔でグループ化するためのキーを作成 + df["TimeGroup"] = df["DateTime"].dt.floor(f"{n}min") + + # 銘柄ごと・n分間隔ごとに集約 + agg_funcs = { + "Date": "first", + "Time": "first", + "Code": "first", + "O": "first", # 始値: 最初の値 + "H": "max", # 高値: 最大値 + "L": "min", # 安値: 最小値 + "C": "last", # 終値: 最後の値 + "Vo": "sum", # 出来高: 合計 + "Va": "sum", # 売買代金: 合計 + } + + result = ( + df.groupby(["Code", "TimeGroup"], as_index=False) + .agg(agg_funcs) + .drop(columns=["TimeGroup"]) + ) + + # ソートして返却 + result.sort_values(["Code", "Date", "Time"], inplace=True) + return result.reset_index(drop=True) + + def get_eq_bars_5minute( + self, + code: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + date_yyyymmdd: str = "", + ) -> pd.DataFrame: + """ + eq-bars-minute から5分足データを算出して取得 + + Args: + code: 銘柄コード (5桁 or 4桁) + from_yyyymmdd: 期間開始日 (YYYYMMDD or YYYY-MM-DD) + to_yyyymmdd: 期間終了日 (YYYYMMDD or YYYY-MM-DD) + date_yyyymmdd: 特定日付 (YYYYMMDD or YYYY-MM-DD) + Returns: + pd.DataFrame: 5分足データ + """ + df_1min = self.get_eq_bars_minute( + code=code, + from_yyyymmdd=from_yyyymmdd, + to_yyyymmdd=to_yyyymmdd, + date_yyyymmdd=date_yyyymmdd, + ) + return self._aggregate_bars_n_minute(df_1min, n=5) + + def get_eq_bars_15minute( + self, + code: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + date_yyyymmdd: str = "", + ) -> pd.DataFrame: + """ + eq-bars-minute から15分足データを算出して取得 + + Args: + code: 銘柄コード (5桁 or 4桁) + from_yyyymmdd: 期間開始日 (YYYYMMDD or YYYY-MM-DD) + to_yyyymmdd: 期間終了日 (YYYYMMDD or YYYY-MM-DD) + date_yyyymmdd: 特定日付 (YYYYMMDD or YYYY-MM-DD) + Returns: + pd.DataFrame: 15分足データ + """ + df_1min = self.get_eq_bars_minute( + code=code, + from_yyyymmdd=from_yyyymmdd, + to_yyyymmdd=to_yyyymmdd, + date_yyyymmdd=date_yyyymmdd, + ) + return self._aggregate_bars_n_minute(df_1min, n=15) + + # ------------------------------------------------------------------ + # eq-investor-types (/equities/investor-types) + # ------------------------------------------------------------------ + def get_eq_investor_types( + self, + section: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + ) -> pd.DataFrame: + """ + eq-investor-types: 投資部門別売買状況 (v2: /equities/investor-types) + + Args: + section: 市場区分 (例: \"TSEPrime\") + from_yyyymmdd: 期間開始日 + to_yyyymmdd: 期間終了日 + Returns: + pd.DataFrame: 投資部門別売買データ + """ + return self._eq_investor_types_api.execute( + self, + section=section, + from_yyyymmdd=from_yyyymmdd, + to_yyyymmdd=to_yyyymmdd, + ) + + # ------------------------------------------------------------------ + # /fins/summary (path_old: /fins/statements) + # ------------------------------------------------------------------ + def get_fin_summary( + self, + code: str = "", + date_yyyymmdd: str = "", + ) -> pd.DataFrame: + """ + 財務情報サマリ (v2: /fins/summary) + + Args: + code: 銘柄コード + date_yyyymmdd: 開示日 (YYYYMMDD or YYYY-MM-DD) + Returns: + pd.DataFrame: 財務情報 (v2のフィールド名で返却) + """ + return self._fin_summary_api.execute( + self, + code=code, + date_yyyymmdd=date_yyyymmdd, + ) + + def get_fin_summary_range( + self, + start_dt: DatetimeLike = "20080707", + end_dt: DatetimeLike = datetime.now(), + cache_dir: str = "", + ) -> pd.DataFrame: + """ + 財務情報サマリを日付範囲指定して取得 (v2: /fins/summary) + + Args: + start_dt: 取得開始日 + end_dt: 取得終了日 + cache_dir: CSV形式のキャッシュファイルが存在するディレクトリ (未指定時はキャッシュしない) + """ + buff: list[pd.DataFrame] = [] + futures: dict[Any, str] = {} + dates = pd.date_range(start_dt, end_dt, freq="D") + + with ThreadPoolExecutor(max_workers=self.MAX_WORKERS) as executor: + for s in dates: + yyyymmdd = s.strftime("%Y%m%d") + yyyy = yyyymmdd[:4] + cache_file = f"v2_fin_summary_{yyyymmdd}.csv.gz" + + if (cache_dir != "") and os.path.isfile( + f"{cache_dir}/{yyyy}/{cache_file}" + ): + df = pd.read_csv(f"{cache_dir}/{yyyy}/{cache_file}", dtype=str) + # 日付カラムをdatetimeに変換 + date_cols = [ + "DiscDate", + "CurPerSt", + "CurPerEn", + "CurFYSt", + "CurFYEn", + "NxtFYSt", + "NxtFYEn", + ] + for col in date_cols: + if col in df.columns: + df[col] = pd.to_datetime(df[col], errors="coerce") + buff.append(df) + else: + future = executor.submit( + self.get_fin_summary, date_yyyymmdd=yyyymmdd + ) + futures[future] = yyyymmdd + + for future in as_completed(futures): + df = future.result() + if df.empty: + continue + buff.append(df) + yyyymmdd = futures[future] + yyyy = yyyymmdd[:4] + cache_file = f"v2_fin_summary_{yyyymmdd}.csv.gz" + if cache_dir != "": + os.makedirs(f"{cache_dir}/{yyyy}", exist_ok=True) + df.to_csv(f"{cache_dir}/{yyyy}/{cache_file}", index=False) + + if not buff: + return pd.DataFrame() + + return ( + pd.concat(buff) + .sort_values(["DiscDate", "DiscTime", "Code"]) + .reset_index(drop=True) + ) + + # ------------------------------------------------------------------ + # /fins/details (path_old: /fins/fs_details) + # ------------------------------------------------------------------ + def get_fin_details( + self, + code: str = "", + date_yyyymmdd: str = "", + ) -> pd.DataFrame: + """ + 財務諸表詳細 (v2: /fins/details) + + Args: + code: 銘柄コード + date_yyyymmdd: 開示日 (YYYYMMDD or YYYY-MM-DD) + Returns: + pd.DataFrame: 財務諸表詳細 (FS列に各項目が含まれる) + """ + return self._fin_details_api.execute( + self, + code=code, + date_yyyymmdd=date_yyyymmdd, + ) + + def get_fin_details_range( + self, + start_dt: DatetimeLike = "20080707", + end_dt: DatetimeLike = datetime.now(), + cache_dir: str = "", + ) -> pd.DataFrame: + """ + 財務諸表詳細を日付範囲指定して取得 (v2: /fins/details) + + Args: + start_dt: 取得開始日 + end_dt: 取得終了日 + cache_dir: CSV形式のキャッシュファイルが存在するディレクトリ (未指定時はキャッシュしない) + """ + buff: list[pd.DataFrame] = [] + futures: dict[Any, str] = {} + dates = pd.date_range(start_dt, end_dt, freq="D") + + with ThreadPoolExecutor(max_workers=self.MAX_WORKERS) as executor: + for s in dates: + yyyymmdd = s.strftime("%Y%m%d") + yyyy = yyyymmdd[:4] + cache_file = f"v2_fin_details_{yyyymmdd}.csv.gz" + + if (cache_dir != "") and os.path.isfile( + f"{cache_dir}/{yyyy}/{cache_file}" + ): + df = pd.read_csv(f"{cache_dir}/{yyyy}/{cache_file}", dtype=str) + if "DiscDate" in df.columns: + df["DiscDate"] = pd.to_datetime(df["DiscDate"], errors="coerce") + buff.append(df) + else: + future = executor.submit( + self.get_fin_details, date_yyyymmdd=yyyymmdd + ) + futures[future] = yyyymmdd + + for future in as_completed(futures): + df = future.result() + if df.empty: + continue + buff.append(df) + yyyymmdd = futures[future] + yyyy = yyyymmdd[:4] + cache_file = f"v2_fin_details_{yyyymmdd}.csv.gz" + if cache_dir != "": + os.makedirs(f"{cache_dir}/{yyyy}", exist_ok=True) + df.to_csv(f"{cache_dir}/{yyyy}/{cache_file}", index=False) + + if not buff: + return pd.DataFrame() + + return ( + pd.concat(buff) + .sort_values(["DiscDate", "DiscTime", "Code"]) + .reset_index(drop=True) + ) + + # ------------------------------------------------------------------ + # /fins/dividend (path_old: /fins/dividend) + # ------------------------------------------------------------------ + def get_fin_dividend( + self, + code: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + date_yyyymmdd: str = "", + ) -> pd.DataFrame: + """ + 配当金情報 (v2: /fins/dividend) + + Args: + code: 銘柄コード + from_yyyymmdd: 期間開始日 + to_yyyymmdd: 期間終了日 + date_yyyymmdd: 特定日付 + Returns: + pd.DataFrame: 配当金データ + """ + return self._fin_dividend_api.execute( + self, + code=code, + from_yyyymmdd=from_yyyymmdd, + to_yyyymmdd=to_yyyymmdd, + date_yyyymmdd=date_yyyymmdd, + ) + + # ------------------------------------------------------------------ + # /equities/earnings-calendar (path_old: /fins/announcement) + # ------------------------------------------------------------------ + def get_eq_earnings_cal(self) -> pd.DataFrame: + """ + 決算発表予定日 (v2: /equities/earnings-calendar) + + Returns: + pd.DataFrame: 決算発表予定データ + """ + return self._eq_earnings_cal_api.execute(self) + + # ------------------------------------------------------------------ + # /markets/short-ratio (path_old: /markets/short_selling) + # ------------------------------------------------------------------ + def get_mkt_short_ratio( + self, + sector_33_code: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + date_yyyymmdd: str = "", + ) -> pd.DataFrame: + """ + 業種別空売り比率 (v2: /markets/short-ratio) + + Args: + sector_33_code: 33業種コード (例: 0050) + from_yyyymmdd: 期間開始日 + to_yyyymmdd: 期間終了日 + date_yyyymmdd: 特定日付 + Returns: + pd.DataFrame: 業種別空売り比率データ + """ + return self._mkt_short_ratio_api.execute( + self, + sector_33_code=sector_33_code, + from_yyyymmdd=from_yyyymmdd, + to_yyyymmdd=to_yyyymmdd, + date_yyyymmdd=date_yyyymmdd, + ) + + def get_mkt_short_ratio_range( + self, + start_dt: DatetimeLike = "20170101", + end_dt: DatetimeLike = datetime.now(), + ) -> pd.DataFrame: + """ + 全33業種の空売り比率データを日付範囲指定して取得 (v2: /markets/short-ratio) + """ + buff: list[pd.DataFrame] = [] + dates = pd.date_range(start_dt, end_dt, freq="D") + with ThreadPoolExecutor(max_workers=self.MAX_WORKERS) as executor: + futures = [ + executor.submit( + self.get_mkt_short_ratio, date_yyyymmdd=s.strftime("%Y-%m-%d") + ) + for s in dates + ] + for future in as_completed(futures): + df = future.result() + if not df.empty: + buff.append(df) + if not buff: + return pd.DataFrame() + return pd.concat(buff).sort_values(["Date", "S33"]).reset_index(drop=True) + + # ------------------------------------------------------------------ + # /markets/short-sale-report (path_old: /markets/short_selling_positions) + # ------------------------------------------------------------------ + def get_mkt_short_sale_report( + self, + code: str = "", + disclosed_date: str = "", + disclosed_date_from: str = "", + disclosed_date_to: str = "", + calculated_date: str = "", + ) -> pd.DataFrame: + """ + 空売り残高報告 (v2: /markets/short-sale-report) + + Args: + code: 銘柄コード + disclosed_date: 開示日 + disclosed_date_from: 開示日(開始) + disclosed_date_to: 開示日(終了) + calculated_date: 算出日 + Returns: + pd.DataFrame: 空売り残高報告データ + """ + return self._mkt_short_sale_report_api.execute( + self, + code=code, + disclosed_date=disclosed_date, + disclosed_date_from=disclosed_date_from, + disclosed_date_to=disclosed_date_to, + calculated_date=calculated_date, + ) + + def get_mkt_short_sale_report_range( + self, + start_dt: DatetimeLike = "20131107", + end_dt: DatetimeLike = datetime.now(), + ) -> pd.DataFrame: + """ + 空売り残高報告データを日付範囲指定して取得 (v2: /markets/short-sale-report) + """ + buff: list[pd.DataFrame] = [] + dates = pd.date_range(start_dt, end_dt, freq="D") + with ThreadPoolExecutor(max_workers=self.MAX_WORKERS) as executor: + futures = [ + executor.submit( + self.get_mkt_short_sale_report, + disclosed_date=s.strftime("%Y-%m-%d"), + ) + for s in dates + ] + for future in as_completed(futures): + df = future.result() + if not df.empty: + buff.append(df) + if not buff: + return pd.DataFrame() + return ( + pd.concat(buff) + .sort_values(["DiscDate", "CalcDate", "Code"]) + .reset_index(drop=True) + ) + + # ------------------------------------------------------------------ + # /markets/margin-interest (path_old: /markets/weekly_margin_interest) + # ------------------------------------------------------------------ + def get_mkt_margin_interest( + self, + code: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + date_yyyymmdd: str = "", + ) -> pd.DataFrame: + """ + 信用取引週末残高 (v2: /markets/margin-interest) + + Args: + code: 銘柄コード + from_yyyymmdd: 期間開始日 + to_yyyymmdd: 期間終了日 + date_yyyymmdd: 特定日付 + Returns: + pd.DataFrame: 信用取引週末残高データ + """ + return self._mkt_margin_interest_api.execute( + self, + code=code, + from_yyyymmdd=from_yyyymmdd, + to_yyyymmdd=to_yyyymmdd, + date_yyyymmdd=date_yyyymmdd, + ) + + def get_mkt_margin_interest_range( + self, + start_dt: DatetimeLike = "20170101", + end_dt: DatetimeLike = datetime.now(), + ) -> pd.DataFrame: + """ + 信用取引週末残高を日付範囲指定して取得 (v2: /markets/margin-interest) + """ + buff: list[pd.DataFrame] = [] + dates = pd.date_range(start_dt, end_dt, freq="D") + with ThreadPoolExecutor(max_workers=self.MAX_WORKERS) as executor: + futures = [ + executor.submit( + self.get_mkt_margin_interest, + date_yyyymmdd=s.strftime("%Y-%m-%d"), + ) + for s in dates + ] + for future in as_completed(futures): + df = future.result() + if not df.empty: + buff.append(df) + if not buff: + return pd.DataFrame() + return pd.concat(buff).sort_values(["Date", "Code"]).reset_index(drop=True) + + # ------------------------------------------------------------------ + # /markets/margin-alert (path_old: /markets/daily_margin_interest) + # ------------------------------------------------------------------ + def get_mkt_margin_alert( + self, + code: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + date_yyyymmdd: str = "", + ) -> pd.DataFrame: + """ + 日々公表信用取引残高 (v2: /markets/margin-alert) + + Args: + code: 銘柄コード + from_yyyymmdd: 期間開始日 + to_yyyymmdd: 期間終了日 + date_yyyymmdd: 特定日付 + Returns: + pd.DataFrame: 日々公表信用取引残高データ + """ + return self._mkt_margin_alert_api.execute( + self, + code=code, + from_yyyymmdd=from_yyyymmdd, + to_yyyymmdd=to_yyyymmdd, + date_yyyymmdd=date_yyyymmdd, + ) + + def get_mkt_margin_alert_range( + self, + start_dt: DatetimeLike = "20170101", + end_dt: DatetimeLike = datetime.now(), + ) -> pd.DataFrame: + """ + 日々公表信用取引残高を日付範囲指定して取得 (v2: /markets/margin-alert) + """ + buff: list[pd.DataFrame] = [] + dates = pd.date_range(start_dt, end_dt, freq="D") + with ThreadPoolExecutor(max_workers=self.MAX_WORKERS) as executor: + futures = [ + executor.submit( + self.get_mkt_margin_alert, + date_yyyymmdd=s.strftime("%Y-%m-%d"), + ) + for s in dates + ] + for future in as_completed(futures): + df = future.result() + if not df.empty: + buff.append(df) + if not buff: + return pd.DataFrame() + return pd.concat(buff).sort_values(["PubDate", "Code"]).reset_index(drop=True) + + # ------------------------------------------------------------------ + # /markets/breakdown (path_old: /markets/breakdown) + # ------------------------------------------------------------------ + def get_mkt_breakdown( + self, + code: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + date_yyyymmdd: str = "", + ) -> pd.DataFrame: + """ + 売買内訳データ (v2: /markets/breakdown) + + Args: + code: 銘柄コード + from_yyyymmdd: 期間開始日 + to_yyyymmdd: 期間終了日 + date_yyyymmdd: 特定日付 + Returns: + pd.DataFrame: 売買内訳データ + """ + return self._mkt_breakdown_api.execute( + self, + code=code, + from_yyyymmdd=from_yyyymmdd, + to_yyyymmdd=to_yyyymmdd, + date_yyyymmdd=date_yyyymmdd, + ) + + def get_mkt_breakdown_range( + self, + start_dt: DatetimeLike = "20170101", + end_dt: DatetimeLike = datetime.now(), + ) -> pd.DataFrame: + """ + 売買内訳データを日付範囲指定して取得 (v2: /markets/breakdown) + """ + buff: list[pd.DataFrame] = [] + dates = pd.date_range(start_dt, end_dt, freq="D") + with ThreadPoolExecutor(max_workers=self.MAX_WORKERS) as executor: + futures = [ + executor.submit( + self.get_mkt_breakdown, date_yyyymmdd=s.strftime("%Y-%m-%d") + ) + for s in dates + ] + for future in as_completed(futures): + df = future.result() + if not df.empty: + buff.append(df) + if not buff: + return pd.DataFrame() + return pd.concat(buff).sort_values(["Code", "Date"]).reset_index(drop=True) + + # ------------------------------------------------------------------ + # /markets/calendar (path_old: /markets/trading_calendar) + # ------------------------------------------------------------------ + def get_mkt_calendar( + self, + holiday_division: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + ) -> pd.DataFrame: + """ + 取引カレンダー (v2: /markets/calendar) + + Args: + holiday_division: 休日区分 (HolDiv コード) + from_yyyymmdd: 期間開始日 + to_yyyymmdd: 期間終了日 + Returns: + pd.DataFrame: 取引カレンダーデータ + """ + return self._mkt_calendar_api.execute( + self, + holiday_division=holiday_division, + from_yyyymmdd=from_yyyymmdd, + to_yyyymmdd=to_yyyymmdd, + ) + + # ------------------------------------------------------------------ + # indices (v2: /indices/bars/daily, /indices/bars/daily/topix) + # ------------------------------------------------------------------ + def get_idx_bars_daily( + self, + code: str = "", + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + date_yyyymmdd: str = "", + ) -> pd.DataFrame: + """ + 指数四本値 (v2: /indices/bars/daily) + + Args: + code: 指数コード + from_yyyymmdd: 取得開始日 + to_yyyymmdd: 取得終了日 + date_yyyymmdd: 取得日 + """ + return self._idx_bars_daily_api.execute( + self, + code=code, + from_yyyymmdd=from_yyyymmdd, + to_yyyymmdd=to_yyyymmdd, + date_yyyymmdd=date_yyyymmdd, + ) + + def get_idx_bars_daily_topix( + self, + from_yyyymmdd: str = "", + to_yyyymmdd: str = "", + ) -> pd.DataFrame: + """ + TOPIX 指数四本値 (v2: /indices/bars/daily/topix) + + Args: + from_yyyymmdd: 取得開始日 + to_yyyymmdd: 取得終了日 + """ + return self._idx_bars_daily_topix_api.execute( + self, + from_yyyymmdd=from_yyyymmdd, + to_yyyymmdd=to_yyyymmdd, + ) + + # ------------------------------------------------------------------ + # derivatives (v2: /derivatives/bars/daily/*) + # ------------------------------------------------------------------ + def get_drv_bars_daily_fut( + self, + date_yyyymmdd: str, + category: str = "", + contract_flag: str = "", + ) -> pd.DataFrame: + """ + 先物四本値 (v2: /derivatives/bars/daily/futures) + """ + return self._drv_bars_daily_fut_api.execute( + self, + date_yyyymmdd=date_yyyymmdd, + category=category, + contract_flag=contract_flag, + ) + + def get_drv_bars_daily_opt( + self, + date_yyyymmdd: str, + category: str = "", + contract_flag: str = "", + code: str = "", + ) -> pd.DataFrame: + """ + オプション四本値 (v2: /derivatives/bars/daily/options) + """ + return self._drv_bars_daily_opt_api.execute( + self, + date_yyyymmdd=date_yyyymmdd, + category=category, + contract_flag=contract_flag, + code=code, + ) + + def get_drv_bars_daily_opt_225( + self, + date_yyyymmdd: str, + ) -> pd.DataFrame: + """ + 日経225オプション四本値 (v2: /derivatives/bars/daily/options/225) + """ + return self._drv_bars_daily_opt_225_api.execute( + self, + date_yyyymmdd=date_yyyymmdd, + ) + + def get_drv_bars_daily_fut_range( + self, + start_dt: DatetimeLike = "20170101", + end_dt: DatetimeLike = datetime.now(), + category: str = "", + contract_flag: str = "", + ) -> pd.DataFrame: + """ + 先物四本値を日付範囲指定して取得 (v2: /derivatives/bars/daily/futures) + """ + buff: list[pd.DataFrame] = [] + dates = pd.date_range(start_dt, end_dt, freq="D") + with ThreadPoolExecutor(max_workers=self.MAX_WORKERS) as executor: + futures = [ + executor.submit( + self.get_drv_bars_daily_fut, + date_yyyymmdd=s.strftime("%Y-%m-%d"), + category=category, + contract_flag=contract_flag, + ) + for s in dates + ] + for future in as_completed(futures): + df = future.result() + if not df.empty: + buff.append(df) + if not buff: + return pd.DataFrame() + return pd.concat(buff).sort_values(["Code", "Date"]).reset_index(drop=True) + + def get_drv_bars_daily_opt_range( + self, + start_dt: DatetimeLike = "20170101", + end_dt: DatetimeLike = datetime.now(), + category: str = "", + contract_flag: str = "", + code: str = "", + ) -> pd.DataFrame: + """ + オプション四本値を日付範囲指定して取得 (v2: /derivatives/bars/daily/options) + """ + buff: list[pd.DataFrame] = [] + dates = pd.date_range(start_dt, end_dt, freq="D") + with ThreadPoolExecutor(max_workers=self.MAX_WORKERS) as executor: + options = [ + executor.submit( + self.get_drv_bars_daily_opt, + date_yyyymmdd=s.strftime("%Y-%m-%d"), + category=category, + contract_flag=contract_flag, + code=code, + ) + for s in dates + ] + for option in as_completed(options): + df = option.result() + if not df.empty: + buff.append(df) + if not buff: + return pd.DataFrame() + return pd.concat(buff).sort_values(["Code", "Date"]).reset_index(drop=True) + + def get_drv_bars_daily_opt_225_range( + self, + start_dt: DatetimeLike = "20170101", + end_dt: DatetimeLike = datetime.now(), + ) -> pd.DataFrame: + """ + 日経225オプション四本値を日付範囲指定して取得 (v2: /derivatives/bars/daily/options/225) + """ + buff: list[pd.DataFrame] = [] + dates = pd.date_range(start_dt, end_dt, freq="D") + with ThreadPoolExecutor(max_workers=self.MAX_WORKERS) as executor: + futures = [ + executor.submit( + self.get_drv_bars_daily_opt_225, + date_yyyymmdd=s.strftime("%Y-%m-%d"), + ) + for s in dates + ] + for future in as_completed(futures): + df = future.result() + if not df.empty: + buff.append(df) + if not buff: + return pd.DataFrame() + return pd.concat(buff).sort_values(["Code", "Date"]).reset_index(drop=True) + + # ------------------------------------------------------------------ + # Bulk API + # ------------------------------------------------------------------ + def get_bulk_list( + self, + endpoint: Union[str, BulkEndpoint], + ) -> pd.DataFrame: + """ + bulk-list: 取得可能なデータ一覧 (v2: /bulk/list) + + Args: + endpoint: 取得したいデータのエンドポイント + (例: BulkEndpoint.EQ_MASTER または "/equities/master") + Returns: + pd.DataFrame: データ一覧 (Key, Size, LastModified) + """ + return self._bulk_list_api.execute(self, endpoint=endpoint) + + def get_bulk(self, key: str) -> str: + """ + bulk-get: データダウンロードURL取得 (v2: /bulk/get) + + Args: + key: get_bulk_listで取得したKey + Returns: + str: ダウンロードURL + """ + return self._bulk_get_api.execute(self, key=key) + + def download_bulk(self, key: str, output_path: str) -> None: + """ + bulk-get で取得した URL からファイルをダウンロードして保存 + + Args: + key: get_bulk_listで取得したKey + output_path: ダウンロードファイルの保存先パス + + Raises: + ValueError: output_path が空文字列の場合 + """ + # バリデーション + if not output_path or not output_path.strip(): + raise ValueError("output_path must not be empty") + + # ダウンロード URL を取得 + url = self._bulk_get_api.execute(self, key=key) + + # ディレクトリが存在しない場合は作成 + output_dir = os.path.dirname(os.path.abspath(output_path)) + if output_dir: + os.makedirs(output_dir, exist_ok=True) + + # ファイルをダウンロード + session = self._request_session() + response = session.get(url, stream=True, timeout=300) + response.raise_for_status() + + # ファイルに書き込み + with open(output_path, "wb") as f: + for chunk in response.iter_content(chunk_size=8192): + f.write(chunk) diff --git a/jquantsapi/constants.py b/jquantsapi/constants.py index 887dbb8..bbf6795 100644 --- a/jquantsapi/constants.py +++ b/jquantsapi/constants.py @@ -1,38 +1,9 @@ -# ref. ja https://jpx.gitbook.io/j-quants-ja/api-reference/listed_info -# ref. en https://jpx.gitbook.io/j-quants-en/api-reference/listed_info -LISTED_INFO_COLUMNS = [ - "Date", - "Code", - "CompanyName", - "CompanyNameEnglish", - "Sector17Code", - "Sector17CodeName", - "Sector33Code", - "Sector33CodeName", - "ScaleCategory", - "MarketCode", - "MarketCodeName", -] +# ============================================================================ +# 共通データ(V1/V2両方で使用) +# ============================================================================ -LISTED_INFO_STANDARD_PREMIUM_COLUMNS = [ - "Date", - "Code", - "CompanyName", - "CompanyNameEnglish", - "Sector17Code", - "Sector17CodeName", - "Sector33Code", - "Sector33CodeName", - "ScaleCategory", - "MarketCode", - "MarketCodeName", - "MarginCode", - "MarginCodeName", -] - -# ref. ja https://jpx.gitbook.io/j-quants-ja/api-reference/listed_info/sector17code -# ref. en https://jpx.gitbook.io/j-quants-en/api-reference/listed_info/sector17code -SECTOR_17_COLUMNS = ["Sector17Code", "Sector17CodeName", "Sector17CodeNameEnglish"] +# ref. ja https://jpx-jquants.com/ja/spec/eq-master/sector17code +# ref. en https://jpx-jquants.com/en/spec/eq-master/sector17code SECTOR_17_DATA = [ ("1", "食品", "FOODS"), ("2", "エネルギー資源", "ENERGY RESOURCES"), @@ -54,15 +25,9 @@ ("99", "その他", "OTHER"), ] -# ref. ja https://jpx.gitbook.io/j-quants-ja/api-reference/listed_info/sector33code -# ref. en https://jpx.gitbook.io/j-quants-en/api-reference/listed_info/sector33code +# ref. ja https://jpx-jquants.com/ja/spec/eq-master/sector33code +# ref. en https://jpx-jquants.com/en/spec/eq-master/sector33code # ref. 33-17 mapping https://www.jpx.co.jp/markets/indices/line-up/files/fac_13_sector.pdf -SECTOR_33_COLUMNS = [ - "Sector33Code", - "Sector33CodeName", - "Sector33CodeNameEnglish", - "Sector17Code", -] SECTOR_33_DATA = [ ("0050", "水産・農林業", "Fishery, Agriculture & Forestry", "1"), ("1050", "鉱業", "Mining", "2"), @@ -100,6 +65,78 @@ ("9999", "その他", "Other", "99"), ] +# ref. ja https://jpx-jquants.com/ja/spec/eq-master/marketcode +# ref. en https://jpx-jquants.com/en/spec/eq-master/marketcode +MARKET_SEGMENT_DATA = [ + ("0101", "東証一部", "1st Section"), + ("0102", "東証二部", "2nd Section"), + ("0104", "マザーズ", "Mothers"), + ("0105", "TOKYO PRO MARKET", "TOKYO PRO MARKET"), + ("0106", "JASDAQ スタンダード", "JASDAQ Standard"), + ("0107", "JASDAQ グロース", "JASDAQ Growth"), + ("0109", "その他", "Others"), + ("0111", "プライム", "Prime"), + ("0112", "スタンダード", "Standard"), + ("0113", "グロース", "Growth"), +] + + +# ============================================================================ +# V1 API用カラム定義 +# ============================================================================ + +# ref. ja https://jpx.gitbook.io/j-quants-ja/api-reference/listed_info/sector17code +# ref. en https://jpx.gitbook.io/j-quants-en/api-reference/listed_info/sector17code +SECTOR_17_COLUMNS = ["Sector17Code", "Sector17CodeName", "Sector17CodeNameEnglish"] + +# ref. ja https://jpx.gitbook.io/j-quants-ja/api-reference/listed_info/sector33code +# ref. en https://jpx.gitbook.io/j-quants-en/api-reference/listed_info/sector33code +SECTOR_33_COLUMNS = [ + "Sector33Code", + "Sector33CodeName", + "Sector33CodeNameEnglish", + "Sector17Code", +] + +# ref. ja https://jpx.gitbook.io/j-quants-ja/api-reference/listed_info/marketcode +# ref. en https://jpx.gitbook.io/j-quants-en/api-reference/listed_info/marketcode +MARKET_SEGMENT_COLUMNS = [ + "MarketCode", + "MarketCodeName", + "MarketCodeNameEnglish", +] + +# ref. ja https://jpx.gitbook.io/j-quants-ja/api-reference/listed_info +# ref. en https://jpx.gitbook.io/j-quants-en/api-reference/listed_info +LISTED_INFO_COLUMNS = [ + "Date", + "Code", + "CompanyName", + "CompanyNameEnglish", + "Sector17Code", + "Sector17CodeName", + "Sector33Code", + "Sector33CodeName", + "ScaleCategory", + "MarketCode", + "MarketCodeName", +] +LISTED_INFO_STANDARD_PREMIUM_COLUMNS = [ + "Date", + "Code", + "CompanyName", + "CompanyNameEnglish", + "Sector17Code", + "Sector17CodeName", + "Sector33Code", + "Sector33CodeName", + "ScaleCategory", + "MarketCode", + "MarketCodeName", + "MarginCode", + "MarginCodeName", +] + # ref. ja https://jpx.gitbook.io/j-quants-ja/api-reference/daily_quotes # ref. en https://jpx.gitbook.io/j-quants-en/api-reference/daily_quotes PRICES_DAILY_QUOTES_COLUMNS = [ @@ -121,6 +158,8 @@ "AdjustmentVolume", ] +# ref. ja https://jpx.gitbook.io/j-quants-ja/api-reference/daily_quotes +# ref. en https://jpx.gitbook.io/j-quants-en/api-reference/daily_quotes PRICES_DAILY_QUOTES_PREMIUM_COLUMNS = [ "Date", "Code", @@ -166,6 +205,19 @@ "AfternoonAdjustmentVolume", ] +# ref. ja https://jpx.gitbook.io/j-quants-ja/api-reference/prices_am +# ref. en https://jpx.gitbook.io/j-quants-en/api-reference/prices_am +PRICES_PRICES_AM_COLUMNS = [ + "Date", + "Code", + "MorningOpen", + "MorningHigh", + "MorningLow", + "MorningClose", + "MorningVolume", + "MorningTurnoverValue", +] + # ref. ja https://jpx.gitbook.io/j-quants-ja/api-reference/indices # ref. en https://jpx.gitbook.io/j-quants-en/api-reference/indices INDICES_COLUMNS = [ @@ -262,28 +314,46 @@ "IssueType", ] -# ref. ja https://jpx.gitbook.io/j-quants-ja/api-reference/listed_info/marketcode -# ref. en https://jpx.gitbook.io/j-quants-en/api-reference/listed_info/marketcode -MARKET_SEGMENT_COLUMNS = [ - "MarketCode", - "MarketCodeName", - "MarketCodeNameEnglish", +# ref. ja https://jpx.gitbook.io/j-quants-ja/api-reference/short_selling +# ref. en https://jpx.gitbook.io/j-quants-en/api-reference/short_selling +MARKET_SHORT_SELLING_COLUMNS = [ + "Date", + "Sector33Code", + "SellingExcludingShortSellingTurnoverValue", + "ShortSellingWithRestrictionsTurnoverValue", + "ShortSellingWithoutRestrictionsTurnoverValue", ] -MARKET_SEGMENT_DATA = [ - ("0101", "東証一部", "1st Section"), - ("0102", "東証二部", "2nd Section"), - ("0104", "マザーズ", "Mothers"), - ("0105", "TOKYO PRO MARKET", "TOKYO PRO MARKET"), - ("0106", "JASDAQ スタンダード", "JASDAQ Standard"), - ("0107", "JASDAQ グロース", "JASDAQ Growth"), - ("0109", "その他", "Others"), - ("0111", "プライム", "Prime"), - ("0112", "スタンダード", "Standard"), - ("0113", "グロース", "Growth"), + +# ref. ja https://jpx.gitbook.io/j-quants-ja/api-reference/breakdown +# ref. en https://jpx.gitbook.io/j-quants-en/api-reference/breakdown +MARKETS_BREAKDOWN_COLUMNS = [ + "Date", + "Code", + "LongSellValue", + "ShortSellWithoutMarginValue", + "MarginSellNewValue", + "MarginSellCloseValue", + "LongBuyValue", + "MarginBuyNewValue", + "MarginBuyCloseValue", + "LongSellVolume", + "ShortSellWithoutMarginVolume", + "MarginSellNewVolume", + "MarginSellCloseVolume", + "LongBuyVolume", + "MarginBuyNewVolume", + "MarginBuyCloseVolume", +] + +# ref. ja https://jpx.gitbook.io/j-quants-ja/api-reference/trading_calendar +# ref. en https://jpx.gitbook.io/j-quants-en/api-reference/trading_calendar +MARKETS_TRADING_CALENDAR = [ + "Date", + "HolidayDivision", ] -# ref ja https://jpx.gitbook.io/j-quants-ja/api-reference/statements -# ref en https://jpx.gitbook.io/j-quants-en/api-reference/statements +# ref. ja https://jpx.gitbook.io/j-quants-ja/api-reference/statements +# ref. en https://jpx.gitbook.io/j-quants-en/api-reference/statements FINS_STATEMENTS_COLUMNS = [ "DisclosedDate", "DisclosedTime", @@ -394,6 +464,44 @@ "NextYearForecastNonConsolidatedEarningsPerShare", ] +# ref. ja https://jpx.gitbook.io/j-quants-ja/api-reference/statements-1 +# ref. en https://jpx.gitbook.io/j-quants-en/api-reference/statements-1 +FINS_FS_DETAILS_COLUMNS = [ + "DisclosedDate", + "DisclosedTime", + "LocalCode", + "DisclosureNumber", + "TypeOfDocument", +] + +# ref. ja https://jpx.gitbook.io/j-quants-ja/api-reference/dividend +# ref. en https://jpx.gitbook.io/j-quants-en/api-reference/dividend +FINS_DIVIDEND_COLUMNS = [ + "AnnouncementDate", + "AnnouncementTime", + "Code", + "ReferenceNumber", + "StatusCode", + "BoardMeetingDate", + "InterimFinalCode", + "ForecastResultCode", + "InterimFinalTerm", + "GrossDividendRate", + "RecordDate", + "ExDate", + "ActualRecordDate", + "PayableDate", + "CAReferenceNumber", + "DistributionAmount", + "RetainedEarnings", + "DeemedDividend", + "DeemedCapitalGains", + "NetAssetDecreaseRatio", + "CommemorativeSpecialCode", + "CommemorativeDividendRate", + "SpecialDividendRate", +] + # ref. ja https://jpx.gitbook.io/j-quants-ja/api-reference/announcement # ref. en https://jpx.gitbook.io/j-quants-en/api-reference/announcement FINS_ANNOUNCEMENT_COLUMNS = [ @@ -406,15 +514,6 @@ "Section", ] -# ref. ja https://jpx.gitbook.io/j-quants-ja/api-reference/short_selling -# ref. en https://jpx.gitbook.io/j-quants-en/api-reference/short_selling -MARKET_SHORT_SELLING_COLUMNS = [ - "Date", - "Sector33Code", - "SellingExcludingShortSellingTurnoverValue", - "ShortSellingWithRestrictionsTurnoverValue", - "ShortSellingWithoutRestrictionsTurnoverValue", -] # ref. ja https://jpx.gitbook.io/j-quants-ja/api-reference/index_option # ref. en https://jpx.gitbook.io/j-quants-en/api-reference/index_option OPTION_INDEX_OPTION_COLUMNS = [ @@ -450,75 +549,8 @@ "InterestRate", ] -MARKETS_BREAKDOWN_COLUMNS = [ - "Date", - "Code", - "LongSellValue", - "ShortSellWithoutMarginValue", - "MarginSellNewValue", - "MarginSellCloseValue", - "LongBuyValue", - "MarginBuyNewValue", - "MarginBuyCloseValue", - "LongSellVolume", - "ShortSellWithoutMarginVolume", - "MarginSellNewVolume", - "MarginSellCloseVolume", - "LongBuyVolume", - "MarginBuyNewVolume", - "MarginBuyCloseVolume", -] - -FINS_DIVIDEND_COLUMNS = [ - "AnnouncementDate", - "AnnouncementTime", - "Code", - "ReferenceNumber", - "StatusCode", - "BoardMeetingDate", - "InterimFinalCode", - "ForecastResultCode", - "InterimFinalTerm", - "GrossDividendRate", - "RecordDate", - "ExDate", - "ActualRecordDate", - "PayableDate", - "CAReferenceNumber", - "DistributionAmount", - "RetainedEarnings", - "DeemedDividend", - "DeemedCapitalGains", - "NetAssetDecreaseRatio", - "CommemorativeSpecialCode", - "CommemorativeDividendRate", - "SpecialDividendRate", -] - -PRICES_PRICES_AM_COLUMNS = [ - "Date", - "Code", - "MorningOpen", - "MorningHigh", - "MorningLow", - "MorningClose", - "MorningVolume", - "MorningTurnoverValue", -] - -MARKETS_TRADING_CALENDAR = [ - "Date", - "HolidayDivision", -] - -FINS_FS_DETAILS_COLUMNS = [ - "DisclosedDate", - "DisclosedTime", - "LocalCode", - "DisclosureNumber", - "TypeOfDocument", -] - +# ref. ja https://jpx.gitbook.io/j-quants-ja/api-reference/futures +# ref. en https://jpx.gitbook.io/j-quants-en/api-reference/futures DERIVATIVES_FUTURES_COLUMNS = [ "Date", "Code", @@ -551,6 +583,8 @@ "CentralContractMonthFlag", ] +# ref. ja https://jpx.gitbook.io/j-quants-ja/api-reference/options +# ref. en https://jpx.gitbook.io/j-quants-en/api-reference/options DERIVATIVES_OPTIONS_COLUMNS = [ "Date", "Code", @@ -591,6 +625,8 @@ "CentralContractMonthFlag", ] +# ref. ja https://jpx.gitbook.io/j-quants-ja/api-reference/short_selling_positions +# ref. en https://jpx.gitbook.io/j-quants-en/api-reference/short_selling_positions SHORT_SELLING_POSITIONS_COLUMNS = [ "DisclosedDate", "CalculatedDate", @@ -637,3 +673,537 @@ "DailyChangeLongStandardizedMarginOutstanding", "TSEMarginBorrowingAndLendingRegulationClassification", ] + + +# ============================================================================ +# V2 API用カラム定義 +# ============================================================================ + +# ref. ja https://jpx-jquants.com/ja/spec/eq-master/sector17code +# ref. en https://jpx-jquants.com/en/spec/eq-master/sector17code +SECTOR_17_COLUMNS_V2 = ["S17", "S17Nm", "S17NmEn"] + +# ref. ja https://jpx-jquants.com/ja/spec/eq-master/sector33code +# ref. en https://jpx-jquants.com/en/spec/eq-master/sector33code +SECTOR_33_COLUMNS_V2 = ["S33", "S33Nm", "S33NmEn", "S17"] + +# ref. ja https://jpx-jquants.com/ja/spec/eq-master/marketcode +# ref. en https://jpx-jquants.com/en/spec/eq-master/marketcode +MARKET_SEGMENT_COLUMNS_V2 = ["Mkt", "MktNm", "MktNmEn"] + +# ref. ja https://jpx-jquants.com/ja/spec/eq-master +# ref. en https://jpx-jquants.com/en/spec/eq-master +EQ_MASTER_COLUMNS_V2 = [ + "Date", + "Code", + "CoName", + "CoNameEn", + "S17", + "S17Nm", + "S33", + "S33Nm", + "ScaleCat", + "Mkt", + "MktNm", + "Mrgn", + "MrgnNm", +] + +# ref. ja https://jpx-jquants.com/ja/spec/eq-bars-daily +# ref. en https://jpx-jquants.com/en/spec/eq-bars-daily +EQ_BARS_DAILY_COLUMNS_V2 = [ + "Date", + "Code", + "O", + "H", + "L", + "C", + "UL", + "LL", + "Vo", + "Va", + "AdjFactor", + "AdjO", + "AdjH", + "AdjL", + "AdjC", + "AdjVo", + "MO", + "MH", + "ML", + "MC", + "MUL", + "MLL", + "MVo", + "MVa", + "MAdjO", + "MAdjH", + "MAdjL", + "MAdjC", + "MAdjVo", + "AO", + "AH", + "AL", + "AC", + "AUL", + "ALL", + "AVo", + "AVa", + "AAdjO", + "AAdjH", + "AAdjL", + "AAdjC", + "AAdjVo", +] + +# ref. ja https://jpx-jquants.com/ja/spec/eq-bars-minute +# ref. en https://jpx-jquants.com/en/spec/eq-bars-minute +EQ_BARS_MINUTE_COLUMNS_V2 = [ + "Date", + "Time", + "Code", + "O", + "H", + "L", + "C", + "Vo", + "Va", +] + +# ref. ja https://jpx-jquants.com/ja/spec/eq-bars-daily-am +# ref. en https://jpx-jquants.com/en/spec/eq-bars-daily-am +PRICES_PRICES_AM_COLUMNS_V2 = [ + "Date", + "Code", + "MO", + "MH", + "ML", + "MC", + "MVo", + "MVa", +] + +# ref. ja https://jpx-jquants.com/ja/spec/idx-bars-daily +# ref. en https://jpx-jquants.com/en/spec/idx-bars-daily +INDICES_COLUMNS_V2 = [ + "Date", + "Code", + "O", + "H", + "L", + "C", +] + +# ref. ja https://jpx-jquants.com/ja/spec/idx-bars-daily-topix +# ref. en https://jpx-jquants.com/en/spec/idx-bars-daily-topix +INDICES_TOPIX_COLUMNS_V2 = [ + "Date", + "O", + "H", + "L", + "C", +] + +# ref. ja https://jpx-jquants.com/ja/spec/eq-investor-types +# ref. en https://jpx-jquants.com/en/spec/eq-investor-types +EQ_INVESTOR_TYPES_COLUMNS_V2 = [ + "PubDate", + "StDate", + "EnDate", + "Section", + "PropSell", + "PropBuy", + "PropTot", + "PropBal", + "BrkSell", + "BrkBuy", + "BrkTot", + "BrkBal", + "TotSell", + "TotBuy", + "TotTot", + "TotBal", + "IndSell", + "IndBuy", + "IndTot", + "IndBal", + "FrgnSell", + "FrgnBuy", + "FrgnTot", + "FrgnBal", + "SecCoSell", + "SecCoBuy", + "SecCoTot", + "SecCoBal", + "InvTrSell", + "InvTrBuy", + "InvTrTot", + "InvTrBal", + "BusCoSell", + "BusCoBuy", + "BusCoTot", + "BusCoBal", + "OthCoSell", + "OthCoBuy", + "OthCoTot", + "OthCoBal", + "InsCoSell", + "InsCoBuy", + "InsCoTot", + "InsCoBal", + "BankSell", + "BankBuy", + "BankTot", + "BankBal", + "TrstBnkSell", + "TrstBnkBuy", + "TrstBnkTot", + "TrstBnkBal", + "OthFinSell", + "OthFinBuy", + "OthFinTot", + "OthFinBal", +] + +# ref. ja https://jpx-jquants.com/ja/spec/mkt-margin-int +# ref. en https://jpx-jquants.com/en/spec/mkt-margin-int +MARKETS_WEEKLY_MARGIN_INTEREST_COLUMNS_V2 = [ + "Date", + "Code", + "ShrtVol", + "LongVol", + "ShrtNegVol", + "LongNegVol", + "ShrtStdVol", + "LongStdVol", + "IssType", +] + +# ref. ja https://jpx-jquants.com/ja/spec/mkt-short-ratio +# ref. en https://jpx-jquants.com/en/spec/mkt-short-ratio +MKT_SHORT_RATIO_COLUMNS_V2 = [ + "Date", + "S33", + "SellExShortVa", + "ShrtWithResVa", + "ShrtNoResVa", +] + +# ref. ja https://jpx-jquants.com/ja/spec/mkt-breakdown +# ref. en https://jpx-jquants.com/en/spec/mkt-breakdown +MKT_BREAKDOWN_COLUMNS_V2 = [ + "Date", + "Code", + "LongSellVa", + "ShrtNoMrgnVa", + "MrgnSellNewVa", + "MrgnSellCloseVa", + "LongBuyVa", + "MrgnBuyNewVa", + "MrgnBuyCloseVa", + "LongSellVo", + "ShrtNoMrgnVo", + "MrgnSellNewVo", + "MrgnSellCloseVo", + "LongBuyVo", + "MrgnBuyNewVo", + "MrgnBuyCloseVo", +] + +# ref. ja https://jpx-jquants.com/ja/spec/mkt-cal +# ref. en https://jpx-jquants.com/en/spec/mkt-cal +MARKETS_TRADING_CALENDAR_COLUMNS_V2 = [ + "Date", + "HolDiv", +] + +# ref. ja https://jpx-jquants.com/ja/spec/fin-summary +# ref. en https://jpx-jquants.com/en/spec/fin-summary +FIN_SUMMARY_COLUMNS_V2 = [ + "DiscDate", + "DiscTime", + "Code", + "DiscNo", + "DocType", + "CurPerType", + "CurPerSt", + "CurPerEn", + "CurFYSt", + "CurFYEn", + "NxtFYSt", + "NxtFYEn", + "Sales", + "OP", + "OdP", + "NP", + "EPS", + "DEPS", + "TA", + "Eq", + "EqAR", + "BPS", + "CFO", + "CFI", + "CFF", + "CashEq", + "Div1Q", + "Div2Q", + "Div3Q", + "DivFY", + "DivAnn", + "DivUnit", + "DivTotalAnn", + "PayoutRatioAnn", + "FDiv1Q", + "FDiv2Q", + "FDiv3Q", + "FDivFY", + "FDivAnn", + "FDivUnit", + "FDivTotalAnn", + "FPayoutRatioAnn", + "NxFDiv1Q", + "NxFDiv2Q", + "NxFDiv3Q", + "NxFDivFY", + "NxFDivAnn", + "NxFDivUnit", + "NxFPayoutRatioAnn", + "FSales2Q", + "FOP2Q", + "FOdP2Q", + "FNP2Q", + "FEPS2Q", + "NxFSales2Q", + "NxFOP2Q", + "NxFOdP2Q", + "NxFNp2Q", + "NxFEPS2Q", + "FSales", + "FOP", + "FOdP", + "FNP", + "FEPS", + "NxFSales", + "NxFOP", + "NxFOdP", + "NxFNp", + "NxFEPS", + "MatChgSub", + "SigChgInC", + "ChgByASRev", + "ChgNoASRev", + "ChgAcEst", + "RetroRst", + "ShOutFY", + "TrShFY", + "AvgSh", + "NCSales", + "NCOP", + "NCOdP", + "NCNP", + "NCEPS", + "NCTA", + "NCEq", + "NCEqAR", + "NCBPS", + "FNCSales2Q", + "FNCOP2Q", + "FNCOdP2Q", + "FNCNP2Q", + "FNCEPS2Q", + "NxFNCSales2Q", + "NxFNCOP2Q", + "NxFNCOdP2Q", + "NxFNCNP2Q", + "NxFNCEPS2Q", + "FNCSales", + "FNCOP", + "FNCOdP", + "FNCNP", + "FNCEPS", + "NxFNCSales", + "NxFNCOP", + "NxFNCOdP", + "NxFNCNP", + "NxFNCEPS", +] + +# ref. ja https://jpx-jquants.com/ja/spec/fin-details +# ref. en https://jpx-jquants.com/en/spec/fin-details +FINS_FS_DETAILS_COLUMNS_V2 = [ + "DiscDate", + "DiscTime", + "Code", + "DiscNo", + "DocType", + "FS", +] + +# ref. ja https://jpx-jquants.com/ja/spec/fin-dividend +# ref. en https://jpx-jquants.com/en/spec/fin-dividend +FINS_DIVIDEND_COLUMNS_V2 = [ + "PubDate", + "PubTime", + "Code", + "RefNo", + "StatCode", + "BoardDate", + "IFCode", + "FRCode", + "IFTerm", + "DivRate", + "RecDate", + "ExDate", + "ActRecDate", + "PayDate", + "CARefNo", + "DistAmt", + "RetEarn", + "DeemDiv", + "DeemCapGains", + "NetAssetDecRatio", + "CommSpecCode", + "CommDivRate", + "SpecDivRate", +] + +# ref. ja https://jpx-jquants.com/ja/spec/eq-earnings-cal +# ref. en https://jpx-jquants.com/en/spec/eq-earnings-cal +FINS_ANNOUNCEMENT_COLUMNS_V2 = [ + "Date", + "Code", + "CoName", + "FY", + "SectorNm", + "FQ", + "Section", +] + +# ref. ja https://jpx-jquants.com/ja/spec/drv-bars-daily-fut +# ref. en https://jpx-jquants.com/en/spec/drv-bars-daily-fut +DERIVATIVES_FUTURES_COLUMNS_V2 = [ + "Code", + "ProdCat", + "Date", + "O", + "H", + "L", + "C", + "MO", + "MH", + "ML", + "MC", + "EO", + "EH", + "EL", + "EC", + "AO", + "AH", + "AL", + "AC", + "Vo", + "OI", + "Va", + "CM", + "VoOA", + "EmMrgnTrgDiv", + "LTD", + "SQD", + "Settle", + "CCMFlag", +] + +# ref. ja https://jpx-jquants.com/ja/spec/drv-bars-daily-opt +# ref. en https://jpx-jquants.com/en/spec/drv-bars-daily-opt +DERIVATIVES_OPTIONS_COLUMNS_V2 = [ + "Code", + "ProdCat", + "UndSSO", + "Date", + "O", + "H", + "L", + "C", + "MO", + "MH", + "ML", + "MC", + "EO", + "EH", + "EL", + "EC", + "AO", + "AH", + "AL", + "AC", + "Vo", + "OI", + "Va", + "CM", + "Strike", + "VoOA", + "EmMrgnTrgDiv", + "PCDiv", + "LTD", + "SQD", + "Settle", + "Theo", + "BaseVol", + "UnderPx", + "IV", + "IR", + "CCMFlag", +] + +# ref. ja https://jpx-jquants.com/ja/spec/mkt-short-sale +# ref. en https://jpx-jquants.com/en/spec/mkt-short-sale +SHORT_SELLING_POSITIONS_COLUMNS_V2 = [ + "DiscDate", + "CalcDate", + "Code", + "SSName", + "SSAddr", + "DICName", + "DICAddr", + "FundName", + "ShrtPosToSO", + "ShrtPosShares", + "ShrtPosUnits", + "PrevRptDate", + "PrevRptRatio", + "Notes", +] + +# ref. ja https://jpx-jquants.com/ja/spec/mkt-margin-alert +# ref. en https://jpx-jquants.com/en/spec/mkt-margin-alert +DAILY_MARGIN_INTEREST_COLUMNS_V2 = [ + "PubDate", + "Code", + "AppDate", + "PubReason", + "ShrtOut", + "ShrtOutChg", + "ShrtOutRatio", + "LongOut", + "LongOutChg", + "LongOutRatio", + "SLRatio", + "ShrtNegOut", + "ShrtNegOutChg", + "ShrtStdOut", + "ShrtStdOutChg", + "LongNegOut", + "LongNegOutChg", + "LongStdOut", + "LongStdOutChg", + "TSEMrgnRegCls", +] + +# ref. ja https://jpx-jquants.com/ja/spec/bulk-list +# ref. en https://jpx-jquants.com/en/spec/bulk-list +BULK_LIST_COLUMNS_V2 = [ + "Key", + "Size", + "LastModified", +] diff --git a/jquantsapi/enums.py b/jquantsapi/enums.py index 50a3307..1fada35 100644 --- a/jquantsapi/enums.py +++ b/jquantsapi/enums.py @@ -5,8 +5,8 @@ class MARKET_API_SECTIONS(str, Enum): """ values of sections for market api - ref. (en) https://jpx.gitbook.io/j-quants-api-en/api-reference/markets-api/section-code - ref. (ja) https://jpx.gitbook.io/j-quants-api/api-reference/market-api/sector_name + ref. (ja) https://jpx-jquants.com/ja/spec/eq-investor-types/section + ref. (en) https://jpx-jquants.com/en/spec/eq-investor-types/section """ TSE1st = "TSE1st" @@ -17,3 +17,50 @@ class MARKET_API_SECTIONS(str, Enum): TSEStandard = "TSEStandard" TSEGrowth = "TSEGrowth" TokyoNagoya = "TokyoNagoya" + + +class BulkEndpoint(str, Enum): + """ + Bulk APIで指定可能なエンドポイント + """ + + # 上場銘柄一覧API + EQ_MASTER = "/equities/master" + # 株価四本値API + EQ_BARS_DAILY = "/equities/bars/daily" + # 分足API + EQ_BARS_MINUTE = "/equities/bars/minute" + # 投資部門別売買状況API + EQ_INVESTOR_TYPES = "/equities/investor-types" + # TickデータAPI + EQ_TRADES = "/equities/trades" + + # 財務情報API + FIN_SUMMARY = "/fins/summary" + # 財務諸表API + FIN_DETAILS = "/fins/details" + # 配当金情報API + FIN_DIVIDEND = "/fins/dividend" + + # 業種別空売り比率API + MKT_SHORT_RATIO = "/markets/short-ratio" + # 空売り残高報告API + MKT_SHORT_SALE_REPORT = "/markets/short-sale-report" + # 信用取引週末残高API + MKT_MARGIN_INTEREST = "/markets/margin-interest" + # 日々公表信用取引残高API + MKT_MARGIN_ALERT = "/markets/margin-alert" + # 売買内訳データAPI + MKT_BREAKDOWN = "/markets/breakdown" + + # 指数四本値API + IDX_BARS_DAILY = "/indices/bars/daily" + # TOPIX指数四本値API + IDX_BARS_DAILY_TOPIX = "/indices/bars/daily/topix" + + # 先物四本値API + DRV_BARS_DAILY_FUT = "/derivatives/bars/daily/futures" + # オプション四本値API + DRV_BARS_DAILY_OPT = "/derivatives/bars/daily/options" + # 日経225オプション四本値API + DRV_BARS_DAILY_OPT_225 = "/derivatives/bars/daily/options/225" diff --git a/poetry.lock b/poetry.lock index 3960eeb..fc95950 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "black" @@ -6,6 +6,7 @@ version = "24.3.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"}, {file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"}, @@ -42,7 +43,7 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +d = ["aiohttp (>=3.7.4) ; sys_platform != \"win32\" or implementation_name != \"pypy\"", "aiohttp (>=3.7.4,!=3.9.0) ; sys_platform == \"win32\" and implementation_name == \"pypy\""] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -52,6 +53,7 @@ version = "2024.2.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, @@ -63,6 +65,7 @@ version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" +groups = ["main"] files = [ {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, @@ -162,6 +165,7 @@ version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, @@ -176,6 +180,8 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +markers = "platform_system == \"Windows\" or sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -187,6 +193,7 @@ version = "7.4.4" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "coverage-7.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2"}, {file = "coverage-7.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf"}, @@ -246,7 +253,7 @@ files = [ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] -toml = ["tomli"] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "exceptiongroup" @@ -254,6 +261,8 @@ version = "1.2.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, @@ -268,6 +277,7 @@ version = "5.0.4" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.6.1" +groups = ["dev"] files = [ {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, @@ -278,12 +288,31 @@ mccabe = ">=0.7.0,<0.8.0" pycodestyle = ">=2.9.0,<2.10.0" pyflakes = ">=2.5.0,<2.6.0" +[[package]] +name = "flake8-pyproject" +version = "1.2.4" +description = "Flake8 plug-in loading the configuration from pyproject.toml" +optional = false +python-versions = ">=3.6" +groups = ["dev"] +files = [ + {file = "flake8_pyproject-1.2.4-py3-none-any.whl", hash = "sha256:ea34c057f9a9329c76d98723bb2bb498cc6ba8ff9872c4d19932d48c91249a77"}, +] + +[package.dependencies] +Flake8 = ">=5" +TOMLi = {version = "*", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["Flit (>=3.4)", "pyTest (>=7)", "pyTest-cov (>=7) ; python_version >= \"3.10\""] + [[package]] name = "idna" version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" +groups = ["main"] files = [ {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, @@ -295,6 +324,7 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -306,6 +336,7 @@ version = "5.13.2" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.8.0" +groups = ["dev"] files = [ {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, @@ -320,6 +351,7 @@ version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, @@ -331,6 +363,7 @@ version = "1.9.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"}, {file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"}, @@ -378,6 +411,7 @@ version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, @@ -385,84 +419,151 @@ files = [ [[package]] name = "numpy" -version = "1.24.4" +version = "2.2.6" description = "Fundamental package for array computing in Python" optional = false -python-versions = ">=3.8" -files = [ - {file = "numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64"}, - {file = "numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1"}, - {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4"}, - {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6"}, - {file = "numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc"}, - {file = "numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e"}, - {file = "numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810"}, - {file = "numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254"}, - {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7"}, - {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5"}, - {file = "numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d"}, - {file = "numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694"}, - {file = "numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61"}, - {file = "numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f"}, - {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e"}, - {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc"}, - {file = "numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2"}, - {file = "numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706"}, - {file = "numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400"}, - {file = "numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f"}, - {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9"}, - {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d"}, - {file = "numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835"}, - {file = "numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2"}, - {file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"}, +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version == \"3.10\"" +files = [ + {file = "numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb"}, + {file = "numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90"}, + {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163"}, + {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf"}, + {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83"}, + {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915"}, + {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680"}, + {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289"}, + {file = "numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d"}, + {file = "numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3"}, + {file = "numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae"}, + {file = "numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a"}, + {file = "numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42"}, + {file = "numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491"}, + {file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a"}, + {file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf"}, + {file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1"}, + {file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab"}, + {file = "numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47"}, + {file = "numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303"}, + {file = "numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff"}, + {file = "numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c"}, + {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3"}, + {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282"}, + {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87"}, + {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249"}, + {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49"}, + {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de"}, + {file = "numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4"}, + {file = "numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2"}, + {file = "numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84"}, + {file = "numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b"}, + {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d"}, + {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566"}, + {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f"}, + {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f"}, + {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868"}, + {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d"}, + {file = "numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd"}, + {file = "numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c"}, + {file = "numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6"}, + {file = "numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda"}, + {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40"}, + {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8"}, + {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f"}, + {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa"}, + {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571"}, + {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1"}, + {file = "numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff"}, + {file = "numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06"}, + {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d"}, + {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db"}, + {file = "numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543"}, + {file = "numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00"}, + {file = "numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd"}, ] [[package]] name = "numpy" -version = "1.26.4" +version = "2.4.0" description = "Fundamental package for array computing in Python" optional = false -python-versions = ">=3.9" -files = [ - {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, - {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, - {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, - {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, - {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, - {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, - {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, - {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, - {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, - {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, - {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, +python-versions = ">=3.11" +groups = ["main"] +markers = "python_version >= \"3.11\"" +files = [ + {file = "numpy-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:316b2f2584682318539f0bcaca5a496ce9ca78c88066579ebd11fd06f8e4741e"}, + {file = "numpy-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2718c1de8504121714234b6f8241d0019450353276c88b9453c9c3d92e101db"}, + {file = "numpy-2.4.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:21555da4ec4a0c942520ead42c3b0dc9477441e085c42b0fbdd6a084869a6f6b"}, + {file = "numpy-2.4.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:413aa561266a4be2d06cd2b9665e89d9f54c543f418773076a76adcf2af08bc7"}, + {file = "numpy-2.4.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0feafc9e03128074689183031181fac0897ff169692d8492066e949041096548"}, + {file = "numpy-2.4.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8fdfed3deaf1928fb7667d96e0567cdf58c2b370ea2ee7e586aa383ec2cb346"}, + {file = "numpy-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e06a922a469cae9a57100864caf4f8a97a1026513793969f8ba5b63137a35d25"}, + {file = "numpy-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:927ccf5cd17c48f801f4ed43a7e5673a2724bd2171460be3e3894e6e332ef83a"}, + {file = "numpy-2.4.0-cp311-cp311-win32.whl", hash = "sha256:882567b7ae57c1b1a0250208cc21a7976d8cbcc49d5a322e607e6f09c9e0bd53"}, + {file = "numpy-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:8b986403023c8f3bf8f487c2e6186afda156174d31c175f747d8934dfddf3479"}, + {file = "numpy-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:3f3096405acc48887458bbf9f6814d43785ac7ba2a57ea6442b581dedbc60ce6"}, + {file = "numpy-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2a8b6bb8369abefb8bd1801b054ad50e02b3275c8614dc6e5b0373c305291037"}, + {file = "numpy-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e284ca13d5a8367e43734148622caf0b261b275673823593e3e3634a6490f83"}, + {file = "numpy-2.4.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:49ff32b09f5aa0cd30a20c2b39db3e669c845589f2b7fc910365210887e39344"}, + {file = "numpy-2.4.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:36cbfb13c152b1c7c184ddac43765db8ad672567e7bafff2cc755a09917ed2e6"}, + {file = "numpy-2.4.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35ddc8f4914466e6fc954c76527aa91aa763682a4f6d73249ef20b418fe6effb"}, + {file = "numpy-2.4.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc578891de1db95b2a35001b695451767b580bb45753717498213c5ff3c41d63"}, + {file = "numpy-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98e81648e0b36e325ab67e46b5400a7a6d4a22b8a7c8e8bbfe20e7db7906bf95"}, + {file = "numpy-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d57b5046c120561ba8fa8e4030fbb8b822f3063910fa901ffadf16e2b7128ad6"}, + {file = "numpy-2.4.0-cp312-cp312-win32.whl", hash = "sha256:92190db305a6f48734d3982f2c60fa30d6b5ee9bff10f2887b930d7b40119f4c"}, + {file = "numpy-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:680060061adb2d74ce352628cb798cfdec399068aa7f07ba9fb818b2b3305f98"}, + {file = "numpy-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:39699233bc72dd482da1415dcb06076e32f60eddc796a796c5fb6c5efce94667"}, + {file = "numpy-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a152d86a3ae00ba5f47b3acf3b827509fd0b6cb7d3259665e63dafbad22a75ea"}, + {file = "numpy-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:39b19251dec4de8ff8496cd0806cbe27bf0684f765abb1f4809554de93785f2d"}, + {file = "numpy-2.4.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:009bd0ea12d3c784b6639a8457537016ce5172109e585338e11334f6a7bb88ee"}, + {file = "numpy-2.4.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5fe44e277225fd3dff6882d86d3d447205d43532c3627313d17e754fb3905a0e"}, + {file = "numpy-2.4.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f935c4493eda9069851058fa0d9e39dbf6286be690066509305e52912714dbb2"}, + {file = "numpy-2.4.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cfa5f29a695cb7438965e6c3e8d06e0416060cf0d709c1b1c1653a939bf5c2a"}, + {file = "numpy-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ba0cb30acd3ef11c94dc27fbfba68940652492bc107075e7ffe23057f9425681"}, + {file = "numpy-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:60e8c196cd82cbbd4f130b5290007e13e6de3eca79f0d4d38014769d96a7c475"}, + {file = "numpy-2.4.0-cp313-cp313-win32.whl", hash = "sha256:5f48cb3e88fbc294dc90e215d86fbaf1c852c63dbdb6c3a3e63f45c4b57f7344"}, + {file = "numpy-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:a899699294f28f7be8992853c0c60741f16ff199205e2e6cdca155762cbaa59d"}, + {file = "numpy-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:9198f447e1dc5647d07c9a6bbe2063cc0132728cc7175b39dbc796da5b54920d"}, + {file = "numpy-2.4.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74623f2ab5cc3f7c886add4f735d1031a1d2be4a4ae63c0546cfd74e7a31ddf6"}, + {file = "numpy-2.4.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:0804a8e4ab070d1d35496e65ffd3cf8114c136a2b81f61dfab0de4b218aacfd5"}, + {file = "numpy-2.4.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:02a2038eb27f9443a8b266a66911e926566b5a6ffd1a689b588f7f35b81e7dc3"}, + {file = "numpy-2.4.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1889b3a3f47a7b5bee16bc25a2145bd7cb91897f815ce3499db64c7458b6d91d"}, + {file = "numpy-2.4.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85eef4cb5625c47ee6425c58a3502555e10f45ee973da878ac8248ad58c136f3"}, + {file = "numpy-2.4.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6dc8b7e2f4eb184b37655195f421836cfae6f58197b67e3ffc501f1333d993fa"}, + {file = "numpy-2.4.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:44aba2f0cafd287871a495fb3163408b0bd25bbce135c6f621534a07f4f7875c"}, + {file = "numpy-2.4.0-cp313-cp313t-win32.whl", hash = "sha256:20c115517513831860c573996e395707aa9fb691eb179200125c250e895fcd93"}, + {file = "numpy-2.4.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b48e35f4ab6f6a7597c46e301126ceba4c44cd3280e3750f85db48b082624fa4"}, + {file = "numpy-2.4.0-cp313-cp313t-win_arm64.whl", hash = "sha256:4d1cfce39e511069b11e67cd0bd78ceff31443b7c9e5c04db73c7a19f572967c"}, + {file = "numpy-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c95eb6db2884917d86cde0b4d4cf31adf485c8ec36bf8696dd66fa70de96f36b"}, + {file = "numpy-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:65167da969cd1ec3a1df31cb221ca3a19a8aaa25370ecb17d428415e93c1935e"}, + {file = "numpy-2.4.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3de19cfecd1465d0dcf8a5b5ea8b3155b42ed0b639dba4b71e323d74f2a3be5e"}, + {file = "numpy-2.4.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6c05483c3136ac4c91b4e81903cb53a8707d316f488124d0398499a4f8e8ef51"}, + {file = "numpy-2.4.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36667db4d6c1cea79c8930ab72fadfb4060feb4bfe724141cd4bd064d2e5f8ce"}, + {file = "numpy-2.4.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9a818668b674047fd88c4cddada7ab8f1c298812783e8328e956b78dc4807f9f"}, + {file = "numpy-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1ee32359fb7543b7b7bd0b2f46294db27e29e7bbdf70541e81b190836cd83ded"}, + {file = "numpy-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e493962256a38f58283de033d8af176c5c91c084ea30f15834f7545451c42059"}, + {file = "numpy-2.4.0-cp314-cp314-win32.whl", hash = "sha256:6bbaebf0d11567fa8926215ae731e1d58e6ec28a8a25235b8a47405d301332db"}, + {file = "numpy-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d857f55e7fdf7c38ab96c4558c95b97d1c685be6b05c249f5fdafcbd6f9899e"}, + {file = "numpy-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:bb50ce5fb202a26fd5404620e7ef820ad1ab3558b444cb0b55beb7ef66cd2d63"}, + {file = "numpy-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:355354388cba60f2132df297e2d53053d4063f79077b67b481d21276d61fc4df"}, + {file = "numpy-2.4.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:1d8f9fde5f6dc1b6fc34df8162f3b3079365468703fee7f31d4e0cc8c63baed9"}, + {file = "numpy-2.4.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e0434aa22c821f44eeb4c650b81c7fbdd8c0122c6c4b5a576a76d5a35625ecd9"}, + {file = "numpy-2.4.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40483b2f2d3ba7aad426443767ff5632ec3156ef09742b96913787d13c336471"}, + {file = "numpy-2.4.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6a7664ddd9746e20b7325351fe1a8408d0a2bf9c63b5e898290ddc8f09544"}, + {file = "numpy-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ecb0019d44f4cdb50b676c5d0cb4b1eae8e15d1ed3d3e6639f986fc92b2ec52c"}, + {file = "numpy-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d0ffd9e2e4441c96a9c91ec1783285d80bf835b677853fc2770a89d50c1e48ac"}, + {file = "numpy-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:77f0d13fa87036d7553bf81f0e1fe3ce68d14c9976c9851744e4d3e91127e95f"}, + {file = "numpy-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b1f5b45829ac1848893f0ddf5cb326110604d6df96cdc255b0bf9edd154104d4"}, + {file = "numpy-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:23a3e9d1a6f360267e8fbb38ba5db355a6a7e9be71d7fce7ab3125e88bb646c8"}, + {file = "numpy-2.4.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b54c83f1c0c0f1d748dca0af516062b8829d53d1f0c402be24b4257a9c48ada6"}, + {file = "numpy-2.4.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:aabb081ca0ec5d39591fc33018cd4b3f96e1a2dd6756282029986d00a785fba4"}, + {file = "numpy-2.4.0-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:8eafe7c36c8430b7794edeab3087dec7bf31d634d92f2af9949434b9d1964cba"}, + {file = "numpy-2.4.0-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2f585f52b2baf07ff3356158d9268ea095e221371f1074fadea2f42544d58b4d"}, + {file = "numpy-2.4.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32ed06d0fe9cae27d8fb5f400c63ccee72370599c75e683a6358dd3a4fb50aaf"}, + {file = "numpy-2.4.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:57c540ed8fb1f05cb997c6761cd56db72395b0d6985e90571ff660452ade4f98"}, + {file = "numpy-2.4.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a39fb973a726e63223287adc6dafe444ce75af952d711e400f3bf2b36ef55a7b"}, + {file = "numpy-2.4.0.tar.gz", hash = "sha256:6e504f7b16118198f138ef31ba24d985b124c2c469fe8467007cf30fd992f934"}, ] [[package]] @@ -471,6 +572,7 @@ version = "24.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, @@ -478,70 +580,103 @@ files = [ [[package]] name = "pandas" -version = "2.0.3" +version = "2.3.3" description = "Powerful data structures for data analysis, time series, and statistics" optional = false -python-versions = ">=3.8" -files = [ - {file = "pandas-2.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8"}, - {file = "pandas-2.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f"}, - {file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce0c6f76a0f1ba361551f3e6dceaff06bde7514a374aa43e33b588ec10420183"}, - {file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba619e410a21d8c387a1ea6e8a0e49bb42216474436245718d7f2e88a2f8d7c0"}, - {file = "pandas-2.0.3-cp310-cp310-win32.whl", hash = "sha256:3ef285093b4fe5058eefd756100a367f27029913760773c8bf1d2d8bebe5d210"}, - {file = "pandas-2.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:9ee1a69328d5c36c98d8e74db06f4ad518a1840e8ccb94a4ba86920986bb617e"}, - {file = "pandas-2.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b084b91d8d66ab19f5bb3256cbd5ea661848338301940e17f4492b2ce0801fe8"}, - {file = "pandas-2.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37673e3bdf1551b95bf5d4ce372b37770f9529743d2498032439371fc7b7eb26"}, - {file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9cb1e14fdb546396b7e1b923ffaeeac24e4cedd14266c3497216dd4448e4f2d"}, - {file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9cd88488cceb7635aebb84809d087468eb33551097d600c6dad13602029c2df"}, - {file = "pandas-2.0.3-cp311-cp311-win32.whl", hash = "sha256:694888a81198786f0e164ee3a581df7d505024fbb1f15202fc7db88a71d84ebd"}, - {file = "pandas-2.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6a21ab5c89dcbd57f78d0ae16630b090eec626360085a4148693def5452d8a6b"}, - {file = "pandas-2.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4da0d45e7f34c069fe4d522359df7d23badf83abc1d1cef398895822d11061"}, - {file = "pandas-2.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:32fca2ee1b0d93dd71d979726b12b61faa06aeb93cf77468776287f41ff8fdc5"}, - {file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:258d3624b3ae734490e4d63c430256e716f488c4fcb7c8e9bde2d3aa46c29089"}, - {file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eae3dc34fa1aa7772dd3fc60270d13ced7346fcbcfee017d3132ec625e23bb0"}, - {file = "pandas-2.0.3-cp38-cp38-win32.whl", hash = "sha256:f3421a7afb1a43f7e38e82e844e2bca9a6d793d66c1a7f9f0ff39a795bbc5e02"}, - {file = "pandas-2.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:69d7f3884c95da3a31ef82b7618af5710dba95bb885ffab339aad925c3e8ce78"}, - {file = "pandas-2.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5247fb1ba347c1261cbbf0fcfba4a3121fbb4029d95d9ef4dc45406620b25c8b"}, - {file = "pandas-2.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81af086f4543c9d8bb128328b5d32e9986e0c84d3ee673a2ac6fb57fd14f755e"}, - {file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1994c789bf12a7c5098277fb43836ce090f1073858c10f9220998ac74f37c69b"}, - {file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec591c48e29226bcbb316e0c1e9423622bc7a4eaf1ef7c3c9fa1a3981f89641"}, - {file = "pandas-2.0.3-cp39-cp39-win32.whl", hash = "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682"}, - {file = "pandas-2.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:1168574b036cd8b93abc746171c9b4f1b83467438a5e45909fed645cf8692dbc"}, - {file = "pandas-2.0.3.tar.gz", hash = "sha256:c02f372a88e0d17f36d3093a644c73cfc1788e876a7c4bcb4020a77512e2043c"}, +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c"}, + {file = "pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a"}, + {file = "pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1"}, + {file = "pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838"}, + {file = "pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250"}, + {file = "pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4"}, + {file = "pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826"}, + {file = "pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523"}, + {file = "pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45"}, + {file = "pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66"}, + {file = "pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b"}, + {file = "pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791"}, + {file = "pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151"}, + {file = "pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c"}, + {file = "pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53"}, + {file = "pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35"}, + {file = "pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908"}, + {file = "pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89"}, + {file = "pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98"}, + {file = "pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084"}, + {file = "pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b"}, + {file = "pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713"}, + {file = "pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8"}, + {file = "pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d"}, + {file = "pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac"}, + {file = "pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c"}, + {file = "pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493"}, + {file = "pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee"}, + {file = "pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5"}, + {file = "pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21"}, + {file = "pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78"}, + {file = "pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110"}, + {file = "pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86"}, + {file = "pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc"}, + {file = "pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0"}, + {file = "pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593"}, + {file = "pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c"}, + {file = "pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b"}, + {file = "pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6"}, + {file = "pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3"}, + {file = "pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5"}, + {file = "pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec"}, + {file = "pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7"}, + {file = "pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450"}, + {file = "pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5"}, + {file = "pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788"}, + {file = "pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87"}, + {file = "pandas-2.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c503ba5216814e295f40711470446bc3fd00f0faea8a086cbc688808e26f92a2"}, + {file = "pandas-2.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a637c5cdfa04b6d6e2ecedcb81fc52ffb0fd78ce2ebccc9ea964df9f658de8c8"}, + {file = "pandas-2.3.3-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:854d00d556406bffe66a4c0802f334c9ad5a96b4f1f868adf036a21b11ef13ff"}, + {file = "pandas-2.3.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf1f8a81d04ca90e32a0aceb819d34dbd378a98bf923b6398b9a3ec0bf44de29"}, + {file = "pandas-2.3.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:23ebd657a4d38268c7dfbdf089fbc31ea709d82e4923c5ffd4fbd5747133ce73"}, + {file = "pandas-2.3.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5554c929ccc317d41a5e3d1234f3be588248e61f08a74dd17c9eabb535777dc9"}, + {file = "pandas-2.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:d3e28b3e83862ccf4d85ff19cf8c20b2ae7e503881711ff2d534dc8f761131aa"}, + {file = "pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b"}, ] [package.dependencies] numpy = [ - {version = ">=1.20.3", markers = "python_version < \"3.10\""}, - {version = ">=1.21.0", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, - {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, + {version = ">=1.22.4", markers = "python_version < \"3.11\""}, + {version = ">=1.23.2", markers = "python_version == \"3.11\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" -tzdata = ">=2022.1" +tzdata = ">=2022.7" [package.extras] -all = ["PyQt5 (>=5.15.1)", "SQLAlchemy (>=1.4.16)", "beautifulsoup4 (>=4.9.3)", "bottleneck (>=1.3.2)", "brotlipy (>=0.7.0)", "fastparquet (>=0.6.3)", "fsspec (>=2021.07.0)", "gcsfs (>=2021.07.0)", "html5lib (>=1.1)", "hypothesis (>=6.34.2)", "jinja2 (>=3.0.0)", "lxml (>=4.6.3)", "matplotlib (>=3.6.1)", "numba (>=0.53.1)", "numexpr (>=2.7.3)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pandas-gbq (>=0.15.0)", "psycopg2 (>=2.8.6)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "python-snappy (>=0.6.0)", "pyxlsb (>=1.0.8)", "qtpy (>=2.2.0)", "s3fs (>=2021.08.0)", "scipy (>=1.7.1)", "tables (>=3.6.1)", "tabulate (>=0.8.9)", "xarray (>=0.21.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)", "zstandard (>=0.15.2)"] -aws = ["s3fs (>=2021.08.0)"] -clipboard = ["PyQt5 (>=5.15.1)", "qtpy (>=2.2.0)"] -compression = ["brotlipy (>=0.7.0)", "python-snappy (>=0.6.0)", "zstandard (>=0.15.2)"] -computation = ["scipy (>=1.7.1)", "xarray (>=0.21.0)"] -excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pyxlsb (>=1.0.8)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)"] -feather = ["pyarrow (>=7.0.0)"] -fss = ["fsspec (>=2021.07.0)"] -gcp = ["gcsfs (>=2021.07.0)", "pandas-gbq (>=0.15.0)"] -hdf5 = ["tables (>=3.6.1)"] -html = ["beautifulsoup4 (>=4.9.3)", "html5lib (>=1.1)", "lxml (>=4.6.3)"] -mysql = ["SQLAlchemy (>=1.4.16)", "pymysql (>=1.0.2)"] -output-formatting = ["jinja2 (>=3.0.0)", "tabulate (>=0.8.9)"] -parquet = ["pyarrow (>=7.0.0)"] -performance = ["bottleneck (>=1.3.2)", "numba (>=0.53.1)", "numexpr (>=2.7.1)"] -plot = ["matplotlib (>=3.6.1)"] -postgresql = ["SQLAlchemy (>=1.4.16)", "psycopg2 (>=2.8.6)"] -spss = ["pyreadstat (>=1.1.2)"] -sql-other = ["SQLAlchemy (>=1.4.16)"] -test = ["hypothesis (>=6.34.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"] -xml = ["lxml (>=4.6.3)"] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] +aws = ["s3fs (>=2022.11.0)"] +clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] +compression = ["zstandard (>=0.19.0)"] +computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] +feather = ["pyarrow (>=10.0.1)"] +fss = ["fsspec (>=2022.11.0)"] +gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] +hdf5 = ["tables (>=3.8.0)"] +html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] +mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] +parquet = ["pyarrow (>=10.0.1)"] +performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] +plot = ["matplotlib (>=3.6.3)"] +postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] +pyarrow = ["pyarrow (>=10.0.1)"] +spss = ["pyreadstat (>=1.2.0)"] +sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.9.2)"] [[package]] name = "pathspec" @@ -549,6 +684,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -560,6 +696,7 @@ version = "4.2.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, @@ -575,6 +712,7 @@ version = "1.4.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, @@ -590,6 +728,7 @@ version = "2.9.1" description = "Python style guide checker" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, @@ -601,32 +740,19 @@ version = "2.5.0" description = "passive checker of Python programs" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, ] -[[package]] -name = "pyproject-flake8" -version = "5.0.4.post1" -description = "pyproject-flake8 (`pflake8`), a monkey patching wrapper to connect flake8 with pyproject.toml configuration" -optional = false -python-versions = "*" -files = [ - {file = "pyproject-flake8-5.0.4.post1.tar.gz", hash = "sha256:c2dfdf1064f47efbb2e4faf1a32b0b6a6ea67dc4d1debb98d862b0cdee377941"}, - {file = "pyproject_flake8-5.0.4.post1-py2.py3-none-any.whl", hash = "sha256:457e52dde1b7a1f84b5230c70d61afa58ced64a44b81a609f19e972319fa68ed"}, -] - -[package.dependencies] -flake8 = "5.0.4" -tomli = {version = "*", markers = "python_version < \"3.11\""} - [[package]] name = "pytest" version = "8.1.1" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"}, {file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"}, @@ -649,6 +775,7 @@ version = "5.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, @@ -667,6 +794,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -681,6 +809,7 @@ version = "2024.1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, @@ -692,6 +821,7 @@ version = "2.31.0" description = "Python HTTP for Humans." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, @@ -713,6 +843,7 @@ version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["main"] files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -724,6 +855,7 @@ version = "8.2.3" description = "Retry code until it succeeds" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "tenacity-8.2.3-py3-none-any.whl", hash = "sha256:ce510e327a630c9e1beaf17d42e6ffacc88185044ad85cf74c0a8887c6a0f88c"}, {file = "tenacity-8.2.3.tar.gz", hash = "sha256:5398ef0d78e63f40007c1fb4c0bff96e1911394d2fa8d194f77619c05ff6cc8a"}, @@ -738,6 +870,8 @@ version = "2.0.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] +markers = "python_version == \"3.10\"" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, @@ -749,6 +883,7 @@ version = "2.9.0.20240316" description = "Typing stubs for python-dateutil" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "types-python-dateutil-2.9.0.20240316.tar.gz", hash = "sha256:5d2f2e240b86905e40944dd787db6da9263f0deabef1076ddaed797351ec0202"}, {file = "types_python_dateutil-2.9.0.20240316-py3-none-any.whl", hash = "sha256:6b8cb66d960771ce5ff974e9dd45e38facb81718cc1e208b10b1baccbfdbee3b"}, @@ -760,6 +895,7 @@ version = "2.31.0.20240406" description = "Typing stubs for requests" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "types-requests-2.31.0.20240406.tar.gz", hash = "sha256:4428df33c5503945c74b3f42e82b181e86ec7b724620419a2966e2de604ce1a1"}, {file = "types_requests-2.31.0.20240406-py3-none-any.whl", hash = "sha256:6216cdac377c6b9a040ac1c0404f7284bd13199c0e1bb235f4324627e8898cf5"}, @@ -774,10 +910,12 @@ version = "4.11.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, ] +markers = {main = "python_version < \"3.13\""} [[package]] name = "tzdata" @@ -785,6 +923,7 @@ version = "2024.1" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" +groups = ["main"] files = [ {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, @@ -796,18 +935,19 @@ version = "2.2.1" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [metadata] -lock-version = "2.0" -python-versions = "^3.8.0" -content-hash = "99905c2ff019460a1f3198d9d05288b09a6c1a63f10344e7a1061cc551f94386" +lock-version = "2.1" +python-versions = "^3.10" +content-hash = "884f135e37a86af5f74d53a6efeb2e9848e2afb32553ba53984f137d22a115f3" diff --git a/pyproject.toml b/pyproject.toml index a1cd032..17895ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,26 +26,24 @@ requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] build-backend = "poetry_dynamic_versioning.backend" [tool.poetry.dependencies] -python = "^3.8.0" -requests = "^2.23.0" -types-requests = "^2.28.5" -types-python-dateutil = "^2.8.19" -pandas = ">=1.4.3" -numpy = [ - { version = "^1.22.4", python = ">=3.8,<3.12" }, - { version = "^1.26.0", python = ">=3.12" } -] -tomli = { version = "^2.0.1", python = ">=3.8,<3.11" } -tenacity = "^8.0.1" +python = "^3.10" +requests = "^2.28.0" +pandas = "^2.2.0" +numpy = ">=1.22.4" +tomli = { version = "^2.0.1", python = "<3.11" } +typing_extensions = { version = "^4.5.0", python = "<3.13" } +tenacity = "^8.2.0" [tool.poetry.group.dev.dependencies] black = "^24.3.0" isort = "^5.10.1" flake8 = "^5.0.0" +Flake8-pyproject = "^1.2.0" mypy = "^1.9.0" -pyproject-flake8 = "^5.0.0" pytest = "8.1.1" pytest-cov = "^5.0.0" +types-requests = "^2.31.0" +types-python-dateutil = "^2.9.0" [tool.poetry-dynamic-versioning] enable = true @@ -56,7 +54,7 @@ max-line-length = 120 max-complexity = 18 ignore = "E203,E266,W503," per-file-ignores = "__init__.py:F401" -exclude = ".venv" +exclude = ".venv,tests" [tool.isort] profile = "black" diff --git a/tests/test_client.py b/tests/test_client.py index 9452026..8ad821f 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,6 +1,6 @@ from contextlib import nullcontext as does_not_raise from datetime import datetime -from unittest.mock import MagicMock, call, patch +from unittest.mock import MagicMock, patch import pandas as pd import pytest @@ -307,11 +307,17 @@ def test_get_price_range(): cli.get_price_range(start, end) # 呼び出しの履歴と、get_prices_daily_quotes()が呼ばれた際の年月日8桁の引数を比較 - assert mock.mock_calls == [ - call.get_prices_daily_quotes(date_yyyymmdd="2020-02-27"), - call.get_prices_daily_quotes(date_yyyymmdd="2020-02-28"), - call.get_prices_daily_quotes(date_yyyymmdd="2020-02-29"), - call.get_prices_daily_quotes(date_yyyymmdd="2020-03-01"), - call.get_prices_daily_quotes(date_yyyymmdd="2020-03-02"), - ] + # 並列実行のため順序は保証されないので、セットとして比較 + expected_dates = { + "2020-02-27", + "2020-02-28", + "2020-02-29", + "2020-03-01", + "2020-03-02", + } + actual_dates = { + call_obj.kwargs["date_yyyymmdd"] for call_obj in mock.mock_calls + } + assert actual_dates == expected_dates + assert len(mock.mock_calls) == 5 mock.reset_mock() diff --git a/tests/test_client_v2.py b/tests/test_client_v2.py new file mode 100644 index 0000000..a35df53 --- /dev/null +++ b/tests/test_client_v2.py @@ -0,0 +1,403 @@ +from contextlib import nullcontext as does_not_raise +from datetime import datetime +from unittest.mock import MagicMock, patch + +import pandas as pd +import pytest +from dateutil import tz + +import jquantsapi +from jquantsapi import client_v2 + + +@pytest.mark.parametrize( + "api_key," "env, isfile, load," "exp_api_key," "exp_raise", + ( + # Case 1: api_key未指定、設定ファイルなし、環境変数なし → エラー + ( + None, + {}, + [False, False, False, False], + [], + "", + pytest.raises(ValueError), + ), + # Case 2: 設定ファイルからapi_key取得 + ( + None, + {}, + [True, False, False, False], + [ + { + "jquants-api-client": { + "api_key": "api_key_from_colab", + } + }, + ], + "api_key_from_colab", + does_not_raise(), + ), + # Case 3: 複数の設定ファイルがある場合、後のものが優先 + ( + None, + {"JQUANTS_API_CLIENT_CONFIG_FILE": "custom.toml"}, + [True, True, True, True], + [ + { + "jquants-api-client": { + "api_key": "api_key_colab", + } + }, + { + "jquants-api-client": { + "api_key": "api_key_user", + } + }, + { + "jquants-api-client": { + "api_key": "api_key_current", + } + }, + { + "jquants-api-client": { + "api_key": "api_key_env_file", + } + }, + ], + "api_key_env_file", + does_not_raise(), + ), + # Case 4: 環境変数JQUANTS_API_KEYが最優先 + ( + None, + {"JQUANTS_API_KEY": "api_key_from_env"}, + [True, False, False, False], + [ + { + "jquants-api-client": { + "api_key": "api_key_from_file", + } + }, + ], + "api_key_from_env", + does_not_raise(), + ), + # Case 5: 引数で直接指定(最優先) + ( + "api_key_from_arg", + {"JQUANTS_API_KEY": "api_key_from_env"}, + [True, False, False, False], + [ + { + "jquants-api-client": { + "api_key": "api_key_from_file", + } + }, + ], + "api_key_from_arg", + does_not_raise(), + ), + # Case 6: 引数のみで指定(設定ファイルなし、環境変数なし) + ( + "api_key_only_arg", + {}, + [False, False, False, False], + [], + "api_key_only_arg", + does_not_raise(), + ), + # Case 7: 設定ファイルにjquants-api-clientセクションがない場合 + ( + None, + {}, + [True, False, False, False], + [ + { + "other-section": { + "api_key": "api_key_wrong_section", + } + }, + ], + "", + pytest.raises(ValueError), + ), + ), +) +def test_client_v2_config( + api_key, + env, + isfile, + load, + exp_api_key, + exp_raise, +): + """ClientV2の設定ファイルと環境変数からのapi_key読み込みテスト""" + with exp_raise, patch.object( + jquantsapi.ClientV2, "_is_colab", return_value=True + ), patch.object(client_v2.os.path, "isfile", side_effect=isfile), patch( + "builtins.open" + ), patch.dict( + client_v2.os.environ, env, clear=True + ), patch.object( + client_v2.tomllib, "load", side_effect=load + ): + cli = jquantsapi.ClientV2(api_key=api_key) + assert cli._api_key == exp_api_key + + +@pytest.mark.parametrize( + "code, date_yyyymmdd, exp_params", + ( + ("", "", {}), + ("86970", "", {"code": "86970"}), + ("", "20220101", {"date": "20220101"}), + ("86970", "20220101", {"code": "86970", "date": "20220101"}), + ), +) +def test_get_eq_master(code, date_yyyymmdd, exp_params): + """get_eq_masterのパラメータテスト""" + ret_value = [] # _get_paginatedは配列を返す + exp_ret_len = 0 + exp_raise = does_not_raise() + + with exp_raise, patch.object( + jquantsapi.ClientV2, "_load_config", return_value={"api_key": "dummy_key"} + ), patch.object(jquantsapi.ClientV2, "_get_paginated") as mock_get_paginated: + mock_get_paginated.return_value = ret_value + + cli = jquantsapi.ClientV2() + ret = cli.get_eq_master(code=code, date=date_yyyymmdd) + args, kwargs = mock_get_paginated.call_args + assert kwargs.get("params", {}) == exp_params + assert len(ret) == exp_ret_len + + +@pytest.mark.parametrize( + "code, from_yyyymmdd, to_yyyymmdd, date_yyyymmdd, exp_params", + ( + ("", "", "", "", {}), + ("86970", "", "", "", {"code": "86970"}), + ("86970", "20220101", "", "", {"code": "86970", "from": "20220101"}), + ("86970", "", "20220131", "", {"code": "86970", "to": "20220131"}), + ( + "86970", + "20220101", + "20220131", + "", + {"code": "86970", "from": "20220101", "to": "20220131"}, + ), + ("", "", "", "20220115", {"date": "20220115"}), + ("86970", "", "", "20220115", {"code": "86970", "date": "20220115"}), + ), +) +def test_get_eq_bars_daily(code, from_yyyymmdd, to_yyyymmdd, date_yyyymmdd, exp_params): + """get_eq_bars_dailyのパラメータテスト""" + ret_value = {"data": []} # resp.json()で返される辞書 + exp_ret_len = 0 + exp_raise = does_not_raise() + + with exp_raise, patch.object( + jquantsapi.ClientV2, "_load_config", return_value={"api_key": "dummy_key"} + ), patch.object(jquantsapi.ClientV2, "_get") as mock_get: + mock_get.return_value.json.return_value = ret_value + + cli = jquantsapi.ClientV2() + ret = cli.get_eq_bars_daily( + code=code, + from_yyyymmdd=from_yyyymmdd, + to_yyyymmdd=to_yyyymmdd, + date_yyyymmdd=date_yyyymmdd, + ) + _, kwargs = mock_get.call_args + assert kwargs["params"] == exp_params + assert len(ret) == exp_ret_len + + +def test_get_eq_bars_daily_range(): + """ + get_eq_bars_daily_range()を呼ぶ際、引数に様々な型が入っていても問題なく + get_eq_bars_daily()に単一の年月日が渡される事を確認する。 + """ + mock = MagicMock(return_value=pd.DataFrame(columns=["Code", "Date"])) + + with patch.object( + jquantsapi.ClientV2, "_load_config", return_value={"api_key": "dummy_key"} + ): + cli = jquantsapi.ClientV2() + cli.get_eq_bars_daily = mock + + formats = { + "str_8digits": ("20200227", "20200302"), + "str_split_by_hyphen": ("2020-02-27", "2020-03-02"), + "datetime_without_tz": (datetime(2020, 2, 27), datetime(2020, 3, 2)), + "datetime_with_tz": ( + datetime(2020, 2, 27, tzinfo=tz.gettz("Asia/Tokyo")), + datetime(2020, 3, 2, tzinfo=tz.gettz("Asia/Tokyo")), + ), + "pd.Timestamp": (pd.Timestamp("2020-02-27"), pd.Timestamp("2020-03-02")), + } + + for _fmt, (start, end) in formats.items(): + cli.get_eq_bars_daily_range(start, end) + + # 並列実行のため順序は保証されないので、セットとして比較 + expected_dates = { + "2020-02-27", + "2020-02-28", + "2020-02-29", + "2020-03-01", + "2020-03-02", + } + actual_dates = { + call_obj.kwargs["date_yyyymmdd"] for call_obj in mock.mock_calls + } + assert actual_dates == expected_dates + assert len(mock.mock_calls) == 5 + mock.reset_mock() + + +def test_aggregate_bars_n_minute(): + """_aggregate_bars_n_minute()のテスト: 1分足から5分足への集計""" + # テスト用の1分足データ + data = { + "Date": ["2024-01-01"] * 10, + "Time": [ + "09:00:00", + "09:01:00", + "09:02:00", + "09:03:00", + "09:04:00", + "09:05:00", + "09:06:00", + "09:07:00", + "09:08:00", + "09:09:00", + ], + "Code": ["86970"] * 10, + "O": [100, 101, 102, 103, 104, 105, 106, 107, 108, 109], + "H": [110, 111, 112, 113, 114, 115, 116, 117, 118, 119], + "L": [90, 91, 92, 93, 94, 95, 96, 97, 98, 99], + "C": [105, 106, 107, 108, 109, 110, 111, 112, 113, 114], + "Vo": [1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900], + "Va": [ + 100000, + 110000, + 120000, + 130000, + 140000, + 150000, + 160000, + 170000, + 180000, + 190000, + ], + } + df_1min = pd.DataFrame(data) + + with patch.object( + jquantsapi.ClientV2, "_load_config", return_value={"api_key": "dummy_key"} + ): + cli = jquantsapi.ClientV2() + result = cli._aggregate_bars_n_minute(df_1min, 5) + + # 5分足に集計されることを確認 + assert len(result) == 2 + + # 最初の5分足(09:00-09:04) + row1 = result.iloc[0] + assert row1["Time"] == "09:00:00" + assert row1["O"] == 100 # 最初の始値 + assert row1["H"] == 114 # 最高値 + assert row1["L"] == 90 # 最安値 + assert row1["C"] == 109 # 最後の終値 + assert row1["Vo"] == 6000 # 出来高合計 + assert row1["Va"] == 600000 # 売買代金合計 + + # 2番目の5分足(09:05-09:09) + row2 = result.iloc[1] + assert row2["Time"] == "09:05:00" + assert row2["O"] == 105 + assert row2["H"] == 119 + assert row2["L"] == 95 + assert row2["C"] == 114 + assert row2["Vo"] == 8500 + assert row2["Va"] == 850000 + + +def test_aggregate_bars_n_minute_15min(): + """_aggregate_bars_n_minute()のテスト: 1分足から15分足への集計""" + # テスト用の1分足データ(15分分) + times = [f"09:{str(i).zfill(2)}:00" for i in range(15)] + data = { + "Date": ["2024-01-01"] * 15, + "Time": times, + "Code": ["86970"] * 15, + "O": list(range(100, 115)), + "H": list(range(120, 135)), + "L": list(range(80, 95)), + "C": list(range(110, 125)), + "Vo": [1000] * 15, + "Va": [100000] * 15, + } + df_1min = pd.DataFrame(data) + + with patch.object( + jquantsapi.ClientV2, "_load_config", return_value={"api_key": "dummy_key"} + ): + cli = jquantsapi.ClientV2() + result = cli._aggregate_bars_n_minute(df_1min, 15) + + # 15分足に集計されることを確認 + assert len(result) == 1 + + row = result.iloc[0] + assert row["Time"] == "09:00:00" + assert row["O"] == 100 # 最初の始値 + assert row["H"] == 134 # 最高値 + assert row["L"] == 80 # 最安値 + assert row["C"] == 124 # 最後の終値 + assert row["Vo"] == 15000 # 出来高合計 + assert row["Va"] == 1500000 # 売買代金合計 + + +@pytest.mark.parametrize( + "endpoint, exp_params", + ( + ("/equities/master", {"endpoint": "/equities/master"}), + ("/equities/bars/daily", {"endpoint": "/equities/bars/daily"}), + ("/fins/summary", {"endpoint": "/fins/summary"}), + ), +) +def test_get_bulk_list(endpoint, exp_params): + """get_bulk_listのパラメータテスト""" + ret_value = {"data": []} # resp.json()で返される辞書 + exp_ret_len = 0 + exp_raise = does_not_raise() + + with exp_raise, patch.object( + jquantsapi.ClientV2, "_load_config", return_value={"api_key": "dummy_key"} + ), patch.object(jquantsapi.ClientV2, "_get") as mock_get: + mock_get.return_value.json.return_value = ret_value + + cli = jquantsapi.ClientV2() + ret = cli.get_bulk_list(endpoint=endpoint) + args, _ = mock_get.call_args + assert args[1] == exp_params + assert len(ret) == exp_ret_len + + +def test_get_bulk(): + """get_bulkのテスト""" + ret_value = {"url": "https://example.com/data.csv"} # resp.json()で返される辞書 + exp_raise = does_not_raise() + + with exp_raise, patch.object( + jquantsapi.ClientV2, "_load_config", return_value={"api_key": "dummy_key"} + ), patch.object(jquantsapi.ClientV2, "_get") as mock_get: + mock_get.return_value.json.return_value = ret_value + + cli = jquantsapi.ClientV2() + ret = cli.get_bulk(key="2024/01/01/eq_master.csv") + args, _ = mock_get.call_args + assert args[1] == {"key": "2024/01/01/eq_master.csv"} + assert ret == "https://example.com/data.csv"