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()
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
| Symptom | Cause | Fix |
|---|---|---|
409 stale_quote | snapshot older than 15 seconds | retry on the next tick; do not force the order |
409 market_execution_unavailable | phase left continuous | check status before every order, not once at boot |
429 rate_limited | free plan: 500 requests a day | one symbol per tick; a watchlist needs Pro |
| Duplicate orders | overlapping cron runs | a lock file around the whole run |
| Key in git history | hard-coded secret | environment 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.