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
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)
Why this works
- Secrets live in the environment (or a secret manager) – no accidental leaks.
- A
requests.SessionwithRetryhandles transient errors and backs off automatically. - The
rate_limiteddecorator 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 ???
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
- By converting inputs to
Decimalfrom strings we avoid binary floating‑point artefacts. - The
getcontext().precensures 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)