Skip to main content
Algorithmic Trading With Python and AI: The System I Actually Built

Algorithmic Trading With Python and AI: The System I Actually Built

Algorithmic Trading With Python and AI: The System I Actually Built
Chudi Mar 9, 2026 Updated Mar 11, 2026 9 min read

Complete architecture of a Python trading bot using Claude Code, Polymarket CLOB, and Binance signals. From paper trading to live deployment.

Why this matters

I built a production algorithmic trading system using Python, Claude Code for development, Polymarket's CLOB API for execution, and Binance price feeds for signals. This guide covers the full architecture: signal generation, position management, exit strategies, and the paper-to-live progression that prevented costly mistakes.

System Architecture Overview

When I started building a trading bot, I expected the hard part to be the trading logic. It wasn’t. The hard part was building a system that could run continuously for weeks without losing state, crashing silently, or entering impossible positions.

A production trading bot needs five core modules that work together:

  1. Signal Generation - Monitors price feeds and generates trade signals
  2. Position Management - Executes trades, tracks holdings, and prevents overlapping positions
  3. Exit Strategies - Knows when to close positions and takes profits or cuts losses
  4. State Persistence - Survives process crashes, power failures, and restarts
  5. Health Monitoring - Detects stuck orders, orphaned positions, and api failures

Each module can fail independently, so the system needs to handle partial failures gracefully. A signal can fail without crashing position management. An API call can timeout without losing the position state. The monitoring system watches everything and alerts when something goes wrong.

The entire codebase is about 4,000 lines of Python. Claude Code wrote 95% of it, including the most complex parts: the asyncio event loop, the database schema and queries, and the deployment scripts.

Signal Generation

Signals are the input to the entire system. My signals come from two sources: Binance price momentum and Polymarket market odds.

Binance provides real-time price data via WebSocket. I watch 5-minute price movements and detect breakouts above 2-sigma bands. When BTC price breaks upward sharply, I look for corresponding prediction markets on Polymarket that are underpriced relative to the momentum.

The signal pipeline is documented in my post on Binance-Polymarket momentum signal generation. The key insight is that prediction market prices lag price momentum by 30-90 seconds, creating a small window to profit from the difference.

The Momentum Window

Here’s how the signal generation actually works in code:

async def detect_momentum_breakout(candles: list[dict]) -> float | None:
    """
    Watch 5-min BTC candles and detect 2-sigma breakouts.
    Returns signal strength (0-1) if breakout detected.
    """
    closes = [c['close'] for c in candles[-20:]]  # Last 20 candles
    mean = sum(closes) / len(closes)
    variance = sum((x - mean) ** 2 for x in closes) / len(closes)
    std_dev = variance ** 0.5

    current_close = closes[-1]
    z_score = (current_close - mean) / std_dev

    if z_score > 2.0:  # Upside breakout
        return min(1.0, z_score / 3.0)  # Cap at 3-sigma
    return None

When a signal fires, the system calculates how many standard deviations above the mean the price has moved. A 2-sigma move occurs about 2% of the time randomly, but when combined with Polymarket underpricing, the edge becomes real.

The efficiency gap between crypto spot prices and prediction markets exists because:

  1. Crypto moves on technicals and sentiment (fast)
  2. Prediction markets move on fundamental news cycles (slower)
  3. Retail traders on Polymarket have longer decision latency than algo traders on Binance

This isn’t a edge I’ll have forever. As more traders build similar systems, the 30-90 second window compresses. But for now, it’s consistent enough to trade.

Signals feed into a queue. The position manager processes signals one at a time, ensuring we never accidentally open two positions on the same market.

Position Management

Once a signal arrives, the position manager decides: do we take this trade, or skip it?

The decision logic checks:

  • Do we already have a position in this market? If yes, skip.
  • Is the order book deep enough to execute at reasonable prices? If no, skip.
  • Has this market been active for more than 24 hours? Recent markets are illiquid.
  • Are we at our maximum concurrent positions? If yes, skip.

If all checks pass, the position manager places a limit order on the Polymarket CLOB. The CLOB is Polymarket’s central limit order book for derivatives trading. It’s lower latency than the REST API but requires understanding the order book structure.

See my detailed post on building the Polymarket trading bot for the specifics of CLOB integration.

The position manager tracks every open position in SQLite. Each position stores:

  • Market ID and outcome tokens
  • Entry price and quantity
  • Timestamp and signal strength
  • Current mark-to-market value
  • Status (open, closing, closed)

This database survives process restarts. On startup, the position manager reads the database and reconstructs the exact state it was in before the crash.

Exit Strategies

The hardest part of algorithmic trading is exits. Most retail traders focus on entries but skip profitable or know when to close positions. It’s the difference between “cool idea” and “actual profit.”

I use three exit types:

  1. Profit target - Close 50% of position at 2% profit, rest at 5% profit
  2. Stop loss - Close entire position if it drops 3% below entry
  3. Time decay - If a position hasn’t moved in 4 hours, close it (markets that aren’t moving are wasting capital)

The exit manager runs every minute, checks all open positions, and executes exits that meet criteria. It places exit orders as limit orders too, so we get the best available prices.

The Math on Asymmetric Position Sizing

Here’s why the split profit target works. Say I enter a position at $0.45 on a binary market with $100 per trade:

Winning trades (55% of the time):

  • Exit 50% at $0.459 (2% profit): +$0.90
  • Exit remaining 50% at $0.4725 (5% profit): +$2.25
  • Total on winner: +$3.15 (3.15% return on $100)

Losing trades (45% of the time):

  • Stop loss hits at $0.4365 (3% below entry): -$3.00
  • Total on loser: -$3.00 (-3% return on $100)

Expected value calculation:

EV = (0.55 × $3.15) + (0.45 × -$3.00)
EV = $1.73 + (-$1.35)
EV = +$0.38 per $100 bet

That’s positive EV at a 55% win rate. Drop to 54% and the math flips negative. This is why signal quality matters more than quantity. A 60% win rate turns $0.38 per $100 into $0.60 per $100. Signal strength compounds.

The split exit structure protects against reversal. If the market hits 2% and I’ve already cashed out half, the second half can either hit 5% or get stopped out. Either way, I’ve locked 50% of the winning outcome. That’s risk management.

The asymmetry is deliberate. I lose 3% on losers but capture 2-5% on winners because prices move differently on prediction markets. They don’t move in smooth linear paths. They bounce. A tight 1% stop gets triggered by noise. A 3% stop lets the position breathe.

State Tracking for Reliable Exits

The position database tracks exit status for each open position:

CREATE TABLE positions (
    id INTEGER PRIMARY KEY,
    market_id TEXT,
    entry_price REAL,
    entry_qty INTEGER,
    entry_time TEXT,
    stop_loss_price REAL,      -- 0.4365 for 0.45 entry
    target_one_price REAL,     -- 0.459 (2% profit)
    target_two_price REAL,     -- 0.4725 (5% profit)
    exit_status TEXT,          -- 'open', 'half_closed', 'closing', 'closed'
    target_one_filled_at TEXT,
    target_two_filled_at TEXT
);

On every minute, the exit manager reads current market price and compares against these thresholds. When target one hits, it updates exit_status to ‘half_closed’ and records the fill time. This prevents double-exits and ensures the second half of the position doesn’t exit prematurely.

Limit orders are crucial here. Market orders on Polymarket can slip 0.5-1% depending on order book depth. A limit order at target_one_price of $0.459 sits on the book until filled. If the market only reaches $0.458, the order stays open. If it bounces to $0.461, it fills at $0.459 (better than market order at $0.461). Over 100 trades, limit order precision saves 0.3-0.5% in aggregate slippage.

The real lesson: don’t set stop losses too tight on prediction markets. Binary outcomes mean prices bounce around more than equity markets. A 1% stop gets triggered by noise. 3% gives the position room to breathe while still protecting against real reversals.

Read my post on betting math for binary markets to understand the probability calculations that feed into exit sizing.

The trickiest exit is time decay. Prediction markets converge to 0% or 100% as the event approaches. If I’m long and the market isn’t moving, the time decay works against me. Exiting stale positions frees capital for new signals.

One thing that surprised me: the time decay exit generated more total profit than the profit target exit. Not because individual exits were bigger, but because freeing stale capital meant the bot could take 2-3 more trades per day. Capital velocity matters more than any single trade’s P&L.

Paper to Live Transition

I paper-traded for 6 weeks before going live. Paper trading means simulating trades without real money, just tracking P&L in spreadsheet.

Paper trading revealed two strategy failures:

  1. Liquidity trap - The culprit was insidious: entry prices looked good because I was catching markets in transition, when stale limit orders sat on the books. But exit? Nightmare. On markets with under < $50K order book depth, I’d win the entry at $0.45 then get forced out at $0.42 because no one was buying. The tight spread at entry reversed hard at exit. Across 30 paper trades, this cost pattern showed up 7 times. Each time: entry P&L looked profitable (+2-3%), but the exit slippage erased it. One trade: up $2.25 on 50% exit at 2% profit, then the final 50% couldn’t execute near target. Ended up closing at $0.41 (-4% from entry) because the order book evaporated. That single trade went from +$3 to -$1. Multiply that by 7 failed exits across the paper period, and I’m looking at roughly $2,000 in prevented losses once I added the liquidity check.

The fix was a simple gate before position entry:

async def check_market_liquidity(market_id: str) -> bool:
    """
    Only enter if order book has sufficient depth.
    Skip if spread > 1% of mid-price or total depth < threshold.
    """
    order_book = await polymarket.get_order_book(market_id)
    best_bid = order_book['bids'][0]['price']
    best_ask = order_book['asks'][0]['price']
    mid = (best_bid + best_ask) / 2
    spread_pct = ((best_ask - best_bid) / mid) * 100

    total_depth = sum(qty for _, qty in order_book['bids'][:5]) + 
                  sum(qty for _, qty in order_book['asks'][:5])

    if spread_pct > 1.0:  # Spread too wide
        return False
    if total_depth < 500:  # Not enough size to exit
        return False
    return True

This single filter would have prevented 7 bad trades and saved ~$2,000 in real losses.

  1. Signal timing - The second failure was time-dependent. Signals arriving during European market hours (roughly 2-8 AM UTC, when US traders sleep) got filled at punishment prices. Same signal, same market, but the order book was thin and slow-moving. I’d get a signal on BTC momentum at 5 AM UTC. By the time I placed the order, retail traders on Polymarket hadn’t woken up yet. Bid-ask spread was 0.5-1% instead of 0.2%. Fills were 200-300 basis points worse than signals arriving during US peak hours (12pm-11pm UTC). Over the 30 paper trades, only 6 arrived during European dead hours, but those 6 had 0.5-1% worse fills than identical signals during US hours. That’s roughly $1,500 in prevented slippage if I’d filtered those out.

The time-of-day filter was even simpler:

async def is_peak_trading_hour() -> bool:
    """
    Only process signals during peak US trading hours.
    12pm-11pm UTC captures most US market activity.
    """
    now_utc = datetime.datetime.utcnow()
    current_hour = now_utc.hour

    # Peak hours: 12pm-11pm UTC (7am-6pm EST)
    if 12 <= current_hour < 23:
        return True
    return False

async def should_process_signal(signal: dict) -> bool:
    if not await is_peak_trading_hour():
        return False  # Skip this signal
    return True

That’s it. One hour check prevented $1,500 in unnecessary slippage by avoiding markets where the book moves like molasses.

Both failures would have cost real money live. The 6-week cost in time was worth it.

I ran at least 30 paper trades before going live. That’s the minimum to see the major failure modes. Anything less and you’re just guessing.

Chudi Nnorukam

Written by Chudi Nnorukam

I build chudi.dev as a live public experiment in AI-visible web architecture: a website designed for human readers, LLM retrieval, and agent interoperability. My work spans WebMCP interfaces, machine-readable identity, technical writing, and the systems that make public knowledge legible to search and AI.

FAQ

What programming language is best for algorithmic trading?

Python is the most practical choice for individual traders. It has mature libraries for every trading need (asyncio for real-time feeds, aiohttp for API calls, SQLite for state), plus AI coding tools like Claude Code can write and debug Python trading systems autonomously.

How much money do you need to start algorithmic trading?

You can start paper trading with zero capital. For live Polymarket trading, $100-500 is enough to test strategies on binary prediction markets. The key is running at least 30 paper trades before risking real money.

Is algorithmic trading profitable for individuals?

It can be, but most retail algo traders lose money in their first year. The edge comes from finding inefficiencies in specific market niches (like prediction markets) rather than competing with institutional traders on major exchanges.

Sources & Further Reading

Sources

Further Reading

Discussion

Comments powered by GitHub Discussions coming soon.