GUIDE · PYTHON

Build your first Saudi trading bot

A loop that asks whether the market is open, reads a realtime quote, applies one rule and places a simulated order. It never touches a broker: paper fills are generated from market snapshots, which is exactly what you want while the logic is still wrong.

Published 25 October 2026 · every endpoint on this page is live today.

1. The shape of the loop

cron (every 5 minutes, Sunday–Thursday)
  └─ GET /market/status          → skip when execution_open is false
  └─ GET /quotes/{symbol}        → realtime price
  └─ rule                        → buy / sell / hold
  └─ POST /paper/portfolio/orders
  └─ append one line to a CSV

2. Gate on the session, not on the clock

The API knows the session phase; your server's clock does not know about holidays, early closes or the auction window. Ask the API.

import os, requests

BASE = "https://api.tasi-api.com/api/v1"
H = {"X-API-Key": os.environ["TASI_API_KEY"]}

def market_open() -> bool:
    r = requests.get(f"{BASE}/market/status", headers=H, timeout=10)
    r.raise_for_status()
    return r.json()["execution_open"]      # true only in phase "continuous"

phase reports where the session is: pre_open, continuous, closing_auction, trade_at_last or closed. Only continuous sets execution_open, and a paper order outside it is refused with 409 market_execution_unavailable.

3. Read one quote

def quote(symbol: str) -> dict:
    r = requests.get(f"{BASE}/quotes/{symbol}", headers=H, timeout=10)
    r.raise_for_status()
    return r.json()["data"]

The response carries last, bid, ask, captured_at, volume_day, value_day and a server-derived change. Do not recompute the change client-side: the server falls back through source change, previous close and the last daily bar, and your own arithmetic will disagree with every screen that shows it.

4. Keep the rule pure

def rule(q: dict, entry: float, take: float) -> str:
    if q["last"] <= entry: return "buy"
    if q["last"] >= take:  return "sell"
    return "hold"

No network calls inside the decision. Every bot that becomes impossible to debug has its logic tangled with its I/O, and then you cannot tell a bad rule from a bad connection.

5. Place the paper order

Paper Trading uses a separate key, bound to one portfolio, with three scopes: market:read, portfolio:read and orders:write. Money and quantities are decimal strings, not floats.

PAPER = {"X-API-Key": os.environ["PAPER_KEY"],
         "Content-Type": "application/json"}

def order(symbol: str, side: str, qty: int) -> dict:
    r = requests.post(f"{BASE}/paper/portfolio/orders", headers=PAPER,
                      json={"symbol": symbol, "side": side,
                            "order_type": "market", "quantity": str(qty)},
                      timeout=10)
    return r.json()
A MARKET order fills at the latest snapshot only while the phase is continuous and the snapshot is at most 15 seconds old. A LIMIT order stays open and reserves cash or quantity until a newer snapshot crosses your price.

6. Schedule it

*/5 10-14 * * 0-4  cd /app && python bot.py >> bot.log 2>&1

0-4 is Sunday to Thursday and the session runs 10:00–15:00 Riyadh. A run at 15:00 can still catch the closing auction; a run on Friday never catches anything.

7. What actually breaks

SymptomCauseFix
409 stale_quotesnapshot older than 15 secondsretry on the next tick; do not force the order
409 market_execution_unavailablephase left continuouscheck status before every order, not once at boot
429 rate_limitedfree plan: 500 requests a dayone symbol per tick; a watchlist needs Pro
Duplicate ordersoverlapping cron runsa lock file around the whole run
Key in git historyhard-coded secretenvironment variable only; rotate in the portal

8. When polling stops being enough

At one symbol every five minutes, polling is the right tool and streaming would be ceremony. Pro accounts that need many symbols can subscribe to wss://api.tasi-api.com/api/v1/ws for up to 20 symbols and receive a frame whenever the snapshot advances — the protocol is in the streaming section.

9. Next

Read the portfolio back with /paper/portfolio/positions and attribute the P&L to each rule you tried — that comparison, not the equity number, is what tells you whether the rule has anything in it. The order contract is in Paper Trading, and the data-side details are in TASI historical data.

Get a key and run this today.

Free covers three Saudi stocks plus the index — no card, no expiry.