github.com/diadata-org/diadata@v1.4.593/internal/pkg/rateDerivatives/rfr.go (about)

     1  package ratederivatives
     2  
     3  import (
     4  	"errors"
     5  	"math"
     6  	"time"
     7  
     8  	"github.com/diadata-org/diadata/pkg/utils"
     9  	log "github.com/sirupsen/logrus"
    10  )
    11  
    12  // RateFactor returns the integer a rate is multiplied by in the computation
    13  // of (compounded) RFRs.
    14  func RateFactor(date time.Time, holidays []time.Time) (int, error) {
    15  	rate := 1
    16  	date = date.AddDate(0, 0, 1)
    17  	for !utils.CheckWeekDay(date) || utils.ContainsDay(holidays, date) {
    18  		rate++
    19  		date = date.AddDate(0, 0, 1)
    20  	}
    21  	return rate, nil
    22  }
    23  
    24  // CompoundedRate returns the compounded index for the rate values given by the slice @rate.
    25  // @rates is a slice with daily rates for all business days in the respective period.
    26  // @dateInit, @dateFinal determine the period of the loan.
    27  // @holidays is a slice of strings where each entry corresponds to a special holiday (i.e. not a
    28  // 			saturday or sunday) in the respective period.
    29  // @daysPerYear determines the total number of days per business year.
    30  // @rounding is a float of type 1e-n which rounds the result to n digits. If @rounding == 0 no rounding
    31  func CompoundedRate(rates []float64, dateInit, dateFinal time.Time, holidays []time.Time, daysPerYear int, rounding int) (float64, error) {
    32  
    33  	// Check feasibility and consistency of input data
    34  	if !utils.CheckWeekDay(dateFinal) || utils.ContainsDay(holidays, dateFinal) {
    35  		// log.Info("No rate information for holidays or weekends")
    36  		return float64(0), errors.New("no rate information for holidays or weekends")
    37  	}
    38  	if utils.AfterDay(dateInit, dateFinal) {
    39  		log.Info("The final date cannot be before the initial date.")
    40  		return float64(0), errors.New("the final date cannot be before the initial date")
    41  	}
    42  	if daysPerYear == 0 {
    43  		log.Info("Days per year must be a positive integer.")
    44  		return float64(0), errors.New("days per year must be a positive integer")
    45  	}
    46  	NumBusinessDays, _ := utils.CountDays(dateInit, dateFinal, true)
    47  	NumBusinessDays -= len(holidays)
    48  	if NumBusinessDays == 0 {
    49  		log.Info("No business days in period of interest.")
    50  		return float64(0), errors.New("no business days in period of interest")
    51  	}
    52  
    53  	if !utils.CheckWeekDay(dateInit) || utils.ContainsDay(holidays, dateInit) {
    54  		// When first day is holiday or weekend, there has to be an additional rate for the
    55  		// previous working day which does not fall into the loan period.
    56  		if len(rates) != NumBusinessDays+1 {
    57  			log.Error("The given number of rate values and business days is not consistent.")
    58  			return float64(0), errors.New("date error")
    59  		}
    60  	} else {
    61  		if len(rates) != NumBusinessDays {
    62  			log.Error("The given number of rate values and business days is not consistent.")
    63  			return float64(0), errors.New("date error")
    64  		}
    65  	}
    66  
    67  	// Iterate through business days to compute the compounded rate
    68  	prod := float64(1)
    69  	for i := 0; i < len(rates); i++ {
    70  		n, _ := RateFactor(dateInit, holidays)
    71  		factor := 1 + (rates[i]/100)*float64(n)/float64(daysPerYear)
    72  		prod *= factor
    73  		dateInit = dateInit.AddDate(0, 0, n)
    74  	}
    75  
    76  	// In case of the SOFR Index, results are rounded to eight decimals
    77  	if rounding != 0 {
    78  		result := math.Round(prod*math.Pow(10, float64(rounding))) / math.Pow(10, float64(rounding))
    79  		return result, nil
    80  	}
    81  	return prod, nil
    82  }
    83  
    84  // CompoundedRateSimple returns the compounded index for the rate values given by the slice @rate.
    85  // @rates is a slice with daily rates for all calendar days in the respective period.
    86  // @dateInit, @dateFinal determine the period of the loan.
    87  // @daysPerYear determines the total number of days per business year.
    88  // @rounding is a float of type 1e-n which rounds the result to n digits. If @rounding == 0 no rounding
    89  func CompoundedRateSimple(rates []float64, dateInit, dateFinal time.Time, daysPerYear int, rounding int) (float64, error) {
    90  
    91  	// Check feasibility and consistency of input data
    92  	if utils.AfterDay(dateInit, dateFinal) {
    93  		log.Info("The final date cannot be before the initial date.")
    94  		return float64(0), errors.New("the final date cannot be before the initial date")
    95  	}
    96  	if daysPerYear == 0 {
    97  		log.Info("Days per year must be a positive integer.")
    98  		return float64(0), errors.New("days per year must be a positive integer")
    99  	}
   100  
   101  	// Iterate through business days to compute the compounded rate
   102  	prod := float64(1)
   103  	for i := 0; i < len(rates); i++ {
   104  		factor := 1 + rates[i]/100/float64(daysPerYear)
   105  		prod *= factor
   106  	}
   107  
   108  	// In case of the SOFR Index, results are rounded to eight decimals
   109  	if rounding != 0 {
   110  		result := math.Round(prod*math.Pow(10, float64(rounding))) / math.Pow(10, float64(rounding))
   111  		return result, nil
   112  	}
   113  	return prod, nil
   114  }