diff --git a/Algorithm.CSharp/CustomOptionPriceModelRegressionAlgorithm.cs b/Algorithm.CSharp/CustomOptionPriceModelRegressionAlgorithm.cs index 157c6719dbbc..9567b519f0bd 100644 --- a/Algorithm.CSharp/CustomOptionPriceModelRegressionAlgorithm.cs +++ b/Algorithm.CSharp/CustomOptionPriceModelRegressionAlgorithm.cs @@ -84,6 +84,7 @@ public OptionPriceModelResult Evaluate(OptionPriceModelParameters parameters) var contract = parameters.Contract; var underlying = contract.UnderlyingLastPrice; var strike = contract.Strike; + var greeks = new Greeks(0.5m, 0.2m, 0.15m, 0.05m, 0.1m, 2.0m); decimal intrinsicValue; if (contract.Right == OptionRight.Call) @@ -93,10 +94,14 @@ public OptionPriceModelResult Evaluate(OptionPriceModelParameters parameters) else { intrinsicValue = Math.Max(0, strike - underlying); + // Delta and Rho are negative for a put + greeks.Delta *= -1; + greeks.Rho *= -1; } - var theoreticalPrice = intrinsicValue + 1.0m; - return new OptionPriceModelResult(theoreticalPrice, new Greeks(0.5m, 0.1m, 0.2m, -0.05m, 0.1m, 2.0m)); + var impliedVolatility = 0.2m; + + return new OptionPriceModelResult(theoreticalPrice, impliedVolatility, greeks); } } diff --git a/Algorithm.Python/CustomOptionPriceModelRegressionAlgorithm.py b/Algorithm.Python/CustomOptionPriceModelRegressionAlgorithm.py index 34bd2ba1e8ee..555f91f4d453 100644 --- a/Algorithm.Python/CustomOptionPriceModelRegressionAlgorithm.py +++ b/Algorithm.Python/CustomOptionPriceModelRegressionAlgorithm.py @@ -62,12 +62,17 @@ def evaluate(self, parameters): contract = parameters.contract underlying = contract.underlying_last_price strike = contract.strike + greeks = Greeks(0.5, 0.2, 0.15, 0.05, 0.1, 2.0) if contract.right == OptionRight.CALL: intrinsic = max(0, underlying - strike) else: intrinsic = max(0, strike - underlying) + # Delta and Rho are negative for a put + greeks.delta *= -1 + greeks.rho *= -1 theoretical_price = intrinsic + 1.0 + implied_volatility = 0.2 - return OptionPriceModelResult(theoretical_price, Greeks(0.5, 0.1, 0.2, -0.05, 0.1, 2.0)) \ No newline at end of file + return OptionPriceModelResult(theoretical_price, implied_volatility, greeks) diff --git a/Common/Securities/Option/OptionPriceModelResult.cs b/Common/Securities/Option/OptionPriceModelResult.cs index 417d0f6efc74..46e9eaa0c70a 100644 --- a/Common/Securities/Option/OptionPriceModelResult.cs +++ b/Common/Securities/Option/OptionPriceModelResult.cs @@ -13,6 +13,7 @@ * limitations under the License. */ +using Python.Runtime; using QuantConnect.Data.Market; using System; @@ -28,16 +29,13 @@ public class OptionPriceModelResult /// public static OptionPriceModelResult None { get; } = new(0, NullGreeks.Instance); - private readonly Lazy _greeks; - private readonly Lazy _impliedVolatility; + private Lazy _greeks; + private Lazy _impliedVolatility; /// /// Gets the theoretical price as computed by the /// - public decimal TheoreticalPrice - { - get; private set; - } + public decimal TheoreticalPrice { get; set; } /// /// Gets the implied volatility of the option contract @@ -48,6 +46,10 @@ public decimal ImpliedVolatility { return _impliedVolatility.Value; } + set + { + _impliedVolatility = new Lazy(() => value, isThreadSafe: false); + } } /// @@ -59,6 +61,17 @@ public Greeks Greeks { return _greeks.Value; } + set + { + _greeks = new Lazy(() => value, isThreadSafe: false); + } + } + + /// + /// Initializes a new instance of the class + /// + public OptionPriceModelResult() + { } /// @@ -73,6 +86,17 @@ public OptionPriceModelResult(decimal theoreticalPrice, Greeks greeks) _greeks = new Lazy(() => greeks, isThreadSafe: false); } + /// + /// Initializes a new instance of the class + /// + /// The theoretical price computed by the price model + /// The calculated implied volatility + /// The sensitivities (greeks) computed by the price model + public OptionPriceModelResult(decimal theoreticalPrice, decimal impliedVolatility, Greeks greeks) + : this(theoreticalPrice, () => impliedVolatility, () => greeks) + { + } + /// /// Initializes a new instance of the class with lazy calculations of implied volatility and greeks /// @@ -85,5 +109,16 @@ public OptionPriceModelResult(decimal theoreticalPrice, Func impliedVol _impliedVolatility = new Lazy(impliedVolatility, isThreadSafe: false); _greeks = new Lazy(greeks, isThreadSafe: false); } + + /// + /// Initializes a new instance of the class with lazy calculations of implied volatility and greeks + /// + /// The theoretical price computed by the price model + /// The calculated implied volatility + /// The sensitivities (greeks) computed by the price model + public OptionPriceModelResult(decimal theoreticalPrice, PyObject impliedVolatility, PyObject greeks) + : this(theoreticalPrice, impliedVolatility.SafeAs>(), greeks.SafeAs>()) + { + } } } diff --git a/Tests/Common/Securities/OptionPriceModelTests.cs b/Tests/Common/Securities/OptionPriceModelTests.cs index 0843358a7ac0..9098c927c0bf 100644 --- a/Tests/Common/Securities/OptionPriceModelTests.cs +++ b/Tests/Common/Securities/OptionPriceModelTests.cs @@ -15,6 +15,7 @@ using Moq; using NUnit.Framework; +using Python.Runtime; using QLNet; using QuantConnect.Data; using QuantConnect.Data.Market; @@ -24,7 +25,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -930,6 +930,35 @@ public void PriceModelEvaluateSpeedTest() Assert.Less(stopWatch.ElapsedMilliseconds, 2200); } + [TestCase(Language.CSharp)] + [TestCase(Language.Python)] + public void OptionPriceModelResultOverloadsAreConsistent(Language language) + { + var impliedVol = 0.25m; + var funcImpliedVol = new Func(() => impliedVol); + var funcGreeks = new Func(() => new ModeledGreeks(() => 0.01m, () => 0.02m, () => 0.03m, () => 0.04m, () => 0.05m, () => 0.06m)); + OptionPriceModelResult optionPriceModelResult = null; + if (language == Language.CSharp) + { + optionPriceModelResult = new OptionPriceModelResult(0.01m, funcImpliedVol, funcGreeks); + } + else + { + using (Py.GIL()) + { + optionPriceModelResult = new OptionPriceModelResult(0.01m, funcImpliedVol.ToPython(), funcGreeks.ToPython()); + } + } + + Assert.AreEqual(0.25m, optionPriceModelResult.ImpliedVolatility); + Assert.AreEqual(0.01m, optionPriceModelResult.Greeks.Delta); + Assert.AreEqual(0.02m, optionPriceModelResult.Greeks.Gamma); + Assert.AreEqual(0.03m, optionPriceModelResult.Greeks.Vega); + Assert.AreEqual(0.04m, optionPriceModelResult.Greeks.Theta); + Assert.AreEqual(0.05m, optionPriceModelResult.Greeks.Rho); + Assert.AreEqual(0.06m, optionPriceModelResult.Greeks.Lambda); + } + private static Symbol GetOptionSymbol(Symbol underlying, OptionStyle optionStyle, OptionRight optionRight, decimal strike = 192m, DateTime? expiry = null) { if (expiry == null)