Trading Bot Mistakes: 7 Implementation Errors That Cost Beginners Money
When an automated trading bot loses money, the cause is usually not a bad strategy but a wrong implementation. The reasons something that backtested beautifully falls apart live are almost always the same short list. Here are the seven that keep recurring.
⚠️ This article is for educational and informational purposes only. It is not investment advice, and any losses are your own responsibility.
1. Quietly Using Future Data (Look-Ahead Bias)
The most common and the most damaging. A backtest that computes a signal from today's close and fills at today's close does not exist in the real world — the close isn't final until the session ends.
# Wrong — buying today on a signal that requires knowing today's close
signal = df["close"] > df["ma20"]
df["position"] = signal.astype(int)
# Right — signal from yesterday, fill today
df["position"] = signal.shift(1).fillna(0).astype(int)
That one-line difference can change backtest returns by multiples. If your returns look unrealistically good, suspect this first.
2. Leaving Out Fees and Slippage
A strategy that trades several times a day can have its entire edge erased by fees alone. Add slippage — the gap between the price you asked for and the price you got — and it's worse.
FEE = 0.0005 # 0.05% per side (varies by exchange/broker)
SLIPPAGE = 0.0005 # assumed execution slippage
trades = df["position"].diff().abs() # a day the position changed = a trade
cost = trades * (FEE + SLIPPAGE)
df["net_ret"] = df["ret"] * df["position"] - cost
print(f"cumulative, net of cost: {(1 + df['net_ret']).prod() - 1:.2%}")
A strategy that doesn't survive costs in a backtest won't survive them live either. Don't defer this check.
3. Forcing Parameters to Fit the Data (Overfitting)
Sweep moving-average periods from 5 to 100 and pick the best one, and you haven't found a strategy — you've memorized coincidences in historical data.
# Warning sign: adjacent values swing wildly
# ma=19 -> +12% ma=20 -> +87% ma=21 -> +8%
# -> 20 isn't special; it just happened to land on a lucky stretch
A healthy parameter is gently good at neighboring values too. If one value spikes alone, discard it. And split your data: tune on the first 70% and leave the last 30% untouched as a verification set.
4. Calling APIs With No Exception Handling
The network will drop. The exchange will go into maintenance. With no exception handling, your bot dies while holding a position.
import time, requests
def safe_get(url, params=None, retries=3):
for i in range(retries):
try:
res = requests.get(url, params=params, timeout=10)
if res.status_code == 429: # rate limited
time.sleep(2 ** i) # back off progressively
continue
res.raise_for_status()
return res.json()
except requests.RequestException as e:
if i == retries - 1:
notify(f"API failed, halting bot: {e}") # alert a human
raise
time.sleep(2 ** i)
The point isn't the retry, it's that a human finds out when it fails. A bot that died silently is the most dangerous kind.
5. Duplicate Orders — Buying Twice on One Signal
You submit an order, the response is slow, your code decides it failed and submits again. Now you're in at double size.
# Check the current position before ordering
pos = get_position(symbol)
if pos["qty"] > 0:
log.info("already holding — skipping new entry")
return
# Prevent duplicates with a client order ID (where the exchange supports it)
client_id = f"{symbol}-{signal_date}-entry" # same signal, same ID
place_order(symbol, qty, client_order_id=client_id)
The rule is singular: check the real balance immediately before ordering. Trusting internal variables alone drifts out of sync on restarts and errors.
6. No Stop-Loss Rule
People pour effort into entry conditions and then finish the exit logic with "sell when it goes up." Without a rule, losses grow without bound.
MAX_LOSS_PCT = 0.02 # max 2% loss on a single trade
def position_size(capital, entry, stop):
risk_per_share = entry - stop
if risk_per_share <= 0:
raise ValueError("stop price is above entry price")
qty = (capital * MAX_LOSS_PCT) / risk_per_share
return int(qty)
# A wider stop automatically reduces the quantity
qty = position_size(10_000_000, entry=71_000, stop=68_000)
The question isn't "how much should I buy" but "how much do I lose if I'm wrong" — that ordering is the whole of money management.
7. Starting on a Live Account
The most expensive mistake of all. Code can look logically correct and the runtime environment will still surprise you.
The recommended sequence:
- Backtest — validate on historical data with costs included
- Paper trading — run on live quotes but log orders instead of sending them. Two to four weeks minimum
- Paper trading account — verify the real order flow
- Small live positions — start with money you can afford to lose
Step 2 catches an enormous number of bugs. Data delays, market-holiday handling, midnight rollovers, token expiry — none of them show up until you run in real time.
Summary
- Look-ahead bias — delay signals with
shift(1) - Costs — confirm an edge remains after fees and slippage
- Overfitting — it's only real if neighboring values work too
- Exception handling — stop and alert on failure
- Duplicate orders — check the real balance before ordering
- Stop-loss — set the loss limit first, then compute quantity
- No direct-to-live — paper trading is not optional
The goal of bot development isn't a spectacular strategy, it's a system that runs by the same rules again tomorrow. Avoid these seven and most accidents simply don't happen.
댓글
댓글 쓰기