diff --git a/Algorithm/QCAlgorithm.Indicators.cs b/Algorithm/QCAlgorithm.Indicators.cs
index ab6470781479..01b2aaef857e 100644
--- a/Algorithm/QCAlgorithm.Indicators.cs
+++ b/Algorithm/QCAlgorithm.Indicators.cs
@@ -1500,19 +1500,26 @@ public SuperTrend STR(Symbol symbol, int period, decimal multiplier, MovingAvera
}
///
- /// Creates a new RollingSharpeRatio indicator.
+ /// Creates a new SharpeRatio indicator.
///
/// The symbol whose RSR we want
/// Period of historical observation for sharpe ratio calculation
- /// Risk-free rate for sharpe ratio calculation
+ ///
+ /// Risk-free rate for sharpe ratio calculation. If not specified, it will use the algorithms'
+ ///
/// The resolution
/// Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value)
- /// The RollingSharpeRatio indicator for the requested symbol over the specified period
+ /// The SharpeRatio indicator for the requested symbol over the specified period
[DocumentationAttribute(Indicators)]
- public SharpeRatio SR(Symbol symbol, int sharpePeriod, decimal riskFreeRate = 0.0m, Resolution ? resolution = null, Func selector = null)
+ public SharpeRatio SR(Symbol symbol, int sharpePeriod, decimal? riskFreeRate = null, Resolution? resolution = null, Func selector = null)
{
- var name = CreateIndicatorName(symbol, $"SR({sharpePeriod},{riskFreeRate})", resolution);
- var sharpeRatio = new SharpeRatio(name, sharpePeriod, riskFreeRate);
+ var baseBame = riskFreeRate.HasValue ? $"SR({sharpePeriod},{riskFreeRate})" : $"SR({sharpePeriod})";
+ var name = CreateIndicatorName(symbol, baseBame, resolution);
+ IRiskFreeInterestRateModel riskFreeRateModel = riskFreeRate.HasValue
+ ? new ConstantRiskFreeRateInterestRateModel(riskFreeRate.Value)
+ // Make it a function so it's lazily evaluated: SetRiskFreeInterestRateModel can be called after this method
+ : new FuncRiskFreeRateInterestRateModel((datetime) => RiskFreeInterestRateModel.GetInterestRate(datetime));
+ var sharpeRatio = new SharpeRatio(name, sharpePeriod, riskFreeRateModel);
InitializeIndicator(symbol, sharpeRatio, resolution, selector);
return sharpeRatio;
diff --git a/Algorithm/QCAlgorithm.Python.cs b/Algorithm/QCAlgorithm.Python.cs
index 31b40bf73024..accb7fbb6f3c 100644
--- a/Algorithm/QCAlgorithm.Python.cs
+++ b/Algorithm/QCAlgorithm.Python.cs
@@ -1148,6 +1148,16 @@ public void SetBrokerageModel(PyObject model)
SetBrokerageModel(brokerageModel);
}
+ ///
+ /// Sets the risk free interest rate model to be used in the algorithm
+ ///
+ /// The risk free interest rate model to use
+ [DocumentationAttribute(Modeling)]
+ public void SetRiskFreeInterestRateModel(PyObject model)
+ {
+ SetRiskFreeInterestRateModel(RiskFreeInterestRateModelPythonWrapper.FromPyObject(model));
+ }
+
///
/// Sets the security initializer function, used to initialize/configure securities after creation
///
diff --git a/Algorithm/QCAlgorithm.cs b/Algorithm/QCAlgorithm.cs
index aaee6ec40b0d..8d67056119ce 100644
--- a/Algorithm/QCAlgorithm.cs
+++ b/Algorithm/QCAlgorithm.cs
@@ -174,6 +174,7 @@ public QCAlgorithm()
SignalExport = new SignalExportManager(this);
BrokerageModel = new DefaultBrokerageModel();
+ RiskFreeInterestRateModel = new InterestRateProvider();
Notify = new NotificationManager(false); // Notification manager defaults to disabled.
//Initialise to unlocked:
@@ -337,6 +338,16 @@ public IBrokerageMessageHandler BrokerageMessageHandler
set;
}
+ ///
+ /// Gets the risk free interest rate model used to get the interest rates
+ ///
+ [DocumentationAttribute(Modeling)]
+ public IRiskFreeInterestRateModel RiskFreeInterestRateModel
+ {
+ get;
+ private set;
+ }
+
///
/// Notification Manager for Sending Live Runtime Notifications to users about important events.
///
@@ -1278,6 +1289,16 @@ public void SetBrokerageMessageHandler(IBrokerageMessageHandler handler)
BrokerageMessageHandler = handler ?? throw new ArgumentNullException(nameof(handler));
}
+ ///
+ /// Sets the risk free interest rate model to be used in the algorithm
+ ///
+ /// The risk free interest rate model to use
+ [DocumentationAttribute(Modeling)]
+ public void SetRiskFreeInterestRateModel(IRiskFreeInterestRateModel model)
+ {
+ RiskFreeInterestRateModel = model ?? throw new ArgumentNullException(nameof(model));
+ }
+
///
/// Sets the benchmark used for computing statistics of the algorithm to the specified symbol
///
diff --git a/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs b/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs
index 34032b3742eb..7707000ccd6f 100644
--- a/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs
+++ b/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs
@@ -187,6 +187,11 @@ public IBrokerageMessageHandler BrokerageMessageHandler
///
public BrokerageName BrokerageName => _baseAlgorithm.BrokerageName;
+ ///
+ /// Gets the risk free interest rate model used to get the interest rates
+ ///
+ public IRiskFreeInterestRateModel RiskFreeInterestRateModel => _baseAlgorithm.RiskFreeInterestRateModel;
+
///
/// Debug messages from the strategy:
///
diff --git a/Common/Data/ConstantRiskFreeRateInterestRateModel.cs b/Common/Data/ConstantRiskFreeRateInterestRateModel.cs
new file mode 100644
index 000000000000..feca8491c966
--- /dev/null
+++ b/Common/Data/ConstantRiskFreeRateInterestRateModel.cs
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+namespace QuantConnect.Data
+{
+ ///
+ /// Constant risk free rate interest rate model
+ ///
+ public class ConstantRiskFreeRateInterestRateModel : IRiskFreeInterestRateModel
+ {
+ private readonly decimal _riskFreeRate;
+
+ ///
+ /// Instantiates a with the specified risk free rate
+ ///
+ public ConstantRiskFreeRateInterestRateModel(decimal riskFreeRate)
+ {
+ _riskFreeRate = riskFreeRate;
+ }
+
+ ///
+ /// Get interest rate by a given date
+ ///
+ /// The date
+ /// Interest rate on the given date
+ public decimal GetInterestRate(DateTime date)
+ {
+ return _riskFreeRate;
+ }
+ }
+}
diff --git a/Common/Data/FuncRiskFreeRateInterestRateModel.cs b/Common/Data/FuncRiskFreeRateInterestRateModel.cs
new file mode 100644
index 000000000000..c662efad6918
--- /dev/null
+++ b/Common/Data/FuncRiskFreeRateInterestRateModel.cs
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+namespace QuantConnect.Data
+{
+ ///
+ /// Constant risk free rate interest rate model
+ ///
+ public class FuncRiskFreeRateInterestRateModel : IRiskFreeInterestRateModel
+ {
+ private readonly Func _getInterestRateFunc;
+
+ ///
+ /// Create class instance of interest rate provider
+ ///
+ public FuncRiskFreeRateInterestRateModel(Func getInterestRateFunc)
+ {
+ _getInterestRateFunc = getInterestRateFunc;
+ }
+
+ ///
+ /// Get interest rate by a given date
+ ///
+ /// The date
+ /// Interest rate on the given date
+ public decimal GetInterestRate(DateTime date)
+ {
+ return _getInterestRateFunc(date);
+ }
+ }
+}
diff --git a/Common/Data/IRiskFreeInterestRateModel.cs b/Common/Data/IRiskFreeInterestRateModel.cs
new file mode 100644
index 000000000000..04a4b1f90743
--- /dev/null
+++ b/Common/Data/IRiskFreeInterestRateModel.cs
@@ -0,0 +1,64 @@
+/*
+ * 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 System.Linq;
+
+namespace QuantConnect.Data
+{
+ ///
+ /// Represents a model that provides risk free interest rate data
+ ///
+ public interface IRiskFreeInterestRateModel
+ {
+ ///
+ /// Get interest rate by a given date
+ ///
+ /// The date
+ /// Interest rate on the given date
+ decimal GetInterestRate(DateTime date);
+ }
+
+ ///
+ /// Provide extension and static methods for
+ ///
+ public static class RiskFreeInterestRateModelExtensions
+ {
+ ///
+ /// Gets the average risk free annual return rate
+ ///
+ /// The interest rate model
+ /// Start date to calculate the average
+ /// End date to calculate the average
+ public static decimal GetRiskFreeRate(this IRiskFreeInterestRateModel model, DateTime startDate, DateTime endDate)
+ {
+ return model.GetAverageRiskFreeRate(Time.EachDay(startDate, endDate));
+ }
+
+ ///
+ /// Gets the average Risk Free Rate from the interest rate of the given dates
+ ///
+ /// The interest rate model
+ ///
+ /// Collection of dates from which the interest rates will be computed and then the average of them
+ ///
+ public static decimal GetAverageRiskFreeRate(this IRiskFreeInterestRateModel model, IEnumerable dates)
+ {
+ var interestRates = dates.Select(x => model.GetInterestRate(x)).DefaultIfEmpty(0);
+ return interestRates.Average();
+ }
+ }
+}
diff --git a/Common/Data/InterestRateProvider.cs b/Common/Data/InterestRateProvider.cs
index 1803b7686d27..b7dadd202969 100644
--- a/Common/Data/InterestRateProvider.cs
+++ b/Common/Data/InterestRateProvider.cs
@@ -28,38 +28,54 @@ namespace QuantConnect.Data
///
/// Fed US Primary Credit Rate at given date
///
- public class InterestRateProvider
+ public class InterestRateProvider : IRiskFreeInterestRateModel
{
- private static readonly DateTime FirstInterestRateDate = new DateTime(1998, 1, 1);
+ private static readonly DateTime _firstInterestRateDate = new DateTime(1998, 1, 1);
+ private static DateTime _lastInterestRateDate;
+ private static Dictionary _riskFreeRateProvider;
+ private static readonly object _lock = new();
///
/// Default Risk Free Rate of 1%
///
- public static decimal DefaultRiskFreeRate { get; } = 0.01m;
-
- private DateTime _lastInterestRateDate;
- private Dictionary _riskFreeRateProvider;
+ public static readonly decimal DefaultRiskFreeRate = 0.01m;
///
- /// Create class instance of interest rate provider
+ /// Lazily loads the interest rate provider from disk and returns it
///
- public InterestRateProvider()
+ private IReadOnlyDictionary RiskFreeRateProvider
{
- LoadInterestRateProvider();
+ get
+ {
+ // let's not lock if the provider is already loaded
+ if (_riskFreeRateProvider != null)
+ {
+ return _riskFreeRateProvider;
+ }
+
+ lock (_lock)
+ {
+ if (_riskFreeRateProvider == null)
+ {
+ LoadInterestRateProvider();
+ }
+ return _riskFreeRateProvider;
+ }
+ }
}
///
- /// Get interest rate by a given datetime
+ /// Get interest rate by a given date
///
- ///
- /// interest rate of the given date
- public decimal GetInterestRate(DateTime dateTime)
+ /// The date
+ /// Interest rate on the given date
+ public decimal GetInterestRate(DateTime date)
{
- if (!_riskFreeRateProvider.TryGetValue(dateTime.Date, out var interestRate))
+ if (!RiskFreeRateProvider.TryGetValue(date.Date, out var interestRate))
{
- return dateTime < FirstInterestRateDate
- ? _riskFreeRateProvider[FirstInterestRateDate]
- : _riskFreeRateProvider[_lastInterestRateDate];
+ return date < _firstInterestRateDate
+ ? RiskFreeRateProvider[_firstInterestRateDate]
+ : RiskFreeRateProvider[_lastInterestRateDate];
}
return interestRate;
@@ -77,7 +93,7 @@ protected void LoadInterestRateProvider()
_lastInterestRateDate = DateTime.UtcNow.Date;
// Sparse the discrete data points into continuous credit rate data for every day
- for (var date = FirstInterestRateDate; date <= _lastInterestRateDate; date = date.AddDays(1))
+ for (var date = _firstInterestRateDate; date <= _lastInterestRateDate; date = date.AddDays(1))
{
if (!_riskFreeRateProvider.TryGetValue(date, out var currentRate))
{
@@ -151,7 +167,7 @@ public static bool TryParse(string csvLine, out DateTime date, out decimal inter
return false;
}
- // Unit conversion from %
+ // Unit conversion from %
interestRate /= 100;
return true;
}
diff --git a/Common/Interfaces/IAlgorithm.cs b/Common/Interfaces/IAlgorithm.cs
index 3092effbc5c4..48788585a697 100644
--- a/Common/Interfaces/IAlgorithm.cs
+++ b/Common/Interfaces/IAlgorithm.cs
@@ -132,6 +132,14 @@ BrokerageName BrokerageName
get;
}
+ ///
+ /// Gets the risk free interest rate model used to get the interest rates
+ ///
+ IRiskFreeInterestRateModel RiskFreeInterestRateModel
+ {
+ get;
+ }
+
///
/// Gets the brokerage message handler used to decide what to do
/// with each message sent from the brokerage
diff --git a/Common/Python/RiskFreeInterestRateModelPythonWrapper.cs b/Common/Python/RiskFreeInterestRateModelPythonWrapper.cs
new file mode 100644
index 000000000000..d8c31a7c8944
--- /dev/null
+++ b/Common/Python/RiskFreeInterestRateModelPythonWrapper.cs
@@ -0,0 +1,64 @@
+/*
+ * 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 Python.Runtime;
+using QuantConnect.Data;
+
+namespace QuantConnect.Python
+{
+ ///
+ /// Wraps a object that represents a risk-free interest rate model
+ ///
+ public class RiskFreeInterestRateModelPythonWrapper : IRiskFreeInterestRateModel
+ {
+ private readonly dynamic _model;
+
+ ///
+ /// Constructor for initializing the class with wrapped object
+ ///
+ /// Represents a security's model of buying power
+ public RiskFreeInterestRateModelPythonWrapper(PyObject model)
+ {
+ _model = model.ValidateImplementationOf();
+ }
+
+ ///
+ /// Get interest rate by a given date
+ ///
+ /// The date
+ /// Interest rate on the given date
+ public decimal GetInterestRate(DateTime date)
+ {
+ using var _ = Py.GIL();
+ return (_model.GetInterestRate(date) as PyObject).GetAndDispose();
+ }
+
+ ///
+ /// Converts a object into a object, wrapping it if necessary
+ ///
+ /// The Python model
+ /// The converted instance
+ public static IRiskFreeInterestRateModel FromPyObject(PyObject model)
+ {
+ if (!model.TryConvert(out IRiskFreeInterestRateModel riskFreeInterestRateModel))
+ {
+ riskFreeInterestRateModel = new RiskFreeInterestRateModelPythonWrapper(model);
+ }
+
+ return riskFreeInterestRateModel;
+ }
+ }
+}
diff --git a/Common/Statistics/AlgorithmPerformance.cs b/Common/Statistics/AlgorithmPerformance.cs
index ebcb5cf4a443..f32ca9e07fcb 100644
--- a/Common/Statistics/AlgorithmPerformance.cs
+++ b/Common/Statistics/AlgorithmPerformance.cs
@@ -13,7 +13,7 @@
* limitations under the License.
*/
-using QuantConnect.Securities;
+using QuantConnect.Data;
using System;
using System.Collections.Generic;
@@ -51,6 +51,7 @@ public class AlgorithmPerformance
/// The algorithm starting capital
/// Number of winning transactions
/// Number of losing transactions
+ /// The risk free interest rate model to use
public AlgorithmPerformance(
List trades,
SortedDictionary profitLoss,
@@ -60,12 +61,13 @@ public AlgorithmPerformance(
List listBenchmark,
decimal startingCapital,
int winningTransactions,
- int losingTransactions)
+ int losingTransactions,
+ IRiskFreeInterestRateModel riskFreeInterestRateModel)
{
TradeStatistics = new TradeStatistics(trades);
PortfolioStatistics = new PortfolioStatistics(profitLoss, equity, portfolioTurnover, listPerformance, listBenchmark, startingCapital,
- winCount: winningTransactions, lossCount: losingTransactions);
+ riskFreeInterestRateModel, winCount: winningTransactions, lossCount: losingTransactions);
ClosedTrades = trades;
}
diff --git a/Common/Statistics/PortfolioStatistics.cs b/Common/Statistics/PortfolioStatistics.cs
index 0f023dccc3f9..dd151b8b084d 100644
--- a/Common/Statistics/PortfolioStatistics.cs
+++ b/Common/Statistics/PortfolioStatistics.cs
@@ -28,8 +28,6 @@ namespace QuantConnect.Statistics
///
public class PortfolioStatistics
{
- private static Lazy _interestRateProvider = new Lazy();
-
///
/// The average rate of return for winning trades
///
@@ -162,6 +160,7 @@ public class PortfolioStatistics
/// The list of algorithm performance values
/// The list of benchmark values
/// The algorithm starting capital
+ /// The risk free interest rate model to use
/// The number of trading days per year
///
/// The number of wins, including ITM options with profitLoss less than 0.
@@ -175,6 +174,7 @@ public PortfolioStatistics(
List listPerformance,
List listBenchmark,
decimal startingCapital,
+ IRiskFreeInterestRateModel riskFreeInterestRateModel,
int tradingDaysPerYear = 252,
int? winCount = null,
int? lossCount = null)
@@ -248,7 +248,7 @@ public PortfolioStatistics(
var benchmarkAnnualPerformance = GetAnnualPerformance(listBenchmark, tradingDaysPerYear);
var annualPerformance = GetAnnualPerformance(listPerformance, tradingDaysPerYear);
- var riskFreeRate = GetAverageRiskFreeRate(equity.Select(x => x.Key));
+ var riskFreeRate = riskFreeInterestRateModel.GetAverageRiskFreeRate(equity.Select(x => x.Key));
SharpeRatio = AnnualStandardDeviation == 0 ? 0 : (annualPerformance - riskFreeRate) / AnnualStandardDeviation;
var benchmarkVariance = listBenchmark.Variance();
@@ -274,27 +274,6 @@ public PortfolioStatistics()
{
}
- ///
- /// Gets the average risk free annual return rate
- ///
- /// Start date to calculate the average
- /// End date to calculate the average
- public static decimal GetRiskFreeRate(DateTime startDate, DateTime endDate)
- {
- return GetAverageRiskFreeRate(Time.EachDay(startDate, endDate));
- }
-
- ///
- /// Gets the average Risk Free Rate from the interest rate of the given dates
- ///
- /// Collection of dates from which the interest rates will be computed
- /// and then the average of them
- public static decimal GetAverageRiskFreeRate(IEnumerable dates)
- {
- var interestRates = dates.Select(x => _interestRateProvider.Value.GetInterestRate(x)).DefaultIfEmpty(0);
- return interestRates.Average();
- }
-
///
/// Drawdown maximum percentage.
///
diff --git a/Common/Statistics/StatisticsBuilder.cs b/Common/Statistics/StatisticsBuilder.cs
index 18b42c7695c2..1e919ab15dce 100644
--- a/Common/Statistics/StatisticsBuilder.cs
+++ b/Common/Statistics/StatisticsBuilder.cs
@@ -17,6 +17,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
+using QuantConnect.Data;
using QuantConnect.Securities;
using QuantConnect.Util;
@@ -44,6 +45,7 @@ public static class StatisticsBuilder
///
/// The transaction manager to get number of winning and losing transactions
///
+ /// The risk free interest rate model to use
/// Returns a object
public static StatisticsResults Generate(
List trades,
@@ -57,7 +59,8 @@ public static StatisticsResults Generate(
int totalTransactions,
CapacityEstimate estimatedStrategyCapacity,
string accountCurrencySymbol,
- SecurityTransactionManager transactions)
+ SecurityTransactionManager transactions,
+ IRiskFreeInterestRateModel riskFreeInterestRateModel)
{
var equity = ChartPointToDictionary(pointsEquity);
@@ -65,9 +68,9 @@ public static StatisticsResults Generate(
var lastDate = equity.Keys.LastOrDefault().Date;
var totalPerformance = GetAlgorithmPerformance(firstDate, lastDate, trades, profitLoss, equity, pointsPerformance, pointsBenchmark,
- pointsPortfolioTurnover, startingCapital, transactions);
+ pointsPortfolioTurnover, startingCapital, transactions, riskFreeInterestRateModel);
var rollingPerformances = GetRollingPerformances(firstDate, lastDate, trades, profitLoss, equity, pointsPerformance, pointsBenchmark,
- pointsPortfolioTurnover, startingCapital, transactions);
+ pointsPortfolioTurnover, startingCapital, transactions, riskFreeInterestRateModel);
var summary = GetSummary(totalPerformance, estimatedStrategyCapacity, totalFees, totalTransactions, accountCurrencySymbol);
return new StatisticsResults(totalPerformance, rollingPerformances, summary);
@@ -88,6 +91,7 @@ public static StatisticsResults Generate(
///
/// The transaction manager to get number of winning and losing transactions
///
+ /// The risk free interest rate model to use
/// The algorithm performance
private static AlgorithmPerformance GetAlgorithmPerformance(
DateTime fromDate,
@@ -99,7 +103,8 @@ private static AlgorithmPerformance GetAlgorithmPerformance(
List pointsBenchmark,
List pointsPortfolioTurnover,
decimal startingCapital,
- SecurityTransactionManager transactions)
+ SecurityTransactionManager transactions,
+ IRiskFreeInterestRateModel riskFreeInterestRateModel)
{
var periodEquity = new SortedDictionary(equity.Where(x => x.Key.Date >= fromDate && x.Key.Date < toDate.AddDays(1)).ToDictionary(x => x.Key, y => y.Value));
@@ -136,7 +141,7 @@ private static AlgorithmPerformance GetAlgorithmPerformance(
var runningCapital = equity.Count == periodEquity.Count ? startingCapital : periodEquity.Values.FirstOrDefault();
return new AlgorithmPerformance(periodTrades, periodProfitLoss, periodEquity, portfolioTurnover, listPerformance, listBenchmark,
- runningCapital, periodWinCount, periodLossCount);
+ runningCapital, periodWinCount, periodLossCount, riskFreeInterestRateModel);
}
///
@@ -154,6 +159,7 @@ private static AlgorithmPerformance GetAlgorithmPerformance(
///
/// The transaction manager to get number of winning and losing transactions
///
+ /// The risk free interest rate model to use
/// A dictionary with the rolling performances
private static Dictionary GetRollingPerformances(
DateTime firstDate,
@@ -165,7 +171,8 @@ private static Dictionary GetRollingPerformances(
List pointsBenchmark,
List pointsPortfolioTurnover,
decimal startingCapital,
- SecurityTransactionManager transactions)
+ SecurityTransactionManager transactions,
+ IRiskFreeInterestRateModel riskFreeInterestRateModel)
{
var rollingPerformances = new Dictionary();
@@ -178,7 +185,7 @@ private static Dictionary GetRollingPerformances(
{
var key = $"M{monthPeriod}_{period.EndDate.ToStringInvariant("yyyyMMdd")}";
var periodPerformance = GetAlgorithmPerformance(period.StartDate, period.EndDate, trades, profitLoss, equity, pointsPerformance,
- pointsBenchmark, pointsPortfolioTurnover, startingCapital, transactions);
+ pointsBenchmark, pointsPortfolioTurnover, startingCapital, transactions, riskFreeInterestRateModel);
rollingPerformances[key] = periodPerformance;
}
}
diff --git a/Engine/Results/BaseResultsHandler.cs b/Engine/Results/BaseResultsHandler.cs
index 3c4e99a903c5..b1afa93a761c 100644
--- a/Engine/Results/BaseResultsHandler.cs
+++ b/Engine/Results/BaseResultsHandler.cs
@@ -848,7 +848,7 @@ protected StatisticsResults GenerateStatisticsResults(Dictionary
statisticsResults = StatisticsBuilder.Generate(trades, profitLoss, equity.Values, performance.Values, benchmark.Values,
portfolioTurnover.Values, StartingPortfolioValue, Algorithm.Portfolio.TotalFees, totalTransactions,
- estimatedStrategyCapacity, AlgorithmCurrencySymbol, Algorithm.Transactions);
+ estimatedStrategyCapacity, AlgorithmCurrencySymbol, Algorithm.Transactions, Algorithm.RiskFreeInterestRateModel);
}
statisticsResults.AddCustomSummaryStatistics(_customSummaryStatistics);
diff --git a/Indicators/SharpeRatio.cs b/Indicators/SharpeRatio.cs
index 99961f02bf70..262870c21fbc 100644
--- a/Indicators/SharpeRatio.cs
+++ b/Indicators/SharpeRatio.cs
@@ -13,6 +13,10 @@
* limitations under the License.
*/
+using Python.Runtime;
+using QuantConnect.Data;
+using QuantConnect.Python;
+
namespace QuantConnect.Indicators
{
///
@@ -32,11 +36,21 @@ public class SharpeRatio : IndicatorBase, IIndicatorWarmUpPe
///
private readonly int _period;
+ ///
+ /// Risk-free rate model
+ ///
+ private readonly IRiskFreeInterestRateModel _riskFreeInterestRateModel;
+
///
/// RateOfChange indicator for calculating the sharpe ratio
///
protected RateOfChange RateOfChange { get; }
+ ///
+ /// RiskFreeRate indicator for calculating the sharpe ratio
+ ///
+ protected Identity RiskFreeRate { get; }
+
///
/// Indicator to store the calculation of the sharpe ratio
///
@@ -62,23 +76,66 @@ public class SharpeRatio : IndicatorBase, IIndicatorWarmUpPe
///
/// The name of this indicator
/// Period of historical observation for sharpe ratio calculation
- /// Risk-free rate for sharpe ratio calculation
- public SharpeRatio(string name, int period, decimal riskFreeRate = 0.0m)
+ /// Risk-free rate model
+ public SharpeRatio(string name, int period, IRiskFreeInterestRateModel riskFreeRateModel)
: base(name)
{
_period = period;
+ _riskFreeInterestRateModel = riskFreeRateModel;
// calculate sharpe ratio using indicators
RateOfChange = new RateOfChange(1);
- Numerator = RateOfChange.SMA(period).Minus(riskFreeRate);
+ RiskFreeRate = new Identity(name + "_RiskFreeRate");
+ Numerator = RateOfChange.SMA(period).Minus(RiskFreeRate);
var denominator = new StandardDeviation(period).Of(RateOfChange);
Ratio = Numerator.Over(denominator);
- // define warmup value;
+ // define warmup value;
// _roc is the base of our indicator chain + period of STD and SMA
WarmUpPeriod = RateOfChange.WarmUpPeriod + period;
}
+ ///
+ /// Creates a new Sharpe Ratio indicator using the specified periods
+ ///
+ /// Period of historical observation for sharpe ratio calculation
+ /// Risk-free rate model
+ public SharpeRatio(int period, IRiskFreeInterestRateModel riskFreeRateModel)
+ : this($"SR({period})", period, riskFreeRateModel)
+ {
+ }
+
+ ///
+ /// Creates a new Sharpe Ratio indicator using the specified period using a Python risk free rate model
+ ///
+ /// Period of historical observation for sharpe ratio calculation
+ /// Risk-free rate model
+ public SharpeRatio(string name, int period, PyObject riskFreeRateModel)
+ : this(name, period, RiskFreeInterestRateModelPythonWrapper.FromPyObject(riskFreeRateModel))
+ {
+ }
+
+ ///
+ /// Creates a new Sharpe Ratio indicator using the specified period using a Python risk free rate model
+ ///
+ /// Period of historical observation for sharpe ratio calculation
+ /// Risk-free rate model
+ public SharpeRatio(int period, PyObject riskFreeRateModel)
+ : this(period, RiskFreeInterestRateModelPythonWrapper.FromPyObject(riskFreeRateModel))
+ {
+ }
+
+ ///
+ /// Creates a new Sharpe Ratio indicator using the specified periods
+ ///
+ /// The name of this indicator
+ /// Period of historical observation for sharpe ratio calculation
+ /// Risk-free rate for sharpe ratio calculation
+ public SharpeRatio(string name, int period, decimal riskFreeRate = 0.0m)
+ : this(name, period, new ConstantRiskFreeRateInterestRateModel(riskFreeRate))
+ {
+ }
+
///
/// Creates a new SharpeRatio indicator using the specified periods
///
@@ -96,6 +153,7 @@ public SharpeRatio(int period, decimal riskFreeRate = 0.0m)
/// A new value for this indicator
protected override decimal ComputeNextValue(IndicatorDataPoint input)
{
+ RiskFreeRate.Update(input.Time, _riskFreeInterestRateModel.GetInterestRate(input.Time));
RateOfChange.Update(input);
return Ratio;
}
diff --git a/Report/Rolling.cs b/Report/Rolling.cs
index 469f427de296..ce5aecda976c 100644
--- a/Report/Rolling.cs
+++ b/Report/Rolling.cs
@@ -19,6 +19,7 @@
using QuantConnect.Statistics;
using System.Collections.Generic;
using System.Linq;
+using QuantConnect.Data;
namespace QuantConnect.Report
{
@@ -27,6 +28,8 @@ namespace QuantConnect.Report
///
public static class Rolling
{
+ private static readonly IRiskFreeInterestRateModel _interestRateProvider = new InterestRateProvider();
+
///
/// Calculate the rolling beta with the given window size (in days)
///
@@ -76,7 +79,7 @@ public static Series Beta(SortedList perform
/// Rolling sharpe ratio
public static Series Sharpe(Series equityCurve, int months)
{
- var riskFreeRate = (double)PortfolioStatistics.GetAverageRiskFreeRate(equityCurve.Keys);
+ var riskFreeRate = (double)_interestRateProvider.GetAverageRiskFreeRate(equityCurve.Keys);
if (equityCurve.IsEmpty)
{
return equityCurve;
diff --git a/Research/QuantBook.cs b/Research/QuantBook.cs
index 1af482be15f1..6d0f1e3b8646 100644
--- a/Research/QuantBook.cs
+++ b/Research/QuantBook.cs
@@ -769,7 +769,7 @@ public PyDict GetPortfolioStatistics(PyObject dataFrame)
var startingCapital = Convert.ToDecimal(dictEquity.FirstOrDefault().Value);
// Compute portfolio statistics
- var stats = new PortfolioStatistics(profitLoss, equity, new(), listPerformance, listBenchmark, startingCapital);
+ var stats = new PortfolioStatistics(profitLoss, equity, new(), listPerformance, listBenchmark, startingCapital, RiskFreeInterestRateModel);
result.SetItem("Average Win (%)", Convert.ToDouble(stats.AverageWinRate * 100).ToPython());
result.SetItem("Average Loss (%)", Convert.ToDouble(stats.AverageLossRate * 100).ToPython());
diff --git a/Tests/Algorithm/AlgorithmIndicatorsTests.cs b/Tests/Algorithm/AlgorithmIndicatorsTests.cs
index 19de0764c286..1ef047f3b732 100644
--- a/Tests/Algorithm/AlgorithmIndicatorsTests.cs
+++ b/Tests/Algorithm/AlgorithmIndicatorsTests.cs
@@ -66,5 +66,32 @@ public void IndicatorsPassSelectorToWarmUp()
Assert.IsTrue(indicator.IsReady);
mockSelector.Verify(_ => _(It.IsAny()), Times.Exactly(indicator.WarmUpPeriod));
}
+
+ [Test]
+ public void SharpeRatioIndicatorUsesAlgorithmsRiskFreeRateModelSetAfterIndicatorRegistration()
+ {
+ // Register indicator
+ var sharpeRatio = _algorithm.SR(Symbols.SPY, 10);
+
+ // Setup risk free rate model
+ var interestRateProviderMock = new Mock();
+ var reference = new DateTime(2023, 11, 21, 10, 0, 0);
+ interestRateProviderMock.Setup(x => x.GetInterestRate(reference)).Verifiable();
+
+ // Update indicator
+ sharpeRatio.Update(new IndicatorDataPoint(Symbols.SPY, reference, 300m));
+
+ // Our interest rate provider shouldn't have been called yet since it's hasn't been set to the algorithm
+ interestRateProviderMock.Verify(x => x.GetInterestRate(reference), Times.Never);
+
+ // Set the interest rate provider to the algorithm
+ _algorithm.SetRiskFreeInterestRateModel(interestRateProviderMock.Object);
+
+ // Update indicator
+ sharpeRatio.Update(new IndicatorDataPoint(Symbols.SPY, reference, 300m));
+
+ // Our interest rate provider should have been called once
+ interestRateProviderMock.Verify(x => x.GetInterestRate(reference), Times.Once);
+ }
}
}
diff --git a/Tests/Common/Statistics/PortfolioStatisticsTests.cs b/Tests/Common/Statistics/PortfolioStatisticsTests.cs
index bd7dff926da5..2662a2a0aacc 100644
--- a/Tests/Common/Statistics/PortfolioStatisticsTests.cs
+++ b/Tests/Common/Statistics/PortfolioStatisticsTests.cs
@@ -17,6 +17,7 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
+using QuantConnect.Data;
using QuantConnect.Statistics;
namespace QuantConnect.Tests.Common.Statistics
@@ -36,7 +37,7 @@ public void ITMOptionAssignment([Values] bool win)
var lossCount = trades.Count - winCount;
var statistics = new PortfolioStatistics(profitLoss, new SortedDictionary(),
new SortedDictionary(), new List { 0, 0 }, new List { 0, 0 }, 100000,
- winCount: winCount, lossCount: lossCount);
+ new InterestRateProvider(), winCount: winCount, lossCount: lossCount);
if (win)
{
diff --git a/Tests/Common/Statistics/StatisticsBuilderTests.cs b/Tests/Common/Statistics/StatisticsBuilderTests.cs
index fab46de6b2b0..8903e13281eb 100644
--- a/Tests/Common/Statistics/StatisticsBuilderTests.cs
+++ b/Tests/Common/Statistics/StatisticsBuilderTests.cs
@@ -17,6 +17,7 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
+using QuantConnect.Data;
using QuantConnect.Statistics;
namespace QuantConnect.Tests.Common.Statistics
@@ -75,7 +76,8 @@ public void MisalignedValues_ShouldThrow_DuringGeneration()
"$",
new QuantConnect.Securities.SecurityTransactionManager(
null,
- new QuantConnect.Securities.SecurityManager(new TimeKeeper(DateTime.UtcNow))));
+ new QuantConnect.Securities.SecurityManager(new TimeKeeper(DateTime.UtcNow))),
+ new InterestRateProvider());
}, "Misaligned values provided, but we still generate statistics");
}
}
diff --git a/Tests/Indicators/SharpeRatioTests.cs b/Tests/Indicators/SharpeRatioTests.cs
index 985c4960994e..42b6742ca7c1 100644
--- a/Tests/Indicators/SharpeRatioTests.cs
+++ b/Tests/Indicators/SharpeRatioTests.cs
@@ -13,16 +13,19 @@
* limitations under the License.
*/
+using Moq;
using NUnit.Framework;
-using QuantConnect.Data.Market;
+using Python.Runtime;
+using QuantConnect.Data;
using QuantConnect.Indicators;
using System;
+using System.Linq;
-namespace QuantConnect.Tests.Indicators
+namespace QuantConnect.Tests.Indicators
{
[TestFixture]
public class SharpeRatioTests : CommonIndicatorTests
- {
+ {
protected override IndicatorBase CreateIndicator()
{
return new SharpeRatio("SR", 10);
@@ -33,48 +36,131 @@ protected override IndicatorBase CreateIndicator()
protected override string TestColumnName => "SR_10";
[Test]
- public void TestTradeBarsWithSameValue()
+ public void TestTradeBarsWithSameValue()
{
// With the value not changing, the indicator should return default value 0m.
var sr = new SharpeRatio("SR", 10);
-
+
// push the value 100000 into the indicator 20 times (sharpeRatioPeriod + movingAveragePeriod)
for(int i = 0; i < 20; i++) {
IndicatorDataPoint point = new IndicatorDataPoint(new DateTime(), 100000m);
sr.Update(point);
}
-
+
Assert.AreEqual(sr.Current.Value, 0m);
}
-
+
[Test]
- public void TestTradeBarsWithDifferingValue()
+ public void TestTradeBarsWithDifferingValue()
{
// With the value changing, the indicator should return a value that is not the default 0m.
var sr = new SharpeRatio("SR", 10);
-
+
// push the value 100000 into the indicator 20 times (sharpeRatioPeriod + movingAveragePeriod)
for(int i = 0; i < 20; i++) {
IndicatorDataPoint point = new IndicatorDataPoint(new DateTime(), 100000m + i);
sr.Update(point);
}
-
+
Assert.AreNotEqual(sr.Current.Value, 0m);
}
-
+
[Test]
public void TestDivByZero()
{
// With the value changing, the indicator should return a value that is not the default 0m.
var sr = new SharpeRatio("SR", 10);
-
+
// push the value 100000 into the indicator 20 times (sharpeRatioPeriod + movingAveragePeriod)
- for(int i = 0; i < 20; i++) {
+ for(int i = 0; i < 20; i++)
+ {
IndicatorDataPoint point = new IndicatorDataPoint(new DateTime(), 0);
sr.Update(point);
}
-
+
Assert.AreEqual(sr.Current.Value, 0m);
}
+
+ [Test]
+ public void UsesRiskFreeInterestRateModel()
+ {
+ const int count = 20;
+ var dates = Enumerable.Range(0, count).Select(i => new DateTime(2023, 11, 21, 10, 0, 0) + TimeSpan.FromMinutes(i)).ToList();
+ var interestRateValues = Enumerable.Range(0, count).Select(i => 0m + (10 - 0m) * (i / (count - 1m))).ToList();
+
+ var interestRateProviderMock = new Mock();
+
+ // Set up
+ for (int i = 0; i < count; i++)
+ {
+ interestRateProviderMock.Setup(x => x.GetInterestRate(dates[i])).Returns(interestRateValues[i]).Verifiable();
+ }
+
+ var sr = new TestableSharpeRatio("SR", 10, interestRateProviderMock.Object);
+
+ // Push the value 100000 into the indicator 20 times (sharpeRatioPeriod + movingAveragePeriod)
+ for (int i = 0; i < count; i++)
+ {
+ sr.Update(new IndicatorDataPoint(dates[i], 100000m + i));
+ Assert.AreEqual(interestRateValues[i], sr.RiskFreeRatePublic.Current.Value);
+ }
+
+ // Assert
+ Assert.IsTrue(sr.IsReady);
+ interestRateProviderMock.Verify(x => x.GetInterestRate(It.IsAny()), Times.Exactly(dates.Count));
+ for (int i = 0; i < count; i++)
+ {
+ interestRateProviderMock.Verify(x => x.GetInterestRate(dates[i]), Times.Once);
+ }
+ }
+
+ [Test]
+ public void UsesPythonDefinedRiskFreeInterestRateModel()
+ {
+ using var _ = Py.GIL();
+
+ var module = PyModule.FromString(Guid.NewGuid().ToString(), @"
+from AlgorithmImports import *
+
+class TestRiskFreeInterestRateModel:
+ CallCount = 0
+
+ def GetInterestRate(self, date: datetime) -> float:
+ TestRiskFreeInterestRateModel.CallCount += 1
+ return 0.5
+
+def getSharpeRatioIndicator() -> SharpeRatio:
+ return SharpeRatio(""SR"", 10, TestRiskFreeInterestRateModel())
+ ");
+
+ var sr = module.GetAttr("getSharpeRatioIndicator").Invoke().GetAndDispose();
+ var modelClass = module.GetAttr("TestRiskFreeInterestRateModel");
+
+ var reference = new DateTime(2023, 11, 21, 10, 0, 0);
+ for (int i = 0; i < 20; i++)
+ {
+ sr.Update(new IndicatorDataPoint(reference + TimeSpan.FromMinutes(i), 100000m + i));
+ Assert.AreEqual(i + 1, modelClass.GetAttr("CallCount").GetAndDispose());
+ }
+ }
+
+ private class TestableSharpeRatio : SharpeRatio
+ {
+ public Identity RiskFreeRatePublic => RiskFreeRate;
+
+ public TestableSharpeRatio(string name, int period, IRiskFreeInterestRateModel riskFreeRateModel)
+ : base(name, period, riskFreeRateModel)
+ {
+ }
+ public TestableSharpeRatio(int period, decimal riskFreeRate = 0.0m)
+ : base(period, riskFreeRate)
+ {
+ }
+
+ public TestableSharpeRatio(string name, int period, decimal riskFreeRate = 0.0m)
+ : base(name, period, riskFreeRate)
+ {
+ }
+ }
}
-}
\ No newline at end of file
+}
diff --git a/Tests/ToolBox/RandomDataGenerator/RandomDataGeneratorTests.cs b/Tests/ToolBox/RandomDataGenerator/RandomDataGeneratorTests.cs
index c238272f1eed..e5312c805520 100644
--- a/Tests/ToolBox/RandomDataGenerator/RandomDataGeneratorTests.cs
+++ b/Tests/ToolBox/RandomDataGenerator/RandomDataGeneratorTests.cs
@@ -117,6 +117,8 @@ public void RandomGeneratorProducesValuesBoundedForEquitiesWhenSplit(string star
}
}
+ private static readonly IRiskFreeInterestRateModel _interestRateProvider = new InterestRateProvider();
+
private static SecurityService GetSecurityService(RandomDataGeneratorSettings settings, SecurityManager securityManager)
{
var securityService = new SecurityService(
@@ -135,7 +137,8 @@ private static SecurityService GetSecurityService(RandomDataGeneratorSettings se
// from settings
if (security is Option option)
{
- option.PriceModel = OptionPriceModels.Create(settings.OptionPriceEngineName, Statistics.PortfolioStatistics.GetRiskFreeRate(settings.Start, settings.End));
+ option.PriceModel = OptionPriceModels.Create(settings.OptionPriceEngineName,
+ _interestRateProvider.GetRiskFreeRate(settings.Start, settings.End));
}
})),
RegisteredSecurityDataTypesProvider.Null,
diff --git a/ToolBox/RandomDataGenerator/RandomDataGeneratorProgram.cs b/ToolBox/RandomDataGenerator/RandomDataGeneratorProgram.cs
index bad490a7f238..91f2937f01b6 100644
--- a/ToolBox/RandomDataGenerator/RandomDataGeneratorProgram.cs
+++ b/ToolBox/RandomDataGenerator/RandomDataGeneratorProgram.cs
@@ -24,6 +24,7 @@
using System;
using System.Collections.Generic;
using QuantConnect.Logging;
+using QuantConnect.Data;
namespace QuantConnect.ToolBox.RandomDataGenerator
{
@@ -32,6 +33,8 @@ namespace QuantConnect.ToolBox.RandomDataGenerator
///
public static class RandomDataGeneratorProgram
{
+ private static readonly IRiskFreeInterestRateModel _interestRateProvider = new InterestRateProvider();
+
public static void RandomDataGenerator(
string startDateString,
string endDateString,
@@ -81,7 +84,7 @@ List tickers
Log.Error($"RandomDataGeneratorProgram(): Required parameter --start must be at least 19980101");
Environment.Exit(1);
}
-
+
var securityManager = new SecurityManager(new TimeKeeper(settings.Start, new[] { TimeZones.Utc }));
var securityService = new SecurityService(
new CashBook(),
@@ -99,7 +102,8 @@ List tickers
// from settings
if (security is Option option)
{
- option.PriceModel = OptionPriceModels.Create(settings.OptionPriceEngineName, Statistics.PortfolioStatistics.GetRiskFreeRate(settings.Start, settings.End));
+ option.PriceModel = OptionPriceModels.Create(settings.OptionPriceEngineName,
+ _interestRateProvider.GetRiskFreeRate(settings.Start, settings.End));
}
})),
RegisteredSecurityDataTypesProvider.Null,