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

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 (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 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):

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

The /api/positions endpoint in service/server/routes_trading.py (lines 504-506) constructs a source field dynamically for each position object. The logic follows this pattern:

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:

{
  "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 to store the origin of each trade.
  • Service Layer: The _update_position_from_signal function in 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 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. 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 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 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 with either a NULL or specific leader_id, that provenance is permanently recorded, ensuring consistent audit trails for trade attribution and performance analysis.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →