High-performance financial forecasting with 100,000 simulations in 500ms using NumPy vectorization and multiprocessing
Drift’s simulation engine runs probabilistic financial forecasts by modeling thousands of possible futures with realistic variance in income, expenses, market returns, and life events.
interface SimulationRequest { financialProfile: { liquidAssets: number creditDebt: number loanDebt: number monthlyLoanPayments: number monthlyIncome: number monthlySpending: number spendingByCategory: Record<string, number> spendingVolatility: number } userInputs: { monthlyIncome: number age: number riskTolerance: 'low' | 'medium' | 'high' } goal: { targetAmount: number timelineMonths: number goalType: string } simulationParams?: { nSimulations?: number // Default: 100,000 }}
interface SimulationResults { successProbability: number // 0-1 probability of reaching goal medianOutcome: number // 50th percentile final balance percentiles: { p10: number // 10th percentile (pessimistic) p25: number // 25th percentile p50: number // Median p75: number // 75th percentile p90: number // 90th percentile (optimistic) } mean: number std: number worstCase: number bestCase: number simulationsRun: number workersUsed: number assumptions: Assumptions // Full transparency on all parameters}
From /home/daytona/workspace/source/simulation/monte_carlo.py:218-245:
def run_monte_carlo( request: SimulationRequest, n_workers: int = None, progress_callback: Optional[Callable[[dict], None]] = None) -> SimulationResults: """Run full Monte Carlo simulation with parallel workers.""" params = request.simulation_params or SimulationParams() n_simulations = params.n_simulations if n_workers is None: n_workers = min(cpu_count(), 4) # Cap at 4 for demo # Generate seeds for reproducibility seeds = np.arange(n_simulations) # Split work across workers batches = np.array_split(seeds, n_workers) batch_args = [(request, batch, i) for i, batch in enumerate(batches)] # Run parallel simulations with progress reporting if n_workers > 1: results_list = [] completed = 0 with Pool(n_workers) as pool: for balances, batch_id in pool.imap_unordered(run_simulation_batch, batch_args): results_list.append(balances) completed += len(balances)
With Plaid integration, Drift models per-credit-card interest and loan amortization:From /home/daytona/workspace/source/simulation/monte_carlo.py:152-190:
if params.use_account_aware_simulation: credit_interest = np.zeros(n_sims) credit_payments = np.zeros(n_sims) actual_loan_payments = np.zeros(n_sims) # Credit card interest accrual and payments if card_balances is not None: for i in range(len(params.credit_cards)): # Monthly interest on outstanding balance monthly_rate = card_aprs[i] / 12 interest = card_balances[:, i] * monthly_rate credit_interest += interest # Add interest to balance card_balances[:, i] += interest # Minimum payment reduces balance payment = np.minimum(card_min_payments[i], card_balances[:, i]) card_balances[:, i] -= payment credit_payments += payment # Loan interest and payments if loan_balances is not None: for i in range(len(params.loans)): # Monthly interest monthly_rate = loan_rates[i] / 12 interest = loan_balances[:, i] * monthly_rate # Fixed payment covers interest + principal payment = np.minimum(loan_payments[i], loan_balances[:, i] + interest) principal_payment = np.maximum(0, payment - interest) # Update loan balance loan_balances[:, i] = np.maximum(0, loan_balances[:, i] - principal_payment) actual_loan_payments += payment
Account-aware simulations provide more accurate forecasts by modeling the actual interest rates, APRs, and payment schedules from linked bank accounts.