Track positions, calculate P&L, monitor account balances, and manage your portfolio programmatically through the eToro API.
Part of: Master Portfolio Management — Step 3 of 3
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 before working with portfolio 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 |
Get a complete snapshot of all open positions:
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,
},
});
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)}`);
Each position in the portfolio includes key trading data:
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,
},
});
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}%)`
);
});
Monitor your account health in real time:
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,
},
});
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)}`);
Build a function that aggregates position data into portfolio-level metrics:
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
),
};
}
Fetch closed positions to analyze historical performance:
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,
},
}
);
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`);
Combine the pieces into a real-time portfolio monitoring function:
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);
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 |
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.
Was this helpful?
A step-by-step guide to building a simple trading bot using the eToro Demo Trading API.
A practical guide for transitioning your trading bot from eToro's demo sandbox to the real trading API — safety checks, key differences, and best practices.