QuantConnect / Lean

Lean Algorithmic Trading Engine by QuantConnect (Python, C#)
https://lean.io
Apache License 2.0
10k stars 3.3k forks source link

ArgumentOutOfRangeException in StatisticsBuilder.CreateBenchmarkDifferences #3400

Closed AlexCatarino closed 5 years ago

AlexCatarino commented 5 years ago

Expected Behavior

No exception unhandled exceptions in Lean.

Actual Behavior

ArgumentOutOfRangeException in StatisticsBuilder.CreateBenchmarkDifferences:

2019-07-15T22:29:15.5762420Z ERROR:: Engine.Run(): Error generating statistics packet System.ArgumentOutOfRangeException: The added or subtracted value results in an un-representable DateTime.
Parameter name: value
  at System.DateTime.AddTicks (System.Int64 value) [0x0002b] in <b0e1ad7573a24fd5a9f2af9595e677e7>:0 
  at System.DateTime.Add (System.Double value, System.Int32 scale) [0x0006c] in <b0e1ad7573a24fd5a9f2af9595e677e7>:0 
  at System.DateTime.AddDays (System.Double value) [0x00000] in <b0e1ad7573a24fd5a9f2af9595e677e7>:0 
  at QuantConnect.Statistics.StatisticsBuilder.CreateBenchmarkDifferences (System.Collections.Generic.SortedDictionary2[TKey,TValue] benchmark, System.Collections.Generic.SortedDictionary2[TKey,TValue] equity) [0x00030] in <99446b66e2ed48f69e373a94c8c6fea4>:0

This exception is thrown by this statement:

var minDate = equity.Keys.FirstOrDefault().AddDays(-1);

when equity is an empty dictionary.

Potential Solution

Return if equity is empty:

if (equity.IsNullOrEmpty())
{
    return listBenchmark;
}

or understand and fix the reason for an empty equity dictionary.

Reproducing the Problem

using MathNet.Numerics.Statistics;
using Python.Runtime;

namespace QuantConnect.Algorithm.CSharp
{
    public class NadionOptimizedCoreWave : QCAlgorithm
    {
        public override void Initialize()
        {
            SetStartDate(2009, 03, 01);
            SetEndDate(2009, 05, 01);
            SetCash(3000);
            SetTimeZone(TimeZones.NewYork);

            // NB: note that fill-forward does not actually fill-forward while market is closed!
            var futureES = AddFuture(Futures.Indices.SP500EMini, Resolution.Minute, fillDataForward: true);
            futureES.SetFilter(x => x.FrontMonth());

            Schedule.On(DateRules.EveryDay(), TimeRules.At(16, 10), () => {
                if (Portfolio.Invested) {
                    Liquidate();
                }
            });
        }

        private const int MinHistory = 250;

        private Symbol currentContract = null;
        private TradeBarConsolidator consolidator = null;
        private HashSet<Symbol> seenFrontMonths = new HashSet<Symbol>();
        private List<TradeBar> allBars = null;

        public override void OnData(Slice slice) {
            Log($"{Time.Date}");
            foreach (var chain in slice.FutureChains) { 
                foreach (var contract in chain.Value) {
                    // Second clause prevents buggy back-and-forth flipping of front month in early 2018
                    if (currentContract != contract.Symbol && !seenFrontMonths.Contains(contract.Symbol)) {
                        Debug($"Cur: {currentContract}");
                        Debug($"New: {contract.Expiry}\t{contract.UnderlyingSymbol}\t{contract.Symbol}");

                        if (consolidator != null) {
                            consolidator.DataConsolidated -= OnDataConsolidated;
                            SubscriptionManager.RemoveConsolidator(currentContract, consolidator);
                        }

                        currentContract = contract.Symbol;
                        seenFrontMonths.Add(contract.Symbol);

                        consolidator = new TradeBarConsolidator(TimeSpan.FromMinutes(30));
                        consolidator.DataConsolidated += OnDataConsolidated;
                        SubscriptionManager.AddConsolidator(contract.Symbol, consolidator);

                        Log("Added new consolidator for " + contract.Symbol.Value);

                        allBars = null;
                    }

                }
            }
        }

        private bool isAllowedTradeTime(DateTime t) {
            var rthStart = TimeSpan.FromHours(9.5);
            // Actual end is 16:15, but this is for safety so we can liquidate at the end
            var rthEnd = TimeSpan.FromHours(16);

            return t.DayOfWeek != DayOfWeek.Saturday &&
                t.DayOfWeek != DayOfWeek.Sunday &&
                t.TimeOfDay >= rthStart &&
                // Need at least 1 bar till the end to do a trade
                t.TimeOfDay <= rthEnd - TimeSpan.FromHours(0.5);
        }

        private void OnDataConsolidated(object sender, TradeBar bar) {
            var consolidator = (TradeBarConsolidator)sender;

            if (!isAllowedTradeTime(Time)) {
                // Liquidate if end of session is near (16:00)
                if (Portfolio.Invested) {
                    Liquidate();
                }

                return;
            }

            if (new Random().NextDouble() > 0.95) {
                if (!Portfolio.Invested) {
                    MarketOrder(currentContract, 1);
                }
            }
            else {
                if (Portfolio.Invested) {
                    Liquidate();
                }
            }
        }
    }
}

Checklist

Martin-Molinero commented 5 years ago

Seems like a duplicate of https://github.com/QuantConnect/Lean/issues/3330 ?

AlexCatarino commented 5 years ago

Duplicate of #3330.