Skip to content
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
9 changes: 7 additions & 2 deletions Algorithm.CSharp/CustomOptionPriceModelRegressionAlgorithm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
return OptionPriceModelResult(theoretical_price, implied_volatility, greeks)
47 changes: 41 additions & 6 deletions Common/Securities/Option/OptionPriceModelResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* limitations under the License.
*/

using Python.Runtime;
using QuantConnect.Data.Market;
using System;

Expand All @@ -28,16 +29,13 @@ public class OptionPriceModelResult
/// </summary>
public static OptionPriceModelResult None { get; } = new(0, NullGreeks.Instance);

private readonly Lazy<Greeks> _greeks;
private readonly Lazy<decimal> _impliedVolatility;
private Lazy<Greeks> _greeks;
private Lazy<decimal> _impliedVolatility;

/// <summary>
/// Gets the theoretical price as computed by the <see cref="IOptionPriceModel"/>
/// </summary>
public decimal TheoreticalPrice
{
get; private set;
}
public decimal TheoreticalPrice { get; set; }

/// <summary>
/// Gets the implied volatility of the option contract
Expand All @@ -48,6 +46,10 @@ public decimal ImpliedVolatility
{
return _impliedVolatility.Value;
}
set
{
_impliedVolatility = new Lazy<decimal>(() => value, isThreadSafe: false);
}
}

/// <summary>
Expand All @@ -59,6 +61,17 @@ public Greeks Greeks
{
return _greeks.Value;
}
set
{
_greeks = new Lazy<Greeks>(() => value, isThreadSafe: false);
}
}

/// <summary>
/// Initializes a new instance of the <see cref="OptionPriceModelResult"/> class
/// </summary>
public OptionPriceModelResult()
{
}

/// <summary>
Expand All @@ -73,6 +86,17 @@ public OptionPriceModelResult(decimal theoreticalPrice, Greeks greeks)
_greeks = new Lazy<Greeks>(() => greeks, isThreadSafe: false);
}

/// <summary>
/// Initializes a new instance of the <see cref="OptionPriceModelResult"/> class
/// </summary>
/// <param name="theoreticalPrice">The theoretical price computed by the price model</param>
/// <param name="impliedVolatility">The calculated implied volatility</param>
/// <param name="greeks">The sensitivities (greeks) computed by the price model</param>
public OptionPriceModelResult(decimal theoreticalPrice, decimal impliedVolatility, Greeks greeks)
: this(theoreticalPrice, () => impliedVolatility, () => greeks)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="OptionPriceModelResult"/> class with lazy calculations of implied volatility and greeks
/// </summary>
Expand All @@ -85,5 +109,16 @@ public OptionPriceModelResult(decimal theoreticalPrice, Func<decimal> impliedVol
_impliedVolatility = new Lazy<decimal>(impliedVolatility, isThreadSafe: false);
_greeks = new Lazy<Greeks>(greeks, isThreadSafe: false);
}

/// <summary>
/// Initializes a new instance of the <see cref="OptionPriceModelResult"/> class with lazy calculations of implied volatility and greeks
/// </summary>
/// <param name="theoreticalPrice">The theoretical price computed by the price model</param>
/// <param name="impliedVolatility">The calculated implied volatility</param>
/// <param name="greeks">The sensitivities (greeks) computed by the price model</param>
public OptionPriceModelResult(decimal theoreticalPrice, PyObject impliedVolatility, PyObject greeks)
: this(theoreticalPrice, impliedVolatility.SafeAs<Func<decimal>>(), greeks.SafeAs<Func<Greeks>>())
{
}
}
}
31 changes: 30 additions & 1 deletion Tests/Common/Securities/OptionPriceModelTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

using Moq;
using NUnit.Framework;
using Python.Runtime;
using QLNet;
using QuantConnect.Data;
using QuantConnect.Data.Market;
Expand All @@ -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;
Expand Down Expand Up @@ -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<decimal>(() => impliedVol);
var funcGreeks = new Func<Greeks>(() => 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)
Expand Down
Loading