Principle:Nautechsystems Nautilus trader Portfolio Analysis
| Field | Value |
|---|---|
| sources | https://github.com/nautechsystems/nautilus_trader , https://nautilustrader.io/docs/ |
| domains | performance analysis, portfolio management, risk assessment |
| last_updated | 2026-02-10 12:00 GMT |
Overview
Portfolio Analysis is the principle of computing post-trade performance metrics from account state and position history to evaluate the quality and risk characteristics of a trading strategy.
Description
After a backtest (or live trading session) completes, the raw results -- account balances, closed positions, realized PnL per trade, and per-period returns -- must be distilled into interpretable statistics. Portfolio analysis provides a structured framework for this distillation.
The principle addresses the following concerns:
- Realized PnL aggregation -- Each closed position has a realized PnL in its settlement currency. The analyzer aggregates these into per-currency PnL series, enabling drill-down by currency for multi-currency portfolios.
- Return series construction -- For each closed position, the realized return (percentage) is recorded at the position's close timestamp. The resulting time series supports return-based statistics such as Sharpe ratio, Sortino ratio, and maximum drawdown.
- Total PnL computation -- The difference between ending and starting account balances, optionally including unrealized PnL, gives the total profit or loss. Percentage changes are computed relative to the starting balance.
- Pluggable statistics -- The analyzer supports a registry of
PortfolioStatisticplugins. Each statistic implements one or more ofcalculate_from_realized_pnls,calculate_from_returns, andcalculate_from_positions. This design allows users to add custom metrics (e.g., Calmar ratio, win rate, profit factor) without modifying the analyzer itself. - Multi-currency support -- Portfolios may trade instruments denominated in different currencies. The analyzer tracks balances and PnLs per currency, requiring the caller to specify a currency when querying multi-currency results.
- Formatted output -- Statistics can be returned as raw dictionaries (for programmatic use) or as formatted string lists (for human-readable log output), with automatic padding for aligned display.
Usage
Apply this principle whenever you need to:
- Evaluate the profitability and risk profile of a completed backtest.
- Compare multiple strategy runs on standardized metrics.
- Generate performance reports for stakeholders.
- Implement custom performance metrics via the statistic plugin interface.
- Feed performance data into optimization or selection pipelines.
Theoretical Basis
Portfolio analysis rests on the decomposition of trading outcomes into PnL-based and return-based metrics, each supporting a different class of performance statistics.
Key theoretical elements:
- PnL series -- A series indexed by position ID, where each value is the realized PnL in the settlement currency. From this series, statistics compute: total PnL, average win/loss, win rate, profit factor, expectancy, and maximum consecutive wins/losses.
- Return series -- A time-indexed series of realized returns (as fractions). At each timestamp, multiple position closures are summed. This series supports: Sharpe ratio, Sortino ratio, maximum drawdown, Calmar ratio, annualized return, and volatility.
- Position-based statistics -- Some metrics (e.g., average holding period, percentage of long vs. short positions) require access to the full
Positionobjects rather than just PnL or return numbers. - Statistic plugin protocol -- Each
PortfolioStatisticmust implement at least one of three methods:calculate_from_realized_pnls(pnls)-- receives PnL values (list or Series).calculate_from_returns(returns)-- receives return values (dict or Series).calculate_from_positions(positions)-- receives position objects.
- Starting balance anchoring -- The total PnL percentage is computed as
(ending_balance - starting_balance) / starting_balance * 100. The starting balance is captured at the beginning of the analysis, protecting against mid-run balance adjustments.
Pseudocode:
FUNCTION calculate_statistics(analyzer, account, positions):
starting_balances = account.starting_balances()
current_balances = account.balances_total()
RESET internal PnL and return series
FOR EACH position in positions:
IF position.realized_pnl is not None:
ADD trade(position.id, position.realized_pnl)
IF position is closed:
ADD return(position.close_time, position.realized_return)
SORT return series by timestamp
-- Statistics are computed lazily when queried:
-- get_performance_stats_pnls() iterates registered statistics with PnL data
-- get_performance_stats_returns() iterates registered statistics with return data
-- get_performance_stats_general() iterates registered statistics with position data