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

399 lines
19 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Indicators;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Securities;
using QuantConnect.Securities.Positions;
namespace QuantConnect.Tests.Common.Securities
{
[TestFixture]
public class PatternDayTradingMarginBuyingPowerModelTests
{
private static readonly DateTime Noon = new DateTime(2016, 02, 16, 12, 0, 0);
private static readonly DateTime Midnight = new DateTime(2016, 02, 16, 0, 0, 0);
private static readonly DateTime NoonWeekend = new DateTime(2016, 02, 14, 12, 0, 0);
private static readonly DateTime NoonHoliday = new DateTime(2016, 02, 15, 12, 0, 0);
private static readonly TimeKeeper TimeKeeper = new TimeKeeper(Noon.ConvertToUtc(TimeZones.NewYork), TimeZones.NewYork);
private BuyingPowerModelComparator GetModel(decimal closed = 2m, decimal open = 4m)
{
return new BuyingPowerModelComparator(
new PatternDayTradingMarginModel(closed, open),
new SecurityPositionGroupBuyingPowerModel(),
timeKeeper: TimeKeeper
);
}
[Test]
public void InitializationTests()
{
// No parameters initialization, used default PDT 4x leverage open market and 2x leverage otherwise
var model = GetModel();
var leverage = model.GetLeverage(CreateSecurity(model.SecurityModel, Noon));
Assert.AreEqual(4.0m, leverage);
model = GetModel(2m, 5m);
leverage = model.GetLeverage(CreateSecurity(model.SecurityModel, Noon));
Assert.AreEqual(5.0m, leverage);
}
[Test]
public void SetLeverageTest()
{
var model = GetModel();
// Open market
var security = CreateSecurity(model.SecurityModel, Noon);
security.BuyingPowerModel = GetModel();
model.SetLeverage(security, 10m);
Assert.AreNotEqual(10m, model.GetLeverage(security));
// Closed market
security = CreateSecurity(model.SecurityModel, Midnight);
model.SetLeverage(security, 10m);
Assert.AreNotEqual(10m, model.GetLeverage(security));
security.Holdings.SetHoldings(100m, 100);
}
[Test]
public void VerifyOpenMarketLeverage()
{
// Market is Open on Tuesday, Feb, 16th 2016 at Noon
// SPY @ $100 * 100 Shares / Leverage (4) = 2500
var leverage = 4m;
var expected = 100 * 100m / leverage;
var model = GetModel();
var security = CreateSecurity(model.SecurityModel, Noon);
var order = new MarketOrder(security.Symbol, 100, security.LocalTime);
Assert.AreEqual((double)leverage, (double)model.GetLeverage(security), 1e-3);
Assert.AreEqual((double)expected, (double)model.GetInitialMarginRequiredForOrder(
new InitialMarginRequiredForOrderParameters(new IdentityCurrencyConverter(Currencies.USD), security, order)).Value, 1e-3);
}
[Test]
public void VerifyOpenMarketLeverageAltVersion()
{
// Market is Open on Tuesday, Feb, 16th 2016 at Noon
// SPY @ $100 * 100 Shares / Leverage (5) = 2000
var leverage = 5m;
var expected = 100 * 100m / leverage;
var model = GetModel(2m, leverage);
var security = CreateSecurity(model.SecurityModel, Noon);
var order = new MarketOrder(security.Symbol, 100, security.LocalTime);
Assert.AreEqual((double)leverage, (double)model.GetLeverage(security), 1e-3);
Assert.AreEqual((double)expected, (double)model.GetInitialMarginRequiredForOrder(
new InitialMarginRequiredForOrderParameters(new IdentityCurrencyConverter(Currencies.USD), security, order)).Value, 1e-3);
}
[Test]
public void VerifyClosedMarketLeverage()
{
// SPY @ $100 * 100 Shares / Leverage (2) = 5000
var leverage = 2m;
var expected = 100 * 100m / leverage;
var model = GetModel();
// Market is Closed on Tuesday, Feb, 16th 2016 at Midnight
var security = CreateSecurity(model.SecurityModel, Midnight);
var order = new MarketOrder(security.Symbol, 100, security.LocalTime);
Assert.AreEqual((double)leverage, (double)model.GetLeverage(security), 1e-3);
Assert.AreEqual((double)expected, (double)model.GetInitialMarginRequiredForOrder(
new InitialMarginRequiredForOrderParameters(new IdentityCurrencyConverter(Currencies.USD), security, order)).Value, 1e-3);
// Market is Closed on Monday, Feb, 15th 2016 at Noon (US President Day)
security = CreateSecurity(model.SecurityModel, NoonHoliday);
order = new MarketOrder(security.Symbol, 100, security.LocalTime);
Assert.AreEqual((double)leverage, (double)model.GetLeverage(security), 1e-3);
Assert.AreEqual((double)expected, (double)model.GetInitialMarginRequiredForOrder(
new InitialMarginRequiredForOrderParameters(new IdentityCurrencyConverter(Currencies.USD), security, order)).Value, 1e-3);
// Market is Closed on Sunday, Feb, 14th 2016 at Noon
security = CreateSecurity(model.SecurityModel, NoonWeekend);
order = new MarketOrder(security.Symbol, 100, security.LocalTime);
Assert.AreEqual((double)leverage, (double)model.GetLeverage(security), 1e-3);
Assert.AreEqual((double)expected, (double)model.GetInitialMarginRequiredForOrder(
new InitialMarginRequiredForOrderParameters(new IdentityCurrencyConverter(Currencies.USD), security, order)).Value, 1e-3);
}
[Test]
public void VerifyClosedMarketLeverageAltVersion()
{
// SPY @ $100 * 100 Shares / Leverage (3) = 3333.33
var leverage = 3m;
var expected = 100 * 100m / leverage;
var model = GetModel(leverage, 4m);
// Market is Closed on Tuesday, Feb, 16th 2016 at Midnight
var security = CreateSecurity(model.SecurityModel, Midnight);
var order = new MarketOrder(security.Symbol, 100, security.LocalTime);
Assert.AreEqual((double)leverage, (double)model.GetLeverage(security), 1e-3);
Assert.AreEqual((double)expected, (double)model.GetInitialMarginRequiredForOrder(
new InitialMarginRequiredForOrderParameters(new IdentityCurrencyConverter(Currencies.USD), security, order)).Value, 1e-3);
// Market is Closed on Monday, Feb, 15th 2016 at Noon (US President Day)
security = CreateSecurity(model.SecurityModel, NoonHoliday);
order = new MarketOrder(security.Symbol, 100, security.LocalTime);
Assert.AreEqual((double)leverage, (double)model.GetLeverage(security), 1e-3);
Assert.AreEqual((double)expected, (double)model.GetInitialMarginRequiredForOrder(
new InitialMarginRequiredForOrderParameters(new IdentityCurrencyConverter(Currencies.USD), security, order)).Value, 1e-3);
// Market is Closed on Sunday, Feb, 14th 2016 at Noon
security = CreateSecurity(model.SecurityModel, NoonWeekend);
order = new MarketOrder(security.Symbol, 100, security.LocalTime);
Assert.AreEqual((double)leverage, (double)model.GetLeverage(security), 1e-3);
Assert.AreEqual((double)expected, (double)model.GetInitialMarginRequiredForOrder(
new InitialMarginRequiredForOrderParameters(new IdentityCurrencyConverter(Currencies.USD), security, order)).Value, 1e-3);
}
[Test]
public void VerifyClosingSoonMarketLeverage()
{
var closedLeverage = 2m;
var openLeverage = 5m;
var model = GetModel(closedLeverage, openLeverage);
// Market is Closed on Tuesday, Feb, 16th 2016 at 16
var security = CreateSecurity(model.SecurityModel, new DateTime(2016, 2, 16, 15, 49, 0));
Assert.AreEqual(openLeverage, model.GetLeverage(security));
Assert.IsFalse(security.Exchange.ClosingSoon);
var localTimeKeeper = TimeKeeper.GetLocalTimeKeeper(TimeZones.NewYork);
localTimeKeeper.UpdateTime(new DateTime(2016, 2, 16, 15, 50, 0).ConvertToUtc(TimeZones.NewYork));
Assert.AreEqual(closedLeverage, model.GetLeverage(security));
Assert.IsTrue(security.Exchange.ClosingSoon);
Assert.IsTrue(security.Exchange.ExchangeOpen);
localTimeKeeper.UpdateTime(new DateTime(2016, 2, 16, 16, 0, 0).ConvertToUtc(TimeZones.NewYork));
Assert.IsFalse(security.Exchange.ExchangeOpen);
}
[Test]
public void VerifyMaintenaceMargin()
{
var model = GetModel();
// Open Market
var security = CreateSecurity(model.SecurityModel, Noon);
security.Holdings.SetHoldings(100m, 100);
Assert.AreEqual((double)100 * 100 / 4, (double)model.GetMaintenanceMargin(security), 1e-3);
// Closed Market
security = CreateSecurity(model.SecurityModel, Midnight);
security.Holdings.SetHoldings(100m, 100);
Assert.AreEqual((double)100 * 100 / 2, (double)model.GetMaintenanceMargin(security), 1e-3);
}
[Test]
public void VerifyMarginCallOrderLongOpenMarket()
{
var securityPrice = 100m;
var quantity = 300;
var orderProcessor = new FakeOrderProcessor();
var portfolio = GetPortfolio(orderProcessor, quantity, Noon);
var model = GetModel();
// Open Market
var security = CreateSecurity(model.SecurityModel, Noon);
security.BuyingPowerModel = model;
security.Holdings.SetHoldings(securityPrice, quantity);
portfolio.Securities.Add(security);
portfolio.CashBook["USD"].AddAmount(-25000);
portfolio.InvalidateTotalPortfolioValue();
var netLiquidationValue = portfolio.TotalPortfolioValue;
var totalMargin = portfolio.TotalMarginUsed;
portfolio.MarginCallModel = new TestDefaultMarginCallModel(portfolio, new OrderProperties());
var expected = -(int)(Math.Round((totalMargin - netLiquidationValue) / securityPrice, MidpointRounding.AwayFromZero) * 4m);
var actual = (portfolio.MarginCallModel as TestDefaultMarginCallModel).GenerateMarginCallOrders(
new MarginCallOrdersParameters(portfolio.Positions.Groups.Single(), netLiquidationValue, totalMargin)).Single().Quantity;
Assert.AreEqual(expected, actual);
}
[Test]
public void VerifyMarginCallOrderLongClosedMarket()
{
var securityPrice = 100m;
var quantity = 300;
var orderProcessor = new FakeOrderProcessor();
var portfolio = GetPortfolio(orderProcessor, quantity, Midnight);
var model = GetModel();
// Open Market
var security = CreateSecurity(model.SecurityModel, Midnight);
security.BuyingPowerModel = model;
security.Holdings.SetHoldings(securityPrice, quantity);
portfolio.Securities.Add(security);
portfolio.CashBook["USD"].AddAmount(-25000);
portfolio.InvalidateTotalPortfolioValue();
var netLiquidationValue = portfolio.TotalPortfolioValue;
var totalMargin = portfolio.TotalMarginUsed;
portfolio.MarginCallModel = new TestDefaultMarginCallModel(portfolio, new OrderProperties());
var expected = -(int)(Math.Round((totalMargin - netLiquidationValue) / securityPrice, MidpointRounding.AwayFromZero) * 2m);
var actual = (portfolio.MarginCallModel as TestDefaultMarginCallModel).GenerateMarginCallOrders(
new MarginCallOrdersParameters(portfolio.Positions.Groups.Single(), netLiquidationValue, totalMargin)).Single().Quantity;
Assert.AreEqual(expected, actual);
}
[Test]
public void VerifyMarginCallOrderShortOpenMarket()
{
var securityPrice = 100m;
var quantity = -300;
var orderProcessor = new FakeOrderProcessor();
var portfolio = GetPortfolio(orderProcessor, quantity, Noon);
var model = GetModel();
// Open Market
var security = CreateSecurity(model.SecurityModel, Noon);
security.BuyingPowerModel = model;
security.Holdings.SetHoldings(securityPrice, quantity);
portfolio.Securities.Add(security);
portfolio.CashBook["USD"].AddAmount(35000);
portfolio.InvalidateTotalPortfolioValue();
var netLiquidationValue = portfolio.TotalPortfolioValue;
var totalMargin = portfolio.TotalMarginUsed;
var expected = (int)(Math.Round((totalMargin - netLiquidationValue) / securityPrice, MidpointRounding.AwayFromZero) * 4m);
var actual = (portfolio.MarginCallModel as TestDefaultMarginCallModel).GenerateMarginCallOrders(
new MarginCallOrdersParameters(portfolio.Positions.Groups.Single(), netLiquidationValue, totalMargin)).Single().Quantity;
Assert.AreEqual(expected, actual);
}
[Test]
public void VerifyMarginCallOrderShortClosedMarket()
{
var securityPrice = 100m;
var quantity = -300;
var orderProcessor = new FakeOrderProcessor();
var portfolio = GetPortfolio(orderProcessor, quantity, Midnight);
var model = GetModel();
// Open Market
var security = CreateSecurity(model.SecurityModel, Midnight);
security.BuyingPowerModel = model;
security.Holdings.SetHoldings(securityPrice, quantity);
portfolio.Securities.Add(security);
portfolio.CashBook["USD"].AddAmount(35000);
portfolio.InvalidateTotalPortfolioValue();
var netLiquidationValue = portfolio.TotalPortfolioValue;
var totalMargin = portfolio.TotalMarginUsed;
var expected = (int)(Math.Round((totalMargin - netLiquidationValue) / securityPrice, MidpointRounding.AwayFromZero) * 2m);
var actual = (portfolio.MarginCallModel as TestDefaultMarginCallModel).GenerateMarginCallOrders(
new MarginCallOrdersParameters(portfolio.Positions.Groups.Single(), netLiquidationValue, totalMargin)).Single().Quantity;
Assert.AreEqual(expected, actual);
}
private SecurityPortfolioManager GetPortfolio(IOrderProcessor orderProcessor, int quantity, DateTime time)
{
var securities = new SecurityManager(new TimeKeeper(time.ConvertToUtc(TimeZones.NewYork), TimeZones.NewYork));
var transactions = new SecurityTransactionManager(null, securities);
transactions.SetOrderProcessor(orderProcessor);
var portfolio = new SecurityPortfolioManager(securities, transactions, new AlgorithmSettings());
portfolio.SetCash(quantity);
portfolio.MarginCallModel = new TestDefaultMarginCallModel(portfolio, new OrderProperties());
return portfolio;
}
private static Security CreateSecurity(IBuyingPowerModel buyingPowerModel, DateTime newLocalTime)
{
var security = new Security(
CreateUsEquitySecurityExchangeHours(),
CreateTradeBarConfig(),
new Cash(Currencies.USD, 0, 1m),
SymbolProperties.GetDefault(Currencies.USD),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
TimeKeeper.SetUtcDateTime(newLocalTime.ConvertToUtc(security.Exchange.TimeZone));
security.BuyingPowerModel = buyingPowerModel;
security.SetLocalTimeKeeper(TimeKeeper.GetLocalTimeKeeper(TimeZones.NewYork));
security.SetMarketPrice(new IndicatorDataPoint(Symbols.SPY, newLocalTime, 100m));
security.FeeModel = new ConstantFeeModel(0);
return security;
}
private static SecurityExchangeHours CreateUsEquitySecurityExchangeHours()
{
var sunday = LocalMarketHours.ClosedAllDay(DayOfWeek.Sunday);
var monday = new LocalMarketHours(DayOfWeek.Monday, new TimeSpan(9, 30, 0), new TimeSpan(16, 0, 0));
var tuesday = new LocalMarketHours(DayOfWeek.Tuesday, new TimeSpan(9, 30, 0), new TimeSpan(16, 0, 0));
var wednesday = new LocalMarketHours(DayOfWeek.Wednesday, new TimeSpan(9, 30, 0), new TimeSpan(16, 0, 0));
var thursday = new LocalMarketHours(DayOfWeek.Thursday, new TimeSpan(9, 30, 0), new TimeSpan(16, 0, 0));
var friday = new LocalMarketHours(DayOfWeek.Friday, new TimeSpan(9, 30, 0), new TimeSpan(16, 0, 0));
var saturday = LocalMarketHours.ClosedAllDay(DayOfWeek.Saturday);
var earlyCloses = new Dictionary<DateTime, TimeSpan>();
var lateOpens = new Dictionary<DateTime, TimeSpan>();
var holidays = MarketHoursDatabase.FromDataFolder()
.GetEntry(Market.USA, (string)null, SecurityType.Equity)
.ExchangeHours
.Holidays;
return new SecurityExchangeHours(TimeZones.NewYork, holidays, new[]
{
sunday, monday, tuesday, wednesday, thursday, friday, saturday
}.ToDictionary(x => x.DayOfWeek), earlyCloses, lateOpens);
}
private static SubscriptionDataConfig CreateTradeBarConfig()
{
return new SubscriptionDataConfig(typeof(TradeBar), Symbols.SPY, Resolution.Minute, TimeZones.NewYork,
TimeZones.NewYork, true, true, false);
}
}
}