DEV Community

Timevolt
Timevolt

Posted on

The Crypto Bot: A New Hope

The Quest Begins (The "Why")

Honestly, I was tired of staring at candlestick charts at 2 a.m., trying to decide whether to buy that dip or wait for a breakout. Every time I thought I’d nailed a pattern, the market would flip like a plot twist in Inception, and I’d end up chasing losses instead of profits. I kept asking myself: Is there a way to let a computer do the heavy lifting while I grab some sleep?

That “aha!” moment came after a particularly brutal weekend where I missed a 15 % surge because I was busy debugging a unrelated script. I realized I wasn’t missing the opportunity—I was missing the automation. If I could encode my simple rule‑based strategy into a bot, I could let it watch the markets 24/7 and execute trades exactly when my criteria were met. The dragon I was slaying? Fatigue and indecision.

The Revelation (The Insight)

The secret sauce turned out to be embarrassingly simple: a moving‑average crossover. When the short‑term average crosses above the long‑term average, we have a bullish signal; the opposite crossover is bearish. No fancy AI needed—just a clear, testable rule that works surprisingly well in sideways‑to‑trending markets.

What blew my mind was how little code it actually took to turn that idea into a live trader. Using the ccxt library (which abstracts away the differences between exchanges) and a tiny scheduler, I could fetch OHLCV data, compute the averages, and place market orders in under a minute of runtime. The real revelation? The bot didn’t need to be perfect—it just needed to be consistent. Consistency beats occasional brilliance every time.

Wielding the Power (Code & Examples)

Below is the “before” version of my trading routine: a manual script I’d run whenever I felt like it.

# before.py – manual check (don’t do this in production!)
import ccxt
import time

exchange = ccxt.binance({
    'apiKey': 'YOUR_KEY',
    'secret': 'YOUR_SECRET',
    'enableRateLimit': True,
})

def check_signal():
    ohlcv = exchange.fetch_ohlcv('BTC/USDT', timeframe='1h', limit=50)
    closes = [c[4] for c in ohlcv]
    short_ma = sum(closes[-5:]) / 5   # 5‑period SMA
    long_ma  = sum(closes[-20:]) / 20 # 20‑period SMA
    if short_ma > long_ma:
        print("📈 Bullish – consider buying")
    elif short_ma < long_ma:
        print("📉 Bearish – consider selling")
    else:
        print("➖ No clear signal")

while True:
    check_signal()
    time.sleep(60)   # poll every minute
Enter fullscreen mode Exit fullscreen mode

Traps I fell into:

  1. Hard‑coded API keys – committing them to a repo is like giving away the One Ring.
  2. No error handling – a network hiccup would crash the loop, leaving the bot blind.
  3. Rate‑limit ignorance – Binance will ban you if you hammer the endpoint without respecting its limits.

Here’s the “after” version – a cleaned‑up, production‑ready bot that avoids those pitfalls.

# bot.py – a simple MA‑crossover trading bot
import os
import time
import ccxt
import logging
from dotenv import load_dotenv

load_dotenv()  # pulls API_KEY and API_SECRET from .env (never commit this file!)

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

exchange = ccxt.binance({
    'apiKey': os.getenv('API_KEY'),
    'secret': os.getenv('API_SECRET'),
    'enableRateLimit': True,   # lets ccxt respect the exchange limits
})

SYMBOL = 'BTC/USDT'
TIMEFRAME = '1h'
SHORT_WINDOW = 5
LONG_WINDOW = 20
TRADE_AMOUNT = 0.001  # BTC per trade – adjust to your risk tolerance

def fetch_ohlcv():
    return exchange.fetch_ohlcv(SYMBOL, timeframe=TIMEFRAME,
                                limit=LONG_WINDOW + 1)

def compute_ma(prices, window):
    return sum(prices[-window:]) / window

def run_bot():
    while True:
        try:
            ohlcv = fetch_ohlcv()
            closes = [c[4] for c in ohlcv]   # closing prices
            short_ma = compute_ma(closes, SHORT_WINDOW)
            long_ma  = compute_ma(closes, LONG_WINDOW)

            if short_ma > long_ma and not getattr(run_bot, 'in_long', False):
                logging.info(f'Bullish crossover – short={short_ma:.2f}, long={long_ma:.2f}')
                exchange.create_market_buy_order(SYMBOL, TRADE_AMOUNT)
                run_bot.in_long = True
                run_bot.in_short = False

            elif short_ma < long_ma and not getattr(run_bot, 'in_short', False):
                logging.info(f'Bearish crossover – short={short_ma:.2f}, long={long_ma:.2f}')
                exchange.create_market_sell_order(SYMBOL, TRADE_AMOUNT)
                run_bot.in_short = True
                run_bot.in_long = False

            else:
                logging.debug(f'No action – short={short_ma:.2f}, long={long_ma:.2f}')

        except ccxt.NetworkError as e:
            logging.warning(f'Network issue: {e}')
        except ccxt.ExchangeError as e:
            logging.error(f'Exchange error: {e}')
        except Exception as e:
            logging.exception(f'Unexpected error: {e}')

        time.sleep(60)   # respect the 1‑hour candle; adjust as needed

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

Why this works:

  • API keys stay out of source via .env and python‑dotenv.
  • Rate‑limit handling is delegated to ccxt (enableRateLimit=True).
  • Simple state flags (in_long, in_short) prevent stacking orders on every tick.
  • Logging gives you a clear audit trail without flooding the console.

Run it with python bot.py and watch it log crossovers and place orders—all while you grab coffee or binge‑watch your favorite show.

Why This New Power Matters

Now you’ve got a repeatable, emotion‑free executor for your strategy. Instead of second‑guessing every tick, you can focus on improving the rule: maybe add a volatility filter, incorporate RSI, or even layer multiple timeframes. The bot becomes a platform for experimentation, not a one‑off script.

More importantly, you’ve reclaimed time and mental energy. The market will still surprise you—just like a boss fight in Dark Souls—but now you have a reliable companion swinging the sword alongside you.


Your turn: Fork the repo, replace SYMBOL and TRADE_AMOUNT with your preferred pair and risk level, and see how the bot behaves on a paper‑trading account (most exchanges offer a testnet). What rule will you encode next? Share your tweaks or questions in the comments—I’d love to hear about your own automation quest! 🚀

Top comments (0)