Skip to content

Commit 9385707

Browse files
Merge pull request QuantConnect#4407 from michael-sena/feature-open-interest-future-universe
Add a futures universe selection model that uses open interest
2 parents 86794a9 + a5d7bda commit 9385707

6 files changed

Lines changed: 424 additions & 0 deletions

File tree

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3+
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Collections.Generic;
18+
using System.Linq;
19+
using QuantConnect.Algorithm.Framework.Selection;
20+
using QuantConnect.Data;
21+
using QuantConnect.Interfaces;
22+
using QuantConnect.Securities;
23+
24+
namespace QuantConnect.Algorithm.CSharp
25+
{
26+
/// <summary>
27+
/// Futures framework algorithm that uses open interest to select the active contract.
28+
/// </summary>
29+
/// <meta name="tag" content="regression test" />
30+
/// <meta name="tag" content="futures" />
31+
/// <meta name="tag" content="using data" />
32+
/// <meta name="tag" content="filter selection" />
33+
public class OpenInterestFuturesRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
34+
{
35+
private static readonly HashSet<DateTime> ExpectedExpiryDates = new HashSet<DateTime>
36+
{
37+
new DateTime(2013, 12, 27),
38+
new DateTime(2014, 02, 26)
39+
};
40+
41+
public override void Initialize()
42+
{
43+
UniverseSettings.Resolution = Resolution.Tick;
44+
45+
SetStartDate(2013, 10, 08);
46+
SetEndDate(2013, 10, 11);
47+
SetCash(10000000);
48+
49+
// set framework models
50+
SetUniverseSelection(
51+
new OpenInterestFutureUniverseSelectionModel(
52+
this,
53+
t => new[] {QuantConnect.Symbol.Create(Futures.Metals.Gold, SecurityType.Future, Market.COMEX)},
54+
null,
55+
ExpectedExpiryDates.Count
56+
)
57+
);
58+
}
59+
60+
public override void OnData(Slice slice)
61+
{
62+
if (Transactions.OrdersCount == 0 && slice.HasData)
63+
{
64+
var matched = slice.Keys.Where(s => !ExpectedExpiryDates.Contains(s.ID.Date)).ToList();
65+
if (matched.Count != 0)
66+
{
67+
throw new Exception($"{matched.Count}/{slice.Keys.Count} were unexpected expiry date(s): " + string.Join(", ", matched.Select(x => x.ID.Date)));
68+
}
69+
70+
foreach (var symbol in slice.Keys)
71+
{
72+
MarketOrder(symbol, 1);
73+
}
74+
}
75+
else if (Portfolio.Any(p => p.Value.Invested))
76+
{
77+
Liquidate();
78+
}
79+
}
80+
81+
/// <summary>
82+
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to
83+
/// run this algorithm.
84+
/// </summary>
85+
public bool CanRunLocally { get; } = true;
86+
87+
/// <summary>
88+
/// This is used by the regression test system to indicate which languages this algorithm is written in.
89+
/// </summary>
90+
public Language[] Languages { get; } = {Language.CSharp};
91+
92+
/// <summary>
93+
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
94+
/// </summary>
95+
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
96+
{
97+
{"Total Trades", "4"},
98+
{"Average Win", "0.00%"},
99+
{"Average Loss", "0.00%"},
100+
{"Compounding Annual Return", "0.003%"},
101+
{"Drawdown", "0%"},
102+
{"Expectancy", "0.351"},
103+
{"Net Profit", "0.000%"},
104+
{"Sharpe Ratio", "0"},
105+
{"Probabilistic Sharpe Ratio", "0%"},
106+
{"Loss Rate", "50%"},
107+
{"Win Rate", "50%"},
108+
{"Profit-Loss Ratio", "1.70"},
109+
{"Alpha", "0"},
110+
{"Beta", "0"},
111+
{"Annual Standard Deviation", "0"},
112+
{"Annual Variance", "0"},
113+
{"Information Ratio", "-58.133"},
114+
{"Tracking Error", "0.173"},
115+
{"Treynor Ratio", "0"},
116+
{"Total Fees", "$7.40"},
117+
{"Fitness Score", "0.017"},
118+
{"Kelly Criterion Estimate", "0"},
119+
{"Kelly Criterion Probability Value", "0"},
120+
{"Sortino Ratio", "79228162514264337593543950335"},
121+
{"Return Over Maximum Drawdown", "79228162514264337593543950335"},
122+
{"Portfolio Turnover", "0.017"},
123+
{"Total Insights Generated", "0"},
124+
{"Total Insights Closed", "0"},
125+
{"Total Insights Analysis Completed", "0"},
126+
{"Long Insight Count", "0"},
127+
{"Short Insight Count", "0"},
128+
{"Long/Short Ratio", "100%"},
129+
{"Estimated Monthly Alpha Value", "$0"},
130+
{"Total Accumulated Estimated Alpha Value", "$0"},
131+
{"Mean Population Estimated Insight Value", "$0"},
132+
{"Mean Population Direction", "0%"},
133+
{"Mean Population Magnitude", "0%"},
134+
{"Rolling Averaged Population Direction", "0%"},
135+
{"Rolling Averaged Population Magnitude", "0%"},
136+
{"OrderListHash", "824503313"}
137+
};
138+
}
139+
}

Algorithm.CSharp/QuantConnect.Algorithm.CSharp.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@
158158
<Compile Include="AltData\TiingoNewsAlgorithm.cs" />
159159
<Compile Include="AutomaticIndicatorWarmupDataTypeRegressionAlgorithm.cs" />
160160
<Compile Include="AutomaticIndicatorWarmupRegressionAlgorithm.cs" />
161+
<Compile Include="OpenInterestFuturesRegressionAlgorithm.cs" />
161162
<Compile Include="CustomPartialFillModelAlgorithm.cs" />
162163
<Compile Include="EquityTradeAndQuotesRegressionAlgorithm.cs" />
163164
<Compile Include="BasicTemplateConstituentUniverseAlgorithm.cs" />

Algorithm.Framework/QuantConnect.Algorithm.Framework.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@
145145
<Compile Include="Alphas\ConstantAlphaModel.cs" />
146146
<Compile Include="Alphas\MacdAlphaModel.cs" />
147147
<Compile Include="Selection\FutureUniverseSelectionModel.cs" />
148+
<Compile Include="Selection\OpenInterestFutureUniverseSelectionModel.cs" />
148149
<Compile Include="Selection\OptionUniverseSelectionModel.cs" />
149150
<Compile Include="Selection\ScheduledUniverseSelectionModel.cs" />
150151
<Compile Include="Selection\QC500UniverseSelectionModel.cs" />
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3+
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Collections.Generic;
18+
using System.Linq;
19+
using NodaTime;
20+
using QuantConnect.Data;
21+
using QuantConnect.Data.Market;
22+
using QuantConnect.Interfaces;
23+
using QuantConnect.Securities;
24+
25+
namespace QuantConnect.Algorithm.Framework.Selection
26+
{
27+
/// <summary>
28+
/// Selects contracts in a futures universe, sorted by open interest. This allows the selection to identifiy current
29+
/// active contract.
30+
/// </summary>
31+
public class OpenInterestFutureUniverseSelectionModel : FutureUniverseSelectionModel
32+
{
33+
private readonly int? _chainContractsLookupLimit;
34+
private readonly IAlgorithm _algorithm;
35+
private readonly int? _resultsLimit;
36+
private readonly MarketHoursDatabase _marketHoursDatabase;
37+
38+
/// <summary>
39+
/// Creates a new instance of <see cref="OpenInterestFutureUniverseSelectionModel" />
40+
/// </summary>
41+
/// <param name="algorithm">Algorithm</param>
42+
/// <param name="futureChainSymbolSelector">Selects symbols from the provided future chain</param>
43+
/// <param name="chainContractsLookupLimit">Limit on how many contracts to query for open interest</param>
44+
/// <param name="resultsLimit">Limit on how many contracts will be part of the universe</param>
45+
public OpenInterestFutureUniverseSelectionModel(IAlgorithm algorithm, Func<DateTime, IEnumerable<Symbol>> futureChainSymbolSelector, int? chainContractsLookupLimit = 6,
46+
int? resultsLimit = 1) : base(TimeSpan.FromDays(1), futureChainSymbolSelector)
47+
{
48+
_marketHoursDatabase = MarketHoursDatabase.FromDataFolder();
49+
if (algorithm == null)
50+
{
51+
throw new ArgumentNullException(nameof(algorithm));
52+
}
53+
54+
_algorithm = algorithm;
55+
_resultsLimit = resultsLimit;
56+
_chainContractsLookupLimit = chainContractsLookupLimit;
57+
}
58+
59+
/// <summary>
60+
/// Defines the future chain universe filter
61+
/// </summary>
62+
protected override FutureFilterUniverse Filter(FutureFilterUniverse filter)
63+
{
64+
return filter.Contracts(FilterByOpenInterest(filter.ToDictionary(x => x, x => _marketHoursDatabase.GetEntry(x.ID.Market, x, x.ID.SecurityType).ExchangeHours)));
65+
}
66+
67+
/// <summary>
68+
/// Filters a set of contracts based on open interest.
69+
/// </summary>
70+
/// <param name="contracts">Contracts to filter</param>
71+
/// <returns>Filtered set</returns>
72+
public IEnumerable<Symbol> FilterByOpenInterest(IReadOnlyDictionary<Symbol, SecurityExchangeHours> contracts)
73+
{
74+
var symbols = new List<Symbol>(_chainContractsLookupLimit.HasValue ? contracts.Keys.OrderBy(x => x.ID.Date).Take(_chainContractsLookupLimit.Value) : contracts.Keys);
75+
var openInterest = symbols.GroupBy(x => contracts[x]).SelectMany(g => GetOpenInterest(g.Key, g.Select(i => i))).ToDictionary(x => x.Key, x => x.Value);
76+
77+
if (openInterest.Count == 0)
78+
{
79+
_algorithm.Error(
80+
$"{nameof(OpenInterestFutureUniverseSelectionModel)}.{nameof(FilterByOpenInterest)}: Failed to get historical open interest, no symbol will be selected."
81+
);
82+
return Enumerable.Empty<Symbol>();
83+
}
84+
85+
var filtered = openInterest.OrderByDescending(x => x.Value).ThenBy(x => x.Key.ID.Date).Select(x => x.Key);
86+
if (_resultsLimit.HasValue)
87+
{
88+
filtered = filtered.Take(_resultsLimit.Value);
89+
}
90+
91+
return filtered;
92+
}
93+
94+
private Dictionary<Symbol, decimal> GetOpenInterest(SecurityExchangeHours exchangeHours, IEnumerable<Symbol> symbols)
95+
{
96+
var current = _algorithm.UtcTime;
97+
var endTime = Instant.FromDateTimeUtc(_algorithm.UtcTime).InZone(exchangeHours.TimeZone).ToDateTimeUnspecified();
98+
var previousDay = Time.GetStartTimeForTradeBars(exchangeHours, endTime, Time.OneDay, 1, true);
99+
var requests = symbols.Select(
100+
symbol => new HistoryRequest(
101+
previousDay,
102+
current,
103+
typeof(Tick),
104+
symbol,
105+
Resolution.Tick,
106+
exchangeHours,
107+
exchangeHours.TimeZone,
108+
null,
109+
true,
110+
false,
111+
DataNormalizationMode.Raw,
112+
TickType.OpenInterest
113+
)
114+
)
115+
.ToArray();
116+
return _algorithm.HistoryProvider.GetHistory(requests, exchangeHours.TimeZone)
117+
.Where(s => s.HasData && s.Ticks.Keys.Count > 0)
118+
.SelectMany(s => s.Ticks.Select(x => new Tuple<Symbol, Tick>(x.Key, x.Value.LastOrDefault())))
119+
.GroupBy(x => x.Item1)
120+
.ToDictionary(x => x.Key, x => x.OrderByDescending(i => i.Item2.Time).LastOrDefault().Item2.Value);
121+
}
122+
}
123+
}

0 commit comments

Comments
 (0)