public void OnDayClose(DateTime todaysDate, decimal totalCapitalToday) { //update the status of each trade foreach (TradeTracker t in TradeTrackers.Values) { t.Update(todaysDate, _data, _fxData); } //update position stats foreach (var kvp in Positions) { int id = kvp.Key; Position p = kvp.Value; decimal fxRate = p.Currency == null || p.Currency.ID == 1 ? 1 : _fxData[p.Currency.ID][0].Close; decimal? lastPrice = !_data.ContainsKey(id) || _data[id].CurrentBar < 0 ? (decimal?)null : _data[id][0].Close; p.GetPnL(lastPrice, fxRate); } //Capital usage and profit/loss for the day Capital.AddLong(Positions.Sum(x => x.Value.Capital.Long.Last())); Capital.AddShort(Positions.Sum(x => x.Value.Capital.Short.Last())); decimal todaysPnl = TradeTrackers.Sum(x => x.Value.TodaysPnL); _logger.Log(LogLevel.Trace, string.Format("Portfolio {0} @ {1}: Capital used: {2:0.00} P/L: {3:0.00}", Name, todaysDate, Capital.TodaysCapitalGross, todaysPnl)); //P/L curves ProfitLossEquityCurve.AddChange((double)todaysPnl, todaysDate); ProfitLossLongEquityCurve.AddValue((double)TradeTrackers.Sum(x => x.Value.TotalPnlLong), todaysDate); ProfitLossShortEquityCurve.AddValue((double)TradeTrackers.Sum(x => x.Value.TotalPnlShort), todaysDate); //ROAC if (Capital.TodaysCapitalGross == 0) { _deferredPnL += todaysPnl; RoacEquityCurve.AddReturn(0, todaysDate); } else { RoacEquityCurve.AddReturn((double)((_deferredPnL + todaysPnl) / Capital.TodaysCapitalGross), todaysDate); _deferredPnL = 0; } //ROTC if (totalCapitalToday == 0) { RotcEquityCurve.AddReturn(0, todaysDate); } else { RotcEquityCurve.AddReturn((double)(todaysPnl / totalCapitalToday), todaysDate); } Capital.EndOfDay(); }
private decimal CalculateCapitalUsage(Order order) { decimal capitalUsage = 0; //If the order is toward the end of the day, we don't want to count it for the day's capital usage var timeLimit = new TimeSpan(15, 40, 00); if (order.TradeDate.TimeOfDay < timeLimit) { capitalUsage = GetCapitalUsage(order); } else { //Unless! It's an intraday trade which we entered and exited after the cutoff, //in which case we want to count it if (Quantity == 0 || Math.Sign(Quantity) == Math.Sign(order.Quantity)) { //Adding to a position //We just want to keep track of this, no reason to add capital usage yet _avgPriceEnteredAfterEodCutoff = (Math.Abs(order.Quantity) * GetCapitalUsagePriceBasis(order.Price, order.FXRateToBase) + Math.Abs(_quantityEnteredAfterEodCutoff) * _avgPriceEnteredAfterEodCutoff) / Math.Abs(order.Quantity + _quantityEnteredAfterEodCutoff); _quantityEnteredAfterEodCutoff += order.Quantity; } else if (_quantityEnteredAfterEodCutoff != 0) { //Exiting a position opened after the cutoff decimal originalQuantityEntered = _quantityEnteredAfterEodCutoff; if (order.Quantity > 0) { //exiting a short capitalUsage = Math.Min(order.Quantity, -_quantityEnteredAfterEodCutoff) * _avgPriceEnteredAfterEodCutoff; Capital.AddShort(capitalUsage); _quantityEnteredAfterEodCutoff -= Math.Min(order.Quantity, -_quantityEnteredAfterEodCutoff); } else { //exiting a long capitalUsage = Math.Min(-order.Quantity, _quantityEnteredAfterEodCutoff) * _avgPriceEnteredAfterEodCutoff; Capital.AddLong(capitalUsage); _quantityEnteredAfterEodCutoff -= Math.Min(-order.Quantity, _quantityEnteredAfterEodCutoff); } //Did we actually reverse the position? if (Math.Abs(order.Quantity) > Math.Abs(originalQuantityEntered)) { _quantityEnteredAfterEodCutoff = Math.Sign(order.Quantity) * (Math.Abs(order.Quantity) - Math.Abs(originalQuantityEntered)); _avgPriceEnteredAfterEodCutoff = GetCapitalUsagePriceBasis(order.Price, order.FXRateToBase); } } } return(capitalUsage); }
/// <summary> /// Update the capital usage given a new order /// </summary> private decimal GetCapitalUsage(Order order) { decimal capitalUsage = 0; if (Math.Sign(order.Quantity) == Math.Sign(Quantity) || Math.Abs(order.Quantity) > Math.Abs(Quantity)) { //if reversing if ((order.Quantity < 0 && Quantity > 0) || (order.Quantity > 0 && Quantity < 0)) { capitalUsage = GetCapitalUsage(Math.Abs(Math.Abs(order.Quantity) - Math.Abs(Quantity)), order.Price, order.FXRateToBase); if (order.Quantity > 0) { Capital.AddLong(Math.Abs(Math.Abs(order.Quantity) - Math.Abs(Quantity)) * order.Price * order.FXRateToBase * Instrument.Multiplier * GetCapitalUsageMultiplier(Instrument.AssetCategory)); } else { Capital.AddShort(Math.Abs(Math.Abs(order.Quantity) - Math.Abs(Quantity)) * order.Price * order.FXRateToBase * Instrument.Multiplier * GetCapitalUsageMultiplier(Instrument.AssetCategory)); } } else //if adding { capitalUsage = GetCapitalUsage(order.Quantity, order.Price, order.FXRateToBase); if (order.Quantity > 0) { Capital.AddLong(Math.Abs(order.Quantity * order.Price) * order.FXRateToBase * Instrument.Multiplier * GetCapitalUsageMultiplier(Instrument.AssetCategory)); } else { Capital.AddShort(Math.Abs(order.Quantity * order.Price) * order.FXRateToBase * Instrument.Multiplier * GetCapitalUsageMultiplier(Instrument.AssetCategory)); } } } return(capitalUsage); }
public void Update(DateTime currentDate, Dictionary <int, TimeSeries> data, Dictionary <int, TimeSeries> fxData) { TodaysPnL = 0; if (!Open) { return; } //Update positions foreach (var kvp in Positions) { int id = kvp.Key; Position p = kvp.Value; decimal fxRate = p.Currency == null || p.Currency.ID <= 1 ? 1 : fxData[p.Currency.ID][0].Close; TodaysPnL += p.GetPnL(data[id].CurrentBar < 0 ? (decimal?)null : data[id][0].Close, fxRate); } //Update currency positions foreach (var kvp in CurrencyPositions) { int id = kvp.Key; if (fxData[id].CurrentBar < 0) { continue; } CurrencyPosition p = kvp.Value; decimal fxRate = fxData[id][0].Close; TodaysPnL += p.Update(fxRate); } if (Positions.Any(x => x.Value.Capital.Gross.Count > 0)) { Capital.AddLong(Positions.Sum(x => x.Value.Capital.Long.Last())); Capital.AddShort(Positions.Sum(x => x.Value.Capital.Short.Last())); } if (Capital.TodaysCapitalGross != 0) { _currentEquity *= (double)(1 + TodaysPnL / Capital.TodaysCapitalGross); } #if DEBUG _logger.Log(LogLevel.Trace, string.Format("Trade tracker ID {0} @ {1}, todays capital usage {2:0.00}, P/L: {3:0.00}", Trade.ID, currentDate, Capital.TodaysCapitalGross, TodaysPnL)); #endif Capital.EndOfDay(); _totalPnL += TodaysPnL; CumulativeReturns.Add(currentDate, _currentEquity); CumulativePnL.Add(currentDate, _totalPnL); Open = Positions.Values.Sum(x => x.Quantity) != 0 || CurrencyPositions.Values.Sum(x => x.Quantity) != 0 || (_ordersRemaining > 0 && _ordersRemaining < Trade.Orders.Count); }
/// <summary> /// Calculates profit/loss given a new price and FX rate. Also updates ROAC. /// </summary> /// <returns>The profit/loss since the last GetPnL() call.</returns> public decimal GetPnL(decimal?newPrice, decimal newFXRate) { //Null: this happens if there is no data on this anywhere. //Can happen when there is no prior position on an instrument. decimal updatePrice = newPrice ?? PriorPeriodCostBasis; var capitalUsage = GetCapitalUsage(_priorPeriodQuantity, PriorPeriodCostBasis, PriorPeriodFXRateBasis); if (_priorPeriodQuantity > 0) { Capital.AddLong(capitalUsage); } else { Capital.AddShort(capitalUsage); } //the change in value from the previous day if (Quantity > 0) { _unrecognizedPnLLong += Instrument.Multiplier * Quantity * (updatePrice * newFXRate - PriorPeriodCostBasis * PriorPeriodFXRateBasis); } else { _unrecognizedPnLShort += Instrument.Multiplier * Quantity * (updatePrice * newFXRate - PriorPeriodCostBasis * PriorPeriodFXRateBasis); } //calculate ROAC if (Capital.TodaysCapitalGross > 0) { ROAC = ROAC * (double)(1 + (_unrecognizedPnLLong + _unrecognizedPnLShort + _deferredPnL) / Capital.TodaysCapitalGross); _deferredPnL = 0; } else { //if no capital is deployed, ROAC calculation is impossible //so we just defer the profit/loss until some capital usage exists to calculate ROAC on //This happens when position is zero, but we get a cash transaction for example. _deferredPnL += _unrecognizedPnLLong + _unrecognizedPnLShort; } LastPrice = updatePrice; _priorPeriodQuantity = Quantity; //prior period cost basis is updated to reflect the latest prices PriorPeriodCostBasis = updatePrice; PriorPeriodFXRateBasis = newFXRate; //Total p/l is updated with today's profit/loss PnLLong += _unrecognizedPnLLong; PnLShort += _unrecognizedPnLShort; decimal toReturn = _unrecognizedPnLLong + _unrecognizedPnLShort; _unrecognizedPnLLong = 0; _unrecognizedPnLShort = 0; _quantityEnteredAfterEodCutoff = 0; _avgPriceEnteredAfterEodCutoff = 0; Capital.EndOfDay(); return(toReturn); }