UNPKG

parallel.es

Version:
189 lines (188 loc) 8.83 kB
"use strict"; var index_1 = require("../src/browser/index"); function initializeOptions(options) { return Object.assign({}, { investmentAmount: 1000000, liquidity: 10000, numRuns: 10000, numYears: 10, performance: 0, projects: [], seed: undefined, volatility: 0.01 }, options); } function createMonteCarloEnvironment(options) { /** * Performs the monte carlo simulation for all years and num runs. * @param cashFlows the cash flows * @returns {number[][]} the simulated outcomes grouped by year */ function simulateOutcomes(cashFlows, numYears) { function toAbsoluteIndices(indices) { var currentPortfolioValue = options.investmentAmount; var previousYearIndex = 100; for (var relativeYear = 0; relativeYear < indices.length; ++relativeYear) { var currentYearIndex = indices[relativeYear]; var cashFlowStartOfYear = relativeYear === 0 ? 0 : cashFlows[relativeYear - 1]; // scale current value with performance gain according to index var performance_1 = currentYearIndex / previousYearIndex; currentPortfolioValue = (currentPortfolioValue + cashFlowStartOfYear) * performance_1; indices[relativeYear] = Math.round(currentPortfolioValue); previousYearIndex = currentYearIndex; } return indices; } var result = new Array(options.numYears); for (var year = 0; year <= numYears; ++year) { result[year] = new Array(options.numRuns); } for (var run = 0; run < options.numRuns; run++) { var indices = [100]; for (var i = 1; i <= numYears; i++) { // const randomPerformance = 1 + random.normal(options.performance, options.volatility); var randomPerformance = 1 + Math.random(); indices.push(indices[i - 1] * randomPerformance); } // convert the relative values from above to absolute values. toAbsoluteIndices(indices); for (var year = 0; year < indices.length; ++year) { result[year][run] = indices[year]; } } return result; } function projectsToCashFlows() { var cashFlows = []; for (var year = 0; year < options.numYears; ++year) { var projectsByThisYear = projectsByStartYear[year] || []; var cashFlow = -projectsByThisYear.reduce(function (memo, project) { return memo + project.totalAmount; }, 0); cashFlows.push(cashFlow); } return cashFlows; } function calculateNoInterestReferenceLine(cashFlows) { var noInterestReferenceLine = []; var investmentAmountLeft = options.investmentAmount; for (var year = 0; year < options.numYears; ++year) { investmentAmountLeft = investmentAmountLeft + cashFlows[year]; noInterestReferenceLine.push(investmentAmountLeft); } return noInterestReferenceLine; } var projectsToSimulate = options.projects; if (options.taskIndex && options.valuesPerWorker) { projectsToSimulate = options.projects.slice(options.taskIndex * options.valuesPerWorker, (options.taskIndex + 1) * options.valuesPerWorker); } var projects = options.projects.sort(function (a, b) { return a.startYear - b.startYear; }); // Group projects by startYear, use lodash groupBy instead var projectsByStartYear = {}; for (var i = 0; i < projects.length; ++i) { var project = projects[i]; var arr = projectsByStartYear[project.startYear] = projectsByStartYear[project.startYear] || []; arr.push(project); } var cashFlows = projectsToCashFlows(); var noInterestReferenceLine = calculateNoInterestReferenceLine(cashFlows); var numYears = projectsToSimulate.reduce(function (memo, project) { return Math.max(memo, project.startYear); }, 0); return { investmentAmount: options.investmentAmount, liquidity: options.liquidity, noInterestReferenceLine: noInterestReferenceLine, numRuns: options.numRuns, numYears: numYears, projectsByStartYear: projectsByStartYear, simulatedValues: simulateOutcomes(cashFlows, numYears) }; } function calculateProject(project, environment) { var NUMBER_OF_BUCKETS = 10; function groupForValue(value, groups) { return groups.find(function (group) { return (typeof group.from === "undefined" || group.from <= value) && (typeof group.to === "undefined" || group.to > value); }); } function createGroups(requiredAmount, noInterestReference) { return [ { description: "Ziel erreichbar", from: requiredAmount, name: "green", percentage: 0, separator: true }, { description: "mit Zusatzliquidität erreichbar", from: requiredAmount - environment.liquidity, name: "yellow", percentage: 0, separator: true, to: requiredAmount }, { description: "nicht erreichbar", from: noInterestReference, name: "gray", percentage: 0, separator: false, to: requiredAmount - environment.liquidity }, { description: "nicht erreichbar, mit Verlust", name: "red", percentage: 0, separator: false, to: noInterestReference } ]; } function calculateRequiredAmount() { var amount = project.totalAmount; var projectsSameYear = environment.projectsByStartYear[project.startYear]; for (var i = 0; i < projectsSameYear.length; ++i) { var otherProject = projectsSameYear[i]; if (otherProject === project) { break; } amount += otherProject.totalAmount; } return amount; } function median(values) { var half = Math.floor(values.length / 2); if (values.length % 2) { return values[half]; } return (values[half - 1] + values[half]) / 2.0; } var requiredAmount = calculateRequiredAmount(); var simulatedValuesThisYear = environment.simulatedValues[project.startYear]; simulatedValuesThisYear.sort(function (a, b) { return a - b; }); var groups = createGroups(requiredAmount, environment.noInterestReferenceLine[project.startYear]); var valuesByGroup = {}; var bucketSize = Math.round(simulatedValuesThisYear.length / NUMBER_OF_BUCKETS); var buckets = []; for (var i = 0; i < simulatedValuesThisYear.length; i += bucketSize) { var bucket = { max: Number.MIN_VALUE, min: Number.MAX_VALUE, subBuckets: {} }; for (var j = i; j < i + bucketSize; ++j) { var value = simulatedValuesThisYear[j]; bucket.min = Math.min(bucket.min, value); bucket.max = Math.max(bucket.max, value); var group = groupForValue(simulatedValuesThisYear[j], groups); valuesByGroup[group.name] = (valuesByGroup[group.name] || 0) + 1; var subBucket = bucket.subBuckets[group.name] = bucket.subBuckets[group.name] || { group: group.name, max: Number.MIN_VALUE, min: Number.MAX_VALUE }; subBucket.min = Math.min(subBucket.min, value); subBucket.max = Math.max(subBucket.max, value); } buckets.push(bucket); } var nonEmptyGroups = groups.filter(function (group) { return !!valuesByGroup[group.name]; }); nonEmptyGroups.forEach(function (group) { return group.percentage = valuesByGroup[group.name] / simulatedValuesThisYear.length; }); var oneSixth = Math.round(simulatedValuesThisYear.length / 6); return { buckets: buckets, groups: nonEmptyGroups, max: simulatedValuesThisYear[simulatedValuesThisYear.length - 1], median: median(simulatedValuesThisYear), min: simulatedValuesThisYear[0], project: project, twoThird: { max: simulatedValuesThisYear[simulatedValuesThisYear.length - oneSixth], min: simulatedValuesThisYear[oneSixth] } }; } function syncMonteCarlo(options) { var environment = createMonteCarloEnvironment(initializeOptions(options)); var projects = []; for (var _i = 0, _a = options.projects; _i < _a.length; _i++) { var project = _a[_i]; projects.push(calculateProject(project, environment)); } return projects; } exports.syncMonteCarlo = syncMonteCarlo; function parallelMonteCarlo(userOptions) { var options = initializeOptions(userOptions); return index_1["default"] .from(options.projects, { minValuesPerTask: 2 }) .inEnvironment(createMonteCarloEnvironment, options) .map(calculateProject); } exports.parallelMonteCarlo = parallelMonteCarlo;