diff --git a/Algorithm.CSharp/OptionAssignmentRegressionAlgorithm.cs b/Algorithm.CSharp/OptionAssignmentRegressionAlgorithm.cs
index bd46d9aec1d5..854714fe1b20 100644
--- a/Algorithm.CSharp/OptionAssignmentRegressionAlgorithm.cs
+++ b/Algorithm.CSharp/OptionAssignmentRegressionAlgorithm.cs
@@ -14,11 +14,13 @@
*
*/
+using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Data;
using QuantConnect.Interfaces;
using QuantConnect.Securities;
+using QuantConnect.Statistics;
namespace QuantConnect.Algorithm.CSharp
{
@@ -79,6 +81,37 @@ public override void OnData(Slice slice)
}
}
+ private Security GetSecurity(Symbol symbol)
+ {
+ if (symbol == Stock.Symbol)
+ {
+ return Stock;
+ }
+ if (symbol == CallOptionSymbol)
+ {
+ return CallOption;
+ }
+ if (symbol == PutOptionSymbol)
+ {
+ return PutOption;
+ }
+ throw new RegressionTestException($"Unexpected symbol: {symbol}");
+ }
+
+ public override void OnEndOfAlgorithm()
+ {
+ foreach (var trade in TradeBuilder.ClosedTrades)
+ {
+ var direction = trade.Direction == TradeDirection.Long ? 1m : -1m;
+ var expectedProfitLoss = Math.Round((trade.ExitPrice - trade.EntryPrice) * trade.Quantity * direction * GetSecurity(trade.Symbols.Single()).SymbolProperties.ContractMultiplier, 2);
+
+ if (trade.ProfitLoss != expectedProfitLoss)
+ {
+ throw new RegressionTestException($"Expected underlying trade profit/loss to be {expectedProfitLoss}. Actual: {trade.ProfitLoss}");
+ }
+ }
+ }
+
///
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
///
diff --git a/Algorithm.Python/OptionAssignmentRegressionAlgorithm.py b/Algorithm.Python/OptionAssignmentRegressionAlgorithm.py
index cb492e8a7a33..af206edf4fe5 100644
--- a/Algorithm.Python/OptionAssignmentRegressionAlgorithm.py
+++ b/Algorithm.Python/OptionAssignmentRegressionAlgorithm.py
@@ -47,3 +47,22 @@ def on_data(self, data):
if self.time < self.call_option_symbol.id.date:
self.market_order(self.call_option_symbol, -1)
+
+ def get_security(self, symbol):
+ if symbol == self.stock.symbol:
+ return self.stock
+ if symbol == self.call_option_symbol:
+ return self.call_option
+ if symbol == self.put_option_symbol:
+ return self.put_option
+ raise RegressionTestException(f"Unexpected symbol: {symbol}")
+
+ def on_end_of_algorithm(self):
+ for trade in self.trade_builder.closed_trades:
+ symbol, = trade.symbols
+ direction = 1 if trade.direction == TradeDirection.LONG else -1
+ multiplier = self.get_security(symbol).symbol_properties.contract_multiplier
+ expected_profit_loss = round((trade.exit_price - trade.entry_price) * trade.quantity * direction * multiplier, 2)
+
+ if trade.profit_loss != expected_profit_loss:
+ raise RegressionTestException(f"Expected underlying trade profit/loss to be {expected_profit_loss}. Actual: {trade.profit_loss}")
diff --git a/Engine/TransactionHandlers/BrokerageTransactionHandler.cs b/Engine/TransactionHandlers/BrokerageTransactionHandler.cs
index 08e84ecf1178..8477400c1158 100644
--- a/Engine/TransactionHandlers/BrokerageTransactionHandler.cs
+++ b/Engine/TransactionHandlers/BrokerageTransactionHandler.cs
@@ -1186,7 +1186,10 @@ private void HandleOrderEvents(List orderEvents)
return;
}
- orders.Add(new OpenOrderState(order, ticket, security ?? _algorithm.Securities[order.Symbol]));
+ // we take the order event symbol security to make sure to match the actual traded asset:
+ // for order exercises, the underlying security events are traded under the same option order id,
+ // but the order event symbol is the underlying security, not the option contract.
+ orders.Add(new OpenOrderState(order, ticket, security != null && security.Symbol == orderEvent.Symbol ? security : _algorithm.Securities[orderEvent.Symbol]));
orderEvent.Ticket = ticket;
}