Forecasting Portfolio Cash Flows

Overview

A portfolio is just a collection of individual assets. As such, to forecast the

Implementation

A portfolio is just an array of individual assets. The code below assumes that there a function, forecast, which takes an object representing a loan or bond, and returns an array of cash flows. The code iterates through each bond, obtains its cash flows, and then adds each in turn to the cashFlows array.

let cashFlows = []; for(let bond of portfolio){ let newFlows = forecast(bond); newFlows.forEach(p=>cashFlows.push(p)); }
let instruments = [ {dateRoll:'following', start:'2020-03-10', years:10, periods:2, monthrule:true, type:'bullet', principal:100,rate:0.045}, {dateRoll:'following', start:'2020-03-10', years:10, periods:2, monthrule:true, type:'annualized', principal:100, rate:0.03}, ]; let pd = await import('/code/pay-dates/v1.0.0/pay-dates.mjs'); let amt = await import('/code/amortization/v1.0.0/amortization.mjs'); let dt = await import('/code/calendar/v1.0.0/calendar.mjs'); /* start is start date of instrument years is integer number of years periodsPerYear is number of pay date in a years endOfMonth is true or false depending on whether the end of month rule is in effect */ function forecast(start, type,principal,years, periodsPerYear, endOfMonth, dateRoll, rate){ let results = []; let dates = pd.dates(start, years, periodsPerYear, endOfMonth); let valid = p=> dt.isWeekday(p); dates = dates.map(date=>{ return pd.dateRoll(date, dateRoll, valid,options={}) }); if(type === 'bullet'){ let payment = principal* rate/periodsPerYear; let payments = dates.map(date=>{ return { date:date, amount:payment }; }); payments.forEach(p=>results.push(p)); results.push({ date:dates[dates.length-1], amount:principal }); } else if(type==='annualized'){ let payment = amt.payment(principal,rate,periodsPerYear*years,periodsPerYear); let payments = dates.map(date=>{ return { date:date, amount:payment }; }); payments.forEach(p=>results.push(p)); } return results; } let cashFlows = instruments.map(p=>forecast(p.start,p.type,p.principal, p.years, p.periods, p.monthrule, p.dateRoll, p.rate)); if(flatten) cashFlows = cashFlows.flat();

Grouping

Once the cashflows have been concatenated into a single array, it is often desirable to merge two cashflows that occur on the same date into a single amount.

The group api can be used to group the records on the date and then to sum the amounts. (see also grouping)

let payments1 = [{date:'2000-01-15', payment:100},{date:'2000-02-15', payment:100},{date:'2000-03-15', payment:100},{date:'2000-04-15', payment:100}] let payments2 = [{date:'2000-01-15', payment:200},{date:'2000-02-15', payment:120},{date:'2000-03-15', payment:100},{date:'2000-04-15', payment:140}]; //aggregate the payments into a single array let payments = [payments1, payments2].flat(); let group = $group(payments, p=>p.date); //group the payments by date, then reduce them to a single payment let portfolioCashFlows = group.map((keys,values)=>{ let payment = values.reduce((total, p)=>p.payment+total, 0); return payment; })();