👅

日経先物分析その1 ... Sessionの割り当て

2023/08/31に公開

何をするのか

日経先物を対象にデータの前処理や分析を行います。一度に全部を書くのが大変なので、シリーズ化しちょこちょこ書いて行こうと思っていますが、勉強中の素人なので間違いは勘弁してください。

その1では何をするのか

日経先物miniの1分足データを使用し、session名を割り当て、sessionごとにuniqueなIDを割り当てる方法を解説します。

日経先物について

以下は生成AIに聞いた回答のコピペです。

日経先物とは

日経先物とは、日本経済新聞社が発行する日本経済指数(日経平均株価)に連動する先物取引です¹. 日経平均株価の値動きを予想し、その値動きに応じて売買を行うことで、利益を得ることができます¹. 日経平均株価が上昇すれば、日経先物の価格も上昇し、逆に下落すれば、価格も下落します¹. なお、日経先物は大阪取引所の他にCMEとSGXで取引されています¹.

¹: 日経平均先物 CME SGX 大取 夜間 リアルタイム チャート

(1) 日経平均先物 CME SGX 大取 夜間 リアルタイム チャート. https://nikkei225jp.com/cme/.
(2) 日経225先物:5日22時=100円高、3万3070円 - みんかぶ. https://minkabu.jp/news/3708471.
(3) 先物・オプション価格情報 | 日本取引所グループ. https://www.jpx.co.jp/markets/derivatives/quotes/index.html.

DaySessionとNightSessionについて

日中取引は8:45から15:15、夜間取引は16:30から翌日の6:00までの時間帯です¹. 取引時間の流れを私たちの生活時間に即してあてはめると「朝の取引から始まり翌日未明までの取引」が1日と考えがちですが、先物取引での「取引日」は「夕方の夜間取引から始まり翌営業日の日中取引終了まで」が1計算区域として「取引日」となります¹.

また、日中取引と夜間取引では、値段の決め方が異なります。日中取引では「板寄せ方式」で始値として基準となる値段を決定し、その後は「時間優先・価格優先」の原則に基づくオークション方式により取引が行われます。一方、夜間取引ではオークション方式によって値段が決定されます¹.

¹: フィリップ証券

(1) 取引時間について:日経225先物を学ぼう | 日経225先物・NYダウ .... https://www.phillip.co.jp/futures/market_hours.php.
(2) ナイト・セッション | 日本取引所グループ. https://www.jpx.co.jp/derivatives/rules/trading-hours/01.html.
(3) 日経平均先物とは?特徴や基本的なルールをわかりやすく解説 .... https://news.mynavi.jp/kabu/nikkeiheikin-sakimono/.

準備

使用するデータ

DataFrame
shape: (2_874_147, 6)
┌─────────────────────┬───────┬───────┬───────┬───────┬────────┐
│ datetime            ┆ op    ┆ hi    ┆ lw    ┆ cl    ┆ volume │
│ ------------------    │
│ datetime[μs]        ┆ i64   ┆ i64   ┆ i64   ┆ i64   ┆ i64    │
╞═════════════════════╪═══════╪═══════╪═══════╪═══════╪════════╡
│ 2013-01-04 09:00:0010750107651074510750101269 │
│ 2013-01-04 09:01:001075010765107351074517336  │
│ 2013-01-04 09:02:00107401075010740107455341   │
│ 2013-01-04 09:03:00107451075010740107457193   │
│ …                   ┆ …     ┆ …     ┆ …     ┆ …     ┆ …      │
│ 2023-06-16 15:12:00336003360033600336000      │
│ 2023-06-16 15:13:00336003360033600336000      │
│ 2023-06-16 15:14:00336003360033600336000      │
│ 2023-06-16 15:15:003364533645336453364524124  │
└─────────────────────┴───────┴───────┴───────┴───────┴────────┘

環境

今回はpandasではなくpolarsを使用しますので、インストールしていない方は下記を参考にインストールしてください。
https://pypi.org/project/polars/

実行

Import

import datetime

import polars

データの読み込み

データはparquetファイルで保存しています。csvと比べて容量も小さく、読み書きも高速なのでオススメです。

fp = r'../datasets/NK225F.parquet'
data = pl.read_parquet(fp)

Session名の割り当て

現在の日経先物の取引時間は8:45から15:15までですが、今回使用するデータは2013年からのものを使用しています。昔とは取引時間が変わっているので、余裕を見て適当に割り当てます。

Session名の割り当て処理
data = data\
    .with_columns([
        pl.col('datetime').dt.time().alias('time') ])\
    .with_columns([
        pl.when((pl.col('time') < datetime.time(7, 0, 0)) 
                | (datetime.time(16, 0, 0) < pl.col('time')))
            .then('NightSession')
            .otherwise('DaySession')
            .alias('session'), ])\
    .drop('time')

session名を割り当てたDataFrame
shape: (2_874_147, 7)
┌─────────────────────┬───────┬───────┬───────┬───────┬────────┬────────────┐
│ datetime            ┆ op    ┆ hi    ┆ lw    ┆ cl    ┆ volume ┆ session    │
│ ---------------------        │
│ datetime[μs]        ┆ i64   ┆ i64   ┆ i64   ┆ i64   ┆ i64    ┆ str        │
╞═════════════════════╪═══════╪═══════╪═══════╪═══════╪════════╪════════════╡
│ 2013-01-04 09:00:0010750107651074510750101269 ┆ DaySession │
│ 2013-01-04 09:01:001075010765107351074517336  ┆ DaySession │
│ 2013-01-04 09:02:00107401075010740107455341   ┆ DaySession │
│ 2013-01-04 09:03:00107451075010740107457193   ┆ DaySession │
│ …                   ┆ …     ┆ …     ┆ …     ┆ …     ┆ …      ┆ …          │
│ 2023-06-16 15:12:00336003360033600336000      ┆ DaySession │
│ 2023-06-16 15:13:00336003360033600336000      ┆ DaySession │
│ 2023-06-16 15:14:00336003360033600336000      ┆ DaySession │
│ 2023-06-16 15:15:003364533645336453364524124  ┆ DaySession │

SessionごとにUniqueなIDを割り当てる

単純に時間単位でresamplingするのではなく、今回はsessionごとにresamplingを行いたいので、sessionごとにuniqueなIDを割り当てる関数を作成します。

uniqueなIDを割り当てる関数
def unique_id_by_session(
    datetimes: Iterable[datetime.datetime],
    sessions: Iterable[str],
    use_first: bool=True
) -> pl.DataFrame:
    
    if isinstance(datetimes, pl.Series):
        dt_col = datetimes.name
    else:
        dt_col = 'datetime'
    
    if isinstance(sessions, pl.Series):
        ses_col = sessions.name
    else:
        ses_col = 'session'

    # DataFrameを作成してSessionの変わり目を判断
    df = pl\
        .DataFrame([
            datetimes, 
            sessions])\
        .with_columns(
            pl.col(ses_col).shift().alias('shift_session'),
            pl.col(dt_col).dt.time().alias('time'))\
        .with_columns([
            pl.when(pl.col(ses_col) != pl.col('shift_session'))
                .then(True)
                .otherwise(False)
                .alias('change_session')
            ])

    if use_first:
        # 最初の行がshiftする結果nullになってしまうが、使用したい場合
        df = df.with_columns([
            pl.when(pl.col('shift_session') == None)
                .then(pl.lit(True))
                .otherwise(pl.col('change_session'))
                .alias('change_session')
        ])
    
    # Sessionの変わり目になる行のみ抽出し、0からIDを割り当てる
    filtered = df\
        .filter(
            pl.col('change_session') == True)
    unique_ids = list(range(0, filtered.shape[0]))
    filtered = filtered\
        .with_columns(
            pl.Series(name='session_id', values=unique_ids))\
        .select([dt_col, 'session_id'])
    # DataFrameを結合してNull値を埋める
    result = df\
        .join(other=filtered, on=dt_col, how='outer')\
        .with_columns(
            pl.col('session_id').fill_null(strategy='forward'))\
        .select([dt_col, 'session_id'])
    
    return result

uniqueなidの結合とついでにresampling
sessions = data\
    .join(
        unique_id_by_session(data['datetime'], data['session']),
        on='datetime',
        how='outer')\
    .groupby('session_id')\
        .agg([
            pl.col('datetime').first(),
            pl.col('session').first(),
            pl.col('op').first(),
            pl.col('hi').max(),
            pl.col('lw').min(),
            pl.col('cl').last(),
            pl.col('volume').sum()
        ])\
    .sort('session_id')
IDを割り当てたDataFrame
shape: (5_125, 8)
┌────────────┬─────────────────────┬──────────────┬───────┬───────┬───────┬───────┬─────────┐
│ session_id ┆ datetime            ┆ session      ┆ op    ┆ hi    ┆ lw    ┆ cl    ┆ volume  │
│ ------------------------     │
│ i32        ┆ datetime[μs]str          ┆ i64   ┆ i64   ┆ i64   ┆ i64   ┆ i64     │
╞════════════╪═════════════════════╪══════════════╪═══════╪═══════╪═══════╪═══════╪═════════╡
│ 02013-01-04 09:00:00 ┆ DaySession   ┆ 107501076510650106851076582 │
│ 12013-01-04 16:30:00 ┆ NightSession ┆ 10705108051067010750620544  │
│ 22013-01-07 09:00:00 ┆ DaySession   ┆ 107401074510585106201250704 │
│ 32013-01-07 16:30:00 ┆ NightSession ┆ 10615106251053510575452390  │
│ …          ┆ …                   ┆ …            ┆ …     ┆ …     ┆ …     ┆ …     ┆ …       │
│ 51212023-06-14 16:30:00 ┆ NightSession ┆ 33680337703321033600897015  │
│ 51222023-06-15 08:45:00 ┆ DaySession   ┆ 336003375033325334101280238 │
│ 51232023-06-15 16:30:00 ┆ NightSession ┆ 33515335403310533450841521  │
│ 51242023-06-16 08:45:00 ┆ DaySession   ┆ 333853372533130336451189195 │
└────────────┴─────────────────────┴──────────────┴───────┴───────┴───────┴───────┴─────────┘

終わりに

今回はSession名の割り当てとIDの割り当てを行いました。
次回はMSQまでの残存時間を計算する予定です。

Discussion