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:

  1. Backtest — validate on historical data with costs included
  2. Paper trading — run on live quotes but log orders instead of sending them. Two to four weeks minimum
  3. Paper trading account — verify the real order flow
  4. 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.

댓글

이 블로그의 인기 게시물

한국투자증권 KIS API로 실시간 시세 받기 (WebSocket 실전)

파이썬으로 업비트 API 연동하기 — 시세 조회부터 주문까지 기초

Go로 자동매매 신호봇 프레임워크 설계하기