Skip to content

Commit f2d566c

Browse files
Minor improvements for LLMs
- Minor tweaks in regression tests - Minor API sugars
1 parent 2cafba8 commit f2d566c

File tree

12 files changed

+282
-55
lines changed

12 files changed

+282
-55
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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 QuantConnect.Interfaces;
18+
using System.Collections.Generic;
19+
20+
namespace QuantConnect.Algorithm.CSharp
21+
{
22+
/// <summary>
23+
/// Regression algorithm asserting the behavior of the time and date rules
24+
/// </summary>
25+
public class TimeAndDateRulesRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
26+
{
27+
private readonly List<DateTime> _scheduleEquityEvents = new();
28+
private readonly List<DateTime> _scheduleCryptoEvents = new();
29+
30+
/// <summary>
31+
/// Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.
32+
/// </summary>
33+
public override void Initialize()
34+
{
35+
SetStartDate(2013, 10, 07);
36+
SetEndDate(2013, 10, 14);
37+
38+
Schedule.On(Schedule.DateRules.EveryDay("SPY"), Schedule.TimeRules.AfterMarketOpen("SPY", 10), () =>
39+
{
40+
_scheduleEquityEvents.Add(Time);
41+
});
42+
Schedule.On(Schedule.DateRules.EveryDay("BTCUSD"), Schedule.TimeRules.Noon, () =>
43+
{
44+
_scheduleCryptoEvents.Add(Time);
45+
});
46+
}
47+
48+
public override void OnEndOfAlgorithm()
49+
{
50+
if (_scheduleEquityEvents.Count != 6)
51+
{
52+
throw new RegressionTestException("No events were scheduled for equity!");
53+
}
54+
if (_scheduleCryptoEvents.Count != 8)
55+
{
56+
throw new RegressionTestException("No events were scheduled for crypto!");
57+
}
58+
}
59+
60+
/// <summary>
61+
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
62+
/// </summary>
63+
public bool CanRunLocally { get; } = true;
64+
65+
/// <summary>
66+
/// This is used by the regression test system to indicate which languages this algorithm is written in.
67+
/// </summary>
68+
public List<Language> Languages { get; } = new() { Language.CSharp };
69+
70+
/// <summary>
71+
/// Data Points count of all timeslices of algorithm
72+
/// </summary>
73+
public long DataPoints => 49;
74+
75+
/// <summary>
76+
/// Data Points count of the algorithm history
77+
/// </summary>
78+
public int AlgorithmHistoryDataPoints => 0;
79+
80+
/// <summary>
81+
/// Final status of the algorithm
82+
/// </summary>
83+
public AlgorithmStatus AlgorithmStatus => AlgorithmStatus.Completed;
84+
85+
/// <summary>
86+
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
87+
/// </summary>
88+
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
89+
{
90+
{"Total Orders", "0"},
91+
{"Average Win", "0%"},
92+
{"Average Loss", "0%"},
93+
{"Compounding Annual Return", "0%"},
94+
{"Drawdown", "0%"},
95+
{"Expectancy", "0"},
96+
{"Start Equity", "100000"},
97+
{"End Equity", "100000"},
98+
{"Net Profit", "0%"},
99+
{"Sharpe Ratio", "0"},
100+
{"Sortino Ratio", "0"},
101+
{"Probabilistic Sharpe Ratio", "0%"},
102+
{"Loss Rate", "0%"},
103+
{"Win Rate", "0%"},
104+
{"Profit-Loss Ratio", "0"},
105+
{"Alpha", "0"},
106+
{"Beta", "0"},
107+
{"Annual Standard Deviation", "0"},
108+
{"Annual Variance", "0"},
109+
{"Information Ratio", "-7.357"},
110+
{"Tracking Error", "0.161"},
111+
{"Treynor Ratio", "0"},
112+
{"Total Fees", "$0.00"},
113+
{"Estimated Strategy Capacity", "$0"},
114+
{"Lowest Capacity Asset", ""},
115+
{"Portfolio Turnover", "0%"},
116+
{"OrderListHash", "d41d8cd98f00b204e9800998ecf8427e"}
117+
};
118+
}
119+
}

Algorithm.Framework/Selection/FutureUniverseSelectionModel.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,32 @@ protected virtual FutureFilterUniverse Filter(FutureFilterUniverse filter)
103103
return filter;
104104
}
105105
}
106+
107+
/// <summary>
108+
/// Provides an implementation of <see cref="IUniverseSelectionModel"/> that subscribes to future chains
109+
/// </summary>
110+
public class FuturesUniverseSelectionModel : FutureUniverseSelectionModel
111+
{
112+
/// <summary>
113+
/// Creates a new instance of <see cref="FutureUniverseSelectionModel"/>
114+
/// </summary>
115+
/// <param name="refreshInterval">Time interval between universe refreshes</param>
116+
/// <param name="futureChainSymbolSelector">Selects symbols from the provided future chain</param>
117+
public FuturesUniverseSelectionModel(TimeSpan refreshInterval, Func<DateTime, IEnumerable<Symbol>> futureChainSymbolSelector)
118+
: base(refreshInterval, futureChainSymbolSelector)
119+
{
120+
}
121+
/// <summary>
122+
/// Creates a new instance of <see cref="FutureUniverseSelectionModel"/>
123+
/// </summary>
124+
/// <param name="refreshInterval">Time interval between universe refreshes</param>
125+
/// <param name="futureChainSymbolSelector">Selects symbols from the provided future chain</param>
126+
/// <param name="universeSettings">Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed</param>
127+
public FuturesUniverseSelectionModel(TimeSpan refreshInterval,
128+
Func<DateTime, IEnumerable<Symbol>> futureChainSymbolSelector,
129+
UniverseSettings universeSettings)
130+
: base(refreshInterval, futureChainSymbolSelector, universeSettings)
131+
{
132+
}
133+
}
106134
}

Algorithm/QCAlgorithm.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3362,6 +3362,24 @@ public OptionChains OptionChains(IEnumerable<Symbol> symbols, bool flatten = fal
33623362
return chains;
33633363
}
33643364

3365+
/// <summary>
3366+
/// Get the futures chain for the specified symbol at the current time (<see cref="Time"/>)
3367+
/// </summary>
3368+
/// <param name="symbol">
3369+
/// The symbol for which the futures chain is asked for.
3370+
/// It can be either the canonical future, a contract or an option symbol.
3371+
/// </param>
3372+
/// <param name="flatten">
3373+
/// Whether to flatten the resulting data frame. Used from Python when accessing <see cref="FuturesChain.DataFrame"/>.
3374+
/// See <see cref="History(PyObject, int, Resolution?, bool?, bool?, DataMappingMode?, DataNormalizationMode?, int?, bool)"/>
3375+
/// </param>
3376+
/// <returns>The futures chain</returns>
3377+
[DocumentationAttribute(AddingData)]
3378+
public FuturesChain FutureChain(Symbol symbol, bool flatten = false)
3379+
{
3380+
return FuturesChain(symbol, flatten);
3381+
}
3382+
33653383
/// <summary>
33663384
/// Get the futures chain for the specified symbol at the current time (<see cref="Time"/>)
33673385
/// </summary>
@@ -3381,6 +3399,24 @@ public FuturesChain FuturesChain(Symbol symbol, bool flatten = false)
33813399
new FuturesChain(GetCanonicalFutureSymbol(symbol), Time.Date);
33823400
}
33833401

3402+
/// <summary>
3403+
/// Get the futures chains for the specified symbols at the current time (<see cref="Time"/>)
3404+
/// </summary>
3405+
/// <param name="symbols">
3406+
/// The symbols for which the futures chains are asked for.
3407+
/// It can be either the canonical future, a contract or an option symbol.
3408+
/// </param>
3409+
/// <param name="flatten">
3410+
/// Whether to flatten the resulting data frame. Used from Python when accessing <see cref="FuturesChains.DataFrame"/>.
3411+
/// See <see cref="History(PyObject, int, Resolution?, bool?, bool?, DataMappingMode?, DataNormalizationMode?, int?, bool)"/>
3412+
/// </param>
3413+
/// <returns>The futures chains</returns>
3414+
[DocumentationAttribute(AddingData)]
3415+
public FuturesChains FutureChains(IEnumerable<Symbol> symbols, bool flatten = false)
3416+
{
3417+
return FuturesChains(symbols, flatten);
3418+
}
3419+
33843420
/// <summary>
33853421
/// Get the futures chains for the specified symbols at the current time (<see cref="Time"/>)
33863422
/// </summary>

Common/Exceptions/StackExceptionInterpreter.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,14 +116,16 @@ public string GetExceptionMessageHeader(Exception exception)
116116
public static StackExceptionInterpreter CreateFromAssemblies(IEnumerable<Assembly> assemblies)
117117
{
118118
var interpreters =
119-
from assembly in assemblies
119+
from assembly in assemblies.Where(x =>
120+
(!x.FullName?.StartsWith("System.", StringComparison.InvariantCultureIgnoreCase) ?? false)
121+
&& (!x.FullName?.StartsWith("Microsoft.", StringComparison.InvariantCultureIgnoreCase) ?? false))
120122
from type in assembly.GetTypes()
121123
// ignore non-public and non-instantiable abstract types
122124
where type.IsPublic && !type.IsAbstract
123125
// type implements IExceptionInterpreter
124126
where typeof(IExceptionInterpreter).IsAssignableFrom(type)
125127
// type is not mocked with MOQ library
126-
where type.FullName != null && !type.FullName.StartsWith("Castle.Proxies.ObjectProxy")
128+
where type.FullName != null && !type.FullName.StartsWith("Castle.Proxies.ObjectProxy", StringComparison.InvariantCultureIgnoreCase)
127129
// type has default parameterless ctor
128130
where type.GetConstructor(new Type[0]) != null
129131
// provide guarantee of deterministic ordering

Common/Symbol.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ namespace QuantConnect
3333
[PandasNonExpandable]
3434
public sealed class Symbol : IEquatable<Symbol>, IComparable
3535
{
36+
private static readonly SymbolPropertiesDatabase _spdb = SymbolPropertiesDatabase.FromDataFolder();
37+
private static readonly SecurityType[] _securityTypes = Enum.GetValues<SecurityType>();
3638
private static readonly Lazy<SecurityDefinitionSymbolResolver> _securityDefinitionSymbolResolver = new (() => SecurityDefinitionSymbolResolver.GetInstance());
3739

3840
private Symbol _canonical;
@@ -861,7 +863,17 @@ public static implicit operator Symbol(string ticker)
861863
return symbol;
862864
}
863865

864-
return new Symbol(new SecurityIdentifier(ticker, 0), ticker);
866+
// let's try our best
867+
for (var i = 0; i < _securityTypes.Length; i++)
868+
{
869+
if (_spdb.TryGetMarket(ticker, _securityTypes[i], out var market))
870+
{
871+
// we got a match!
872+
return Create(ticker, _securityTypes[i], market);
873+
}
874+
}
875+
// better than nothing
876+
return Create(ticker, SecurityType.Equity, Market.USA);
865877
}
866878

867879
#endregion

Tests/AlgorithmRunner.cs

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,11 @@ public static AlgorithmRunnerResults RunLocalBacktest(
111111
var initialLogHandler = Log.LogHandler;
112112
var initialDebugEnabled = Log.DebuggingEnabled;
113113

114-
var newLogHandlers = new List<ILogHandler>() { MaintainLogHandlerAttribute.LogHandler };
114+
var newLogHandlers = new List<ILogHandler>();
115+
if (MaintainLogHandlerAttribute.LogHandler != null)
116+
{
117+
newLogHandlers.Add(MaintainLogHandlerAttribute.LogHandler);
118+
}
115119
// Use our current test LogHandler and a FileLogHandler
116120
if (!reducedDiskSize)
117121
{
@@ -190,30 +194,7 @@ public static AlgorithmRunnerResults RunLocalBacktest(
190194
}
191195
}
192196

193-
if (algorithmManager?.State != expectedFinalStatus)
194-
{
195-
Assert.Fail($"Algorithm state should be {expectedFinalStatus} and is: {algorithmManager?.State}");
196-
}
197-
198-
foreach (var expectedStat in expectedStatistics)
199-
{
200-
string result;
201-
Assert.IsTrue(statistics.TryGetValue(expectedStat.Key, out result), "Missing key: " + expectedStat.Key);
202-
203-
// normalize -0 & 0, they are the same thing
204-
var expected = expectedStat.Value;
205-
if (expected == "-0")
206-
{
207-
expected = "0";
208-
}
209-
210-
if (result == "-0")
211-
{
212-
result = "0";
213-
}
214-
215-
Assert.AreEqual(expected, result, "Failed on " + expectedStat.Key);
216-
}
197+
AssertAlgorithmState(expectedFinalStatus, algorithmManager?.State, expectedStatistics, statistics);
217198

218199
if (!reducedDiskSize)
219200
{
@@ -272,5 +253,40 @@ public override IEnumerable<Slice> GetHistory(IEnumerable<HistoryRequest> reques
272253
public class TestWorkerThread : WorkerThread
273254
{
274255
}
256+
257+
public static void AssertAlgorithmState(AlgorithmStatus expectedFinalStatus, AlgorithmStatus? actualState,
258+
IDictionary<string, string> expectedStatistics, IDictionary<string, string> statistics)
259+
{
260+
if (actualState != expectedFinalStatus)
261+
{
262+
Assert.Fail($"Algorithm state should be {expectedFinalStatus} and is: {actualState}");
263+
}
264+
265+
foreach (var expectedStat in expectedStatistics)
266+
{
267+
if (statistics == null)
268+
{
269+
Assert.Fail("Algorithm statistics are null");
270+
break;
271+
}
272+
273+
string result;
274+
Assert.IsTrue(statistics.TryGetValue(expectedStat.Key, out result), "Missing key: " + expectedStat.Key);
275+
276+
// normalize -0 & 0, they are the same thing
277+
var expected = expectedStat.Value;
278+
if (expected == "-0")
279+
{
280+
expected = "0";
281+
}
282+
283+
if (result == "-0")
284+
{
285+
result = "0";
286+
}
287+
288+
Assert.AreEqual(expected, result, "Failed on " + expectedStat.Key);
289+
}
290+
}
275291
}
276292
}

Tests/Api/ApiTestBase.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ private void CreateTestProjectAndBacktest()
104104
return;
105105
}
106106
Log.Debug("ApiTestBase.Setup(): Waiting for test backtest to complete");
107-
TestBacktest = WaitForBacktestCompletion(TestProject.ProjectId, backtest.BacktestId);
107+
TestBacktest = WaitForBacktestCompletion(ApiClient, TestProject.ProjectId, backtest.BacktestId);
108108
if (!TestBacktest.Success)
109109
{
110110
Assert.Warn("Could not create backtest for the test project, tests using it will fail.");
@@ -151,15 +151,15 @@ public static bool IsValidJson(string jsonString)
151151
/// <param name="projectId">Id of the project</param>
152152
/// <param name="compileId">Id of the compilation of the project</param>
153153
/// <returns></returns>
154-
protected static Compile WaitForCompilerResponse(Api.Api apiClient, int projectId, string compileId, int seconds = 60)
154+
public static Compile WaitForCompilerResponse(Api.Api apiClient, int projectId, string compileId, int seconds = 60)
155155
{
156156
var compile = new Compile();
157157
var finish = DateTime.UtcNow.AddSeconds(seconds);
158158
do
159159
{
160160
Thread.Sleep(100);
161161
compile = apiClient.ReadCompile(projectId, compileId);
162-
} while (compile.State != CompileState.BuildSuccess && DateTime.UtcNow < finish);
162+
} while (compile.State == CompileState.InQueue && DateTime.UtcNow < finish);
163163

164164
return compile;
165165
}
@@ -170,14 +170,18 @@ protected static Compile WaitForCompilerResponse(Api.Api apiClient, int projectI
170170
/// <param name="projectId">Project id to scan</param>
171171
/// <param name="backtestId">Backtest id previously started</param>
172172
/// <returns>Completed backtest object</returns>
173-
protected Backtest WaitForBacktestCompletion(int projectId, string backtestId)
173+
public static Backtest WaitForBacktestCompletion(Api.Api apiClient, int projectId, string backtestId, int secondsTimeout = 60)
174174
{
175175
Backtest backtest;
176-
var finish = DateTime.UtcNow.AddSeconds(60);
176+
var finish = DateTime.UtcNow.AddSeconds(secondsTimeout);
177177
do
178178
{
179179
Thread.Sleep(1000);
180-
backtest = ApiClient.ReadBacktest(projectId, backtestId);
180+
backtest = apiClient.ReadBacktest(projectId, backtestId);
181+
if (backtest != null && (!string.IsNullOrEmpty(backtest.Error) || backtest.HasInitializeError))
182+
{
183+
Assert.Fail($"Backtest {projectId}/{backtestId} failed: {backtest.Error}. Stacktrace: {backtest.Stacktrace}. Api errors: {string.Join(",", backtest.Errors)}");
184+
}
181185
} while (backtest.Success && backtest.Progress < 1 && DateTime.UtcNow < finish);
182186

183187
return backtest;

Tests/Api/OptimizationTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ private int GetProjectCompiledAndWithBacktest(out Compile compile)
230230
var backtest = ApiClient.CreateBacktest(projectId, compile.CompileId, backtestName);
231231

232232
// Now wait until the backtest is completed and request the orders again
233-
var backtestReady = WaitForBacktestCompletion(projectId, backtest.BacktestId);
233+
var backtestReady = WaitForBacktestCompletion(ApiClient, projectId, backtest.BacktestId);
234234
Assert.IsTrue(backtestReady.Success);
235235

236236
return projectId;

0 commit comments

Comments
 (0)