AI Prediction MarketsModule 5

5.1Bot Architecture — Scheduler, Executor & Monitor

35 min 3 code blocks Practice Lab Quiz (4Q)

Bot Architecture — Scheduler, Executor & Monitor

A trading bot that only works when you're watching it is a bot in name only. The production Oracle needs three systems working independently and in concert: a Scheduler that orchestrates timing, an Executor that places and manages trades, and a Monitor that watches the bot itself for failures and performance degradation. This lesson builds the architecture that runs your Oracle 24/7 without manual intervention.

The Three-Component Architecture

Think of your production Oracle as three specialized workers in a trading room:

The Scheduler runs on a cron-like loop. Every 5 minutes, it fetches the latest market data. Every 30 minutes, it runs the full signal pipeline. Every 6 hours, it performs portfolio rebalancing and exposure checks. It doesn't make trading decisions — it triggers the right workflows at the right times.

The Executor receives trade signals from the Scheduler and actually interacts with the Polymarket CLOB API. It handles order placement, monitors open orders, manages partial fills, and logs all activity to the database. The Executor is the only component that moves real money.

The Monitor watches the Scheduler and Executor from outside. It checks every 15 minutes that the pipeline is running. It alerts via WhatsApp/Telegram if a component crashes, if API errors spike, if drawdown limits are breached, or if no trades have been placed in an unusual period.

The Scheduler Implementation

python
import asyncio
import schedule
import time
from datetime import datetime

class OracleScheduler:
    def __init__(self, pipeline, executor, monitor):
        self.pipeline = pipeline
        self.executor = executor
        self.monitor = monitor
        self.last_signal_time = None

    async def run_signal_cycle(self):
        """Full signal generation cycle — runs every 30 minutes."""
        print(f"[{datetime.now().isoformat()}] Starting signal cycle...")

        # Step 1: Fetch fresh market data
        markets = await self.pipeline.get_active_markets(min_volume=50_000)

        # Step 2: Ingest news
        articles = await self.pipeline.ingest_all_sources()

        # Step 3: Triage
        signals = await self.pipeline.triage_all_pairs(articles, markets)

        # Step 4: Deep analysis for survivors
        analyzed = [
            await self.pipeline.deep_analyze_signal(s)
            for s in signals
            if s["triage"]["route"] == "DEEP_ANALYZE"
        ]

        # Step 5: Ensemble voting
        trade_candidates = [
            self.pipeline.aggregate_ensemble(a)
            for a in analyzed
            if a is not None
        ]

        # Step 6: Portfolio filter and execution
        approved_trades = [
            t for t in trade_candidates
            if t["execute"] and self.executor.portfolio.get_position_size(100) > 0
        ]

        for trade in approved_trades:
            await self.executor.place_trade(trade)

        self.last_signal_time = datetime.now()
        print(f"Cycle complete. {len(approved_trades)} trades executed.")

    async def run_forever(self):
        """Main scheduler loop."""
        while True:
            await self.run_signal_cycle()
            await asyncio.sleep(1800)  # 30 minutes between cycles

The Executor Implementation

python
from py_clob_client.client import ClobClient
from py_clob_client.clob_types import OrderType, BUY, SELL

class TradeExecutor:
    def __init__(self, clob_client: ClobClient, db: OracleDB,
                 portfolio: PortfolioManager):
        self.clob = clob_client
        self.db = db
        self.portfolio = portfolio

    async def place_trade(self, trade_signal: dict):
        """Place a limit order based on a trade signal."""
        market_id = trade_signal["market_id"]
        direction = trade_signal["direction"]
        ensemble_prob = trade_signal["final_probability"]
        current_price = trade_signal["current_price"]

        # Calculate position size
        base_size = 100  # $100 base position
        actual_size = self.portfolio.get_position_size(base_size)
        if actual_size == 0:
            print(f"HALTED: Portfolio in drawdown stop. Skipping {market_id}")
            return

        # Determine order parameters
        shares = int(actual_size / current_price)
        limit_price = current_price + 0.01  # Small premium above current ask

        # Place limit order
        try:
            order_id = self.clob.create_order(
                token_id=market_id,
                price=limit_price,
                size=shares,
                side=BUY if "YES" in direction else SELL,
                order_type=OrderType.GTC  # Good Till Cancelled
            )

            # Log trade
            self.db.log_trade(
                market_id=market_id,
                side=direction,
                action="BUY",
                price=limit_price,
                shares=shares
            )

            print(f"Order placed: {shares} shares {direction} at {limit_price}")
            return order_id

        except Exception as e:
            print(f"Order failed for {market_id}: {e}")
            return None

The Monitor System

The Monitor prevents silent failures — the worst kind of bot problem. Your bot crashes at 3am PKT, you wake up at 8am, and 5 hours of prime signal window are lost with no alerts.

python
import requests
import os

class BotMonitor:
    def __init__(self, scheduler, db):
        self.scheduler = scheduler
        self.db = db
        self.WHATSAPP_API = os.getenv("WATI_API_KEY")

    def send_alert(self, message: str, severity: str = "WARNING"):
        """Send WhatsApp alert via WATI API."""
        # Use your WATI integration from other bots
        print(f"[ALERT - {severity}] {message}")

    def health_check(self):
        """Run every 15 minutes via cron."""
        issues = []

        # Check if scheduler ran recently
        if self.scheduler.last_signal_time:
            minutes_since_last = (datetime.now() -
                                   self.scheduler.last_signal_time).seconds / 60
            if minutes_since_last > 60:
                issues.append(f"No signal cycle in {minutes_since_last:.0f} minutes")

        # Check portfolio status
        status = self.scheduler.executor.portfolio.trading_status
        if status != "FULL":
            issues.append(f"Portfolio in {status} mode")

        # Check recent win rate
        win_stats = self.db.get_win_rate()
        if win_stats["total"] > 10 and win_stats["win_rate"] < 0.40:
            issues.append(f"Win rate below threshold: {win_stats['win_rate']:.0%}")

        if issues:
            self.send_alert("\n".join(issues), severity="WARNING")

The Daily Report

Every morning at 7am PKT, your bot sends you a summary:

  • Trades placed yesterday: count, total value, expected P&L
  • Current open positions
  • Portfolio status and drawdown %
  • Win rate (last 30 days)
  • API cost breakdown

This replaces manual monitoring and gives you full visibility in a 30-second read each morning.

Practice Lab

Practice Lab

  1. Scheduler dry run: Implement the OracleScheduler with a 5-minute cycle (not 30-minute) and run it for 15 minutes in a test environment. Use mock data from your database instead of live API calls. Verify the cycle runs without errors three consecutive times.

  2. Executor simulation: Build the TradeExecutor but in paper trade mode — instead of calling the CLOB API, just log what trades would have been placed. Run a full cycle and review the paper trade log. Are the position sizes, limit prices, and directions as expected?

  3. Monitor alert test: Manually set the last_signal_time to 2 hours ago and run health_check(). Verify it detects the issue and would send an alert. Then set a mock win rate of 0.35 and verify that also triggers an alert.

Key Takeaways

  • Three-component architecture (Scheduler + Executor + Monitor) separates concerns — the Scheduler orchestrates timing, Executor handles money, Monitor watches the system
  • The Executor is the only component that moves real money — all other components only read data or generate signals
  • Silent failures are the worst bot problem; a Monitor that alerts within 15 minutes of a failure protects against hours of missed trading windows
  • Paper trading mode on the Executor is mandatory before live deployment — verify the full pipeline produces sensible trade signals for at least 2 weeks before committing real USDC

Lesson Summary

Includes hands-on practice lab3 runnable code examples4-question knowledge check below

Bot Architecture Quiz

4 questions to test your understanding. Score 60% or higher to pass.