金融のカレンダーにまつわる機能を実装したクラス

金融のカレンダー周りで使用するクラスを記載。


```Calendar.cls
Option Explicit

Private Const cntSONIA = "SONIA"
Private TenorNumber As Object
Private TenorUnit As Object
Private AccrualSpotLag As Object
Private AddedDate As Date
Private dicPubHol As Object
Private Cell As Range
Private ReverseFLG As Boolean


Private Sub Class_Initialize()
  Call SetTenorNum
  Call SetTenorFormat
  Call SetSpotLag
End Sub

Public Property Get TENORNUM() As Object
  Set TENORNUM = TenorNumber
End Property

Public Property Get TENORFORMAT() As Object
  Set TENORFORMAT = TenorUnit
End Property

Public Property Get SPOTLAG() As Object
  Set SPOTLAG = AccrualSpotLag
End Property



Private Sub SetTenorNum()
  Set TenorNumber = CreateObject("Scripting.Dictionary")
  TenorNumber.Add Key:="O/N", Item:="1"
  TenorNumber.Add Key:="T/N", Item:="2"
  TenorNumber.Add Key:="SPOT", Item:="3"
  TenorNumber.Add Key:="S/N", Item:="4"
  TenorNumber.Add Key:="1D", Item:="1"
  TenorNumber.Add Key:="2D", Item:="2"
  TenorNumber.Add Key:="3D", Item:="3"
  TenorNumber.Add Key:="1W", Item:="7"
  TenorNumber.Add Key:="2W", Item:="14"
  TenorNumber.Add Key:="3W", Item:="21"
  TenorNumber.Add Key:="1M", Item:="1"
  TenorNumber.Add Key:="2M", Item:="2"
  TenorNumber.Add Key:="3M", Item:="3"
  TenorNumber.Add Key:="4M", Item:="4"
  TenorNumber.Add Key:="5M", Item:="5"
  TenorNumber.Add Key:="6M", Item:="6"
  TenorNumber.Add Key:="7M", Item:="7"
  TenorNumber.Add Key:="8M", Item:="8"
  TenorNumber.Add Key:="9M", Item:="9"
  TenorNumber.Add Key:="10M", Item:="10"
  TenorNumber.Add Key:="11M", Item:="11"
  TenorNumber.Add Key:="12M", Item:="12"
  TenorNumber.Add Key:="1Y", Item:="1"
  TenorNumber.Add Key:="15M", Item:="15"
  TenorNumber.Add Key:="18M", Item:="18"
  TenorNumber.Add Key:="2Y", Item:="2"
  TenorNumber.Add Key:="3Y", Item:="3"
  TenorNumber.Add Key:="4Y", Item:="4"
  TenorNumber.Add Key:="5Y", Item:="5"
  TenorNumber.Add Key:="6Y", Item:="6"
  TenorNumber.Add Key:="7Y", Item:="7"
  TenorNumber.Add Key:="8Y", Item:="8"
  TenorNumber.Add Key:="9Y", Item:="9"
  TenorNumber.Add Key:="10Y", Item:="10"
  TenorNumber.Add Key:="12Y", Item:="12"
  TenorNumber.Add Key:="15Y", Item:="15"
  TenorNumber.Add Key:="20Y", Item:="20"
  TenorNumber.Add Key:="25Y", Item:="25"
  TenorNumber.Add Key:="30Y", Item:="30"
End Sub


Private Sub SetTenorFormat()
  Set TenorUnit = CreateObject("Scripting.Dictionary")
  TenorUnit.Add Key:="O/N", Item:="d"
  TenorUnit.Add Key:="T/N", Item:="d"
  TenorUnit.Add Key:="SPOT", Item:="d"
  TenorUnit.Add Key:="S/N", Item:="d"
  TenorUnit.Add Key:="1D", Item:="d"
  TenorUnit.Add Key:="2D", Item:="d"
  TenorUnit.Add Key:="3D", Item:="d"
  TenorUnit.Add Key:="1W", Item:="d"
  TenorUnit.Add Key:="2W", Item:="d"
  TenorUnit.Add Key:="3W", Item:="d"
  TenorUnit.Add Key:="1M", Item:="m"
  TenorUnit.Add Key:="2M", Item:="m"
  TenorUnit.Add Key:="3M", Item:="m"
  TenorUnit.Add Key:="4M", Item:="m"
  TenorUnit.Add Key:="5M", Item:="m"
  TenorUnit.Add Key:="6M", Item:="m"
  TenorUnit.Add Key:="7M", Item:="m"
  TenorUnit.Add Key:="8M", Item:="m"
  TenorUnit.Add Key:="9M", Item:="m"
  TenorUnit.Add Key:="10M", Item:="m"
  TenorUnit.Add Key:="11M", Item:="m"
  TenorUnit.Add Key:="12M", Item:="m"
  TenorUnit.Add Key:="1Y", Item:="yyyy"
  TenorUnit.Add Key:="15M", Item:="m"
  TenorUnit.Add Key:="18M", Item:="m"
  TenorUnit.Add Key:="2Y", Item:="yyyy"
  TenorUnit.Add Key:="3Y", Item:="yyyy"
  TenorUnit.Add Key:="4Y", Item:="yyyy"
  TenorUnit.Add Key:="5Y", Item:="yyyy"
  TenorUnit.Add Key:="6Y", Item:="yyyy"
  TenorUnit.Add Key:="7Y", Item:="yyyy"
  TenorUnit.Add Key:="8Y", Item:="yyyy"
  TenorUnit.Add Key:="9Y", Item:="yyyy"
  TenorUnit.Add Key:="10Y", Item:="yyyy"
  TenorUnit.Add Key:="12Y", Item:="yyyy"
  TenorUnit.Add Key:="15Y", Item:="yyyy"
  TenorUnit.Add Key:="20Y", Item:="yyyy"
  TenorUnit.Add Key:="25Y", Item:="yyyy"
  TenorUnit.Add Key:="30Y", Item:="yyyy"
End Sub

Private Sub SetSpotLag()
    Set AccrualSpotLag = CreateObject("Scripting.Dictionary")
    AccrualSpotLag.Add Key:="TONA", Item:=2
    AccrualSpotLag.Add Key:="SOFR", Item:=2
    AccrualSpotLag.Add Key:="SONIA", Item:=0
    AccrualSpotLag.Add Key:="ESTR", Item:=2
End Sub


Public Function BizDayCheck(ByRef CheckTarget As Date, ByRef PUBLICHOLIDAY As Range) As String
    Dim Cell As Range
    Dim dicPubHol As Object
    
    Set dicPubHol = CreateObject("Scripting.Dictionary")
    For Each Cell In PUBLICHOLIDAY.Cells
        If dicPubHol.exists(Cell.Value) = False Then
            dicPubHol.Add Key:=Cell.Value, Item:=True
        End If
    Next
    If Weekday(CheckTarget) = vbSaturday Or Weekday(CheckTarget) = vbSunday Then
        BizDayCheck = "HOL"
    ElseIf dicPubHol.Item(CheckTarget) = True Then
        BizDayCheck = "HOL"
    Else
        BizDayCheck = "BIZ"
    End If
End Function


Public Function RequestShiftedBusinessday(ByRef TargetDate As Date, _
                                          ByRef ShiftLength As Long, _
                                          ByRef PUBLICHOLIDAY As Range) As Date
Dim Holidayshift As Long
Dim Direction As Long
Dim AddedDate As Date

If ShiftLength < 0 Then
    Direction = -1
ElseIf ShiftLength > 0 Then
    Direction = 1
Else
    RequestShiftedBusinessday = TargetDate
    Exit Function
End If

'テナーの分シフトさせる
AddedDate = DateAss("d", Direction, TargetDate)

'祝日であることを表すフラグを設定する。
If Holidayshift = 0 Then
    Set dicPubHol = CreateObject("Scripting.Dictionary")
    
    For Each Cell In PUBLICHOLIDAY.Cells
        If dicPubHol.exists(Cell.Value) = False Then
            dicPubHol.Add Key:=Cell.Value, Item:=True
        End If
    Next
End If

Select Case Weekday(AddedDate, vbSunday)
Case Is = vbMonday, vbTuesday, vbWednesday, vbThursday, vbFriday
    If dicPubHol.Item(AddedDate) = True Then
        GoTo RECURSIVE
    Else
        If CountShiftLength = 0 Then
            CountShiftLength = Direction
        ElseIf CountShiftLength <> 0 Then
            CountShiftLength = CountShiftLength + Direction
        End If
        
        If ShiftLength <> CountShiftLength Then
            GoTo RECURSIVE
        Else
            RequestShiftedBusinessday = AddedDate
        End If
    End If
    
Case Is = vbSunday, vbSaturday
RECURSIVE:
    TargetDate = DateAdd("d", Direction, TargetDate)
    dicPubHol.RemoveAll
    RequestShiftedBusinessday = RequestShiftedBusinessday(TargetDate, ShiftLength, PUBLICHOLIDAY)
End Select
End Function

Public Function FollowingTenor(ByRef TargetDate As Date, _
                               ByRef Tenor As String, _
                               ByRef PUBLICHOLIDAY As Range) As Date
Dim Holidayshift As Long
Dim AddedDate As Date
Dim dicPubHol As Object
Dim Cell As Range

'テナーの分シフトさせる
AddedDate = DateAdd(TENORFORMAT.Item(Tenor), TENORNUM.Item(Tenor), TargetDate)

'祝日であることを表すフラグを設定する。
If Holidayshift = 0 Then

    Set dicPubHol = CreateObject("Scripting.Dictionary")
    
    For Each Cell In PUBLICHOLIDAY.Cells
        If dicPubHol.exists(Cell.Value) = False Then
            dicPubHol.Add Key:=Cell.Value, Item:=True
        End If
    Next
End If

Select Case Weekday(AddedDate, vbSunday)
Case Is = vbMonday, vbTuesday, vbWednesday, vbThursday, vbFriday
    If dicPubHol.Item(AddedDate) = True Then
        GoTo RECURSIVE
    Else
        FollowingTenor = AddedDate
    End If
    
Case Is = vbSunday, vbSaturday
RECURSIVE:
    
    If Holidayshift = 0 Then
        Holidayshift = 1
    ElseIf Holidayshift > 0 Then
        Holidayshift = Holidayshift + 1
    End If
    
    TargetDate = DateAdd("d", 1, TargetDate)
    
    dicPubHol.RemoveAll
    FollowingTenor = FollowingTenor(TargetDate, Tenor, PUBLICHOLIDAY)

End Select
End Function

'RequestTenorメソッド-----------------------------------------------------------------------------------------------
'
'引数---------------------------------------------------------------------------------------------------------------
'TARGETDATE                  : DateAdd(単位,加算時間,日時)の第3引数に渡す。セル範囲が1つでない場合エラー。
'                            : weekday関数で曜日を取得する。
                             '0：vbUseSystemDayOfWeek(PCのOSのシステム時間)
                             '1：vbSunday (日曜日)
                             '2：vbMonday (月曜日)
                             '3：vbTuesday (火曜日)
                             '4：vbWednesday (水曜日)
                             '5：vbThursday (木曜日)
                             '6：vbFriday (金曜日)
                             '7：vbSaturday (土曜日)
'TENOR                       : テナー一覧をConstClassで辞書として定義。テナーに対応する値はDateAdd
'                              DateAdd関数の第1・2引数に渡す。セル範囲が1つでない場合エラー。
'PUBLICHOLIDAY               : セル範囲に指定した全ての日付を重複削除したうえで辞書に格納。
'                               キーにセル範囲の日付､値にTRUEを格納｡
'戻り値--------------------------------------------------------------------------------------------------------------
'PUBLICHOLIDAYの辞書の格納値がTRUE､もしくはweekday(DateAdd(,TARGETDATE,TENOR))


Public Function RequestTenor(ByRef TargetDate As Date, _
                             ByRef Tenor As String, _
                             ByRef PUBLICHOLIDAY As Range) As Date

Dim Holidayshift As Long

'テナーの分シフトさせる
AddedDate = DateAdd(TENORFORMAT.Item(Tenor), TENORNUM.Item(Tenor), TargetDate)

'祝日であることを表すフラグを設定する。
If Holidayshift = 0 Then
    Set dicPubHol = CreateObject("Scripting.Dictionary")
    For Each Cell In PUBLICHOLIDAY.Cells
        If dicPubHol.exists(Cell.Value) = False Then
            dicPubHol.Add Key:=Cell.Value, Item:=True
            dicPubHol(Cell.Value) = True
        End If
    Next
End If


'平日だった場合はそのままテナーの分シフトさせた値を返す。
Select Case Weekday(AddedDate, vbSunday)
Case Is = vbMonday, vbTuesday, vbWednesday, vbThursday, vbFriday
    If dicPubHol.Item(AddedDate) = True Then
        GoTo RECURSIVE
    Else
        RequestTenor = AddedDate
    End If
Exit Function


'休日・祝日だった場合は翌日にシフトさせたうえで、再帰的に関数呼び出し
Case Is = vbSunday, vbSaturday
RECURSIVE:

    '再帰関数の呼び出し回数をカウント
    If Holidayshift = 0 Then
        Holidayshift = 1
    ElseIf Holidayshift > 0 Then
        Holidayshift = Holidayshift + 1
    End If

    '翌日が月末でない場合
    If ReverseFLG = False And Month(TargetDate) = Month(DateAdd("d", 1, TargetDate)) Then
        TargetDate = DateAdd("d", 1, TargetDate)

    '翌日が月末である場合
    ElseIf ReverseFLG = False And Month(TargetDate) <> Month(DateAdd("d", 1, TargetDate)) Then
        ReverseFLG = True
        TargetDate = DateAdd("d", -1 * Holidayshift, TargetDate)

    '月末であると判定され、月初方向に巻き戻し場合
    ElseIf ReverseFLG = True Then
        TargetDate = DateAdd("d", -1, TargetDate)
    End If

    dicPubHol.RemoveAll '祝日定義のディクショナリーを初期化
    RequestTenor = RequestTenor(TargetDate, Tenor, PUBLICHOLIDAY)
    ReverseFLG = False
End Select
End Function

Public Function RequestAccrualEndDate(ByRef RateRecordDay As Date, _
                                      ByRef Tenor As String, _
                                      ByRef ACCRUALPUBLICHOLIDAY As Range, _
                                      ByVal INTEREST As String) As Date
Dim SpotDate As Date
Dim AccrualStartDate As Date
Dim DiffSpotOffset As Long
Dim Count As Long
Dim ccySpotLag As Long
Dim dicAccrualPubHol As Object
Dim Offsetlag As Long

Set dicAccrualPubHol = CreateObject("Scripting.Dictionary")

For Each Cell In ACCRUALPUBLICHOLIDAY.Cells
    If dicAccrualPubHol.exists(Cell.Value) = False Then
        dicAccrualPubHol.Add Key:=Cell.Value, Item:=True
    End If
Next



SpotDate = RateRecordDay
Count = 0

ccySpotLag = SPOTLAG.Item(INTEREST)
If ccySpotLag <> 0 Then
    Do
        If dicAccrualPubHol.Item(DateAdd("d", 1, SpotDate)) = True Then
            SpotDate = DateAdd("d", 1, SpotDate)
        ElseIf Weekday(DateAdd("d", 1, SpotDate)) = vbSunday Then
            SpotDate = DateAdd("d", 1, SpotDate)
        ElseIf Weekday(DateAdd("d", 1, SpotDate)) = vbSaturday Then
            SpotDate = DateAdd("d", 1, SpotDate)
        Else
            SpotDate = DateAdd("d", 1, SpotDate)
            Count = Count + 1
        End If
    Loop Until ccySpotLag = Count
End If

AccrualStartDate = SpotDate
Count = 0
Do
    If dicAccrualPubHol.Item(DateAdd("d", -1, AccrualStartDate)) = True Then
        AccrualStartDate = DateAdd("d", -1, AccrualStartDate)
    ElseIf Weekday(DateAdd("d", -1, AccrualStartDate)) = vbSunday Then
        AccrualStartDate = DateAdd("d", -1, AccrualStartDate)
    ElseIf Weekday(DateAdd("d", -1, AccrualStartDate)) = vbSaturday Then
        AccrualStartDate = DateAdd("d", -1, AccrualStartDate)
    Else
        AccrualStartDate = DateAdd("d", -1, AccrualStartDate)
        Count = Count - 1
    End If
Loop Until Offsetlag = Count

Dim tmpAccrualStartDate As Date
tmpAccrualStartDate = AccrualStartDate

RequestAccrualEndDate = Me.RequestTenor(tmpAccrualStartDate, Tenor, ACCRUALPUBLICHOLIDAY)
Debug.Print "RFR: " & INTEREST & ", TENOR: " & Tenor & _
", RATERECORDDAY: " & RateRecordDay & ", SpotDate: " & SpotDate & ", AccrualStartDate: " & AccrualStartDate & ", AccrualEndDate;" & RequestAccrualEndDate

End Function



Public Function RequestRateRecordDay(ByRef ACCRUALENDTARGET As Date, _
                                     ByRef Tenor As String, _
                                     ByRef ACCRUALPUBLICHOLIDAY As Range, _
                                     ByRef INTEREST As Date) As Object

Dim elemDataRange As Date
Dim AccrualStartDate As Date
Dim DiffSpotOffset As Long
Dim Cell As Variant
Dim dicAccrualPubHol As Object: Set dicAccrualPubHol = CreateObject("Scripting,Dictionary")


ReqRRDCount = ReqRRDCount + 1

For Each Cell In ACCRUALPUBLICHOLIDAY
    If dicAccrualPubHol.exists(Cell) = False Then
        dicAccrualPubHol(Cell.Value) = True
    End If

Dim dicAccrualDateRange As Object: Set dicAccrualDataRange = CreateObject("Scripting.Dictionary")
dicAccrualDateRange.Add Key:="ON", Item:=15
dicAccrualDateRange.Add Key:="SN", Item:=15
dicAccrualDateRange.Add Key:="1W", Item:=20
dicAccrualDateRange.Add Key:="1M", Item:=45
dicAccrualDateRange.Add Key:="2M", Item:=75
dicAccrualDateRange.Add Key:="3M", Item:=105
dicAccrualDateRange.Add Key:="6M", Item:=195
dicAccrualDateRange.Add Key:="12M", Item:=380

elemDataRange = DateAdd("d", dicAccrualDateRange.Item(Tenor) * -1, ACCRUALENDTARGET)

Do While Weekday(elemDataRange) = vbSaturday Or Weekday(elemDataRange) = vbSunday
    elemDataRange = DateAdd("d", -1, elemDataRange)
Loop

Dim INTERMdicRateRecord_AccrualEnd As Object: setINTERMdicRateRecord_AccrualEnd = CrateObject("Scripting.Dictionary")

Dim dicRateRecord_AccrualEnd As Object: Set dicRateRecord_AccrualEnd = CreateObject("Scripting.Dictionary")

INTERMdicRateRecord_AccrualEnd.Add Key:=elemDataRange, _
Item:=Me.RequestAccrualEndDate(elemDataRange, Tenor, ACCRUALPUBHOLIDAY, INTEREST)

Do Until INTERMdicRateRecord_AccrualEnd.Item(elemDataRange) >= ACCRUALENDTARGET
elemDataRange = DataAdd("d", 1, elemDataRange)

    If Weekday(elemDataRange) <> vbSaturday And Weekday(elemDataRange) <> vbSunday Then
        If Tenor = "ON" And INTEREST = cntSONIA Then
            INTERMdicRateRecord_AccrualEnd.Add Key:=elemDataRange, Item:=elemDataRange
            dicRateRecord_AccrualEnd.Add Key:=elemDataRange, Item:=elemDataRange
        Else
            INTERMdicRateRecord_AccrualEnd.Add Key:=elemDataRange, _
            Item:=Me.RequestAccrualEndDate(elemDataRange, Tenor, ACCRUALPUBLICHOLIDAY, INTEREST)
            
            dicRateRecord_AccrualEnd.Add Key:=elemDataRange, _
            Item:=Me.RequestAccrualEndDate(elemDataRange, Tenor, ACCRUALPUBLICHOLIDAY, INTEREST)
        End If
    End If

Loop

Set RequestRateRecordDay = dicRateRecord_AccrualEnd

End Function
```