Skip to content

Commit 55b7cb7

Browse files
committed
Add universe selection regression algorithm
1 parent 521b81a commit 55b7cb7

1 file changed

Lines changed: 201 additions & 0 deletions

File tree

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
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.Data.UniverseSelection;
20+
using QuantConnect.Interfaces;
21+
using QuantConnect.Securities;
22+
23+
namespace QuantConnect.Algorithm.CSharp
24+
{
25+
/// <summary>
26+
/// Regression algorithm testing the behavior of the algorithm when a security is removed and re-added.
27+
/// It asserts that the securities are marked as non-tradable when removed and that they are tradable when re-added.
28+
/// It also asserts that the algorithm receives the correct security changed events for the added and removed securities.
29+
///
30+
/// Additionally, it tests that the security is initialized after every addition, and no more.
31+
///
32+
/// This specific algorithm tests this behavior for securities selected, deselected and re-selected from universes.
33+
/// </summary>
34+
public class SecurityInitializationOnReAdditionForUniverseSelectionRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
35+
{
36+
private List<Symbol> _symbolsToSelect;
37+
private List<Symbol> _selectedSymbols;
38+
private int _selectionsCount;
39+
40+
private Dictionary<Security, int> _securityInializationCounts = new();
41+
42+
public override void Initialize()
43+
{
44+
SetStartDate(2014, 03, 24);
45+
SetEndDate(2014, 04, 07);
46+
SetCash(100000);
47+
48+
UniverseSettings.Resolution = Resolution.Daily;
49+
50+
var seeder = new FuncSecuritySeeder((security) =>
51+
{
52+
if (!_securityInializationCounts.TryGetValue(security, out var count))
53+
{
54+
count = 0;
55+
}
56+
_securityInializationCounts[security] = count + 1;
57+
58+
Debug($"[{Time}] Seeding {security.Symbol}");
59+
return GetLastKnownPrices(security);
60+
});
61+
62+
SetSecurityInitializer(security => seeder.SeedSecurity(security));
63+
64+
_symbolsToSelect = new List<Symbol>()
65+
{
66+
QuantConnect.Symbol.Create("SPY", SecurityType.Equity, Market.USA),
67+
QuantConnect.Symbol.Create("IWM", SecurityType.Equity, Market.USA),
68+
QuantConnect.Symbol.Create("QQQ", SecurityType.Equity, Market.USA),
69+
QuantConnect.Symbol.Create("AIG", SecurityType.Equity, Market.USA),
70+
QuantConnect.Symbol.Create("BAC", SecurityType.Equity, Market.USA),
71+
QuantConnect.Symbol.Create("IBM", SecurityType.Equity, Market.USA),
72+
};
73+
74+
AddUniverse("MyUniverse", Resolution.Daily, SelectionFunction);
75+
}
76+
77+
private IEnumerable<string> SelectionFunction(DateTime dateTime)
78+
{
79+
_securityInializationCounts.Clear();
80+
_selectionsCount++;
81+
82+
_selectedSymbols = _symbolsToSelect.Skip(dateTime.Day % 2 == 0 ? 0 : 3).Take(3).ToList();
83+
return _selectedSymbols.Select(x => x.Value);
84+
}
85+
86+
public override void OnSecuritiesChanged(SecurityChanges changes)
87+
{
88+
foreach (var security in changes.AddedSecurities)
89+
{
90+
if (!security.IsTradable)
91+
{
92+
throw new RegressionTestException($"Expected the security to be tradable. Symbol: {security.Symbol}");
93+
}
94+
}
95+
96+
foreach (var security in changes.RemovedSecurities)
97+
{
98+
if (security.IsTradable)
99+
{
100+
throw new RegressionTestException($"Expected the security to be not tradable. Symbol: {security.Symbol}");
101+
}
102+
}
103+
104+
if (changes.AddedSecurities.Count != _selectedSymbols.Count ||
105+
changes.AddedSecurities.Any(x => !_selectedSymbols.Contains(x.Symbol)))
106+
{
107+
throw new RegressionTestException($"Expected the added securities to be the selected ones. " +
108+
$"Added: {string.Join(", ", changes.AddedSecurities.Select(x => x.Symbol.Value))}, " +
109+
$"Selected: {string.Join(", ", _selectedSymbols)}");
110+
}
111+
112+
if (changes.AddedSecurities.Count != _securityInializationCounts.Count ||
113+
changes.AddedSecurities.Any(x => !_securityInializationCounts.TryGetValue(x, out var count) || count != 1))
114+
{
115+
throw new RegressionTestException($"Expected all contracts to be initialized. " +
116+
$"Added: {string.Join(", ", changes.AddedSecurities.Select(x => x.Symbol.Value))}, " +
117+
$"Initialized: {string.Join(", ", _securityInializationCounts.Select(x => $"{x.Key.Symbol.Value} - {x.Value}"))}");
118+
}
119+
120+
if (changes.RemovedSecurities.Count > 0)
121+
{
122+
var expectedDeselectedSymbols = _symbolsToSelect.Where(x => !_selectedSymbols.Contains(x)).ToList();
123+
124+
if (changes.RemovedSecurities.Count != expectedDeselectedSymbols.Count ||
125+
changes.RemovedSecurities.Any(x => !expectedDeselectedSymbols.Contains(x.Symbol)))
126+
{
127+
throw new RegressionTestException($"Expected the removed securities to be the deselected ones. " +
128+
$"Removed: {string.Join(", ", changes.RemovedSecurities.Select(x => x.Symbol.Value))}, " +
129+
$"Deselected: {string.Join(", ", expectedDeselectedSymbols)}");
130+
}
131+
}
132+
}
133+
134+
public override void OnEndOfAlgorithm()
135+
{
136+
if (_selectionsCount < 2)
137+
{
138+
throw new RegressionTestException("Expected at least two selections");
139+
}
140+
}
141+
142+
/// <summary>
143+
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
144+
/// </summary>
145+
public bool CanRunLocally { get; } = true;
146+
147+
/// <summary>
148+
/// This is used by the regression test system to indicate which languages this algorithm is written in.
149+
/// </summary>
150+
public List<Language> Languages { get; } = new() { Language.CSharp };
151+
152+
/// <summary>
153+
/// Data Points count of all timeslices of algorithm
154+
/// </summary>
155+
public long DataPoints => 128;
156+
157+
/// <summary>
158+
/// Data Points count of the algorithm history
159+
/// </summary>
160+
public int AlgorithmHistoryDataPoints => 150;
161+
162+
/// <summary>
163+
/// Final status of the algorithm
164+
/// </summary>
165+
public AlgorithmStatus AlgorithmStatus => AlgorithmStatus.Completed;
166+
167+
/// <summary>
168+
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
169+
/// </summary>
170+
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
171+
{
172+
{"Total Orders", "0"},
173+
{"Average Win", "0%"},
174+
{"Average Loss", "0%"},
175+
{"Compounding Annual Return", "0%"},
176+
{"Drawdown", "0%"},
177+
{"Expectancy", "0"},
178+
{"Start Equity", "100000"},
179+
{"End Equity", "100000"},
180+
{"Net Profit", "0%"},
181+
{"Sharpe Ratio", "0"},
182+
{"Sortino Ratio", "0"},
183+
{"Probabilistic Sharpe Ratio", "0%"},
184+
{"Loss Rate", "0%"},
185+
{"Win Rate", "0%"},
186+
{"Profit-Loss Ratio", "0"},
187+
{"Alpha", "0"},
188+
{"Beta", "0"},
189+
{"Annual Standard Deviation", "0"},
190+
{"Annual Variance", "0"},
191+
{"Information Ratio", "0.97"},
192+
{"Tracking Error", "0.097"},
193+
{"Treynor Ratio", "0"},
194+
{"Total Fees", "$0.00"},
195+
{"Estimated Strategy Capacity", "$0"},
196+
{"Lowest Capacity Asset", ""},
197+
{"Portfolio Turnover", "0%"},
198+
{"OrderListHash", "d41d8cd98f00b204e9800998ecf8427e"}
199+
};
200+
}
201+
}

0 commit comments

Comments
 (0)