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; }