DEV Community

Timevolt
Timevolt

Posted on

Building Your First Crypto Trading Bot: A Journey Like Neo in The Matrix

The Quest Begins (The "Why")

Honestly, I was tired of staring at candlestick charts at 2 a.m., feeling like I was playing a never‑ending game of whack‑a‑mole with my portfolio. Every time I thought I’d caught a break, the market would swing the other way and I’d end up chasing losses like a dog after its tail. I kept asking myself: Is there a way to let the computer do the heavy lifting while I grab some sleep?

That “aha!” moment came after I missed a 12 % spike on Bitcoin because I was busy debugging a totally unrelated script. I realized that if I could encode my simple strategy — buy when the price drops 2 % below the 20‑period moving average and sell when it rises 1.5 % above — into a bot, I could let it watch the markets 24/7 while I focused on anything else (like finally finishing that side‑project I keep postponing). The dragon I wanted to slay? Manual, emotion‑driven trading that was draining both my wallet and my sanity.

The Revelation (The Insight)

The real treasure wasn’t just “automate a trade”; it was understanding that a trading bot is essentially a state machine that reacts to market data, manages risk, and logs everything for later review. Once I saw the bot as a series of tiny, testable functions — fetch data, compute indicator, decide, execute, handle errors — the whole thing stopped feeling like a mystical black box and started feeling like a solvable puzzle.

It felt like I was trying to solve a puzzle in Portal — each piece had to fit perfectly, but once they did, the portal opened and I could walk through to the next level. The key insight? Separate concerns. Keep the data‑fetching layer independent from the strategy layer, and wrap the exchange API calls in a thin, retry‑friendly wrapper. That way, if the exchange hiccups (rate limits, network glitches), the bot doesn’t crash; it backs off, waits, and tries again.

Wielding the Power (Code & Examples)

Let’s look at the before and after. First, the naïve approach I started with — just a while loop that polled the price and placed market orders blindly:

# 🚫 Naïve version – don't do this!
import time
import ccxt

exchange = ccxt.binance()
symbol = 'BTC/USDT'

while True:
    ticker = exchange.fetch_ticker(symbol)
    price = ticker['last']
    ma = exchange.fetch_ohlcv(symbol, timeframe='15m', limit=20)
    ma_val = sum(c[4] for c in ma) / len(ma)   # close price average

    if price < ma_val * 0.98:      # 2% dip
        order = exchange.create_market_buy_order(symbol, 0.001)
        print(f"Bought at {price}")
    elif price > ma_val * 1.015:   # 1.5% rise
        order = exchange.create_market_sell_order(symbol, 0.001)
        print(f"Sold at {price}")

    time.sleep(30)
Enter fullscreen mode Exit fullscreen mode

What’s wrong here?

  1. No error handling – a single network blip kills the loop.
  2. Market orders can slip badly in volatile moments.
  3. No rate‑limit respect – Binance will ban you after a few hundred requests.
  4. No logging or persistence – you can’t review performance.

Now, the “after” – a clean, production‑ish skeleton that you can actually run and extend:

# ✅ Refined bot – core loop with safety nets
import time
import ccxt
import logging
from typing import Optional

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

class SimpleMaBot:
    def __init__(self, exchange_id: str = 'binance', symbol: str = 'BTC/USDT',
                 fast_ma: int = 20, buy_pct: float = 0.98, sell_pct: float = 1.015,
                 amount: float = 0.001):
        self.exchange = getattr(ccxt, exchange_id)({'enableRateLimit': True})
        self.symbol = symbol
        self.fast_ma = fast_ma
        self.buy_pct = buy_pct
        self.sell_pct = sell_pct
        self.amount = amount

    def fetch_ohlcv(self, limit: int) -> Optional[list]:
        try:
            return self.exchange.fetch_ohlcv(self.symbol, timeframe='15m', limit=limit)
        except Exception as e:
            log.warning(f"OHLCV fetch failed: {e}")
            return None

    def compute_ma(self, candles: list) -> float:
        closes = [c[4] for c in candles]   # close price
        return sum(closes) / len(closes)

    def run(self):
        while True:
            candles = self.fetch_ohlcv(self.fast_ma)
            if not candles:
                time.sleep(10)
                continue

            ma = self.compute_ma(candles)
            ticker = self.exchange.fetch_ticker(self.symbol)
            price = ticker['last']

            log.info(f"Price: {price:.2f} | MA({self.fast_ma}): {ma:.2f}")

            if price < ma * self.buy_pct:
                try:
                    order = self.exchange.create_limit_buy_order(
                        self.symbol, self.amount, price * 0.999)  # slight buffer
                    log.info(f"Buy order placed: {order['id']}")
                except Exception as e:
                    log.error(f"Buy failed: {e}")

            elif price > ma * self.sell_pct:
                try:
                    order = self.exchange.create_limit_sell_order(
                        self.symbol, self.amount, price * 1.001)
                    log.info(f"Sell order placed: {order['id']}")
                except Exception as e:
                    log.error(f"Sell failed: {e}")

            time.sleep(self.exchange.rateLimit / 1000)  # respect exchange limits

if __name__ == '__main__':
    bot = SimpleMaBot()
    bot.run()
Enter fullscreen mode Exit fullscreen mode

Why this feels like a win:

  • Rate‑limit aware – we instantiate the exchange with enableRateLimit: True and sleep rateLimit ms between calls.
  • Error resilient – each network call is wrapped in a try/except; failures are logged, not fatal.
  • Limit orders with a tiny buffer – reduces slippage compared to raw market orders.
  • Clear separation – fetching, indicator calculation, decision, and execution are distinct methods, making unit‑testing a breeze.

You can drop this into a file, run python bot.py, and watch it trade on Binance’s testnet (just switch the endpoint). From here, you can add stop‑loss, position sizing, or even a simple machine‑learning filter — each as another plug‑in module.

Why This New Power Matters

Now you’ve got a template that does more than just execute trades; it gives you a framework for experimentation. Want to test a new indicator? Swap out compute_ma. Curious about execution slippage? Adjust the limit‑order buffer. Because the core loop is clean, you can iterate fast without fear of taking down the whole system.

Imagine waking up to find your bot caught a 3 % dip you’d have missed while you were asleep, or watching it automatically tighten a stop‑loss during a flash crash. That’s the kind of freedom automation buys you: time to learn, to build, to live — while your code watches the charts.

Your next quest: Deploy this bot on a testnet, tweak one parameter, and log the results for a week. Did the bot improve your risk‑adjusted return? What broke, and how did you fix it? Share your findings — let’s turn this shared adventure into a community of bot‑building wizards.

Happy coding, and may your trades be ever in your favor!

Top comments (0)