Files
quantconnect--lean/Tests/Common/Securities/Options/OptionPortfolioModelTests.cs
T
2026-07-13 13:02:50 +08:00

140 lines
5.8 KiB
C#

/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using NodaTime;
using NUnit.Framework;
using QuantConnect.Algorithm;
using QuantConnect.Brokerages.Backtesting;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Lean.Engine.Results;
using QuantConnect.Lean.Engine.TransactionHandlers;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Securities;
using QuantConnect.Securities.Option;
using QuantConnect.Tests.Engine;
namespace QuantConnect.Tests.Common.Securities.Options
{
[TestFixture]
public class OptionPortfolioModelTests
{
private IResultHandler _resultHandler;
[SetUp]
public void SetUp()
{
_resultHandler = new TestResultHandler(Console.WriteLine);
}
[TearDown]
public void TearDown()
{
_resultHandler.Exit();
}
[Test]
public void OptionExercise_NonAccountCurrency()
{
var algorithm = new QCAlgorithm();
var securities = new SecurityManager(new TimeKeeper(DateTime.Now, TimeZones.NewYork));
var transactions = new SecurityTransactionManager(null, securities);
var transactionHandler = new BacktestingTransactionHandler();
var portfolio = new SecurityPortfolioManager(securities, transactions, new AlgorithmSettings());
var EUR = new Cash("EUR", 100*192, 10);
portfolio.CashBook.Add("EUR", EUR);
portfolio.SetCash("USD", 0, 1);
algorithm.Securities = securities;
using var backtestingBrokerage = new BacktestingBrokerage(algorithm);
transactionHandler.Initialize(algorithm, backtestingBrokerage, _resultHandler);
try
{
transactions.SetOrderProcessor(transactionHandler);
securities.Add(
Symbols.SPY,
new Security(
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
CreateTradeBarConfig(Symbols.SPY),
EUR,
SymbolProperties.GetDefault(EUR.Symbol),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
)
);
securities.Add(
Symbols.SPY_C_192_Feb19_2016,
new Option(
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
CreateTradeBarConfig(Symbols.SPY_C_192_Feb19_2016),
EUR,
new OptionSymbolProperties(new SymbolProperties("EUR", "EUR", 100, 0.01m, 1, string.Empty)),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null
)
);
securities[Symbols.SPY_C_192_Feb19_2016].Holdings.SetHoldings(1, 1);
securities[Symbols.SPY].SetMarketPrice(new Tick { Value = 200 });
transactions.AddOrder(new SubmitOrderRequest(OrderType.OptionExercise, SecurityType.Option, Symbols.SPY_C_192_Feb19_2016, -1, 0, 0, securities.UtcTime, ""));
var option = (Option)securities[Symbols.SPY_C_192_Feb19_2016];
var order = (OptionExerciseOrder)transactions.GetOrders(x => true).First();
option.Underlying = securities[Symbols.SPY];
var fills = option.OptionExerciseModel.OptionExercise(option, order).ToList();
Assert.AreEqual(2, fills.Count);
Assert.IsFalse(fills[0].IsAssignment);
StringAssert.Contains("Automatic Exercise", fills[0].Message);
Assert.AreEqual("Option Exercise", fills[1].Message);
foreach (var fill in fills)
{
fill.Ticket = order.ToOrderTicket(transactions);
portfolio.ProcessFills(new List<OrderEvent> { fill });
}
// now we have long position in SPY with average price equal to strike
var newUnderlyingHoldings = securities[Symbols.SPY].Holdings;
// we added 100*192 EUR (strike price) at beginning, all consumed by exercise
Assert.AreEqual(0, EUR.Amount);
Assert.AreEqual(0, portfolio.CashBook["USD"].Amount);
Assert.AreEqual(100, newUnderlyingHoldings.Quantity);
Assert.AreEqual(192.0, newUnderlyingHoldings.AveragePrice);
// and long call option position has disappeared
Assert.AreEqual(0, securities[Symbols.SPY_C_192_Feb19_2016].Holdings.Quantity);
}
finally
{
transactionHandler.Exit();
}
}
private static SubscriptionDataConfig CreateTradeBarConfig(Symbol symbol)
{
return new SubscriptionDataConfig(typeof(TradeBar), symbol, Resolution.Minute, TimeZones.NewYork, TimeZones.NewYork, true, true, false);
}
}
}