- ago
The dailyReturnsBench variable (below) returns NULL in BacktestComplete.
CODE:
   public override void BacktestComplete()    {       TimeSeries dailyReturns = Backtester.DailyReturns;       PlotTimeSeries(dailyReturns, "Daily Returns", "Daily Returns", WLColor.Green);       TimeSeries dailyReturnsBench = Backtester.BenchmarkBacktestResults.DailyReturns;       PlotTimeSeries(dailyReturns, "BenchMark Daily Returns", "Daily Returns", WLColor.DarkGreen);    }
How am I suppose to create a ScottPlot comparing dailyReturns and dailyReturnsBench in BacktestComplete? And "no," I'm not interested in creating a Performance Visualizer just to run a ScottPlot. However, if someone wants to supply some code to do that, I'll graciously accept that. :)

I think if BenchmarkBacktestResults returned something useful in BacktestComplete, that would be the best solution.
0
233
Solved
13 Replies

Reply

Bookmark

Sort
- ago
#1
Both calls to PlotTimeSerires use dailyReturns. Did you mean to use dailyReturnsBench in the second call to PlotTimeSerires?
0
- ago
#2
QUOTE:
Did you mean to use dailyReturnsBench in the second call ...

Yes, that's what I want. For running the correlations between strategy daily returns and benchmark (SPY) daily returns, I may run these time series through a SUM(12) indicator so I get an average over the entire position on the ScottPlot.

But I think what you're really asking is what is this all about? ... which is probably how I should have presented it in the first place (perhaps in a new topic). I want to use ScottPlot to correlate strategy gains with benchmark gains. Notice how the strategy below only buys (say 2 or 3 positions) when the benchmark has a positive slope. I want to quantitatively represent that correlation with ScottPlot. Ultimately, I want to do this correlation analysis with several IndexLab composites to see which composite correlates the best, but that's also another topic.



I should add that this is a MoneyFlow divergence strategy. See the code for how it's calculated.
CODE:
   moneyFlowOsc = MoneyFlowOscillator.Series(bars, 15);    PlotIndicatorLine(moneyFlowOsc,WLColor.Blue);    moneyFlowOscFast = MoneyFlowOscillator.Series(bars, 5);    PlotIndicatorLine(moneyFlowOscFast,WLColor.Orchid);    moneyFlowDiff = moneyFlowOscFast - moneyFlowOsc;    PlotTimeSeriesHistogramTwoColor(moneyFlowDiff, "MoneyFlow diverge difference", "MoneyFlow diverge difference");
This kind of MoneyFlow strategy is very sensitive to overall market performance for large cap stocks. If I run the same 34 large cap stocks on another strategy, I don't get this highly correlated behavior so much.
0
- ago
#3
I got the gist of what you were trying to achieve. In any case, I tried every which way to get BenchmarkBacktestResults, and it was always null. Also, in BacktestComplete I would get an exception if I referenced Backtester instead of StrategyHost.Backtester (see code below).

In any case, maybe its just not supported within BacktestComplete. I guess the WL crew could chime in on this one.

CODE:
using System; using WealthLab.Backtest; using WealthLab.Core; using WealthLab.Indicators; namespace WealthScript1 {    public class MyStrategy : UserStrategyBase    {       public override void Initialize(BarHistory bars)       {          rSI = RSI.Series(bars.Close, 4);       }       public override void Execute(BarHistory bars, int idx)       {          var position = FindOpenPosition(-1);          if (position == null)          {             if (rSI.CrossesOver(30, idx))             {                PlaceTrade(bars, TransactionType.Buy, OrderType.Market, 0, "Buy");             }          }          else          {             if (idx == position.EntryBar + 10)             {                ClosePosition(position, OrderType.Market, 0, "Sell at 10 bars");             }          }       }       public override void BacktestComplete()       {          // referencing JUST Backtester would cause an exception, so I had to first          // go to the StrategyHost and then get the Backtester.          var bt = StrategyHost.Backtester;                   TimeSeries dailyReturns = bt.DailyReturns;          if (dailyReturns != null)          {             PlotTimeSeries(dailyReturns, "Daily Returns", "Daily Returns", WLColor.Green);          }          var benchBt = StrategyHost.BenchmarkBacktester;          if (benchBt == null)          {             WriteToDebugLog("StrategyHost.BenchmarkBacktester is null");             return;          }          var bbtTester = benchBt.BenchmarkBacktestResults;          if (bbtTester == null)          {             WriteToDebugLog("benchBt.BenchmarkBacktestResults is null");             return;          }                    PlotTimeSeries(bbtTester.BenchmarkBacktestResults.DailyReturns, "BenchMark Daily Returns", "Daily Returns", WLColor.DarkGreen);                }       private RSI rSI;    } }
0
Glitch8
 ( 10.92% )
- ago
#4
It's only created after the backtest process, so is inaccessible within the Strategy code while the Strategy runs. It's intended to be used when creating Performance Visualizers.
0
- ago
#5
QUOTE:
in BacktestComplete I would get an exception if I referenced Backtester instead of StrategyHost.Backtester

Interesting. I call my own Backtester extension method in Cleanup{} without a problem. But maybe BacktestComplete is different. If so, it should be documented better.
0
- ago
#6
QUOTE:
It's intended to be used when creating Performance Visualizers.

So what we need is an example of how to do a ScottPlot with a Performance Visualizer. That would solve the problem.
0
Glitch8
 ( 10.92% )
- ago
#7
I understand you'd like an example of using ScottPlot in a Performance Visualizer. Unfortunately, we don't have the bandwidth to come up with a concrete example. We have documented the API for creating a Performance Visualizer, so you can find that here:

https://www.wealth-lab.com/Support/ExtensionApi/PerformanceVisualizer

Using ScottPlot in a Performance Visualizer would just be a matter of adding the ScottPlot to the Visualizer's XAML and populating it as desired in code.
0
- ago
#8
Manipulating the XAML is outside my experience. Perhaps someone else can post an example.
0
- ago
#9
@superticker - here you go. The following is a complete solution for a WL 8 performance visualizer that shows a ScottPlot in a new tab in the backtest results. It shows the strategy dataset's daily returns and the benchmark symbol's daily returns in the chart along with a data box that shows the plotted values. It's nothing fancy but may be a start for what you want. This was a fun project, and I found that it is very easy to create visualizers.

To use, create the solution, compile it and then copy the resulting DLL to your WL 8 folder and restart WL 8.

Here's a screenshot...


The solution folder structure is that typical of a Visual Studio solution. A base folder named BenchVisualizer contains the .sln file. There is a sub-folder in BenchVisualizer also named BenchVisualizer. Inside that sub-folder is the .csproj, .cs and .xaml file.

Hence:
CODE:
BenchVisualizer - BenchVisualizer - BenchmarkCompare.xaml - BenchmarkCompare.xaml.cs - BenchVisualizer.csproj

You may have-to/want to adjust build configuration.

Solution file BenchVisualizer.sln below...
CODE:
Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.8.34316.72 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchVisualizer", "BenchVisualizer\BenchVisualizer.csproj", "{B9B307F4-760C-476C-A9CF-6A64D111CE98}" EndProject Global    GlobalSection(SolutionConfigurationPlatforms) = preSolution       Debug|Any CPU = Debug|Any CPU       Debug|x64 = Debug|x64       Release|Any CPU = Release|Any CPU       Release|x64 = Release|x64    EndGlobalSection    GlobalSection(ProjectConfigurationPlatforms) = postSolution       {B9B307F4-760C-476C-A9CF-6A64D111CE98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU       {B9B307F4-760C-476C-A9CF-6A64D111CE98}.Debug|Any CPU.Build.0 = Debug|Any CPU       {B9B307F4-760C-476C-A9CF-6A64D111CE98}.Debug|x64.ActiveCfg = Debug|x64       {B9B307F4-760C-476C-A9CF-6A64D111CE98}.Debug|x64.Build.0 = Debug|x64       {B9B307F4-760C-476C-A9CF-6A64D111CE98}.Release|Any CPU.ActiveCfg = Release|Any CPU       {B9B307F4-760C-476C-A9CF-6A64D111CE98}.Release|Any CPU.Build.0 = Release|Any CPU       {B9B307F4-760C-476C-A9CF-6A64D111CE98}.Release|x64.ActiveCfg = Release|x64       {B9B307F4-760C-476C-A9CF-6A64D111CE98}.Release|x64.Build.0 = Release|x64    EndGlobalSection    GlobalSection(SolutionProperties) = preSolution       HideSolutionNode = FALSE    EndGlobalSection    GlobalSection(ExtensibilityGlobals) = postSolution       SolutionGuid = {341DF225-8FF9-4798-81FC-861B1E2E75D8}    EndGlobalSection EndGlobal


BenchmarkCompare.xaml below...
CODE:
<wpf:VisualizerBase x:Class="BenchVisualizer.BenchmarkCompare" xmlns="<a href="http://schemas.microsoft.com/winfx/2006/xaml/presentation" target="_blank">http://schemas.microsoft.com/winfx/2006/xaml/presentation</a>" xmlns:x="<a href="http://schemas.microsoft.com/winfx/2006/xaml" target="_blank">http://schemas.microsoft.com/winfx/2006/xaml</a>" xmlns:mc="<a href="http://schemas.openxmlformats.org/markup-compatibility/2006" target="_blank">http://schemas.openxmlformats.org/markup-compatibility/2006</a>" xmlns:d="<a href="http://schemas.microsoft.com/expression/blend/2008" target="_blank">http://schemas.microsoft.com/expression/blend/2008</a>" xmlns:local="clr-namespace:BenchVisualizer" xmlns:wpf="clr-namespace:WealthLab.WPF;assembly=WealthLab.WPF" xmlns:scott="clr-namespace:ScottPlot;assembly=ScottPlot" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <Grid Background="White"> <Grid.ColumnDefinitions> <ColumnDefinition Width="82*"></ColumnDefinition> <ColumnDefinition Width="18*"></ColumnDefinition> </Grid.ColumnDefinitions> <WpfPlot Name="MyPlot" Grid.Column="0"> </WpfPlot> <Grid x:Name="ChartValues" Grid.Column="1"> <Grid.ColumnDefinitions> <ColumnDefinition Width="50*"></ColumnDefinition> <ColumnDefinition Width="50*"></ColumnDefinition> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition x:Name="ChartValuesHeader" Height="25"></RowDefinition> <RowDefinition Height="25"></RowDefinition> <RowDefinition Height="25"></RowDefinition> <RowDefinition Height="25"></RowDefinition> <RowDefinition Height="25"></RowDefinition> <RowDefinition Height="*"></RowDefinition> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Grid.Column="0" Foreground="Black" FontSize="18" FontWeight="Bold">Item</TextBlock> <TextBlock Grid.Row="0" Grid.Column="1" Foreground="Black" FontSize="18" FontWeight="Bold">Value</TextBlock> <TextBlock Grid.Row="1" Grid.Column="0" Foreground="Black" FontSize="18" >Date</TextBlock> <TextBlock Grid.Row="1" Grid.Column="1" Name="DateValue" Foreground="Black" FontSize="18"></TextBlock> <TextBlock Grid.Row="2" Grid.Column="0" Foreground="Black" FontSize="18" >Strategy Return</TextBlock> <TextBlock Grid.Row="2" Grid.Column="1" Name="StrategyReturn" Foreground="Black" FontSize="18"></TextBlock> <TextBlock Grid.Row="3" Grid.Column="0" Foreground="Black" FontSize="18" >Benchmark Return</TextBlock> <TextBlock Grid.Row="3" Grid.Column="1" Name="BenchmarkReturn" Foreground="Black" FontSize="18"></TextBlock> <TextBlock Grid.Row="4" Grid.Column="0" Foreground="Black" FontSize="18" >Difference (S-B)</TextBlock> <TextBlock Grid.Row="4" Grid.Column="1" Name="StrategyLessBench" Foreground="Black" FontSize="18"></TextBlock> </Grid> </Grid> </wpf:VisualizerBase>


BenchmarkCompare.xaml.cs below...
CODE:
using System.Windows.Input; using ScottPlot; using ScottPlot.Plottable; using WealthLab.Backtest; using WealthLab.WPF; using Color = System.Drawing.Color; namespace BenchVisualizer { /// <summary> /// A WL 8 Performance Visualizer for comparing /// the strategy dataset's daily returns and that of the benchmark symbol's /// daily returns in a ScottPlot. /// </summary> public partial class BenchmarkCompare : VisualizerBase { public BenchmarkCompare() { InitializeComponent(); } private MarkerPlot HighlightPointBenchmark { get; set; } private MarkerPlot HighlightPointStrategy { get; set; } private int LastHighlightedIndex { get; set; } = -1; private double LastYPoint { get; set; } = -1; private ScatterPlot BenchmarkBackTestScatter { get; set; } private ScatterPlot StrategyBackTestScatter { get; set; } private VLine VLine { get; set; } private HLine HLine { get; set; } public override string GlyphResource => "WealthLab.Candlesticks.WPF.Glyphs.Candlesticks.png"; public override string ViewerName => "Daily Returns Comparison"; public override bool AllowForStrategyType(StrategyType st) => true; public override void Initialize() { base.Initialize(); // when the mouse is moved, then update a data box and chart cross-hairs MyPlot.MouseMove += MyPlot_MouseMove; } private static string CleanDataSetName(string name) { if (string.IsNullOrWhiteSpace(name)) { return name; } var idx = name.LastIndexOf(';'); return idx == -1 ? name : name.Substring(0, idx); } /// <summary> /// Populate the plot /// </summary> /// <param name="backtester"></param> /// <param name="backtesterBenchmark"></param> public override void Populate(Backtester backtester, Backtester backtesterBenchmark) { MyPlot.Plot.Clear(); var plt = MyPlot.Plot; // get the returns for the strategy's dataset GetDailyReturns(backtester, out var xs, out var ys); // add a scatter plot for the strategy's dataset returns StrategyBackTestScatter = plt.AddScatter(xs, ys); StrategyBackTestScatter.Color = Color.Green; StrategyBackTestScatter.Label = "Strategy Return"; // get the returns for the benchmark symbol GetDailyReturns(backtesterBenchmark, out xs, out ys); // add a scatter plot for the benchmark symbol's returns BenchmarkBackTestScatter = plt.AddScatter(xs, ys); BenchmarkBackTestScatter.Color = Color.Blue; BenchmarkBackTestScatter.Label = "Benchmark Return"; var strategy = ParentStrategyWindow.Strategy; plt.Title($"{(strategy.SingleSymbolMode ? "Symbol" : "DataSet")} '{CleanDataSetName(strategy.SingleSymbolMode ? strategy.SingleSymbol : strategy.DataSetName)}' vs. Benchmark '{strategy.Benchmark}'"); plt.YLabel("Return %"); plt.XAxis.DateTimeFormat(true); plt.Legend(); // manually add a crosshair cursor because the ScottPlot one did not seem to work // within WL 8 VLine = plt.AddVerticalLine(0); VLine.Color = Color.Gray; VLine.IsVisible = false; HLine = plt.AddHorizontalLine(0); HLine.Color = Color.Gray; HLine.IsVisible = false; // as you move the mouse around draw a little red circle around // the value points on the chart HighlightPointStrategy = plt.AddPoint(0, 0); HighlightPointStrategy.Color = Color.Red; HighlightPointStrategy.MarkerSize = 10; HighlightPointStrategy.MarkerShape = MarkerShape.openCircle; HighlightPointStrategy.IsVisible = false; HighlightPointBenchmark = plt.AddPoint(0, 0); HighlightPointBenchmark.Color = Color.Red; HighlightPointBenchmark.MarkerSize = 10; HighlightPointBenchmark.MarkerShape = MarkerShape.openCircle; HighlightPointBenchmark.IsVisible = false; MyPlot.Refresh(); } /// <summary> /// On a mouse move update the data box (on the right) and the crosshairs. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void MyPlot_MouseMove(object sender, MouseEventArgs e) { var (x, y) = MyPlot.GetMouseCoordinates(); var (pointStrategyX, pointStrategyY, index) = StrategyBackTestScatter.GetPointNearestX(x); if (LastHighlightedIndex == index && Math.Abs(LastYPoint - y) < 0.0001) { // do not do unnecessary refreshes return; } LastHighlightedIndex = index; LastYPoint = HLine.Y = y; HLine.IsVisible = true; HighlightPointStrategy.X = pointStrategyX; HighlightPointStrategy.Y = pointStrategyY; HighlightPointStrategy.IsVisible = true; HighlightPointBenchmark.X = pointStrategyX; var benchYs = BenchmarkBackTestScatter.Ys[index]; HighlightPointBenchmark.Y = benchYs; HighlightPointBenchmark.IsVisible = true; VLine.X = pointStrategyX; VLine.IsVisible = true; DateValue.Text = DateTime.FromOADate(StrategyBackTestScatter.Xs[index]).ToString("d"); var strategyYs = StrategyBackTestScatter.Ys[index]; StrategyReturn.Text = strategyYs.ToString("N2") + "%"; BenchmarkReturn.Text = benchYs.ToString("N2") + "%"; StrategyLessBench.Text = (strategyYs - benchYs).ToString("N2"); MyPlot.Refresh(); } /// <summary> /// Gets the daily returns from the given backtester instance /// </summary> /// <param name="backTester"></param> /// <param name="xs"></param> /// <param name="ys"></param> private static void GetDailyReturns(Backtester backTester, out double[] xs, out double[] ys) { xs = backTester.DailyReturns.DateTimes.Select(x => x.ToOADate()).ToArray(); ys = backTester.DailyReturns.Values.ToArray(); } } }


BenchVisualizer.csproj below. You may need to adjust reference HintPaths depending on your installations. Also, note the PackageReferences.
CODE:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net8.0-windows</TargetFramework> <Nullable>disable</Nullable> <UseWPF>true</UseWPF> <ImplicitUsings>enable</ImplicitUsings> <Platforms>AnyCPU;x64</Platforms> </PropertyGroup> <ItemGroup> <PackageReference Include="ScottPlot.WPF" Version="4.1.68" /> <PackageReference Include="System.Drawing.Common" Version="8.0.0" /> </ItemGroup> <ItemGroup> <Reference Include="WealthLab.Core"> <HintPath>..\..\..\..\..\..\Program Files\Quantacula, LLC\WealthLab 8\WealthLab.Core.dll</HintPath> </Reference> <Reference Include="WealthLab.WPF"> <HintPath>..\..\..\..\..\..\Program Files\Quantacula, LLC\WealthLab 8\WealthLab.WPF.dll</HintPath> </Reference> </ItemGroup> </Project>


1
Best Answer
- ago
#10
Interesting. I have since thought about approaching this problem differently. But your code example has inspired me to possibly use this code for other/future ScottPlot Performance Visualizers. We need more code examples like this. Thanks a bunch!

I'm not sure why mouse position is sampled here. Perhaps it's for zooming. I'm not familiar with the ScottPlot WPF interface.
0
- ago
#11
QUOTE:
I'm not sure why mouse position is sampled here.


When you move the mouse across the chart, the crosshairs are updated, the little red circles are updated and the data box is updated. See the screenshot in Post #9.
1
- ago
#12
QUOTE:
When you move the mouse across the chart, the crosshairs are updated, the little red circles are updated and the data box is updated.

I didn't know the ScottPlot.WPF interface lets you do that. Interesting.

You should include this example in either the GitHub
https://github.com/Mkey85/WeathLab.Community
or
https://github.com/LucidDion/WL8ExtensionDemos
public libraries. I'm not sure which repository would be more appropriate. But I think if we had more examples like this, more people would be motivated to create extensions. And this would be the first example of a Performance Visualizer extension.
0
Glitch8
 ( 10.92% )
- ago
#13
WL8ExtensionDemos would be the appropriate one.
1

Reply

Bookmark

Sort