⛰️

複数の営業日カレンダーを扱う業務のための日付モデルの紹介

2020/12/24に公開

FOLIO Advent Calendar 2020の24日目の記事です。

背景

日本の証券会社は日本での取引を業務としていますので、基本的に「JPX開場日」を前提として業務が構築されています。
一方で米国上場の株式を取り扱うようなサービスを展開している場合は「米国開場日」も考慮しなければなりません。

金融業界(もしかしたら別の業界でも?)では業務で何かのイベントの日時 T を起点として、N営業日後に別のイベントが発火する場合、後続のイベントの日付を T+N という表記が用いられています。

この表現は営業日が一つだけであれば自明で分かりやすいのですが、FOLIOのように営業日が2つ以上存在する場合に、どの営業日もしくは単にカレンダー通りにN日進めれば良いのかが表現できていませんでした。
また人手で業務を行なっていた時代は営業日のみを考慮すれば良かったのですが、システム化が進んだ現代では営業日の翌日である土曜も処理が行なわれることも多いですが、単純な T+N という表記では、土曜日(上の例で行くと12/26)が表現できませんでした。
このためモデルの表現力のなさを自然言語で補うことが常態化し、業務とシステムの連携に必要な厳密さが不足して、不具合や運用の困難さの温床となっていました。

そこでそういった日付の曖昧さを無くすべく、 T+N モデルをベースにより表現力の高い日付モデルを開発しました。
本稿では、その日付モデルを紹介したいと思います。

開発した日付モデル

カレンダーIDと営業日カレンダー

まず営業日が何かを明らかにするために、カレンダーと営業日カレンダーという概念を定義します。

カレンダー名 定義
カレンダー いわゆるカレンダー、過去から未来の1/1~12/31の全ての日付が含まれる集合 = 全体集合
営業日カレンダー カレンダーから非営業日を除外した集合、非営業日はコンテキストによって異なる (e.g. 米国閉場日, JPX閉場日 etc)

次にカレンダーと営業日カレンダーを一意に定めるカレンダーIDを導入します。
冒頭の例ですと「米国開場日」という営業日カレンダーと「JPX開場日」という営業日カレンダーの2つを扱います。
それぞれ「米国閉場日」と「JPX閉場日」という非営業日をもっており、ベン図であらわすと関係性は以下のようになります。

非営業日の和集合,積集合を考慮すると、計4種類の非営業日が定義出来ます。

  • 米国閉場日 = A+B
  • JPX閉場日 = B+C
  • 米国閉場日 ∪ JPX閉場日 (和集合) = A+B+C
  • 米国閉場日 ∩ JPX閉場日 (積集合) = B

4つの非営業日が定義されると自動的に営業日カレンダーも定義出来ますので、それぞれ以下のようにカレンダーIDを割り当てておきます。

名前 要素 カレンダーID
米国開場日 C+D us
JPX開場日 A+D jp
米国とJPXの両方が開場している日 D jp&us
米国とJPXのどちらかまたは両方が開場している日 A+C+D jp|us

また全体集合であるカレンダーのカレンダーIDも同様に割り当てておきます。

名前 要素 カレンダーID
カレンダー A+B+C+D c

これは 営業日の翌日 のような、営業日に属さない日付を表現したい時などに使用します。

演算子

日付モデルの演算子は大きく2種類存在します。
一つは T+Nモデルでもあった加減算で、N日後、N日前を表現するものです。

演算子 挙動
+ あるカレンダーIDでN日後を計算する
- あるカレンダーIDでN日前を計算する

例えばT=2020/12/24としたときに、12/28は「カレンダー」だと T+4 、「JPX開場日」だと T+2 、「米国開場日」だと T+1 となります。(米国はクリスマスが祝日)

もう一つはキャスト演算子で、カレンダーIDを指定することで、ある日付を指定したカレンダーまたは営業日カレンダー上の日付にキャストするものとなります。

演算子 記述例 挙動
_ T_jp ある日付Tを、営業日カレンダーで T または T 以降 で最も近い日付に変換する
^ T^jp ある日付Tを、営業日カレンダーで T または T 以前 で最も近い日付に変換する

この演算子を T=2020/12/26の時にそれぞれのカレンダーIDに適用すると以下のようになります。

EBNF表記

このモデルをEBNFで記述したものが以下になります。 (イメージは https://bottlecaps.de/rr/ui から引用)

binop    ::= '+'
           | '-'
castop   ::= '_'
           | '^'
cal      ::= 'jp'
           | 'us'
           | 'c'
           | 'jp & us'
           | 'jp | us'
expression
         ::= ( ( "T" | expression ) cast_op ) cal
           | ( "(" expression ")" )
           | expression binop num

では実際にT=2020/12/26とした時に、上のEBNFに従って書いた ((T_c+1)^jp+1)_us-1 が何日になるかみてみましょう。

答えはこちら


正解は12/24でした。

何がうれしいのか?

業務で発生する大体の日付が表現できるようになります。
例えば冒頭で困る例として出した、全てのJPX開場日の翌日に稼働する処理を考えましょう。
これは (T_jp)_c+1 で表現できますね。 ( _^ は変えても問題ありません)
他にもJPX開場日のみ稼働してほしいと言うことを表現したいときは、 T==T_jp という条件で誤解なく伝えられるようになります。

また当初の目的通り、複数の営業日カレンダーを跨いで日付の指定が可能となりました。
私が関わる範囲では ((T_jp&us)_c+1)_jp+2 という複雑な日付を取り扱うのですが、それまでは自然言語でやり取りしていたので、いちいち計算して齟齬がないか確認したりしていました。
日付モデルを導入したおかげで、(少なくともエンジニア内では)どんなに複雑でも誤解なく日付が定義出来るようになったのはとても大きいです。

おまけ

あまりにも複雑な日付の概念を表現する絵文字が出来ました。(前日の明日は今日になるとは限らないんやで)

Discussion