Build a daily eToro trading bot on top of the Stock Screener API
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.
What it does, in three steps
- Fetch today's picks from one screener via the Stock Screener API.
- Resolve each ticker symbol to eToro's numeric instrument ID (cached, so you only look it up once).
- 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:
- Position cap. Limit how many names the bot will hold so a large pick list doesn't over-diversify you into dust.
- Cash buffer. Keep a minimum free balance so rebalances never push the account into a margin situation.
- Leverage locked to 1. Hard-code it. eToro supports CFD leverage; this tutorial deliberately does not use it.
- Buy-only. No shorting unless you have explicitly decided to.
- Kill switch. An environment flag that halts all order placement instantly.
- Order logging and rate limiting. Log every order for reconciliation, and space out API calls.
- Test long in demo. Watch it through different market conditions, then start with minimal 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 →