/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Orders.Fills;
using QuantConnect.Securities;
using QuantConnect.Securities.Option;
namespace QuantConnect.Tests.Common.Securities
{
[TestFixture]
public class MarginCallModelTests
{
// Test class to enable calling protected methods
public class TestSecurityMarginModel : SecurityMarginModel
{
public TestSecurityMarginModel(decimal leverage) : base(leverage) {}
public new decimal GetInitialMarginRequiredForOrder(
InitialMarginRequiredForOrderParameters parameters)
{
return base.GetInitialMarginRequiredForOrder(parameters).Value;
}
public new decimal GetMarginRemaining(SecurityPortfolioManager portfolio, Security security, OrderDirection direction)
{
return base.GetMarginRemaining(portfolio, security, direction);
}
}
[Test]
public void InitializationTest()
{
const decimal actual = 2;
var security = GetSecurity(Symbols.AAPL);
security.BuyingPowerModel = new SecurityMarginModel(actual);
var expected = security.Leverage;
Assert.AreEqual(expected, actual);
}
[Test]
public void SetAndGetLeverageTest()
{
var security = GetSecurity(Symbols.AAPL);
security.BuyingPowerModel = new SecurityMarginModel(2);
const decimal actual = 50;
security.SetLeverage(actual);
var expected = security.Leverage;
Assert.AreEqual(expected, actual);
expected = security.BuyingPowerModel.GetLeverage(security);
Assert.AreEqual(expected, actual);
}
[Test]
public void GetInitialMarginRequiredForOrderTest()
{
var security = GetSecurity(Symbols.AAPL);
var buyingPowerModel = new TestSecurityMarginModel(2);
security.BuyingPowerModel = buyingPowerModel;
var order = new MarketOrder(security.Symbol, 100, DateTime.Now);
var actual = buyingPowerModel.GetInitialMarginRequiredForOrder(
new InitialMarginRequiredForOrderParameters(new IdentityCurrencyConverter(Currencies.USD), security, order));
Assert.AreEqual(0, actual);
}
[Test]
public void GetMaintenanceMarginTest()
{
const int quantity = 1000;
const decimal leverage = 2;
var expected = quantity / leverage;
var security = GetSecurity(Symbols.AAPL);
security.BuyingPowerModel = new SecurityMarginModel(leverage);
security.Holdings.SetHoldings(1m, quantity);
// current value is used to determine reserved buying power
security.SetMarketPrice(new TradeBar
{
Time = DateTime.Now,
Symbol = security.Symbol,
Open = 1,
High = 1,
Low = 1,
Close = 1
});
var actual = security.BuyingPowerModel.GetReservedBuyingPowerForPosition(security);
Assert.AreEqual(expected, actual);
}
[Test]
public void GetMarginRemainingTests()
{
const int quantity = 1000;
const decimal leverage = 2;
var orderProcessor = new FakeOrderProcessor();
var portfolio = GetPortfolio(orderProcessor, cash:1000);
var security = GetSecurity(Symbols.AAPL);
var buyingPowerModel = new TestSecurityMarginModel(leverage);
security.BuyingPowerModel = buyingPowerModel;
portfolio.Securities.Add(security);
// we buy $1000 worth of shares
security.Holdings.SetHoldings(1m, quantity);
portfolio.SetCash(0);
// current value is used to determine reserved buying power
security.SetMarketPrice(new TradeBar
{
Time = DateTime.Now,
Symbol = security.Symbol,
Open = 1,
High = 1,
Low = 1,
Close = 1
});
var actual1 = buyingPowerModel.GetMarginRemaining(portfolio, security, OrderDirection.Buy);
Assert.AreEqual(quantity / leverage, actual1);
var actual2 = buyingPowerModel.GetMarginRemaining(portfolio, security, OrderDirection.Sell);
Assert.AreEqual(quantity + quantity / leverage, actual2);
security.Holdings.SetHoldings(1m, -quantity);
var actual3 = buyingPowerModel.GetMarginRemaining(portfolio, security, OrderDirection.Sell);
Assert.AreEqual(quantity / leverage, actual3);
var actual4 = buyingPowerModel.GetMarginRemaining(portfolio, security, OrderDirection.Buy);
Assert.AreEqual(quantity + quantity / leverage, actual4);
}
///
/// Test GenerateMarginCallOrder with SecurityPortfolioManager.ScanForMarginCall
/// to comprehensively test margin call dynamics
///
[Test]
public void GenerateMarginCallOrderTests()
{
const int quantity = 1000;
const decimal leverage = 1m;
var orderProcessor = new FakeOrderProcessor();
var portfolio = GetPortfolio(orderProcessor, quantity);
portfolio.MarginCallModel = new DefaultMarginCallModel(portfolio, null);
var security = GetSecurity(Symbols.AAPL);
portfolio.Securities.Add(security);
var time = DateTime.Now;
const decimal buyPrice = 1m;
security.SetMarketPrice(new Tick(time, Symbols.AAPL, buyPrice, buyPrice));
var order = new MarketOrder(Symbols.AAPL, quantity, time) {Price = buyPrice};
var fill = new OrderEvent(order, DateTime.UtcNow, OrderFee.Zero)
{ FillPrice = buyPrice, FillQuantity = quantity, Status = OrderStatus.Filled};
orderProcessor.AddOrder(order);
var request = new SubmitOrderRequest(OrderType.Market, security.Type, security.Symbol, order.Quantity, 0, 0, order.Time, null);
request.SetOrderId(0);
orderProcessor.AddTicket(new OrderTicket(null, request));
Assert.AreEqual(portfolio.Cash, fill.FillPrice*fill.FillQuantity);
portfolio.ProcessFills(new List { fill });
Assert.AreEqual(0, portfolio.MarginRemaining);
Assert.AreEqual(quantity, portfolio.TotalMarginUsed);
Assert.AreEqual(quantity, portfolio.TotalPortfolioValue);
// we shouldn't be able to place a trader
var newOrder = new MarketOrder(Symbols.AAPL, 1, time.AddSeconds(1)) {Price = buyPrice};
var hasSufficientBuyingPower = security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(portfolio, security, newOrder).IsSufficient;
Assert.IsFalse(hasSufficientBuyingPower);
// now the stock doubles, so we should have margin remaining
time = time.AddDays(1);
const decimal highPrice = buyPrice * 2;
security.SetMarketPrice(new Tick(time, Symbols.AAPL, highPrice, highPrice));
portfolio.InvalidateTotalPortfolioValue();
// leverage is 1 we shouldn't have more margin remaining
Assert.AreEqual(0, portfolio.MarginRemaining);
Assert.AreEqual(quantity * 2, portfolio.TotalMarginUsed);
Assert.AreEqual(quantity * 2, portfolio.TotalPortfolioValue);
// we shouldn't be able to place a trader
var anotherOrder = new MarketOrder(Symbols.AAPL, 1, time.AddSeconds(1)) { Price = highPrice };
hasSufficientBuyingPower = security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(portfolio, security, anotherOrder).IsSufficient;
Assert.IsFalse(hasSufficientBuyingPower);
// now the stock plummets, leverage is 1 we shouldn't have more margin remaining
time = time.AddDays(1);
const decimal lowPrice = buyPrice/2;
security.SetMarketPrice(new Tick(time, Symbols.AAPL, lowPrice, lowPrice));
portfolio.InvalidateTotalPortfolioValue();
Assert.AreEqual(0, portfolio.MarginRemaining);
Assert.AreEqual(quantity/2m, portfolio.TotalMarginUsed);
Assert.AreEqual(quantity/2m, portfolio.TotalPortfolioValue);
// this would not cause a margin call due to leverage = 1
bool issueMarginCallWarning;
var marginCallOrders = portfolio.MarginCallModel.GetMarginCallOrders(out issueMarginCallWarning);
Assert.IsFalse(issueMarginCallWarning);
Assert.AreEqual(0, marginCallOrders.Count);
// now change the leverage to test margin call warning and margin call logic
security.SetLeverage(leverage * 2);
// simulate a loan - when we fill using leverage it will set a negative cash amount
portfolio.CashBook[Currencies.USD].SetAmount(-250);
// Stock price increase by minimum variation
const decimal newPrice = lowPrice + 0.01m;
security.SetMarketPrice(new Tick(time, Symbols.AAPL, newPrice, newPrice));
portfolio.InvalidateTotalPortfolioValue();
// this would not cause a margin call, only a margin call warning
marginCallOrders = portfolio.MarginCallModel.GetMarginCallOrders(out issueMarginCallWarning);
Assert.IsTrue(issueMarginCallWarning);
Assert.AreEqual(0, marginCallOrders.Count);
// Price drops again to previous low, margin call orders will be issued
security.SetMarketPrice(new Tick(time, Symbols.AAPL, lowPrice, lowPrice));
portfolio.InvalidateTotalPortfolioValue();
order = new MarketOrder(Symbols.AAPL, quantity, time) { Price = buyPrice };
fill = new OrderEvent(order, DateTime.UtcNow, OrderFee.Zero)
{ FillPrice = buyPrice, FillQuantity = quantity };
portfolio.ProcessFills(new List { fill });
Assert.AreEqual(-250, portfolio.TotalPortfolioValue);
marginCallOrders = portfolio.MarginCallModel.GetMarginCallOrders(out issueMarginCallWarning);
Assert.IsTrue(issueMarginCallWarning);
Assert.AreEqual(1, marginCallOrders.Count);
}
[Test]
public void GenerateMarginCallOrdersForPositionGroup()
{
const int cash = 22000;
var orderProcessor = new FakeOrderProcessor();
var portfolio = GetPortfolio(orderProcessor, cash);
portfolio.MarginCallModel = new DefaultMarginCallModel(portfolio, null, 0m);
var underlying = GetSecurity(Symbols.SPY);
var callOption = GetOption(Symbols.SPY_C_192_Feb19_2016);
var putOption = GetOption(Symbols.SPY_P_192_Feb19_2016);
callOption.Underlying = underlying;
putOption.Underlying = underlying;
portfolio.Securities.Add(underlying);
portfolio.Securities.Add(callOption);
portfolio.Securities.Add(putOption);
var time = DateTime.Now;
const decimal underlyingPrice = 100m;
const decimal callOptionPrice = 1m;
const decimal putOptionPrice = 1m;
underlying.SetMarketPrice(new Tick(time, underlying.Symbol, underlyingPrice, underlyingPrice));
callOption.SetMarketPrice(new Tick(time, callOption.Symbol, callOptionPrice, callOptionPrice));
putOption.SetMarketPrice(new Tick(time, putOption.Symbol, putOptionPrice, putOptionPrice));
var groupOrderManager = new GroupOrderManager(1, 2, -10);
var callOptionOrder = new ComboMarketOrder(callOption.Symbol, -10, time, groupOrderManager) { Price = callOptionPrice };
var putOptionOrder = new ComboMarketOrder(putOption.Symbol, -10, time, groupOrderManager) { Price = putOptionPrice };
var callOptionOrderFill = new OrderEvent(callOptionOrder, DateTime.UtcNow, OrderFee.Zero)
{
FillPrice = callOptionOrder.Price,
FillQuantity = callOptionOrder.Quantity,
Status = OrderStatus.Filled
};
var putOptionOrderFill = new OrderEvent(putOptionOrder, DateTime.UtcNow, OrderFee.Zero)
{
FillPrice = putOptionOrder.Price,
FillQuantity = putOptionOrder.Quantity,
Status = OrderStatus.Filled
};
orderProcessor.AddOrder(callOptionOrder);
orderProcessor.AddOrder(putOptionOrder);
var callOptionRequest = new SubmitOrderRequest(
OrderType.ComboMarket,
callOption.Type,
callOption.Symbol,
callOptionOrder.Quantity,
0,
0,
callOptionOrder.Time,
"",
groupOrderManager: groupOrderManager);
var putOptionRequest = new SubmitOrderRequest(
OrderType.ComboMarket,
putOption.Type,
putOption.Symbol,
putOptionOrder.Quantity,
0,
0,
putOptionOrder.Time,
"",
groupOrderManager: groupOrderManager);
callOptionRequest.SetOrderId(1);
putOptionRequest.SetOrderId(2);
groupOrderManager.OrderIds.Add(1);
groupOrderManager.OrderIds.Add(2);
callOptionOrderFill.Ticket = new OrderTicket(null, callOptionRequest);
orderProcessor.AddTicket(callOptionOrderFill.Ticket);
putOptionOrderFill.Ticket = new OrderTicket(null, putOptionRequest);
orderProcessor.AddTicket(putOptionOrderFill.Ticket);
portfolio.ProcessFills(new List { callOptionOrderFill, putOptionOrderFill });
// Simulate options price increase so the remaining margin goes below zero
callOption.SetMarketPrice(new Tick(time.AddMinutes(1), callOption.Symbol, callOptionPrice * 1.2m, callOptionPrice * 1.2m));
putOption.SetMarketPrice(new Tick(time.AddMinutes(1), putOption.Symbol, putOptionPrice * 1.2m, putOptionPrice * 1.2m));
var marginCallOrders = portfolio.MarginCallModel.GetMarginCallOrders(out var issueMarginCallWarning);
Assert.IsTrue(issueMarginCallWarning);
Assert.AreEqual(2, marginCallOrders.Count);
}
private SecurityPortfolioManager GetPortfolio(IOrderProcessor orderProcessor, int cash)
{
var securities = new SecurityManager(new TimeKeeper(DateTime.Now, new[] { TimeZones.NewYork }));
var transactions = new SecurityTransactionManager(null, securities);
transactions.SetOrderProcessor(orderProcessor);
var portfolio = new SecurityPortfolioManager(securities, transactions, new AlgorithmSettings());
portfolio.SetCash(cash);
return portfolio;
}
private Security GetSecurity(Symbol symbol)
{
return new Security(
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
new SubscriptionDataConfig(
typeof(TradeBar),
symbol,
Resolution.Minute,
TimeZones.NewYork,
TimeZones.NewYork,
true,
true,
true
),
new Cash(Currencies.USD, 0, 1m),
SymbolProperties.GetDefault(Currencies.USD),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
}
private Option GetOption(Symbol symbol)
{
return new Option(
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
new SubscriptionDataConfig(
typeof(TradeBar),
symbol,
Resolution.Minute,
TimeZones.NewYork,
TimeZones.NewYork,
true,
true,
true
),
new Cash(Currencies.USD, 0, 1m),
new OptionSymbolProperties("", Currencies.USD, 100, 0.01m, 1),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null);
}
}
}