Build a fully functional algorithmic trading bot using the eToro API with position management, risk controls, and automated strategy execution.
Part of: Build a Trading Bot — Step 4 of 4
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.
Important: 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?
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.