# How the Portfolio Manager Aggregates Analyst Signals in AI Hedge Fund

> Learn how the AI hedge fund Portfolio Manager aggregates analyst signals by leveraging agent state and LLM for decisive trading strategies. Discover the process now.

- Repository: [Virat Singh/ai-hedge-fund](https://github.com/virattt/ai-hedge-fund)
- Tags: deep-dive
- Published: 2026-03-09

---

**The Portfolio Manager aggregates analyst signals by collecting raw signal-confidence pairs from the shared agent state and compressing them into a compact per-ticker map before passing them to the LLM for deterministic trading decisions.**

In the `virattt/ai-hedge-fund` repository, the Portfolio Manager serves as the central orchestrator that transforms dispersed analyst outputs into actionable trading instructions. Understanding how the Portfolio Manager aggregates analyst signals reveals the deterministic pipeline that bridges quantitative analysis with LLM-based decision making. The entire workflow operates within [`src/agents/portfolio_manager.py`](https://github.com/virattt/ai-hedge-fund/blob/main/src/agents/portfolio_manager.py), where raw signals from valuation, technical, and sentiment analysts are normalized into a structured payload.

## Signal Aggregation Pipeline

The aggregation process follows a strict two-phase architecture designed to minimize token consumption while preserving signal fidelity.

### Step 1: Collecting Raw Signals from Agent State

The Portfolio Manager begins by accessing the shared state object that persists across the agent workflow. Every specialized analyst—including valuation, technicals, and sentiment agents—writes its conclusions to `state["data"]["analyst_signals"]` as a dictionary mapping agent IDs to per-ticker payloads.

At the start of the `portfolio_management_agent` function, the manager extracts three critical elements from the state:

```python
portfolio = state["data"]["portfolio"]
analyst_signals = state["data"]["analyst_signals"]
tickers = state["data"]["tickers"]

```

*(source: [`src/agents/portfolio_manager.py`](https://github.com/virattt/ai-hedge-fund/blob/main/src/agents/portfolio_manager.py), lines 28‑31)*

Each entry in `analyst_signals` contains at minimum a `signal` field (e.g., `"bullish"`, `"bearish"`, or `"neutral"`) and a `confidence` score representing the analyst's certainty.

### Step 2: Compressing Signals by Ticker

The core aggregation logic iterates over every ticker and compacts the scattered analyst outputs into a hierarchical map. The manager explicitly filters out risk-management agents to maintain separation between analytical signals and risk constraints.

The implementation in [`portfolio_manager.py`](https://github.com/virattt/ai-hedge-fund/blob/main/portfolio_manager.py) (lines 58‑64) constructs the final structure:

```python
signals_by_ticker = {}
for ticker in tickers:
    ticker_signals = {}
    for agent, signals in analyst_signals.items():
        # Exclude risk-management agents from signal aggregation

        if not agent.startswith("risk_management_agent") and ticker in signals:
            sig = signals[ticker].get("signal")
            conf = signals[ticker].get("confidence")
            if sig is not None and conf is not None:
                ticker_signals[agent] = {"sig": sig, "conf": conf}
    signals_by_ticker[ticker] = ticker_signals

```

This produces a JSON-serializable structure like:

```json
{
  "AAPL": {
    "valuation": {"sig": "bullish", "conf": 85},
    "technicals": {"sig": "neutral", "conf": 60},
    "sentiment": {"sig": "bearish", "conf": 70}
  },
  "TSLA": {
    "valuation": {"sig": "bullish", "conf": 72},
    "fundamentals": {"sig": "bullish", "conf": 65}
  }
}

```

## Risk Management Data Separation

The aggregation pipeline maintains strict isolation between analytical signals and risk management constraints. While risk data resides in the same `analyst_signals` dictionary under keys prefixed with `risk_management_agent`, the Portfolio Manager retrieves this data separately for position sizing and limit checks.

According to the source code at lines 46‑48, risk-specific data—including current prices and position limits—is accessed via dedicated keys without being mixed into the analyst signal map intended for the LLM.

## LLM Payload Construction

After aggregation, the Portfolio Manager further optimizes the payload through the `_compact_signals` helper function (defined at lines 60‑74). This utility trims empty agent entries and normalizes the data structure to reduce token usage.

The final payload is constructed within `generate_trading_decision` (lines 84‑88 and 107‑112):

```python
compact_signals = _compact_signals({t: signals_by_ticker.get(t, {}) for t in tickers_for_llm})

prompt_data = {
    "signals": json.dumps(compact_signals, separators=(",", ":"), ensure_ascii=False),
    "allowed": json.dumps(compact_allowed, separators=(",", ":"), ensure_ascii=False),
}
prompt = template.invoke(prompt_data)

```

This compact serialization removes whitespace and unnecessary metadata, ensuring the LLM receives only the essential signal-confidence pairs required for trading decisions.

## Summary

- **The Portfolio Manager reads from `state["data"]["analyst_signals"]`** to access outputs from all analyst agents including valuation, technicals, and sentiment modules.
- **Aggregation excludes risk-management agents** by filtering keys starting with `risk_management_agent`, keeping risk data separate from analytical signals.
- **The `_compact_signals` helper and JSON minification** dramatically reduce token usage when transmitting data to the LLM via `generate_trading_decision`.
- **The final structure maps tickers to agents**, with each agent containing `sig` (signal direction) and `conf` (confidence score) fields.
- **Source files involved**: [`src/agents/portfolio_manager.py`](https://github.com/virattt/ai-hedge-fund/blob/main/src/agents/portfolio_manager.py) (main aggregation), [`src/agents/risk_manager.py`](https://github.com/virattt/ai-hedge-fund/blob/main/src/agents/risk_manager.py) (excluded risk data), and various analyst agents that populate the shared state.

## Frequently Asked Questions

### What data structure stores analyst signals in the AI Hedge Fund?

Analyst signals are stored in `state["data"]["analyst_signals"]`, a nested dictionary where top-level keys represent agent IDs (e.g., `"valuation"`, `"technicals"`) and values contain per-ticker objects with `signal` and `confidence` fields. The Portfolio Manager accesses this structure at lines 28‑31 of [`src/agents/portfolio_manager.py`](https://github.com/virattt/ai-hedge-fund/blob/main/src/agents/portfolio_manager.py).

### Why does the Portfolio Manager exclude risk management signals from aggregation?

Risk management signals are excluded to maintain a clean separation between analytical opinions and constraint-based risk data. The Portfolio Manager specifically checks `if not agent.startswith("risk_management_agent")` during aggregation (lines 58‑64) because risk data—such as position limits and current prices—is handled separately for pre-trade validation rather than LLM decision-making.

### How does the Portfolio Manager reduce token usage when sending data to the LLM?

The manager employs two optimization strategies: first, the `_compact_signals` helper function (lines 60‑74) removes empty agent entries and normalizes the structure; second, the `generate_trading_decision` function uses `json.dumps` with `separators=(",", ":")` to eliminate whitespace, creating a minified payload that preserves all signal-confidence data while minimizing token consumption.

### Which source file contains the main aggregation logic for trading decisions?

The primary aggregation logic resides in [`src/agents/portfolio_manager.py`](https://github.com/virattt/ai-hedge-fund/blob/main/src/agents/portfolio_manager.py), specifically within the `portfolio_management_agent` function (lines 28‑112). This file handles state retrieval, signal compression, risk data separation, and the final LLM prompt construction through `generate_trading_decision`.