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:
-
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.
-
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)
-
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
-
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
-
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
-
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?
-
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?
-
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?
-
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?
-
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).