Monte Carlo Trade Simulator via WPFToolkit Charting






2.86/5 (4 votes)
To find out the tail risk (black swan) of your trading
Introduction
This project demonstrates how to use WPFToolkit Charting to plot Monte Carlo equity & max drawdown simulation. This is for educational purposes only. DO NOT use this tool as a trading advising tool.
Background
Monte Carlo trade simulation is based on assuming that the market condition remains unchanged in the future. Hence, the same trading method will have general similar profitable and losing trades, but the order of those trades will not be the same, otherwise, history repeats exactly and this is not likely. However, even simple order difference may wreck havoc to your portfolio. Imagine that you had 50 winning trades and 50 losing trades distributed evenly, your portfolio may have survived nicely, but the same 100 trades with losing 50 of them first may bankrupt your portfolio. Monte Carlo simulation is to gauge how much risk your portfolio has by shuffling (bootstrapping actually) trade order.
For example, if 10 percentile crosses equity at 1.1 means 90% of the time, your porfolio's final equity will be more than 1.1 times of your initial capital. If 10 percentile crosses max drawdown at 20% means 90% of time, your portfolio's max drawdown will be less than 20%.
I spent about 3 hours during a weekend writing this tool for my investment seminar. I figured I could share it with the trading & programming community.
For those who are interested in making your own tools to assist your trading, this simple simulation may be a good starting point; And for those who want to learning WPFToolkit Charting, I hope this tiny little project may serve the purpose.
Using the Code
Basically, there are only MainWindow.xaml and MainWindow.xaml.cs files. If you prefer to create your own project from scratch, remember to add System.Windows.Controls.DataVisualization.Toolkit
and WPFToolkit
to References. You may need to NuGet both.
MainWindow.xaml
<!--Software Disclaimer
End User License Agreement
© 2017 Mark M.H. Chan
This SOFTWARE PRODUCT is provided by
Mark M.H. Chan "as is" and "with all faults."
By downloading and/or using it, you agree the following:
Mark M.H. Chan makes no representations or warranties
of any kind concerning the safety, suitability,
lack of viruses, inaccuracies, typographical errors,
or other harmful components of this SOFTWARE PRODUCT.
There are inherent dangers in the use of any software,
and you are solely responsible for determining whether
this SOFTWARE PRODUCT is compatible with your equipment
and other software installed on your equipment.You are
also solely responsible for the protection of your equipment
and backup of your data, and Mark M.H. Chan will not be
liable for any damages and/or losses you may suffer
in connection with using, modifying, or distributing this
SOFTWARE PRODUCT. This SOFTWARE PRODUCT is for educational purpose only.
It is NOT meant to be for trading advice.-->
<Window x:Class="MonteCarloSim.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:chartingToolkit="
clr-namespace:System.Windows.Controls.DataVisualization.Charting;
assembly=System.Windows.Controls.DataVisualization.Toolkit"
Title="Monte Carlo Trade Simulator"
Height="811.539" Width="855">
<DockPanel>
<!--app menu-->
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem x:Name="Open"
Header="_Open" Click="Open_Click"/>
<MenuItem x:Name="Save"
Header="_Save" Click="Save_Click"/>
<MenuItem x:Name="Exit"
Header="_Exit" Click="Exit_Click"/>
</MenuItem>
<MenuItem Header="_Help">
<MenuItem x:Name="Help"
Header="_Help" Click="Help_Click" />
<MenuItem x:Name="About"
Header="_About" Click="About_Click" />
</MenuItem>
</Menu>
<Grid Margin="0,0,0,0" DockPanel.Dock="Bottom">
<TextBox x:Name="tb"
Margin="0,0,119,0" TextWrapping="Wrap"
VerticalScrollBarVisibility="Auto"
AcceptsReturn="True"
TextChanged="tb_TextChanged" Height="130"
VerticalAlignment="Top" />
<Button x:Name="button"
Content="Go" HorizontalAlignment="Right"
Margin="0,55,22,0" Width="75"
Click="button_Click" IsEnabled="False"
Height="20" VerticalAlignment="Top"/>
<!--Equity Chart-->
<chartingToolkit:Chart Name="equity"
Title="Equity" Margin="0,131,0,317">
<chartingToolkit:Chart.LegendStyle>
<Style TargetType="Control">
<Setter Property="Width"
Value="0" /> <!--make chart legend invisible-->
<Setter Property="Height"
Value="0" />
</Style>
</chartingToolkit:Chart.LegendStyle>
<!--Value & Key here is KeyValuePair from code behind-->
<chartingToolkit:LineSeries DependentValuePath="Value"
IndependentValuePath="Key"
ItemsSource="{Binding}" DataContext="{Binding}"
IsSelectionEnabled="False">
<chartingToolkit:LineSeries.DataPointStyle>
<Style TargetType="chartingToolkit:LineDataPoint">
<Setter Property="Template"
Value="{x:Null}" /> <!--don't
show datapoint maker as it slows down the app hugely-->
<Setter Property="Background"
Value="DarkGreen" />
</Style>
</chartingToolkit:LineSeries.DataPointStyle>
<chartingToolkit:LineSeries.DependentRangeAxis>
<!--Y Axis range is not preset-->
<chartingToolkit:LinearAxis Orientation="Y"
Title="Equity from init capital (times)"
ShowGridLines="True" />
</chartingToolkit:LineSeries.DependentRangeAxis>
</chartingToolkit:LineSeries>
<chartingToolkit:Chart.Axes>
<!--X Axis range is from 0 to 100 percentile-->
<chartingToolkit:LinearAxis
Orientation="X" Title="Percentile"
ShowGridLines="True"
Minimum="0" Maximum="100" />
</chartingToolkit:Chart.Axes>
</chartingToolkit:Chart>
<!--Drawdown Chart-->
<chartingToolkit:Chart Name="drawdown"
Title="Drawdown" RenderTransformOrigin="0.446,0.463"
Height="316" VerticalAlignment="Bottom">
<chartingToolkit:Chart.LegendStyle>
<Style TargetType="Control">
<Setter Property="Width"
Value="0" />
<Setter Property="Height"
Value="0" />
</Style>
</chartingToolkit:Chart.LegendStyle>
<chartingToolkit:LineSeries DependentValuePath="Value"
IndependentValuePath="Key"
ItemsSource="{Binding}" DataContext="{Binding}"
IsSelectionEnabled="False" Margin="0,0,0,0.5">
<chartingToolkit:LineSeries.DataPointStyle>
<Style TargetType="chartingToolkit:LineDataPoint">
<Setter Property="Template"
Value="{x:Null}" />
<Setter Property="Background"
Value="Red" />
</Style>
</chartingToolkit:LineSeries.DataPointStyle>
<chartingToolkit:LineSeries.DependentRangeAxis>
<chartingToolkit:LinearAxis Orientation="Y"
Title="Max Drawdown %"
ShowGridLines="True" />
</chartingToolkit:LineSeries.DependentRangeAxis>
</chartingToolkit:LineSeries>
<chartingToolkit:Chart.Axes>
<chartingToolkit:LinearAxis
Orientation="X" Title="Percentile"
ShowGridLines="True" Minimum="0"
Maximum="100" />
</chartingToolkit:Chart.Axes>
</chartingToolkit:Chart>
</Grid>
</DockPanel>
</Window>
Part of MainWindow.xaml
Assign a List
of KeyValuePair
from the code behind CS to DataContext
of the chart (equity.DataContext
& drawdown.DataContext
), where Key
is X
axis, Value
is Y
axis. You may set IsSelectionEnabled = "True"
if you have lesser number of datapoints. I have 5000 datapoints here.
<!--Value & Key here is KeyValuePair from code behind-->
<chartingToolkit:LineSeries DependentValuePath="Value"
IndependentValuePath="Key"
ItemsSource="{Binding}" DataContext="{Binding}"
IsSelectionEnabled="False">
Part of MainWindow.xaml
You may comment out this part to show series legend on the right part of the chart.
<chartingToolkit:Chart.LegendStyle>
<Style TargetType="Control">
<Setter Property="Width"
Value="0" /> <!--make series legend invisible-->
<Setter Property="Height"
Value="0" />
</Style>
</chartingToolkit:Chart.LegendStyle>
Part of MainWindow.xaml
You may comment out this part to show series datapoint maker, but this will slow down the app.
<Setter Property="Template" Value="{x:Null}" />
<!--don't show datapoint maker as it slows down the app hugely-->
MainWindow.xaml.cs
// Software Disclaimer
// End User License Agreement
//
// © 2017 Mark M.H. Chan
//
// This SOFTWARE PRODUCT is provided by Mark M.H. Chan
// "as is" and "with all faults."
// By downloading and/or using it, you agree the following:
//
// Mark M.H. Chan makes no representations or warranties
// of any kind concerning the safety, suitability,
// lack of viruses, inaccuracies, typographical errors,
// or other harmful components of this SOFTWARE PRODUCT.
// There are inherent dangers in the use of any software,
// and you are solely responsible for determining whether
// this SOFTWARE PRODUCT is compatible with your equipment
// and other software installed on your equipment.You are
// also solely responsible for the protection of your equipment
// and backup of your data, and Mark M.H. Chan will not be
// liable for any damages and/or losses you may suffer
// in connection with using, modifying, or distributing this
// SOFTWARE PRODUCT. This SOFTWARE PRODUCT is for educational purpose only.
// It is NOT meant to be for trading advice.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.IO;
namespace MonteCarloSim
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button_Click(object sender, RoutedEventArgs e)
{
if (tb.Text != "")
{
List<List<KeyValuePair<double, double>>> resultList = go(tb.Text);
equity.DataContext = resultList[0];
drawdown.DataContext = resultList[1];
}
}
private List<List<KeyValuePair<double, double>>> go(string txt)
{
List<double> pl = new List<double>();
string[] tmp = txt.Split(new[]
{ Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
foreach(var t in tmp)
{
double d;
if (Double.TryParse(t, out d)) // make sure each entry is a number
{
pl.Add(d);
}
}
return MakeLineSeries(pl);
}
// make Line series for equity and max drawdown
private List<List<KeyValuePair<double,
double>>> MakeLineSeries(List<double> pl)
{
int simNum = 5000; // number of simulations
List <double> eqFinal = new List<double>();
List <double> mdd = new List<double>();
List<KeyValuePair<double, double>>
eqKVlist = new List<KeyValuePair<double, double>>();
List<KeyValuePair<double, double>>
mddKVlist = new List<KeyValuePair<double, double>>();
Random rnd = new Random(DateTime.Now.Millisecond);
for (int i = 0; i < simNum; i++)
{
List<double>
shuffled = shuffle(pl, rnd); // shuffle (bootstrap) the P&L list
// as per Monte Carlo method
List<double> eqlist = new List<double>();
double equity = 1; // equity starts at 1 or 100%
foreach (var s in shuffled) // calculate equity line
// based on shuffled P&L list
{
eqlist.Add((s / 100 + 1) * equity);
equity = eqlist[eqlist.Count - 1];
}
eqFinal.Add(equity); // record the final equity of each sim iteration
mdd.Add(Maxdrawdown(eqlist)); // get the maximum drawdown % from this sim iteration
}
var eqFinalSorted = eqFinal.OrderBy(d => d).ToList(); // sort the final
// equity of each sim iteration in ASC
var mddSorted = mdd.OrderBy(d => d).ToList(); // sort the maximum
// drawdown % of each sim iteration in ASC
for (int i = 0; i < simNum; i++)
{
// KeyValuePair<double, double> : percentile, sorted value
eqKVlist.Add(new KeyValuePair<double,
double>((i + 1) / (double)simNum * 100, eqFinalSorted[i]) );
mddKVlist.Add(new KeyValuePair<double,
double>((i + 1) / (double)simNum * 100, mddSorted[i]) );
}
return new List<List<KeyValuePair<double,
double>>> { eqKVlist, mddKVlist }; // return for chart display
}
private double Maxdrawdown(List<double> eqlist)
{
int count = eqlist.Count;
double mdd_ratio = 0;
double peak = 0;
for (int i = 0; i < count; i++)
{
if (eqlist[i] >= peak) // if current equity peak > last equity peak
{
peak = eqlist[i]; // record peak equity
}
if ((eqlist[i] - peak) /
eqlist[i] < mdd_ratio) // if last max drawdown < current max drawdown
{
mdd_ratio = (eqlist[i] - peak) / peak; // record max drawdown
}
}
return mdd_ratio * 100; // return max drawdown %
}
private List<double> shuffle(List<double> pl, Random rnd)
{
int count = pl.Count;
List<double> shuffled = new List<double>();
for (int i = 0; i < count; i++) // shuffle (bootstrap) P&L from the P&L list
{
shuffled.Add(pl[rnd.Next(count)]);
}
return shuffled;
}
private void tb_TextChanged(object sender, TextChangedEventArgs e)
{
if (tb.Text != "")
button.IsEnabled = true; // if TextBox isn't empty, enable Go button
else
button.IsEnabled = false; // if TextBox is empty, disable Go button
}
private void Save_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog();
dlg.DefaultExt = ".txt";
dlg.Filter = "Text documents (.txt)|*.txt";
if (dlg.ShowDialog() == true)
{
string filename = dlg.FileName;
StreamWriter writer = new StreamWriter(filename);
writer.Write(tb.Text); //write TextBox Text to a file
writer.Dispose();
writer.Close();
}
}
private void Open_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
dlg.DefaultExt = ".txt";
dlg.Filter = "Text documents (.txt)|*.txt";
Nullable<bool> result = dlg.ShowDialog();
if (result == true)
{
string filename = dlg.FileName;
tb.Text = File.ReadAllText(filename); // read text file into TextBox
}
}
private void Exit_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
private void About_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show
("Monte Carlo Trade Simulator 1.0\n\n© 2017 Mark M.H. Chan");
}
private void Help_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("1. Enter profit/loss % of trades,
and each trade per line in the Text Box. \n\n2. Click GO button");
}
}
}
Part of MainWindow.xaml.cs
You may change number of simulations by changing int simNum
, e.g., 10000
.
private List<List<KeyValuePair<double,
double>>> makeLineSeries(List<double> pl)
{
int simNum = 5000;
Running the Program
- Enter profit/loss % of trades, and each trade per line in the Text Box:
Example:
1.02 1.13 -2.4 3.5 -1.3 5
- Click GO button
Points of Interest
To find out more about Monte Carlo Simulation:
Software Disclaimer
End User License Agreement
© 2017 Mark M.H. Chan
This SOFTWARE PRODUCT is provided by Mark M.H. Chan "as is" and "with all faults." By downloading and/or using it, you agree the following:
Mark M.H. Chan makes no representations or warranties of any kind concerning the safety, suitability, lack of viruses, inaccuracies, typographical errors, or other harmful components of this SOFTWARE PRODUCT. There are inherent dangers in the use of any software, and you are solely responsible for determining whether this SOFTWARE PRODUCT is compatible with your equipment and other software installed on your equipment. You are also solely responsible for the protection of your equipment and backup of your data, and Mark M.H. Chan will not be liable for any damages and/or losses you may suffer in connection with using, modifying, or distributing this SOFTWARE PRODUCT. This SOFTWARE PRODUCT is for educational purpose only. It is NOT meant to be for trading advice.