Building an Algo Trading Bot
Build a fully functional algorithmic trading bot using the eToro API with position management, risk controls, and automated strategy execution.
Overview
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.
Architecture
Our bot consists of four main components:
- Data collector — Fetches historical and real-time price data
- Strategy engine — Implements trading logic (SMA crossover)
- Order manager — Executes trades and tracks positions
- Risk controller — Enforces position limits and stop-losses
Setting Up the Project
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_API_SECRET=your_api_secret_here
ETORO_ENVIRONMENT=demo
The Strategy: SMA Crossover
A simple moving average (SMA) crossover strategy generates signals when a fast-period SMA crosses above or below a slow-period SMA:
- Buy signal: Fast SMA crosses above slow SMA (bullish)
- Sell signal: Fast SMA crosses below slow SMA (bearish)
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";
}
Order Manager
The order manager handles trade execution through the eToro API:
class OrderManager {
constructor(apiKey, environment) {
this.apiBase =
environment === "demo"
? "https://demo-api.etoro.com/api/sor/v3"
: "https://api.etoro.com/api/sor/v3";
this.apiKey = apiKey;
this.positions = new Map();
}
async openPosition(instrument, direction, amount) {
const response = await fetch(`${this.apiBase}/orders`, {
method: "POST",
headers: {
Authorization: `Bearer ${this.apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
instrument,
direction,
amount,
type: "market",
}),
});
const order = await response.json();
this.positions.set(instrument, {
id: order.data.positionId,
direction,
amount,
entryPrice: order.data.executionPrice,
});
console.log(
`Opened ${direction} position on ${instrument} at ${order.data.executionPrice}`
);
return order;
}
async closePosition(instrument) {
const position = this.positions.get(instrument);
if (!position) return null;
const response = await fetch(
`${this.apiBase}/positions/${position.id}/close`,
{
method: "POST",
headers: {
Authorization: `Bearer ${this.apiKey}`,
"Content-Type": "application/json",
},
}
);
this.positions.delete(instrument);
const result = await response.json();
console.log(`Closed position on ${instrument}`);
return result;
}
}
Risk Controller
Never trade without risk controls. Our risk controller enforces:
- Maximum position size
- Stop-loss percentage
- Maximum number of concurrent positions
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;
}
}
Putting It All Together
async function runBot() {
const orderManager = new OrderManager(
process.env.ETORO_API_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();
Best Practices
- Start with demo — Always validate your strategy in the sandbox first
- Log everything — Record all trades, signals, and errors for analysis
- Set hard limits — Use the risk controller to prevent catastrophic losses
- Monitor actively — Don't leave a bot running unattended for extended periods
- Handle errors gracefully — Network failures, API errors, and edge cases will happen
- Backtest first — Test your strategy against historical data before live trading
Next Steps
- eToro API Reference — Complete endpoint docs
- Getting Started — API basics and authentication
- Real-Time Market Data — WebSocket streaming guide