Overall Statistics |
Total Trades 3 Average Win 160.21% Average Loss 0% Compounding Annual Return 15709.764% Drawdown 34.800% Expectancy 0 Net Profit 253.162% Sharpe Ratio 91.875 Probabilistic Sharpe Ratio 95.835% Loss Rate 0% Win Rate 100% Profit-Loss Ratio 0 Alpha 115.617 Beta 9.828 Annual Standard Deviation 1.319 Annual Variance 1.74 Information Ratio 99.455 Tracking Error 1.213 Treynor Ratio 12.331 Total Fees $99.90 |
namespace QuantConnect.Algorithm.CSharp { public class _20201223_AF_es_mean_reverting_A : QCAlgorithm { // Parameters to optimize in walk-forwards testing private int _LookbackDays = 5; private int _ExitDays = 3; // Other parameters private int _DaysBeforeExpiryToRollover = 1; private TradeBarConsolidator _dailyConsolidator; #region Managed Rollover Fields private FuturesContract _activeContract; private bool _areFuturesInitialized = false; private bool _triggerRollover = false; #endregion Managed Rollover Fields public override void Initialize() { SetStartDate(2019, 1, 1); SetEndDate(2019, 4, 1); SetCash(100000); AddFuture(Futures.Indices.SP500EMini, Resolution.Minute) .SetFilter(TimeSpan.Zero, TimeSpan.FromDays(240)); // optional, helps reduce margin calls Settings.FreePortfolioValuePercentage = 0.3m; // using a QC Consolidator to get Daily bars since Futures can't use Resolution.Daily _dailyConsolidator = new TradeBarConsolidator(TimeSpan.FromDays(1)); _dailyConsolidator.DataConsolidated += OnDay; //register the consolidator for data. } private void InitializeFutures(Slice slice) { // QC API can't initialize futures in Initialize() because slice.FuturesChains only exists in OnData() // https://www.quantconnect.com/forum/discussion/10185/how-to-initialize-futures-using-history-in-the-initialize-method/p1/comment-29314 SetActiveContract(slice); ScheduleRolloverTrigger(); _areFuturesInitialized = true; // custom entry/exit ("alpha") logic goes here // TODO - History would be nice to use for initialization, but I'm not sure if it works with consolidators? // you could make your own consolidator for history, but that won't have a significant impact in backtesting // versus just using a warm-up period... //var bars = History<TradeBar>(_activeContract.Symbol, TimeSpan.FromMinutes(_LookbackDays), Resolution.Minute); //Log($"{bars.Count()}"); //foreach (var bar in bars) //{ // Log($"{bar.Time}: HIGH IS {bar.High}, LOW IS {bar.Low}"); //} SetHoldings(_activeContract.Symbol, 1); Log($"Futures initialized, new contract purchased: {_activeContract.Symbol.Value}"); } public void OnDay(object sender, TradeBar bar) { Log($"{bar.Time}: Trading contract {bar.Symbol.Value}, Open is {bar.Open}, Close is {bar.Close}"); } override public void OnData(Slice slice) { if (!_areFuturesInitialized) { InitializeFutures(slice); return; } if (_triggerRollover) SetActiveContractAndRollover(slice); // custom entry/exit ("alpha") logic goes here // ... } #region Managed Futures Rollover private void SetActiveContractAndRollover(Slice slice) { // SubscriptionManager.RemoveConsolidator(_activeContract.Symbol, _dailyConsolidator); // Log($"Subscription REMOVED from consolidator {nameof(_dailyConsolidator)} for contract {_activeContract.Symbol.Value}"); SetActiveContract(slice); Rollover(); _triggerRollover = false; ScheduleRolloverTrigger(); } private void SetActiveContract(Slice slice) { if (slice.FutureChains.Count == 0) { var exceptionMessage = $"ERROR - No contracts in var {nameof(slice.FutureChains)}"; Log(exceptionMessage); throw new Exception(exceptionMessage); } var futureChain = slice.FutureChains.First(); var recentContracts = futureChain.Value .Where(x => (x.Expiry - Time.Date).TotalDays > _DaysBeforeExpiryToRollover + 1) .OrderBy(x => (x.Expiry - Time.Date).TotalDays) .ToList(); if (recentContracts.Count == 0) { _activeContract = null; Liquidate(); var exceptionMessage = $"ERROR - No contracts in var {nameof(recentContracts)}, no active contract assigned, no scheduled rollover. Liquidating portfolio."; Log(exceptionMessage); throw new Exception(exceptionMessage); } Log($"list of sorted recent contracts: {string.Join(", ", recentContracts.Select(x => x.Symbol.Value).ToList())}"); var frontContract = recentContracts.First(); _activeContract = frontContract; Log($"New active contract set to {_activeContract.Symbol.Value}"); // custom entry/exit ("alpha") logic goes here // SubscriptionManager.AddConsolidator(_activeContract.Symbol, _dailyConsolidator); // Log($"Subscription ADDED for consolidator {nameof(_dailyConsolidator)} for contract {_activeContract.Symbol.Value}"); } private void Rollover() { if (Portfolio.Invested) { Liquidate(); Log($"Portfolio liquidated."); SetHoldings(_activeContract.Symbol, 1); Log($"New contract purchased: {_activeContract.Symbol.Value}"); } } private void ScheduleRolloverTrigger() { Action callback = () => { _triggerRollover = true; }; Schedule.On(DateRules.On(_activeContract.Expiry.AddDays(-1 * _DaysBeforeExpiryToRollover)), TimeRules.BeforeMarketClose(_activeContract.Symbol, 120), callback); Log($"Contract {_activeContract.Symbol.Value} has will have rollover triggered on: {_activeContract.Expiry.AddDays(-1 * _DaysBeforeExpiryToRollover).ToShortDateString()}"); } #endregion Managed Futures Rollover } }