Skip to content

Commit 63209a3

Browse files
authored
Add Python overload for OptionPriceModelResult (#9277)
* Add python overload for OptionPriceModelResult constructor * Solve review comments * Update regression algorithms
1 parent 769843b commit 63209a3

4 files changed

Lines changed: 84 additions & 10 deletions

File tree

Algorithm.CSharp/CustomOptionPriceModelRegressionAlgorithm.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ public OptionPriceModelResult Evaluate(OptionPriceModelParameters parameters)
8484
var contract = parameters.Contract;
8585
var underlying = contract.UnderlyingLastPrice;
8686
var strike = contract.Strike;
87+
var greeks = new Greeks(0.5m, 0.2m, 0.15m, 0.05m, 0.1m, 2.0m);
8788

8889
decimal intrinsicValue;
8990
if (contract.Right == OptionRight.Call)
@@ -93,10 +94,14 @@ public OptionPriceModelResult Evaluate(OptionPriceModelParameters parameters)
9394
else
9495
{
9596
intrinsicValue = Math.Max(0, strike - underlying);
97+
// Delta and Rho are negative for a put
98+
greeks.Delta *= -1;
99+
greeks.Rho *= -1;
96100
}
97-
98101
var theoreticalPrice = intrinsicValue + 1.0m;
99-
return new OptionPriceModelResult(theoreticalPrice, new Greeks(0.5m, 0.1m, 0.2m, -0.05m, 0.1m, 2.0m));
102+
var impliedVolatility = 0.2m;
103+
104+
return new OptionPriceModelResult(theoreticalPrice, impliedVolatility, greeks);
100105
}
101106
}
102107

Algorithm.Python/CustomOptionPriceModelRegressionAlgorithm.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,17 @@ def evaluate(self, parameters):
6262
contract = parameters.contract
6363
underlying = contract.underlying_last_price
6464
strike = contract.strike
65+
greeks = Greeks(0.5, 0.2, 0.15, 0.05, 0.1, 2.0)
6566

6667
if contract.right == OptionRight.CALL:
6768
intrinsic = max(0, underlying - strike)
6869
else:
6970
intrinsic = max(0, strike - underlying)
71+
# Delta and Rho are negative for a put
72+
greeks.delta *= -1
73+
greeks.rho *= -1
7074

7175
theoretical_price = intrinsic + 1.0
76+
implied_volatility = 0.2
7277

73-
return OptionPriceModelResult(theoretical_price, Greeks(0.5, 0.1, 0.2, -0.05, 0.1, 2.0))
78+
return OptionPriceModelResult(theoretical_price, implied_volatility, greeks)

Common/Securities/Option/OptionPriceModelResult.cs

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* limitations under the License.
1414
*/
1515

16+
using Python.Runtime;
1617
using QuantConnect.Data.Market;
1718
using System;
1819

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

31-
private readonly Lazy<Greeks> _greeks;
32-
private readonly Lazy<decimal> _impliedVolatility;
32+
private Lazy<Greeks> _greeks;
33+
private Lazy<decimal> _impliedVolatility;
3334

3435
/// <summary>
3536
/// Gets the theoretical price as computed by the <see cref="IOptionPriceModel"/>
3637
/// </summary>
37-
public decimal TheoreticalPrice
38-
{
39-
get; private set;
40-
}
38+
public decimal TheoreticalPrice { get; set; }
4139

4240
/// <summary>
4341
/// Gets the implied volatility of the option contract
@@ -48,6 +46,10 @@ public decimal ImpliedVolatility
4846
{
4947
return _impliedVolatility.Value;
5048
}
49+
set
50+
{
51+
_impliedVolatility = new Lazy<decimal>(() => value, isThreadSafe: false);
52+
}
5153
}
5254

5355
/// <summary>
@@ -59,6 +61,17 @@ public Greeks Greeks
5961
{
6062
return _greeks.Value;
6163
}
64+
set
65+
{
66+
_greeks = new Lazy<Greeks>(() => value, isThreadSafe: false);
67+
}
68+
}
69+
70+
/// <summary>
71+
/// Initializes a new instance of the <see cref="OptionPriceModelResult"/> class
72+
/// </summary>
73+
public OptionPriceModelResult()
74+
{
6275
}
6376

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

89+
/// <summary>
90+
/// Initializes a new instance of the <see cref="OptionPriceModelResult"/> class
91+
/// </summary>
92+
/// <param name="theoreticalPrice">The theoretical price computed by the price model</param>
93+
/// <param name="impliedVolatility">The calculated implied volatility</param>
94+
/// <param name="greeks">The sensitivities (greeks) computed by the price model</param>
95+
public OptionPriceModelResult(decimal theoreticalPrice, decimal impliedVolatility, Greeks greeks)
96+
: this(theoreticalPrice, () => impliedVolatility, () => greeks)
97+
{
98+
}
99+
76100
/// <summary>
77101
/// Initializes a new instance of the <see cref="OptionPriceModelResult"/> class with lazy calculations of implied volatility and greeks
78102
/// </summary>
@@ -85,5 +109,16 @@ public OptionPriceModelResult(decimal theoreticalPrice, Func<decimal> impliedVol
85109
_impliedVolatility = new Lazy<decimal>(impliedVolatility, isThreadSafe: false);
86110
_greeks = new Lazy<Greeks>(greeks, isThreadSafe: false);
87111
}
112+
113+
/// <summary>
114+
/// Initializes a new instance of the <see cref="OptionPriceModelResult"/> class with lazy calculations of implied volatility and greeks
115+
/// </summary>
116+
/// <param name="theoreticalPrice">The theoretical price computed by the price model</param>
117+
/// <param name="impliedVolatility">The calculated implied volatility</param>
118+
/// <param name="greeks">The sensitivities (greeks) computed by the price model</param>
119+
public OptionPriceModelResult(decimal theoreticalPrice, PyObject impliedVolatility, PyObject greeks)
120+
: this(theoreticalPrice, impliedVolatility.SafeAs<Func<decimal>>(), greeks.SafeAs<Func<Greeks>>())
121+
{
122+
}
88123
}
89124
}

Tests/Common/Securities/OptionPriceModelTests.cs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
using Moq;
1717
using NUnit.Framework;
18+
using Python.Runtime;
1819
using QLNet;
1920
using QuantConnect.Data;
2021
using QuantConnect.Data.Market;
@@ -24,7 +25,6 @@
2425
using System;
2526
using System.Collections.Generic;
2627
using System.Diagnostics;
27-
using System.Globalization;
2828
using System.IO;
2929
using System.Linq;
3030
using System.Threading;
@@ -930,6 +930,35 @@ public void PriceModelEvaluateSpeedTest()
930930
Assert.Less(stopWatch.ElapsedMilliseconds, 2200);
931931
}
932932

933+
[TestCase(Language.CSharp)]
934+
[TestCase(Language.Python)]
935+
public void OptionPriceModelResultOverloadsAreConsistent(Language language)
936+
{
937+
var impliedVol = 0.25m;
938+
var funcImpliedVol = new Func<decimal>(() => impliedVol);
939+
var funcGreeks = new Func<Greeks>(() => new ModeledGreeks(() => 0.01m, () => 0.02m, () => 0.03m, () => 0.04m, () => 0.05m, () => 0.06m));
940+
OptionPriceModelResult optionPriceModelResult = null;
941+
if (language == Language.CSharp)
942+
{
943+
optionPriceModelResult = new OptionPriceModelResult(0.01m, funcImpliedVol, funcGreeks);
944+
}
945+
else
946+
{
947+
using (Py.GIL())
948+
{
949+
optionPriceModelResult = new OptionPriceModelResult(0.01m, funcImpliedVol.ToPython(), funcGreeks.ToPython());
950+
}
951+
}
952+
953+
Assert.AreEqual(0.25m, optionPriceModelResult.ImpliedVolatility);
954+
Assert.AreEqual(0.01m, optionPriceModelResult.Greeks.Delta);
955+
Assert.AreEqual(0.02m, optionPriceModelResult.Greeks.Gamma);
956+
Assert.AreEqual(0.03m, optionPriceModelResult.Greeks.Vega);
957+
Assert.AreEqual(0.04m, optionPriceModelResult.Greeks.Theta);
958+
Assert.AreEqual(0.05m, optionPriceModelResult.Greeks.Rho);
959+
Assert.AreEqual(0.06m, optionPriceModelResult.Greeks.Lambda);
960+
}
961+
933962
private static Symbol GetOptionSymbol(Symbol underlying, OptionStyle optionStyle, OptionRight optionRight, decimal strike = 192m, DateTime? expiry = null)
934963
{
935964
if (expiry == null)

0 commit comments

Comments
 (0)