annualReturns = {
const annualReturns = new Map();
function addAnnualReturns(startYear, endYear, inputPortfolios, shouldRebalance, retryAttempt) {
let url = `https://www.portfoliovisualizer.com/backtest-asset-class-allocation?s=y&mode=1&timePeriod=4&startYear=${startYear}&firstMonth=1&endYear=${endYear}&lastMonth=12&calendarAligned=true&includeYTD=false&initialAmount=1000000&annualOperation=0&annualAdjustment=0&inflationAdjusted=true&annualPercentage=0.0&frequency=4&rebalanceType=${shouldRebalance?1:0}&absoluteDeviation=5.0&relativeDeviation=25.0&leverageType=0&leverageRatio=0.0&debtAmount=0&debtInterest=0.0&maintenanceMargin=25.0&leveragedBenchmark=false&portfolioNames=false&portfolioName1=Portfolio+1&portfolioName2=Portfolio+2&portfolioName3=Portfolio+3&asset1=TotalStockMarket&allocation1_1=${inputPortfolios[0].percentUS}&asset2=IntlStockMarket&allocation2_1=${inputPortfolios[0].percentIntl}&asset3=TotalBond&allocation3_1=${inputPortfolios[0].percentBond}`;
if (inputPortfolios.length == 2) {
url += `&allocation1_2=${inputPortfolios[1].percentUS}&allocation2_2=${inputPortfolios[1].percentIntl}&allocation3_2=${inputPortfolios[1].percentBond}`;
}
return tabletojson.tabletojson.convertUrl(url,
{ stripHtmlFromCells: false }
).then(async (tablesAsJson) => {
if (tablesAsJson.length == 0) {
console.log(`Got 429, sleeping and retrying. ${startYear}-${endYear} rebalance=${shouldRebalance} retryAttempt ${retryAttempt}`);
await new Promise(r => setTimeout(r, 2000));
await addAnnualReturns(startYear, endYear, inputPortfolios, shouldRebalance, retryAttempt + 1);
return;
}
for (let i=0; i < inputPortfolios.length; i++) {
const cagrCell = tablesAsJson[inputPortfolios.length][i].CAGR;
const annualReturn = Number(cagrCell.slice(0, cagrCell.search("%")));
const inflationAdjustedAnnualReturn = Number(cagrCell.substring(cagrCell.search("is ") + 3, cagrCell.search("%\"")));
if (!annualReturns.get(endYear - startYear)) {
annualReturns.set(endYear - startYear, new Array());
}
let outputPortfolios = annualReturns.get(endYear - startYear);
outputPortfolios.push({
name: `${inputPortfolios[i].name} - ${inputPortfolios[i].percentUS} US / ${inputPortfolios[i].percentIntl} Intl / ${inputPortfolios[i].percentBond} Bond`,
startYear: startYear,
annualReturn: annualReturn,
inflationAdjustedAnnualReturn: inflationAdjustedAnnualReturn,
});
}
}).catch ((e) => {
console.log(`Error: ${e}`);
});
}
// Data gathering 1: Go as far back as possible at the expense of starting portfolioNumYears. Stored in https://gist.github.com/melissachang/b4a888c550a2e02406af12cf7252ee2b
// endYear must be at least 1987; otherwise get error "Not enough data for Total US Bond Market
// for the selected time period. Available data range is Jan 1987 - Sep 2023". Start with
// portfolioNumYears = 15 (so firstYearOnPortfolioVisualizer can go as far back as possible) because 1972 + 15 = 1987.
// const firstYearOnPortfolioVisualizer = 1972;
// const lastYearOnPortfolioVisualizer = 2023;
// for (var portfolioNumYears = 15; portfolioNumYears <= 50; portfolioNumYears++) {
// Data gathering 2: Make firstYearOnPortfolioVisualizer later so portfolioNumYears can go as low as 1.
// I want to show Allan 2018-2023.
// Stored in https://gist.github.com/melissachang/0a5a77ffa4ed2ca7cd3112a29478824b
const firstYearOnPortfolioVisualizer = 1986;
const lastYearOnPortfolioVisualizer = 2023;
for (var portfolioNumYears = 1; portfolioNumYears <= 50; portfolioNumYears++) {
for (var startYear = firstYearOnPortfolioVisualizer; startYear <= lastYearOnPortfolioVisualizer - portfolioNumYears; startYear++) {
await addAnnualReturns(
startYear,
startYear + portfolioNumYears,
[
{ name: "High risk - no rebalance", percentUS: 60, percentIntl: 30, percentBond: 10},
{ name: "All stock", percentUS: 100, percentIntl: 0, percentBond: 0},
],
/* shouldRebalance */ false,
/* retryAttempt */ 0,
);
await addAnnualReturns(
startYear,
startYear + portfolioNumYears,
[
{ name: "High risk - with rebalance", percentUS: 60, percentIntl: 30, percentBond: 10},
{ name: "Moderate risk - with rebalance", percentUS: 40, percentIntl: 20, percentBond: 40},
],
/* shouldRebalance */ true,
/* retryAttempt */ 0,
);
}
}
return annualReturns;
}