/*
* 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;
namespace QuantConnect.Securities
{
///
/// Represents the model responsible for applying cash settlement rules
///
/// This model applies cash settlement after T+N days
public class DelayedSettlementModel : ISettlementModel
{
private readonly int _numberOfDays;
private readonly TimeSpan _timeOfDay;
private CashBook _cashBook;
///
/// The list of pending funds waiting for settlement time
///
private readonly Queue _unsettledCashAmounts;
///
/// Creates an instance of the class
///
/// The number of days required for settlement
/// The time of day used for settlement
public DelayedSettlementModel(int numberOfDays, TimeSpan timeOfDay)
{
_timeOfDay = timeOfDay;
_numberOfDays = numberOfDays;
_unsettledCashAmounts = new();
}
///
/// Applies cash settlement rules
///
/// The funds application parameters
public void ApplyFunds(ApplyFundsSettlementModelParameters applyFundsParameters)
{
var currency = applyFundsParameters.CashAmount.Currency;
var amount = applyFundsParameters.CashAmount.Amount;
var security = applyFundsParameters.Security;
var portfolio = applyFundsParameters.Portfolio;
if (amount > 0)
{
// positive amount: sell order filled
portfolio.UnsettledCashBook[currency].AddAmount(amount);
// find the correct settlement date (usually T+3 or T+1)
var settlementDate = applyFundsParameters.UtcTime.ConvertFromUtc(security.Exchange.TimeZone).Date;
for (var i = 0; i < _numberOfDays; i++)
{
settlementDate = settlementDate.AddDays(1);
// only count days when market is open
if (!security.Exchange.Hours.IsDateOpen(settlementDate))
i--;
}
// use correct settlement time
var settlementTimeUtc = settlementDate.Add(_timeOfDay).ConvertToUtc(security.Exchange.Hours.TimeZone);
lock (_unsettledCashAmounts)
{
_unsettledCashAmounts.Enqueue(new UnsettledCashAmount(settlementTimeUtc, currency, amount));
}
}
else
{
// negative amount: buy order filled
portfolio.CashBook[currency].AddAmount(amount);
}
// We just keep it to use currency conversion in GetUnsettledCash method
if (_cashBook == null)
{
_cashBook = portfolio.UnsettledCashBook;
}
}
///
/// Scan for pending settlements
///
/// The settlement parameters
public void Scan(ScanSettlementModelParameters settlementParameters)
{
lock (_unsettledCashAmounts)
{
while (_unsettledCashAmounts.TryPeek(out var item)
// check if settlement time has passed
&& settlementParameters.UtcTime >= item.SettlementTimeUtc)
{
// remove item from unsettled funds list
_unsettledCashAmounts.Dequeue();
// update unsettled cashbook
settlementParameters.Portfolio.UnsettledCashBook[item.Currency].AddAmount(-item.Amount);
// update settled cashbook
settlementParameters.Portfolio.CashBook[item.Currency].AddAmount(item.Amount);
}
}
}
///
/// Gets the unsettled cash amount for the security
///
public CashAmount GetUnsettledCash()
{
var accountCurrency = _cashBook != null ? _cashBook.AccountCurrency : Currencies.USD;
lock (_unsettledCashAmounts)
{
if (_unsettledCashAmounts.Count == 0)
{
return default;
}
return new CashAmount(_unsettledCashAmounts.Sum(x => _cashBook.ConvertToAccountCurrency(x.Amount, x.Currency)), accountCurrency);
}
}
}
}