Skip to main content
Glama

simulate_montecarlo

Read-only

Quantify uncertainty in a single random factor by running Monte Carlo simulation from parametric distributions. Returns summary statistics, percentiles, and histogram.

Instructions

Sample N draws from a parametric distribution and return summary statistics + percentiles + histogram. Use to quantify uncertainty around a single random factor: project NPV with uncertain growth rate, estimate latency tail percentiles, size insurance reserves. Supports normal/lognormal/uniform/triangular/beta/exponential. For multi-asset portfolio risk with correlations, use analyze_risk. Each call re-samples (non-idempotent). Capped at 2000 iterations on the free tier.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
distributionYesDistribution family to sample from.
paramsYesDistribution parameters. Required keys depend on distribution: normal/lognormal={mean,stddev}, uniform={min,max}, triangular={min,mode,max}, beta={alpha,beta}, exponential={lambda}.
simulationsNoNumber of samples (default: 1000, max: 2000 free).

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
meanYes
stdDevYes
percentilesYes
histogramNoBucketed counts.
iterationsYes
executionTimeMsNo
timedOutNo

Implementation Reference

  • HTTP route handler for POST /api/v1/simulate/montecarlo. Parses request body (simulations, distribution, params), maps distribution params, calls MonteCarloService.runSingleFactorSimulation, and returns mean, stdDev, percentiles, histogram, iterations, executionTimeMs, timedOut.
    fastify.post("/api/v1/simulate/montecarlo", async (request: FastifyRequest, reply: FastifyReply) => {
      const body = request.body as {
        simulations: number;
        distribution: DistributionParams["type"];
        params: { mean?: number; stddev?: number; min?: number; max?: number; mode?: number; alpha?: number; beta?: number; lambda?: number };
        iterations?: number;
      };
    
      // Map user-friendly params to distribution params array
      const distParams: number[] = [];
      switch (body.distribution) {
        case "normal":
          distParams.push(body.params.mean ?? 0, body.params.stddev ?? 1);
          break;
        case "lognormal":
          distParams.push(body.params.mean ?? 0, body.params.stddev ?? 1);
          break;
        case "uniform":
          distParams.push(body.params.min ?? 0, body.params.max ?? 1);
          break;
        case "triangular":
          distParams.push(body.params.min ?? 0, body.params.mode ?? 0.5, body.params.max ?? 1);
          break;
        case "beta":
          distParams.push(body.params.alpha ?? 2, body.params.beta ?? 5);
          break;
        case "exponential":
          distParams.push(body.params.lambda ?? 1);
          break;
        default:
          distParams.push(body.params.mean ?? 0, body.params.stddev ?? 1);
      }
    
      const mcService = new MonteCarloService();
      const iterations = Math.min(body.simulations ?? body.iterations ?? 1000, 2000);
    
      const result = await mcService.runSingleFactorSimulation(
        { type: body.distribution, params: distParams },
        iterations,
      );
    
      return {
        mean: result.mean,
        stdDev: result.stdDev,
        percentiles: {
          p5: result.percentiles.p5,
          p25: result.percentiles.p25,
          p50: result.percentiles.p50,
          p75: result.percentiles.p75,
          p95: result.percentiles.p95,
        },
        histogram: result.distribution,
        iterations: result.iterations,
        executionTimeMs: result.executionTimeMs,
        timedOut: result.timedOut,
      };
    });
  • MonteCarloService class with runSimulation, runSingleFactorSimulation, and runScenarioAnalysis methods. Contains all distribution sampling logic (normal, lognormal, uniform, triangular, beta, exponential), random number generation (Mulberry32), histogram creation, percentile calculation, and timeout handling.
    /**
     * Monte Carlo Simulation Service
     * Story 5.1 - ORACLE Probability Engine
     */
    
    export interface SimulationConfig {
      iterations: number;
      timeoutMs: number;
      seed?: number;
    }
    
    export interface DistributionParams {
      type: 'normal' | 'lognormal' | 'uniform' | 'triangular' | 'beta' | 'exponential';
      params: number[];
    }
    
    export interface SimulationFactor {
      name: string;
      distribution: DistributionParams;
    }
    
    export interface SimulationOutput {
      mean: number;
      stdDev: number;
      percentiles: {
        p5: number;
        p10: number;
        p25: number;
        p50: number;
        p75: number;
        p90: number;
        p95: number;
      };
      distribution: Array<{ bucket: number; count: number; percentage: number }>;
      iterations: number;
      executionTimeMs: number;
      timedOut: boolean;
    }
    
    // Simple seeded random number generator (Mulberry32)
    function createRandom(seed: number): () => number {
      return function () {
        let t = (seed += 0x6d2b79f5);
        t = Math.imul(t ^ (t >>> 15), t | 1);
        t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
        return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
      };
    }
    
    // Box-Muller transform for normal distribution
    function normalSample(mean: number, stdDev: number, random: () => number): number {
      const u1 = random();
      const u2 = random();
      const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
      return mean + stdDev * z;
    }
    
    // Sample from various distributions
    function sampleDistribution(dist: DistributionParams, random: () => number): number {
      const { type, params } = dist;
    
      switch (type) {
        case 'normal': {
          // params: [mean, stdDev]
          const [mean, stdDev] = params;
          return normalSample(mean, stdDev, random);
        }
    
        case 'lognormal': {
          // params: [mu, sigma] (parameters of underlying normal)
          const [mu, sigma] = params;
          return Math.exp(normalSample(mu, sigma, random));
        }
    
        case 'uniform': {
          // params: [min, max]
          const [min, max] = params;
          return min + random() * (max - min);
        }
    
        case 'triangular': {
          // params: [min, mode, max]
          const [min, mode, max] = params;
          const u = 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));
          }
        }
    
        case 'beta': {
          // params: [alpha, beta]
          // Using Gamma distribution method
          const [alpha, beta] = params;
          const gamma1 = gammaSample(alpha, random);
          const gamma2 = gammaSample(beta, random);
          return gamma1 / (gamma1 + gamma2);
        }
    
        case 'exponential': {
          // params: [lambda (rate)]
          const [lambda] = params;
          return -Math.log(1 - random()) / lambda;
        }
    
        default:
          return random();
      }
    }
    
    // Gamma sampling using Marsaglia and Tsang's method
    function gammaSample(shape: number, random: () => number): number {
      if (shape < 1) {
        // Use transformation for shape < 1
        return gammaSample(shape + 1, random) * Math.pow(random(), 1 / shape);
      }
    
      const d = shape - 1 / 3;
      const c = 1 / Math.sqrt(9 * d);
    
      while (true) {
        let x: number, v: number;
        do {
          x = normalSample(0, 1, random);
          v = 1 + c * x;
        } while (v <= 0);
    
        v = v * v * v;
        const u = random();
    
        if (u < 1 - 0.0331 * x * x * x * x) {
          return d * v;
        }
    
        if (Math.log(u) < 0.5 * x * x + d * (1 - v + Math.log(v))) {
          return d * v;
        }
      }
    }
    
    // Calculate percentile from sorted array
    function percentile(sortedValues: number[], p: number): number {
      const index = (p / 100) * (sortedValues.length - 1);
      const lower = Math.floor(index);
      const upper = Math.ceil(index);
      const weight = index - lower;
    
      if (lower === upper) {
        return sortedValues[lower];
      }
    
      return sortedValues[lower] * (1 - weight) + sortedValues[upper] * weight;
    }
    
    // Create histogram buckets
    function createHistogram(values: number[], bucketCount: number = 20): Array<{ bucket: number; count: number; percentage: number }> {
      const min = Math.min(...values);
      const max = Math.max(...values);
      const range = max - min || 1;
      const bucketSize = range / bucketCount;
    
      const buckets = new Array(bucketCount).fill(0);
    
      for (const value of values) {
        const bucketIndex = Math.min(Math.floor((value - min) / bucketSize), bucketCount - 1);
        buckets[bucketIndex]++;
      }
    
      return buckets.map((count, i) => ({
        bucket: min + (i + 0.5) * bucketSize,
        count,
        percentage: (count / values.length) * 100,
      }));
    }
    
    export class MonteCarloService {
      private defaultConfig: SimulationConfig = {
        iterations: 1000,
        timeoutMs: 10000, // 10 second timeout
      };
    
      /**
       * Run Monte Carlo simulation with multiple factors
       */
      async runSimulation(
        factors: SimulationFactor[],
        aggregator: (samples: Record<string, number>) => number = (s) => Object.values(s).reduce((a, b) => a + b, 0),
        config: Partial<SimulationConfig> = {}
      ): Promise<SimulationOutput> {
        const startTime = Date.now();
        const iterations = Math.min(config.iterations || this.defaultConfig.iterations, 2000); // Cap at 2000
        const timeoutMs = config.timeoutMs || this.defaultConfig.timeoutMs;
        const seed = config.seed || Date.now();
    
        const random = createRandom(seed);
        const results: number[] = [];
        let timedOut = false;
    
        for (let i = 0; i < iterations; i++) {
          // Check timeout
          if (Date.now() - startTime > timeoutMs) {
            timedOut = true;
            break;
          }
    
          // Sample each factor
          const samples: Record<string, number> = {};
          for (const factor of factors) {
            samples[factor.name] = sampleDistribution(factor.distribution, random);
          }
    
          // Aggregate samples into single outcome
          results.push(aggregator(samples));
        }
    
        // Calculate statistics
        const sortedResults = [...results].sort((a, b) => a - b);
        const n = results.length;
    
        const mean = results.reduce((a, b) => a + b, 0) / n;
        const variance = results.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / n;
        const stdDev = Math.sqrt(variance);
    
        return {
          mean,
          stdDev,
          percentiles: {
            p5: percentile(sortedResults, 5),
            p10: percentile(sortedResults, 10),
            p25: percentile(sortedResults, 25),
            p50: percentile(sortedResults, 50),
            p75: percentile(sortedResults, 75),
            p90: percentile(sortedResults, 90),
            p95: percentile(sortedResults, 95),
          },
          distribution: createHistogram(sortedResults),
          iterations: n,
          executionTimeMs: Date.now() - startTime,
          timedOut,
        };
      }
    
      /**
       * Simple single-factor simulation
       */
      async runSingleFactorSimulation(
        distribution: DistributionParams,
        iterations: number = 1000
      ): Promise<SimulationOutput> {
        return this.runSimulation(
          [{ name: 'value', distribution }],
          (s) => s.value,
          { iterations }
        );
      }
    
      /**
       * Run scenario analysis with multiple named scenarios
       */
      async runScenarioAnalysis(
        scenarios: Record<string, SimulationFactor[]>,
        aggregator: (samples: Record<string, number>) => number,
        iterations: number = 500
      ): Promise<Record<string, SimulationOutput>> {
        const results: Record<string, SimulationOutput> = {};
    
        for (const [scenarioName, factors] of Object.entries(scenarios)) {
          results[scenarioName] = await this.runSimulation(factors, aggregator, { iterations });
        }
    
        return results;
      }
    }
    
    // Singleton instance
    export const monteCarloService = new MonteCarloService();
  • TypeScript interfaces for SimulationConfig, DistributionParams (6 distribution types), SimulationFactor, and SimulationOutput (includes mean, stdDev, percentiles p5-p95, histogram buckets, iterations, executionTimeMs, timedOut).
    export interface SimulationConfig {
      iterations: number;
      timeoutMs: number;
      seed?: number;
    }
    
    export interface DistributionParams {
      type: 'normal' | 'lognormal' | 'uniform' | 'triangular' | 'beta' | 'exponential';
      params: number[];
    }
    
    export interface SimulationFactor {
      name: string;
      distribution: DistributionParams;
    }
    
    export interface SimulationOutput {
      mean: number;
      stdDev: number;
      percentiles: {
        p5: number;
        p10: number;
        p25: number;
        p50: number;
        p75: number;
        p90: number;
        p95: number;
      };
      distribution: Array<{ bucket: number; count: number; percentage: number }>;
      iterations: number;
      executionTimeMs: number;
      timedOut: boolean;
    }
  • MCP tool registration listing 'simulate_montecarlo' as a free-tier tool with description 'Monte Carlo simulation'.
    { name: "simulate_montecarlo", description: "Monte Carlo simulation", tier: "free" },
  • Billing configuration listing 'simulate_montecarlo' in the FREE_TOOLS array.
    export const FREE_TOOLS = [
      'optimize_bandit', 'optimize_contextual', 'solve_schedule',
      'score_convergence', 'plan_pathfind', 'simulate_montecarlo',
    ];
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already provide readOnlyHint, destructiveHint, idempotentHint, openWorldHint. Description adds specifics: each call re-samples (non-idempotent), capped at 2000 iterations free. No contradiction.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Single concise paragraph, front-loaded with core functionality, then usage, alternative, behavioral notes. Every sentence adds value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers all essentials: 6 distributions, parameter requirements, non-idempotence, free tier cap. Output schema exists for return values, so no need to detail them.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% with detailed descriptions. Description adds meaning by noting required keys depend on distribution and specifying defaults (1000) and limits (2000 free).

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Description starts with a specific verb+resource ('Sample N draws from a parametric distribution') and clearly lists returned outputs. It provides concrete use cases and distinguishes from sibling tool analyze_risk.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states when to use (quantify uncertainty around a single random factor) and when not to (multi-asset portfolio, use analyze_risk). Mentions behavioral details like non-idempotent and free tier cap.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/Whatsonyourmind/oraclaw'

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