【J-Quants】業種別空売り比率データ・業種別指数データの紹介及び利用例について

（修正履歴）2024/5/20: コード及び画像に一部誤りがありましたので、修正いたしました。

## はじめに

こんにちは！J-Quants運営チームです。

運営チームでは、個人向けに金融データをAPIで配信するサブスクリプションサービスである[J-Quants](https://jpx-jquants.com/)を活用した、金融データの分析例などの技術記事を投稿しています。

当記事では、J-Quantsの業種別空売り比率API及び指数四本値APIを用いて、業種別に空売り比率と指数の騰落率を算出し、算出した空売り比率が指数の騰落に影響を与えるかどうかを、散布図を描画して分析してみます。

今回の記事で使用するデータは、いずれもJ-Quantsのスタンダードプラン以上をご契約いただくとご利用いただけるものとなります。

なお、指数四本値APIでは、2024/3/28より業種別や市場別等の指数データを配信対象として追加しています。当記事では、新たに配信対象となった業種別指数を用います。

J-Quantsについては、巻末の[J-Quantsとは](#j-quantsとは)をご参照ください。

## 業種別空売り比率とは

空売り比率の説明の前に、まずは株式の売買における売注文について説明します。
株式の売買における売注文は、以下のとおり分類されます。

| 売注文の種類           | 説明                                               |
|:-----------------------|:---------------------------------------------------|
| 実売り                 | 自身が所有している株式等の売り                     |
| 空売り（信用取引以外） | 株主との交渉や契約等により株式等を借りて行う空売り |
| 空売り（信用取引）     | 顧客が証券会社から株式等を借りて行う空売り         |

当記事で取扱う**空売り比率**とは、市場全体の売りに占める空売りの割合のことを指します。
この空売り比率には、個人投資家が主に利用する信用取引のほか、機関投資家が証券会社や株主等から株式の貸借契約を結んで借りた株式を売却する取引も含まれます。
東京証券取引所（以下、東証といいます。）では、東京株式市場における空売りの比率を、市場全体と業種別とに分けて集計して日々公表しています。

東証では、空売り比率を「空売り（価格規制あり）」と「
空売り（価格規制なし）」という2つの区分に分けて公表しています[^1]。
空売りにおける価格規制についてですが、空売りによって株価を意図的に下落させて利益を得ようとしたり、株価の下落を加速させたりする行為を防ぐために価格規制が設けられています。この規制は「金融商品取引法施行令」等の下で定められ、2013年11月5日から新ルールが適用されています[^2]。

空売り比率は以下の計算式で求めることができます。

| 求める項目(単位：%)        | 算出式                                              |
|:---------------------------|:----------------------------------------------------|
| 空売り比率（価格規制あり） | 価格規制ありの空売り代金 / 売買代金合計（※） \* 100 |
| 空売り比率（価格規制なし） | 価格規制なしの空売り代金 / 売買代金合計（※） \* 100 |

（※）実注文の売買代金 +価格規制ありの空売り代金 +
価格規制なしの空売り代金

なお、当記事では分析をシンプルにするため、使用する空売り比率は**価格規制あり＋価格規制なし**を用いることとします。

## データ概要

当記事で利用する業種別空売り比率データ及び指数四本値データの項目を紹介します。

### データ項目（業種別空売り比率）

| データ名                                     | データ概要                   | データ型 |
|----------------------------------------------|------------------------------|----------|
| Date                                         | 日付                         | String   |
| Sector33Code                                 | 33業種コード                 | String   |
| SellingExcludingShortSellingTurnoverValue    | 実注文の売買代金             | Number   |
| ShortSellingWithRestrictionsTurnoverValue    | 価格規制有りの空売り売買代金 | Number   |
| ShortSellingWithoutRestrictionsTurnoverValue | 価格規制無しの空売り売買代金 | Number   |

### データ項目（指数四本値）

| データ名 | データ概要 | データ型 |
|----------|------------|----------|
| Date     | 日付       | String   |
| Code     | 指数コード | String   |
| Open     | 始値       | Number   |
| High     | 高値       | Number   |
| Low      | 安値       | Number   |
| Close    | 終値       | Number   |

### データ項目の補足説明

- 業種別空売り比率データに収録されている33業種コードについては、[こちら](https://jpx.gitbook.io/j-quants-ja/api-reference/listed_info/sector33code)を参照してください。
- 指数四本値データに収録されている指数コードについては、[こちら](https://jpx.gitbook.io/j-quants-ja/api-reference/indices/indexcodes)を参照してください。

### 環境

当記事執筆時の環境は以下の通りです。

- MacOS (Sonoma 14.4.1)
- Python 3.11.4
- jquants-api-client 1.6.1
- pandas 1.5.3
- plotly 5.20.0
- statsmodels 0.14.0
- numpy 1.25.0

使うライブラリをimportしておきます。

``` python
import jquantsapi
import pandas as pd

# グラフ描画用ライブラリ
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import statsmodels.api as sm
import numpy as np
```

インストールされていなければ、あらかじめインストールしておいてください。  
ライブラリをインストールするコマンドを以下に示します。

``` {bash}
$ pip install jquants-api-client pandas plotly statsmodels numpy
```

## データ取得・可視化

ここからは、業種別に空売り比率と指数の騰落率を算出し、算出した空売り比率が指数の騰落に影響を与えるかどうかを、散布図を描画して分析してみます。
散布図は、以下2つの観点で作成します。

- **x軸にt日の空売り比率を、y軸にt+1日/t日（翌日比）の指数の騰落率をプロットした散布図**
  - 短期的にみて、空売り比率が指数の騰落に影響を与えるかを分析する  
- **x軸にt日/t-5日（前週比）の空売り比率を、y軸にt+5日/t日（翌週比）の指数の騰落率をプロットした散布図**
  - 中期的にみて、空売り比率が指数の騰落に影響を与えるかを分析する

J-Quants APIのPython用Clientパッケージを利用してデータを取得します。
まずは、J-Quantsにご登録いただいているメールアドレス及びパスワードを利用し、分析に必要な業種別空売り比率データ及び業種別指数のデータを業種ごとに取得して、DataFrameにAPPENDします。
取得する期間は、2023年（1月1日〜12月31日）とします。

なお、当記事では取得する期間における非営業日（土休日）は考慮しないこととします。
営業日のみのデータを取得する場合は、取引カレンダーAPIもあわせてご利用ください。

まず、Python用クライアント及び値の準備をしておきます。

``` python
cli = jquantsapi.Client(mail_address=MAIL_ADDRESS, password=PASSWORD)

from_yyyymmdd = 20230101
to_yyyymmdd = 20231231

# 業種別指数コードと業種コード（業種別空売り比率を取得する際のクエリパラメータ）を辞書化
index_to_sector = {
    "0040": "0050",
    "0041": "1050",
    "0042": "2050",
    "0043": "3050",
    "0044": "3100",
    "0045": "3150",
    "0046": "3200",
    "0047": "3250",
    "0048": "3300",
    "0049": "3350",
    "004A": "3400",
    "004B": "3450",
    "004C": "3500",
    "004D": "3550",
    "004E": "3600",
    "004F": "3650",
    "0050": "3700",
    "0051": "3750",
    "0052": "3800",
    "0053": "4050",
    "0054": "5050",
    "0055": "5100",
    "0056": "5150",
    "0057": "5200",
    "0058": "5250",
    "0059": "6050",
    "005A": "6100",
    "005B": "7050",
    "005C": "7100",
    "005D": "7150",
    "005E": "7200",
    "005F": "8050",
    "0060": "9050",
}

# 散布図を描画する際のタイトル用辞書
sector_names = {
    "0050": "水産・農林業",
    "1050": "鉱業",
    "2050": "建設業",
    "3050": "食料品",
    "3100": "繊維製品",
    "3150": "パルプ・紙",
    "3200": "化学",
    "3250": "医薬品",
    "3300": "石油･石炭製品",
    "3350": "ゴム製品",
    "3400": "ガラス･土石製品",
    "3450": "鉄鋼",
    "3500": "非鉄金属",
    "3550": "金属製品",
    "3600": "機械",
    "3650": "電気機器",
    "3700": "輸送用機器",
    "3750": "精密機器",
    "3800": "その他製品",
    "4050": "電気･ガス業",
    "5050": "陸運業",
    "5100": "海運業",
    "5150": "空運業",
    "5200": "倉庫･運輸関連業",
    "5250": "情報･通信業",
    "6050": "卸売業",
    "6100": "小売業",
    "7050": "銀行業",
    "7100": "証券･商品先物取引業",
    "7150": "保険業",
    "7200": "その他金融業",
    "8050": "不動産業",
    "9050": "サービス業",
}

# 空のデータフレームを作成
df_merged = pd.DataFrame()
```

次に、散布図を描画するためのx軸・y軸の値を計算します。

業種別空売り比率APIと指数四本値APIからデータを取得し、取得したデータを基にx軸・y軸の計算をします。

そして、x軸にt日の空売り比率を、y軸にt+1日/t日の指数の騰落率（翌日比）をプロットして散布図を描画してみます。

短期的にみて、空売り比率が指数の騰落に影響を与えるかを分析する、というものです。

以下のとおり、x軸・y軸それぞれの値を計算します。

なお、この後x軸にt日/t-5日（前週比）の空売り比率を、y軸にt+5日/t日（翌週比）の指数の騰落率をプロットした散布図を描画して比較してみますので、この後描画する散布図で使用するデータも併せて取得しておきます。

``` python
# コードの組み合わせごとにデータを取得し、df_mergedにInsert
for code_indices, code_sectors in index_to_sector.items():

    # 業種別指数を取得
    df_idx = cli.get_indices(
        code=code_indices, from_yyyymmdd=from_yyyymmdd, to_yyyymmdd=to_yyyymmdd
    )

    # 業種別空売り比率を取得
    df_ss = cli.get_markets_short_selling(
        sector_33_code=code_sectors,
        from_yyyymmdd=from_yyyymmdd,
        to_yyyymmdd=to_yyyymmdd,
    )

    # データフレームをmerge
    df_temp = pd.merge(df_idx, df_ss, how="inner", on="Date")
    # x軸：空売り比率（価格規制あり＋価格規制なし）
    # 売買高（TotalTurnOverVolume）を算出
    df_temp["TotalTurnOverVolume"] = df_temp[
        [
            "SellingExcludingShortSellingTurnoverValue",
            "ShortSellingWithRestrictionsTurnoverValue",
            "ShortSellingWithoutRestrictionsTurnoverValue",
        ]
    ].sum(axis=1)

    # パターン1：空売り比率（t日）を算出
    df_temp["ShortRatio_t"] = (
        df_temp[
            [
                "ShortSellingWithRestrictionsTurnoverValue",
                "ShortSellingWithoutRestrictionsTurnoverValue",
            ]
        ].sum(axis=1) / df_temp["TotalTurnOverVolume"] * 100
    )

    # パターン2：空売り比率（t日/t-5日（前週比）の平均）を算出
    df_temp["ShortRatio_t-5"] = df_temp["ShortRatio_t"] / df_temp["ShortRatio_t"].shift(5)
    df_temp["ShortRatio_avglast5days"] = (
        df_temp["ShortRatio_t-5"].rolling(window=5).mean()
    )

    # y軸：業種別指数の騰落率
    # パターン1：騰落率（t+1日/t日（翌日比））
    df_temp["IndexUpDownRatio_t1"] = df_temp["Close"].shift(-1) / df_temp["Close"]

    # パターン2：騰落率（t+5日/t日（翌週比）の平均）を算出
    df_temp["IndexUpDownRatio_t5"] = df_temp["Close"].shift(-5) / df_temp["Close"]
    df_temp["IndexUpDownRatio_avgnext5days"] = (
        df_temp["IndexUpDownRatio_t5"].rolling(window=5).mean()
    )

    # df_mergedにInsert
    df_merged = pd.concat([df_merged, df_temp])
```

計算した値を基に、散布図を描画します。

散布図だけでは傾向が分かりにくい可能性があるので、回帰直線も併せて描画します。

``` python
# 散布図を描画
fig = go.Figure()

# subplot設定（33業種分のグラフを描画する。1行当たり2枚 * 17行 ＝ 34枚になるよう設定）
rows = 17
cols = 2
sorted_sectors = sorted(set(index_to_sector.values()))
subplot_titles = [
    f"業種：{code}（{sector_names[code]}）" for code in sorted_sectors
]

# subplotのレイアウトを設定
fig = make_subplots(
    subplot_titles=subplot_titles,
    rows=rows,
    cols=cols,
    specs=[[{}, {}] for _ in range(17)],
    horizontal_spacing=0.05,
    vertical_spacing=0.005,
)

# 業種別の散布図（subplots）を描画
for i, facet in enumerate(sorted_sectors):
    row = i // cols + 1
    col = i % cols + 1
    fig.add_trace(
        go.Scatter(
            x=df_merged[df_merged["Sector33Code"] == facet]["ShortRatio_t"],
            y=df_merged[df_merged["Sector33Code"] == facet]["IndexUpDownRatio_t1"],
            mode="markers",
        ),
        row=row,
        col=col,
    )

    # inf, NaNを含むデータを除外
    df_merged_filtered = df_merged[df_merged["Sector33Code"] == facet].dropna(
        subset=["ShortRatio_t", "IndexUpDownRatio_t1"]
    )
    df_merged_filtered = df_merged_filtered[
        ~df_merged_filtered["ShortRatio_t"].isin([np.inf, -np.inf])
    ]

    # 線形回帰を実行し、回帰直線を描画
    X = df_merged_filtered["ShortRatio_t"].values.reshape(-1, 1)
    y = df_merged_filtered["IndexUpDownRatio_t1"].values
    model = sm.OLS(y, sm.add_constant(X)).fit()

    x_range = (
        df_merged_filtered["ShortRatio_t"].max()
        - df_merged_filtered["ShortRatio_t"].min()
    )
    x_line = np.linspace(
        df_merged_filtered["ShortRatio_t"].min() - 0.1 * x_range,
        df_merged_filtered["ShortRatio_t"].max() + 0.1 * x_range,
        100,
    )
    y_line = model.params[0] + model.params[1] * x_line

    fig.add_trace(
        go.Scatter(x=x_line, y=y_line, mode="lines", name="Trendline"), row=row, col=col
    )

    fig.update_xaxes(title_text="空売り比率（t日）", row=row, col=col)
    fig.update_yaxes(title_text="業種別指数の騰落率（翌日比：t+1日/t日）", row=row, col=col)

fig.update_layout(
    title_text="散布図（業種別株価騰落率・業種別空売り比率（2023/1/1〜2023/12/31））",
    margin=dict(t=100, b=20, l=30, r=30),
    width=1500,
    height=20000,
    autosize=True,
    showlegend=False,
)
```

コードを実行すると、以下のような散布図が描画されると思います。

（例示したコードでは、33業種分のグラフを描画しますが、当記事では一部を抜粋して掲載します。）

![scatter_pattern1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3515935/911db9c4-50ce-8f97-e215-72ee1764ff42.png)

描画された散布図のうち、業種が「電気・ガス業」と「海運業」を挙げてみますが、どちらとも回帰直線を見るとほぼ直線に近いような傾きで、空売り比率と指数値の騰落との関係性があまり見えません。

そこで、今度はx軸にt日/t-5日の空売り比率（前週比）を、y軸にt+5日/t日の指数の騰落率（翌週比）をプロットした散布図を描画してみます。  
中期的にみて、空売り比率が指数の騰落に影響を与えるかを分析する、というものです。

先ほどと同じように、散布図と回帰直線を描画してみます。

``` python
# 散布図を描画
fig = go.Figure()

# subplot設定（33業種分のグラフを描画する。1行当たり2枚 * 17行 ＝ 34枚になるよう設定）
rows = 17
cols = 2
sorted_sectors = sorted(set(index_to_sector.values()))
subplot_titles = [
    f"業種：{code}（{sector_names[code]}）" for code in sorted_sectors
]

# subplotのレイアウトを設定
fig = make_subplots(
    subplot_titles=subplot_titles,
    rows=rows,
    cols=cols,
    specs=[[{}, {}] for _ in range(17)],
    horizontal_spacing=0.05,
    vertical_spacing=0.005,
)

# 業種別の散布図（subplots）を描画
for i, facet in enumerate(sorted_sectors):
    row = i // cols + 1
    col = i % cols + 1
    fig.add_trace(
        go.Scatter(
            x=df_merged[df_merged["Sector33Code"] == facet]["ShortRatio_avglast5days"],
            y=df_merged[df_merged["Sector33Code"] == facet][
                "IndexUpDownRatio_avgnext5days"
            ],
            mode="markers",
        ),
        row=row,
        col=col,
    )

    # inf, NaNを含むデータを除外
    df_merged_filtered = df_merged[df_merged["Sector33Code"] == facet].dropna(
        subset=["ShortRatio_avglast5days", "IndexUpDownRatio_avgnext5days"]
    )
    df_merged_filtered = df_merged_filtered[
        ~df_merged_filtered["ShortRatio_avglast5days"].isin([np.inf, -np.inf])
    ]

    # 線形回帰を実行し、回帰直線を描画
    X = df_merged_filtered["ShortRatio_avglast5days"].values.reshape(-1, 1)
    y = df_merged_filtered["IndexUpDownRatio_avgnext5days"].values
    model = sm.OLS(y, sm.add_constant(X)).fit()

    x_range = (
        df_merged_filtered["ShortRatio_avglast5days"].max()
        - df_merged_filtered["ShortRatio_avglast5days"].min()
    )
    x_line = np.linspace(
        df_merged_filtered["ShortRatio_avglast5days"].min() - 0.1 * x_range,
        df_merged_filtered["ShortRatio_avglast5days"].max() + 0.1 * x_range,
        100,
    )
    y_line = model.params[0] + model.params[1] * x_line

    fig.add_trace(
        go.Scatter(x=x_line, y=y_line, mode="lines", name="Trendline"), row=row, col=col
    )

    fig.update_xaxes(title_text="空売り比率（前週比：t日/t-5日の平均）", row=row, col=col)
    fig.update_yaxes(title_text="業種別指数の騰落率（翌週比：t+5日/t日の平均）", row=row, col=col)

fig.update_layout(
    title_text="散布図（業種別株価騰落率・業種別空売り比率（2023/1/1〜2023/12/31））",
    margin=dict(t=100, b=20, l=30, r=30),
    width=1500,
    height=20000,
    autosize=True,
    showlegend=False,
)
```

コードを実行すると、以下のような散布図が描画されると思います。

（例示したコードでは、33業種分のグラフを描画しますが、当記事では一部を抜粋して掲載します。）

![scatter_pattern2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3515935/0990c3ca-4deb-7ab5-3631-85e41566316a.png)

先ほどの散布図とは異なり、どちらとも負の相関がはっきり見て取れます。

この散布図における「負の相関」とは、「空売り比率が高まると1週間（5営業日）後の指数値が下がる傾向にある」ということを示しています。

もちろん、業種によっては空売り比率が高まったからといって必ずしも指数値が下がるということにならない場合や、はっきりとした相関が出ない場合があります。また、指数値の騰落は空売り比率の騰落だけでなくその他の要因により左右される場合がありますが、このように、今回の分析期間においては**空売り比率が高まると近い将来指数値が下がる**という特徴を捉えることができたのではないかと思います。

## おわりに

今回は、空売りの情報を基に分析をする具体例を出してみました。簡易な分析ではありますが、業種別空売り比率のデータは一見して使い道が思い浮かびづらいデータかもしれませんので、活用例の一つとして参考にしていただけたら幸いです。

また、今回の記事では、描画した散布図の特徴を分かりやすくするために回帰直線を描画して、回帰直線の傾きから空売り比率と指数の騰落率に関する傾向を可視化するところまでとしましたが、次のステップとして、回帰直線の傾きに関する統計的な検定を行うことで、目視では分からなかった空売り比率と指数の騰落率が有意に関係するかを確かめられるようになります。

その他のデータについての取得例や分析例をColab
Notebookを用いて作成しているものが[こちらのリポジトリ](https://github.com/J-Quants/jquants-api-client-python/tree/main/examples)にございますので、お時間がありましたら是非覗いてみてください。

## JPX総研について

JPX総研ではJ-Quantsなどの新しいプロダクトを内製開発しています。キャリア採用を通年で行っておりますので、ご興味がある方は以下のリンクをご覧ください！

[JPX総研キャリア採用ページ](https://jpx-mid-career-recruitment.com/)

## J-Quantsとは

J-Quantsとは、ヒストリカル株価データ・財務データなどの金融データを取得できる、個人投資家向けのAPIデータ配信サービスです。投資にまつわるデータを分析しやすい形式で提供し、個人投資家の皆様がデータを活用しやすくなることを目的としております。

個人投資家が投資などのために金融データを分析する際の大きな障壁は、整形された金融データの取得が難しいことであるという理由から、2022年7月にベータ版をリリースいたしました。ご好評の声を頂いたこともあり、2023年4月より正式にリリースしております。

## ディスクレーマー

当記事は、J-Quantsを利用した金融データ分析に関する技術的事項の共有を目的としたものであり、株式などの投資商品への投資の推奨を目的とするものではありません。

また、当記事の内容をもとに投資等を行った結果損害等が生じた場合においても、一切の責任を負わないものとします。

## 参考リンク

- [J-Quants](https://jpx-jquants.com/)
- [jquants-api-client-python](https://github.com/J-Quants/jquants-api-client-python)（J-QuantsのPythonクライアントライブラリ）

[^1]: 新聞等の報道ではこの両者を合計して「空売り比率」と呼んでいるケースが多いようです。

[^2]: https://www.jpx.co.jp/equities/trading/regulations/02.html