COUP*



Here's my suggestion for getting bases done and dusted.  I have not
had much time to test it, so be warned.

I think it's quite clean; it removes about 50 lines of code, makes us
compatible with Excel for Windows (to the best of my knowledge) and is
quite clear how day counts work.

Neil.

2002-01-16  Neil Booth  <neil daikokuya demon co uk>

        * datetime.c (adjust_dates_basis): Remove.
        (days_between_dep_basis): Remove.
        (day_count): New function.
        (coupdays): Return float, use day_count.
        (coupdaysbs, coupdaysnc): Use day_count.
        * datetime.h (basis_t): Document, and add convention 30E+/360.
        (adjust_dates_basis, days_between_dep_basis): Remove.
        (day_count): Update.

Index: datetime.c
===================================================================
RCS file: /cvs/gnome/gnumeric/src/datetime.c,v
retrieving revision 1.18
diff -u -p -r1.18 datetime.c
--- datetime.c  2002/01/15 06:17:04     1.18
+++ datetime.c  2002/01/16 07:46:49
@@ -308,117 +308,66 @@ datetime_weeknum (GDate *date, int metho
 /* ------------------------------------------------------------------------- */
 
 /*
- * adjust_dates_basis
+ * day_count
  *
  * @from      : GDate *
  * @to        : GDate * 
- * @basis     : int
- *        "0  US 30/360\n"
- *         "1  actual days/actual days\n"
- *         "2  actual days/360\n"
- *         "3  actual days/365\n"
- *         "4  European 30/360\n"
+ * @basis     : basis_t
  *
- * returns    : nothing. As side effects possibly adjusts from and to date
- *
- *
- */
-
-void
-adjust_dates_basis (GDate *from, GDate *to, int basis)
-{
-       switch (basis) {
-       case BASIS_30Ep360:
-               if (g_date_day(to) == 31)
-                       g_date_set_day (to, 30);
-               break;
-       case BASIS_30_360:
-               if (g_date_day(from) >= 30) {
-                       g_date_set_day (from, 30);
-                       if (g_date_day(to) == 31)
-                               g_date_set_day (to, 30);
-               }
-               break;
-       case BASIS_30E360:
-               if (g_date_day(from) == 31)
-                       g_date_set_day (from, 30);
-               if (g_date_day(to) == 31)
-                       g_date_set_day (to, 30);
-               break;
-       default:
-               break;
-       }
-       return;
-}
-
-/* ------------------------------------------------------------------------- */
-
-/*
- * days_between_dep_basis
- *
- * @from      : GDate *
- * @to        : GDate * 
- * @basis     : int
- *        "0  US 30/360\n"
- *         "1  actual days/actual days\n"
- *         "2  actual days/360\n"
- *         "3  actual days/365\n"
- *         "4  European 30/360\n"
- *
- * returns    : Number of days strictly between from and to +1
- *
- * Note: before calling this function adjust_dates_basis _must_
- *       have been called.
+ * returns    : Number of days between from and to under the day count
+ *             convention @basis.
  */
 
 gint32
-days_between_dep_basis (GDate *from, GDate *to, int basis)
+day_count (GDate *from, GDate *to, basis_t basis)
 {
-       GDate      from_date;
-       gint32     days;
-       gint       years;
-
-       switch (g_date_compare (from, to)) {
-       case 1:
-               return (- days_between_dep_basis (to, from, basis));
-       case 0:
-               return 0;
-       default:
-               break;
-       } 
-
-       if (basis == BASIS_ACTACT) 
-               return (g_date_julian (to) - g_date_julian (from));
-
-       g_date_clear (&from_date, 1);
-       g_date_set_julian (&from_date, g_date_julian (from));
-       years = g_date_year(to) - g_date_year(from);
-       g_date_add_years (&from_date, years);
-
-       if ((basis == BASIS_ACT365 || basis == BASIS_ACT360) &&
-           g_date_compare (&from_date, to) >= 0) {
-               years -= 1;
-               g_date_set_julian (&from_date, g_date_julian (from));
-               g_date_add_years (&from_date, years);
-       }
+       int d1, m1, y1, d2, m2, y2;
 
-       switch (basis) {
-       case BASIS_ACT365:
-               days = years * 365;
-               break;
-       default:
-               days = years * 360;
-               break;
+       /* ACT in the numerator means the number of days.  */
+       if (basis == BASIS_ACTACT
+           || basis == BASIS_ACT360
+           || basis == BASIS_ACT365)
+               return datetime_g_days_between (from, to);
+
+       /* The rest are the four 30/360-style bases.  */
+       d1 = g_date_day (from);
+       m1 = g_date_month (from);
+       y1 = g_date_day (from);
+
+       d2 = g_date_day (to);
+       m2 = g_date_month (to);
+       y2 = g_date_day (to);
+
+       /* Deal with this broken Excel-specific basis.  */
+       if (basis == BASIS_30N360)
+       {
+               if (d1 == 31 && d2 >= 30)
+                       d1 = 30;
+               if (d2 == 31 || (m2 == 2 && d2 >= 28))
+                       d2 = 30;
+       }
+       else
+       {
+               if (d1 == 31)
+                       d1 = 30;
+
+               if (d2 == 31)
+               {
+                       /* Only 30E/360 is unconditional, the others
+                          require d1 == 30.  */
+                       if (basis == BASIS_30E360)
+                               d2 = 30;
+                       else if (d1 == 30)
+                       {
+                               if (basis == BASIS_30Ep360)
+                                       d2 = 1, m2++;
+                               else
+                                       d2 = 30;
+                       }
+               }
        }
 
-       switch (basis) {
-       case BASIS_ACT360:
-       case BASIS_ACT365:
-               return days + g_date_julian (to) - g_date_julian (&from_date);
-       default:
-               return (days + 30 * (g_date_month(to) - g_date_month(&from_date))
-                       + g_date_day(to) - g_date_day(&from_date));
-       }
+       return ((y2 - y1) * 12 + (m2 - m1)) * 30 + (d2 - d1);
 }
 
 /* ------------------------------------------------------------------------- */
@@ -526,57 +475,57 @@ coup_cd_xl (GDate *settlement, GDate *ma
 
 
 /*
- * Returns the number of days in the coupon period of the settlement date.
- * Currently, returns negative numbers if the branch is not implemented.
+ * Returns the number of days in the coupon period of the settlement
+ * date.
  */
-int
-coupdays (GDate *settlement, GDate *maturity, int freq, int basis, gboolean oem, 
-         gboolean xl)
+gfloat
+coupdays (GDate *settlement, GDate *maturity, int freq, basis_t basis,
+         gboolean oem, gboolean xl)
 {
-       GDate *prev;
-       GDate *next;
-       int   days;
-
-        switch (basis) {
-        case BASIS_30Ep360:
-        case BASIS_30E360:
-        case BASIS_30_360:
-               return (12 / freq) * 30;
-       default:
-               break;
-        }
+       GDate *prev, *next;
+       gint32 result;
 
-/* Now we need to look at the real coupon dates */
+       switch (basis)
+       {
+       case BASIS_ACT365:
+               return 365.0 / freq;
+       case BASIS_30N360:
+       case BASIS_ACT360:
+       case BASIS_30E360:
+       case BASIS_30_360:
+       case BASIS_30Ep360:
+               return 360.0 / freq;
+       case BASIS_ACTACT:
+               break;
+       }
 
+       /* Act/Act is the only "interesting" case.  */
        prev = xl ? coup_cd_xl (settlement, maturity, freq, FALSE) :
                coup_cd (settlement, maturity, freq, oem, FALSE);
        next = xl ? coup_cd_xl (settlement, maturity, freq, TRUE) :
                coup_cd (settlement, maturity, freq, oem, TRUE);
-       adjust_dates_basis (prev, next, basis);
-       days = days_between_dep_basis (prev, next, basis);
+       result = datetime_g_days_between (prev, next) / (gfloat) freq;
        g_date_free (prev);
        g_date_free (next);
-       return days;
+       return result;
 }
 
 /* ------------------------------------------------------------------------- */
 
-
 /*
  * Returns the number of days from the beginning of the coupon period to
  * the settlement date.
  */
 int
-coupdaybs (GDate *settlement, GDate *maturity, int freq, int basis, gboolean oem, 
-          gboolean xl)
+coupdaybs (GDate *settlement, GDate *maturity, int freq, basis_t basis,
+          gboolean oem, gboolean xl)
 {
        GDate      *prev_coupon;
        int        days;
 
        prev_coupon = xl ? coup_cd_xl (settlement, maturity, freq, FALSE) :
                coup_cd (settlement, maturity, freq, oem, FALSE);
-       adjust_dates_basis (settlement, prev_coupon, basis);
-       days = - days_between_dep_basis (settlement, prev_coupon, basis);
+       days = day_count (prev_coupon, settlement, basis);
        g_date_free (prev_coupon);
        return days;
 }
@@ -590,16 +539,15 @@ coupdaybs (GDate *settlement, GDate *mat
  */
 
 int
-coupdaysnc (GDate *settlement, GDate *maturity, int freq, int basis, gboolean oem, 
-           gboolean xl)
+coupdaysnc (GDate *settlement, GDate *maturity, int freq, basis_t basis,
+           gboolean oem, gboolean xl)
 {
        GDate      *next_coupon;
        int        days;
 
        next_coupon = xl ? coup_cd_xl (settlement, maturity, freq, TRUE) :
                coup_cd (settlement, maturity, freq, oem, TRUE);
-       adjust_dates_basis (settlement, next_coupon, basis);
-       days = days_between_dep_basis (settlement, next_coupon, basis);
+       days = day_count (settlement, next_coupon, basis);
        g_date_free (next_coupon);
        return days;
 }
Index: datetime.h
===================================================================
RCS file: /cvs/gnome/gnumeric/src/datetime.h,v
retrieving revision 1.9
diff -u -p -r1.9 datetime.h
--- datetime.h  2002/01/15 06:17:05     1.9
+++ datetime.h  2002/01/16 07:46:49
@@ -57,30 +57,42 @@ int datetime_g_years_between (GDate *dat
 /* week number according to the given method. */
 int datetime_weeknum (GDate *date, int method);
 
+/* 30N360 is what Excel calls 30/360 (NASD).  For d2, it puts 31->30 and
+       last-day-of-Feb->30, and for d1 sets 31->30 if original d2 >= 30.
+       It is unused in today's financial markets to the best of my knowledge.
+   Act/Act is actual-number-of-days / actual-number-of-days.
+   Act/360 is actual-number-of-days / 360.
+   Act/365 is actual-number-of-days / 365.
+   30E360 puts 31->30 for both dates.
+   30_360 puts 31->30 for d1, and d2 puts 31->30 iff d1 >= 30.
+   30Ep360 puts 31->30 for d1, and if d1 is >= 30 and d2 is 31, sets
+       d2->1 and increments m2.
+*/
+
 typedef enum {
-       BASIS_30Ep360 = 0, /* first date untouched, second date 31->30 */
+       BASIS_30N360  = 0,
        BASIS_ACTACT  = 1, 
        BASIS_ACT360  = 2,
        BASIS_ACT365  = 3,
-       BASIS_30E360  = 4, /* 31->30 for both dates */
-       BASIS_30_360  = 5  /* 31->30 for first date, 31->30 if first date is >= 30 */
+       BASIS_30E360  = 4,
+       BASIS_30_360  = 5,
+       BASIS_30Ep360 = 6
 } basis_t;
-
-void adjust_dates_basis (GDate *from, GDate *to, int basis);
-
-gint32 days_between_dep_basis (GDate *from, GDate *to, int basis);
 
-GDate * coup_cd (GDate *settlement, GDate *maturity, int freq, gboolean oem, gboolean next);
-GDate * coup_cd_xl (GDate *settlement, GDate *maturity, int freq, gboolean next);
+gint32 day_count (GDate *from, GDate *to, basis_t basis);
 
-int coupdays (GDate *settlement, GDate *maturity, int freq, int basis, gboolean oem, 
-             gboolean xl);
+GDate * coup_cd (GDate *settlement, GDate *maturity, int freq, gboolean oem,
+                gboolean next);
+GDate * coup_cd_xl (GDate *settlement, GDate *maturity, int freq,
+                   gboolean next);
 
-int coupdaybs (GDate *settlement, GDate *maturity, int freq, int basis, gboolean oem, 
-              gboolean xl);
+gfloat coupdays (GDate *settlement, GDate *maturity, int freq, basis_t basis,
+                gboolean oem, gboolean xl);
 
-int coupdaysnc (GDate *settlement, GDate *maturity, int freq, int basis, gboolean oem, 
-               gboolean xl);
+int coupdaybs (GDate *settlement, GDate *maturity, int freq, basis_t basis,
+              gboolean oem, gboolean xl);
 
+int coupdaysnc (GDate *settlement, GDate *maturity, int freq, basis_t basis,
+               gboolean oem, gboolean xl);
 
 #endif




[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]