Skip to content

Commit b2d25fe

Browse files
authored
Add C# snippets to Live Reconciliation step pages (#2300) (#2302)
* Add C# snippets to Live Reconciliation step pages (#2300) The step pages under Research Environment > Meta Analysis > Live Reconciliation previously showed only Python code blocks. Add matching C# snippets alongside every Python block so readers can follow the workflow end-to-end in either language: reading the live Strategy Equity chart, compiling and running the OOS backtest with progress polling, overlaying the equity curves with Plotly.NET, reading orders with a retry loop, and rendering the per-symbol fill overlay. * Link Live Reconciliation tutorial from Reconciliation Introduction (#2300) Add a cross-link from the Writing Algorithms > Live Trading > Reconciliation introduction to the new Research Environment > Meta Analysis > Live Reconciliation tutorial so readers discovering the reconciliation concept can jump straight to the how-to.
1 parent 05d53a3 commit b2d25fe

File tree

5 files changed

+157
-5
lines changed

5 files changed

+157
-5
lines changed

04 Research Environment/10 Meta Analysis/05 Live Reconciliation/02 Get Live Deployment Parameters.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<ol>
44
<li>Define the project Id.</li>
55
<div class="section-example-container">
6+
<pre class="csharp">var projectId = 23034953;</pre>
67
<pre class="python">project_id = 23034953</pre>
78
</div>
89
@@ -33,6 +34,40 @@
3334
3435
<li>Read the live "Strategy Equity" chart with the <code class="csharp">ReadLiveChart</code><code class="python">read_live_chart</code> method. The first and last <code>Equity</code> points give you the start datetime, starting equity, and end datetime.</li>
3536
<div class="section-example-container">
37+
<pre class="csharp">var nowSec = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds();
38+
39+
Chart ReadLiveChartWithRetry(int projectId, string chartName)
40+
{
41+
for (var attempt = 0; attempt &lt; 10; attempt++)
42+
{
43+
var result = api.ReadLiveChart(projectId, chartName, 0, nowSec, 500);
44+
if (result.Success) return result.Chart;
45+
Console.WriteLine($"Chart data is loading... (attempt {attempt + 1}/10)");
46+
Thread.Sleep(10000);
47+
}
48+
throw new Exception($"Failed to read {chartName} chart after 10 attempts");
49+
}
50+
51+
var strategyEquity = ReadLiveChartWithRetry(projectId, "Strategy Equity");
52+
// The first few points in the series can have a null close, so keep only
53+
// the points with a valid close value before extracting start/end.
54+
var validValues = strategyEquity.Series["Equity"].Values
55+
.OfType&lt;Candlestick&gt;()
56+
.Where(v =&gt; v.Close.HasValue)
57+
.ToList();
58+
59+
// Start datetime and starting equity: first valid point.
60+
var startDatetime = validValues.First().Time;
61+
var startingCash = validValues.First().Close.Value;
62+
// End datetime: last valid timestamp of the live Strategy Equity series.
63+
// Uncomment the next line instead to reconcile up to "now" and see what
64+
// would have happened had you not stopped the live algorithm:
65+
// var endDatetime = DateTime.UtcNow;
66+
var endDatetime = validValues.Last().Time;
67+
68+
Console.WriteLine($"Start (UTC): {startDatetime}");
69+
Console.WriteLine($"Starting equity: ${startingCash:N2}");
70+
Console.WriteLine($"End (UTC): {endDatetime}");</pre>
3671
<pre class="python">from datetime import datetime
3772
from time import sleep, time
3873

04 Research Environment/10 Meta Analysis/05 Live Reconciliation/03 Run OOS Backtest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,21 @@
55
66
<li>Compile the project by calling the <code class="csharp">CreateCompile</code><code class="python">create_compile</code> method, then poll <code class="csharp">ReadCompile</code><code class="python">read_compile</code> until the compile state is <code>BuildSuccess</code>.</li>
77
<div class="section-example-container">
8+
<pre class="csharp">var compilation = api.CreateCompile(projectId);
9+
var compileId = compilation.CompileId;
10+
11+
// Poll until the build succeeds.
12+
for (var attempt = 0; attempt &lt; 10; attempt++)
13+
{
14+
var result = api.ReadCompile(projectId, compileId);
15+
if (result.State == CompileState.BuildSuccess) break;
16+
if (result.State == CompileState.BuildError)
17+
{
18+
throw new Exception($"Compilation failed: {string.Join(Environment.NewLine, result.Logs)}");
19+
}
20+
Console.WriteLine($"Compile in queue... (attempt {attempt + 1}/10)");
21+
Thread.Sleep(5000);
22+
}</pre>
823
<pre class="python">from time import sleep
924
1025
compilation = api.create_compile(project_id)
@@ -23,13 +38,25 @@
2338
2439
<li>Create the OOS backtest with the <code class="csharp">CreateBacktest</code><code class="python">create_backtest</code> method.</li>
2540
<div class="section-example-container">
41+
<pre class="csharp">var backtest = api.CreateBacktest(projectId, compileId, "OOS Reconciliation");
42+
var backtestId = backtest.BacktestId;
43+
Console.WriteLine($"Backtest Id: {backtestId}");</pre>
2644
<pre class="python">backtest = api.create_backtest(project_id, compile_id, 'OOS Reconciliation')
2745
backtest_id = backtest.backtest_id
2846
print(f"Backtest Id: {backtest_id}")</pre>
2947
</div>
3048
3149
<li>Poll the <code class="csharp">ReadBacktest</code><code class="python">read_backtest</code> method until the <code>completed</code> flag is <code>True</code>. Log the <code>progress</code> attribute on each poll so you can watch the backtest advance.</li>
3250
<div class="section-example-container">
51+
<pre class="csharp">var completed = false;
52+
while (!completed)
53+
{
54+
var result = api.ReadBacktest(projectId, backtestId);
55+
completed = result.Completed;
56+
Console.WriteLine($"Backtest running... {result.Progress:P2}");
57+
Thread.Sleep(10000);
58+
}
59+
Console.WriteLine("Backtest completed.");</pre>
3360
<pre class="python">completed = False
3461
while not completed:
3562
result = api.read_backtest(project_id, backtest_id)

04 Research Environment/10 Meta Analysis/05 Live Reconciliation/04 Plot Equity Curves.php

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,25 @@
33
<ol>
44
<li>Read the live "Strategy Equity" chart using the retry helper from the previous step.</li>
55
<div class="section-example-container">
6+
<pre class="csharp">var liveEquityChart = ReadLiveChartWithRetry(projectId, "Strategy Equity");</pre>
67
<pre class="python">live_equity_chart = read_chart(project_id, 'Strategy Equity')</pre>
78
</div>
89
910
<li>Read the backtest "Strategy Equity" chart by calling the <code class="csharp">ReadBacktestChart</code><code class="python">read_backtest_chart</code> method with the same retry pattern.</li>
1011
<div class="section-example-container">
12+
<pre class="csharp">Chart ReadBacktestChartWithRetry(int projectId, string backtestId, string chartName)
13+
{
14+
for (var attempt = 0; attempt &lt; 10; attempt++)
15+
{
16+
var result = api.ReadBacktestChart(projectId, chartName, 0, nowSec, 500, backtestId);
17+
if (result.Success) return result.Chart;
18+
Console.WriteLine($"Chart data is loading... (attempt {attempt + 1}/10)");
19+
Thread.Sleep(10000);
20+
}
21+
throw new Exception($"Failed to read backtest {chartName} chart after 10 attempts");
22+
}
23+
24+
var backtestEquityChart = ReadBacktestChartWithRetry(projectId, backtestId, "Strategy Equity");</pre>
1125
<pre class="python">def read_backtest_chart(project_id, backtest_id, chart_name, start=0, end=int(time()), count=500):
1226
for attempt in range(10):
1327
result = api.read_backtest_chart(project_id, chart_name, start, end, count, backtest_id)
@@ -20,8 +34,16 @@
2034
backtest_equity_chart = read_backtest_chart(project_id, backtest_id, 'Strategy Equity')</pre>
2135
</div>
2236
23-
<li>Extract the <code>Equity</code> series from each chart into a <code>pandas.Series</code> indexed by timestamp. Preserve every timestamp from both the live and backtest series and strip any timezone info so <code>pandas</code> and <code>plotly</code> can align and render the curves cleanly.</li>
37+
<li>Extract the <code>Equity</code> series from each chart, filtering out points with a null close. Python uses a <code>pandas.Series</code> indexed by timestamp so the two curves can be aligned on the union of their timestamps; C# keeps two lists of <code>Candlestick</code> points and lets Plotly.NET align them on the same x-axis.</li>
2438
<div class="section-example-container">
39+
<pre class="csharp">var liveValues = liveEquityChart.Series["Equity"].Values
40+
.OfType&lt;Candlestick&gt;()
41+
.Where(v =&gt; v.Close.HasValue)
42+
.ToList();
43+
var backtestValues = backtestEquityChart.Series["Equity"].Values
44+
.OfType&lt;Candlestick&gt;()
45+
.Where(v =&gt; v.Close.HasValue)
46+
.ToList();</pre>
2547
<pre class="python">import pandas as pd
2648
2749
def to_naive(t):
@@ -42,8 +64,28 @@
4264
df = pd.concat([live_series.rename('Live'), backtest_series.rename('OOS Backtest')], axis=1).sort_index().ffill()</pre>
4365
</div>
4466
45-
<li>Plot both curves on a single <code>matplotlib</code> axis.</li>
67+
<li>Plot both curves on the same axis. Python uses <code>matplotlib</code>; C# uses <a href='/docs/v2/research-environment/charting/plotly-net'>Plotly.NET</a> — load <code>Plotly.NET</code> and <code>Plotly.NET.Interactive</code> from NuGet and alias <code>Plotly.NET.Chart</code> to avoid ambiguity with <code>QuantConnect.Chart</code>.</li>
4668
<div class="section-example-container">
69+
<pre class="csharp">#r "nuget: Plotly.NET"
70+
#r "nuget: Plotly.NET.Interactive"
71+
using PlotlyChart = Plotly.NET.Chart;
72+
using Plotly.NET;
73+
using Plotly.NET.Interactive;
74+
using Plotly.NET.LayoutObjects;
75+
76+
var equityChart = PlotlyChart.Combine(new[]
77+
{
78+
Chart2D.Chart.Line&lt;DateTime, decimal, string&gt;(
79+
liveValues.Select(v =&gt; v.Time),
80+
liveValues.Select(v =&gt; v.Close.Value),
81+
Name: "Live"),
82+
Chart2D.Chart.Line&lt;DateTime, decimal, string&gt;(
83+
backtestValues.Select(v =&gt; v.Time),
84+
backtestValues.Select(v =&gt; v.Close.Value),
85+
Name: "OOS Backtest")
86+
}).WithTitle("Live vs OOS Backtest Equity");
87+
88+
display(equityChart);</pre>
4789
<pre class="python">import matplotlib.pyplot as plt
4890
4991
fig, ax = plt.subplots(figsize=(12, 6))
@@ -57,7 +99,7 @@
5799
</div>
58100
<img class='docs-image' src="https://cdn.quantconnect.com/i/tu/reconciliation-4.png" alt="Live vs OOS backtest equity curves">
59101
60-
<li>Score the reconciliation with the annualized returns DTW distance and the Pearson correlation of daily returns. Use <code>tslearn</code>'s <code>dtw</code> with a Sakoe-Chiba band so the algorithm runs in linear time.</li>
102+
<li>Score the reconciliation with the annualized returns DTW distance and the Pearson correlation of daily returns. Use <code>tslearn</code>'s <code>dtw</code> with a Sakoe-Chiba band so the algorithm runs in linear time. The <code>tslearn</code> library is Python-only; run this step in a Python research notebook.</li>
61103
<div class="section-example-container">
62104
<pre class="python">from tslearn.metrics import dtw as DynamicTimeWarping
63105

04 Research Environment/10 Meta Analysis/05 Live Reconciliation/05 Plot Order Fills.php

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
11
<p>Follow these steps to overlay live and OOS backtest order fills on a single marker-only chart per symbol. The chart deliberately omits candlesticks and any price history so the comparison between live and backtest executions is not drowned out by other series.</p>
22

33
<ol>
4-
<li>Read the live and backtest orders. Both endpoints can take a few seconds to load the first time, so retry until the response reports success.</li>
4+
<li>Read the live and backtest orders. Both endpoints can take a few seconds to load the first time, so retry until the response is non-empty.</li>
55
<div class="section-example-container">
6+
<pre class="csharp">List&lt;ApiOrderResponse&gt; ReadOrders(Func&lt;List&lt;ApiOrderResponse&gt;&gt; fetch)
7+
{
8+
for (var attempt = 0; attempt &lt; 10; attempt++)
9+
{
10+
var result = fetch();
11+
if (result.Any()) return result;
12+
Console.WriteLine($"Orders loading... (attempt {attempt + 1}/10)");
13+
Thread.Sleep(10000);
14+
}
15+
throw new Exception("Failed to read orders after 10 attempts");
16+
}
17+
18+
var liveOrders = ReadOrders(() =&gt; api.ReadLiveOrders(projectId, 0, 100));
19+
var backtestOrders = ReadOrders(() =&gt; api.ReadBacktestOrders(projectId, backtestId, 0, 100));</pre>
620
<pre class="python">from time import sleep
721
822
def read_orders(fetch):
@@ -21,6 +35,10 @@
2135
2236
<li>Organize the trade times and prices for each security into a dictionary for both the live and backtest fills.</li>
2337
<div class="section-example-container">
38+
<pre class="csharp">var liveBySymbol = liveOrders.Select(x =&gt; x.Order).GroupBy(o =&gt; o.Symbol);
39+
var backtestBySymbol = backtestOrders.Select(x =&gt; x.Order)
40+
.GroupBy(o =&gt; o.Symbol)
41+
.ToDictionary(g =&gt; g.Key, g =&gt; g.ToList());</pre>
2442
<pre class="python">import pandas as pd
2543
2644
def to_naive(t):
@@ -52,6 +70,34 @@ class OrderData:
5270
5371
<li>Plot one figure per symbol with four marker traces: live buys, live sells, backtest buys, backtest sells. Distinct markers keep live versus backtest executions visually separable.</li>
5472
<div class="section-example-container">
73+
<pre class="csharp">foreach (var liveGroup in liveBySymbol)
74+
{
75+
var symbol = liveGroup.Key;
76+
var live = liveGroup.ToList();
77+
var bt = backtestBySymbol.TryGetValue(symbol, out var btList) ? btList : new List&lt;Order&gt;();
78+
79+
var traces = new[]
80+
{
81+
Chart2D.Chart.Point&lt;DateTime, decimal, string&gt;(
82+
live.Where(o =&gt; o.Quantity &gt; 0).Select(o =&gt; o.LastFillTime ?? o.Time),
83+
live.Where(o =&gt; o.Quantity &gt; 0).Select(o =&gt; o.Price),
84+
Name: "Live Buys"),
85+
Chart2D.Chart.Point&lt;DateTime, decimal, string&gt;(
86+
live.Where(o =&gt; o.Quantity &lt; 0).Select(o =&gt; o.LastFillTime ?? o.Time),
87+
live.Where(o =&gt; o.Quantity &lt; 0).Select(o =&gt; o.Price),
88+
Name: "Live Sells"),
89+
Chart2D.Chart.Point&lt;DateTime, decimal, string&gt;(
90+
bt.Where(o =&gt; o.Quantity &gt; 0).Select(o =&gt; o.LastFillTime ?? o.Time),
91+
bt.Where(o =&gt; o.Quantity &gt; 0).Select(o =&gt; o.Price),
92+
Name: "OOS Backtest Buys"),
93+
Chart2D.Chart.Point&lt;DateTime, decimal, string&gt;(
94+
bt.Where(o =&gt; o.Quantity &lt; 0).Select(o =&gt; o.LastFillTime ?? o.Time),
95+
bt.Where(o =&gt; o.Quantity &lt; 0).Select(o =&gt; o.Price),
96+
Name: "OOS Backtest Sells")
97+
};
98+
var fillsChart = PlotlyChart.Combine(traces).WithTitle($"{symbol} Live vs OOS Backtest Fills");
99+
display(fillsChart);
100+
}</pre>
55101
<pre class="python">import plotly.graph_objects as go
56102
57103
symbols = set(live_by_symbol.keys()) | set(backtest_by_symbol.keys())

Resources/live-trading/reconciliation/introduction.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@
22

33
<img class="img-responsive" src="https://cdn.quantconnect.com/i/tu/live-strategy-equity.png" alt="The live and OSS backtest equity curves of an Alpha">
44

5-
<p>If your algorithm is perfectly reconciled, it has an exact overlap between its live and OOS backtest equity curves. Deviations mean that the performance of your algorithm has differed between the two execution modes. Several factors can contribute to the deviations.</p>
5+
<p>If your algorithm is perfectly reconciled, it has an exact overlap between its live and OOS backtest equity curves. Deviations mean that the performance of your algorithm has differed between the two execution modes. Several factors can contribute to the deviations.</p>
6+
7+
<p>To generate the OOS reconciliation curve for a live deployment and overlay live vs. backtest equity and order fills from the Research Environment, see <a href="/docs/v2/research-environment/meta-analysis/live-reconciliation">Live Reconciliation</a>.</p>

0 commit comments

Comments
 (0)