DEV Community

Timevolt
Timevolt

Posted on

Building Trading Systems Like a Jedi: Avoiding Common Pitfalls

The Quest Begins (The "Why")

I still remember the first time I tried to spin up a crypto‑trading bot. I was fresh out of a hackathon, buzzing with excitement, and I thought, “How hard can it be? Grab a price ticker, send an order, repeat.” I fired up my laptop, cracked open a Python file, and started typing like I was casting a spell.

Thirty minutes later, the bot was live— and then it blew up. My account got hit with a cascade of rejected orders, I’d accidentally sent a market buy for 10 BTC instead of 0.01, and the exchange started throttling me like an angry dragon guarding its hoard. I stared at the screen, heart pounding, and thought, “What just happened? I felt like I’d just faced the final boss in a Dark Souls game and got smashed by a single swing.”

That moment kicked off a quest: I needed to slay the dragons of sloppy code, hidden assumptions, and over‑confidence before they burned my trading account to ash. Along the way I discovered a handful of mistakes that keep tripping up even seasoned devs. Let’s walk through them, see the wreckage they cause, and then forge a sturdier blade.

The Revelation (The Insight)

The biggest insight? Trading systems aren’t just about the algorithm; they’re about the infrastructure that surrounds it.

If your code can’t talk reliably to the exchange, handle errors gracefully, or keep numbers precise, the fanciest signal generator is just a fancy paperweight.

Think of the exchange as a cantina in Mos Eisley: it’s full of weird characters, strict dress codes, and a bouncer who will throw you out if you step on the wrong toe. Your bot needs to know the rules, respect the rate limits, and keep its ledger clean.

Once I internalized that, the bugs started to disappear like fog under a twin‑sun sunset. Below are the two most common traps I fell into (and saw others fall into) and the exact code changes that turned them from quest‑ending catastrophes into minor speed bumps.

Wielding the Power (Code & Examples)

Trap #1 – Hard‑coding Secrets & Ignoring Rate Limits

The Struggle (Before)

# 🚫 DON'T DO THIS
API_KEY = "sk_live_abcdef1234567890"      # oops, committed to GitHub!
API_SECRET = "my_super_secret"

import time, requests

def get_price():
    resp = requests.get(
        "https://api.example.com/v1/ticker/btcusd",
        headers={"X-API-Key": API_KEY, "X-API-Secret": API_SECRET}
    )
    return resp.json()["price"]

def place_order(size, side):
    payload = {"symbol": "BTCUSD", "size": size, "side": side}
    resp = requests.post(
        "https://api.example.com/v1/order",
        json=payload,
        headers={"X-API-Key": API_KEY, "X-API-Secret": API_SECRET}
    )
    return resp.json()

while True:
    price = get_price()
    if price < 30000:
        place_order(0.01, "buy")
    time.sleep(0.1)          # naive sleep – no respect for limits
Enter fullscreen mode Exit fullscreen mode

What’s wrong?

  • Secrets in source – one slip and your key is public.
  • No rate‑limit awareness – the exchange may allow only 5 requests per second; sleeping 0.1 s means 10 req/s → instant ban.
  • No error handling – a network hiccup crashes the loop.

The Victory (After)

# ✅ DO THIS INSTEAD
import os, time, logging, requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

# Load from environment – never commit!
API_KEY = os.getenv("EXCHANGE_API_KEY")
API_SECRET = os.getenv("EXCHANGE_API_SECRET")
if not API_KEY or not API_SECRET:
    raise RuntimeError("Missing exchange credentials in env")

# Session with retry + back‑off
session = requests.Session()
retry = Retry(
    total=5,
    backoff_factor=0.5,          # 0.5s, 1s, 2s, 4s …
    status_forcelist=[429, 500, 502, 503, 504],
    allowed_methods=["GET", "POST"]
)
session.mount("https://", HTTPAdapter(max_retries=retry))

HEADERS = {"X-API-Key": API_KEY, "X-API-Secret": API_SECRET}

def get_price():
    resp = session.get(
        "https://api.example.com/v1/ticker/btcusd",
        headers=HEADERS,
        timeout=5
    )
    resp.raise_for_status()
    return float(resp.json()["price"])

def place_order(size, side):
    payload = {"symbol": "BTCUSD", "size": size, "side": side}
    resp = session.post(
        "https://api.example.com/v1/order",
        json=payload,
        headers=HEADERS,
        timeout=5
    )
    resp.raise_for_status()
    return resp.json()

# Respect a conservative rate limit: 4 req/sec → 250ms between calls
MIN_INTERVAL = 0.25
last_call = 0

def rate_limited(fn):
    def wrapper(*args, **kwargs):
        nonlocal last_call
        elapsed = time.time() - last_call
        if elapsed < MIN_INTERVAL:
            time.sleep(MIN_INTERVAL - elapsed)
        result = fn(*args, **kwargs)
        last_call = time.time()
        return result
    return wrapper

get_price = rate_limited(get_price)
place_order = rate_limited(place_order)

# Main loop – now safe and observable
logging.basicConfig(level=logging.INFO)
while True:
    try:
        price = get_price()
        logging.info(f"Current BTC/USD: {price:.2f}")
        if price < 30000:
            order = place_order(0.01, "buy")
            logging.info(f"Buy order placed: {order['orderId']}")
    except Exception as e:
        logging.exception(f"Something went wrong: {e}")
    # small sleep to avoid busy‑waiting; rate limiter does the heavy lifting
    time.sleep(0.05)
Enter fullscreen mode Exit fullscreen mode

Why this works

  • Secrets live in the environment (or a secret manager) – no accidental leaks.
  • A requests.Session with Retry handles transient errors and backs off automatically.
  • The rate_limited decorator guarantees we never exceed the exchange’s request ceiling, turning a potential ban into a smooth, predictable flow.
  • Structured logging lets us see what’s happening in production without shouting into the void.

Trap #2 – Floating‑Point Math for Money

The Struggle (Before)

# 🚫 DON'T DO THIS
def calculate_profit(entry_price, exit_price, size):
    gross = (exit_price - entry_price) * size
    fee = gross * 0.001          # 0.1% taker fee
    return gross - fee

# Example usage
profit = calculate_profit(30000.0, 30100.0, 0.00012345)
print(profit)   # 1.2299999999999998 ???
Enter fullscreen mode Exit fullscreen mode

Floating‑point binary fractions can’t represent decimal cents exactly, so after a few trades your P&L drifts. In a high‑frequency system that drift becomes a costly accounting error—or worse, a regulatory red flag.

The Victory (After)

# ✅ DO THIS
from decimal import Decimal, getcontext

# Set enough precision for crypto (8‑10 decimal places is common)
getcontext().prec = 28

def calculate_profit(entry_price, exit_price, size):
    entry = Decimal(str(entry_price))
    exit_ = Decimal(str(exit_price))
    sz = Decimal(str(size))

    gross = (exit_ - entry) * sz
    fee = gross * Decimal("0.001")      # 0.1%
    return gross - fee

# Example usage
profit = calculate_profit(30000, 30100, "0.00012345")
print(profit)   # 1.230000000000000000000000000
Enter fullscreen mode Exit fullscreen mode
  • By converting inputs to Decimal from strings we avoid binary floating‑point artefacts.
  • The getcontext().prec ensures we keep enough digits for the smallest tick size the exchange offers.
  • The result is exact to the last satoshi (or wei, or whatever the base unit is).

Why This New Power Matters

When you stop treating the exchange like a wild west saloon and start respecting its rules, your bot becomes reliable enough to run unattended for days, weeks, or months. You’ll see:

  • Fewer bans – rate‑limit compliance keeps the API doors open.
  • Cleaner accounting – decimal math means your profit‑and‑loss matches the exchange’s statements down to the last tick.
  • Peace of mind – proper retries and logging turn mysterious crashes into actionable diagnostics.

In short, you go from “I hope this works” to “I know this works, and I can prove it.” That confidence lets you focus on refining your strategy instead of firefighting infrastructure bugs.

Your Turn – The Challenge

Pick one of the traps above (or another you’ve battled) and refactor a snippet of your own trading code. Share the before/after in the comments, or tweet a link to your gist with the hashtag #JediTradingCode. Let’s see who can turn their bot into the most polished lightsaber in the galaxy!

May your orders be filled, your limits never hit, and your profits always decimal‑exact. Happy coding! 🚀

Top comments (0)