Portfolio Analysis
The portfolio command
Analyse trade export files and generate a combined HTML report:
algo portfolio trades/*.json --output my_portfolio.htmlThe report includes summary metrics, per-strategy breakdown, equity curves, a correlation matrix, and detailed trade listings.
Portfolio with a description
algo portfolio trades/*.json --output my_portfolio.html \
--description "Diversified Trend Following"The index command
Generate an index page linking to multiple portfolio reports:
algo index portfolios/*.html --output index.htmlThe index page shows a sortable table with key metrics extracted from each portfolio report.
Portfolio accessor functions
When a strategy runs under the Trade Director, it can access portfolio-level state through methods on RunState. Always check HasPortfolio() before calling other portfolio methods. When running standalone (not through the Trade Director), all portfolio methods return zero values or nil.
Equity and Profit
| Method | Return Type | Description |
|---|---|---|
HasPortfolio() | bool | Whether running under Trade Director |
PortfolioEquity() | float64 | Current equity: initial capital + realised profit + unrealised P&L |
PortfolioNetProfit() | float64 | Total realised profit across all strategies |
PortfolioNetProfitPeak() | float64 | Historical peak of realised profit |
PortfolioOpenPositionProfit() | float64 | Total unrealised P&L (mark-to-market) |
Positions
| Method | Return Type | Description |
|---|---|---|
PortfolioOpenPositions() | []*Order | All open positions across all strategies |
PortfolioOpenPositionCount() | int | Total count of open positions |
PortfolioFills() | []*Order | All fills (executed trades) across all strategies |
Exposure
| Method | Return Type | Description |
|---|---|---|
PortfolioTotalExposure() | float64 | Total notional exposure across all positions |
PortfolioExposureByAssetClass() | map[string]float64 | Exposure by asset class |
PortfolioExposureByCategory() | map[string]float64 | Exposure by category |
PortfolioPositionCountByAssetClass() | map[string]int | Position count by asset class |
PortfolioPositionCountByCategory() | map[string]int | Position count by category |
Asset class keys: "futures", "stock", "crypto", "forex", "option", "unknown"
Category keys: "agriculture", "crypto", "currency", "energy", "index", "interest", "meat", "metal", "soft", "unknown"
Per-Strategy State
| Method | Return Type | Description |
|---|---|---|
PortfolioStrategyIDs() | []string | List of all strategy IDs (sorted) |
PortfolioStrategyInfo(id) | *StrategyInfo | Detailed info for a specific strategy |
The StrategyInfo struct contains:
type StrategyInfo struct {
StrategyID string // Unique identifier (e.g., "goldencross-0")
StrategyName string // Display name (e.g., "Golden Cross")
Symbol string // Trading symbol (e.g., "@ES")
BarInterval int // Bar interval in minutes (0 for daily)
AssetClass string // Asset class (e.g., "futures")
CategoryType string // Category (e.g., "index")
MarketPosition int // Current position: positive=long, negative=short, 0=flat
OpenProfit float64 // Unrealised P&L for this strategy
ClosedProfit float64 // Realised profit for this strategy
OpenPositionCount int // Number of open positions
}Example usage
func (s *MyStrategy) Strategy(rs *engine.RunState) error {
if rs.HasPortfolio() {
// Position sizing based on portfolio equity
equity := rs.PortfolioEquity()
riskPerTrade := equity * 0.02 // 2% risk per trade
// Check sector concentration before entering
exposureByCategory := rs.PortfolioExposureByCategory()
if exposureByCategory["energy"] > equity*0.25 {
return nil // Skip entry if energy exposure exceeds 25%
}
// Reduce size when portfolio is in drawdown
profitPeak := rs.PortfolioNetProfitPeak()
currentProfit := rs.PortfolioNetProfit()
if profitPeak > 0 {
drawdownPct := (profitPeak - currentProfit) / profitPeak
if drawdownPct > 0.10 {
riskPerTrade *= 0.5 // Halve size when drawdown exceeds 10%
}
}
}
// ... rest of strategy logic
return nil
}Important notes
- Snapshot data: Values returned are snapshots at call time. If the portfolio changes after your call, the cached values become stale.
- Map copies: Exposure and position count maps are copies. Modifying them does not affect portfolio state.
- Nil safety: All methods return safe zero values (0, nil, empty maps) when not running under a Trade Director.
- Performance: Methods that return maps create copies on each call. Cache results if calling repeatedly within a single bar.
Max drawdown calculation in portfolio reports
The portfolio report calculates maximum drawdown using two methods:
Path-based intraday drawdown: Uses tick-by-tick price movements during each trade to track the true peak-to-trough drawdown. Most accurate as it captures intraday swings.
Trade-level drawdown: Calculates drawdown from the equity curve built from trade entry/exit points only. May underestimate true maximum drawdown.
| Scenario | Method Used |
|---|---|
| All symbol rows selected, no filters | Path-based (accurate) |
| Any symbol row deselected | Trade-level (fallback) |
| Direction filter applied (Long/Short only) | Trade-level (fallback) |
| Date range zoom applied | Trade-level (fallback) |
The NP/DD ratio shown in the TOTAL row is most accurate when viewing the full unfiltered portfolio.