0
$\begingroup$

I am translating a code from MATLAB to Python and I need to find equivalent setting to MATLAB’s day-count basis of 0 = actual / actual. My MATLAB code uses date2time function to determine the length of period (measured in years) between two dates, with the default setting for the Basis parameter set to “0”.

MATLAB documentation for the day-count bases is here: https://uk.mathworks.com/help/fininst/day-count-basis.html. The original Matlab code is using default first option ‘actual/actual’.

The most appropriate Python library I could find for this sort of task is ‘QuantLib’, which has several day-count basis options as described here: https://quantlib-python-docs.readthedocs.io/en/latest/dates.html#daycounter.

However, having tried all the ql.ActualActual options (ISMA, Bond, ISDA, … ) it seems none of them replicates the result of the default MATLAB actual/actual basis precisely.

How can I get an identical result? I could obviously dive deep into the MATLAB function implementation and write it in python from scratch, but I would have hoped that there is already an existing solution out there.

As a reproducible example, Matlab gives a period length of 30.0302 years between 20-Mar-2006 and 31-Mar-2031 as follows:

period = date2time(datetime(2006,3,20), datetime(2036,3,31), 1, 0)
period = 30.0302

I'm trying to find a function in Python that would replicate the 30.0302 figure. However, I'm seeing the following:

import QuantLib as ql

dateS = ql.Date(20,3,2006)
dateE = ql.Date(31,3,2036)
ql.ActualActual(ql.ActualActual.ISDA).yearFraction(dateS, dateE) = 30.032203009207276
ql.ActualActual(ql.ActualActual.Bond).yearFraction(dateS, dateE) = 30.083333333333332
ql.ActualActual(ql.ActualActual.AFB).yearFraction(dateS, dateE)  = 30.03013698630137
$\endgroup$
2
  • 3
    $\begingroup$ What is the 30.0302 value representative of. Personally, I can't replicate it under the rules of any natural day count mechanism. And the Matlab help is especially vague "Number of days in both a period and a year is the actual number of days". If you use that definition and make the calculation manually its shows that Matlab is wrong. $\endgroup$
    – Attack68
    Commented Jun 17 at 11:00
  • $\begingroup$ Stepping though Matlab’s date2time function I can see that the calculations are done by generating quasi semi-annual dates locked to the end date: 31/3/2006 – 30/9/2006 - .... – 30/9/2035 – 31/3/2036. It then counts the number of full years (30) and adds the fraction of a year derived as (11 / 182) / 2 = 0.03021978. The 182 is the actual number of days in the current semi-annual period into which the start date falls (30/9/2005 to 31/3/2006) and 11 is the number of days from the start date to the next quasi semiannual date (from 20/3/2006 – 31/3/2006). $\endgroup$ Commented Jun 18 at 12:18

4 Answers 4

4
$\begingroup$

I think you will never find 30.0302 using quantlib :

dateS = ql.Date(20, 3, 2006)
dateE = ql.Date(31, 3, 2036)

# List of day count conventions to try
day_counts = {
    'Actual360': ql.Actual360(),

    'Actual365Fixed': ql.Actual365Fixed(),
    'Actual365Fixed(Canadian)': ql.Actual365Fixed(ql.Actual365Fixed.Canadian),
    'Actual365FixedNoLeap': ql.Actual365Fixed(ql.Actual365Fixed.NoLeap),

    'ActualActual ISDA': ql.ActualActual(ql.ActualActual.ISDA),
    'ActualActual Bond': ql.ActualActual(ql.ActualActual.Bond),
    'ActualActual ISMA': ql.ActualActual(ql.ActualActual.ISMA),
    'ActualActual Historical': ql.ActualActual(ql.ActualActual.Historical),
    'ActualActual Actual365': ql.ActualActual(ql.ActualActual.Actual365),
    'ActualActual AFB': ql.ActualActual(ql.ActualActual.AFB),
    'ActualActual Euro': ql.ActualActual(ql.ActualActual.Euro),

    'Business252' : ql.Business252(),

    'Thirty360 ISDA': ql.Thirty360(ql.Thirty360.ISDA),
    'Thirty360 USA': ql.Thirty360(ql.Thirty360.USA),
    'Thirty360 BondBAsis': ql.Thirty360(ql.Thirty360.BondBasis),
    'Thirty360 European': ql.Thirty360(ql.Thirty360.European),
    'Thirty360 EuroBondBasis': ql.Thirty360(ql.Thirty360.EurobondBasis),

    'SimpleDayCounter NASD': ql.SimpleDayCounter(),    

    'Business252': ql.Business252()
}


for name, day_count in day_counts.items():
    try:
        year_fraction = day_count.yearFraction(dateS, dateE)
        print(f"{name}: {year_fraction:.4f}")
    except RuntimeError as e:
        print(f"Error calculating year fraction for {name}: {e}")

output:

Actual360: 30.4694
Actual365Fixed: 30.0521
Error calculating year fraction for Actual365Fixed(Canadian): invalid refPeriodStart
Actual365FixedNoLeap: 30.0301
ActualActual ISDA: 30.0322
ActualActual Bond: 30.0833
ActualActual ISMA: 30.0833
ActualActual Historical: 30.0322
ActualActual Actual365: 30.0322
ActualActual AFB: 30.0301
ActualActual Euro: 30.0301
Business252: 29.9206
Thirty360 ISDA: 30.0278
Thirty360 USA: 30.0306
Thirty360 BondBAsis: 30.0306
Thirty360 European: 30.0278
Thirty360 EuroBondBasis: 30.0278
SimpleDayCounter NASD: 30.0306
$\endgroup$
1
  • $\begingroup$ Thanks, this is useful and confirms my 'worry‘ that there might not be readily available equivalent function in quantlib ☹. $\endgroup$ Commented Jun 18 at 10:40
3
$\begingroup$

I'm not sure what convention Matlab means when they say "actual/actual"; there are a few of them, see e.g. https://www.isda.org/a/AIJEE/1998-ISDA-memo-%E2%80%9CEMU-and-Market-Conventions-Recent-Developments%E2%80%9D.pdf .

In general, though, the actual/actual conventions are used to calculate coupon period and require to define some kind of underlying reference period (annual, semiannual or whatever the coupons say). If the start and end date of the reference period are not passed, QuantLib is forced to make some kind of assumption. This might be the source of the difference.

Also, the requirement of a reference period might make the actual/actual conventions a poor choice for measuring time in general, as you seem to be doing here (or so I'm guessing, since your two sample dates are 30 years apart and therefore are unlikely to define a coupon). Again, hard to say without knowing what's your purpose—but you might be better served by using some simpler day count convention like actual/360 or actual/365.

$\endgroup$
2
  • $\begingroup$ Thanks, the background is that the existing Matlab application generates (fits) yield curve and internally it uses date2time function with act/act basis to calculate terms. I cannot change the way how that curve data is represented and my task is that the new python-based system can take those curves from Matlab app and derive discount factors consistently with the way they were originally fitted in the Matlab code. I cannot change the old Matlab code at this stage, which is why I’m searching for equivalent function in python. $\endgroup$ Commented Jun 18 at 10:38
  • $\begingroup$ I see. You might have to rewrite the matlab logic, then. Unless you can do something like having your matlab code output a table of discount factors for each day, at which point the conversion to times becomes moot. I don't know if that would be useable in your Python code, though. $\endgroup$ Commented Jun 18 at 14:42
1
$\begingroup$

I think the underlying question should be why you would want to use Matlab's date2time computation as opposed to others? Also, do you know the exact use case of date2time specifically? According to Matlab, the following applies: enter image description here

Honestly, I am not sure of the use case of this, especially when combined with daycounts (it should be what yearfrac shows imho). Yearfrac is also consistent with implementations like quantlib.

enter image description here

What I find even more worrysome is that splitting the dates in date2time gives different answers. enter image description here

$\endgroup$
4
  • $\begingroup$ The background is that there is an existing Matlab-based system that generates (fits) yield curves and internally it uses date2time function to calculate 'terms'. If I want to replicate the same discount factors in my new python code (which will take the yield curve data from the old system as inputs), I need to use the same 'terms', otherwise the interpretation of that yield curve data is not consistent with the way it was derived. $\endgroup$ Commented Jun 18 at 10:19
  • $\begingroup$ But are you sure that these results are correct? Did you try to match your Matlab result with say Bloomberg? Quantlib and Rateslib both will match BBG. Using Yearfrac will also match it. $\endgroup$
    – AKdemy
    Commented Jun 18 at 11:04
  • $\begingroup$ Well I’m not sure if ‘correctness‘ is an issue here - the ‘terms‘ derived in matlab are not used explicitely outside of the system in any way, it is just used to convert discount factors to yields and vice versa internally in the Matlab app. As long as the same ‘term’ is used on both sides of the conversion we’ll end up with identical discount factors. My issue is that now I want to take the curves from Matlab and interpret them consistently in the new python system. I can obviously challenge Mathworks regarding their date2time function but that won’t really help me at this stage. $\endgroup$ Commented Jun 18 at 11:29
  • $\begingroup$ I really do not think date2time is providing yearfractions according to market standards. If you insist on using this logic, you will need to reverse engineer whatever Matlab is doing with this function. Personally, I would be more content using market standards. $\endgroup$
    – AKdemy
    Commented Jun 18 at 12:01
0
$\begingroup$

I ran into a similar problem when trying to move MATLAB code to QuantLib. This one is for pricing a bond, but the problem was the underlying calculation of year Fraction.

After some research, I figured out that the problem was due to how time periods were calculated in MATLAB vs how they should be calculated.

Consider the following bond:

Settlement Date: 20/05/2023 Maturity Date: 24/12/2023 Interest Rate (Assume Flat): 5% with Semiannual compounding

To calculate the time periods for compounding, both MATLAB and Quantlib move back in time from maturity date.

Days in First half moving back (24/12/2022 - 24/06/2023): 182 Days in Second half moving back (24/06/2023 - 24/12/2023): 183

We get a nice table of how many days are there in each of these periods:

enter image description here

Now MATLAB would output the periods as 1.192307692 (1+0.192307692) while Quantlib would give them as 1.194520548 (0.597260274 x 2). You can see there is a small difference here which carries over to pricing, yields etc.

As far as I know, there is no way to do mimic MATLAB's calculation in QuantLib.

I created a custom function to calculate the discounting period for prices, yields and durations in python and was able to match Quantlib. I will have to dig that function up but I could post it if it would be of interest.

$\endgroup$
1
  • 1
    $\begingroup$ With your specific example, the "quantlib" value you refer to is an "ActActISDA" day count convention, whilst your "Matlab" calculation is an "ActActICMA" convention applied to a front stub. An "ActActICMA" convention using back stub would give a third value of 0.5934065 (or 1.1868131/2), and since you specify the maturity of the bond is at the end of the current period then this really should be a back stub. I do believe Quantlib can properly handle these conventions. But you can also review: rateslib.readthedocs.io/en/latest/api/… $\endgroup$
    – Attack68
    Commented Jul 9 at 20:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.