時刻変更の週末を境に、二つの市場の引けが同じ瞬間に重なった
目標
4つの市場の現地時刻の約定記録を、タイムゾーンの規則でUTCに変換し、サマータイムの切り替え日の特殊な時刻を分け、セッション区間と取引日ラベルを付けたうえで、市場ごとの引けがUTCで重なる日と、カレンダー自体の矛盾を見つけ出します。
なぜ重要なのか
時刻の変換を固定オフセットで行っても、例外は出ません。ただ1時間ずれた値が保存されるだけで、その事実は、切り替えの週末が過ぎて突合が合わなくなって初めて表に出ます。資本市場では、このずれが、すぐに締めバッチと集計に届きます。しかも、市場ごとに切り替え日が違うので、一方はすでに夏時間で、他方はまだ冬時間である数週間が、毎年あります。その数週間だけ、2つの市場の引けが同じ瞬間に置かれることが、実際に起きます。
ステップ
python3で/root/session/dataに、markets.json・calendar.json・trades.csvを作成してください。- 現地時刻をタイムゾーンの規則でUTCに変換し、
/root/session/normalized.csvにtrade_id,market,utc_msで書いてください。その現地時刻に存在しない時刻は入れません。 - 同じ約定を、冬のオフセットを固定して変換してみて、結果が異なるものを
/root/session/fixed_diff.csvに書いてください。 - 存在しない時刻を
/root/session/nonexistent.csvに、2回ある時刻を/root/session/ambiguous.csvに書いてください。 - 約定ごとのセッション区間を、
/root/session/phase.csvにtrade_id,phaseで書いてください。 - 暦日と取引日が分かれる約定を、
/root/session/tradingday.csvに書いてください。 - 基準日ごとの市場別の通常取引の引けを
/root/session/close_utc.csvに、引けが近い市場のペアを/root/session/overlap.csvに書いてください。 - カレンダー自体の矛盾を
/root/session/calendar_defects.csvに書き、前のステップの数を/root/session/summary.jsonに要約してください。
参考
utc_msは、UTC基準のミリ秒の整数です。2回ある時刻は、foldが0のほう、つまり先に通り過ぎた瞬間を使います。- ステップ3の冬のオフセットは、そのタイムゾーンの2025-01-15 12:00のオフセットです。その値をコードに書かず、タイムゾーンから求めてください。
- ステップ5の区間は、
pre・regular・post・closed・holidayの5つです。区間は、始まりを含み、終わりを含みません。半日取引は、通常取引の終了だけが変わり、休場日は、ほかの何よりも先に判定します。 - ステップ6で、取引日が暦日と異なりうる市場は、
calendar.jsonのovernight_marketsにあります。その市場で、開場後の区間が終わる時刻より早い約定は、前日の取引日です。 - ステップ7で、午前0時を越えるセッションの引けは、翌日にあります。近さの基準は
calendar.jsonのoverlap_minutesで、市場名は、辞書順で前のほうをmarket_aに書きます。 - ステップ8の矛盾は、
휴장일에반일장정의と반일장마감이정규보다늦음の2つです(韓国語の2つの値は、順に「休場日に半日取引の定義」「半日取引の終了が通常より遅い」という意味です)。 - よくある間違い1: 存在しない時刻を、そのまま変換して保存してしまいます。例外が出ず、1時間後の値になります。
- よくある間違い2: 休場日の判定を、セッション区間の判定のあとにしてしまいます。休場日の約定が、通常取引に分類されます。
- 公開ドキュメント: IANA tz、zoneinfo、PEP 615、RFC 3339。市場名とセッション時刻は、このラボの合成データで、タイムゾーン識別子だけが、実際のIANAの名前です。
4つの市場のセッション定義とカレンダー、約定データを作る
python3で/root/session/dataに、markets.json・calendar.json・trades.csvを作成してください。乱数を使わない生成スクリプトを、そのまま使ってください。
顧客のデータをそのまま使えないので、同じ形の合成データを作ります。乱数を使わなければ、誰が何回回しても同じデータが出ます。採点ツールは、データを標準形に変換してフィンガープリントを突き合わせるので、手で直すと、あとのステップがすべて行き詰まります。
現地時刻をタイムゾーンの規則でUTCに変換する
/root/session/normalized.csvに、1行目にtrade_id,market,utc_msを置いて、約定をUTCのミリ秒で書いてください。その現地時刻に存在しない時刻は入れません。
zoneinfo.ZoneInfoでタイムゾーンを付けます。存在しない時刻かどうかは、UTCに行って同じタイムゾーンに戻ってきたとき、壁時計の値がそのままかどうかでわかります。2回ある時刻は、先に通り過ぎたほうを使います。
固定オフセットで変換してみて、ずれた日を探す
/root/session/fixed_diff.csvに、1行目にtrade_id,tz_utc_ms,fixed_utc_ms,diff_minを置いて、冬のオフセットを固定して変換した値が、タイムゾーンの規則で変換した値と異なる約定を書いてください。
冬のオフセットは、そのタイムゾーンの2025-01-15 12:00のオフセットです。値をコードに書かず、タイムゾーンから求めてください。diff_minは、固定オフセットの値から規則の値を引いた、分単位の差です。存在しない時刻は、この表には入れません。
存在しない時刻と2回ある時刻を分ける
/root/session/nonexistent.csvにtrade_id,market,local_tsを、/root/session/ambiguous.csvにtrade_id,market,local_ts,first_utc_ms,second_utc_msを書いてください。
春に時計を進める市場では、飛ばされた1時間が存在しません。秋に戻す市場では、同じ壁時計の値が2回通ります。foldが0のものと1のもののオフセットが違うかどうかを見れば、2つの場合をまとめて捕まえられ、どちらの場合かは、往復のテストで分けます。
約定ごとにセッション区間を付ける
/root/session/phase.csvに、1行目にtrade_id,phaseを置いて、すべての約定にpre・regular・post・closed・holidayのうち1つを付けてください。
休場日を最初に判定します。そのあと、開場前、通常取引、開場後の順に見て、どこにもなければ閉まっている時刻です。半日取引がある日は、通常取引の終了だけが変わります。区間は、始まりを含み、終わりを含みません。
午前0時を越えるセッションの取引日ラベルを付ける
/root/session/tradingday.csvに、1行目にtrade_id,calendar_date,trading_dayを置いて、2つの値が異なる約定だけを書いてください。
午前0時を越えるセッションがある市場は、データに書かれています。その市場で、開場後の区間が終わる時刻より早い約定は、前日のセッションに属します。ほかの市場は、常に暦日と同じです。
市場ごとの引けをUTCにまとめて、重なるバッチウィンドウを探す
/root/session/close_utc.csvにmarket,date,close_utc_msを、/root/session/overlap.csvにdate,market_a,market_b,gap_minを書いてください。
基準日の一覧と近さの基準は、データにあります。休場日の市場は、その日の引けがありません。午前0時を越えるセッションの引けは、翌日にあるので、日付を1日足す必要があります。市場のペアは、辞書順で前のほうを先に書きます。
カレンダー自体の矛盾を探して、全体を要約する
/root/session/calendar_defects.csvにmarket,date,defectを書き、/root/session/summary.jsonにtrades・nonexistent・ambiguous・fixed_diff・phases・trading_day_differs・overlaps・calendar_defectsを要約してください。
矛盾は2つです。同じ日が休場日でありながら半日取引としても書かれている場合と、半日取引の終了が通常の終了より遅い場合です。要約の数は、手で数えず、前のステップと同じ計算から持ってきてください。phasesは、区間名をキーとする件数のオブジェクトです。