Portfolio Analysis

Portfolio Analysis

The portfolio command

Analyse trade export files and generate a combined HTML report:

algo portfolio trades/*.json --output my_portfolio.html

The 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.html

The 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

MethodReturn TypeDescription
HasPortfolio()boolWhether running under Trade Director
PortfolioEquity()float64Current equity: initial capital + realised profit + unrealised P&L
PortfolioNetProfit()float64Total realised profit across all strategies
PortfolioNetProfitPeak()float64Historical peak of realised profit
PortfolioOpenPositionProfit()float64Total unrealised P&L (mark-to-market)

Positions

MethodReturn TypeDescription
PortfolioOpenPositions()[]*OrderAll open positions across all strategies
PortfolioOpenPositionCount()intTotal count of open positions
PortfolioFills()[]*OrderAll fills (executed trades) across all strategies

Exposure

MethodReturn TypeDescription
PortfolioTotalExposure()float64Total notional exposure across all positions
PortfolioExposureByAssetClass()map[string]float64Exposure by asset class
PortfolioExposureByCategory()map[string]float64Exposure by category
PortfolioPositionCountByAssetClass()map[string]intPosition count by asset class
PortfolioPositionCountByCategory()map[string]intPosition 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

MethodReturn TypeDescription
PortfolioStrategyIDs()[]stringList of all strategy IDs (sorted)
PortfolioStrategyInfo(id)*StrategyInfoDetailed 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

  1. Snapshot data: Values returned are snapshots at call time. If the portfolio changes after your call, the cached values become stale.
  2. Map copies: Exposure and position count maps are copies. Modifying them does not affect portfolio state.
  3. Nil safety: All methods return safe zero values (0, nil, empty maps) when not running under a Trade Director.
  4. 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:

  1. 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.

  2. Trade-level drawdown: Calculates drawdown from the equity curve built from trade entry/exit points only. May underestimate true maximum drawdown.

ScenarioMethod Used
All symbol rows selected, no filtersPath-based (accurate)
Any symbol row deselectedTrade-level (fallback)
Direction filter applied (Long/Short only)Trade-level (fallback)
Date range zoom appliedTrade-level (fallback)

The NP/DD ratio shown in the TOTAL row is most accurate when viewing the full unfiltered portfolio.