# 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:

1. **Data collector** — Fetches historical and real-time price data
2. **Strategy engine** — Implements trading logic (SMA crossover)
3. **Order manager** — Executes trades and tracks positions
4. **Risk controller** — Enforces position limits and stop-losses

## Setting Up the Project

```bash skip-test
mkdir etoro-trading-bot && cd etoro-trading-bot
npm init -y
npm install ws node-fetch dotenv
```

Create a `.env` file for your credentials:

```bash skip-test
ETORO_API_KEY=your_api_key_here
ETORO_USER_KEY=your_user_key_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)

```javascript skip-test
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:

```javascript skip-test
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;
  }
}
```

## Risk Controller

Never trade without risk controls. Our risk controller enforces:

- Maximum position size
- Stop-loss percentage
- Maximum number of concurrent positions

```javascript skip-test
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;
  }
}
```

## Fetching the Current Price

We need a helper to fetch the latest price for an instrument via the REST API:

```javascript skip-test
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;
}
```

## Putting It All Together

```javascript skip-test
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();
```

## Best Practices

1. **Start with demo** — Always validate your strategy in the sandbox first
2. **Log everything** — Record all trades, signals, and errors for analysis
3. **Set hard limits** — Use the risk controller to prevent catastrophic losses
4. **Monitor actively** — Don't leave a bot running unattended for extended periods
5. **Handle errors gracefully** — Network failures, API errors, and edge cases will happen
6. **Backtest first** — Test your strategy against historical data before live trading

## Next Steps

- [eToro API Reference](https://api-portal.etoro.com/api-reference) — Complete endpoint docs
- [Getting Started](/learn/getting-started-with-etoro-api-v2) — API basics and authentication
- [Real-Time Market Data](/learn/real-time-market-data-websocket) — WebSocket streaming guide
