Click here to Skip to main content
15,886,362 members
Articles / Desktop Programming / WPF

Monte Carlo Trade Simulator via WPFToolkit Charting

Rate me:
Please Sign up or sign in to vote.
2.86/5 (4 votes)
12 Mar 2017CPOL3 min read 5K   131   4  
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

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.

XAML
<!--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.

XAML
<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.

XAML
<Setter Property="Template" Value="{x:Null}" /> 
<!--don't show datapoint maker as it slows down the app hugely-->

MainWindow.xaml.cs

C#
// 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.

C#
private List<List<KeyValuePair<double,
double>>> makeLineSeries(List<double> pl)
{
    int simNum = 5000;

Running the Program

  1. 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
  2. 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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Team Leader
Hong Kong Hong Kong
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --