- ago
Greetings WealthLabbers: I am currently iterating on the development of a WealthLabClientExtension. I have run the extension against ~60% of the datasets provided by WealthLab with no issue. I do have a subscription to NorgateData and some of those datasets are where the issue arises. Specifically, any of the datasets appended with "Current & Past" cause the exception "Could not obtain Benchmark Data for Symbol: SPY" to be raised. Unfortunately, the datasets appended with "Current & Past" are also the most interesting to me ;-)

As you can see in the code fragment below, I specifically load the BenchmarkSymbol and BenchmarkData prior to the call to RunBacktest(). I can also attest that the WLHOST.Instance.GetHistory() call successfully returned a BarHistory for for the StrategyRunner. Could you take a look at the code below and see if you see something blatantly obvious? I find it suspicious that it is only Norgate datasets with "Current & Past" which cause the exception.

CODE:
private async void RunBtn_Click(object sender, RoutedEventArgs e) { // --- Validate inputs ------------------------------------------------ string stratName = StrategyBox.Text.Trim(); if (string.IsNullOrEmpty(stratName)) { MessageBox.Show("Please enter a strategy name.", "Monte Carlo Lab", MessageBoxButton.OK, MessageBoxImage.Warning); return; } if (!double.TryParse(CapitalBox.Text, out double capital) || capital <= 0) { MessageBox.Show("Please enter a valid starting capital.", "Monte Carlo Lab", MessageBoxButton.OK, MessageBoxImage.Warning); return; } int numRuns = int.TryParse(RunsBox.Text, out int rv) && rv >= 10 ? rv : 1000; int years = int.TryParse(YearsBox.Text, out int yv) && yv >= 1 ? yv : 10; // --- Lock UI -------------------------------------------------------- RunBtn.IsEnabled = false; CancelBtn.IsEnabled = true; _cts = new CancellationTokenSource(); ProgressBar.Maximum = numRuns; ProgressBar.Value = 0; StatusText.Text = "Running base backtest…"; _result = null; StatsGrid.ItemsSource = null; ChartCanvas.Children.Clear(); try { // ── 1. Base backtest ───────────────────────────────────────────── var sr = new StrategyRunner(); sr.PositionSize.StartingCapital = capital; sr.DataRange = new DataRange(DataRangeType.YearRange, 0, DateTime.Now.Date.AddYears(-10), DateTime.Now.Date); // sr.DataRange.DataRangeType = DataRangeTypes.RecentYears; sr.DataRange.RecentValue = years; if (DatasetCombo.SelectedItem is DataSet ds) sr.DataSet = ds; // Explicitly pre-load the benchmark so StrategyRunner doesn't have // to fetch it internally, avoiding the intermittent SPY data error. StatusText.Text = "Loading benchmark data…"; DateTime benchStart = DateTime.Now.Date.AddYears(-years); DateTime benchEnd = DateTime.Now.Date; BarHistory? benchmark = WLHost.Instance.GetHistory( "SPY", HistoryScale.Daily, benchStart, benchEnd, years * 265, null); if (benchmark != null) { StatusText.Text = "Benchmark data loaded: " + benchmark.Count + " bars."; sr.BenchmarkData = benchmark; MessageBox.Show( $"Benchmark data for SPY loaded successfully.\n\n" + $"Bars: {benchmark.Count}\n" + $"Date range: {benchmark.StartDate:d} to {benchmark.EndDate:d}", "Monte Carlo Lab", MessageBoxButton.OK, MessageBoxImage.Information); } else { StatusText.Text = "Failed to load benchmark data."; MessageBox.Show( "Failed to load benchmark data for SPY.\n\n" + "Please verify your dataset selection and internet connection.", "Monte Carlo Lab", MessageBoxButton.OK, MessageBoxImage.Warning); } // StrategyRunner.RunBacktest is thread-safe and may be called from // a background thread (WL8 uses this for optimization runs). StatusText.Text = "Running base backtest…"; Backtester bt = await Task.Run( () => sr.RunBacktest(stratName), _cts.Token); if (bt is null || bt.Positions.Count == 0) { StatusText.Text = "No trades found."; MessageBox.Show( "The backtest produced no closed trades.\n\n" + "Please verify:\n" + " • The strategy name exactly matches a saved strategy\n" + " • The selected dataset and date range are appropriate\n" + " • The strategy generates signals over the selected period", "Monte Carlo Lab", MessageBoxButton.OK, MessageBoxImage.Warning); return; } // ── 2. Extract trade data ──────────────────────────────────────── var positions = new List<Position>(bt.Positions.Count); foreach (Position p in bt.Positions) positions.Add(p); StatusText.Text = $"Base backtest: {positions.Count} trades. Starting {numRuns:N0} MC runs…"; var profits = positions.Select(p => p.Profit).ToList(); DateTime t0 = positions.Min(p => p.EntryDate); DateTime t1 = positions.Max(p => p.ExitDate); double startCap = bt.StartingCapital > 0 ? bt.StartingCapital : capital; // Read baseline metrics. WL8 Metrics is a dynamic property; individual // metrics are only present if the matching ScoreCard is installed. double baseNetPnlPct = 0, baseMaxDD = 0, baseAPR = 0; try { baseNetPnlPct = (double)bt.Metrics.NetProfit / startCap * 100.0; } catch { /* ScoreCard not installed */ } try { baseMaxDD = (double)bt.Metrics.MaxDrawdownPct; } catch { } try { baseAPR = (double)bt.Metrics.APR; } catch { } // ── 3. Monte Carlo simulation ──────────────────────────────────── var progress = new Progress<int>(v => { ProgressBar.Value = v; StatusText.Text = $"Monte Carlo: {v:N0} / {numRuns:N0}"; }); _result = await Task.Run(() => new MonteCarloEngine().Run( profits, startCap, numRuns, t0, t1, progress, _cts.Token)); _result.BaseNetPnlPct = baseNetPnlPct; _result.BaseMaxDDPct = baseMaxDD; _result.BaseAPR = baseAPR; // ── 4. Display ─────────────────────────────────────────────────── DisplayResults(_result); StatusText.Text = $"Done — {numRuns:N0} runs · {positions.Count} trades · " + $"{_result.Years:F1} yrs · {_result.ProbabilityOfProfit:F1}% profitable"; } catch (OperationCanceledException) { StatusText.Text = "Cancelled."; } catch (Exception ex) { StatusText.Text = "Error — see details."; MessageBox.Show( $"Error running Monte Carlo:\n\n{ex.Message}", "Monte Carlo Lab", MessageBoxButton.OK, MessageBoxImage.Error); } finally { RunBtn.IsEnabled = true; CancelBtn.IsEnabled = false; _cts?.Dispose(); _cts = null; } }
0
146
2 Replies

Reply

Bookmark

Sort
Cone8
- ago
#1
Probably you're testing delisted symbols whose End date is before SPY's Start date. Instead use Norgate's $SPX (or Y!'s ^GSPC) for the benchmark that will have more history.

Sometimes you just need to believe what the message is telling you ;)
1
- ago
#2
OK, finally got back to this... Further investigation seems to point at the DataSet.IsDynamic property. If TRUE, then this causes StrategyRunner.RunBacktest() to try and reload the StrategyRunner.BenchmarkSymbol even if StrategyRunner.BenchmarkData is non-null. The only way I found to work around the problem is to explicitly load the StrategyRunner.Data member with the Symbols which are active during the StrategyRunner.DataRange. Takes a while on a large dataset, but it seems to work. Just leaving breadcrumbs for others to find. Thanks for the pointer Cone.
1

Reply

Bookmark

Sort