Home · Blog
Tutorial

Build a daily eToro trading bot on top of the Stock Screener API

June 3, 2026 · ~7 min read

A screener is only half the job. It tells you what to hold; it doesn't put the trades on. So I wrote a small Python bot that closes that loop: every weekday morning it pulls the latest picks from the Stock Screener API, maps each ticker to its eToro instrument, and rebalances an equal-weight portfolio to match. The whole thing is about 120 lines and one dependency.

The full code lives in the etoro-python-trading-bot repo. This post walks through how it fits together. Everything below runs in eToro's demo (paper trading) environment by default, which is exactly where you want to start.

Read this first. This is an educational tutorial, not investment advice. Run it in demo mode. Automated order execution can lose money quickly if it misbehaves, so understand every line before you point it at a funded account.

What it does, in three steps

  1. Fetch today's picks from one screener via the Stock Screener API.
  2. Resolve each ticker symbol to eToro's numeric instrument ID (cached, so you only look it up once).
  3. Rebalance the demo portfolio: close anything that dropped out of the screener, open equal-weight positions for the new names.

What you need

Just Python 3.10+ and the requests library. The bot talks to both REST APIs directly, so there's no SDK to install.

pip install requests

You'll set four environment variables. Two for the screener, two for eToro:

RAPIDAPI_KEY=your-rapidapi-key
RAPIDAPI_HOST=stock-screener6.p.rapidapi.com
ETORO_API_KEY=your-etoro-api-key
ETORO_USER_KEY=your-etoro-user-key

The RapidAPI pair comes from your Stock Screener API subscription (there's a free tier). For the eToro pair, go to Settings → Trading → API Key Management, create a key in the Demo environment with Read + Write permissions, complete the SMS verification, and copy both the API key and the user key.

The eToro request pattern

Every eToro call needs three headers: x-api-key, x-user-key, and a fresh x-request-id (a new UUID per request). Rather than repeat that everywhere, wrap it once:

import os, uuid, requests

ETORO_BASE = "https://www.etoro.com/api/public/v1"

def etoro_headers():
    return {
        "x-api-key":    os.environ["ETORO_API_KEY"],
        "x-user-key":   os.environ["ETORO_USER_KEY"],
        "x-request-id": str(uuid.uuid4()),
        "Content-Type": "application/json",
    }

def etoro_get(path):
    r = requests.get(ETORO_BASE + path, headers=etoro_headers(), timeout=30)
    r.raise_for_status()
    return r.json()

def etoro_post(path, body):
    r = requests.post(ETORO_BASE + path, headers=etoro_headers(), json=body, timeout=30)
    r.raise_for_status()
    return r.json()

Step 1: pull the picks

One request to the Stock Screener API returns the latest tickers for a given screener. Swap in whichever strategy you want to track.

def fetch_picks(screener_id):
    r = requests.get(
        f"https://{os.environ['RAPIDAPI_HOST']}/tickers/latest",
        headers={
            "x-rapidapi-key":  os.environ["RAPIDAPI_KEY"],
            "x-rapidapi-host": os.environ["RAPIDAPI_HOST"],
        },
        params={"screener": screener_id},
        timeout=30,
    )
    r.raise_for_status()
    return [row["ticker"] for row in r.json()]

Step 2: resolve tickers to instrument IDs

eToro trades on numeric instrument IDs, not ticker strings, so each symbol has to be looked up. Those IDs never change, so you only pay the lookup cost once per ticker per machine. A tiny JSON file on disk is enough of a cache.

import json, pathlib

CACHE = pathlib.Path("instruments.json")

def load_cache():
    return json.loads(CACHE.read_text()) if CACHE.exists() else {}

def save_cache(cache):
    CACHE.write_text(json.dumps(cache, indent=2))

def resolve_instrument(ticker, cache):
    if ticker in cache:
        return cache[ticker]
    data = etoro_get(f"/market-data/instruments?symbols={ticker}")
    instrument_id = data["instruments"][0]["instrumentId"]
    cache[ticker] = instrument_id
    save_cache(cache)
    return instrument_id

Step 3: rebalance the portfolio

Now the actual logic. Pull the current demo portfolio, compute an equal-weight target from total account equity divided by the number of picks, then reconcile: close what's no longer picked, open what's new. Sizing off equity means the bot scales automatically as the account grows or shrinks.

def rebalance(screener_id):
    cache = load_cache()

    # Today's picks, resolved to eToro instrument IDs
    tickers  = fetch_picks(screener_id)
    pick_ids = {resolve_instrument(t, cache): t for t in tickers}

    # Current demo portfolio and equal-weight target size
    portfolio = etoro_get("/portfolio/demo")
    equity    = portfolio["credit"] + portfolio["totalInvested"]
    target    = equity / max(len(pick_ids), 1)

    held = {p["instrumentId"]: p for p in portfolio["positions"]}

    # Close positions that fell out of the screener
    for instrument_id, pos in held.items():
        if instrument_id not in pick_ids:
            etoro_post("/trading/execution/demo/close",
                       {"positionId": pos["positionId"]})

    # Open equal-weight positions for new picks
    for instrument_id in pick_ids:
        if instrument_id not in held:
            etoro_post("/trading/execution/demo/open", {
                "instrumentId": instrument_id,
                "amount":       round(target, 2),
                "leverage":     1,
                "isBuy":        True,
            })

if __name__ == "__main__":
    rebalance(os.environ.get("SCREENER_ID", "magic-formula"))

Note the execution paths: /trading/execution/demo/.... The mode segment is the one thing that separates paper from real money, which we'll come back to.

Let the screener pick the screener

Instead of hard-coding one strategy, you can let recent performance choose. The Stock Screener API exposes cohort-based forward returns, so the bot can rank screeners over a window (1m, 3m, 6m) and trade whichever is leading. That turns a static bot into a simple strategy-rotation engine. It's also a fast way to chase a hot streak, so weigh it against the "one month proves nothing" caution from the last post.

Running it every morning

The script is stateless apart from the instrument cache, so any scheduler works. Three options, in order of how much infrastructure they need:

Local cron

# 9:35 AM ET, weekdays, just after the open
35 9 * * 1-5  cd /path/to/bot && /usr/bin/python3 bot.py >> bot.log 2>&1

GitHub Actions

Store the four credentials as repository secrets and let a scheduled workflow run the bot. No server to keep alive, and you get run logs for free. The repo includes a .github/workflows/trade.yml to start from.

AWS Lambda + EventBridge

Package the script as a Lambda and trigger it on an EventBridge schedule. This is the route to look at if you're already running the rest of your stack on AWS.

Before you ever touch real money

Going from /demo/ to /real/ is a one-word change in the code, which is exactly why it deserves real guardrails first. Generate a separate real-environment API key in eToro, and add protections before you flip the switch:

The takeaway. The screener produces the signal; this bot is just plumbing that keeps a portfolio in sync with it. Keeping that plumbing boring and well-guarded is the entire job. Get it solid in demo, prove the screener earns its keep, and only then think about real capital.

Want the picks behind the bot?

Daily screener picks, consensus signals, and cohort-based performance, all as a simple JSON API, with a free tier.

Get it on RapidAPI →