| 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
}
}