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
33 changes: 33 additions & 0 deletions Algorithm.CSharp/OptionAssignmentRegressionAlgorithm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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}");
}
}
}

/// <summary>
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
/// </summary>
Expand Down
19 changes: 19 additions & 0 deletions Algorithm.Python/OptionAssignmentRegressionAlgorithm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}")
5 changes: 4 additions & 1 deletion Engine/TransactionHandlers/BrokerageTransactionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1186,7 +1186,10 @@ private void HandleOrderEvents(List<OrderEvent> 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;
}

Expand Down
Loading