Building a Monte Carlo Simulator with React
Dive into the probabilistic world of Monte Carlo simulations and learn how to implement them in a React application.
Introduction
Monte Carlo simulations are a class of computational algorithms that rely on repeated random sampling to estimate a numerical result. While the name might suggest a connection to gambling, Monte Carlo methods find their application in a variety of fields like physics, finance, and project management.
In this blog post, we will be building a Monte Carlo simulator using React, a popular JavaScript library for building user interfaces. The simulator will be based on a simple scenario - predicting future values of an investment given variable rates of return and inflation. Why? Well, we built one in our last blog post!
Setting Up the React Application
Firstly, you’ll need to set up a new React application. For this, we’ll use Next.js, a widely used tool for creating new React applications with a good default configuration. If you don’t already have Node.js and npm installed, you will need to do that first. Once you have Node.js and npm, you can install Next.js and create a new project by running the following commands:
npx create-next-app@latest monte-carlo-simulator
This command creates a new directory called monte-carlo-simulator
, sets up a new React application in it, and installs the necessary dependencies.
Once the installation process is complete, navigate to the project directory:
cd monte-carlo-simulator
You can start the development server and open the application in your browser by running:
npm run dev
Now that you have your React app set up, you’re ready to start building the Monte Carlo simulator. You can press “Control + C” to stop the running application.
Next, we’ll be installing a couple of additional dependencies. First is victory
, a set of modular charting components for React and React Native. Victory makes it easy to get started with a set of simple, flexible, and composable charts. We’ll also install tailwindcss
, a utility-first CSS framework for rapidly building custom user interfaces.
You can add these to your project by running:
npm install -D tailwindcss postcss autoprefixer
npm install victory
Now, we’ll want ot set up tailwind:
npx tailwindcss init -p
And we can edit the newly created tailwind.config.js
file to have the following:
module.exports = {
content: [
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
// Or if using `src` directory:
"./src/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {},
},
plugins: [],
}
We should also import our tailwind directives into our globals.css
:
@tailwind base;
@tailwind components;
@tailwind utilities;
We’re now ready to start building our Monte Carlo simulator!
Creating the Simulator Component
With our new React application set up, it’s time to start creating the Monte Carlo simulator component. Firstly, create a new file MonteCarlo.jsx
in the src
directory. This is where we’ll put all the logic for our simulation.
The simulator will use a number of state variables to control and track the simulation:
numSimulations
: the number of simulations to runnumYears
: the number of years for each simulationinitialInvestment
: the starting value for the simulated investmentgoal
: the target value we hope the investment will exceednumSuccesses
: the number of simulations where the investment exceeded the goalsimulationRuns
: the history of investment values for each simulation
Here’s how to set up these state variables at the beginning of our MonteCarlo
component:
import { useState } from 'react';
const MonteCarlo = () => {
const [numSimulations, setNumSimulations] = useState(1000);
const [numYears, setNumYears] = useState(20);
const [initialInvestment, setInitialInvestment] = useState(10000);
const [goal, setGoal] = useState(20000);
const [numSuccesses, setNumSuccesses] = useState(0);
const [simulationRuns, setSimulationRuns] = useState([]);
// runSimulation function will go here
return (
// JSX will be added here
);
};
export default MonteCarlo;
In the next sections, we’ll add the logic to run the simulations and render the results.
Building the Simulation Logic
Now that we’ve established our state variables and basic structure of our Monte Carlo component, let’s implement the logic for running the simulations. We’ll define a runSimulation
function that performs the Monte Carlo simulation.
Here’s a step-by-step walkthrough of the runSimulation
function:
// This goes in place of the comment "runSimulation function will go here"
const runSimulation = () => {
let successes = 0;
let newSimulationRuns = [];
for (let i = 0; i < numSimulations; i++) {
let value = initialInvestment;
let yearlyValues = [initialInvestment];
for (let j = 0; j < numYears; j++) {
// Random rate of return between -5% and +15%
const rateOfReturn = -0.05 + Math.random() * 0.2;
// Random inflation rate between 0% and 4%
const inflationRate = Math.random() * 0.04;
value = value * (1 + rateOfReturn) / (1 + inflationRate);
yearlyValues.push(value);
}
if (value > goal) {
successes += 1;
}
newSimulationRuns.push({
initialInvestment,
goal,
numYears,
finalValue: value,
yearlyValues
});
}
setNumSuccesses(successes);
setSimulationRuns(newSimulationRuns);
};
Each simulation starts with the initial investment and then for each year, we simulate a random rate of return and a random inflation rate. The new value of the investment is calculated accordingly, and then added to the yearlyValues
array. This is repeated for the desired number of years. At the end of the simulation, if the final value of the investment exceeds the goal, we increment the successes
counter. Finally, we store the details of this simulation run in the newSimulationRuns
array.
Once all simulations are complete, we update the numSuccesses
and simulationRuns
state variables with the final results.
In the next section, we’ll build the user interface to input the simulation parameters and display the results.
Building the User Interface
With the simulation logic in place, it’s time to build the user interface that will allow users to interact with our Monte Carlo Simulator. We’re going to create a form for the user to input the initial investment, the goal, the number of years and the number of simulations to run. We’ll also provide a button to start the simulation. Lastly, we’ll display the result of the simulation.
Here’s the basic layout of our interface:
// This goes in the "return" area in our component above, where we say "JSX will be added here"
<div className="flex flex-col items-center justify-center h-screen">
<h2 className="text-2xl font-bold mb-4">Monte Carlo Simulation - Investment Portfolio</h2>
<form className="mb-4">
<label className="block mb-2">
Initial Investment:
<input type="number" value={initialInvestment} onChange={e => setInitialInvestment(parseFloat(e.target.value))} />
</label>
<label className="block mb-2">
Goal:
<input type="number" value={goal} onChange={e => setGoal(parseFloat(e.target.value))} />
</label>
<label className="block mb-2">
Number of Years:
<input type="number" value={numYears} onChange={e => setNumYears(parseInt(e.target.value, 10))} />
</label>
<label className="block mb-2">
Number of Simulations:
<input type="number" value={numSimulations} onChange={e => setNumSimulations(parseInt(e.target.value))} />
</label>
<button type="button" onClick={runSimulation}>Run Simulation</button>
</form>
{numSuccesses > 0 && (
<p>Successful simulations: {numSuccesses}</p>
)}
{/* Victory code will go here */}
</div>
This form has four input fields, each associated with a state variable. When the value of an input field changes, we update the corresponding state variable. The button has an onClick
event handler which triggers the runSimulation
function when clicked. We also display the number of successful simulations after the simulation is run.
In the next section, we’ll add charting to visualize the results of our simulations.
Charting the Results with Victory
To visualize the results of our Monte Carlo simulations, we’re going to use a powerful data visualization library called Victory. Victory provides a set of modular charting components for React and React Native. It’s flexible, easy to use, and it allows us to create rich, interactive data visualizations.
We’re going to use VictoryChart
, VictoryLine
, and VictoryTheme
from Victory. VictoryChart
is a wrapper component that renders a chart area with some default chart elements, VictoryLine
is a component for drawing line charts, and VictoryTheme
contains a set of predefined themes that we can apply to our charts.
Below is an example of how you can create a chart with Victory:
// This will go at the top of your component
import { VictoryChart, VictoryLine, VictoryTheme } from 'victory';
// Inside your render function, where we commented "Victory code will go here"
<VictoryChart theme={VictoryTheme.material}>
{simulationRuns.map((run, index) => (
<VictoryLine data={run.yearlyValues.map((value, year) => ({ x: year, y: value }))} />
))}
</VictoryChart>
This code will render a line chart with a line for each simulation run. The x
coordinate of a point represents the year, and the y
coordinate represents the value of the investment at that year.
In the next section, we’ll discuss how to add more advanced features to our simulator.
Adding a Dash of Color
To improve the visual impact of our chart, let’s color-code the results. We want the color of each line to reflect the success of that simulation run: green if the final value is above the goal, and red otherwise.
Victory makes it easy to customize the appearance of chart elements. The style
prop of the VictoryLine
component is an object that can contain various styling properties. For instance, data
is the styling property for the line itself.
Here’s how you can set the color of each line based on the finalValue
property of the simulation run:
<VictoryChart theme={VictoryTheme.material}>
{simulationRuns.map((run, index) => (
<VictoryLine
style={{ data: { stroke: run.finalValue >= run.goal ? 'green' : 'red' } }}
data={run.yearlyValues.map((value, year) => ({ x: year, y: value }))}
/>
))}
</VictoryChart>
In the style
prop, we’re using a JavaScript ternary operator to conditionally set the color of the line. If finalValue
is greater than or equal to goal
, we use ‘green’; otherwise, we use ‘red’.
Styling is an essential part of data visualization, as it helps users interpret the data more effectively. Using colors wisely can greatly enhance the readability and appeal of your charts.
Making the Chart Interactive with Tooltips
Visualizations are most effective when they enable users to explore data in detail. A great way to achieve this is by adding tooltips that display specific data points when the user hovers over a line in the chart.
Victory provides a built-in VictoryVoronoiContainer
component that makes it simple to add interactive tooltips to your chart. We can set the containerComponent
prop of the VictoryChart
to a VictoryVoronoiContainer
:
<VictoryChart
theme={VictoryTheme.material}
containerComponent={<VictoryVoronoiContainer />}
>
The VictoryVoronoiContainer
adds a voronoi overlay to the chart which is used to determine which data point the user’s cursor is closest to.
Next, we need to add a VictoryTooltip
to our VictoryLine
. The VictoryTooltip
component is rendered whenever the user hovers over a data point in a line:
<VictoryLine
style={{ data: { stroke: run.finalValue >= run.goal ? 'green' : 'red' } }}
data={run.yearlyValues.map((value, year) => ({ x: year, y: value }))}
labels={({ datum }) => `Year: ${datum.x}, Value: ${datum.y.toFixed(2)}`}
labelComponent={<VictoryTooltip />}
/>
In the labels
prop, we’re defining what text the tooltip should show. We’re accessing the x
and y
properties of the hovered data point (referred to as datum
) to display the year and the value of the investment. The labelComponent
prop tells Victory to use VictoryTooltip
to render the labels.
And that’s it! With tooltips, your users can now hover over any line in the chart to see the exact investment value for each year.
Conclusion
Congratulations! You’ve successfully built a Monte Carlo simulator using React. You can see the final result below:
Monte Carlo Simulation - Investment Portfolio
The investment goal was achieved in 0.00% of the simulations.
The final code for this component looks like this:
import { useState } from 'react';
import { VictoryChart, VictoryLine, VictoryTheme, VictoryTooltip, VictoryVoronoiContainer } from 'victory';
import { useState } from 'react';
const MonteCarlo = () => {
const [numSimulations, setNumSimulations] = useState(1000);
const [numYears, setNumYears] = useState(20);
const [initialInvestment, setInitialInvestment] = useState(10000);
const [goal, setGoal] = useState(20000);
const [numSuccesses, setNumSuccesses] = useState(0);
const [simulationRuns, setSimulationRuns] = useState([]);
// runSimulation function will go here
const runSimulation = () => {
let successes = 0;
let newSimulationRuns = [];
for (let i = 0; i < numSimulations; i++) {
let value = initialInvestment;
let yearlyValues = [initialInvestment];
for (let j = 0; j < numYears; j++) {
// Random rate of return between -5% and +15%
const rateOfReturn = -0.05 + Math.random() * 0.2;
// Random inflation rate between 0% and 4%
const inflationRate = Math.random() * 0.04;
value = value * (1 + rateOfReturn) / (1 + inflationRate);
yearlyValues.push(value);
}
if (value > goal) {
successes += 1;
}
newSimulationRuns.push({
initialInvestment,
goal,
numYears,
finalValue: value,
yearlyValues
});
}
setNumSuccesses(successes);
setSimulationRuns(newSimulationRuns);
};
// JSX will be added here
return (
<div className="flex flex-col items-center justify-center h-screen">
<h2 className="text-2xl font-bold mb-4">Monte Carlo Simulation - Investment Portfolio</h2>
<form className="mb-4">
<label className="block mb-2">
Initial Investment:
<input type="number" value={initialInvestment} onChange={e => setInitialInvestment(parseFloat(e.target.value))} />
</label>
<label className="block mb-2">
Goal:
<input type="number" value={goal} onChange={e => setGoal(parseFloat(e.target.value))} />
</label>
<label className="block mb-2">
Number of Years:
<input type="number" value={numYears} onChange={e => setNumYears(parseInt(e.target.value, 10))} />
</label>
<label className="block mb-2">
Number of Simulations:
<input type="number" value={numSimulations} onChange={e => setNumSimulations(parseInt(e.target.value))} />
</label>
<button type="button" onClick={runSimulation}>Run Simulation</button>
</form>
{numSuccesses > 0 && (
<p>Successful simulations: {numSuccesses}</p>
)}
{/* Victory code will go here */}
<VictoryChart
theme={VictoryTheme.material}
containerComponent={<VictoryVoronoiContainer />}
>
<VictoryLine
style={{ data: { stroke: run.finalValue >= run.goal ? 'green' : 'red' } }}
data={run.yearlyValues.map((value, year) => ({ x: year, y: value }))}
labels={({ datum }) => `$${datum.y.toFixed(2)}`}
labelComponent={<VictoryTooltip />}
/>
</VictoryChart>
</div>
);
};
export default MonteCarlo;
By the end of this tutorial, you should have a working Monte Carlo simulation running in your browser. This tutorial just scratches the surface of what Monte Carlo simulations can do. They’re a powerful tool in any data scientist’s arsenal, and being able to build one with React opens up a world of possibilities for interactive, web-based data analysis.
Remember, the most important part of learning is experimenting, so play around with the code and try adding new features or tweaking the existing ones to better suit your needs.