diff --git a/04 Research Environment/10 Meta Analysis/05 Live Reconciliation/02 Get Live Deployment Parameters.php b/04 Research Environment/10 Meta Analysis/05 Live Reconciliation/02 Get Live Deployment Parameters.php index ca15829191..3fde439dda 100644 --- a/04 Research Environment/10 Meta Analysis/05 Live Reconciliation/02 Get Live Deployment Parameters.php +++ b/04 Research Environment/10 Meta Analysis/05 Live Reconciliation/02 Get Live Deployment Parameters.php @@ -3,6 +3,7 @@
  1. Define the project Id.
  2. +
    var projectId = 23034953;
    project_id = 23034953
    @@ -33,6 +34,40 @@
  3. Read the live "Strategy Equity" chart with the ReadLiveChartread_live_chart method. The first and last Equity points give you the start datetime, starting equity, and end datetime.
  4. +
    var nowSec = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds();
    +
    +Chart ReadLiveChartWithRetry(int projectId, string chartName)
    +{
    +    for (var attempt = 0; attempt < 10; attempt++)
    +    {
    +        var result = api.ReadLiveChart(projectId, chartName, 0, nowSec, 500);
    +        if (result.Success) return result.Chart;
    +        Console.WriteLine($"Chart data is loading... (attempt {attempt + 1}/10)");
    +        Thread.Sleep(10000);
    +    }
    +    throw new Exception($"Failed to read {chartName} chart after 10 attempts");
    +}
    +
    +var strategyEquity = ReadLiveChartWithRetry(projectId, "Strategy Equity");
    +// The first few points in the series can have a null close, so keep only
    +// the points with a valid close value before extracting start/end.
    +var validValues = strategyEquity.Series["Equity"].Values
    +    .OfType<Candlestick>()
    +    .Where(v => v.Close.HasValue)
    +    .ToList();
    +
    +// Start datetime and starting equity: first valid point.
    +var startDatetime = validValues.First().Time;
    +var startingCash = validValues.First().Close.Value;
    +// End datetime: last valid timestamp of the live Strategy Equity series.
    +// Uncomment the next line instead to reconcile up to "now" and see what
    +// would have happened had you not stopped the live algorithm:
    +// var endDatetime = DateTime.UtcNow;
    +var endDatetime = validValues.Last().Time;
    +
    +Console.WriteLine($"Start (UTC): {startDatetime}");
    +Console.WriteLine($"Starting equity: ${startingCash:N2}");
    +Console.WriteLine($"End (UTC): {endDatetime}");
    from datetime import datetime
     from time import sleep, time
     
    diff --git a/04 Research Environment/10 Meta Analysis/05 Live Reconciliation/03 Run OOS Backtest.php b/04 Research Environment/10 Meta Analysis/05 Live Reconciliation/03 Run OOS Backtest.php
    index b6b355c09e..7ecf8825f9 100644
    --- a/04 Research Environment/10 Meta Analysis/05 Live Reconciliation/03 Run OOS Backtest.php	
    +++ b/04 Research Environment/10 Meta Analysis/05 Live Reconciliation/03 Run OOS Backtest.php	
    @@ -5,6 +5,21 @@
     
         
  5. Compile the project by calling the CreateCompilecreate_compile method, then poll ReadCompileread_compile until the compile state is BuildSuccess.
  6. +
    var compilation = api.CreateCompile(projectId);
    +var compileId = compilation.CompileId;
    +
    +// Poll until the build succeeds.
    +for (var attempt = 0; attempt < 10; attempt++)
    +{
    +    var result = api.ReadCompile(projectId, compileId);
    +    if (result.State == CompileState.BuildSuccess) break;
    +    if (result.State == CompileState.BuildError)
    +    {
    +        throw new Exception($"Compilation failed: {string.Join(Environment.NewLine, result.Logs)}");
    +    }
    +    Console.WriteLine($"Compile in queue... (attempt {attempt + 1}/10)");
    +    Thread.Sleep(5000);
    +}
    from time import sleep
     
     compilation = api.create_compile(project_id)
    @@ -23,6 +38,9 @@
     
         
  7. Create the OOS backtest with the CreateBacktestcreate_backtest method.
  8. +
    var backtest = api.CreateBacktest(projectId, compileId, "OOS Reconciliation");
    +var backtestId = backtest.BacktestId;
    +Console.WriteLine($"Backtest Id: {backtestId}");
    backtest = api.create_backtest(project_id, compile_id, 'OOS Reconciliation')
     backtest_id = backtest.backtest_id
     print(f"Backtest Id: {backtest_id}")
    @@ -30,6 +48,15 @@
  9. Poll the ReadBacktestread_backtest method until the completed flag is True. Log the progress attribute on each poll so you can watch the backtest advance.
  10. +
    var completed = false;
    +while (!completed)
    +{
    +    var result = api.ReadBacktest(projectId, backtestId);
    +    completed = result.Completed;
    +    Console.WriteLine($"Backtest running... {result.Progress:P2}");
    +    Thread.Sleep(10000);
    +}
    +Console.WriteLine("Backtest completed.");
    completed = False
     while not completed:
         result = api.read_backtest(project_id, backtest_id)
    diff --git a/04 Research Environment/10 Meta Analysis/05 Live Reconciliation/04 Plot Equity Curves.php b/04 Research Environment/10 Meta Analysis/05 Live Reconciliation/04 Plot Equity Curves.php
    index e2cbf5adc1..51591ea372 100644
    --- a/04 Research Environment/10 Meta Analysis/05 Live Reconciliation/04 Plot Equity Curves.php	
    +++ b/04 Research Environment/10 Meta Analysis/05 Live Reconciliation/04 Plot Equity Curves.php	
    @@ -3,11 +3,25 @@
     
    1. Read the live "Strategy Equity" chart using the retry helper from the previous step.
    2. +
      var liveEquityChart = ReadLiveChartWithRetry(projectId, "Strategy Equity");
      live_equity_chart = read_chart(project_id, 'Strategy Equity')
    3. Read the backtest "Strategy Equity" chart by calling the ReadBacktestChartread_backtest_chart method with the same retry pattern.
    4. +
      Chart ReadBacktestChartWithRetry(int projectId, string backtestId, string chartName)
      +{
      +    for (var attempt = 0; attempt < 10; attempt++)
      +    {
      +        var result = api.ReadBacktestChart(projectId, chartName, 0, nowSec, 500, backtestId);
      +        if (result.Success) return result.Chart;
      +        Console.WriteLine($"Chart data is loading... (attempt {attempt + 1}/10)");
      +        Thread.Sleep(10000);
      +    }
      +    throw new Exception($"Failed to read backtest {chartName} chart after 10 attempts");
      +}
      +
      +var backtestEquityChart = ReadBacktestChartWithRetry(projectId, backtestId, "Strategy Equity");
      def read_backtest_chart(project_id, backtest_id, chart_name, start=0, end=int(time()), count=500):
           for attempt in range(10):
               result = api.read_backtest_chart(project_id, chart_name, start, end, count, backtest_id)
      @@ -20,8 +34,16 @@
       backtest_equity_chart = read_backtest_chart(project_id, backtest_id, 'Strategy Equity')
      -
    5. Extract the Equity series from each chart into a pandas.Series indexed by timestamp. Preserve every timestamp from both the live and backtest series and strip any timezone info so pandas and plotly can align and render the curves cleanly.
    6. +
    7. Extract the Equity series from each chart, filtering out points with a null close. Python uses a pandas.Series indexed by timestamp so the two curves can be aligned on the union of their timestamps; C# keeps two lists of Candlestick points and lets Plotly.NET align them on the same x-axis.
    8. +
      var liveValues = liveEquityChart.Series["Equity"].Values
      +    .OfType<Candlestick>()
      +    .Where(v => v.Close.HasValue)
      +    .ToList();
      +var backtestValues = backtestEquityChart.Series["Equity"].Values
      +    .OfType<Candlestick>()
      +    .Where(v => v.Close.HasValue)
      +    .ToList();
      import pandas as pd
       
       def to_naive(t):
      @@ -42,8 +64,28 @@
       df = pd.concat([live_series.rename('Live'), backtest_series.rename('OOS Backtest')], axis=1).sort_index().ffill()
      -
    9. Plot both curves on a single matplotlib axis.
    10. +
    11. Plot both curves on the same axis. Python uses matplotlib; C# uses Plotly.NET — load Plotly.NET and Plotly.NET.Interactive from NuGet and alias Plotly.NET.Chart to avoid ambiguity with QuantConnect.Chart.
    12. +
      #r "nuget: Plotly.NET"
      +#r "nuget: Plotly.NET.Interactive"
      +using PlotlyChart = Plotly.NET.Chart;
      +using Plotly.NET;
      +using Plotly.NET.Interactive;
      +using Plotly.NET.LayoutObjects;
      +
      +var equityChart = PlotlyChart.Combine(new[]
      +{
      +    Chart2D.Chart.Line<DateTime, decimal, string>(
      +        liveValues.Select(v => v.Time),
      +        liveValues.Select(v => v.Close.Value),
      +        Name: "Live"),
      +    Chart2D.Chart.Line<DateTime, decimal, string>(
      +        backtestValues.Select(v => v.Time),
      +        backtestValues.Select(v => v.Close.Value),
      +        Name: "OOS Backtest")
      +}).WithTitle("Live vs OOS Backtest Equity");
      +
      +display(equityChart);
      import matplotlib.pyplot as plt
       
       fig, ax = plt.subplots(figsize=(12, 6))
      @@ -57,7 +99,7 @@
           
      Live vs OOS backtest equity curves -
    13. Score the reconciliation with the annualized returns DTW distance and the Pearson correlation of daily returns. Use tslearn's dtw with a Sakoe-Chiba band so the algorithm runs in linear time.
    14. +
    15. Score the reconciliation with the annualized returns DTW distance and the Pearson correlation of daily returns. Use tslearn's dtw with a Sakoe-Chiba band so the algorithm runs in linear time. The tslearn library is Python-only; run this step in a Python research notebook.
    16. from tslearn.metrics import dtw as DynamicTimeWarping
       
      diff --git a/04 Research Environment/10 Meta Analysis/05 Live Reconciliation/05 Plot Order Fills.php b/04 Research Environment/10 Meta Analysis/05 Live Reconciliation/05 Plot Order Fills.php
      index 63512edb40..d613a72ce8 100644
      --- a/04 Research Environment/10 Meta Analysis/05 Live Reconciliation/05 Plot Order Fills.php	
      +++ b/04 Research Environment/10 Meta Analysis/05 Live Reconciliation/05 Plot Order Fills.php	
      @@ -1,8 +1,22 @@
       

      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.

        -
      1. 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.
      2. +
      3. 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.
      4. +
        List<ApiOrderResponse> ReadOrders(Func<List<ApiOrderResponse>> fetch)
        +{
        +    for (var attempt = 0; attempt < 10; attempt++)
        +    {
        +        var result = fetch();
        +        if (result.Any()) return result;
        +        Console.WriteLine($"Orders loading... (attempt {attempt + 1}/10)");
        +        Thread.Sleep(10000);
        +    }
        +    throw new Exception("Failed to read orders after 10 attempts");
        +}
        +
        +var liveOrders = ReadOrders(() => api.ReadLiveOrders(projectId, 0, 100));
        +var backtestOrders = ReadOrders(() => api.ReadBacktestOrders(projectId, backtestId, 0, 100));
        from time import sleep
         
         def read_orders(fetch):
        @@ -21,6 +35,10 @@
         
             
      5. Organize the trade times and prices for each security into a dictionary for both the live and backtest fills.
      6. +
        var liveBySymbol = liveOrders.Select(x => x.Order).GroupBy(o => o.Symbol);
        +var backtestBySymbol = backtestOrders.Select(x => x.Order)
        +    .GroupBy(o => o.Symbol)
        +    .ToDictionary(g => g.Key, g => g.ToList());
        import pandas as pd
         
         def to_naive(t):
        @@ -52,6 +70,34 @@ class OrderData:
         
             
      7. 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.
      8. +
        foreach (var liveGroup in liveBySymbol)
        +{
        +    var symbol = liveGroup.Key;
        +    var live = liveGroup.ToList();
        +    var bt = backtestBySymbol.TryGetValue(symbol, out var btList) ? btList : new List<Order>();
        +
        +    var traces = new[]
        +    {
        +        Chart2D.Chart.Point<DateTime, decimal, string>(
        +            live.Where(o => o.Quantity > 0).Select(o => o.LastFillTime ?? o.Time),
        +            live.Where(o => o.Quantity > 0).Select(o => o.Price),
        +            Name: "Live Buys"),
        +        Chart2D.Chart.Point<DateTime, decimal, string>(
        +            live.Where(o => o.Quantity < 0).Select(o => o.LastFillTime ?? o.Time),
        +            live.Where(o => o.Quantity < 0).Select(o => o.Price),
        +            Name: "Live Sells"),
        +        Chart2D.Chart.Point<DateTime, decimal, string>(
        +            bt.Where(o => o.Quantity > 0).Select(o => o.LastFillTime ?? o.Time),
        +            bt.Where(o => o.Quantity > 0).Select(o => o.Price),
        +            Name: "OOS Backtest Buys"),
        +        Chart2D.Chart.Point<DateTime, decimal, string>(
        +            bt.Where(o => o.Quantity < 0).Select(o => o.LastFillTime ?? o.Time),
        +            bt.Where(o => o.Quantity < 0).Select(o => o.Price),
        +            Name: "OOS Backtest Sells")
        +    };
        +    var fillsChart = PlotlyChart.Combine(traces).WithTitle($"{symbol} Live vs OOS Backtest Fills");
        +    display(fillsChart);
        +}
        import plotly.graph_objects as go
         
         symbols = set(live_by_symbol.keys()) | set(backtest_by_symbol.keys())
        diff --git a/Resources/live-trading/reconciliation/introduction.html b/Resources/live-trading/reconciliation/introduction.html
        index fc0b2e73a6..1bff14a67d 100644
        --- a/Resources/live-trading/reconciliation/introduction.html
        +++ b/Resources/live-trading/reconciliation/introduction.html
        @@ -2,4 +2,6 @@
         
         The live and OSS backtest equity curves of an Alpha
         
        -

        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.

        \ No newline at end of file +

        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.

        + +

        To generate the OOS reconciliation curve for a live deployment and overlay live vs. backtest equity and order fills from the Research Environment, see Live Reconciliation.

        \ No newline at end of file