JavaScript でリスクフリーレート (RFR) を計算する

LIBOR 閉塞で話題のリスクフリーレートを JavaScript で計算してみます。

やりたいこと

リスクフリーレート自体の説明は他でいろいろされていますので、ここでは特に行いません。ここでは日本銀行 (以下日銀) が公表している 無担保コール翌日物金利 (TONA) を指標に Lookback without Observation Shift 方式の Compound the Rate 方式でリスクフリーレートを JavaScript で計算してみます。

何を計算するのか

Lookback without Observation Shift 方式の Compound the Rate 方式とは全国銀行協会 (以下全銀協) の資料に次のように記載があります。

「日次累積複利レート(TONA)」とは、各利息計算期間に属する各営業日について、その 5 営業日前の日の TONA(又はその後継指標)としてその翌営業日において日本銀行(又はそのレートの管理を承継するその他の者)が公表する確報値を参照する手法を用いて算出される当該利息計算期間における TONA の日次累積複利(利息計算期間に属する各休業日については、その前営業日においてかかる参照の結果適用された TONA の確報値を複利計算せずに適用する。)の値を、当該利息計算期間に含まれる 暦日数で除し、365 を乗じて計算される利率(小数点第 6 位を四捨五入する。)をいう。 - TONA複利(後決め)レートの規定参考例

難解な文章ですね。なお全銀協の資料の中でも触れられていますが元ネタは日銀の「貸出におけるTONA (後決め) のコンベンション (利息計算方式) について」という資料です。全銀協の資料のほうが綺麗に一文に纏まっていたので、こちらを引用させてもらいましたが、人によっては日銀の資料のほうがわかりやすいかもしれません。

ちなみに数式にすると以下のようになります。

Π i=1 db 1+ TONAi×ni 365 -1 × 365tndp
  • db: 計算期間の営業日数
  • i: 計算期間初日から時系列に数えてi番目の営業日
  • TONAi: i番目の営業日に対応するTONA
  • ni: TONAiが適用されるカレンダー日数
  • tni: 営業日1から営業日i+1のカレンダー日数

余計にわからないかもしれません。具体例を示してみます。まずは適用する TONA 基準日の考え方について、利息計算期間を 2022/03/11 から 2022/03/16 とした場合、以下のようになります。

利息計算日 営休業日 適用する TONA 基準日 メモ
2022/03/11 (金) 営業日 2022/03/04 (金) 5 営業日前
2022/03/12 (土) 休業日 2022/03/04 (金) 直前の営業日に適用する基準日
2022/03/13 (日) 休業日 2022/03/04 (金) 直前の営業日に適用する基準日
2022/03/14 (月) 営業日 2022/03/07 (月) 5 営業日前
2022/03/15 (火) 営業日 2022/03/08 (火) 5 営業日前
2022/03/16 (水) 営業日 - 後落しによる片端

休業日の適用基準日については以下の通り記述があります。

利息計算期間に属する各日について、その数営業日(=ルックバック(Lookback)期間(例:5 営業日))前の TONA を参照する方式です。複利計算の際は、利息計算期間の休業日を勘案し計算します(当該休業日については直前の営業日に係る TONA を複利せずにそのまま(横置きして)適用します。)。 - TONA複利(後決め)レートの規定参考例

また後落しによる片端については以下の通り。

「利息計算期間」は、原契約の定めに従うものとします。なお、本資料では、利息計算期間として、「直前の利払日から次回利払日までの期間(初回は貸付実行日から第 1 回利払日までの期間)」(後落しによる片端)といった規定によることを前提としています。 - TONA複利(後決め)レートの規定参考例

適用する日数 ni は次のようになります。 また bp3 で、tndp5 となることがわかります。

利息計算日 営休業日 適用するTONA基準日 TONA 適用する日数
2022/03/11 (金) 営業日 2022/03/04 (金) -0.009% 3
2022/03/12 (土) 休業日 2022/03/04 (金) -0.009% -
2022/03/13 (日) 休業日 2022/03/04 (金) -0.009% -
2022/03/14 (月) 営業日 2022/03/07 (月) -0.007% 1
2022/03/15 (火) 営業日 2022/03/08 (火) -0.007% 1
2022/03/16 (水) 営業日 - - -
-0.009%×3 365 +1 -0.007%×1 365 +1 -0.007%×1 365 +1 -1 × 365 5

となり -0.00820% と求まります。

実装

では、実際に JavaScrip で計算してみます。 まず次のようなオブジェクトが時系列順に格納された配列を JSON で用意します。 日付毎に営業日フラグとその日の TONA を格納しています。 日付は扱いやすいように ECMAScript 元期からのミリ秒 (Date.getTime() で取得できるやつ) で保持します。

ちなみにTONAは日銀の Web サイトで確認できます。

{
  "time": "ECMAScript元期からのミリ秒",
  "tonaBusinessDay": "Boolean",
  "tonaRate": "TONA(%)"
}

計算するメインの関数を用意します。計算開始日、計算終了日、look back day を引数とします。冒頭で上記の JSON を読み込んでおきます。ここでは node.js 前提でファイルシステムから読み込んでいますが、ブラウザベースの場合は fetch 等で読み込むことで同様に対応できます。

import fs from 'fs'

/** 
 * 与えられた計算期間とルックバック期間に基づき累積複利を計算する
 * @param {Number} calcFrom - 計算開始日に対応する ECMAScript 元期からのミリ秒
 * @param {Number} calcTo - 計算終了日に対応する ECMAScript 元期からのミリ秒
 * @param {Number} lag - ルックバック期間 (日)
 * @return {Number} 累積複利 (ACR)
 */ 
export const main = (calcFrom, calcTo, lag) => {
  const tona = JSON.parse(fs.readFileSync('tona.json').toString())
}

次に特定日から何営業日前 / 後を探す関数を用意します。最初はレート参照日を探すためだけに何営業日前だけ返せばよいかなと思っていたのですが、適用日数計算で先の営業日も拾う必要があるので、前にも後ろにも探せるようにしたら、ちょっとごちゃっとしてしまいました。

  /**
   * 特定日の何営業日前後の営業日を検索
   * @param {Number} time - 特定日
   * @param {Number} lag - 何営業日前後を探すか
   * @param {Boolean} forward - 未来を探すか過去を探すか
   * @return {Number} 該当営業日の TONA オブジェクト
   */ 
  const refBz = (time, lag, forward = true) => {
    let count = 0, countBz = 0
    const index = tona.findIndex(e => e.time == time)
    while (countBz != lag) {
      if (tona[index + (forward ? count + 1 : - count - 1)].tonaBusinessDay) countBz ++
      count ++
    }
    return tona[index + (forward ? count : - count)]
  }

上記関数を使って参照日のレート取得や適用日数を算出しながら、複利計算を行い、カレンダー日数も計算します。

  const { comRate, dayCount } = tona.filter(v => v.tonaBusinessDay && v.time >= calcFrom && v.time < calcTo)
    .reduce((p, v) => {
      const rate = refBz(v.time, lag, false).tonaRate
      const count = (refBz(v.time, 1).time - v.time) / 24 / 60 / 60 / 1000
      p.comRate *= (rate /100 * count / 365 + 1)
      p.dayCount += count
      return p
    }, {comRate: 1, dayCount: 0})

最後に年率に戻して少数点第 6 位を四捨五入します。

  return Math.round((comRate - 1) * 365 / dayCount * 100 * Math.pow(10, 5)) / Math.pow(10, 5)

あとがき

JavaScript で RFR を計算してみました。今回 TONA の参照を前提としたので日数カウントは 365 でハードコードしましたが、SOFR 等欧米系のレートの参照にも対応するためには、日数カウントもパラメーター化したほうがよいかなと思います。