/* * 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 QuantConnect.Orders; using QuantConnect.Logging; using QuantConnect.Orders.Fees; namespace QuantConnect.Securities { /// /// Provides a default implementation of that simply /// applies the fills to the algorithm's portfolio. This implementation is intended to /// handle all security types. /// public class SecurityPortfolioModel : ISecurityPortfolioModel { /// /// Performs application of an OrderEvent to the portfolio /// /// The algorithm's portfolio /// The fill's security /// The order event fill object to be applied public virtual void ProcessFill(SecurityPortfolioManager portfolio, Security security, OrderEvent fill) { var quoteCash = security.QuoteCurrency; //Get the required information from the vehicle this order will affect var isLong = security.Holdings.IsLong; var isShort = security.Holdings.IsShort; var closedPosition = false; //Make local decimals to avoid any rounding errors from int multiplication var quantityHoldings = (decimal)security.Holdings.Quantity; var averageHoldingsPrice = security.Holdings.AveragePrice; try { // apply sales value to holdings in the account currency var saleValue = security.Holdings.GetQuantityValue(fill.AbsoluteFillQuantity, fill.FillPrice).InAccountCurrency; security.Holdings.AddNewSale(saleValue); // subtract transaction fees from the portfolio var feeInAccountCurrency = 0m; if (fill.OrderFee != OrderFee.Zero // this is for user friendliness because some // Security types default to use 0 USD ConstantFeeModel && fill.OrderFee.Value.Amount != 0) { var feeThisOrder = fill.OrderFee.Value; feeInAccountCurrency = portfolio.CashBook.ConvertToAccountCurrency(feeThisOrder).Amount; security.Holdings.AddNewFee(feeInAccountCurrency); fill.OrderFee.ApplyToPortfolio(portfolio, fill); } // apply the funds using the current settlement model // we dont adjust funds for futures and CFDs: it is zero upfront payment derivative (margin applies though) // We do however apply funds for futures options, pay/gained premium, since they affect our cash balance the moment they are purchased/sold. if (security.Type != SecurityType.Future && security.Type != SecurityType.Cfd && security.Type != SecurityType.CryptoFuture) { security.SettlementModel.ApplyFunds(new ApplyFundsSettlementModelParameters(portfolio, security, fill.UtcTime, new CashAmount(-fill.FillQuantity * fill.FillPrice * security.SymbolProperties.ContractMultiplier, quoteCash.Symbol), fill)); } if (security.Type == SecurityType.Forex || security.Type == SecurityType.Crypto) { // model forex fills as currency swaps var forex = (IBaseCurrencySymbol) security; security.SettlementModel.ApplyFunds(new ApplyFundsSettlementModelParameters(portfolio, security, fill.UtcTime, new CashAmount(fill.FillQuantity, forex.BaseCurrency.Symbol), fill)); } // did we close or open a position further? closedPosition = isLong && fill.Direction == OrderDirection.Sell || isShort && fill.Direction == OrderDirection.Buy; // calculate the last trade profit if (closedPosition) { var lastTradeProfit = ProcessCloseTradeProfit(portfolio, security, fill); //Update Vehicle Profit Tracking: var lastTradeProfitInAccountCurrency = lastTradeProfit.InAccountCurrency; security.Holdings.AddNewProfit(lastTradeProfitInAccountCurrency); security.Holdings.SetLastTradeProfit(lastTradeProfitInAccountCurrency); var transactionProfitLoss = lastTradeProfitInAccountCurrency - 2 * feeInAccountCurrency; portfolio.AddTransactionRecord( security.LocalTime.ConvertToUtc(security.Exchange.TimeZone), transactionProfitLoss, fill.IsWin(security, transactionProfitLoss)); } //UPDATE HOLDINGS QUANTITY, AVG PRICE: //Currently NO holdings. The order is ALL our holdings. if (quantityHoldings == 0) { //First transaction just subtract order from cash and set our holdings: averageHoldingsPrice = fill.FillPrice; quantityHoldings = fill.FillQuantity; } else if (isLong) { //If we're currently LONG on the stock. switch (fill.Direction) { case OrderDirection.Buy: //Update the Holding Average Price: Total Value / Total Quantity: averageHoldingsPrice = ((averageHoldingsPrice*quantityHoldings) + (fill.FillQuantity*fill.FillPrice))/(quantityHoldings + fill.FillQuantity); //Add the new quantity: quantityHoldings += fill.FillQuantity; break; case OrderDirection.Sell: quantityHoldings += fill.FillQuantity; //+ a short = a subtraction if (quantityHoldings < 0) { //If we've now passed through zero from selling stock: new avg price: averageHoldingsPrice = fill.FillPrice; } else if (quantityHoldings == 0) { averageHoldingsPrice = 0; } break; } } else if (isShort) { //We're currently SHORTING the stock: What is the new position now? switch (fill.Direction) { case OrderDirection.Buy: //Buying when we're shorting moves to close position: quantityHoldings += fill.FillQuantity; if (quantityHoldings > 0) { //If we were short but passed through zero, new average price is what we paid. The short position was closed. averageHoldingsPrice = fill.FillPrice; } else if (quantityHoldings == 0) { averageHoldingsPrice = 0; } break; case OrderDirection.Sell: //We are increasing a Short position: //E.g. -100 @ $5, adding -100 @ $10: Avg: $7.5 // dAvg = (-500 + -1000) / -200 = 7.5 averageHoldingsPrice = ((averageHoldingsPrice*quantityHoldings) + (fill.FillQuantity*fill.FillPrice))/(quantityHoldings + fill.FillQuantity); quantityHoldings += fill.FillQuantity; break; } } } catch (Exception err) { Log.Error(err); } //Set the results back to the vehicle. security.Holdings.SetHoldings(averageHoldingsPrice, quantityHoldings); } /// /// Helper method to determine the close trade profit /// protected virtual ConvertibleCashAmount ProcessCloseTradeProfit(SecurityPortfolioManager portfolio, Security security, OrderEvent fill) { var absoluteHoldingsQuantity = security.Holdings.AbsoluteQuantity; // profit = (closed sale value - cost)*conversion to account currency // closed sale value = quantity closed * fill price BUYs are deemed negative cash flow // cost = quantity closed * average holdings price SELLS are deemed positive cash flow var absoluteQuantityClosed = Math.Min(fill.AbsoluteFillQuantity, absoluteHoldingsQuantity); var quantityClosed = Math.Sign(-fill.FillQuantity) * absoluteQuantityClosed; var closedCost = security.Holdings.GetQuantityValue(quantityClosed, security.Holdings.AveragePrice); var closedSaleValueInQuoteCurrency = security.Holdings.GetQuantityValue(quantityClosed, fill.FillPrice); var lastTradeProfit = new ConvertibleCashAmount(closedSaleValueInQuoteCurrency.Amount - closedCost.Amount, closedSaleValueInQuoteCurrency.Cash); // Reflect account cash adjustment for futures/CFD position if (security.Type == SecurityType.Future || security.Type == SecurityType.Cfd || security.Type == SecurityType.CryptoFuture) { security.SettlementModel.ApplyFunds(new ApplyFundsSettlementModelParameters(portfolio, security, fill.UtcTime, lastTradeProfit, fill)); } return lastTradeProfit; } } }