DEV Community

Timevolt
Timevolt

Posted on

Build a Crypto Trading Bot Like Neo in *The Matrix*: Your First Automation

The Quest Begins (The "Why")

Honestly, I was tired of staring at charts at 2 a.m., wondering if I should hit “buy” or “sell” while my coffee went cold. I’d tried manual trading a few times, made a profit here, a loss there, and always ended up feeling like I was playing a rigged slot machine. The moment that flipped the switch? I missed a 12 % swing on Bitcoin because I was busy debugging a unrelated script. I thought, “If only I could automate this… like a cheat code.”

That’s when I decided to build my first crypto trading bot. Not some black‑box hedge‑fund monster, but a simple, transparent script that could watch the market, execute a basic strategy, and free me up to actually enjoy life (or at least get a decent night’s sleep).

The Revelation (The Insight)

The big insight? You don’t need a PhD in quant finance to get started. A handful of ideas—price data, a simple indicator, and an order‑placement API—are enough to create something that works today. The real magic is in the loop: fetch data → compute signal → place order → wait → repeat.

I chose the popular ccxt library because it normalizes dozens of exchanges under one clean Python interface. With it, I could write the same code for Binance, Kraken, or Coinbase Pro and swap them out with a single line change.

The strategy I went with is a classic moving‑average crossover: when the short‑term MA crosses above the long‑term MA, we buy; when it crosses below, we sell. It’s not going to make you a whale overnight, but it’s a perfect sandbox to learn about order execution, error handling, and rate limits—those pesky “boss levels” that trips up many beginners.

Like when Neo dodges bullets, we need to dodge rate limits.

If you ignore them, the exchange will happily return HTTP 429 errors and your bot will grind to a halt—definitely not the hero moment you’re after.

Wielding the Power (Code & Examples)

Below is the before version—what I wrote the first night, full of optimism and a few hidden traps.

# before.py – naïve bot (don’t run this as‑is!)
import time
import ccxt

exchange = ccxt.binance({'enableRateLimit': True})  # we set it, but we don’t actually respect it
symbol = 'BTC/USDT'
short_window = 10
long_window = 30

def get_ohlcv():
    return exchange.fetch_ohlcv(symbol, timeframe='1h', limit=long_window)

def compute_mas(ohlcv):
    closes = [c[4] for c in ohlcv]  # close price
    short_ma = sum(closes[-short_window:]) / short_window
    long_ma  = sum(closes[-long_window:])  / long_window
    return short_ma, long_ma

while True:
    ohlcv = get_ohlcv()
    short_ma, long_ma = compute_mas(ohlcv)
    ticker = exchange.fetch_ticker(symbol)
    price = ticker['last']

    if short_ma > long_ma and not position:
        print(f'🚀 BUY at {price}')
        exchange.create_market_buy_order(symbol, 0.001)  # tiny test size
        position = True
    elif short_ma < long_ma and position:
        print(f'🔻 SELL at {price}')
        exchange.create_market_sell_order(symbol, 0.001)
        position = False

    time.sleep(60)  # wait a minute
Enter fullscreen mode Exit fullscreen mode

What went wrong?

  1. Rate‑limit ignorance – Even though we passed enableRateLimit: True, we never checked exchange.rateLimit or handled the occasional 429. After a few hundred calls, Binance would start returning errors, and our bot would crash.
  2. Missing error handling – Network hiccups, exchange maintenance, or a malformed response would raise an uncaught exception and stop the loop.
  3. Hard‑coded order size – 0.001 BTC is fine for a testnet, but on mainnet it could be too big or too small depending on your balance.

The after version – a battle‑tested bot

# after.py – resilient bot with proper rate‑limit handling
import time
import ccxt
import logging

logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s')
log = logging.getLogger(__name__)

exchange = ccxt.binance({
    'enableRateLimit': True,
    'options': {'defaultType': 'future'},  # adjust if you want spot/futures
})

symbol = 'BTC/USDT'
short_window = 12
long_window = 26
trade_amount = 0.001   # adjust to your risk tolerance
position = False       # simple in‑memory flag; replace with persistence for production

def safe_fetch_ohlcv():
    """Fetch OHLCV while respecting rate limits and handling errors."""
    try:
        return exchange.fetch_ohlcv(symbol, timeframe='1h', limit=long_window)
    except (ccxt.NetworkError, ccxt.ExchangeError) as e:
        log.warning(f'Transient error fetching OHLCV: {e}')
        return None

def compute_mas(ohlcv):
    closes = [c[4] for c in ohlcv]
    short_ma = sum(closes[-short_window:]) / short_window
    long_ma  = sum(closes[-long_window:])  / long_window
    return short_ma, long_ma

def place_order(side, amount):
    try:
        order = exchange.create_market_order(symbol, side, amount)
        log.info(f'{side.upper()} order placed: {order["id"]}')
        return order
    except (ccxt.InsufficientFunds, ccxt.InvalidOrder) as e:
        log.error(f'Order failed: {e}')
    except ccxt.NetworkError as e:
        log.warning(f'Network issue on order: {e}')
    except ccxt.ExchangeError as e:
        log.error(f'Exchange error on order: {e}')
    return None

def main_loop():
    global position
    while True:
        ohlcv = safe_fetch_ohlcv()
        if ohlcv is None:
            time.sleep(exchange.rateLimit / 1000)  # back off a bit
            continue

        short_ma, long_ma = compute_mas(ohlcv)
        ticker = exchange.fetch_ticker(symbol)
        price = ticker['last']

        log.info(f'Price: {price:.2f} | Short MA: {short_ma:.2f} | Long MA: {long_ma:.2f} | Position: {"LONG" if position else "FLAT"}')

        if short_ma > long_ma and not position:
            log.info('🚀 Signal: BUY')
            place_order('buy', trade_amount)
            position = True
        elif short_ma < long_ma and position:
            log.info('🔻 Signal: SELL')
            place_order('sell', trade_amount)
            position = False
        else:
            log.info('⏳ No action this cycle.')

        # Sleep respecting the exchange's rate limit (in ms)
        time.sleep(exchange.rateLimit / 1000)

if __name__ == '__main__':
    main_loop()
Enter fullscreen mode Exit fullscreen mode

Why this feels like a win:

  • The safe_fetch_ohlcv wrapper catches network glitches and respects the exchange’s built‑in rate limit (exchange.rateLimit).
  • Logging replaces noisy prints, giving you timestamps and severity levels—essential when you leave the bot running overnight.
  • Order placement is isolated, so a bad order doesn’t kill the whole loop.
  • The core logic (MA crossover) stays unchanged, proving you can iterate on safety without reinventing the wheel.

Why This New Power Matters

Now you have a template that you can morph into anything:

  • Swap the MA crossover for RSI, MACD, or even a machine‑learning signal.
  • Persist position in a tiny SQLite DB or Redis so survivable restarts don’t lose state.
  • Add risk‑management layers: stop‑loss, take‑profit, position sizing based on account equity.
  • Deploy it on a cheap VPS, a Raspberry Pi, or a serverless function (just remember to keep the loop alive).

The biggest shift? You’re no longer reacting to the market; you’re letting a disciplined script do the heavy lifting while you focus on strategy, learning, or actually sleeping. It’s like upgrading from a wooden sword to a lightsaber—same goal, far less fatigue.

Your Turn

Ready to forge your own bot? Grab the code above, plug in your API keys (testnet first!), and see what happens when the short MA kisses the long MA. Tweak one thing—maybe the timeframe, maybe the trade size—and observe how the bot’s behavior changes.

Challenge: After you get the basic loop running, add a simple stop‑loss that triggers if the price drops 2 % below your entry price. Share your results (or your struggles) in the comments—I’d love to hear how your quest went!

Happy hacking, and may your trades be ever in your favor. 🚀

Top comments (0)