# Portfolio Management & Position Tracking

Track positions, calculate P&L, monitor account balances, and manage your portfolio programmatically through the eToro API.

---


## Overview

The Portfolio API gives you programmatic access to your trading positions, account balance, and profit/loss data. This guide covers how to fetch portfolio data, track individual positions, calculate returns, and build a portfolio monitoring system.

> **Prerequisite:** You should be comfortable with [API authentication](/learn/authentication-and-api-keys) before working with portfolio endpoints.

## Portfolio API Endpoints

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/trading/info/real/portfolio` | GET | Full portfolio snapshot with all positions |
| `/trading/info/real/pnl` | GET | P&L, credits, and account balances |
| `/trading/info/real/history` | GET | Closed position history |

## Fetching Your Portfolio

Get a complete snapshot of all open positions:

```javascript skip-test
import { randomUUID } from "node:crypto";

const API_BASE = "https://public-api.etoro.com/api/v1";

async function getPortfolio() {
  const response = await fetch(`${API_BASE}/trading/info/real/portfolio`, {
    headers: {
      "x-api-key": process.env.ETORO_API_KEY,
      "x-user-key": process.env.ETORO_USER_KEY,
      "x-request-id": randomUUID(),
    },
  });

  if (!response.ok) {
    throw new Error(`Portfolio fetch failed: ${response.status}`);
  }

  return response.json();
}

const portfolio = await getPortfolio();
console.log(`Open positions: ${portfolio.positions.length}`);
console.log(`Total equity: $${portfolio.equity.toFixed(2)}`);
```

## Position Details

Each position in the portfolio includes key trading data:

```javascript skip-test
import { randomUUID } from "node:crypto";

const API_BASE = "https://public-api.etoro.com/api/v1";

async function getPositions() {
  const response = await fetch(`${API_BASE}/trading/info/real/portfolio`, {
    headers: {
      "x-api-key": process.env.ETORO_API_KEY,
      "x-user-key": process.env.ETORO_USER_KEY,
      "x-request-id": randomUUID(),
    },
  });

  const positions = await response.json();

  return positions.map((p) => ({
    id: p.positionId,
    instrument: p.instrumentName,
    direction: p.isBuy ? "LONG" : "SHORT",
    openPrice: p.openRate,
    currentPrice: p.currentRate,
    amount: p.investedAmount,
    pnl: p.netProfit,
    pnlPercent: ((p.netProfit / p.investedAmount) * 100).toFixed(2),
    stopLoss: p.stopLossRate,
    takeProfit: p.takeProfitRate,
    openDate: new Date(p.openDateTime).toLocaleDateString(),
  }));
}

const positions = await getPositions();
positions.forEach((p) => {
  const emoji = p.pnl >= 0 ? "+" : "";
  console.log(
    `${p.instrument} ${p.direction} | ${emoji}$${p.pnl.toFixed(2)} (${emoji}${p.pnlPercent}%)`
  );
});
```

## Account Balance and Equity

Monitor your account health in real time:

```javascript skip-test
import { randomUUID } from "node:crypto";

const API_BASE = "https://public-api.etoro.com/api/v1";

async function getAccountBalance() {
  const response = await fetch(`${API_BASE}/trading/info/real/pnl`, {
    headers: {
      "x-api-key": process.env.ETORO_API_KEY,
      "x-user-key": process.env.ETORO_USER_KEY,
      "x-request-id": randomUUID(),
    },
  });

  const balance = await response.json();

  return {
    totalBalance: balance.totalBalance,
    availableBalance: balance.availableBalance,
    equity: balance.equity,
    unrealizedPnL: balance.equity - balance.totalBalance,
    marginUsed: balance.totalBalance - balance.availableBalance,
    marginLevel:
      balance.totalBalance > 0
        ? ((balance.equity / balance.totalBalance) * 100).toFixed(1)
        : "N/A",
  };
}

const account = await getAccountBalance();
console.log(`Equity: $${account.equity.toFixed(2)}`);
console.log(`Available: $${account.availableBalance.toFixed(2)}`);
console.log(`Unrealized P&L: $${account.unrealizedPnL.toFixed(2)}`);
```

## Calculating Portfolio Returns

Build a function that aggregates position data into portfolio-level metrics:

```javascript skip-test
function calculatePortfolioMetrics(positions) {
  const totalInvested = positions.reduce(
    (sum, p) => sum + p.amount,
    0
  );
  const totalPnL = positions.reduce((sum, p) => sum + p.pnl, 0);
  const winners = positions.filter((p) => p.pnl > 0);
  const losers = positions.filter((p) => p.pnl < 0);

  return {
    totalPositions: positions.length,
    totalInvested: totalInvested.toFixed(2),
    totalPnL: totalPnL.toFixed(2),
    returnPercent: ((totalPnL / totalInvested) * 100).toFixed(2),
    winRate: ((winners.length / positions.length) * 100).toFixed(1),
    winners: winners.length,
    losers: losers.length,
    bestPosition: positions.reduce(
      (best, p) => (p.pnl > (best?.pnl || -Infinity) ? p : best),
      null
    ),
    worstPosition: positions.reduce(
      (worst, p) => (p.pnl < (worst?.pnl || Infinity) ? p : worst),
      null
    ),
  };
}
```

## Trade History

Fetch closed positions to analyze historical performance:

```javascript skip-test
import { randomUUID } from "node:crypto";

const API_BASE = "https://public-api.etoro.com/api/v1";

async function getTradeHistory(options = {}) {
  const params = new URLSearchParams({
    limit: options.limit || 50,
    ...(options.startDate && { startDate: options.startDate }),
    ...(options.endDate && { endDate: options.endDate }),
  });

  const response = await fetch(
    `${API_BASE}/trading/info/real/history?${params}`,
    {
      headers: {
        "x-api-key": process.env.ETORO_API_KEY,
        "x-user-key": process.env.ETORO_USER_KEY,
        "x-request-id": randomUUID(),
      },
    }
  );

  const history = await response.json();

  return history.map((trade) => ({
    instrument: trade.instrumentName,
    direction: trade.isBuy ? "LONG" : "SHORT",
    openDate: trade.openDateTime,
    closeDate: trade.closeDateTime,
    openPrice: trade.openRate,
    closePrice: trade.closeRate,
    invested: trade.investedAmount,
    pnl: trade.netProfit,
    holdingDays: Math.ceil(
      (new Date(trade.closeDateTime) - new Date(trade.openDateTime)) /
        (1000 * 60 * 60 * 24)
    ),
  }));
}

// Get last 30 days of trades
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
  .toISOString()
  .split("T")[0];

const history = await getTradeHistory({ startDate: thirtyDaysAgo });
console.log(`Closed ${history.length} trades in the last 30 days`);
```

## Building a Portfolio Monitor

Combine the pieces into a real-time portfolio monitoring function:

```javascript skip-test
async function monitorPortfolio(intervalMs = 60000) {
  console.log("Starting portfolio monitor...\n");

  async function check() {
    const [positions, balance] = await Promise.all([
      getPositions(),
      getAccountBalance(),
    ]);

    const metrics = calculatePortfolioMetrics(positions);

    console.clear();
    console.log("=== Portfolio Monitor ===");
    console.log(`Time: ${new Date().toLocaleTimeString()}`);
    console.log(`Equity: $${balance.equity.toFixed(2)}`);
    console.log(`Available: $${balance.availableBalance.toFixed(2)}`);
    console.log(`Positions: ${metrics.totalPositions}`);
    console.log(`P&L: $${metrics.totalPnL} (${metrics.returnPercent}%)`);
    console.log(`Win rate: ${metrics.winRate}%`);
    console.log();

    // Alert on significant drawdown
    if (parseFloat(metrics.returnPercent) < -5) {
      console.warn("WARNING: Portfolio drawdown exceeds 5%");
    }

    // Log position details
    positions.forEach((p) => {
      const sign = p.pnl >= 0 ? "+" : "";
      console.log(
        `  ${p.instrument.padEnd(12)} ${p.direction.padEnd(6)} ${sign}$${p.pnl.toFixed(2).padStart(10)}`
      );
    });
  }

  await check();
  setInterval(check, intervalMs);
}

// Monitor every 60 seconds
monitorPortfolio(60000);
```

## Demo vs Real Portfolio

The same code works for both demo and real accounts — the only difference is the endpoint path:

| Environment | Portfolio Endpoint |
|-------------|-------------------|
| Demo | `/trading/info/demo/portfolio` |
| Real | `/trading/info/real/portfolio` |

```javascript skip-test
function getExecutionPrefix(environment = "demo") {
  return environment === "demo"
    ? "trading/execution/demo"
    : "trading/execution";
}

const API_BASE = "https://public-api.etoro.com/api/v1";
const prefix = getExecutionPrefix(process.env.ETORO_ENVIRONMENT);
const portfolioUrl = `${API_BASE}/${prefix}/portfolio`;
```

> **Tip:** Always develop and test with demo portfolios first. Switch to real only after thorough testing.

## Best Practices

1. **Poll wisely** — Don't fetch portfolio data more than once per minute. Position data doesn't change that frequently unless you're actively trading.
2. **Use WebSocket for real-time** — For live price updates on open positions, connect to the [WebSocket API](/learn/real-time-market-data-websocket) instead of polling REST.
3. **Handle empty portfolios** — New accounts or accounts with no open positions return empty arrays. Handle this gracefully.
4. **Store historical snapshots** — Record portfolio snapshots periodically for performance tracking and debugging.
5. **Mind your rate limit** — All requests count toward your rate limit. See the [Rate Limits documentation](https://api-portal.etoro.com/getting-started/rate-limits) for details.

## Next Steps

- [Building an Algo Trading Bot](/learn/building-an-algo-trading-bot) — Automate your trading strategy
- [Authentication Deep Dive](/learn/authentication-and-api-keys) — Secure your API credentials
- [Real-Time Market Data](/learn/real-time-market-data-websocket) — Stream live prices
- [API Reference](https://api-portal.etoro.com/api-reference) — Full endpoint documentation
