Back

Multi-venue trading bot

A paper-trading bot on Base where every order passes a risk manager with a kill switch.

DeFi trading R&D · Blockchain Advisors internship

What it's for

A trading system on Base, where a bad order is not a bug report, it is money gone, and there is nobody to call afterwards.

What it is

An R&D trading bot for WETH/USDC and cbBTC/USDC on Base, routing through Aerodrome with a Uniswap v3 fallback. Rule-based, moving-average and ML strategies share one interface, every order passes risk checks, fills are simulated with fees and slippage, and swaps were tested on the Base Sepolia testnet.

How it works

  1. Market data DEX Screener, on-chain pool reads, CCXT 01
  2. Market state One shared shape, optional TimescaleDB 02
  3. Strategies Range, moving-average cross, logistic regression 03
  4. Risk manager Position, exposure, drawdown and daily-loss caps 04
  5. Paper fills Fees and slippage modelled 05
  6. Live gate web3.py signing, testnet only 06

Engineering decisions

Risk limits are enforced in code rather than in config, with a tested kill switch.
Per-user wallet keys are encrypted with Fernet, and a gas-aware guard skips trades that wouldn't pay for themselves.
Model inference runs as its own FastAPI service with Prometheus metrics, and CI enforces over 80% test coverage.

Built with

Pythonweb3.pyCCXTFastAPITimescaleDBPrometheus

Case study

On authorship. This was an internship on a team repository, and most of the commits are under another developer’s name. What follows describes the system, not who wrote each part of it, which is why this project is listed on the site as R&D engineering on the prototype rather than anything stronger.

Most trading bots are written in the exciting order: strategy first, risk controls bolted on once real money is involved. That order is how accounts die, because the safety layer arrives after the habits have formed around not having one.

This one was built the other way round.

Where the limits live

Every order, from every strategy, passes through one risk manager: position size, total exposure, drawdown, daily loss, total capital, plus a global kill switch with a test covering it.

Not in a config file, in the code path. A limit that lives only in configuration stops existing the moment something loads the wrong file, and on-chain there is no support line to call afterwards.

Around that:

  • Keys encrypted at rest with Fernet, per user, rather than sitting in environment variables.
  • A gas-aware guard that skips trades whose expected edge is smaller than the gas to execute them. On-chain, that trade is a loss wearing a signal’s clothes.
  • Startup reconciliation, because a bot that restarts with a stale idea of its own positions is worse than one that’s switched off.
  • CI failing under 80% coverage, which for software that can move money is a floor, not an accomplishment.

One contract, three strategies

A rule-based range strategy, a moving-average cross and a logistic-regression model all implement the same interface, so they can be swapped and compared on identical conditions. Market data arrives from DEX Screener, on-chain pool reads or a synthetic feed and is normalised into one shape before any strategy sees it.

Execution runs on Base through Aerodrome with a Uniswap v3 fallback, behind a live gate, signing with web3.py. Swaps were exercised on the Sepolia testnet. No mainnet trades were placed.

What the backtests actually showed

On synthetic data, 400 ticks, one fixed seed:

StrategyReturnTradesSharpeMax drawdown
Rule-based range+8.68%122.221.73%
Moving-average cross−15.44%25−2.5117.09%
ML momentum (placeholder)−19.44%52−2.7319.44%
Buy and hold+0.16%10.1311.05%

Here’s the honest reading, and it’s the reason the table is on the page at all: +8.68% over 12 trades with a 100% win rate is not a strategy, it’s an anecdote. Twelve trades on generated data is far too small a sample to conclude anything, and a 100% win rate is a warning sign rather than a selling point. What the table does establish is that the harness works, that fees and slippage are modelled, and that the placeholder ML model traded the most and lost the most, which is exactly what a placeholder should be allowed to do before anyone trusts it.

Parameter sensitivity moved the range strategy between +7.5% and +10.6%, so it isn’t balanced on a knife edge. That’s the most that can be said.

What I’d fix first

Real market data instead of synthetic, and enough trades for the numbers to mean something. Until then the interesting output of this project isn’t a return figure, it’s the risk layer, and the fact that it was there first.