PositionTracker¶
PositionTracker implements IPositionManager with full support for FIFO, LIFO, and Average cost basis methods. This is critical for compliance and accurate PnL reporting.
enum class CostBasisMethod { FIFO, LIFO, AVERAGE };
class PositionTracker : public IPositionManager
{
public:
PositionTracker(SubscriberId id, CostBasisMethod method = CostBasisMethod::FIFO);
};
Purpose¶
- Track positions across all symbols
- Calculate realized PnL using configurable cost basis method
- Provide average entry price for position sizing and risk management
- Thread-safe access from multiple components
Cost Basis Methods¶
FIFO (First In, First Out)¶
Oldest lots are closed first. Default method, required by many tax jurisdictions.
Buy 10 @ $100
Buy 10 @ $110
Sell 15 @ $120
Realized PnL:
- Close 10 @ $100: ($120 - $100) * 10 = $200
- Close 5 @ $110: ($120 - $110) * 5 = $50
Total: $250
Remaining: 5 @ $110
LIFO (Last In, First Out)¶
Newest lots are closed first.
Buy 10 @ $100
Buy 10 @ $110
Sell 15 @ $120
Realized PnL:
- Close 10 @ $110: ($120 - $110) * 10 = $100
- Close 5 @ $100: ($120 - $100) * 5 = $100
Total: $200
Remaining: 5 @ $100
AVERAGE (Volume-Weighted Average)¶
All lots consolidated into single VWAP position.
Buy 10 @ $100
Buy 10 @ $110
Avg price: (10*100 + 10*110) / 20 = $105
Sell 15 @ $120
Realized PnL: ($120 - $105) * 15 = $225
Remaining: 5 @ $105
API¶
Constructor¶
Position Queries¶
Quantity getPosition(SymbolId symbol) const override;
Price getAvgEntryPrice(SymbolId symbol) const;
Price getRealizedPnl(SymbolId symbol) const;
Price getTotalRealizedPnl() const;
CostBasisMethod method() const;
Order Event Handlers¶
Inherited from IOrderExecutionListener:
void onOrderFilled(const Order& order) override;
void onOrderPartiallyFilled(const Order& order, Quantity fillQty) override;
Example Usage¶
Backtest Integration¶
BacktestConfig config;
config.initialCapital = 10000.0;
config.feeRate = 0.0004;
BacktestRunner runner(config);
// Create strategy
MyStrategy strategy(1, symbol);
runner.setStrategy(&strategy);
// Add position tracker
PositionTracker positions(2, CostBasisMethod::FIFO);
runner.addExecutionListener(&positions);
// Run backtest
auto result = runner.run(*reader);
// Check results
std::cout << "Position: " << positions.getPosition(symbol).toDouble() << "\n";
std::cout << "Avg entry: " << positions.getAvgEntryPrice(symbol).toDouble() << "\n";
std::cout << "Realized PnL: " << positions.getRealizedPnl(symbol).toDouble() << "\n";
Multiple Symbols¶
PositionTracker tracker(1, CostBasisMethod::LIFO);
// Track fills from executor
executor.addExecutionListener(&tracker);
// Query per-symbol
for (SymbolId sym : symbols)
{
std::cout << "Symbol " << sym << ": "
<< tracker.getPosition(sym).toDouble() << " @ "
<< tracker.getAvgEntryPrice(sym).toDouble() << "\n";
}
// Total across all symbols
std::cout << "Total realized PnL: " << tracker.getTotalRealizedPnl().toDouble() << "\n";
Internal Structure¶
Lot-Based Tracking¶
struct Lot
{
Quantity quantity; // Signed: positive=long, negative=short
Price price; // Entry price (fixed-point)
};
struct PositionState
{
std::deque<Lot> lots; // Open lots
Price realizedPnl{}; // Accumulated realized PnL (fixed-point)
Quantity position() const; // Sum of lot quantities
Price avgEntryPrice() const; // VWAP of open lots
};
Position Updates¶
- Opening trade: Add new lot to deque
- Closing trade: Remove lots per cost basis method, calculate realized PnL
- Flipping trade (long to short): Close all, then open opposite
For AVERAGE method, lots are consolidated into single VWAP lot.
Thread Safety¶
All public methods are protected by std::mutex:
- Safe for concurrent access from multiple threads
- Position queries can be called while fills are being processed
- Uses SymbolStateMap for O(1) per-symbol access
Fixed-Point Arithmetic¶
All calculations use Price and Quantity fixed-point types:
- No floating-point precision issues
- Portable across all platforms (no __int128)
- Intermediate calculations use double then convert back
Compliance Notes¶
- FIFO is required by IRS for tax reporting (US)
- LIFO may be preferred for tax optimization (where allowed)
- AVERAGE is common for mutual funds and some jurisdictions
- All methods track exact lot-level PnL for audit trails
Migration Notes¶
getRealizedPnl() and getTotalRealizedPnl() now return Price instead of double:
// Old API
double pnl = tracker.getRealizedPnl(symbol);
// New API
Price pnl = tracker.getRealizedPnl(symbol);
double pnlDouble = pnl.toDouble(); // If double needed
See Also¶
- IPositionManager - Interface definition
- Strategy - Strategy base class
- BacktestRunner - Backtesting framework