Skip to content

Commit 60e88cc

Browse files
committed
Add algorithm naming and tagging unit tests
1 parent 1640ab7 commit 60e88cc

File tree

2 files changed

+343
-7
lines changed

2 files changed

+343
-7
lines changed

Algorithm/QCAlgorithm.cs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,15 @@ public partial class QCAlgorithm : MarshalByRefObject, IAlgorithm
8181
const string StatisticsTag = "Statistics";
8282
#endregion
8383

84-
const int MaxNameAndTagsLength = 200;
84+
/// <summary>
85+
/// Maximum length of the name or tags of a backtest
86+
/// </summary>
87+
protected const int MaxNameAndTagsLength = 200;
88+
89+
/// <summary>
90+
/// Maximum number of tags allowed for a backtest
91+
/// </summary>
92+
protected const int MaxTagsCount = 100;
8593

8694
private readonly TimeKeeper _timeKeeper;
8795
private LocalTimeKeeper _localTimeKeeper;
@@ -514,13 +522,15 @@ public HashSet<string> Tags
514522
return;
515523
}
516524

517-
if (value.Count > 20 && !_tagsCollectionTruncatedLogSent)
525+
var tags = value.Where(x => !string.IsNullOrEmpty(x?.Trim())).ToList();
526+
527+
if (tags.Count > MaxTagsCount && !_tagsCollectionTruncatedLogSent)
518528
{
519-
Log("Warning: The tags collection cannot contain more than 20 items. It will be truncated.");
529+
Log($"Warning: The tags collection cannot contain more than {MaxTagsCount} items. It will be truncated.");
520530
_tagsCollectionTruncatedLogSent = true;
521531
}
522532

523-
_tags = value.Take(20).ToHashSet(tag => tag.Truncate(MaxNameAndTagsLength));
533+
_tags = tags.Take(MaxTagsCount).ToHashSet(tag => tag.Truncate(MaxNameAndTagsLength));
524534
if (_locked)
525535
{
526536
TagsUpdated?.Invoke(this, Tags);
@@ -1490,13 +1500,13 @@ public void SetName(string name)
14901500
/// <param name="tag">The tag to add</param>
14911501
public void AddTag(string tag)
14921502
{
1493-
if (!string.IsNullOrEmpty(tag))
1503+
if (!string.IsNullOrEmpty(tag?.Trim()))
14941504
{
1495-
if (Tags.Count >= 20)
1505+
if (Tags.Count >= MaxTagsCount)
14961506
{
14971507
if (!_tagsLimitReachedLogSent)
14981508
{
1499-
Log($"Warning: AddTag({tag}): Unable to add tag. Tags are limited to a maximum of 20.");
1509+
Log($"Warning: AddTag({tag}): Unable to add tag. Tags are limited to a maximum of {MaxTagsCount}.");
15001510
_tagsLimitReachedLogSent = true;
15011511
}
15021512
return;
Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
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 NUnit.Framework;
20+
using QuantConnect.Algorithm;
21+
using QuantConnect.Util;
22+
23+
namespace QuantConnect.Tests.Algorithm
24+
{
25+
[TestFixture, Parallelizable(ParallelScope.All)]
26+
public class AlgorithmNamingTest
27+
{
28+
#region Naming tests
29+
30+
private static void SetAlgorithmNameUsingPropertySetter(QCAlgorithm algorithm, string name)
31+
{
32+
algorithm.Name = name;
33+
}
34+
35+
private static void SetAlgorithmNameUsingSetMethod(QCAlgorithm algorithm, string name)
36+
{
37+
algorithm.SetName(name);
38+
}
39+
40+
private static void TestSettingAlgorithmName(Action<QCAlgorithm, string> setName)
41+
{
42+
var algorithm = new QCAlgorithm();
43+
var name = "TestName";
44+
setName(algorithm, name);
45+
Assert.AreEqual(name, algorithm.Name);
46+
}
47+
48+
[Test]
49+
public void AlgorithmNameIsSetUsingPropertySetter()
50+
{
51+
TestSettingAlgorithmName(SetAlgorithmNameUsingPropertySetter);
52+
}
53+
54+
[Test]
55+
public void AlgorithmNameIsSetUsingSetMethod()
56+
{
57+
TestSettingAlgorithmName(SetAlgorithmNameUsingSetMethod);
58+
}
59+
60+
private static void TestSettingLongAlgorithmName(Action<QCAlgorithm, string> setName)
61+
{
62+
var algorithm = new QCAlgorithm();
63+
var name = new string('a', MaxNameAndTagsLength + 1);
64+
setName(algorithm, name);
65+
Assert.AreEqual(name.Substring(0, MaxNameAndTagsLength), algorithm.Name);
66+
}
67+
68+
[Test]
69+
public void AlgorithmNameTruncatedUsingPropertySetter()
70+
{
71+
TestSettingLongAlgorithmName(SetAlgorithmNameUsingPropertySetter);
72+
}
73+
74+
[Test]
75+
public void AlgorithmNameTruncatedUsingSetMethod()
76+
{
77+
TestSettingLongAlgorithmName(SetAlgorithmNameUsingSetMethod);
78+
}
79+
80+
private static void TestSettingAlgorithmNameAfterInitialization(Action<QCAlgorithm, string> setName)
81+
{
82+
var algorithm = new QCAlgorithm();
83+
algorithm.SetLocked();
84+
Assert.Throws<InvalidOperationException>(() => setName(algorithm, "TestName"));
85+
}
86+
87+
[Test]
88+
public void AlgorithmNameCannotBeSetAfterInitializationUsingPropertySetter()
89+
{
90+
TestSettingAlgorithmNameAfterInitialization(SetAlgorithmNameUsingPropertySetter);
91+
}
92+
93+
[Test]
94+
public void AlgorithmNameCannotBeSetAfterInitializationUsingSetMethod()
95+
{
96+
TestSettingAlgorithmNameAfterInitialization(SetAlgorithmNameUsingSetMethod);
97+
}
98+
99+
#endregion
100+
101+
#region Tagging tests
102+
103+
private static void SetAlgorithmTagsUsingPropertySetter(QCAlgorithm algorithm, string[] tags)
104+
{
105+
algorithm.Tags = tags?.ToHashSet();
106+
}
107+
108+
private static void SetAlgorithmTagsUsingSetMethod(QCAlgorithm algorithm, string[] tags)
109+
{
110+
algorithm.SetTags(tags?.ToHashSet());
111+
}
112+
113+
private static void TestAlgorithmTagsAreSet(Action<QCAlgorithm, string[]> setTags, QCAlgorithm algorithm, string[] tags)
114+
{
115+
setTags(algorithm, tags);
116+
CollectionAssert.AreEquivalent(tags, algorithm.Tags);
117+
}
118+
119+
private static void TestSettingAlgorithmTags(Action<QCAlgorithm, string[]> setTags)
120+
{
121+
var algorithm = new QCAlgorithm();
122+
var tags = new[] { "tag1", "tag2" };
123+
TestAlgorithmTagsAreSet(setTags, algorithm, tags);
124+
}
125+
126+
[Test]
127+
public void AlgorithmTagsAreSetUsingPropertySetter()
128+
{
129+
TestSettingAlgorithmTags(SetAlgorithmTagsUsingPropertySetter);
130+
}
131+
132+
[Test]
133+
public void AlgorithmTagsAreSetUsingSetMethod()
134+
{
135+
TestSettingAlgorithmTags(SetAlgorithmTagsUsingSetMethod);
136+
}
137+
138+
private static void TestOverridingAlgorithmTags(Action<QCAlgorithm, string[]> setTags)
139+
{
140+
var algorithm = new QCAlgorithm();
141+
142+
var tags = new[] { "tag1", "tag2" };
143+
TestAlgorithmTagsAreSet(setTags, algorithm, tags);
144+
145+
var newTags = new[] { "tag3", "tag4" };
146+
TestAlgorithmTagsAreSet(setTags, algorithm, newTags);
147+
}
148+
149+
[Test]
150+
public void AlgorithmTagsCanBeOverriddenUsingPropertySetter()
151+
{
152+
TestOverridingAlgorithmTags(SetAlgorithmTagsUsingPropertySetter);
153+
}
154+
155+
[Test]
156+
public void AlgorithmTagsCanBeOverriddenUsingSetMethod()
157+
{
158+
TestOverridingAlgorithmTags(SetAlgorithmTagsUsingSetMethod);
159+
}
160+
161+
public static void TestSettingNullAlgorithmTags(Action<QCAlgorithm, string[]> setTags)
162+
{
163+
var algorithm = new QCAlgorithm();
164+
setTags(algorithm, null);
165+
CollectionAssert.IsEmpty(algorithm.Tags);
166+
167+
var tags = new[] { "tag1", "tag2" };
168+
setTags(algorithm, tags);
169+
setTags(algorithm, null);
170+
CollectionAssert.AreEquivalent(tags, algorithm.Tags);
171+
}
172+
173+
[Test]
174+
public void AlgorithmTagsAreLeftUntouchedWhenTryingToSetNullUsingPropertySetter()
175+
{
176+
TestSettingNullAlgorithmTags(SetAlgorithmTagsUsingPropertySetter);
177+
}
178+
179+
[Test]
180+
public void AlgorithmTagsAreLeftUntouchedWhenTryingToSetNullUsingSetMethod()
181+
{
182+
TestSettingNullAlgorithmTags(SetAlgorithmTagsUsingSetMethod);
183+
}
184+
185+
private static void TestSettingTooManyAlgorithmTags(Action<QCAlgorithm, string[]> setTags)
186+
{
187+
var algorithm = new QCAlgorithm();
188+
var tags = Enumerable.Range(0, MaxTagsCount + 1).Select(i => $"tag{i}").ToArray();
189+
setTags(algorithm, tags);
190+
CollectionAssert.AreEquivalent(tags.ToHashSet().Take(MaxTagsCount), algorithm.Tags);
191+
}
192+
193+
[Test]
194+
public void AlgorithmTagsCollectionIsTruncatedWhenSettingTooManyUsingPropertySetter()
195+
{
196+
TestSettingTooManyAlgorithmTags(SetAlgorithmTagsUsingPropertySetter);
197+
}
198+
199+
[Test]
200+
public void AlgorithmTagsCollectionIsTruncatedWhenSettingTooManyUsingSetMethod()
201+
{
202+
TestSettingTooManyAlgorithmTags(SetAlgorithmTagsUsingSetMethod);
203+
}
204+
205+
private static void TestSettingTagsAreTruncatedWhenTooLong(Action<QCAlgorithm, string[]> setTags)
206+
{
207+
var algorithm = new QCAlgorithm();
208+
var tags = Enumerable.Range(0, MaxTagsCount)
209+
.Select(i => "tag" + string.Concat(Enumerable.Repeat(i, MaxNameAndTagsLength + 1)))
210+
.ToArray();
211+
setTags(algorithm, tags);
212+
CollectionAssert.AreEquivalent(tags.ToHashSet(t => t.Substring(0, MaxNameAndTagsLength)), algorithm.Tags);
213+
}
214+
215+
[Test]
216+
public void AlgorithmTagsAreTruncatedWhenTooLongUsingPropertySetter()
217+
{
218+
TestSettingTagsAreTruncatedWhenTooLong(SetAlgorithmTagsUsingPropertySetter);
219+
}
220+
221+
[Test]
222+
public void AlgorithmTagsAreTruncatedWhenTooLongUsingSetMethod()
223+
{
224+
TestSettingTagsAreTruncatedWhenTooLong(SetAlgorithmTagsUsingSetMethod);
225+
}
226+
227+
public static void TestSettingEmptyTagsAreIgnored(Action<QCAlgorithm, string[]> setTags)
228+
{
229+
var algorithm = new QCAlgorithm();
230+
var tags = new[] { "tag1", "", "tag2", null, "tag3", " " };
231+
setTags(algorithm, tags);
232+
233+
var expectedTags = new[] { "tag1", "tag2", "tag3" };
234+
CollectionAssert.AreEquivalent(expectedTags, algorithm.Tags);
235+
}
236+
237+
[Test]
238+
public void AlgorithmSetContainingEmptyTagsAreIgnoredUsingPropertySetter()
239+
{
240+
TestSettingEmptyTagsAreIgnored(SetAlgorithmTagsUsingPropertySetter);
241+
}
242+
243+
[Test]
244+
public void AlgorithmSetContainingEmptyTagsAreIgnoredUsingSetMethod()
245+
{
246+
TestSettingEmptyTagsAreIgnored(SetAlgorithmTagsUsingSetMethod);
247+
}
248+
249+
[Test]
250+
public void AlgorithmTagsAreAddedOneByOne()
251+
{
252+
var algorithm = new QCAlgorithm();
253+
var tags = new List<string>();
254+
for (var i = 0; i < MaxTagsCount; i++)
255+
{
256+
tags.Add($"tag{i}");
257+
algorithm.AddTag(tags.Last());
258+
CollectionAssert.AreEquivalent(tags, algorithm.Tags);
259+
}
260+
}
261+
262+
[Test]
263+
public void DuplicatedTagsAreIgnored()
264+
{
265+
var algorithm = new QCAlgorithm();
266+
267+
var tag = "tag";
268+
algorithm.AddTag(tag);
269+
Assert.AreEqual(1, algorithm.Tags.Count);
270+
Assert.AreEqual(tag, algorithm.Tags.Single());
271+
272+
algorithm.AddTag(tag);
273+
Assert.AreEqual(1, algorithm.Tags.Count);
274+
Assert.AreEqual(tag, algorithm.Tags.Single());
275+
}
276+
277+
[TestCase("")]
278+
[TestCase(" ")]
279+
[TestCase(null)]
280+
public void EmptyTagsAreIgnored(string emptyTag)
281+
{
282+
var algorithm = new QCAlgorithm();
283+
284+
algorithm.AddTag(emptyTag);
285+
CollectionAssert.IsEmpty(algorithm.Tags);
286+
}
287+
288+
[Test]
289+
public void LongTagsAreTruncatedWhenAddedOneByOne()
290+
{
291+
var algorithm = new QCAlgorithm();
292+
var tag = new string('a', MaxNameAndTagsLength + 1);
293+
algorithm.AddTag(tag);
294+
Assert.AreEqual(1, algorithm.Tags.Count);
295+
Assert.AreEqual(tag.Substring(0, MaxNameAndTagsLength), algorithm.Tags.Single());
296+
}
297+
298+
[Test]
299+
public void TagsAreIgnoredWhenAddedOneByOneIfCollectionIsFull()
300+
{
301+
var algorithm = new QCAlgorithm();
302+
var tags = Enumerable.Range(0, MaxTagsCount).Select(i => $"tag{i}").ToArray();
303+
foreach (var tag in tags)
304+
{
305+
algorithm.AddTag(tag);
306+
}
307+
CollectionAssert.AreEquivalent(tags, algorithm.Tags);
308+
309+
// This will not be added
310+
algorithm.AddTag("LastTag");
311+
CollectionAssert.AreEquivalent(tags, algorithm.Tags);
312+
}
313+
314+
#endregion
315+
316+
private class TestQCAlgorithm : QCAlgorithm
317+
{
318+
public static int PublicMaxNameAndTagsLength => MaxNameAndTagsLength;
319+
320+
public static int PublicMaxTagsCount => MaxTagsCount;
321+
}
322+
323+
private static int MaxNameAndTagsLength => TestQCAlgorithm.PublicMaxNameAndTagsLength;
324+
private static int MaxTagsCount => TestQCAlgorithm.PublicMaxTagsCount;
325+
}
326+
}

0 commit comments

Comments
 (0)