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 ConsolidatorManager _consolidatorManager;
#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);
SetCash(100000);
AddFuture(Futures.Indices.SP500EMini, Resolution.Minute)
.SetFilter(TimeSpan.Zero, TimeSpan.FromDays(240));
// optional, helps reduce margin calls
Settings.FreePortfolioValuePercentage = 0.3m;
}
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}");
}
override public void OnData(Slice slice)
{
if (!_areFuturesInitialized)
{
InitializeFutures(slice);
return;
}
if (_triggerRollover)
SetActiveContractAndRollover(slice);
// custom entry/exit ("alpha") logic goes here
// ...
}
public void OnDay(object sender, TradeBar bar)
{
Log($"{bar.Time}: Trading contract {bar.Symbol.Value}, Open is {bar.Open}, Close is {bar.Close}");
}
#region Managed Futures Rollover
private void SetActiveContractAndRollover(Slice slice)
{
SubscriptionManager.RemoveConsolidator(_activeContract.Symbol, _consolidatorManager.TradeBarConsolidator);
Log($"Subscription REMOVED from consolidator {nameof(_consolidatorManager.TradeBarConsolidator)} 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
// using a QC Consolidator to get Daily bars since Futures can't use Resolution.Daily
_consolidatorManager = new ConsolidatorManager();
_consolidatorManager.TradeBarConsolidator = new TradeBarConsolidator(TimeSpan.FromDays(1));
_consolidatorManager.TradeBarConsolidator.DataConsolidated += OnDay;
SubscriptionManager.AddConsolidator(_activeContract.Symbol, _consolidatorManager.TradeBarConsolidator);
Log($"Subscription ADDED for consolidator {nameof(_consolidatorManager.TradeBarConsolidator)} 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
}
public class ConsolidatorManager
{
public TradeBarConsolidator TradeBarConsolidator;
}
}