/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Linq;
using QuantConnect.Data;
using QuantConnect.Indicators;
using QuantConnect.Interfaces;
using QuantConnect.Securities;
using System.Collections.Generic;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Algorithm.Framework.Execution;
using QuantConnect.Algorithm.Framework.Portfolio;
using QuantConnect.Algorithm.Framework.Selection;
using QuantConnect.Securities.Future;
namespace QuantConnect.Algorithm.CSharp
{
///
/// Regression algorithm asserting the behavior of using universe selection with futures
///
public class FuturesFrameworkRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
{
private static Symbol _es = QuantConnect.Symbol.Create(Futures.Indices.SP500EMini, SecurityType.Future, Market.CME);
private static Symbol _gold = QuantConnect.Symbol.Create(Futures.Metals.Gold, SecurityType.Future, Market.COMEX);
private readonly Dictionary _addedCanonical = new() { { _gold, false }, { _es, false } };
private readonly Dictionary _removedCanonical = new() { { _gold, false }, { _es, false } };
private readonly Dictionary _canonicalData = new() { { _gold, null }, { _es, null } };
public override void Initialize()
{
UniverseSettings.Resolution = Resolution.Minute;
SetStartDate(2013, 10, 07);
SetEndDate(2013, 10, 11);
SetUniverseSelection(new FutureUniverseSelectionModel(QuantConnect.Time.OneDay, SelectFutureChainSymbols));
SetAlpha(new ConstantAlphaModel(InsightType.Price, InsightDirection.Up, TimeSpan.FromDays(1)));
SetPortfolioConstruction(new EqualWeightingPortfolioConstructionModel());
SetExecution(new ImmediateExecutionModel());
}
private static IEnumerable SelectFutureChainSymbols(DateTime utcTime)
{
var newYorkTime = utcTime.ConvertFromUtc(TimeZones.NewYork);
if (newYorkTime.Date < new DateTime(2013, 10, 09))
{
yield return _es;
}
if (newYorkTime.Date >= new DateTime(2013, 10, 09))
{
yield return _gold;
}
}
public override void OnData(Slice slice)
{
var future = _es;
if (Time.Date >= new DateTime(2013, 10, 09))
{
future = _gold;
}
var continuous = Securities[future];
if (continuous.Price == Securities[(continuous as Future).Mapped].Price)
{
// prices should never match because we are using the default backwards adjusted mode, they would match if we used raw mode
throw new RegressionTestException($"Unexpected continuous future price {continuous.Price}");
}
}
public override void OnSecuritiesChanged(SecurityChanges changes)
{
foreach (var added in changes.AddedSecurities)
{
if (added.Symbol.IsCanonical())
{
_addedCanonical[added.Symbol] = true;
_canonicalData[added.Symbol] = SMA(added.Symbol, 10);
}
}
foreach (var removed in changes.RemovedSecurities)
{
if (removed.Symbol.IsCanonical())
{
_removedCanonical[removed.Symbol] = true;
}
}
var canonicals = changes.AddedSecurities.Select(x => x.Symbol.Canonical).ToHashSet();
var nonCanonicals = changes.AddedSecurities.Where(x => !x.Symbol.IsCanonical()).ToList();
foreach (var subscriptions in SubscriptionManager.Subscriptions.Where(x => canonicals.Contains(x.Symbol.Canonical)).GroupBy(x => x.Symbol.Canonical))
{
// trade & quote for canonical + contract chain (universe data)
if (subscriptions.Count(x => x.Symbol.IsCanonical()) != canonicals.Count * 3)
{
throw new RegressionTestException($"Unexpected canonical subscription count {subscriptions.Count(x => x.Symbol.IsCanonical())}");
}
// trade and quote for non canonicals
if (subscriptions.Count(x => !x.Symbol.IsCanonical()) != nonCanonicals.Count * 2)
{
throw new RegressionTestException($"Unexpected non canonical subscription count {subscriptions.Count(x => !x.Symbol.IsCanonical())}");
}
}
var internalSubscriptions = SubscriptionManager.SubscriptionDataConfigService.GetSubscriptionDataConfigs(includeInternalConfigs: true)
.Where(x => x.SecurityType == SecurityType.Future && x.IsInternalFeed && canonicals.Contains(x.Symbol.Canonical)).ToList();
// an open interest subscription for each + trade and quote for the currently mapped continuous future
if (internalSubscriptions.Count != (nonCanonicals.Count + canonicals.Count + canonicals.Count * 2))
{
throw new RegressionTestException($"Unexpected internal subscription count {internalSubscriptions.Count}");
}
// we expect a single continuous universe at the time
var universeSubscriptions = SubscriptionManager.Subscriptions.Count(x => x.Symbol.ID.Symbol.Contains("QC-UNIVERSE-CONTINUOUS"));
if (universeSubscriptions != 1)
{
throw new RegressionTestException($"Unexpected universe subscription count {universeSubscriptions}");
}
// we expect a single canonical at the time
var canonicalSubscriptions = SubscriptionManager.Subscriptions.Where(x => !x.Symbol.ID.Symbol.Contains("QC-UNIVERSE-CONTINUOUS") && x.Symbol.IsCanonical())
.Select(x => x.Symbol.Canonical).ToHashSet();
if (canonicalSubscriptions.Count != 1)
{
throw new RegressionTestException($"Unexpected universe subscription count {universeSubscriptions}");
}
}
public override void OnEndOfAlgorithm()
{
foreach (var canonical in _addedCanonical)
{
if (!canonical.Value)
{
throw new RegressionTestException($"Canonical {canonical} was not added!");
}
}
foreach (var canonical in _removedCanonical)
{
if (canonical.Key.ID.Symbol == "ES" && !canonical.Value || canonical.Key.ID.Symbol == "GC" && canonical.Value)
{
throw new RegressionTestException($"Canonical {canonical} was not removed!");
}
}
foreach (var canonical in _canonicalData)
{
if (canonical.Value == null || !canonical.Value.IsReady)
{
throw new RegressionTestException($"Canonical {canonical} emitted no data!");
}
}
if (SubscriptionManager.Subscriptions.Any(x => x.Symbol.ID.Symbol == "ES"))
{
throw new RegressionTestException($"There should be no ES subscription!");
}
}
///
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
///
public virtual bool CanRunLocally { get; } = true;
///
/// This is used by the regression test system to indicate which languages this algorithm is written in.
///
public virtual List Languages { get; } = new() { Language.CSharp };
///
/// Data Points count of all timeslices of algorithm
///
public virtual long DataPoints => 101119;
///
/// Data Points count of the algorithm history
///
public virtual int AlgorithmHistoryDataPoints => 0;
///
/// Final status of the algorithm
///
public AlgorithmStatus AlgorithmStatus => AlgorithmStatus.Completed;
///
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
///
public virtual Dictionary ExpectedStatistics => new Dictionary
{
{"Total Orders", "10"},
{"Average Win", "0%"},
{"Average Loss", "-4.59%"},
{"Compounding Annual Return", "-100.000%"},
{"Drawdown", "33.200%"},
{"Expectancy", "-1"},
{"Start Equity", "100000"},
{"End Equity", "79014"},
{"Net Profit", "-20.986%"},
{"Sharpe Ratio", "-0.537"},
{"Sortino Ratio", "0"},
{"Probabilistic Sharpe Ratio", "18.566%"},
{"Loss Rate", "100%"},
{"Win Rate", "0%"},
{"Profit-Loss Ratio", "0"},
{"Alpha", "-11.401"},
{"Beta", "5.262"},
{"Annual Standard Deviation", "1.875"},
{"Annual Variance", "3.515"},
{"Information Ratio", "-1.71"},
{"Tracking Error", "1.744"},
{"Treynor Ratio", "-0.191"},
{"Total Fees", "$86.00"},
{"Estimated Strategy Capacity", "$410000.00"},
{"Lowest Capacity Asset", "ES VRJST036ZY0X"},
{"Portfolio Turnover", "766.37%"},
{"Drawdown Recovery", "0"},
{"OrderListHash", "97cfbcf973995ceb72b937f0f4684f88"}
};
}
}