通过 WPF Toolkit 图表进行蒙特卡洛交易模拟






2.86/5 (4投票s)
发现您交易的尾部风险(黑天鹅)
引言
此项目演示了如何使用 WPFToolkit Charting 来绘制蒙特卡洛权益和最大回撤模拟。这仅用于教育目的。请勿将此工具用作交易建议工具。
背景
蒙特卡洛交易模拟基于假设市场状况在未来保持不变。因此,相同的交易方法将具有大致相似的盈利和亏损交易,但这些交易的顺序将不同,否则,历史将完全重演,这不太可能。然而,即使简单的顺序差异也可能对您的投资组合造成破坏。想象一下,您有 50 笔盈利交易和 50 笔亏损交易均匀分布,您的投资组合可能很好地幸存下来,但是同样的 100 笔交易,先亏损 50 笔交易可能会使您的投资组合破产。蒙特卡洛模拟旨在通过洗牌(实际上是自展)交易顺序来衡量您的投资组合的风险程度。
例如,如果 10 百分位数交叉权益于 1.1,这意味着 90% 的时间,您的投资组合的最终权益将是您初始资本的 1.1 倍以上。如果 10 百分位数交叉最大回撤于 20%,这意味着 90% 的时间,您的投资组合的最大回撤将小于 20%。
我花了一个周末大约 3 个小时的时间编写了这个工具用于我的投资研讨会。我想我可以与交易和编程社区分享它。
对于那些有兴趣制作自己的工具来辅助您的交易的人来说,这个简单的模拟可能是一个很好的起点;对于那些想学习 WPFToolkit Charting 的人来说,我希望这个小项目能达到目的。
Using the Code
基本上,只有 MainWindow.xaml 和 MainWindow.xaml.cs 文件。如果您更喜欢从头开始创建自己的项目,请记住将 System.Windows.Controls.DataVisualization.Toolkit
和 WPFToolkit
添加到引用中。您可能需要使用 NuGet 安装这两个组件。
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>
MainWindow.xaml 的一部分
从代码隐藏 CS 将 KeyValuePair
的 List
分配给图表的 DataContext
(equity.DataContext
& drawdown.DataContext
),其中 Key
是 X
轴,Value
是 Y
轴。如果您有较少的数据点,可以设置 IsSelectionEnabled = "True"
。这里我有 5000 个数据点。
<!--Value & Key here is KeyValuePair from code behind-->
<chartingToolkit:LineSeries DependentValuePath="Value"
IndependentValuePath="Key"
ItemsSource="{Binding}" DataContext="{Binding}"
IsSelectionEnabled="False">
MainWindow.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>
MainWindow.xaml 的一部分
您可以注释掉这部分以显示系列数据点标记,但这会降低应用程序的速度。
<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");
}
}
}
MainWindow.xaml.cs 的一部分
您可以通过更改 int simNum
(例如 10000
)来更改模拟次数。
private List<List<KeyValuePair<double,
double>>> makeLineSeries(List<double> pl)
{
int simNum = 5000;
运行程序
- 在文本框中输入交易的盈亏百分比,每行输入一笔交易。
示例
1.02 1.13 -2.4 3.5 -1.3 5
- 点击 GO 按钮
关注点
要了解更多关于蒙特卡洛模拟的信息
软件免责声明
最终用户许可协议
© 2017 Mark M.H. Chan
此软件产品由 Mark M.H. Chan “按原样” 和 “包含所有错误” 提供。通过下载和/或使用它,您同意以下内容
Mark M.H. Chan 不对本软件产品的安全性、适用性、缺乏病毒、不准确性、印刷错误或其他有害组件做出任何形式的陈述或保证。使用任何软件都存在固有风险,您有责任自行决定本软件产品是否与您的设备和您的设备上安装的其他软件兼容。您还自行负责保护您的设备和备份您的数据,并且 Mark M.H. Chan 不对您在使用、修改或分发本软件产品时可能遭受的任何损害和/或损失承担任何责任。此软件产品仅用于教育目的。它不用于交易建议。