Skip to main content
Glama
sigaihealth

RealVest Real Estate MCP Server

monte-carlo.js29.5 kB
export class MonteCarloSimulator { constructor() { this.name = 'Monte Carlo Real Estate Simulator'; this.description = 'Simulate thousands of scenarios to assess investment risk and return probabilities'; } getSchema() { return { type: 'object', properties: { investment_parameters: { type: 'object', description: 'Base investment parameters', properties: { purchase_price: { type: 'number', description: 'Property purchase price', minimum: 0 }, down_payment_percent: { type: 'number', description: 'Down payment percentage', minimum: 0, maximum: 100, default: 20 }, closing_costs: { type: 'number', description: 'Closing costs', minimum: 0, default: 0 }, holding_period_years: { type: 'number', description: 'Investment holding period', minimum: 1, maximum: 30, default: 5 }, loan_interest_rate: { type: 'number', description: 'Mortgage interest rate (%)', minimum: 0, maximum: 20, default: 7 }, loan_term_years: { type: 'number', description: 'Loan term in years', minimum: 1, maximum: 40, default: 30 } }, required: ['purchase_price'] }, variable_distributions: { type: 'object', description: 'Probability distributions for uncertain variables', properties: { rental_income: { type: 'object', description: 'Monthly rental income distribution', properties: { type: { type: 'string', enum: ['normal', 'uniform', 'triangular'], default: 'normal' }, mean: { type: 'number', description: 'Mean/expected monthly rent', minimum: 0 }, std_dev: { type: 'number', description: 'Standard deviation (for normal distribution)', minimum: 0 }, min: { type: 'number', description: 'Minimum value (for uniform/triangular)', minimum: 0 }, max: { type: 'number', description: 'Maximum value (for uniform/triangular)', minimum: 0 }, mode: { type: 'number', description: 'Most likely value (for triangular)', minimum: 0 } }, required: ['type', 'mean'] }, vacancy_rate: { type: 'object', description: 'Vacancy rate distribution (%)', properties: { type: { type: 'string', enum: ['normal', 'uniform', 'triangular'], default: 'triangular' }, mean: { type: 'number', description: 'Mean vacancy rate', minimum: 0, maximum: 100, default: 5 }, std_dev: { type: 'number', description: 'Standard deviation', minimum: 0 }, min: { type: 'number', description: 'Minimum vacancy rate', minimum: 0, default: 0 }, max: { type: 'number', description: 'Maximum vacancy rate', minimum: 0, maximum: 100, default: 20 }, mode: { type: 'number', description: 'Most likely vacancy rate', minimum: 0, default: 5 } } }, operating_expenses: { type: 'object', description: 'Annual operating expenses distribution', properties: { type: { type: 'string', enum: ['normal', 'uniform', 'triangular'], default: 'normal' }, mean: { type: 'number', description: 'Mean annual expenses', minimum: 0 }, std_dev: { type: 'number', description: 'Standard deviation', minimum: 0 }, min: { type: 'number', description: 'Minimum expenses', minimum: 0 }, max: { type: 'number', description: 'Maximum expenses', minimum: 0 } }, required: ['type', 'mean'] }, appreciation_rate: { type: 'object', description: 'Annual appreciation rate distribution (%)', properties: { type: { type: 'string', enum: ['normal', 'uniform', 'triangular'], default: 'normal' }, mean: { type: 'number', description: 'Mean appreciation rate', default: 3 }, std_dev: { type: 'number', description: 'Standard deviation', minimum: 0, default: 2 }, min: { type: 'number', description: 'Minimum appreciation', default: -5 }, max: { type: 'number', description: 'Maximum appreciation', default: 10 } } }, exit_cap_rate: { type: 'object', description: 'Exit cap rate distribution (%)', properties: { type: { type: 'string', enum: ['normal', 'uniform', 'triangular'], default: 'normal' }, mean: { type: 'number', description: 'Mean exit cap rate', minimum: 0, default: 6 }, std_dev: { type: 'number', description: 'Standard deviation', minimum: 0, default: 1 } } } }, required: ['rental_income', 'operating_expenses'] }, simulation_settings: { type: 'object', description: 'Monte Carlo simulation settings', properties: { num_simulations: { type: 'number', description: 'Number of simulations to run', minimum: 100, maximum: 100000, default: 10000 }, random_seed: { type: 'number', description: 'Random seed for reproducibility', default: null }, confidence_levels: { type: 'array', description: 'Confidence levels for VaR calculation', items: { type: 'number', minimum: 0, maximum: 100 }, default: [5, 10, 25, 50, 75, 90, 95] } } }, target_metrics: { type: 'object', description: 'Target values for probability calculations', properties: { minimum_irr: { type: 'number', description: 'Minimum acceptable IRR (%)', default: 10 }, minimum_cash_flow: { type: 'number', description: 'Minimum monthly cash flow', default: 0 }, maximum_loss: { type: 'number', description: 'Maximum acceptable loss', default: 0 } } } }, required: ['investment_parameters', 'variable_distributions'] }; } calculate(params) { const { investment_parameters, variable_distributions, simulation_settings = {}, target_metrics = {} } = params; const { num_simulations = 10000, random_seed = null, confidence_levels = [5, 10, 25, 50, 75, 90, 95] } = simulation_settings; // Initialize random number generator this.initializeRandom(random_seed); // Run simulations const simulationResults = []; for (let i = 0; i < num_simulations; i++) { const scenario = this.generateScenario(variable_distributions); const results = this.calculateScenarioResults(investment_parameters, scenario); simulationResults.push(results); } // Analyze results const statistics = this.calculateStatistics(simulationResults); const distributions = this.analyzeDistributions(simulationResults); const riskMetrics = this.calculateRiskMetrics(simulationResults, confidence_levels); const probabilities = this.calculateProbabilities(simulationResults, target_metrics); const correlations = this.calculateCorrelations(simulationResults); const scenarios = this.identifyKeyScenarios(simulationResults); return { summary_statistics: statistics, distributions: distributions, risk_metrics: riskMetrics, probability_analysis: probabilities, correlations: correlations, scenario_analysis: scenarios, confidence_intervals: this.calculateConfidenceIntervals(simulationResults, confidence_levels), recommendations: this.generateRecommendations(statistics, riskMetrics, probabilities), simulation_metadata: { num_simulations: num_simulations, random_seed: random_seed, timestamp: new Date().toISOString() } }; } initializeRandom(seed) { // Simple seedable random number generator this.seed = seed || Date.now(); this.random = () => { this.seed = (this.seed * 9301 + 49297) % 233280; return this.seed / 233280; }; } generateScenario(distributions) { const scenario = {}; // Generate rental income scenario.monthly_rent = this.sampleDistribution(distributions.rental_income); // Generate vacancy rate scenario.vacancy_rate = distributions.vacancy_rate ? this.sampleDistribution(distributions.vacancy_rate) : 5; // Generate operating expenses scenario.annual_expenses = this.sampleDistribution(distributions.operating_expenses); // Generate appreciation rate scenario.appreciation_rate = distributions.appreciation_rate ? this.sampleDistribution(distributions.appreciation_rate) : 3; // Generate exit cap rate scenario.exit_cap_rate = distributions.exit_cap_rate ? this.sampleDistribution(distributions.exit_cap_rate) : 6; return scenario; } sampleDistribution(distribution) { const { type, mean, std_dev, min, max, mode } = distribution; switch (type) { case 'normal': return this.sampleNormal(mean, std_dev || mean * 0.1); case 'uniform': return this.sampleUniform(min || mean * 0.8, max || mean * 1.2); case 'triangular': return this.sampleTriangular( min || mean * 0.8, max || mean * 1.2, mode || mean ); default: return mean; } } sampleNormal(mean, stdDev) { // Box-Muller transform const u1 = this.random(); const u2 = this.random(); const z0 = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2); return mean + z0 * stdDev; } sampleUniform(min, max) { return min + this.random() * (max - min); } sampleTriangular(min, max, mode) { const u = this.random(); const fc = (mode - min) / (max - min); if (u < fc) { return min + Math.sqrt(u * (max - min) * (mode - min)); } else { return max - Math.sqrt((1 - u) * (max - min) * (max - mode)); } } calculateScenarioResults(params, scenario) { const { purchase_price, down_payment_percent = 20, closing_costs = 0, holding_period_years = 5, loan_interest_rate = 7, loan_term_years = 30 } = params; const { monthly_rent, vacancy_rate, annual_expenses, appreciation_rate, exit_cap_rate } = scenario; // Initial investment const down_payment = purchase_price * (down_payment_percent / 100); const total_cash_invested = down_payment + closing_costs; const loan_amount = purchase_price - down_payment; // Calculate monthly payment const monthly_rate = loan_interest_rate / 100 / 12; const num_payments = loan_term_years * 12; let monthly_payment = 0; if (loan_amount > 0 && monthly_rate > 0) { monthly_payment = loan_amount * (monthly_rate * Math.pow(1 + monthly_rate, num_payments)) / (Math.pow(1 + monthly_rate, num_payments) - 1); } // Annual income and expenses const annual_rental_income = monthly_rent * 12 * (1 - vacancy_rate / 100); const annual_debt_service = monthly_payment * 12; const annual_cash_flow = annual_rental_income - annual_expenses - annual_debt_service; const monthly_cash_flow = annual_cash_flow / 12; // Build cash flows for IRR const cashFlows = [-total_cash_invested]; let cumulative_cash_flow = 0; for (let year = 1; year <= holding_period_years; year++) { if (year < holding_period_years) { cashFlows.push(annual_cash_flow); cumulative_cash_flow += annual_cash_flow; } else { // Exit year const future_value = purchase_price * Math.pow(1 + appreciation_rate / 100, holding_period_years); // Calculate exit value using cap rate if provided let exit_value = future_value; if (exit_cap_rate > 0) { const exit_noi = annual_rental_income - annual_expenses; const cap_rate_value = exit_noi / (exit_cap_rate / 100); exit_value = Math.min(future_value, cap_rate_value); // Conservative approach } // Calculate remaining loan balance const remaining_balance = this.calculateRemainingBalance( loan_amount, monthly_rate, num_payments, holding_period_years * 12 ); const sale_proceeds = exit_value - remaining_balance; cashFlows.push(annual_cash_flow + sale_proceeds); cumulative_cash_flow += annual_cash_flow; } } // Calculate metrics const irr = this.calculateIRR(cashFlows) * 100; const total_return = ((cumulative_cash_flow + cashFlows[cashFlows.length - 1] - annual_cash_flow) / total_cash_invested) * 100; const cash_on_cash = (annual_cash_flow / total_cash_invested) * 100; const equity_multiple = (cumulative_cash_flow + cashFlows[cashFlows.length - 1] - annual_cash_flow + total_cash_invested) / total_cash_invested; return { irr: irr, total_return: total_return, cash_on_cash_return: cash_on_cash, equity_multiple: equity_multiple, monthly_cash_flow: monthly_cash_flow, annual_cash_flow: annual_cash_flow, total_profit: cumulative_cash_flow + cashFlows[cashFlows.length - 1] - annual_cash_flow, exit_value: cashFlows[cashFlows.length - 1] - annual_cash_flow, // Include scenario inputs for correlation analysis inputs: { monthly_rent: monthly_rent, vacancy_rate: vacancy_rate, annual_expenses: annual_expenses, appreciation_rate: appreciation_rate, exit_cap_rate: exit_cap_rate } }; } calculateRemainingBalance(principal, monthlyRate, totalPayments, paymentsMade) { if (monthlyRate === 0) { return principal * (1 - paymentsMade / totalPayments); } const monthlyPayment = principal * (monthlyRate * Math.pow(1 + monthlyRate, totalPayments)) / (Math.pow(1 + monthlyRate, totalPayments) - 1); const remainingBalance = principal * Math.pow(1 + monthlyRate, paymentsMade) - monthlyPayment * (Math.pow(1 + monthlyRate, paymentsMade) - 1) / monthlyRate; return Math.max(0, remainingBalance); } calculateIRR(cashFlows) { let rate = 0.1; const maxIterations = 100; const tolerance = 0.00001; for (let i = 0; i < maxIterations; i++) { let npv = 0; let dnpv = 0; for (let j = 0; j < cashFlows.length; j++) { npv += cashFlows[j] / Math.pow(1 + rate, j); if (j > 0) { dnpv -= j * cashFlows[j] / Math.pow(1 + rate, j + 1); } } if (Math.abs(npv) < tolerance) { return rate; } const newRate = rate - npv / dnpv; if (Math.abs(newRate - rate) < tolerance) { return newRate; } // Bound the rate to prevent divergence rate = Math.max(-0.99, Math.min(newRate, 10)); } return rate; } calculateStatistics(results) { const metrics = ['irr', 'total_return', 'cash_on_cash_return', 'equity_multiple', 'monthly_cash_flow', 'total_profit']; const statistics = {}; metrics.forEach(metric => { const values = results.map(r => r[metric]); const sorted = [...values].sort((a, b) => a - b); const n = values.length; statistics[metric] = { mean: this.mean(values), median: sorted[Math.floor(n / 2)], std_dev: this.standardDeviation(values), min: sorted[0], max: sorted[n - 1], skewness: this.skewness(values), kurtosis: this.kurtosis(values) }; }); return statistics; } mean(values) { return values.reduce((sum, val) => sum + val, 0) / values.length; } standardDeviation(values) { const avg = this.mean(values); const squareDiffs = values.map(val => Math.pow(val - avg, 2)); return Math.sqrt(this.mean(squareDiffs)); } skewness(values) { const n = values.length; const mean = this.mean(values); const stdDev = this.standardDeviation(values); const sum = values.reduce((acc, val) => acc + Math.pow((val - mean) / stdDev, 3), 0); return (n / ((n - 1) * (n - 2))) * sum; } kurtosis(values) { const n = values.length; const mean = this.mean(values); const stdDev = this.standardDeviation(values); const sum = values.reduce((acc, val) => acc + Math.pow((val - mean) / stdDev, 4), 0); return (n * (n + 1) / ((n - 1) * (n - 2) * (n - 3))) * sum - 3 * Math.pow(n - 1, 2) / ((n - 2) * (n - 3)); } analyzeDistributions(results) { const distributions = {}; const metrics = ['irr', 'total_return', 'monthly_cash_flow']; metrics.forEach(metric => { const values = results.map(r => r[metric]); const histogram = this.createHistogram(values, 20); distributions[metric] = { histogram: histogram, percentiles: this.calculatePercentiles(values, [1, 5, 10, 25, 50, 75, 90, 95, 99]) }; }); return distributions; } createHistogram(values, numBins) { const min = Math.min(...values); const max = Math.max(...values); const binWidth = (max - min) / numBins; const bins = Array(numBins).fill(0).map((_, i) => ({ min: min + i * binWidth, max: min + (i + 1) * binWidth, count: 0, frequency: 0 })); values.forEach(val => { const binIndex = Math.min(Math.floor((val - min) / binWidth), numBins - 1); bins[binIndex].count++; }); bins.forEach(bin => { bin.frequency = bin.count / values.length; }); return bins; } calculatePercentiles(values, percentiles) { const sorted = [...values].sort((a, b) => a - b); const result = {}; percentiles.forEach(p => { const index = Math.ceil((p / 100) * sorted.length) - 1; result[`p${p}`] = sorted[Math.max(0, index)]; }); return result; } calculateRiskMetrics(results, confidenceLevels) { const metrics = {}; // Value at Risk (VaR) for different metrics ['irr', 'total_return', 'monthly_cash_flow'].forEach(metric => { const values = results.map(r => r[metric]); const sorted = [...values].sort((a, b) => a - b); metrics[metric] = { value_at_risk: {}, conditional_value_at_risk: {}, probability_of_loss: (values.filter(v => v < 0).length / values.length) * 100, downside_deviation: this.calculateDownsideDeviation(values) }; confidenceLevels.forEach(level => { const index = Math.floor((level / 100) * sorted.length); const var_value = sorted[index]; metrics[metric].value_at_risk[`var_${level}`] = var_value; // CVaR (average of values below VaR) const below_var = sorted.slice(0, index); metrics[metric].conditional_value_at_risk[`cvar_${level}`] = below_var.length > 0 ? this.mean(below_var) : var_value; }); }); // Sharpe ratio approximation (using 0 as risk-free rate) const returns = results.map(r => r.total_return); const avgReturn = this.mean(returns); const stdReturn = this.standardDeviation(returns); metrics.sharpe_ratio = stdReturn > 0 ? avgReturn / stdReturn : 0; // Maximum drawdown metrics.max_drawdown = Math.min(...returns); return metrics; } calculateDownsideDeviation(values, target = 0) { const downsideValues = values.filter(v => v < target); if (downsideValues.length === 0) return 0; const downsideSquares = downsideValues.map(v => Math.pow(v - target, 2)); return Math.sqrt(this.mean(downsideSquares)); } calculateProbabilities(results, targets) { const { minimum_irr = 10, minimum_cash_flow = 0, maximum_loss = 0 } = targets; const probabilities = { irr_above_target: (results.filter(r => r.irr >= minimum_irr).length / results.length) * 100, positive_cash_flow: (results.filter(r => r.monthly_cash_flow >= minimum_cash_flow).length / results.length) * 100, profitable_exit: (results.filter(r => r.total_profit > maximum_loss).length / results.length) * 100, double_money: (results.filter(r => r.equity_multiple >= 2).length / results.length) * 100, loss_probability: (results.filter(r => r.total_return < 0).length / results.length) * 100 }; // Joint probabilities probabilities.meet_all_targets = (results.filter(r => r.irr >= minimum_irr && r.monthly_cash_flow >= minimum_cash_flow && r.total_profit > maximum_loss ).length / results.length) * 100; return probabilities; } calculateCorrelations(results) { const correlations = {}; // Calculate correlations between inputs and outputs const inputs = ['monthly_rent', 'vacancy_rate', 'annual_expenses', 'appreciation_rate']; const outputs = ['irr', 'total_return', 'monthly_cash_flow']; outputs.forEach(output => { correlations[output] = {}; const outputValues = results.map(r => r[output]); inputs.forEach(input => { const inputValues = results.map(r => r.inputs[input]); correlations[output][input] = this.pearsonCorrelation(inputValues, outputValues); }); }); // Rank inputs by absolute correlation strength const sensitivity_ranking = []; Object.entries(correlations.irr).forEach(([input, corr]) => { sensitivity_ranking.push({ variable: input, correlation: corr, impact: Math.abs(corr) > 0.7 ? 'High' : Math.abs(corr) > 0.4 ? 'Medium' : 'Low' }); }); sensitivity_ranking.sort((a, b) => Math.abs(b.correlation) - Math.abs(a.correlation)); return { correlation_matrix: correlations, sensitivity_ranking: sensitivity_ranking }; } pearsonCorrelation(x, y) { const n = x.length; const sumX = x.reduce((a, b) => a + b, 0); const sumY = y.reduce((a, b) => a + b, 0); const sumXY = x.reduce((total, xi, i) => total + xi * y[i], 0); const sumX2 = x.reduce((total, xi) => total + xi * xi, 0); const sumY2 = y.reduce((total, yi) => total + yi * yi, 0); const numerator = n * sumXY - sumX * sumY; const denominator = Math.sqrt((n * sumX2 - sumX * sumX) * (n * sumY2 - sumY * sumY)); return denominator === 0 ? 0 : numerator / denominator; } identifyKeyScenarios(results) { const sorted_by_irr = [...results].sort((a, b) => b.irr - a.irr); const n = results.length; return { best_case: this.formatScenario(sorted_by_irr[0], 'Best Case'), worst_case: this.formatScenario(sorted_by_irr[n - 1], 'Worst Case'), median_case: this.formatScenario(sorted_by_irr[Math.floor(n / 2)], 'Median Case'), percentile_10: this.formatScenario(sorted_by_irr[Math.floor(n * 0.9)], '10th Percentile'), percentile_90: this.formatScenario(sorted_by_irr[Math.floor(n * 0.1)], '90th Percentile') }; } formatScenario(result, label) { return { label: label, inputs: result.inputs, outputs: { irr: parseFloat(result.irr.toFixed(2)), total_return: parseFloat(result.total_return.toFixed(2)), monthly_cash_flow: parseFloat(result.monthly_cash_flow.toFixed(2)), equity_multiple: parseFloat(result.equity_multiple.toFixed(2)) } }; } calculateConfidenceIntervals(results, levels) { const intervals = {}; const metrics = ['irr', 'total_return', 'monthly_cash_flow']; metrics.forEach(metric => { const values = results.map(r => r[metric]); const sorted = [...values].sort((a, b) => a - b); intervals[metric] = {}; levels.forEach(level => { const lowerIndex = Math.floor(((100 - level) / 2 / 100) * sorted.length); const upperIndex = Math.floor(((100 + level) / 2 / 100) * sorted.length) - 1; intervals[metric][`ci_${level}`] = { lower: sorted[lowerIndex], upper: sorted[upperIndex], width: sorted[upperIndex] - sorted[lowerIndex] }; }); }); return intervals; } generateRecommendations(statistics, riskMetrics, probabilities) { const recommendations = []; // IRR recommendations if (statistics.irr.mean > 15) { recommendations.push({ type: 'Performance', priority: 'High', message: `Strong expected IRR of ${statistics.irr.mean.toFixed(1)}%`, action: 'Investment shows attractive returns across scenarios' }); } else if (statistics.irr.mean < 8) { recommendations.push({ type: 'Performance', priority: 'High', message: `Low expected IRR of ${statistics.irr.mean.toFixed(1)}%`, action: 'Consider alternative investments or improve deal terms' }); } // Risk recommendations if (riskMetrics.irr.probability_of_loss > 20) { recommendations.push({ type: 'Risk', priority: 'High', message: `${riskMetrics.irr.probability_of_loss.toFixed(1)}% chance of negative returns`, action: 'High risk investment - ensure adequate risk tolerance' }); } // Cash flow recommendations if (probabilities.positive_cash_flow < 80) { recommendations.push({ type: 'Cash Flow', priority: 'Medium', message: `Only ${probabilities.positive_cash_flow.toFixed(1)}% chance of positive cash flow`, action: 'Prepare for potential negative cash flow periods' }); } // Volatility recommendations const irr_cv = statistics.irr.std_dev / Math.abs(statistics.irr.mean); if (irr_cv > 0.5) { recommendations.push({ type: 'Volatility', priority: 'Medium', message: 'High return volatility across scenarios', action: 'Consider strategies to reduce uncertainty in key variables' }); } // Downside protection const var_10 = riskMetrics.irr.value_at_risk.var_10; if (var_10 < 0) { recommendations.push({ type: 'Downside Risk', priority: 'High', message: `10% chance of IRR below ${var_10.toFixed(1)}%`, action: 'Implement downside protection strategies' }); } // Positive recommendations if (probabilities.double_money > 50) { recommendations.push({ type: 'Upside Potential', priority: 'Low', message: `${probabilities.double_money.toFixed(1)}% chance of doubling investment`, action: 'Strong upside potential in favorable scenarios' }); } return recommendations; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sigaihealth/realvestmcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server