A step-by-step guide to building a simple trading bot using the eToro Demo Trading API.
Automated trading on a demo account is the safest way to learn how orders, risk, and execution behave before you put real capital at risk. This tutorial walks you through a minimal Node.js bot that authenticates against the eToro Demo Trading API, polls instrument rates, applies a simple moving average crossover rule, places market orders when the signal flips, and periodically reconciles open positions.
You will need an API key with demo trading enabled, and a user key that identifies the demo portfolio. Store both in environment variables and never commit them to source control.
Create a new folder, run npm init -y, and add a single dependency if you want structured logging (pino) or use console.log for simplicity. Your .env file should define ETORO_API_KEY, ETORO_USER_KEY, and optionally ETORO_INSTRUMENT_ID for the instrument you want to trade in demo mode.
Every HTTP call in this tutorial uses the public API base URL https://public-api.etoro.com/api/v1/. Each request should send a unique x-request-id for traceability (UUIDs work well), your x-api-key, and when the endpoint acts on behalf of a user, the x-user-key header.
Before placing trades, confirm that your credentials are accepted by calling a lightweight session or account endpoint. The pattern below is reusable for any authenticated GET.
import { randomUUID } from "node:crypto";
const BASE = "https://public-api.etoro.com/api/v1";
function headers() {
return {
"x-api-key": process.env.ETORO_API_KEY,
"x-user-key": process.env.ETORO_USER_KEY,
"x-request-id": randomUUID(),
"content-type": "application/json",
};
}
async function verifyDemoSession() {
const res = await fetch(`${BASE}/trading/info/demo/portfolio`, { headers: headers() });
if (!res.ok) throw new Error(`Session check failed: ${res.status}`);
return res.json();
}
If this call returns account metadata (currency, buying power, open P/L), you are ready to pull market data and send orders.
For a moving average crossover you need a rolling window of mid prices or last trade prices. Poll an instrument rates endpoint at a sensible interval (for example every 15–60 seconds on demo), append each sample to an in-memory array, and trim the array to the longest period you need (for instance 50 closes for a 50-period slow average).
const closes = [];
const FAST = 10;
const SLOW = 30;
function sma(period) {
if (closes.length < period) return null;
const slice = closes.slice(-period);
return slice.reduce((a, b) => a + b, 0) / period;
}
async function fetchLastClose(instrumentId) {
const res = await fetch(
`${BASE}/market-data/rates?instrumentIds=${instrumentId}`,
{ headers: headers() }
);
if (!res.ok) throw new Error(`Rates failed: ${res.status}`);
const body = await res.json();
const last = body.data?.candles?.at(-1);
return last?.close ?? last?.mid;
}
In production you might replace polling with WebSocket candles; for a first bot, polling keeps the control flow linear and easier to debug.
When the fast SMA crosses above the slow SMA, treat it as a buy signal; when it crosses below, treat it as a sell or close long signal depending on your demo rules. Keep position sizing conservative: pass a fixed notional or a fraction of buying power, and always send an idempotency-friendly client reference in the body if the API supports it.
import { randomUUID } from "node:crypto";
let lastSignal = "flat";
function crossoverSignal() {
const fast = sma(FAST);
const slow = sma(SLOW);
if (fast == null || slow == null) return "hold";
if (lastSignal !== "long" && fast > slow) return "buy";
if (lastSignal === "long" && fast < slow) return "sell";
return "hold";
}
async function placeMarketOrder({ instrumentId, side, amount }) {
const res = await fetch(
`${BASE}/trading/execution/demo/market-open-orders/by-amount`,
{
method: "POST",
headers: headers(),
body: JSON.stringify({
InstrumentID: instrumentId,
IsBuy: side === "BUY",
Amount: amount,
}),
}
);
if (!res.ok) {
const errText = await res.text();
throw new Error(`Order failed ${res.status}: ${errText}`);
}
return res.json();
}
After each order, poll open positions to confirm fills, average price, and unrealized P/L. Use the same header helper so support can correlate logs with x-request-id.
async function listOpenPositions() {
const res = await fetch(`${BASE}/trading/info/demo/portfolio`, {
headers: headers(),
});
if (!res.ok) throw new Error(`Positions failed: ${res.status}`);
return res.json();
}
async function tick(instrumentId) {
const price = await fetchLastClose(instrumentId);
if (typeof price === "number") closes.push(price);
const signal = crossoverSignal();
if (signal === "buy") {
await placeMarketOrder({ instrumentId, side: "BUY", amount: 100 });
lastSignal = "long";
} else if (signal === "sell" && lastSignal === "long") {
await placeMarketOrder({ instrumentId, side: "SELL", amount: 100 });
lastSignal = "flat";
}
const book = await listOpenPositions();
console.log("signal=%s positions=%j", signal, book);
}
Rate-limit your polling loop, handle HTTP 429 with exponential backoff, and log the request id whenever you escalate a support ticket. Demo trading mirrors many production constraints but not slippage or liquidity perfectly—treat results as educational, not as a guarantee of live performance.
From here you can add stop-loss and take-profit orders, multi-instrument portfolios, or swap polling for streaming data. The same authentication and header discipline applies across those upgrades.
Was this helpful?
How to programmatically search, filter, and explore eToro's instrument catalog — asset classes, exchanges, industries, and historical data.
How to use the eToro MCP server with Cursor, Claude, and other AI code editors to build trading apps without writing API calls by hand.
Track positions, calculate P&L, monitor account balances, and manage your portfolio programmatically through the eToro API.
Build a fully functional algorithmic trading bot using the eToro API with position management, risk controls, and automated strategy execution.
Be the first to know when we publish new API guides, changelog entries, and developer resources.
Newsletter coming soon. We'll only email you when it launches.