/*
* 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.Reflection;
using NUnit.Framework;
using QuantConnect.Interfaces;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Securities;
using QuantConnect.Securities.Positions;
namespace QuantConnect.Tests.Common.Securities
{
///
/// Provides an implementation of that verifies consistency with
/// the
///
public class BuyingPowerModelComparator : IBuyingPowerModel
{
public SecurityPortfolioManager Portfolio { get; }
public IBuyingPowerModel SecurityModel { get; }
public IPositionGroupBuyingPowerModel PositionGroupModel { get; }
private bool reentry;
public BuyingPowerModelComparator(
IBuyingPowerModel securityModel,
IPositionGroupBuyingPowerModel positionGroupModel,
SecurityPortfolioManager portfolio = null,
ITimeKeeper timeKeeper = null,
IOrderProcessor orderProcessor = null
)
{
Portfolio = portfolio;
SecurityModel = securityModel;
PositionGroupModel = positionGroupModel;
if (portfolio == null)
{
var securities = new SecurityManager(timeKeeper ?? new TimeKeeper(DateTime.UtcNow));
Portfolio = new SecurityPortfolioManager(securities, new SecurityTransactionManager(null, securities), new AlgorithmSettings());
}
if (orderProcessor != null)
{
Portfolio.Transactions.SetOrderProcessor(orderProcessor);
}
}
public decimal GetLeverage(Security security)
{
return SecurityModel.GetLeverage(security);
}
public void SetLeverage(Security security, decimal leverage)
{
SecurityModel.SetLeverage(security, leverage);
}
public MaintenanceMargin GetMaintenanceMargin(MaintenanceMarginParameters parameters)
{
EnsureSecurityExists(parameters.Security);
var expected = SecurityModel.GetMaintenanceMargin(parameters);
if (reentry)
{
return expected;
}
reentry = true;
var actual = PositionGroupModel.GetMaintenanceMargin(new PositionGroupMaintenanceMarginParameters(
Portfolio, new PositionGroup(PositionGroupModel, parameters.Quantity, new Position(parameters.Security, parameters.Quantity))
));
Assert.AreEqual(expected.Value, actual.Value,
$"{PositionGroupModel.GetType().Name}:{nameof(GetMaintenanceMargin)}"
);
reentry = false;
return expected;
}
public InitialMargin GetInitialMarginRequirement(InitialMarginParameters parameters)
{
EnsureSecurityExists(parameters.Security);
var expected = SecurityModel.GetInitialMarginRequirement(parameters);
if (reentry)
{
return expected;
}
reentry = true;
var actual = PositionGroupModel.GetInitialMarginRequirement(new PositionGroupInitialMarginParameters(
Portfolio, new PositionGroup(PositionGroupModel, parameters.Quantity, new Position(parameters.Security, parameters.Quantity))
));
Assert.AreEqual(expected.Value, actual.Value,
$"{PositionGroupModel.GetType().Name}:{nameof(GetInitialMarginRequirement)}"
);
reentry = false;
return expected;
}
public InitialMargin GetInitialMarginRequiredForOrder(InitialMarginRequiredForOrderParameters parameters)
{
reentry = true;
EnsureSecurityExists(parameters.Security);
var expected = SecurityModel.GetInitialMarginRequiredForOrder(parameters);
if (reentry)
{
return expected;
}
var actual = PositionGroupModel.GetInitialMarginRequiredForOrder(new PositionGroupInitialMarginForOrderParameters(
Portfolio, new PositionGroup(PositionGroupModel, parameters.Order.Quantity, new Position(parameters.Security, parameters.Order.Quantity)), parameters.Order
));
Assert.AreEqual(expected.Value, actual.Value,
$"{PositionGroupModel.GetType().Name}:{nameof(GetInitialMarginRequiredForOrder)}"
);
reentry = false;
return expected;
}
public HasSufficientBuyingPowerForOrderResult HasSufficientBuyingPowerForOrder(
HasSufficientBuyingPowerForOrderParameters parameters
)
{
EnsureSecurityExists(parameters.Security);
var expected = SecurityModel.HasSufficientBuyingPowerForOrder(parameters);
if (reentry)
{
return expected;
}
reentry = true;
var position = new Position(parameters.Security, parameters.Order.Quantity);
var actual = PositionGroupModel.HasSufficientBuyingPowerForOrder(
new HasSufficientPositionGroupBuyingPowerForOrderParameters(
Portfolio,
new PositionGroup(PositionGroupModel, position.GetGroupQuantity(), position),
new List { parameters.Order }
)
);
Assert.AreEqual(expected.IsSufficient, actual.IsSufficient,
$"{PositionGroupModel.GetType().Name}:{nameof(HasSufficientBuyingPowerForOrder)}: " +
$"ExpectedReason: {expected.Reason}{Environment.NewLine}" +
$"ActualReason: {actual.Reason}"
);
Assert.AreEqual(expected.Reason, actual.Reason,
$"{PositionGroupModel.GetType().Name}:{nameof(HasSufficientBuyingPowerForOrder)}"
);
reentry = false;
return expected;
}
public GetMaximumOrderQuantityResult GetMaximumOrderQuantityForTargetBuyingPower(
GetMaximumOrderQuantityForTargetBuyingPowerParameters parameters
)
{
EnsureSecurityExists(parameters.Security);
var expected = SecurityModel.GetMaximumOrderQuantityForTargetBuyingPower(parameters);
if (reentry)
{
return expected;
}
reentry = true;
var security = parameters.Security;
var positionGroup = Portfolio.Positions[new PositionGroupKey(PositionGroupModel, security)];
var actual = PositionGroupModel.GetMaximumLotsForTargetBuyingPower(
new GetMaximumLotsForTargetBuyingPowerParameters(
parameters.Portfolio,
positionGroup,
parameters.TargetBuyingPower,
parameters.MinimumOrderMarginPortfolioPercentage,
parameters.SilenceNonErrorReasons
)
);
var lotSize = security.SymbolProperties.LotSize;
Assert.AreEqual(expected.IsError, actual.IsError,
$"{PositionGroupModel.GetType().Name}:{nameof(GetMaximumOrderQuantityForTargetBuyingPower)}: " +
$"ExpectedQuantity: {expected.Quantity} ActualQuantity: {actual.NumberOfLots * lotSize} {Environment.NewLine}" +
$"ExpectedReason: {expected.Reason}{Environment.NewLine}" +
$"ActualReason: {actual.Reason}"
);
// we're not comparing group quantities, which is the number of position lots, but rather the implied
// position quantities resulting from having that many lots.
var resizedPositionGroup = positionGroup.WithQuantity(
Math.Sign(positionGroup.Quantity) == -1 ? -actual.NumberOfLots : actual.NumberOfLots, Portfolio.Positions);
var position = resizedPositionGroup.GetPosition(security.Symbol);
var bpmOrder = new MarketOrder(security.Symbol, expected.Quantity, parameters.Portfolio.Securities.UtcTime);
var pgbpmOrder = new MarketOrder(security.Symbol, position.Quantity, parameters.Portfolio.Securities.UtcTime);
var bpmOrderValue = bpmOrder.GetValue(security);
var pgbpmOrderValue = pgbpmOrder.GetValue(security);
var bpmOrderFees = security.FeeModel.GetOrderFee(new OrderFeeParameters(security, bpmOrder)).Value.Amount;
var pgbpmOrderFees = security.FeeModel.GetOrderFee(new OrderFeeParameters(security, pgbpmOrder)).Value.Amount;
var bpmMarginRequired = bpmOrderValue + bpmOrderFees;
var pgbpmMarginRequired = pgbpmOrderValue + pgbpmOrderFees;
Assert.AreEqual(expected.Quantity, position.Quantity,
$"{PositionGroupModel.GetType().Name}:{nameof(GetMaximumOrderQuantityForTargetBuyingPower)}: " +
$"ExpectedReason: {expected.Reason}{Environment.NewLine}" +
$"ActualReason: {actual.Reason}"
);
Assert.AreEqual(expected.Reason, actual.Reason,
$"{PositionGroupModel.GetType().Name}:{nameof(GetMaximumOrderQuantityForTargetBuyingPower)}: " +
$"ExpectedReason: {expected.Reason}{Environment.NewLine}" +
$"ActualReason: {actual.Reason}"
);
reentry = false;
return expected;
}
public GetMaximumOrderQuantityResult GetMaximumOrderQuantityForDeltaBuyingPower(
GetMaximumOrderQuantityForDeltaBuyingPowerParameters parameters
)
{
EnsureSecurityExists(parameters.Security);
var expected = SecurityModel.GetMaximumOrderQuantityForDeltaBuyingPower(parameters);
if (reentry)
{
return expected;
}
reentry = true;
var security = parameters.Security;
var positionGroup = Portfolio.Positions[new PositionGroupKey(PositionGroupModel, security)];
var actual = PositionGroupModel.GetMaximumLotsForDeltaBuyingPower(
new GetMaximumLotsForDeltaBuyingPowerParameters(
parameters.Portfolio,
positionGroup,
parameters.DeltaBuyingPower,
parameters.MinimumOrderMarginPortfolioPercentage,
parameters.SilenceNonErrorReasons
)
);
var lotSize = security.SymbolProperties.LotSize;
Assert.AreEqual(expected.IsError, actual.IsError,
$"{PositionGroupModel.GetType().Name}:{nameof(GetMaximumOrderQuantityForDeltaBuyingPower)}: " +
$"ExpectedQuantity: {expected.Quantity} ActualQuantity: {actual.NumberOfLots * lotSize} {Environment.NewLine}" +
$"ExpectedReason: {expected.Reason}{Environment.NewLine}" +
$"ActualReason: {actual.Reason}"
);
// we're not comparing group quantities, which is the number of position lots, but rather the implied
// position quantities resulting from having that many lots.
var resizedPositionGroup = positionGroup.WithQuantity(
Math.Sign(positionGroup.Quantity) == -1 ? -actual.NumberOfLots : actual.NumberOfLots, Portfolio.Positions);
var position = resizedPositionGroup.GetPosition(security.Symbol);
var bpmOrder = new MarketOrder(security.Symbol, expected.Quantity, parameters.Portfolio.Securities.UtcTime);
var pgbpmOrder = new MarketOrder(security.Symbol, position.Quantity, parameters.Portfolio.Securities.UtcTime);
var bpmMarginRequired = security.BuyingPowerModel.GetInitialMarginRequirement(security, bpmOrder.Quantity);
var pgbpmMarginRequired = PositionGroupModel.GetInitialMarginRequiredForOrder(Portfolio, resizedPositionGroup, pgbpmOrder);
var availableBuyingPower = security.BuyingPowerModel.GetBuyingPower(parameters.Portfolio, security, bpmOrder.Direction);
Assert.AreEqual(expected.Quantity, position.Quantity,
$"{PositionGroupModel.GetType().Name}:{nameof(GetMaximumOrderQuantityForDeltaBuyingPower)}: " +
$"ExpectedReason: {expected.Reason}{Environment.NewLine}" +
$"ActualReason: {actual.Reason}"
);
Assert.AreEqual(expected.Reason, actual.Reason,
$"{PositionGroupModel.GetType().Name}:{nameof(GetMaximumOrderQuantityForDeltaBuyingPower)}"
);
reentry = false;
return expected;
}
public ReservedBuyingPowerForPosition GetReservedBuyingPowerForPosition(ReservedBuyingPowerForPositionParameters parameters)
{
EnsureSecurityExists(parameters.Security);
var expected = SecurityModel.GetReservedBuyingPowerForPosition(parameters);
if (reentry)
{
return expected;
}
reentry = true;
reentry = false;
return expected;
}
public BuyingPower GetBuyingPower(BuyingPowerParameters parameters)
{
EnsureSecurityExists(parameters.Security);
var expected = SecurityModel.GetBuyingPower(parameters);
if (reentry)
{
return expected;
}
reentry = false;
return expected;
}
private void EnsureSecurityExists(Security security)
{
if (!Portfolio.Securities.ContainsKey(security.Symbol))
{
var timeKeeper = (LocalTimeKeeper) typeof(Security).GetField("_localTimeKeeper", BindingFlags.NonPublic|BindingFlags.Instance).GetValue(security);
Portfolio.Securities[security.Symbol] = security;
security.SetLocalTimeKeeper(timeKeeper);
}
}
}
}