Skip to content
2 changes: 1 addition & 1 deletion Algorithm/QCAlgorithm.History.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1474,7 +1474,7 @@ private void SetWarmup(int? barCount, TimeSpan? timeSpan, Resolution? resolution
{
if (_locked)
{
throw new InvalidOperationException("QCAlgorithm.SetWarmup(): This method cannot be used after algorithm initialized");
throw new InvalidOperationException(Messages.QCAlgorithm.SetWarmupAlreadyInitialized());
}

_warmupTimeSpan = timeSpan;
Expand Down
19 changes: 9 additions & 10 deletions Algorithm/QCAlgorithm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1315,7 +1315,7 @@ public void SetTimeZone(DateTimeZone timeZone)
{
if (_locked)
{
throw new InvalidOperationException("Algorithm.SetTimeZone(): Cannot change time zone after algorithm running.");
throw new InvalidOperationException(Messages.QCAlgorithm.SetTimeZoneAlreadyRunning());
}

if (timeZone == null) throw new ArgumentNullException(nameof(timeZone));
Expand Down Expand Up @@ -1450,7 +1450,7 @@ public void SetBenchmark(SecurityType securityType, string symbol)
{
if (_locked)
{
throw new InvalidOperationException("Algorithm.SetBenchmark(): Cannot change Benchmark after algorithm initialized.");
throw new InvalidOperationException(Messages.QCAlgorithm.SetBenchmarkAlreadyInitialized());
}

var market = GetMarket(null, symbol, securityType, defaultMarket: Market.USA);
Expand Down Expand Up @@ -1503,7 +1503,7 @@ public void SetBenchmark(Symbol symbol)
{
if (_locked)
{
throw new InvalidOperationException("Algorithm.SetBenchmark(): Cannot change Benchmark after algorithm initialized.");
throw new InvalidOperationException(Messages.QCAlgorithm.SetBenchmarkAlreadyInitialized());
}

// Create our security benchmark
Expand All @@ -1522,7 +1522,7 @@ public void SetBenchmark(Func<DateTime, decimal> benchmark)
{
if (_locked)
{
throw new InvalidOperationException("Algorithm.SetBenchmark(): Cannot change Benchmark after algorithm initialized.");
throw new InvalidOperationException(Messages.QCAlgorithm.SetBenchmarkAlreadyInitialized());
}

Benchmark = new FuncBenchmark(benchmark);
Expand Down Expand Up @@ -1599,8 +1599,7 @@ public void SetAccountCurrency(string accountCurrency, decimal? startingCash = n
{
if (_locked)
{
throw new InvalidOperationException("Algorithm.SetAccountCurrency(): " +
"Cannot change AccountCurrency after algorithm initialized.");
throw new InvalidOperationException(Messages.QCAlgorithm.SetAccountCurrencyAlreadyInitialized());
}

if (startingCash == null)
Expand Down Expand Up @@ -1653,7 +1652,7 @@ public void SetCash(decimal startingCash)
}
else
{
throw new InvalidOperationException("Algorithm.SetCash(): Cannot change cash available after algorithm initialized.");
throw new InvalidOperationException(Messages.QCAlgorithm.SetCashAlreadyInitialized());
}
}

Expand All @@ -1672,7 +1671,7 @@ public void SetCash(string symbol, decimal startingCash, decimal conversionRate
}
else
{
throw new InvalidOperationException("Algorithm.SetCash(): Cannot change cash available after algorithm initialized.");
throw new InvalidOperationException(Messages.QCAlgorithm.SetCashAlreadyInitialized());
}
}

Expand Down Expand Up @@ -1778,7 +1777,7 @@ public void SetStartDate(DateTime start)
}
else
{
throw new InvalidOperationException("Algorithm.SetStartDate(): Cannot change start date after algorithm initialized.");
throw new InvalidOperationException(Messages.QCAlgorithm.SetStartDateAlreadyInitialized());
}
}

Expand All @@ -1797,7 +1796,7 @@ public void SetEndDate(DateTime end)
//1. Check not locked already:
if (_locked)
{
throw new InvalidOperationException("Algorithm.SetEndDate(): Cannot change end date after algorithm initialized.");
throw new InvalidOperationException(Messages.QCAlgorithm.SetEndDateAlreadyInitialized());
}

//Validate:
Expand Down
4 changes: 2 additions & 2 deletions AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -901,7 +901,7 @@ public void OnMarginCall(List<SubmitOrderRequest> requests)
// If the method does not return or returns a non-iterable PyObject, throw an exception
if (result == null || !result.IsIterable())
{
throw new Exception("OnMarginCall must return a non-empty list of SubmitOrderRequest");
throw new Exception(Messages.AlgorithmPythonWrapper.OnMarginCallMustReturnNonEmptyList());
}

requests.Clear();
Expand All @@ -919,7 +919,7 @@ public void OnMarginCall(List<SubmitOrderRequest> requests)
// If the PyObject is an empty list or its items are not SubmitOrderRequest objects, throw an exception
if (requests.Count == 0)
{
throw new Exception("OnMarginCall must return a non-empty list of SubmitOrderRequest");
throw new Exception(Messages.AlgorithmPythonWrapper.OnMarginCallMustReturnNonEmptyList());
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions Common/Messages/Messages.Algorithm.Framework.Portfolio.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ public static class PortfolioTarget
public static string InvalidTargetPercent(IAlgorithm algorithm, decimal percent)
{
return Invariant($@"The portfolio target percent: {
percent}, does not comply with the current 'Algorithm.Settings' 'MaxAbsolutePortfolioTargetPercentage': {
algorithm.Settings.MaxAbsolutePortfolioTargetPercentage} or 'MinAbsolutePortfolioTargetPercentage': {
percent}, does not comply with the current '{FormatCodeRoot("Settings")}.{FormatCode("MaxAbsolutePortfolioTargetPercentage")}': {
algorithm.Settings.MaxAbsolutePortfolioTargetPercentage} or '{FormatCodeRoot("Settings")}.{FormatCode("MinAbsolutePortfolioTargetPercentage")}': {
algorithm.Settings.MinAbsolutePortfolioTargetPercentage}. Skipping");
}

Expand Down
110 changes: 110 additions & 0 deletions Common/Messages/Messages.Algorithm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* 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.Runtime.CompilerServices;

namespace QuantConnect
{
/// <summary>
/// Provides user-facing message construction methods and static messages for the <see cref="Algorithm"/> namespace
/// </summary>
public static partial class Messages
{
/// <summary>
/// Provides user-facing messages for the <see cref="Algorithm.QCAlgorithm"/> class and its consumers or related classes
/// </summary>
public static class QCAlgorithm
{
/// <summary>
/// Returns a string message saying the time zone cannot be changed after the algorithm is running
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SetTimeZoneAlreadyRunning()
{
return $"{AlgorithmPrefix()}.{FormatCode("SetTimeZone")}(): Cannot change time zone after algorithm running.";
}

/// <summary>
/// Returns a string message saying the benchmark cannot be changed after the algorithm is initialized
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SetBenchmarkAlreadyInitialized()
{
return $"{AlgorithmPrefix()}.{FormatCode("SetBenchmark")}(): Cannot change Benchmark after algorithm initialized.";
}

/// <summary>
/// Returns a string message saying the account currency cannot be changed after the algorithm is initialized
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SetAccountCurrencyAlreadyInitialized()
{
return $"{AlgorithmPrefix()}.{FormatCode("SetAccountCurrency")}(): Cannot change AccountCurrency after algorithm initialized.";
}

/// <summary>
/// Returns a string message saying the cash cannot be changed after the algorithm is initialized
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SetCashAlreadyInitialized()
{
return $"{AlgorithmPrefix()}.{FormatCode("SetCash")}(): Cannot change cash available after algorithm initialized.";
}

/// <summary>
/// Returns a string message saying the start date cannot be changed after the algorithm is initialized
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SetStartDateAlreadyInitialized()
{
return $"{AlgorithmPrefix()}.{FormatCode("SetStartDate")}(): Cannot change start date after algorithm initialized.";
}

/// <summary>
/// Returns a string message saying the end date cannot be changed after the algorithm is initialized
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SetEndDateAlreadyInitialized()
{
return $"{AlgorithmPrefix()}.{FormatCode("SetEndDate")}(): Cannot change end date after algorithm initialized.";
}

/// <summary>
/// Returns a string message saying SetWarmup cannot be used after the algorithm is initialized
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SetWarmupAlreadyInitialized()
{
return $"{AlgorithmPrefix()}.{FormatCode("SetWarmup")}(): This method cannot be used after algorithm initialized";
}
}

/// <summary>
/// Provides user-facing messages for the <see cref="AlgorithmFactory.Python.Wrappers.AlgorithmPythonWrapper"/> class
/// and its consumers or related classes
/// </summary>
public static class AlgorithmPythonWrapper
{
/// <summary>
/// Returns a string message saying OnMarginCall must return a non-empty list of SubmitOrderRequest
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string OnMarginCallMustReturnNonEmptyList()
{
return $"{FormatCode("OnMarginCall")} must return a non-empty list of SubmitOrderRequest";
}
}
}
}
4 changes: 2 additions & 2 deletions Common/Messages/Messages.Brokerages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ public static string UnsupportedMarketOnOpenOrderTime(
in TimeOnly windowStart,
in TimeOnly windowEnd)
{
return Invariant($"MarketOnOpen submission time is invalid. Valid local times are {windowStart: hh\\:mm}–{windowEnd: hh\\:mm}. Consider setting DailyPreciseEndTime = false or using {nameof(Schedule)}.{nameof(Schedule.On)}.");
return Invariant($"MarketOnOpen submission time is invalid. Valid local times are {windowStart: hh\\:mm}–{windowEnd: hh\\:mm}. Consider setting {FormatCode(nameof(AlgorithmSettings.DailyPreciseEndTime))} = false or using {FormatCodeRoot(nameof(Schedule))}.{FormatCode(nameof(Schedule.On))}.");
}
}

Expand Down Expand Up @@ -411,7 +411,7 @@ public static class FxcmBrokerageModel
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string InvalidOrderQuantityForLotSize(Securities.Security security)
{
return Invariant($"The order quantity must be a multiple of LotSize: [{security.SymbolProperties.LotSize}].");
return Invariant($"The order quantity must be a multiple of {FormatCode("LotSize")}: [{security.SymbolProperties.LotSize}].");
}

/// <summary>
Expand Down
6 changes: 3 additions & 3 deletions Common/Messages/Messages.Orders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ public static string ZeroQuantity(Orders.OrderRequest request)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string MissingSecurity(Orders.SubmitOrderRequest request)
{
return Invariant($"You haven't requested {request.Symbol} data. Add this with AddSecurity() in the Initialize() Method.");
return Invariant($"You haven't requested {request.Symbol} data. Add this with {FormatCode("AddSecurity")}() in the {FormatCode("Initialize")}() method.");
}

/// <summary>
Expand All @@ -365,8 +365,8 @@ public static string MissingSecurity(Orders.SubmitOrderRequest request)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string WarmingUp(Orders.OrderRequest request)
{
return Invariant($@"This operation is not allowed in Initialize or during warm up: OrderRequest.{
request.OrderRequestType}. Please move this code to the OnWarmupFinished() method.");
return Invariant($@"This operation is not allowed in {FormatCode("Initialize")} or during warm up: OrderRequest.{
FormatCode(request.OrderRequestType)}. Please move this code to the {FormatCode("OnWarmupFinished")}() method.");
}
}

Expand Down
50 changes: 46 additions & 4 deletions Common/Messages/Messages.QuantConnect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,48 @@ namespace QuantConnect
/// </summary>
public static partial class Messages
{
private static Language _algorithmLanguage = Language.CSharp;

/// <summary>
/// Sets the algorithm language used to format code identifiers in error messages.
/// </summary>
public static void SetAlgorithmLanguage(Language language)
{
_algorithmLanguage = language;
}

/// <summary>
/// Returns the code identifier formatted for the current algorithm language.
/// For Python, converts PascalCase/camelCase to snake_case.
/// </summary>
private static string FormatCode(string code)
{
return _algorithmLanguage switch
{
Language.Python => code.ToSnakeCase(),
_ => code
};
}

private static string FormatCodeRoot(string code)
{
return _algorithmLanguage switch
{
Language.Python => "self." + code.ToSnakeCase(),
_ => code
};
}

private static string FormatCode<T>(T value) where T : Enum
{
return FormatCode(value.ToString());
}

private static string AlgorithmPrefix()
{
return _algorithmLanguage == Language.Python ? "self" : "QCAlgorithm";
}

/// <summary>
/// Provides user-facing messages for the <see cref="AlphaRuntimeStatistics"/> class and its consumers or related classes
/// </summary>
Expand Down Expand Up @@ -187,8 +229,8 @@ public static string RemoveInvalidOperation<TKey, TValue>(ExtendedDictionary<TKe
public static string TickerNotFoundInSymbolCache(string ticker)
{
return $"The ticker {ticker} was not found in the SymbolCache. Use the Symbol object as key instead. " +
"Accessing the securities collection/slice object by string ticker is only available for securities added with " +
"the AddSecurity-family methods. For more details, please check out the documentation.";
$"Accessing the securities collection/slice object by string ticker is only available for securities added with " +
$"the {FormatCode("AddSecurity")}-family methods. For more details, please check out the documentation.";
}

/// <summary>
Expand Down Expand Up @@ -282,7 +324,7 @@ public static string DownloadDataFailed(string url)
public static string ZeroPriceForSecurity(QuantConnect.Symbol symbol)
{
return $"{symbol}: The security does not have an accurate price as it has not yet received a bar of data. " +
"Before placing a trade (or using SetHoldings) warm up your algorithm with SetWarmup, or use slice.Contains(symbol) " +
$"Before placing a trade (or using {FormatCode("SetHoldings")}) warm up your algorithm with {FormatCode("SetWarmup")}, or use slice.{FormatCode("Contains")}(symbol) " +
"to confirm the Slice object has price before using the data. Data does not necessarily all arrive at the same " +
"time so your algorithm should confirm the data is ready before using it. In live trading this can mean you do " +
"not have an active subscription to the asset class you're trying to trade. If using custom data make sure you've " +
Expand Down Expand Up @@ -724,7 +766,7 @@ public static string ErrorParsingSecurityIdentifier(string value, Exception exce
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string MarketNotFound(string market)
{
return $@"The specified market wasn't found in the markets lookup. Requested: {market}. You can add markets by calling QuantConnect.Market.Add(string,int)";
return $@"The specified market wasn't found in the markets lookup. Requested: {market}. You can add markets by calling QuantConnect.Market.{FormatCode("Add")}(string,int)";
}
}

Expand Down
Loading
Loading