void Portfolio::TransactionCollection::append(const Instrument & instrument, Timestamp t, long quantity, numeric price, numeric fees) { if (container_.size() == 0) { // Add an all-zeroes transaction as origin container_.emplace_back(t - Poco::Timespan(1)); } // Transactions must be added in chronological order! poco_assert(t > container_.back().timestamp); // Get the previous quantity for this position long ppq = container_.back().positionQuantity; Transaction transaction(t, quantity, price, fees); if (ppq != 0 && ppq != -transaction.quantity && sign(ppq + transaction.quantity) != sign(ppq)) { // Split the transaction into two, first add the zero-ing transaction numeric perUnitFee = transaction.fees / abs(transaction.quantity); append(instrument, transaction.timestamp, -ppq, transaction.price, perUnitFee*abs(ppq)); // ajdust the inputs to reflect what's left to transact, increase the // date time by a bit to keep the uniqueness in the transaction set transaction.incrementTimestamp(); transaction.quantity += ppq; ppq = 0; transaction.fees = perUnitFee*std::abs(transaction.quantity); } // Transaction value, gross of fees // transaction.value = transaction.quantity*transaction.price*instrument.bpv() - transaction.fees; transaction.value = transaction.quantity*transaction.price*instrument.bpv(); // Transaction average cost transaction.averageCost = transaction.value / (transaction.quantity*instrument.bpv()); // Calculate the new quantity for this position transaction.positionQuantity = ppq + transaction.quantity; // Previous position average cost numeric ppac = container_.back().positionAverageCost; // Calculate position average cost if (transaction.positionQuantity == 0) transaction.positionAverageCost = 0.0; else if (abs(ppq) > abs(transaction.positionQuantity)) transaction.positionAverageCost = ppac; else transaction.positionAverageCost = ((ppq*ppac*instrument.bpv() + transaction.value) / (transaction.positionQuantity*instrument.bpv())); // Calculate PnL if (abs(ppq) < abs(transaction.positionQuantity) || ppq == 0) transaction.grossPnl = 0.0; else transaction.grossPnl = transaction.quantity*instrument.bpv()*(ppac - transaction.averageCost); transaction.netPnl = transaction.grossPnl + transaction.fees; container_.push_back(transaction); }
/** * @brief Computes the PnL for the current position * * Computes the PnL for the current position and the specified price. Undefined * behaviour if a position doesn't exist. * * A position starts with the first transaction which sets a quantity different than 0. * * Uses the gross PnL for all transactions part of this trade. * * @param[in] instrument the instrument * @param[in] price the price to compute the PnL * @param[out] realized the realized PnL * @param[out] unrealized the unrealized PnL */ void Portfolio::TransactionCollection::getPositionPnl(const Instrument & instrument, numeric price, numeric & realized, numeric & unrealized) const { auto it = container_.rbegin(); // Must not be called without a position poco_assert(it->positionQuantity != 0); unrealized = instrument.bpv()*it->positionQuantity*(price - it->positionAverageCost); // Add all realized pnl realized = 0.0; while (it->positionQuantity != 0) { realized += it->grossPnl; ++it; } }
/** * @brief Computes statistics for each trade and a summary * * @param[in] instrument the instrument * @param[out] tradeStats the per-trade statistics * @param[out] summary the totals */ void Portfolio::TransactionCollection::getTradeStats(const Instrument & instrument, TradeStatsVector & tradeStats) const { tradeStats.resize(0); ContainerType::const_iterator beginIt = container_.begin(); // Positiont at the first non-zero quantity while (true) { if (beginIt == container_.end()) return; // No meaningful transactions if (beginIt->positionQuantity != 0) break; // Found a real transaction ++beginIt; } ContainerType::const_iterator endIt = beginIt; for (++endIt; endIt != container_.end() && endIt->positionQuantity != 0; ++endIt) { } if (endIt != container_.end()) ++endIt; // [beginIt, endIt) contains all transactions participating in the current trade while (true) { numeric quantity = 0.0; numeric positionCostBasis = 0.0; numeric pnl = 0.0; TradeStats ts; ContainerType::const_iterator lastIt = std::prev(endIt); ts.start = beginIt->timestamp; ts.end = lastIt->timestamp; ts.initialPosition = beginIt->quantity; ts.maxPosition = 0; ts.numTransacations = 0; ts.maxNotionalCost = 0.0; ts.fees = 0; for (ContainerType::const_iterator it = beginIt; it != endIt; ++it) { if (it->value != 0) ++ts.numTransacations; positionCostBasis += it->value; ts.fees += it->fees; if (std::abs(it->positionQuantity) > std::abs(ts.maxPosition)) { ts.maxPosition = it->positionQuantity; ts.maxNotionalCost = positionCostBasis; } } numeric positionValue = lastIt->positionQuantity*instrument.bpv()*lastIt->price; ts.pnl = positionValue - positionCostBasis; ts.pctPnl = ts.pnl / std::abs(ts.maxNotionalCost); tradeStats.push_back(ts); // Advance to the next trade if (endIt == container_.end()) break; beginIt = endIt; for (++endIt; endIt != container_.end() && endIt->positionQuantity != 0; ++endIt) { } if (endIt != container_.end()) ++endIt; } }
void Portfolio::TransactionCollection::getPnl(const Instrument & instrument, const NumericIndexer & prices, NumericIndexer & pnl) const { pnl.resize(0); // Handle the trivial case of no transactions if (container_.size() <= 1) { pnl.append(std::begin(prices.index), std::end(prices.index)); return; } // Find the start sint currentTransaction = 1; sint ii = 0; while (ii < prices.size() && prices.index[ii] < container_[currentTransaction].timestamp) ++ii; // Set the pnl to 0 from beginning of time to the first transaction pnl.append(std::begin(prices.index), std::begin(prices.index) + ii); if (ii == prices.size()) return; numeric previousPositionValue = 0.0; while (ii < prices.size() && currentTransaction < container_.size()) { if (prices.index[ii] == container_[currentTransaction].timestamp) { // The current time is both in the price list and in the transaction list numeric transactionValue = container_[currentTransaction].value; numeric positionValue = container_[currentTransaction].positionQuantity*instrument.bpv()*prices.container[ii]; pnl.push_back(prices.index[ii], positionValue - previousPositionValue - transactionValue); ++ii; ++currentTransaction; previousPositionValue = positionValue; } else if (prices.index[ii] < container_[currentTransaction].timestamp) { // Only in the price list - use the previous position numeric positionValue = container_[currentTransaction - 1].positionQuantity*instrument.bpv()*prices.container[ii]; pnl.push_back(prices.index[ii], positionValue - previousPositionValue); ++ii; previousPositionValue = positionValue; } else { if (ii > 0) { // Only in the transaction list - use the previous price numeric positionValue = container_[currentTransaction].positionQuantity*instrument.bpv()*prices.container[ii - 1]; pnl.push_back(container_[currentTransaction].timestamp, positionValue - previousPositionValue - container_[currentTransaction].value); previousPositionValue = positionValue; } else { // No "previous" price - no pnl pnl.push_back(container_[currentTransaction].timestamp, 0.0); } ++currentTransaction; } } while (ii < prices.size()) { numeric positionValue = container_[currentTransaction - 1].positionQuantity*instrument.bpv()*prices.container[ii]; pnl.push_back(prices.index[ii], positionValue - previousPositionValue); ++ii; previousPositionValue = positionValue; } /* while (ii < time.size()) { numeric transactionValue = 0.0; if (next_transaction < container_.size()) { std::cout << time[ii] << " " << container_[next_transaction].time() << std::endl; BOOST_ASSERT_MSG(time[ii] <= container_[next_transaction].time(), "all transactions must be present in the price list"); if (time[ii] == container_[next_transaction].time()) { ++currentTransaction; ++next_transaction; transactionValue = container_[currentTransaction].value(); } } numeric positionValue = container_[currentTransaction].position_quantity()*instrument.bpv()*prices[ii]; pnl[ii] = positionValue - previousPositionValue - transactionValue; previousPositionValue = positionValue; // advance to the next date ++ii; } */ }