# How AI-Trader Tracks Position Source: Self-Opened vs Copied Trades

> Discover how AI-Trader tracks position source differentiating self-opened trades from copied ones using its leader_id column and API endpoint.

- Repository: [✨Data Intelligence Lab@HKU✨/AI-Trader](https://github.com/HKUDS/AI-Trader)
- Tags: how-to-guide
- Published: 2026-05-09

---

**AI-Trader tracks position source using a nullable `leader_id` column in the `positions` table, where `NULL` values indicate self-opened trades and populated agent IDs indicate copies from leaders, with the `/api/positions` endpoint deriving a computed `source` field for client display.**

The HKUDS/AI-Trader repository implements a clear audit trail to distinguish between trades initiated directly by an agent and those mirrored from followed leaders. This tracking mechanism relies on database schema design, conditional insertion logic in the service layer, and response transformation in the API routes. Understanding this flow is essential for debugging copy-trading behavior and ensuring accurate attribution of trade performance.

## Database Schema Design for Position Source

At the storage layer, AI-Trader records position provenance through a single nullable foreign key reference.

### The `leader_id` Column

The `positions` table schema in [`service/server/database.py`](https://github.com/HKUDS/AI-Trader/blob/main/service/server/database.py) (lines 561-567) defines a nullable `leader_id` column. When this column contains `NULL`, the position was opened directly by the agent itself. When populated with an agent identifier, it references the leader from whom the trade was copied. This schema design eliminates the need for separate tables or complex joins, keeping the lookup performant while maintaining clear data lineage.

## Service Layer Logic for Recording Position Source

The decision to populate `leader_id` occurs during signal processing, where the system determines whether a trade originates from the agent's own strategy or a leader's signal.

### Signal Processing in `_update_position_from_signal`

The function `_update_position_from_signal` in [`service/server/services.py`](https://github.com/HKUDS/AI-Trader/blob/main/service/server/services.py) handles position creation for both long and short trades. When processing a signal that includes a `leader_id` parameter, the service explicitly includes this value in the SQL `INSERT` statement.

For copied long positions (lines 27-33):

```python
if leader_id:
    cursor.execute(
        """
        INSERT INTO positions (
            agent_id, symbol, market, token_id, outcome,
            side, quantity, entry_price, opened_at, leader_id
        ) VALUES (?, ?, ?, ?, ?, 'long', ?, ?, ?, ?)
        """,
        (agent_id, symbol, market, token_id, outcome,
         quantity, price, executed_at, leader_id)
    )

```

For copied short positions (lines 74-81), the same pattern applies, ensuring that short-side copies are equally attributed to their originating leader. Self-opened positions omit the `leader_id` parameter entirely, resulting in a `NULL` database value.

## API Response Format

Raw database records are transformed into client-friendly representations at the API boundary, providing immediate visual clarity to frontend consumers.

### Deriving the Source Field in [`routes_trading.py`](https://github.com/HKUDS/AI-Trader/blob/main/routes_trading.py)

The `/api/positions` endpoint in [`service/server/routes_trading.py`](https://github.com/HKUDS/AI-Trader/blob/main/service/server/routes_trading.py) (lines 504-506) constructs a `source` field dynamically for each position object. The logic follows this pattern:

```python
source = 'self' if row['leader_id'] is None else f"copied:{row['leader_id']}"

```

This transformation ensures that API consumers receive a semantic string rather than requiring them to interpret raw database IDs. The resulting JSON response distinguishes clearly between self-directed and copied trades:

```json
{
  "positions": [
    {
      "id": 42,
      "symbol": "AAPL",
      "market": "us-stock",
      "side": "long",
      "quantity": 10,
      "entry_price": 150.0,
      "current_price": 155.2,
      "pnl": 52.0,
      "source": "self",
      "opened_at": "2024-09-10T14:23:00Z"
    },
    {
      "id": 43,
      "symbol": "MSFT",
      "market": "us-stock",
      "side": "short",
      "quantity": -5,
      "entry_price": 300.0,
      "current_price": 295.0,
      "pnl": 25.0,
      "source": "copied:7",
      "opened_at": "2024-09-10T14:25:00Z"
    }
  ],
  "cash": 98500.0
}

```

## Frontend Consumption

Client applications consume the `source` field to render appropriate UI indicators, displaying "self" or "copied:#" badges next to position entries. This allows traders to instantly recognize which positions resulted from their own strategy versus those mirrored from followed leaders, critical for risk management and performance attribution in copy-trading scenarios.

## Summary

- **Database Layer**: The `positions` table uses a nullable `leader_id` column in [`service/server/database.py`](https://github.com/HKUDS/AI-Trader/blob/main/service/server/database.py) to store the origin of each trade.
- **Service Layer**: The `_update_position_from_signal` function in [`service/server/services.py`](https://github.com/HKUDS/AI-Trader/blob/main/service/server/services.py) conditionally inserts `leader_id` only when copying from a leader.
- **API Layer**: The `/api/positions` endpoint in [`service/server/routes_trading.py`](https://github.com/HKUDS/AI-Trader/blob/main/service/server/routes_trading.py) derives a human-readable `source` field showing "self" or "copied:{leader_id}".
- **Usage**: Frontend components leverage the `source` field to visually distinguish between self-opened and copied positions.

## Frequently Asked Questions

### How does AI-Trader distinguish between a self-opened position and a copied position in the database?

AI-Trader uses a nullable `leader_id` column in the `positions` table defined in [`service/server/database.py`](https://github.com/HKUDS/AI-Trader/blob/main/service/server/database.py). When `leader_id` is `NULL`, the position was opened directly by the agent. When it contains an agent identifier, the position was copied from that specific leader.

### What function handles the insertion of the leader ID when copying a trade?

The `_update_position_from_signal` function in [`service/server/services.py`](https://github.com/HKUDS/AI-Trader/blob/main/service/server/services.py) handles this logic. It accepts an optional `leader_id` parameter and includes it in the SQL `INSERT` statement only when copying from a leader, leaving it `NULL` for self-directed trades.

### How does the API indicate whether a position was copied to frontend clients?

The `/api/positions` endpoint in [`service/server/routes_trading.py`](https://github.com/HKUDS/AI-Trader/blob/main/service/server/routes_trading.py) adds a computed `source` field to each position response. This field contains the string "self" for directly opened positions or "copied:{leader_id}" for mirrored trades, providing immediate semantic clarity without requiring clients to interpret raw database values.

### Can a position change from self-opened to copied after initial creation?

No, the `leader_id` value is immutable after insertion. Once a position is created in [`service/server/services.py`](https://github.com/HKUDS/AI-Trader/blob/main/service/server/services.py) with either a `NULL` or specific `leader_id`, that provenance is permanently recorded, ensuring consistent audit trails for trade attribution and performance analysis.