Build a fully functional algorithmic trading bot using the eToro API with position management, risk controls, and automated strategy execution.
Finished: Build a Trading Bot — all 4 steps
This guide walks through building an algorithmic trading bot that connects to the eToro API, implements a simple moving average crossover strategy, and manages positions with proper risk controls. We'll start in the demo environment before going live.
Always test thoroughly with the Demo Trading API before using real funds. Algorithmic trading carries significant risk.
Our bot consists of four main components:
mkdir etoro-trading-bot && cd etoro-trading-bot
npm init -y
npm install ws node-fetch dotenv
Create a .env file for your credentials:
ETORO_API_KEY=your_api_key_here
ETORO_USER_KEY=your_user_key_here
ETORO_ENVIRONMENT=demo
A simple moving average (SMA) crossover strategy generates signals when a fast-period SMA crosses above or below a slow-period SMA:
function calculateSMA(prices, period) {
if (prices.length < period) return null;
const slice = prices.slice(-period);
return slice.reduce((sum, p) => sum + p, 0) / period;
}
function getSignal(prices, fastPeriod = 10, slowPeriod = 30) {
const fastSMA = calculateSMA(prices, fastPeriod);
const slowSMA = calculateSMA(prices, slowPeriod);
if (!fastSMA || !slowSMA) return "HOLD";
const prevFast = calculateSMA(prices.slice(0, -1), fastPeriod);
const prevSlow = calculateSMA(prices.slice(0, -1), slowPeriod);
if (!prevFast || !prevSlow) return "HOLD";
if (prevFast <= prevSlow && fastSMA > slowSMA) return "BUY";
if (prevFast >= prevSlow && fastSMA < slowSMA) return "SELL";
return "HOLD";
}
The order manager handles trade execution through the eToro API:
import { randomUUID } from "node:crypto";
class OrderManager {
constructor(apiKey, userKey, environment) {
this.apiBase = "https://public-api.etoro.com/api/v1";
this.executionPrefix =
environment === "demo"
? "trading/execution/demo"
: "trading/execution";
this.apiKey = apiKey;
this.userKey = userKey;
this.positions = new Map();
}
headers() {
return {
"x-api-key": this.apiKey,
"x-user-key": this.userKey,
"x-request-id": randomUUID(),
"Content-Type": "application/json",
};
}
async openPosition(instrument, direction, amount) {
const response = await fetch(
`${this.apiBase}/${this.executionPrefix}/market-open-orders/by-amount`,
{
method: "POST",
headers: this.headers(),
body: JSON.stringify({
InstrumentID: instrument,
IsBuy: direction === "BUY",
Amount: amount,
}),
}
);
const order = await response.json();
this.positions.set(instrument, {
id: order.positionId,
direction,
amount,
entryPrice: order.executionPrice,
});
console.log(
`Opened ${direction} position on ${instrument} at ${order.executionPrice}`
);
return order;
}
async closePosition(instrument) {
const position = this.positions.get(instrument);
if (!position) return null;
const response = await fetch(
`${this.apiBase}/${this.executionPrefix}/market-close-orders/positions/${position.id}`,
{
method: "POST",
headers: this.headers(),
body: JSON.stringify({}),
}
);
this.positions.delete(instrument);
const result = await response.json();
console.log(`Closed position on ${instrument}`);
return result;
}
}
Never trade without risk controls. Our risk controller enforces:
class RiskController {
constructor(config) {
this.maxPositionSize = config.maxPositionSize || 1000;
this.stopLossPercent = config.stopLossPercent || 0.02;
this.maxPositions = config.maxPositions || 5;
this.currentPositions = 0;
}
canOpenPosition(amount) {
if (amount > this.maxPositionSize) {
console.warn(`Position size $${amount} exceeds max $${this.maxPositionSize}`);
return false;
}
if (this.currentPositions >= this.maxPositions) {
console.warn(`Max concurrent positions (${this.maxPositions}) reached`);
return false;
}
return true;
}
shouldStopLoss(entryPrice, currentPrice, direction) {
const change =
direction === "BUY"
? (currentPrice - entryPrice) / entryPrice
: (entryPrice - currentPrice) / entryPrice;
return change <= -this.stopLossPercent;
}
}
We need a helper to fetch the latest price for an instrument via the REST API:
async function getCurrentPrice(instrument) {
const response = await fetch(
`https://public-api.etoro.com/api/v1/market-data/rates?instrumentIds=${instrument}`,
{
headers: {
"x-api-key": process.env.ETORO_API_KEY,
"x-user-key": process.env.ETORO_USER_KEY,
},
}
);
const data = await response.json();
return data.lastPrice;
}
async function runBot() {
const orderManager = new OrderManager(
process.env.ETORO_API_KEY,
process.env.ETORO_USER_KEY,
process.env.ETORO_ENVIRONMENT
);
const riskController = new RiskController({
maxPositionSize: 500,
stopLossPercent: 0.02,
maxPositions: 3,
});
const instrument = "AAPL";
const priceHistory = [];
// Simulated price feed loop
setInterval(async () => {
// In production, fetch from WebSocket or REST API
const price = await getCurrentPrice(instrument);
priceHistory.push(price);
// Keep last 100 prices
if (priceHistory.length > 100) priceHistory.shift();
const signal = getSignal(priceHistory);
console.log(`${instrument}: $${price} | Signal: ${signal}`);
if (signal === "BUY" && !orderManager.positions.has(instrument)) {
if (riskController.canOpenPosition(500)) {
await orderManager.openPosition(instrument, "BUY", 500);
riskController.currentPositions++;
}
}
if (signal === "SELL" && orderManager.positions.has(instrument)) {
await orderManager.closePosition(instrument);
riskController.currentPositions--;
}
// Check stop-loss
const position = orderManager.positions.get(instrument);
if (
position &&
riskController.shouldStopLoss(position.entryPrice, price, position.direction)
) {
console.log(`Stop-loss triggered for ${instrument}`);
await orderManager.closePosition(instrument);
riskController.currentPositions--;
}
}, 60000);
}
runBot();
Was this helpful?
Nice work finishing all 4 steps. Here are some next steps to keep building.
A step-by-step guide to building a simple trading bot using the eToro Demo Trading API.
How to programmatically search, filter, and explore eToro's instrument catalog — asset classes, exchanges, industries, and historical data.