Files
2026-07-13 13:02:50 +08:00

853 lines
41 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 NodaTime;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Securities;
using QuantConnect.Securities.Crypto;
using QuantConnect.Securities.Future;
using QuantConnect.Securities.Option;
namespace QuantConnect.Tests.Common.Securities
{
[TestFixture]
public class SecurityPortfolioModelTests
{
[Test]
public void LastTradeProfit_FlatToLong()
{
var reference = new DateTime(2016, 02, 16, 11, 53, 30);
SecurityPortfolioManager portfolio;
var security = InitializeTest(reference, out portfolio);
var fillPrice = 100m;
var fillQuantity = 100;
var orderFee = new OrderFee(new CashAmount(1m, Currencies.USD));
var orderDirection = fillQuantity > 0 ? OrderDirection.Buy : OrderDirection.Sell;
var fill = new OrderEvent(1, security.Symbol, reference, OrderStatus.Filled, orderDirection, fillPrice, fillQuantity, orderFee);
portfolio.ProcessFills(new List<OrderEvent> {fill});
// zero since we're from flat
Assert.AreEqual(0, security.Holdings.LastTradeProfit);
}
[Test]
public void LastTradeProfit_FlatToShort()
{
var reference = new DateTime(2016, 02, 16, 11, 53, 30);
SecurityPortfolioManager portfolio;
var security = InitializeTest(reference, out portfolio);
var fillPrice = 100m;
var fillQuantity = -100;
var orderFee = new OrderFee(new CashAmount(1m, Currencies.USD));
var orderDirection = fillQuantity > 0 ? OrderDirection.Buy : OrderDirection.Sell;
var fill = new OrderEvent(1, security.Symbol, reference, OrderStatus.Filled, orderDirection, fillPrice, fillQuantity, orderFee);
portfolio.ProcessFills(new List<OrderEvent> {fill});
// zero since we're from flat
Assert.AreEqual(0, security.Holdings.LastTradeProfit);
}
[Test]
public void LastTradeProfit_LongToLonger()
{
var reference = new DateTime(2016, 02, 16, 11, 53, 30);
SecurityPortfolioManager portfolio;
var security = InitializeTest(reference, out portfolio);
security.Holdings.SetHoldings(50m, 100);
var fillPrice = 100m;
var fillQuantity = 100;
var orderFee = new OrderFee(new CashAmount(1m, Currencies.USD));
var orderDirection = fillQuantity > 0 ? OrderDirection.Buy : OrderDirection.Sell;
var fill = new OrderEvent(1, security.Symbol, reference, OrderStatus.Filled, orderDirection, fillPrice, fillQuantity, orderFee);
portfolio.ProcessFills(new List<OrderEvent> {fill});
// zero since we're from flat
Assert.AreEqual(0, security.Holdings.LastTradeProfit);
}
[Test]
public void LastTradeProfit_LongToFlat()
{
var reference = new DateTime(2016, 02, 16, 11, 53, 30);
SecurityPortfolioManager portfolio;
var security = InitializeTest(reference, out portfolio);
security.Holdings.SetHoldings(50m, 100);
var fillPrice = 100m;
var fillQuantity = -security.Holdings.Quantity;
var orderFee = new OrderFee(new CashAmount(1m, Currencies.USD));
var orderDirection = fillQuantity > 0 ? OrderDirection.Buy : OrderDirection.Sell;
var fill = new OrderEvent(1, security.Symbol, reference, OrderStatus.Filled, orderDirection, fillPrice, fillQuantity, orderFee);
portfolio.ProcessFills(new List<OrderEvent> {fill});
// bought @50 and sold @100 = (-50*100)+(100*100 - 1) = 4999
// current implementation doesn't back out fees.
Assert.AreEqual(5000m, security.Holdings.LastTradeProfit);
}
[Test]
public void LastTradeProfit_LongToShort()
{
var reference = new DateTime(2016, 02, 16, 11, 53, 30);
SecurityPortfolioManager portfolio;
var security = InitializeTest(reference, out portfolio);
security.Holdings.SetHoldings(50m, 100);
var fillPrice = 100m;
var fillQuantity = -2*security.Holdings.Quantity;
var orderFee = new OrderFee(new CashAmount(1m, Currencies.USD));
var orderDirection = fillQuantity > 0 ? OrderDirection.Buy : OrderDirection.Sell;
var fill = new OrderEvent(1, security.Symbol, reference, OrderStatus.Filled, orderDirection, fillPrice, fillQuantity, orderFee);
portfolio.ProcessFills(new List<OrderEvent> {fill});
// we can only take 'profit' on the closing part of the position, so we closed 100
// shares and opened a new for the second 100, so ony the frst 100 go into the calculation
// bought @50 and sold @100 = (-50*100)+(100*100 - 1) = 4999
// current implementation doesn't back out fees.
Assert.AreEqual(5000m, security.Holdings.LastTradeProfit);
}
[Test]
public void LastTradeProfit_ShortToShorter()
{
var reference = new DateTime(2016, 02, 16, 11, 53, 30);
SecurityPortfolioManager portfolio;
var security = InitializeTest(reference, out portfolio);
security.Holdings.SetHoldings(50m, -100);
var fillPrice = 100m;
var fillQuantity = -100;
var orderFee = new OrderFee(new CashAmount(1m, Currencies.USD));
var orderDirection = fillQuantity > 0 ? OrderDirection.Buy : OrderDirection.Sell;
var fill = new OrderEvent(1, security.Symbol, reference, OrderStatus.Filled, orderDirection, fillPrice, fillQuantity, orderFee);
portfolio.ProcessFills(new List<OrderEvent> {fill});
Assert.AreEqual(0, security.Holdings.LastTradeProfit);
}
[TestCase("USD")]
[TestCase("ARG")]
public void LastTradeProfit_ShortToFlat(string accountCurrency)
{
var reference = new DateTime(2016, 02, 16, 11, 53, 30);
SecurityPortfolioManager portfolio;
var security = InitializeTest(reference, out portfolio, accountCurrency);
security.Holdings.SetHoldings(50m, -100);
var fillPrice = 100m;
var fillQuantity = -security.Holdings.Quantity;
var orderFee = new OrderFee(new CashAmount(1m, Currencies.USD));
var orderDirection = fillQuantity > 0 ? OrderDirection.Buy : OrderDirection.Sell;
var fill = new OrderEvent(1, security.Symbol, reference, OrderStatus.Filled, orderDirection, fillPrice, fillQuantity, orderFee);
portfolio.ProcessFills(new List<OrderEvent> {fill});
// sold @50 and bought @100 = (50*100)+(-100*100 - 1) = -5001
// current implementation doesn't back out fees.
Assert.AreEqual(-5000m, security.Holdings.LastTradeProfit);
}
public void LastTradeProfit_ShortToLong()
{
var reference = new DateTime(2016, 02, 16, 11, 53, 30);
SecurityPortfolioManager portfolio;
var security = InitializeTest(reference, out portfolio);
security.Holdings.SetHoldings(50m, -100);
var fillPrice = 100m;
var fillQuantity = -2*security.Holdings.Quantity; // flip from -100 to +100
var orderFee = new OrderFee(new CashAmount(1m, Currencies.USD));
var orderDirection = fillQuantity > 0 ? OrderDirection.Buy : OrderDirection.Sell;
var fill = new OrderEvent(1, security.Symbol, reference, OrderStatus.Filled, orderDirection, fillPrice, fillQuantity, orderFee);
portfolio.ProcessFills(new List<OrderEvent> {fill});
// we can only take 'profit' on the closing part of the position, so we closed 100
// shares and opened a new for the second 100, so ony the frst 100 go into the calculation
// sold @50 and bought @100 = (50*100)+(-100*100 - 1) = -5001
// current implementation doesn't back out fees.
Assert.AreEqual(-5000m, security.Holdings.LastTradeProfit);
}
[TestCase("USD")]
[TestCase("ARG")]
public void NonAccountCurrencyEquity_LongToFlat(string accountCurrency)
{
var reference = new DateTime(2016, 02, 16, 11, 53, 30);
SecurityPortfolioManager portfolio;
InitializeTest(reference, out portfolio, accountCurrency);
var cash = new Cash("EUR", 0, 10);
portfolio.CashBook.Add("EUR", cash);
var equity = new Security(
Symbols.AAPL,
SecurityExchangeHours.AlwaysOpen(DateTimeZone.Utc),
cash,
SymbolProperties.GetDefault("EUR"),
portfolio.CashBook,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
equity.Holdings.SetHoldings(50m, 100);
portfolio.Securities.Add(equity);
var fillPrice = 100m;
var fillQuantity = -equity.Holdings.Quantity;
var orderFee = new OrderFee(new CashAmount(1m, "EUR"));
var orderDirection = fillQuantity > 0 ? OrderDirection.Buy : OrderDirection.Sell;
var fill = new OrderEvent(1, equity.Symbol, reference, OrderStatus.Filled, orderDirection, fillPrice, fillQuantity, orderFee);
portfolio.ProcessFills(new List<OrderEvent> {fill});
// current implementation doesn't back out fees.
Assert.AreEqual(10, equity.Holdings.TotalFees); // 1 * 10 (conversion rate to account currency)
// bought @50 and sold @100 = (-50*100)+(100*100) = 50000 * 10 (conversion rate to account currency)
Assert.AreEqual(50000m, equity.Holdings.LastTradeProfit);
// sold @100 = (100*100) = 10000 - 1 fee
Assert.AreEqual(9999, portfolio.CashBook["EUR"].Amount);
Assert.AreEqual(0m, equity.Holdings.AveragePrice);
Assert.AreEqual(0m, equity.Holdings.AbsoluteQuantity);
Assert.AreEqual(0m, equity.Holdings.AbsoluteHoldingsCost);
Assert.AreEqual(0m, equity.Holdings.AbsoluteHoldingsValue);
Assert.AreEqual(0m, equity.Holdings.TotalCloseProfit());
}
[TestCase("USD")]
[TestCase("ARG")]
public void NonAccountCurrencyEquity_ShortToFlat(string accountCurrency)
{
var reference = new DateTime(2016, 02, 16, 11, 53, 30);
SecurityPortfolioManager portfolio;
InitializeTest(reference, out portfolio, accountCurrency);
var cash = new Cash("EUR", 0, 10);
portfolio.CashBook.Add("EUR", cash);
var equity = new Security(
Symbols.AAPL,
SecurityExchangeHours.AlwaysOpen(DateTimeZone.Utc),
cash,
SymbolProperties.GetDefault("EUR"),
portfolio.CashBook,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
equity.Holdings.SetHoldings(50m, -100);
portfolio.Securities.Add(equity);
var fillPrice = 100m;
var fillQuantity = -equity.Holdings.Quantity;
var orderFee = new OrderFee(new CashAmount(1m, "EUR"));
var orderDirection = fillQuantity > 0 ? OrderDirection.Buy : OrderDirection.Sell;
var fill = new OrderEvent(1, equity.Symbol, reference, OrderStatus.Filled, orderDirection, fillPrice, fillQuantity, orderFee);
portfolio.ProcessFills(new List<OrderEvent> {fill});
// current implementation doesn't back out fees.
Assert.AreEqual(10, equity.Holdings.TotalFees); // 1 * 10 (conversion rate to account currency)
// sold @50 and bought @100 = (-50*100)+(100*100) = -50000 * 10 (conversion rate to account currency)
Assert.AreEqual(-50000m, equity.Holdings.LastTradeProfit);
// bought @100 = (-100*100) = -10000 - 1 fee
Assert.AreEqual(-10001, portfolio.CashBook["EUR"].Amount);
Assert.AreEqual(0m, equity.Holdings.AveragePrice);
Assert.AreEqual(0m, equity.Holdings.AbsoluteQuantity);
Assert.AreEqual(0m, equity.Holdings.AbsoluteHoldingsCost);
Assert.AreEqual(0m, equity.Holdings.AbsoluteHoldingsValue);
Assert.AreEqual(0m, equity.Holdings.TotalCloseProfit());
}
[TestCase("USD")]
[TestCase("ARG")]
public void NonAccountCurrencyEquity_FlatToShort(string accountCurrency)
{
var reference = new DateTime(2016, 02, 16, 11, 53, 30);
SecurityPortfolioManager portfolio;
InitializeTest(reference, out portfolio, accountCurrency);
var cash = new Cash("EUR", 0, 10);
portfolio.CashBook.Add("EUR", cash);
var equity = new Security(
Symbols.AAPL,
SecurityExchangeHours.AlwaysOpen(DateTimeZone.Utc),
cash,
SymbolProperties.GetDefault("EUR"),
portfolio.CashBook,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
portfolio.Securities.Add(equity);
var fillPrice = 100m;
var fillQuantity = -100;
var orderFee = new OrderFee(new CashAmount(1m, "EUR"));
var orderDirection = fillQuantity > 0 ? OrderDirection.Buy : OrderDirection.Sell;
var fill = new OrderEvent(1, equity.Symbol, reference, OrderStatus.Filled, orderDirection, fillPrice, fillQuantity, orderFee);
portfolio.ProcessFills(new List<OrderEvent> {fill});
// current implementation doesn't back out fees.
Assert.AreEqual(10, equity.Holdings.TotalFees); // 1 * 10 (conversion rate to account currency)
Assert.AreEqual(-10, equity.Holdings.NetProfit); // fees
Assert.AreEqual(0m, equity.Holdings.LastTradeProfit);
// sold @100 = (100*100) = 10000 - 1 fee
Assert.AreEqual(9999, portfolio.CashBook["EUR"].Amount);
Assert.AreEqual(100m, equity.Holdings.AveragePrice);
Assert.AreEqual(100m, equity.Holdings.AbsoluteQuantity);
equity.SetMarketPrice(new Tick(DateTime.UtcNow, equity.Symbol, 90, 90));
// -100 quantity * 100 average price * 10 rate = 100000m
Assert.AreEqual(100000m, equity.Holdings.AbsoluteHoldingsCost);
// -100 quantity * 90 current price * 10 rate = 90000m
Assert.AreEqual(90000m, equity.Holdings.AbsoluteHoldingsValue);
// (90 average price - 100 current price) * -100 quantity * 10 rate - 1 fee = 9999m
Assert.AreEqual(9999m, equity.Holdings.TotalCloseProfit());
}
[TestCase("USD")]
[TestCase("ARG")]
public void NonAccountCurrencyEquity_FlatToLong(string accountCurrency)
{
var reference = new DateTime(2016, 02, 16, 11, 53, 30);
SecurityPortfolioManager portfolio;
InitializeTest(reference, out portfolio, accountCurrency);
var cash = new Cash("EUR", 0, 10);
portfolio.CashBook.Add("EUR", cash);
var equity = new Security(
Symbols.AAPL,
SecurityExchangeHours.AlwaysOpen(DateTimeZone.Utc),
cash,
SymbolProperties.GetDefault("EUR"),
portfolio.CashBook,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
portfolio.Securities.Add(equity);
var fillPrice = 100m;
var fillQuantity = 100;
var orderFee = new OrderFee(new CashAmount(1m, "EUR"));
var orderDirection = fillQuantity > 0 ? OrderDirection.Buy : OrderDirection.Sell;
var fill = new OrderEvent(1, equity.Symbol, reference, OrderStatus.Filled, orderDirection, fillPrice, fillQuantity, orderFee);
portfolio.ProcessFills(new List<OrderEvent> {fill});
// current implementation doesn't back out fees.
Assert.AreEqual(10, equity.Holdings.TotalFees); // 1 * 10 (conversion rate to account currency)
Assert.AreEqual(-10, equity.Holdings.NetProfit); // fees
Assert.AreEqual(0m, equity.Holdings.LastTradeProfit);
// bought @100 = -(100*100) = -10000 - 1 fee
Assert.AreEqual(-10001, portfolio.CashBook["EUR"].Amount);
Assert.AreEqual(100m, equity.Holdings.AveragePrice);
Assert.AreEqual(100m, equity.Holdings.AbsoluteQuantity);
equity.SetMarketPrice(new Tick(DateTime.UtcNow, equity.Symbol, 110, 110));
// 100 quantity * 100 average price * 10 rate = 100000m
Assert.AreEqual(100000m, equity.Holdings.AbsoluteHoldingsCost);
// 100 quantity * 110 current price * 10 rate = 110000m
Assert.AreEqual(110000m, equity.Holdings.AbsoluteHoldingsValue);
// (110 current price - 100 average price) * 100 quantity * 10 rate - 1 fee = 9999m
Assert.AreEqual(9999m, equity.Holdings.TotalCloseProfit());
}
[TestCase("USD")]
[TestCase("ARG")]
public void NonAccountCurrencyFuture_LongToFlat(string accountCurrency)
{
var reference = new DateTime(2016, 02, 16, 11, 53, 30);
SecurityPortfolioManager portfolio;
InitializeTest(reference, out portfolio, accountCurrency);
var cash = new Cash("EUR", 0, 10);
portfolio.CashBook.Add("EUR", cash);
var future = new Future(
Symbols.Fut_SPY_Feb19_2016,
SecurityExchangeHours.AlwaysOpen(DateTimeZone.Utc),
cash,
SymbolProperties.GetDefault("EUR"),
portfolio.CashBook,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
future.Holdings.SetHoldings(50m, 100);
portfolio.Securities.Add(future);
var fillPrice = 100m;
var fillQuantity = -future.Holdings.Quantity;
var orderFee = new OrderFee(new CashAmount(1m, "EUR"));
var orderDirection = fillQuantity > 0 ? OrderDirection.Buy : OrderDirection.Sell;
var fill = new OrderEvent(1, future.Symbol, reference, OrderStatus.Filled, orderDirection, fillPrice, fillQuantity, orderFee);
portfolio.ProcessFills(new List<OrderEvent> {fill});
// current implementation doesn't back out fees.
Assert.AreEqual(10, future.Holdings.TotalFees); // 1 * 10 (conversion rate to account currency)
// bought @50 and sold @100 = (-50*100)+(100*100) = 50000 * 10 (conversion rate to account currency)
Assert.AreEqual(50000m, future.Holdings.LastTradeProfit);
Assert.AreEqual(49990m, future.Holdings.NetProfit); // LastTradeProfit - fees
// bought @50 and sold @100 = (-50*100)+(100*100) = 5000 - 1 fee
Assert.AreEqual(4999, portfolio.CashBook["EUR"].Amount);
Assert.AreEqual(0m, future.Holdings.AveragePrice);
Assert.AreEqual(0m, future.Holdings.AbsoluteQuantity);
Assert.AreEqual(0m, future.Holdings.AbsoluteHoldingsCost);
Assert.AreEqual(0m, future.Holdings.AbsoluteHoldingsValue);
Assert.AreEqual(0m, future.Holdings.TotalCloseProfit());
}
[TestCase("USD")]
[TestCase("ARG")]
public void NonAccountCurrencyFuture_ShortToFlat(string accountCurrency)
{
var reference = new DateTime(2016, 02, 16, 11, 53, 30);
SecurityPortfolioManager portfolio;
InitializeTest(reference, out portfolio, accountCurrency);
var cash = new Cash("EUR", 0, 10);
portfolio.CashBook.Add("EUR", cash);
var future = new Future(
Symbols.Fut_SPY_Feb19_2016,
SecurityExchangeHours.AlwaysOpen(DateTimeZone.Utc),
cash,
SymbolProperties.GetDefault("EUR"),
portfolio.CashBook,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
future.Holdings.SetHoldings(50m, -100);
portfolio.Securities.Add(future);
var fillPrice = 100m;
var fillQuantity = -future.Holdings.Quantity;
var orderFee = new OrderFee(new CashAmount(1m, "EUR"));
var orderDirection = fillQuantity > 0 ? OrderDirection.Buy : OrderDirection.Sell;
var fill = new OrderEvent(1, future.Symbol, reference, OrderStatus.Filled, orderDirection, fillPrice, fillQuantity, orderFee);
portfolio.ProcessFills(new List<OrderEvent> {fill});
// current implementation doesn't back out fees.
Assert.AreEqual(10, future.Holdings.TotalFees); // 1 * 10 (conversion rate to account currency)
// sold @50 and bought @100 = (50*100)+(-100*100) = -50000 * 10 (conversion rate to account currency)
Assert.AreEqual(-50000m, future.Holdings.LastTradeProfit);
Assert.AreEqual(-50010m, future.Holdings.NetProfit); // LastTradeProfit - fees
// sold @50 and bought @100 = (50*100)+(-100*100) = -5000 - 1 fee
Assert.AreEqual(-5001, portfolio.CashBook["EUR"].Amount);
Assert.AreEqual(0m, future.Holdings.AveragePrice);
Assert.AreEqual(0m, future.Holdings.AbsoluteQuantity);
Assert.AreEqual(0m, future.Holdings.AbsoluteHoldingsCost);
Assert.AreEqual(0m, future.Holdings.AbsoluteHoldingsValue);
Assert.AreEqual(0m, future.Holdings.TotalCloseProfit());
}
[TestCase("USD")]
[TestCase("ARG")]
public void NonAccountCurrencyFuture_FlatToLong(string accountCurrency)
{
var reference = new DateTime(2016, 02, 16, 11, 53, 30);
SecurityPortfolioManager portfolio;
InitializeTest(reference, out portfolio, accountCurrency);
var cash = new Cash("EUR", 1, 10);
portfolio.CashBook.Add("EUR", cash);
var future = new Future(
Symbols.Fut_SPY_Feb19_2016,
SecurityExchangeHours.AlwaysOpen(DateTimeZone.Utc),
cash,
SymbolProperties.GetDefault("EUR"),
portfolio.CashBook,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
portfolio.Securities.Add(future);
var fillPrice = 100m;
var fillQuantity = 100;
var orderFee = new OrderFee(new CashAmount(1m, "EUR"));
var orderDirection = fillQuantity > 0 ? OrderDirection.Buy : OrderDirection.Sell;
var fill = new OrderEvent(1, future.Symbol, reference, OrderStatus.Filled, orderDirection, fillPrice, fillQuantity, orderFee);
portfolio.ProcessFills(new List<OrderEvent> { fill });
// current implementation doesn't back out fees.
Assert.AreEqual(10, future.Holdings.TotalFees); // 1 * 10 (conversion rate to account currency)
Assert.AreEqual(0m, future.Holdings.LastTradeProfit);
Assert.AreEqual(100m, future.Holdings.Quantity);
Assert.AreEqual(100m, future.Holdings.AveragePrice);
// had 1 EUR - 1 fee
Assert.AreEqual(0, portfolio.CashBook["EUR"].Amount);
// 100 quantity * 100 average price * 10 rate = 100000m
Assert.AreEqual(100000m, future.Holdings.AbsoluteHoldingsCost);
future.SetMarketPrice(new Tick(DateTime.UtcNow, future.Symbol, 110, 110));
// 100 quantity * 110 current price * 10 rate = 110000m
Assert.AreEqual(110000m, future.Holdings.AbsoluteHoldingsValue);
// (110 current price - 100 average price) * 100 quantity * 10 rate - 2.15 fee * 100 quantity = 9785m
Assert.AreEqual(9785m, future.Holdings.TotalCloseProfit());
}
[TestCase("USD")]
[TestCase("ARG")]
public void NonAccountCurrencyFuture_FlatToShort(string accountCurrency)
{
var reference = new DateTime(2016, 02, 16, 11, 53, 30);
SecurityPortfolioManager portfolio;
InitializeTest(reference, out portfolio, accountCurrency);
var cash = new Cash("EUR", 1, 10);
portfolio.CashBook.Add("EUR", cash);
var future = new Future(
Symbols.Fut_SPY_Feb19_2016,
SecurityExchangeHours.AlwaysOpen(DateTimeZone.Utc),
cash,
SymbolProperties.GetDefault("EUR"),
portfolio.CashBook,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
portfolio.Securities.Add(future);
var fillPrice = 100m;
var fillQuantity = -100;
var orderFee = new OrderFee(new CashAmount(1m, "EUR"));
var orderDirection = fillQuantity > 0 ? OrderDirection.Buy : OrderDirection.Sell;
var fill = new OrderEvent(1, future.Symbol, reference, OrderStatus.Filled, orderDirection, fillPrice, fillQuantity, orderFee);
portfolio.ProcessFills(new List<OrderEvent> {fill});
// current implementation doesn't back out fees.
Assert.AreEqual(10, future.Holdings.TotalFees); // 1 * 10 (conversion rate to account currency)
Assert.AreEqual(0m, future.Holdings.LastTradeProfit);
Assert.AreEqual(-100m, future.Holdings.Quantity);
Assert.AreEqual(100m, future.Holdings.AveragePrice);
// had 1 EUR - 1 fee
Assert.AreEqual(0, portfolio.CashBook["EUR"].Amount);
// 100 quantity * 100 average price * 10 rate = 100000m
Assert.AreEqual(100000m, future.Holdings.AbsoluteHoldingsCost);
future.SetMarketPrice(new Tick(DateTime.UtcNow, future.Symbol, 110, 110));
// 100 quantity * 110 current price * 10 rate = 110000m
Assert.AreEqual(110000m, future.Holdings.AbsoluteHoldingsValue);
// (110 current price - 100 average price) * - 100 quantity * 10 rate - 2.15 fee * 100 quantity = -10215m
Assert.AreEqual(-10215m, future.Holdings.TotalCloseProfit());
}
[TestCase("USD")]
[TestCase("ARG")]
public void NonAccountCurrencyCrypto_LongToFlat(string accountCurrency)
{
var reference = new DateTime(2016, 02, 16, 11, 53, 30);
SecurityPortfolioManager portfolio;
InitializeTest(reference, out portfolio, accountCurrency);
var cash = portfolio.CashBook.Add("EUR", 0, 10);
var btcCash = portfolio.CashBook.Add("BTC", 0, 1000);
var crypto = new Crypto(
Symbols.BTCEUR,
SecurityExchangeHours.AlwaysOpen(DateTimeZone.Utc),
cash,
btcCash,
SymbolProperties.GetDefault("EUR"),
portfolio.CashBook,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
crypto.Holdings.SetHoldings(50m, 100);
portfolio.Securities.Add(crypto);
var fillPrice = 100m;
var fillQuantity = -crypto.Holdings.Quantity;
var orderFee = new OrderFee(new CashAmount(1m, "EUR"));
var orderDirection = fillQuantity > 0 ? OrderDirection.Buy : OrderDirection.Sell;
var fill = new OrderEvent(1, crypto.Symbol, reference, OrderStatus.Filled, orderDirection, fillPrice, fillQuantity, orderFee);
portfolio.ProcessFills(new List<OrderEvent> {fill});
// current implementation doesn't back out fees.
Assert.AreEqual(10, crypto.Holdings.TotalFees); // 1 * 10 (conversion rate to account currency)
// bought @50 and sold @100 = (-50*100)+(100*100) = 50000 * 10 (conversion rate to account currency)
Assert.AreEqual(50000m, crypto.Holdings.LastTradeProfit);
// sold @100 * 100 = 10000 - 1 fee
Assert.AreEqual(9999, portfolio.CashBook["EUR"].Amount);
Assert.AreEqual(0m, crypto.Holdings.AveragePrice);
Assert.AreEqual(0m, crypto.Holdings.AbsoluteQuantity);
}
[TestCase("USD")]
[TestCase("ARG")]
public void NonAccountCurrencyCrypto_FlatToLong(string accountCurrency)
{
var reference = new DateTime(2016, 02, 16, 11, 53, 30);
SecurityPortfolioManager portfolio;
InitializeTest(reference, out portfolio, accountCurrency);
var cash = portfolio.CashBook.Add("EUR", 0, 10);
var btcCash = portfolio.CashBook.Add("BTC", 0, 1000);
var crypto = new Crypto(
Symbols.BTCEUR,
SecurityExchangeHours.AlwaysOpen(DateTimeZone.Utc),
cash,
btcCash,
SymbolProperties.GetDefault("EUR"),
portfolio.CashBook,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
portfolio.Securities.Add(crypto);
var fillPrice = 100m;
var fillQuantity = 100;
var orderFee = new OrderFee(new CashAmount(1m, "EUR"));
var orderDirection = fillQuantity > 0 ? OrderDirection.Buy : OrderDirection.Sell;
var fill = new OrderEvent(1, crypto.Symbol, reference, OrderStatus.Filled, orderDirection, fillPrice, fillQuantity, orderFee);
portfolio.ProcessFills(new List<OrderEvent> {fill});
// current implementation doesn't back out fees.
Assert.AreEqual(10, crypto.Holdings.TotalFees); // 1 * 10 (conversion rate to account currency)
Assert.AreEqual(0m, crypto.Holdings.LastTradeProfit);
Assert.AreEqual(100m, crypto.Holdings.Quantity);
Assert.AreEqual(100m, crypto.Holdings.AveragePrice);
// had 0 EUR - 1 fee
Assert.AreEqual(-10001, portfolio.CashBook["EUR"].Amount);
Assert.AreEqual(100, portfolio.CashBook["BTC"].Amount);
}
[Test]
public void ITMOptionExerciseWinLossCount(
[Values(OrderDirection.Buy, OrderDirection.Sell)] OrderDirection orderDirection,
[Values] bool win)
{
var reference = new DateTime(2016, 02, 16, 11, 53, 30);
var option = InitializeTestWithOption(reference, out var portfolio);
var underlying = option.Underlying;
option.SetMarketPrice(new Tick { Value = 100m });
var underlyingPrice = 0m;
if (win)
{
underlyingPrice = orderDirection == OrderDirection.Buy ? 300m : 290m;
}
else
{
underlyingPrice = orderDirection == OrderDirection.Buy ? 290m : 300m;
}
underlying.SetMarketPrice(new Tick { Value = underlyingPrice });
var orderProcessor = new FakeOrderProcessor();
var quantity = orderDirection == OrderDirection.Buy ? 10 : -10;
var request = new SubmitOrderRequest(OrderType.Market, option.Type, option.Symbol, quantity, 0, 0, reference, "");
var order = Order.CreateOrder(request);
order.Id = 1;
orderProcessor.AddOrder(order);
portfolio.Transactions.SetOrderProcessor(orderProcessor);
var fillPrice = 100m;
var fillQuantity = quantity;
var orderFee = new OrderFee(new CashAmount(1m, Currencies.USD));
var fill = new OrderEvent(1, option.Symbol, reference, OrderStatus.Filled, orderDirection, fillPrice, fillQuantity, orderFee) { Ticket = new OrderTicket(portfolio.Transactions, request) };
fill.IsInTheMoney = true;
portfolio.ProcessFills(new List<OrderEvent> { fill });
Assert.AreEqual(0, portfolio.Transactions.WinCount);
Assert.AreEqual(0, portfolio.Transactions.LossCount);
// Now close the option position simulating an assignment on expiration
fillPrice = 0;
fillQuantity *= -1;
var closingOrderDirection = orderDirection == OrderDirection.Buy ? OrderDirection.Sell : OrderDirection.Buy;
var ticket = new OrderTicket(null, new SubmitOrderRequest(OrderType.OptionExercise, option.Type, option.Symbol, fillQuantity, 0, 0,
reference, ""));
fill = new OrderEvent(1, option.Symbol, reference, OrderStatus.Filled, closingOrderDirection, fillPrice, fillQuantity, orderFee)
{
IsInTheMoney = true,
Ticket = ticket,
};
portfolio.ProcessFills(new List<OrderEvent> { fill });
Assert.AreEqual(win ? 1 : 0, portfolio.Transactions.WinCount);
Assert.AreEqual(win ? 0 : 1, portfolio.Transactions.LossCount);
}
[TestCase(OrderDirection.Buy)]
[TestCase(OrderDirection.Sell)]
public void OTMOptionExerciseWinLossCount(OrderDirection orderDirection)
{
var reference = new DateTime(2016, 02, 16, 11, 53, 30);
var option = InitializeTestWithOption(reference, out var portfolio);
var underlying = option.Underlying;
option.SetMarketPrice(new Tick { Value = 100m });
underlying.SetMarketPrice(new Tick { Value = 150m });
var orderProcessor = new FakeOrderProcessor();
var quantity = orderDirection == OrderDirection.Buy ? 10 : -10;
var request = new SubmitOrderRequest(OrderType.Market, option.Type, option.Symbol, quantity, 0, 0, reference, "");
var order = Order.CreateOrder(request);
order.Id = 1;
orderProcessor.AddOrder(order);
portfolio.Transactions.SetOrderProcessor(orderProcessor);
var fillPrice = 100m;
var fillQuantity = quantity;
var orderFee = new OrderFee(new CashAmount(1m, Currencies.USD));
var fill = new OrderEvent(1, option.Symbol, reference, OrderStatus.Filled, orderDirection, fillPrice, fillQuantity, orderFee);
fill.IsInTheMoney = true;
fill.Ticket = new OrderTicket(portfolio.Transactions, request);
portfolio.ProcessFills(new List<OrderEvent> { fill });
Assert.AreEqual(0, portfolio.Transactions.WinCount);
Assert.AreEqual(0, portfolio.Transactions.LossCount);
// Now close the option position simulating an assignment on expiration
fillPrice = 0;
fillQuantity *= -1;
var closingOrderDirection = orderDirection == OrderDirection.Buy ? OrderDirection.Sell : OrderDirection.Buy;
var ticket = new OrderTicket(null, new SubmitOrderRequest(OrderType.OptionExercise, option.Type, option.Symbol, fillQuantity, 0, 0,
reference, ""));
fill = new OrderEvent(1, option.Symbol, reference, OrderStatus.Filled, closingOrderDirection, fillPrice, fillQuantity, orderFee)
{
IsInTheMoney = true,
Ticket = ticket,
};
portfolio.ProcessFills(new List<OrderEvent> { fill });
var expectedWin = orderDirection == OrderDirection.Buy ? false : true;
Assert.AreEqual(expectedWin ? 1 : 0, portfolio.Transactions.WinCount);
Assert.AreEqual(expectedWin ? 0 : 1, portfolio.Transactions.LossCount);
}
[Test]
public void OptionPositionCloseWithoutExerciseWinLossCount(
[Values(OrderDirection.Buy, OrderDirection.Sell)] OrderDirection orderDirection,
[Values] bool win)
{
var reference = new DateTime(2016, 02, 16, 11, 53, 30);
var option = InitializeTestWithOption(reference, out var portfolio);
var underlying = option.Underlying;
var initialOptionPrice = 100m;
option.SetMarketPrice(new Tick { Value = initialOptionPrice });
underlying.SetMarketPrice(new Tick { Value = 300m });
var orderProcessor = new FakeOrderProcessor();
var quantity = orderDirection == OrderDirection.Buy ? 10 : -10;
var request = new SubmitOrderRequest(OrderType.Market, option.Type, option.Symbol, quantity, 0, 0, reference, "");
var order = Order.CreateOrder(request);
order.Id = 1;
orderProcessor.AddOrder(order);
portfolio.Transactions.SetOrderProcessor(orderProcessor);
var fillPrice = 100m;
var fillQuantity = quantity;
var orderFee = new OrderFee(new CashAmount(1m, Currencies.USD));
var fill = new OrderEvent(1, option.Symbol, reference, OrderStatus.Filled, orderDirection, fillPrice, fillQuantity, orderFee) { Ticket = new OrderTicket(portfolio.Transactions, request) };
fill.IsInTheMoney = true;
portfolio.ProcessFills(new List<OrderEvent> { fill });
Assert.AreEqual(0, portfolio.Transactions.WinCount);
Assert.AreEqual(0, portfolio.Transactions.LossCount);
// Before closing, update option market price
var finalOptionPrice = 0m;
if (win)
{
finalOptionPrice = orderDirection == OrderDirection.Buy ? 150m : 50m;
}
else
{
finalOptionPrice = orderDirection == OrderDirection.Buy ? 50m : 150m;
}
option.SetMarketPrice(new Tick { Value = finalOptionPrice });
// Now close the option position simulating an assignment on expiration
fillPrice = finalOptionPrice;
fillQuantity *= -1;
var closingOrderDirection = orderDirection == OrderDirection.Buy ? OrderDirection.Sell : OrderDirection.Buy;
var ticket = new OrderTicket(null, new SubmitOrderRequest(OrderType.Market, option.Type, option.Symbol, fillQuantity, 0, 0,
reference, ""));
fill = new OrderEvent(1, option.Symbol, reference, OrderStatus.Filled, closingOrderDirection, fillPrice, fillQuantity, orderFee)
{
IsInTheMoney = true,
Ticket = ticket,
};
portfolio.ProcessFills(new List<OrderEvent> { fill });
Assert.AreEqual(win ? 1 : 0, portfolio.Transactions.WinCount);
Assert.AreEqual(win ? 0 : 1, portfolio.Transactions.LossCount);
}
private Security InitializeTest(DateTime reference,
out SecurityPortfolioManager portfolio,
string accountCurrency = "USD")
{
var security = new Security(
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
CreateTradeBarConfig(),
new Cash(Currencies.USD, 0, 1m),
SymbolProperties.GetDefault(Currencies.USD),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
security.SetMarketPrice(new Tick { Value = 100 });
var timeKeeper = new TimeKeeper(reference);
var securityManager = new SecurityManager(timeKeeper);
securityManager.Add(security);
var transactionManager = new SecurityTransactionManager(null, securityManager);
portfolio = new SecurityPortfolioManager(securityManager, transactionManager, new AlgorithmSettings());
portfolio.SetCash(accountCurrency, 100 * 1000m, 1m);
Assert.AreEqual(0, security.Holdings.Quantity);
Assert.AreEqual(100*1000m, portfolio.CashBook[accountCurrency].Amount);
portfolio.SetCash(security.QuoteCurrency.Symbol, 0, 1m);
return security;
}
private Option InitializeTestWithOption(DateTime reference,
out SecurityPortfolioManager portfolio,
string accountCurrency = "USD")
{
var underlying = InitializeTest(reference, out portfolio, accountCurrency);
var option = new Option(
Symbols.SPY_C_192_Feb19_2016,
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
new Cash(Currencies.USD, 0, 1m),
new OptionSymbolProperties(SymbolProperties.GetDefault(Currencies.USD)),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache(),
underlying
);
portfolio.Securities.Add(option);
return option;
}
private static SubscriptionDataConfig CreateTradeBarConfig()
{
return new SubscriptionDataConfig(typeof(TradeBar), Symbols.SPY, Resolution.Minute, TimeZones.NewYork, TimeZones.NewYork, true, true, false);
}
}
}