# From Demo to Production: Migrating Your Trading Bot

A practical guide for transitioning your trading bot from eToro's demo sandbox to the real trading API — safety checks, key differences, and best practices.

---


Paper trading proves your signal logic, order sequencing, and state machine; **production** proves your discipline. Moving a bot from eToro’s **virtual** environment to **real** execution is not a find-and-replace on a URL—it is a controlled rollout: separate credentials, different execution paths, stricter risk controls, and observability you can trust when money moves. This guide highlights the differences that matter and shows how to structure configuration so you can switch environments without duplicating your strategy code.

## Demo vs real: endpoints and portfolios

The public API shares one base URL: `https://public-api.etoro.com/api/v1/`. What changes is the **path** and the **key environment**. Demo execution lives under paths that include **`demo`** (for example **`POST /trading/execution/demo/market-open-orders/by-amount`**), while live trading uses the non-demo execution routes (for example **`POST /trading/execution/market-open-orders/by-amount`**). Portfolio and P/L discovery follow the same split: demo under **`/trading/info/demo/...`**, real under **`/trading/info/real/portfolio`**, **`/trading/info/real/pnl`**, and related endpoints.

Your **User Key** is issued per environment (Virtual vs Real). A key that works for demo portfolio endpoints must not be assumed to work for live trading—store **`ETORO_ENV`** alongside secrets and validate on startup.

## Authentication: OAuth vs API keys

Integrations may use **OAuth** (`Authorization: Bearer ...`) or **manual keys** (`x-api-key` + `x-user-key`). The switching logic is the same: production traffic should use **production-issued** tokens or keys, rotated on a schedule and never logged. Whichever method you use, send a unique **`x-request-id`** (UUID) on **every** request so platform logs and your application logs can be joined during an incident.

## Configuration pattern in code

Centralize base URL and execution path prefixes so strategy code only calls **`openMarketByAmount(payload)`** and does not embed `/demo/` scattered across files.

<!-- skip-test -->
```javascript
import { randomUUID } from "node:crypto";

const BASE = "https://public-api.etoro.com/api/v1";

/** @type {{ env: "demo" | "real"; apiKey: string; userKey: string }} */
const cfg = {
  env: process.env.ETORO_ENV === "real" ? "real" : "demo",
  apiKey: process.env.ETORO_API_KEY,
  userKey: process.env.ETORO_USER_KEY,
};

function authHeaders() {
  return {
    "x-request-id": randomUUID(),
    "x-api-key": cfg.apiKey,
    "x-user-key": cfg.userKey,
    "content-type": "application/json",
  };
}

function executionPath(kind) {
  const root =
    cfg.env === "demo"
      ? `${BASE}/trading/execution/demo`
      : `${BASE}/trading/execution`;
  return `${root}/${kind}`;
}

export async function openMarketByAmount(body) {
  const url = executionPath("market-open-orders/by-amount");
  const res = await fetch(url, {
    method: "POST",
    headers: authHeaders(),
    body: JSON.stringify(body),
  });
  if (!res.ok) {
    const text = await res.text();
    throw new Error(`openMarket ${res.status}: ${text}`);
  }
  return res.json();
}
```

Trading bodies use **PascalCase** fields such as **`InstrumentID`**, **`IsBuy`**, **`Leverage`**, and **`Amount`**—keep serializers shared between environments so you do not drift.

## Portfolio and P/L: match the environment

Reconciliation loops should hit the same “side” of the API as execution. Demo positions and P/L come from **`GET /trading/info/demo/portfolio`** and **`GET /trading/info/demo/pnl`**; live accounts use **`GET /trading/info/real/portfolio`** and **`GET /trading/info/real/pnl`**. The snippet below keeps polling logic identical while swapping only the path—pair it with exponential backoff when you receive **`429`**.

<!-- skip-test -->
```javascript
function tradingInfoPath(resource) {
  if (cfg.env === "demo") {
    return resource === "pnl"
      ? `${BASE}/trading/info/demo/pnl`
      : `${BASE}/trading/info/demo/portfolio`;
  }
  return resource === "pnl"
    ? `${BASE}/trading/info/real/pnl`
    : `${BASE}/trading/info/real/portfolio`;
}

export async function fetchPortfolioSnapshot() {
  const res = await fetch(tradingInfoPath("portfolio"), {
    headers: authHeaders(),
  });
  if (res.status === 429) throw new Error("rate_limited");
  if (!res.ok) throw new Error(`portfolio ${res.status}`);
  return res.json();
}
```

## Safety checks before going live

**Position sizing**: cap **`Amount`** and **`AmountInUnits`** with config that is stricter in production than in demo—many incidents are correct logic with wrong magnitude.

**Rate limiting**: backoff on **`429`** and avoid tight loops hitting execution or portfolio endpoints; use **`GET /trading/info/real/portfolio`** (or demo equivalent) at a sane interval, not every tick.

**Error handling**: treat **network errors**, **5xx**, and **partial fills** as first-class states; never assume `fetch` success means a fully working order—parse the response body and reconcile open orders and positions.

**Staged rollout**: run production keys against **read-only** endpoints first, then enable **small** live notional with manual approval, then widen limits after metrics look stable.

## Monitoring, logging, and rollback

Log structured fields: **`x-request-id`**, **`InstrumentID`**, order id, position id, and your internal **`strategyRunId`**. Alert on repeated failures, slippage spikes, or divergence between intended and reported positions.

Rollback means more than “turn off the bot”: cancel open orders with the appropriate **`DELETE`** on market or limit order endpoints, close or reduce positions via **`market-close-orders`** using **`positionId`**, and disable cron triggers in your scheduler. Keep a **kill switch** environment variable that your process checks before every execution call.

## Summary

Migrating from demo to production is primarily about **credential isolation**, **correct execution paths**, and **operational guardrails**—not about rewriting your alpha. Share one codebase, parameterize environment, validate keys on startup, and treat the first week of live trading as a **limited experiment** with tight limits and full observability. For exact path names and body schemas, always cross-check the official API Portal before you deploy.
