Architecture Overview
Relevant source files
The following files were used as context for generating this wiki page:
The universal-portfolios library is a Python-based framework designed for Online Portfolio Selection (OLPS). Its primary architecture focuses on providing unified tools for implementing, running, and analyzing algorithms that choose portfolio weights in every period to maximize final wealth. The system is built around a clear separation between the strategy logic (Algorithms) and the performance evaluation (Results).
The core architecture consists of three main pillars: the base Algo class which defines the execution lifecycle, a suite of specific strategy implementations (e.g., UP, MPT, CRP), and the AlgoResult class which handles metrics, visualization, and backtesting analysis. This modularity allows researchers to implement new strategies by simply subclassing the base algorithm while inheriting all analysis capabilities.
Sources: README.md:14-22, universal/algo.py:12-25
Core Components and Class Hierarchy
The project follows an object-oriented design where all portfolio selection strategies inherit from a central base class. This ensures a consistent interface for the execution engine.
The Algorithm Base Class (Algo)
The Algo class serves as the foundation for all strategies. it manages the iteration over price data, conversion of price formats (ratio, log, or raw), and the maintenance of portfolio history. It provides two primary modes of operation: sequential updates via the step method (simulating real-time trading) or batch calculation via the weights method.
The Result System (AlgoResult)
Once an algorithm is executed, it returns an AlgoResult object. This component is responsible for calculating financial metrics such as Sharpe ratio, turnover, drawdowns, and profit factors. It also manages transaction costs (fees) and risk-free rate adjustments post-execution.
The diagram shows the inheritance structure and the relationship between the algorithm execution and the resulting data object.
Sources: universal/algo.py:12-45, universal/result.py:31-60, universal/algos/mpt.py:20-50, universal/algos/up.py:11-20
Execution Data Flow
The data flow within the library starts with raw price data (typically a Pandas DataFrame) and moves through a transformation layer before being processed by the algorithm.
Execution Lifecycle
- Price Conversion: Raw prices are converted based on the algorithm's
PRICE_TYPErequirement (e.g., price relatives $p_t / p_{t-1}$). - Initialization:
init_weightsandinit_stepare called to prepare stateful variables. - Iterative Processing: The
runmethod iterates through the time series. For each step, thestepmethod calculates the new portfolio weights based on the current price relativexand the previous weightslast_b. - Result Generation: The final weight matrix and price data are wrapped in an
AlgoResultobject.
This flowchart illustrates the internal logic of the Algo.run method and how it processes time-series data.
Sources: universal/algo.py:84-135, universal/tools.py:72-85
Key Features and Metrics
The architecture supports a wide array of financial analysis tools built directly into the result objects.
| Component | Description | Primary File |
|---|---|---|
| Price Converters | Converts between "ratio", "log", and "raw" prices to normalize inputs for different mathematical models. | universal/algo.py |
| Metric Engine | Calculates Sharpe Ratio, Ulcer Index, Information Ratio, and Alpha/Beta. | universal/result.py |
| Toolbox | Provides utility functions for simplex projection, random portfolio generation, and optimization. | universal/tools.py |
| Backtester | Simulates transaction fees and rebalancing effects on equity. | universal/result.py |
Transaction Costs and Rebalancing
The system calculates fees based on the turnover between periods. The _to_rebalance method identifies the difference between the weights a strategy wants to hold and the weights it actually holds after price changes in the previous period.
def to_rebalance(B, X):
# equity increase
E = (B * (X - 1)).sum(axis=1) + 1
# calculate new value of assets and normalize by new equity
hold_B = (B * X).div(E, axis=0)
return B - hold_B.shift(1)
Sources: universal/tools.py:794-805, universal/result.py:101-118
Performance Optimization
The architecture includes several features to handle large datasets and complex optimizations:
- Parallel Execution: The
runmethod supportsn_jobsto parallelize weight calculations across multiple cores, provided the algorithm'sstepdoes not depend on the previous step's weight in a way that prevents chunking. - Vectorization: While the
stepmethod is sequential, the system encourages using theweightsmethod for batch matrix operations where possible. - Estimation Wrappers: For Modern Portfolio Theory (MPT), the system uses specific estimators (e.g.,
Ledoit-Wolf,EmpiricalCovariance) to handle high-dimensional covariance matrices.
Sources: universal/algo.py:151-177, universal/algos/mpt.py:53-85, universal/tools.py:27-35
Conclusion
The universal-portfolios architecture is designed for extensibility and rigor. By centralizing the execution logic in Algo and the metric logic in AlgoResult, it provides a standardized environment for testing diverse Online Portfolio Selection strategies—ranging from classical Modern Portfolio Theory to heuristic anti-correlation methods—ensuring that results are comparable and accurate through unified data handling and fee simulation.
Sources: README.md:25-35, universal/algo.py:12-15