Skip to content

Security initialization on re-addition #8681

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 26 additions & 26 deletions Algorithm.CSharp/RawPricesUniverseRegressionAlgorithm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,12 @@ public override void OnSecuritiesChanged(SecurityChanges changes)
/// <summary>
/// Data Points count of all timeslices of algorithm
/// </summary>
public long DataPoints => 135;
public long DataPoints => 156;

/// <summary>
/// Data Points count of the algorithm history
/// </summary>
public int AlgorithmHistoryDataPoints => 30;
public int AlgorithmHistoryDataPoints => 150;

/// <summary>
/// Final status of the algorithm
Expand All @@ -108,33 +108,33 @@ public override void OnSecuritiesChanged(SecurityChanges changes)
/// </summary>
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
{
{"Total Orders", "12"},
{"Average Win", "0.34%"},
{"Average Loss", "-0.14%"},
{"Compounding Annual Return", "4.586%"},
{"Drawdown", "0.700%"},
{"Expectancy", "0.158"},
{"Total Orders", "57"},
{"Average Win", "0.18%"},
{"Average Loss", "-0.24%"},
{"Compounding Annual Return", "-47.380%"},
{"Drawdown", "2.500%"},
{"Expectancy", "-0.352"},
{"Start Equity", "50000"},
{"End Equity", "50090.17"},
{"Net Profit", "0.180%"},
{"Sharpe Ratio", "5.991"},
{"Sortino Ratio", "0"},
{"Probabilistic Sharpe Ratio", "99.393%"},
{"Loss Rate", "67%"},
{"Win Rate", "33%"},
{"Profit-Loss Ratio", "2.47"},
{"Alpha", "0.17"},
{"Beta", "0.029"},
{"Annual Standard Deviation", "0.028"},
{"Annual Variance", "0.001"},
{"Information Ratio", "2.734"},
{"Tracking Error", "0.098"},
{"Treynor Ratio", "5.803"},
{"End Equity", "48726.48"},
{"Net Profit", "-2.547%"},
{"Sharpe Ratio", "-3.372"},
{"Sortino Ratio", "-3.889"},
{"Probabilistic Sharpe Ratio", "10.352%"},
{"Loss Rate", "63%"},
{"Win Rate", "37%"},
{"Profit-Loss Ratio", "0.75"},
{"Alpha", "-0.208"},
{"Beta", "0.815"},
{"Annual Standard Deviation", "0.086"},
{"Annual Variance", "0.007"},
{"Information Ratio", "-4.871"},
{"Tracking Error", "0.039"},
{"Treynor Ratio", "-0.357"},
{"Total Fees", "$0.00"},
{"Estimated Strategy Capacity", "$99000000.00"},
{"Estimated Strategy Capacity", "$230000000.00"},
{"Lowest Capacity Asset", "AIG R735QTJ8XC9X"},
{"Portfolio Turnover", "15.96%"},
{"OrderListHash", "d915ae36ce856457b32ebbfce4581281"}
{"Portfolio Turnover", "77.40%"},
{"OrderListHash", "4fb8ffbdfd2cce69ac28b0d0992d7198"}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,16 @@ namespace QuantConnect.Algorithm.CSharp
/// It asserts that the securities are marked as non-tradable when removed and that they are tradable when re-added.
/// It also asserts that the algorithm receives the correct security changed events for the added and removed securities.
///
/// Additionally, it tests that the security is initialized after every addition, and no more.
///
/// This specific algorithm tests this behavior for equities.
/// </summary>
public class SecurityInitializationOnReAdditionForEquityRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
{
private Security _security;
private Queue<DateTime> _tradableDates;
private bool _securityWasRemoved;
private Dictionary<Security, int> _securityInializationCounts = new();

protected virtual DateTime StartTimeToUse => new DateTime(2013, 10, 05);

Expand All @@ -44,7 +47,20 @@ public override void Initialize()
SetStartDate(StartTimeToUse);
SetEndDate(EndTimeToUse);

_security = AddSecurity();
var seeder = new FuncSecuritySeeder((security) =>
{
if (!_securityInializationCounts.TryGetValue(security, out var count))
{
count = 0;
}
_securityInializationCounts[security] = count + 1;

Debug($"[{Time}] Seeding {security.Symbol}");
return GetLastKnownPrices(security);
});
SetSecurityInitializer(security => seeder.SeedSecurity(security));

_security = AddSecurityImpl();

_tradableDates = new(QuantConnect.Time.EachTradeableDay(_security.Exchange.Hours, StartDate, EndDate));

Expand All @@ -61,6 +77,9 @@ public override void Initialize()
return;
}

// Before we remove the security let's check that it was not initialized again
AssertSecurityInitializationCount(_securityInializationCounts, _security);

// Remove the security every day
Debug($"[{Time}] Removing the security");
_securityWasRemoved = RemoveSecurity(_security.Symbol);
Expand All @@ -77,6 +96,21 @@ public override void Initialize()
});
}

private Security AddSecurityImpl()
{
_securityInializationCounts.Clear();
var security = AddSecurity();

if (_security != null && !ReferenceEquals(_security, security))
{
throw new RegressionTestException($"Expected the security to be the same as the original security");
}

AssertSecurityInitializationCount(_securityInializationCounts, security);

return security;
}

protected virtual Security AddSecurity()
{
return AddEquity("SPY");
Expand All @@ -100,7 +134,7 @@ public override void OnSecuritiesChanged(SecurityChanges changes)

// Add the security back
Debug($"[{Time}] Re-adding the security");
var reAddedSecurity = AddSecurity();
var reAddedSecurity = AddSecurityImpl();

if (!ReferenceEquals(reAddedSecurity, _security))
{
Expand All @@ -126,6 +160,20 @@ public override void OnEndOfAlgorithm()
}
}

protected virtual void AssertSecurityInitializationCount(Dictionary<Security, int> securityInializationCounts, Security security)
{
if (securityInializationCounts.Count != 1)
{
throw new RegressionTestException($"Expected only one security to be initialized. Got {securityInializationCounts.Count}");
}

if (!securityInializationCounts.TryGetValue(security, out var count) || count != 1)
{
throw new RegressionTestException($"Expected the security to be initialized once and once only, " +
$"but was initialized {count} times");
}
}

/// <summary>
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
/// </summary>
Expand All @@ -144,7 +192,7 @@ public override void OnEndOfAlgorithm()
/// <summary>
/// Data Points count of the algorithm history
/// </summary>
public virtual int AlgorithmHistoryDataPoints => 0;
public virtual int AlgorithmHistoryDataPoints => 3848;

/// <summary>
/// Final status of the algorithm
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* 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.Collections.Generic;
using QuantConnect.Securities;

namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// Regression algorithm testing the behavior of the algorithm when a security is removed and re-added.
/// It asserts that the securities are marked as non-tradable when removed and that they are tradable when re-added.
/// It also asserts that the algorithm receives the correct security changed events for the added and removed securities.
///
/// Additionally, it tests that the security is initialized after every addition, and no more.
///
/// This specific algorithm tests this behavior for manually added future contracts.
/// </summary>
public class SecurityInitializationOnReAdditionForManuallyAddedFutureContractRegressionAlgorithm : SecurityInitializationOnReAdditionForEquityRegressionAlgorithm
{
private static readonly Symbol _futureContractSymbol = QuantConnect.Symbol.CreateFuture(Futures.Indices.SP500EMini, Market.CME, new DateTime(2013, 12, 20));

protected override DateTime StartTimeToUse => new DateTime(2013, 10, 07);

protected override DateTime EndTimeToUse => new DateTime(2013, 10, 17);

protected override Security AddSecurity()
{
return AddFutureContract(_futureContractSymbol, Resolution.Daily);
}

/// <summary>
/// Data Points count of all timeslices of algorithm
/// </summary>
public override long DataPoints => 85;

/// <summary>
/// Data Points count of the algorithm history
/// </summary>
public override int AlgorithmHistoryDataPoints => 48;

/// <summary>
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
/// </summary>
public override Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
{
{"Total Orders", "0"},
{"Average Win", "0%"},
{"Average Loss", "0%"},
{"Compounding Annual Return", "0%"},
{"Drawdown", "0%"},
{"Expectancy", "0"},
{"Start Equity", "100000"},
{"End Equity", "100000"},
{"Net Profit", "0%"},
{"Sharpe Ratio", "0"},
{"Sortino Ratio", "0"},
{"Probabilistic Sharpe Ratio", "0%"},
{"Loss Rate", "0%"},
{"Win Rate", "0%"},
{"Profit-Loss Ratio", "0"},
{"Alpha", "0"},
{"Beta", "0"},
{"Annual Standard Deviation", "0"},
{"Annual Variance", "0"},
{"Information Ratio", "-9.029"},
{"Tracking Error", "0.155"},
{"Treynor Ratio", "0"},
{"Total Fees", "$0.00"},
{"Estimated Strategy Capacity", "$0"},
{"Lowest Capacity Asset", ""},
{"Portfolio Turnover", "0%"},
{"OrderListHash", "d41d8cd98f00b204e9800998ecf8427e"}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ namespace QuantConnect.Algorithm.CSharp
/// It asserts that the securities are marked as non-tradable when removed and that they are tradable when re-added.
/// It also asserts that the algorithm receives the correct security changed events for the added and removed securities.
///
/// Additionally, it tests that the security is initialized after every addition, and no more.
///
/// This specific algorithm tests this behavior for manually added option contracts.
/// </summary>
public class SecurityInitializationOnReAdditionForManuallyAddedOptionRegressionAlgorithm : SecurityInitializationOnReAdditionForEquityRegressionAlgorithm
Expand All @@ -36,15 +38,48 @@ public class SecurityInitializationOnReAdditionForManuallyAddedOptionRegressionA
342.9m,
new DateTime(2014, 07, 19));

private int _securityAdditionsCount;

protected override DateTime StartTimeToUse => new DateTime(2014, 06, 04);

protected override DateTime EndTimeToUse => new DateTime(2014, 06, 20);

protected override Security AddSecurity()
{
_securityAdditionsCount++;
return AddOptionContract(_optionContractSymbol, Resolution.Daily);
}

protected override void AssertSecurityInitializationCount(Dictionary<Security, int> securityInializationCounts, Security security)
{
// The first time the contract is added, the underlying equity will be added and initialized as well.
// The following times the contract is added, the underlying equity will not be added again.
var expectedSecuritiesInitialized = 1;
if (_securityAdditionsCount == 1)
{
expectedSecuritiesInitialized = 2;
}

if (securityInializationCounts.Count != expectedSecuritiesInitialized)
{
throw new RegressionTestException($"Expected {expectedSecuritiesInitialized} security to be initialized. " +
$"Got {securityInializationCounts.Count}");
}

if (!securityInializationCounts.TryGetValue(security, out var count) || count != 1)
{
throw new RegressionTestException($"Expected the option contract to be initialized once and once only, " +
$"but was initialized {count} times");
}

if (expectedSecuritiesInitialized == 2 &&
!securityInializationCounts.TryGetValue(Securities[security.Symbol.Underlying], out count) || count != 1)
{
throw new RegressionTestException($"Expected the underlying security to be initialized once and once only, " +
$"but was initialized {count} times");
}
}

/// <summary>
/// Data Points count of all timeslices of algorithm
/// </summary>
Expand All @@ -53,7 +88,7 @@ protected override Security AddSecurity()
/// <summary>
/// Data Points count of the algorithm history
/// </summary>
public override int AlgorithmHistoryDataPoints => 0;
public override int AlgorithmHistoryDataPoints => 5;

/// <summary>
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
Expand Down
Loading