Case Study 2: Certificate of Deposit Interest Accrual System

Background

Prairie Plains Savings Bank (PPSB) is a community bank operating in a four-state region with approximately $2.8 billion in total assets. Certificates of Deposit (CDs) represent a significant portion of the bank's funding base: approximately 42,000 active CDs with a combined balance of $1.1 billion. CDs are time deposits where the customer agrees to leave funds on deposit for a fixed term in exchange for a higher interest rate than a regular savings account.

PPSB offers CDs with terms ranging from 7 days to 60 months, at rates that vary by term length and deposit amount. Interest accrues daily and is either paid out monthly, credited to the CD at maturity, or compounded into the balance depending on the product type. The bank's nightly batch processing system, written in COBOL, runs a daily interest accrual program that calculates and records interest for every active CD.

This case study examines the design and implementation of PPSB's CD interest accrual system, covering daily interest calculation using day count conventions, compound interest for various CD terms, early withdrawal penalty computation, and maturity processing with automatic rollover logic. The system demonstrates the critical role of packed-decimal arithmetic in ensuring that every penny of interest is calculated correctly across tens of thousands of accounts, 365 days per year.

Business Requirements

The CD interest accrual system must handle the following:

  1. Daily interest accrual: Calculate interest earned each day for every active CD based on the CD's balance, rate, and the Actual/365 day count convention. Interest amounts must be accurate to the penny.

  2. Multiple compounding methods: Support three interest crediting options: - Simple interest (interest accrues but does not compound; paid at maturity) - Monthly compounding (interest is added to the balance on the last day of each month) - Continuous accrual (interest compounds daily)

  3. Early withdrawal penalty: When a customer breaks a CD before maturity, calculate the penalty based on the CD term: - Terms 7-90 days: 30 days of simple interest - Terms 91-365 days: 90 days of simple interest - Terms over 365 days: 180 days of simple interest

  4. Maturity processing: When a CD reaches its maturity date: - Credit final accrued interest to the CD balance - If auto-rollover is enabled, open a new CD at the current rate for the same term - If no auto-rollover, transfer the proceeds to the customer's savings account - Generate maturity notices for customer mailing

  5. Regulatory compliance: Interest calculations must comply with the Truth in Savings Act (Regulation DD), which requires that the Annual Percentage Yield (APY) disclosed to the customer matches the actual yield produced by the bank's calculation system.

Day Count Convention: Actual/365

PPSB uses the Actual/365 day count convention for all CD products. Under this convention:

Daily Rate = Annual Rate / 365

Interest for one day is calculated as:

Daily Interest = Balance * Daily Rate

This means that in a leap year, 366 days of interest accrue, producing a slightly higher annual yield than the stated rate. The Actual/365 convention is the most common for consumer deposit products in the United States. Some commercial instruments use Actual/360, which produces higher interest because the daily rate is larger (annual rate divided by 360 instead of 365).

Complete COBOL Program

       IDENTIFICATION DIVISION.
       PROGRAM-ID.  CDACCRUAL.
       AUTHOR.      PPSB DEPOSIT SYSTEMS.
       DATE-WRITTEN. 2024-06-01.
      *================================================================*
      * PROGRAM: CDACCRUAL - CERTIFICATE OF DEPOSIT INTEREST ACCRUAL  *
      *                                                                *
      * PURPOSE: Perform daily interest accrual for all active CDs.   *
      *          Handle compounding, maturity processing, early        *
      *          withdrawal penalties, and auto-rollover.              *
      *                                                                *
      * PROCESSING FREQUENCY: Daily batch (nightly cycle)              *
      *                                                                *
      * INPUT FILES:                                                   *
      *   CDMASTER - CD master file (VSAM KSDS, keyed by account)     *
      *   CDRATES  - Current rate table for rollovers                  *
      *   DATEPARM - Processing date parameter                        *
      *                                                                *
      * OUTPUT FILES:                                                  *
      *   CDMASTER - Updated CD master file (in place)                 *
      *   ACCRRPT  - Daily accrual report                              *
      *   MATNOTIC - Maturity notice file for customer mailing         *
      *   PENALTYL - Early withdrawal penalty log                      *
      *================================================================*

       ENVIRONMENT DIVISION.
       CONFIGURATION SECTION.
       REPOSITORY.
           FUNCTION ALL INTRINSIC.

       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT CD-MASTER-FILE
               ASSIGN TO CDMASTER
               ORGANIZATION IS INDEXED
               ACCESS MODE IS SEQUENTIAL
               RECORD KEY IS CM-ACCOUNT-NUMBER
               FILE STATUS IS WS-MASTER-STATUS.

           SELECT RATE-TABLE-FILE
               ASSIGN TO CDRATES
               ORGANIZATION IS LINE SEQUENTIAL
               FILE STATUS IS WS-RATE-STATUS.

           SELECT ACCRUAL-REPORT
               ASSIGN TO ACCRRPT
               ORGANIZATION IS LINE SEQUENTIAL
               FILE STATUS IS WS-REPORT-STATUS.

           SELECT MATURITY-NOTICE-FILE
               ASSIGN TO MATNOTIC
               ORGANIZATION IS LINE SEQUENTIAL
               FILE STATUS IS WS-NOTICE-STATUS.

       DATA DIVISION.
       FILE SECTION.

       FD  CD-MASTER-FILE.
       01  CD-MASTER-RECORD.
           05  CM-ACCOUNT-NUMBER      PIC X(12).
           05  CM-CUSTOMER-NAME       PIC X(30).
           05  CM-ORIGINAL-BALANCE    PIC S9(11)V99 COMP-3.
           05  CM-CURRENT-BALANCE     PIC S9(11)V99 COMP-3.
           05  CM-ANNUAL-RATE         PIC S9(02)V9(06) COMP-3.
           05  CM-OPEN-DATE           PIC 9(08).
           05  CM-MATURITY-DATE       PIC 9(08).
           05  CM-TERM-DAYS           PIC S9(04) COMP-3.
           05  CM-COMPOUND-CODE       PIC X(01).
               88  CM-SIMPLE-INT      VALUE 'S'.
               88  CM-MONTHLY-COMP    VALUE 'M'.
               88  CM-DAILY-COMP      VALUE 'D'.
           05  CM-ACCRUED-INTEREST    PIC S9(09)V99 COMP-3.
           05  CM-YTD-INTEREST        PIC S9(09)V99 COMP-3.
           05  CM-LAST-ACCRUAL-DATE   PIC 9(08).
           05  CM-AUTO-ROLLOVER       PIC X(01).
               88  CM-ROLLOVER-YES    VALUE 'Y'.
               88  CM-ROLLOVER-NO     VALUE 'N'.
           05  CM-STATUS-CODE         PIC X(01).
               88  CM-ACTIVE          VALUE 'A'.
               88  CM-MATURED         VALUE 'M'.
               88  CM-WITHDRAWN       VALUE 'W'.
               88  CM-ROLLED-OVER     VALUE 'R'.
           05  CM-PENALTY-AMOUNT      PIC S9(07)V99 COMP-3.
           05  FILLER                 PIC X(20).

       FD  RATE-TABLE-FILE.
       01  RATE-TABLE-RECORD.
           05  RT-TERM-MIN            PIC 9(04).
           05  RT-TERM-MAX            PIC 9(04).
           05  RT-CURRENT-RATE        PIC 99V9(04).
           05  FILLER                 PIC X(62).

       FD  ACCRUAL-REPORT
           RECORDING MODE IS F
           RECORD CONTAINS 132 CHARACTERS.
       01  REPORT-LINE                PIC X(132).

       FD  MATURITY-NOTICE-FILE
           RECORDING MODE IS F
           RECORD CONTAINS 132 CHARACTERS.
       01  NOTICE-LINE                PIC X(132).

       WORKING-STORAGE SECTION.

      *----------------------------------------------------------------*
      * FILE STATUS FIELDS                                             *
      *----------------------------------------------------------------*
       01  WS-FILE-STATUSES.
           05  WS-MASTER-STATUS       PIC X(02).
           05  WS-RATE-STATUS         PIC X(02).
           05  WS-REPORT-STATUS       PIC X(02).
           05  WS-NOTICE-STATUS       PIC X(02).

       01  WS-FLAGS.
           05  WS-EOF-MASTER          PIC X(01) VALUE 'N'.
               88  MASTER-EOF         VALUE 'Y'.
               88  MASTER-NOT-EOF     VALUE 'N'.
           05  WS-EOF-RATES           PIC X(01) VALUE 'N'.
               88  RATES-EOF          VALUE 'Y'.
           05  WS-RATE-FOUND          PIC X(01) VALUE 'N'.
               88  ROLLOVER-RATE-FOUND VALUE 'Y'.

      *----------------------------------------------------------------*
      * PROCESSING DATE - LOADED FROM PARAMETER FILE OR SYSTEM         *
      *----------------------------------------------------------------*
       01  WS-PROCESS-DATE            PIC 9(08).
       01  WS-PROCESS-DATE-R REDEFINES WS-PROCESS-DATE.
           05  WS-PROC-YEAR           PIC 9(04).
           05  WS-PROC-MONTH          PIC 9(02).
           05  WS-PROC-DAY            PIC 9(02).

       01  WS-PROCESS-INTEGER-DATE    PIC 9(08) COMP.

      *----------------------------------------------------------------*
      * INTEREST CALCULATION FIELDS - EXTENDED PRECISION               *
      *----------------------------------------------------------------*
       01  WS-INTEREST-CALC.
           05  WS-DAILY-RATE          PIC S9(02)V9(12) COMP-3.
           05  WS-DAILY-INTEREST      PIC S9(07)V9(06) COMP-3.
           05  WS-DAILY-INT-ROUNDED   PIC S9(07)V99    COMP-3.
           05  WS-ACCRUAL-BASE        PIC S9(13)V99    COMP-3.
           05  WS-DAYS-TO-ACCRUE      PIC S9(04)       COMP-3.
           05  WS-DAY-COUNTER         PIC S9(04)       COMP-3.

      *----------------------------------------------------------------*
      * MATURITY AND PENALTY FIELDS                                    *
      *----------------------------------------------------------------*
       01  WS-MATURITY-FIELDS.
           05  WS-MATURITY-INTEGER    PIC 9(08) COMP.
           05  WS-DAYS-REMAINING      PIC S9(04) COMP-3.
           05  WS-DAYS-HELD           PIC S9(04) COMP-3.
           05  WS-PENALTY-DAYS        PIC S9(04) COMP-3.
           05  WS-PENALTY-AMOUNT      PIC S9(07)V99 COMP-3.
           05  WS-MATURITY-BALANCE    PIC S9(13)V99 COMP-3.
           05  WS-ROLLOVER-RATE       PIC S9(02)V9(06) COMP-3.

      *----------------------------------------------------------------*
      * MONTH-END COMPOUNDING FIELDS                                   *
      *----------------------------------------------------------------*
       01  WS-MONTH-END-FIELDS.
           05  WS-IS-MONTH-END        PIC X(01) VALUE 'N'.
               88  MONTH-END-TODAY    VALUE 'Y'.
               88  NOT-MONTH-END      VALUE 'N'.
           05  WS-NEXT-DAY-MONTH      PIC 9(02).
           05  WS-NEXT-DAY-DATE       PIC 9(08).
           05  WS-NEXT-DAY-INTEGER    PIC 9(08) COMP.

      *----------------------------------------------------------------*
      * CURRENT RATE TABLE (LOADED AT INITIALIZATION)                  *
      *----------------------------------------------------------------*
       01  WS-RATE-TABLE.
           05  WS-RATE-COUNT          PIC 9(02) VALUE ZEROS.
           05  WS-RATE-ENTRY OCCURS 20 TIMES.
               10  WS-RT-TERM-MIN    PIC 9(04).
               10  WS-RT-TERM-MAX    PIC 9(04).
               10  WS-RT-RATE        PIC S9(02)V9(06) COMP-3.
       01  WS-RATE-IDX                PIC 9(02).

      *----------------------------------------------------------------*
      * COUNTERS AND ACCUMULATORS                                      *
      *----------------------------------------------------------------*
       01  WS-COUNTERS.
           05  WS-CDS-PROCESSED       PIC 9(06) VALUE ZEROS.
           05  WS-CDS-ACCRUED         PIC 9(06) VALUE ZEROS.
           05  WS-CDS-MATURED         PIC 9(06) VALUE ZEROS.
           05  WS-CDS-ROLLED          PIC 9(06) VALUE ZEROS.
           05  WS-CDS-WITHDRAWN       PIC 9(06) VALUE ZEROS.
           05  WS-TOTAL-ACCRUAL       PIC S9(11)V99 COMP-3
                                      VALUE ZEROS.
           05  WS-TOTAL-PENALTIES     PIC S9(09)V99 COMP-3
                                      VALUE ZEROS.

      *----------------------------------------------------------------*
      * REPORT LINES                                                   *
      *----------------------------------------------------------------*
       01  WS-RPT-HEADER.
           05  FILLER                 PIC X(01) VALUE SPACE.
           05  FILLER                 PIC X(55)
               VALUE 'PRAIRIE PLAINS SAVINGS BANK - DAILY CD '
                     'ACCRUAL REPORT'.
           05  FILLER                 PIC X(40) VALUE SPACES.
           05  FILLER                 PIC X(17) VALUE 'PROCESS DATE:   '.
           05  WH-PROC-DATE          PIC 9999/99/99.
           05  FILLER                 PIC X(10) VALUE SPACES.

       01  WS-RPT-COL-HDR.
           05  FILLER                 PIC X(01) VALUE SPACE.
           05  FILLER                 PIC X(14) VALUE 'ACCOUNT      '.
           05  FILLER                 PIC X(18) VALUE '   BALANCE        '.
           05  FILLER                 PIC X(10) VALUE 'RATE      '.
           05  FILLER                 PIC X(06) VALUE 'COMP  '.
           05  FILLER                 PIC X(18) VALUE '  DAILY INT       '.
           05  FILLER                 PIC X(18) VALUE '  ACCRUED INT     '.
           05  FILLER                 PIC X(12) VALUE 'STATUS      '.
           05  FILLER                 PIC X(35) VALUE SPACES.

       01  WS-RPT-DETAIL.
           05  FILLER                 PIC X(01) VALUE SPACE.
           05  WRD-ACCOUNT            PIC X(12).
           05  FILLER                 PIC X(02) VALUE SPACES.
           05  WRD-BALANCE            PIC $Z,ZZZ,ZZ9.99.
           05  FILLER                 PIC X(02) VALUE SPACES.
           05  WRD-RATE               PIC Z9.9999.
           05  FILLER                 PIC X(02) VALUE '%'.
           05  FILLER                 PIC X(01) VALUE SPACE.
           05  WRD-COMP-TYPE          PIC X(03).
           05  FILLER                 PIC X(02) VALUE SPACES.
           05  WRD-DAILY-INT          PIC $ZZZ,ZZ9.99.
           05  FILLER                 PIC X(02) VALUE SPACES.
           05  WRD-ACCRUED            PIC $ZZZ,ZZ9.99.
           05  FILLER                 PIC X(02) VALUE SPACES.
           05  WRD-STATUS             PIC X(10).
           05  FILLER                 PIC X(35) VALUE SPACES.

       01  WS-RPT-SUMMARY-LINE.
           05  FILLER                 PIC X(132) VALUE ALL '='.

       01  WS-RPT-SUM-1.
           05  FILLER                 PIC X(01) VALUE SPACE.
           05  FILLER                 PIC X(25)
               VALUE 'CDS PROCESSED:          '.
           05  WSUM-PROCESSED         PIC ZZ,ZZ9.
           05  FILLER                 PIC X(10) VALUE SPACES.
           05  FILLER                 PIC X(25)
               VALUE 'CDS ACCRUED:            '.
           05  WSUM-ACCRUED           PIC ZZ,ZZ9.
           05  FILLER                 PIC X(53) VALUE SPACES.

       01  WS-RPT-SUM-2.
           05  FILLER                 PIC X(01) VALUE SPACE.
           05  FILLER                 PIC X(25)
               VALUE 'CDS MATURED:            '.
           05  WSUM-MATURED           PIC ZZ,ZZ9.
           05  FILLER                 PIC X(10) VALUE SPACES.
           05  FILLER                 PIC X(25)
               VALUE 'CDS ROLLED OVER:        '.
           05  WSUM-ROLLED            PIC ZZ,ZZ9.
           05  FILLER                 PIC X(53) VALUE SPACES.

       01  WS-RPT-SUM-3.
           05  FILLER                 PIC X(01) VALUE SPACE.
           05  FILLER                 PIC X(28)
               VALUE 'TOTAL ACCRUAL TODAY:     $'.
           05  WSUM-TOTAL-ACCR        PIC Z,ZZZ,ZZ9.99.
           05  FILLER                 PIC X(07) VALUE SPACES.
           05  FILLER                 PIC X(28)
               VALUE 'TOTAL PENALTIES TODAY:   $'.
           05  WSUM-TOTAL-PEN         PIC Z,ZZZ,ZZ9.99.
           05  FILLER                 PIC X(39) VALUE SPACES.

       01  WS-BLANK-LINE             PIC X(132) VALUE SPACES.

       PROCEDURE DIVISION.

      *================================================================*
      * MAIN CONTROL                                                   *
      *================================================================*
       0000-MAIN-CONTROL.
           PERFORM 1000-INITIALIZE
           PERFORM 2000-PROCESS-CDS
               UNTIL MASTER-EOF
           PERFORM 9000-FINALIZE
           STOP RUN
           .

      *================================================================*
      * INITIALIZE - OPEN FILES, LOAD RATE TABLE, SET PROCESS DATE     *
      *================================================================*
       1000-INITIALIZE.
           OPEN I-O    CD-MASTER-FILE
           OPEN INPUT  RATE-TABLE-FILE
           OPEN OUTPUT ACCRUAL-REPORT
           OPEN OUTPUT MATURITY-NOTICE-FILE

      *    Set processing date from system date
           MOVE FUNCTION CURRENT-DATE(1:8)
               TO WS-PROCESS-DATE

      *    Convert process date to integer for date arithmetic
           COMPUTE WS-PROCESS-INTEGER-DATE =
               FUNCTION INTEGER-OF-DATE(WS-PROCESS-DATE)

      *    Determine if today is month-end by checking
      *    whether tomorrow is the first of a new month
           COMPUTE WS-NEXT-DAY-INTEGER =
               WS-PROCESS-INTEGER-DATE + 1
           COMPUTE WS-NEXT-DAY-DATE =
               FUNCTION DATE-OF-INTEGER(WS-NEXT-DAY-INTEGER)
           MOVE WS-NEXT-DAY-DATE(5:2) TO WS-NEXT-DAY-MONTH
           IF WS-NEXT-DAY-MONTH NOT = WS-PROC-MONTH
               SET MONTH-END-TODAY TO TRUE
           ELSE
               SET NOT-MONTH-END TO TRUE
           END-IF

      *    Load current rate table for rollovers
           PERFORM 1100-LOAD-RATE-TABLE

      *    Write report header
           MOVE WS-PROCESS-DATE TO WH-PROC-DATE
           WRITE REPORT-LINE FROM WS-RPT-HEADER
           WRITE REPORT-LINE FROM WS-RPT-SUMMARY-LINE
           WRITE REPORT-LINE FROM WS-RPT-COL-HDR
           WRITE REPORT-LINE FROM WS-RPT-SUMMARY-LINE

      *    Read first CD record
           PERFORM 8000-READ-MASTER
           .

      *================================================================*
      * LOAD RATE TABLE FROM FILE INTO WORKING-STORAGE TABLE           *
      *================================================================*
       1100-LOAD-RATE-TABLE.
           MOVE ZEROS TO WS-RATE-COUNT
           READ RATE-TABLE-FILE
               AT END SET RATES-EOF TO TRUE
           END-READ

           PERFORM UNTIL RATES-EOF
               ADD 1 TO WS-RATE-COUNT
               MOVE RT-TERM-MIN TO
                   WS-RT-TERM-MIN(WS-RATE-COUNT)
               MOVE RT-TERM-MAX TO
                   WS-RT-TERM-MAX(WS-RATE-COUNT)
               COMPUTE WS-RT-RATE(WS-RATE-COUNT) =
                   RT-CURRENT-RATE / 100
               READ RATE-TABLE-FILE
                   AT END SET RATES-EOF TO TRUE
               END-READ
           END-PERFORM

           CLOSE RATE-TABLE-FILE
           .

      *================================================================*
      * PROCESS EACH CD - MAIN DISPATCH LOGIC                          *
      *================================================================*
       2000-PROCESS-CDS.
           ADD 1 TO WS-CDS-PROCESSED

           EVALUATE TRUE
               WHEN CM-ACTIVE
                   PERFORM 3000-CHECK-MATURITY
               WHEN CM-WITHDRAWN
                   CONTINUE
               WHEN CM-MATURED
                   CONTINUE
               WHEN CM-ROLLED-OVER
                   CONTINUE
               WHEN OTHER
                   DISPLAY 'UNKNOWN STATUS FOR ACCOUNT: '
                       CM-ACCOUNT-NUMBER
           END-EVALUATE

           PERFORM 8000-READ-MASTER
           .

      *================================================================*
      * CHECK IF CD HAS REACHED MATURITY DATE                          *
      *================================================================*
       3000-CHECK-MATURITY.
      *    Convert maturity date to integer for comparison
           COMPUTE WS-MATURITY-INTEGER =
               FUNCTION INTEGER-OF-DATE(CM-MATURITY-DATE)

           IF WS-PROCESS-INTEGER-DATE >= WS-MATURITY-INTEGER
      *        CD has matured - process maturity
               PERFORM 5000-PROCESS-MATURITY
           ELSE
      *        CD is still active - accrue interest
               PERFORM 4000-ACCRUE-DAILY-INTEREST
           END-IF
           .

      *================================================================*
      * ACCRUE DAILY INTEREST FOR AN ACTIVE CD                         *
      *                                                                *
      * Daily interest = Balance * (Annual Rate / 365)                 *
      *                                                                *
      * For daily compounding, the balance grows each day.             *
      * For monthly compounding, interest accrues in a separate        *
      * field and is added to the balance at month-end.                *
      * For simple interest, interest accrues separately and is        *
      * never added to the balance during the term.                    *
      *================================================================*
       4000-ACCRUE-DAILY-INTEREST.
           ADD 1 TO WS-CDS-ACCRUED

      *    Calculate daily rate with 12 decimal places
      *    to preserve precision
           COMPUTE WS-DAILY-RATE =
               CM-ANNUAL-RATE / 365

      *    Determine the accrual base depending on
      *    compounding method
           EVALUATE TRUE
               WHEN CM-DAILY-COMP
      *            Daily compounding: interest accrues on
      *            balance plus previously accrued interest
                   COMPUTE WS-ACCRUAL-BASE =
                       CM-CURRENT-BALANCE
               WHEN CM-MONTHLY-COMP
      *            Monthly compounding: interest accrues on
      *            original balance (balance is updated at
      *            month-end only)
                   COMPUTE WS-ACCRUAL-BASE =
                       CM-CURRENT-BALANCE
               WHEN CM-SIMPLE-INT
      *            Simple interest: always accrue on the
      *            original deposit amount
                   COMPUTE WS-ACCRUAL-BASE =
                       CM-ORIGINAL-BALANCE
           END-EVALUATE

      *    Calculate daily interest with extended precision
           COMPUTE WS-DAILY-INTEREST ROUNDED =
               WS-ACCRUAL-BASE * WS-DAILY-RATE
               ON SIZE ERROR
                   DISPLAY 'SIZE ERROR: DAILY INT CALC '
                       CM-ACCOUNT-NUMBER
           END-COMPUTE

      *    Round to cents for posting
           COMPUTE WS-DAILY-INT-ROUNDED ROUNDED =
               WS-DAILY-INTEREST
               ON SIZE ERROR
                   DISPLAY 'SIZE ERROR: ROUNDING '
                       CM-ACCOUNT-NUMBER
           END-COMPUTE

      *    Add daily interest to accrued interest
           ADD WS-DAILY-INT-ROUNDED TO CM-ACCRUED-INTEREST
               ON SIZE ERROR
                   DISPLAY 'SIZE ERROR: ACCRUED ADD '
                       CM-ACCOUNT-NUMBER
           END-ADD

      *    Add to year-to-date interest
           ADD WS-DAILY-INT-ROUNDED TO CM-YTD-INTEREST
               ON SIZE ERROR
                   DISPLAY 'SIZE ERROR: YTD ADD '
                       CM-ACCOUNT-NUMBER
           END-ADD

      *    Add to daily total for report
           ADD WS-DAILY-INT-ROUNDED TO WS-TOTAL-ACCRUAL

      *    For daily compounding, add interest to balance
           IF CM-DAILY-COMP
               ADD WS-DAILY-INT-ROUNDED TO CM-CURRENT-BALANCE
               MOVE ZEROS TO CM-ACCRUED-INTEREST
           END-IF

      *    For monthly compounding, add accrued interest to
      *    balance on the last day of the month
           IF CM-MONTHLY-COMP AND MONTH-END-TODAY
               ADD CM-ACCRUED-INTEREST TO CM-CURRENT-BALANCE
               MOVE ZEROS TO CM-ACCRUED-INTEREST
           END-IF

      *    Update last accrual date
           MOVE WS-PROCESS-DATE TO CM-LAST-ACCRUAL-DATE

      *    Rewrite the updated record
           REWRITE CD-MASTER-RECORD

      *    Write report detail line
           PERFORM 4500-WRITE-ACCRUAL-DETAIL
           .

      *================================================================*
      * WRITE DETAIL LINE TO ACCRUAL REPORT                            *
      *================================================================*
       4500-WRITE-ACCRUAL-DETAIL.
           MOVE CM-ACCOUNT-NUMBER     TO WRD-ACCOUNT
           MOVE CM-CURRENT-BALANCE    TO WRD-BALANCE
           COMPUTE WRD-RATE = CM-ANNUAL-RATE * 100
           MOVE WS-DAILY-INT-ROUNDED  TO WRD-DAILY-INT
           MOVE CM-ACCRUED-INTEREST   TO WRD-ACCRUED

           EVALUATE TRUE
               WHEN CM-SIMPLE-INT
                   MOVE 'SMP' TO WRD-COMP-TYPE
               WHEN CM-MONTHLY-COMP
                   MOVE 'MTH' TO WRD-COMP-TYPE
               WHEN CM-DAILY-COMP
                   MOVE 'DLY' TO WRD-COMP-TYPE
           END-EVALUATE

           MOVE 'ACCRUED' TO WRD-STATUS

           WRITE REPORT-LINE FROM WS-RPT-DETAIL
           .

      *================================================================*
      * PROCESS CD MATURITY                                            *
      *                                                                *
      * When a CD matures:                                             *
      *   1. Credit any remaining accrued interest                     *
      *   2. Either roll over into a new CD or mark as matured         *
      *   3. Generate maturity notice                                  *
      *================================================================*
       5000-PROCESS-MATURITY.
           ADD 1 TO WS-CDS-MATURED

      *    Accrue any remaining interest for the maturity day
           PERFORM 4000-ACCRUE-DAILY-INTEREST

      *    Credit remaining accrued interest to the balance
      *    (for simple and monthly, there may be uncredited
      *    accrued interest)
           ADD CM-ACCRUED-INTEREST TO CM-CURRENT-BALANCE
           MOVE ZEROS TO CM-ACCRUED-INTEREST

      *    Store the final maturity balance
           MOVE CM-CURRENT-BALANCE TO WS-MATURITY-BALANCE

      *    Check for auto-rollover
           IF CM-ROLLOVER-YES
               PERFORM 6000-PROCESS-ROLLOVER
           ELSE
               MOVE 'M' TO CM-STATUS-CODE
               REWRITE CD-MASTER-RECORD
           END-IF

      *    Generate maturity notice
           PERFORM 7000-WRITE-MATURITY-NOTICE
           .

      *================================================================*
      * PROCESS AUTOMATIC ROLLOVER INTO NEW CD                         *
      *                                                                *
      * Look up the current rate for the same term length and          *
      * update the CD record with new dates, rate, and reset           *
      * all accrual fields.                                            *
      *================================================================*
       6000-PROCESS-ROLLOVER.
           ADD 1 TO WS-CDS-ROLLED

      *    Find current rate for this term
           MOVE 'N' TO WS-RATE-FOUND
           PERFORM VARYING WS-RATE-IDX FROM 1 BY 1
               UNTIL WS-RATE-IDX > WS-RATE-COUNT
               OR ROLLOVER-RATE-FOUND
               IF CM-TERM-DAYS >= WS-RT-TERM-MIN(WS-RATE-IDX)
                   AND CM-TERM-DAYS <=
                       WS-RT-TERM-MAX(WS-RATE-IDX)
                   MOVE WS-RT-RATE(WS-RATE-IDX)
                       TO WS-ROLLOVER-RATE
                   MOVE 'Y' TO WS-RATE-FOUND
               END-IF
           END-PERFORM

      *    If no rate found, keep the existing rate
           IF NOT ROLLOVER-RATE-FOUND
               MOVE CM-ANNUAL-RATE TO WS-ROLLOVER-RATE
           END-IF

      *    Update CD record for the new term
           MOVE CM-CURRENT-BALANCE TO CM-ORIGINAL-BALANCE
           MOVE WS-ROLLOVER-RATE   TO CM-ANNUAL-RATE
           MOVE WS-PROCESS-DATE    TO CM-OPEN-DATE

      *    Calculate new maturity date by adding term days
           COMPUTE WS-MATURITY-INTEGER =
               WS-PROCESS-INTEGER-DATE + CM-TERM-DAYS
           COMPUTE CM-MATURITY-DATE =
               FUNCTION DATE-OF-INTEGER(WS-MATURITY-INTEGER)

      *    Reset accrual fields
           MOVE ZEROS TO CM-ACCRUED-INTEREST
           MOVE ZEROS TO CM-YTD-INTEREST
           MOVE WS-PROCESS-DATE TO CM-LAST-ACCRUAL-DATE
           MOVE ZEROS TO CM-PENALTY-AMOUNT
           MOVE 'R' TO CM-STATUS-CODE

           REWRITE CD-MASTER-RECORD
           .

      *================================================================*
      * CALCULATE EARLY WITHDRAWAL PENALTY                             *
      *                                                                *
      * Penalty schedule (based on original term):                     *
      *   7-90 days:    30 days of simple interest                     *
      *   91-365 days:  90 days of simple interest                     *
      *   Over 365:    180 days of simple interest                     *
      *                                                                *
      * Penalty = Original Balance * Daily Rate * Penalty Days         *
      * The penalty is deducted from the accrued interest first;       *
      * if the penalty exceeds accrued interest, the remainder is      *
      * deducted from principal.                                       *
      *================================================================*
       6500-CALC-EARLY-WITHDRAWAL.
      *    Determine penalty days based on original term
           EVALUATE TRUE
               WHEN CM-TERM-DAYS >= 7
                   AND CM-TERM-DAYS <= 90
                   MOVE 30 TO WS-PENALTY-DAYS
               WHEN CM-TERM-DAYS >= 91
                   AND CM-TERM-DAYS <= 365
                   MOVE 90 TO WS-PENALTY-DAYS
               WHEN CM-TERM-DAYS > 365
                   MOVE 180 TO WS-PENALTY-DAYS
               WHEN OTHER
                   MOVE 30 TO WS-PENALTY-DAYS
           END-EVALUATE

      *    Calculate penalty amount
      *    Penalty = Original Balance * (Annual Rate / 365)
      *              * Penalty Days
           COMPUTE WS-DAILY-RATE =
               CM-ANNUAL-RATE / 365

           COMPUTE WS-PENALTY-AMOUNT ROUNDED =
               CM-ORIGINAL-BALANCE * WS-DAILY-RATE
               * WS-PENALTY-DAYS
               ON SIZE ERROR
                   DISPLAY 'SIZE ERROR: PENALTY CALC '
                       CM-ACCOUNT-NUMBER
           END-COMPUTE

      *    Deduct penalty from accrued interest first
           IF WS-PENALTY-AMOUNT <= CM-ACCRUED-INTEREST
               SUBTRACT WS-PENALTY-AMOUNT
                   FROM CM-ACCRUED-INTEREST
           ELSE
      *        Penalty exceeds accrued interest: deduct
      *        remainder from balance
               SUBTRACT CM-ACCRUED-INTEREST
                   FROM WS-PENALTY-AMOUNT
                   GIVING WS-PENALTY-AMOUNT
               MOVE ZEROS TO CM-ACCRUED-INTEREST
               SUBTRACT WS-PENALTY-AMOUNT
                   FROM CM-CURRENT-BALANCE
           END-IF

      *    Record penalty and update status
           MOVE WS-PENALTY-AMOUNT TO CM-PENALTY-AMOUNT
           ADD WS-PENALTY-AMOUNT TO WS-TOTAL-PENALTIES
           MOVE 'W' TO CM-STATUS-CODE

           REWRITE CD-MASTER-RECORD
           .

      *================================================================*
      * WRITE MATURITY NOTICE FOR CUSTOMER MAILING                     *
      *================================================================*
       7000-WRITE-MATURITY-NOTICE.
           INITIALIZE NOTICE-LINE

           STRING 'CD MATURITY NOTICE - ACCOUNT: '
                  DELIMITED BY SIZE
                  CM-ACCOUNT-NUMBER
                  DELIMITED BY SIZE
                  '  CUSTOMER: '
                  DELIMITED BY SIZE
                  CM-CUSTOMER-NAME
                  DELIMITED BY SIZE
               INTO NOTICE-LINE
           END-STRING

           WRITE NOTICE-LINE
           .

      *================================================================*
      * READ NEXT CD MASTER RECORD                                     *
      *================================================================*
       8000-READ-MASTER.
           READ CD-MASTER-FILE
               AT END
                   SET MASTER-EOF TO TRUE
           END-READ
           .

      *================================================================*
      * FINALIZE - WRITE SUMMARY AND CLOSE FILES                       *
      *================================================================*
       9000-FINALIZE.
      *    Write summary to report
           WRITE REPORT-LINE FROM WS-RPT-SUMMARY-LINE
           WRITE REPORT-LINE FROM WS-BLANK-LINE

           MOVE WS-CDS-PROCESSED    TO WSUM-PROCESSED
           MOVE WS-CDS-ACCRUED      TO WSUM-ACCRUED
           WRITE REPORT-LINE FROM WS-RPT-SUM-1

           MOVE WS-CDS-MATURED      TO WSUM-MATURED
           MOVE WS-CDS-ROLLED       TO WSUM-ROLLED
           WRITE REPORT-LINE FROM WS-RPT-SUM-2

           MOVE WS-TOTAL-ACCRUAL    TO WSUM-TOTAL-ACCR
           MOVE WS-TOTAL-PENALTIES  TO WSUM-TOTAL-PEN
           WRITE REPORT-LINE FROM WS-RPT-SUM-3

      *    Close all files
           CLOSE CD-MASTER-FILE
                 ACCRUAL-REPORT
                 MATURITY-NOTICE-FILE

      *    Display processing summary
           DISPLAY 'CD ACCRUAL PROCESSING COMPLETE'
           DISPLAY '  CDS PROCESSED:    ' WS-CDS-PROCESSED
           DISPLAY '  CDS ACCRUED:      ' WS-CDS-ACCRUED
           DISPLAY '  CDS MATURED:      ' WS-CDS-MATURED
           DISPLAY '  CDS ROLLED OVER:  ' WS-CDS-ROLLED
           DISPLAY '  TOTAL ACCRUAL:    ' WS-TOTAL-ACCRUAL
           .

The JCL to execute the daily accrual batch follows:

//CDACCRUA JOB (ACCT),'CD DAILY ACCRUAL',CLASS=A,
//         MSGCLASS=X,NOTIFY=&SYSUID
//*
//*================================================================*
//* CD INTEREST ACCRUAL - DAILY BATCH PROCESSING                   *
//*================================================================*
//*
//STEP01   EXEC PGM=CDACCRUAL
//STEPLIB  DD   DSN=PPSB.DEPOSIT.LOADLIB,DISP=SHR
//CDMASTER DD   DSN=PPSB.CD.MASTER,DISP=SHR
//CDRATES  DD   DSN=PPSB.CD.RATETABLE,DISP=SHR
//ACCRRPT  DD   DSN=PPSB.CD.ACCRUAL.REPORT(+1),
//         DISP=(NEW,CATLG,DELETE),
//         SPACE=(CYL,(2,1),RLSE),
//         DCB=(RECFM=FB,LRECL=132,BLKSIZE=0)
//MATNOTIC DD   DSN=PPSB.CD.MATURITY.NOTICES(+1),
//         DISP=(NEW,CATLG,DELETE),
//         SPACE=(CYL,(1,1),RLSE),
//         DCB=(RECFM=FB,LRECL=132,BLKSIZE=0)
//SYSOUT   DD   SYSOUT=*

How the Program Works

Initialization (1000-INITIALIZE)

The program opens all files, determines the processing date from the system clock, and performs an important month-end check. It calculates whether today is the last day of the month by adding 1 to the current date and checking whether the month changes. This determination drives the monthly compounding logic. The current CD rate table is loaded into a WORKING-STORAGE table for use in rollover processing.

Daily Interest Accrual (4000-ACCRUE-DAILY-INTEREST)

The core of the system, this paragraph calculates one day of interest for a single CD. The daily rate is derived by dividing the annual rate by 365, using a field with 12 decimal places (PIC S9(02)V9(12)) to avoid premature truncation of the repeating decimal. The accrual base depends on the compounding method:

  • Simple interest: Always uses the original deposit amount, so interest never compounds.
  • Monthly compounding: Uses the current balance, which is updated with accrued interest only at month-end.
  • Daily compounding: Uses the current balance, which includes previously credited interest. Interest is added to the balance immediately, creating true compound growth.

The daily interest is first computed with six decimal places of precision, then rounded to cents for posting. This two-step approach prevents cumulative rounding bias: the high-precision calculation captures the true fractional-cent interest, and rounding occurs only at the final step.

Month-End Processing

On the last day of each month, CDs with monthly compounding have their accumulated accrued interest credited to the balance. This increases the base for the next month's interest calculations, implementing compound growth at a monthly frequency. The month-end detection uses COBOL intrinsic functions: INTEGER-OF-DATE converts the processing date to a serial integer, adds 1 to get tomorrow, DATE-OF-INTEGER converts back, and the month is compared.

Maturity Processing (5000-PROCESS-MATURITY)

When the processing date reaches or exceeds a CD's maturity date, the program accrues any remaining interest, credits all accrued interest to the balance, and either processes a rollover or marks the CD as matured. The maturity balance is preserved for the maturity notice.

Rollover Logic (6000-PROCESS-ROLLOVER)

For auto-rollover CDs, the program looks up the current rate for the same term length from the rate table loaded during initialization. The CD record is updated with new dates, the new rate, and reset accrual fields. The matured balance (including all earned interest) becomes the opening balance of the new CD.

Early Withdrawal Penalty (6500-CALC-EARLY-WITHDRAWAL)

The penalty is calculated as a specified number of days of simple interest on the original deposit amount. The penalty days are determined by the original term: 30 days for short-term CDs, 90 days for medium-term, and 180 days for long-term. The penalty is deducted first from accrued interest; if the penalty exceeds the accrued interest, the remainder is deducted from principal, which can result in the customer receiving less than their original deposit.

Precision Analysis

Consider a $100,000 CD at 4.50% with daily compounding:

  • Daily rate: 0.045000 / 365 = 0.000123287671... (repeating decimal)
  • Daily interest (day 1): $100,000.00 * 0.000123287671 = $12.3287671... rounded to $12.33
  • New balance: $100,012.33
  • Daily interest (day 2): $100,012.33 * 0.000123287671 = $12.3302873... rounded to $12.33
  • After 365 days: Balance grows to approximately $104,602.78

If the daily rate were stored with only 2 decimal places (0.00), the interest calculation would produce $0.00 per day, clearly wrong. With 4 decimal places (0.0001), the daily interest would be $10.00, significantly understated. The 12-decimal-place precision ensures the daily rate is captured accurately enough to produce correct penny-level results when multiplied by the balance.

APY Verification

The Truth in Savings Act requires banks to disclose the Annual Percentage Yield (APY), which accounts for the compounding effect. For the CD above:

  • Stated rate: 4.50%
  • APY with daily compounding: (1 + 0.045/365)^365 - 1 = 4.6027% approximately

The bank must ensure that the APY produced by the COBOL calculation matches the disclosed APY. This is verified by running the daily accrual for a full year on a test CD and computing the actual yield: (ending balance - starting balance) / starting balance * 100.

Discussion Questions

  1. Why does the program use the original deposit amount (CM-ORIGINAL-BALANCE) as the accrual base for simple interest CDs, while using the current balance for compound interest CDs? What would happen if simple interest were calculated on the current balance?

  2. Explain why the daily rate field uses 12 decimal places. What would be the annual impact on a $500,000 CD if the daily rate were truncated to 6 decimal places?

  3. The early withdrawal penalty is based on simple interest regardless of the CD's compounding method. Why is this the standard practice? How would the penalty differ if it were based on compound interest?

  4. When processing a rollover, the program looks up the current rate rather than using the original rate. What business and regulatory reasons require this? What would the customer impact be during a period of rising rates versus falling rates?

  5. The month-end detection logic adds 1 to the integer date and checks whether the month changes. Why is this more reliable than comparing the day to a table of month-end dates (28, 30, or 31)?

Connection to Chapter Concepts

This case study directly applies these Chapter 33 concepts:

  • COMP-3 packed decimal: All monetary fields, rates, and intermediate calculations use COMP-3 for exact decimal arithmetic (Section 33.2).
  • Day count conventions: The Actual/365 convention is implemented with discussion of how Actual/360 would produce different results (Section 33.6).
  • Compound interest calculation: Three compounding methods (simple, monthly, daily) demonstrate different applications of the compound interest principle (Section 33.3).
  • ROUNDED phrase: Every interest COMPUTE uses ROUNDED to ensure proper cent-level rounding (Section 33.3).
  • ON SIZE ERROR: Overflow detection on all monetary calculations protects against silent data corruption (Section 33.3).
  • Intermediate precision: The daily rate field carries 12 decimal places to prevent premature truncation (Section 33.2).
  • Table-driven rate lookups: The rate table demonstrates the pattern of loading configurable rates from a file rather than hardcoding them (Section 33.7).
  • Date arithmetic: COBOL intrinsic functions INTEGER-OF-DATE and DATE-OF-INTEGER perform maturity date calculations (Section 33.6).