DEV Community

Phi Thành
Phi Thành

Posted on

Refactoring a Python Bot into a Hybrid AI Architecture

Feeding raw tick data directly into an LLM will destroy your latency profile and your API budget within an afternoon. This guide breaks down how we restructured a brittle execution script into a dual-loop hybrid AI trading bot that isolates order execution from LLM decision latency.

TL;DR: Decouple your runtime into two threads. Let a local Python loop poll ticks and run hardcoded risk parameters, while an autonomous background agent queries higher-timeframe market structure on a 15-minute interval.

Why Running an LLM on Every Tick Broke Our Loop

Our initial prototype was naive. Every time a new 1-minute candle closed for BTCUSD, the bot formatted the payload into a prompt, called an external model, waited for an inference response, and tried to execute a market order.

That setup failed for two clear reasons:

  1. Latency variance. Generating model tokens takes anywhere from 800 milliseconds to four seconds. On volatile moves, the Bid and Ask prices shifted across multiple spread widths before the order reached the exchange, leading to terrible slippage.
  2. Runaway costs. A single asset generating 1,440 candles per day across multiple prompt revisions quickly piles up thousands of tokens an hour. If you run multiple assets like BTCUSD, EURUSD, and XAUUSD simultaneously, you burn through token allowances for zero added insight.

As covered in our breakdown on algorithmic bot vs ai agent, LLMs should never sit directly on the hot path of order routing.

Splitting the Pipeline: Fast Execution vs Background Analysis

To fix the bottleneck, we divided the system into two distinct components that communicate via an in-memory state store:

  • The Fast Execution Loop: A Python process running every few hundred milliseconds. It tracks real-time prices, monitors bid-ask spreads, verifies position sizing, and checks stop levels. It contains zero LLM dependencies.
  • The Strategic Context Agent: An asynchronous worker running on a 15-minute clock. It inspects larger market patterns, tracks RSI levels, checks support and resistance, and writes an updated market bias into the local cache.

Under this pattern, the local executor is in total control of orders. If the strategic agent sets a bias = "LONG", the fast loop evaluates whether local volatility and spreads on BTCUSD actually justify an entry.

Querying RealMarketAPI MCP Tools on a 15-Minute Cadence

Instead of parsing raw charts with expensive multimodal vision prompts, the background worker uses standard tool calls to retrieve structured metrics. We connect our model environment directly to the market data MCP server hosted at https://ai.realmarketapi.com/mcp.

The server exposes 33 tools covering quotes, technical indicators, and multi-asset intelligence. If you are on the Pro plan ($34.99 per month), you get full MCP access along with 200,000 monthly REST requests.

For simple setups that do not use an agent runtime, you can make standard REST calls directly from a background Python thread to retrieve the same indicators from https://realmarketapi.com/?utm_source=devto&utm_medium=blog&utm_campaign=hybrid-ai-agent-and-rule-bot-architecture:

import requests

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.realmarketapi.com"

def get_market_context(symbol: str):
    # Fetch latest M1 candle for base numbers
    price_resp = requests.get(
        f"{BASE_URL}/api/v1/price",
        params={"apiKey": API_KEY, "symbolCode": symbol, "timeFrame": "M1"}
    ).json()

    # Fetch H1 RSI for macro momentum
    rsi_resp = requests.get(
        f"{BASE_URL}/api/v1/indicator/rsi",
        params={
            "apiKey": API_KEY,
            "symbolCode": symbol,
            "timeFrame": "H1",
            "period": 14
        }
    ).json()

    return {
        "symbol": price_resp.get("SymbolCode"),
        "close": price_resp.get("ClosePrice"),
        "rsi": rsi_resp
    }
Enter fullscreen mode Exit fullscreen mode

The background agent runs this query once every 15 minutes, evaluates the values against previous intervals, and updates a shared state file on disk or in Redis.

Passing Context Down to Local Risk Checks for BTCUSD

The local execution loop reads the latest context object and queries real-time pricing directly. If the background context reports an overbought condition or conflicts with current spread conditions, the local loop blocks the trade.

Here is how the fast loop evaluates incoming price data against local safety limits:

import time
import requests

API_KEY = "YOUR_API_KEY"
PRICE_URL = "https://api.realmarketapi.com/api/v1/price"

def fetch_live_quote(symbol: str):
    response = requests.get(
        PRICE_URL,
        params={"apiKey": API_KEY, "symbolCode": symbol, "timeFrame": "M1"}
    )
    return response.json()

def run_fast_loop():
    # Example illustrative response fields: SymbolCode, Bid, Ask, ClosePrice
    # In production, cache your bias state from the 15-minute worker
    current_bias = "BULLISH"
    max_allowed_spread = 15.0

    while True:
        data = fetch_live_quote("BTCUSD")

        bid = float(data.get("Bid", 0.0))
        ask = float(data.get("Ask", 0.0))
        spread = ask - bid

        if spread > max_allowed_spread:
            print(f"Spread too wide ({spread}). Skipping execution.")
            time.sleep(1)
            continue

        if current_bias == "BULLISH":
            # Execute entry logic here on local exchange client
            print(f"Executing entry for {data.get('SymbolCode')} at Ask: {ask}")
            break

        time.sleep(1)

if __name__ == "__main__":
    run_fast_loop()
Enter fullscreen mode Exit fullscreen mode

By keeping network execution within standard HTTP or WebSocket polling, the decision takes less than a millisecond of compute on your own machine.

Token Costs and Tradeoffs Under the Hybrid AI Trading Bot Model

Decoupling the architecture dropped our LLM token consumption by more than 90 percent. Instead of sending 1,440 full prompt cycles per day per symbol, the system runs exactly 96 analysis loops per day.

There are honest engineering tradeoffs to consider before adopting this architecture:

  • State desynchronization: If your background agent hangs or hits an unhandled exception, your local loop might trade against stale context. You must implement a staleness threshold. For example, if the context file has an OpenTime older than 30 minutes, the fast loop defaults to neutral and stops opening new risk.
  • Rate limits: On the Free tier, you receive 5,000 requests per month covering symbols like BTCUSD, EURUSD, and XAUUSD across M1, M5, and H1 timeframes without a credit card. Polling a fast loop every second on REST will burn that quota in less than two hours. To poll continuously, you need to use WebSocket streams (available starting on the Plus tier at $14.99 per month for 10 streams) or increase your REST intervals.
  • Tool latency: Tools accessed through an MCP client still depend on network round-trips to the upstream model provider. Do not expect real-time reflex reactions from the agent thread.

Pick one symbol like BTCUSD or EURUSD, spin up a background worker that checks indicators every 15 minutes, and verify your local loop execution before connecting live capital.

Top comments (0)