Entries, exits & trade management
Entries get the glory; exits pay the bills. Define exact stops, targets and time-based exits, see how much a good exit changes the result, and assemble a complete entry-plus-exit rule set as one signal function.
Amateurs obsess over entries. Professionals know a hard truth: the exit determines the return. You can enter almost randomly and still be profitable with disciplined exits; you can have a brilliant entry and give it all back by holding too long or bailing too early. This lesson is about the other 90% of the trade — the part that decides how much of your edge you actually keep.
The four ways a trade ends
- Stop-loss — the price that proves your idea wrong. Not "the most I can stomach losing" but a *structural* level: below the range you broke out of, beyond the swing that would invalidate the setup. Placed with logic, not with fear.
- Take-profit — a target where you bank the win. Often set as a multiple of the risk (e.g. 2R = twice the stop distance) or at a structural level like the opposite Bollinger band.
- Trailing stop — a stop that ratchets in your favour, letting winners run while locking in gains. The trend follower’s best friend; it turns the rare big winner into a *captured* big winner.
- Time exit — close after N bars regardless. If the move has not happened by then, the hypothesis has quietly expired and the position is just risk with no thesis. Underrated and powerful.
How much does the exit matter?
It is easy to underestimate. Take one fixed entry rule and run it with three different exits over the same trades — a fixed target, a wider target, and a trailing stop. The entries are identical; only the exit changes. The equity curves diverge dramatically:
The tight target wins slightly more often but caps every winner at a pittance; over enough trades it barely beats costs. The trailing stop lets the occasional monster trend run, and that fat right tail is where the money is. This is the whole reason exits dominate: they shape the distribution of outcomes, not just the average.
Scaling in and out
You do not have to treat a position as all-or-nothing. Scaling trades size against conviction and lets you manage a position as new information arrives:
- Scaling in — add to a position as it confirms (e.g. a second unit after a trend extends). Pyramiding into winners is a hallmark of trend systems — but only ever add to *winners*, never to losers.
- Scaling out — take partial profit at a first target and let the rest ride under a trailing stop. This banks a guaranteed win, removes the pressure to be perfect, and still keeps upside if the move continues.
A complete rule set as one function
Let us assemble everything into a single, honest trade manager: a breakout entry with an ATR-based stop, a 2R take-profit, a trailing stop, and a time-based exit. This is what a *finished* strategy looks like — not a signal, a complete plan expressed as code. Study how every exit is checked on every bar, and how the stop ratchets but never loosens.
import numpy as np
import pandas as pd
def atr(df: pd.DataFrame, n: int = class="n">14) -> pd.Series:
class="s">""class="s">"Average True Range — the volatility unit we size stops in."class="s">""
prev = df[class="s">"close"].shift(class="n">1)
tr = pd.concat([
df[class="s">"high"] - df[class="s">"low"],
(df[class="s">"high"] - prev).abs(),
(df[class="s">"low"] - prev).abs(),
], axis=class="n">1).max(axis=class="n">1)
return tr.rolling(n).mean()
def managed_breakout(df: pd.DataFrame, entry=class="n">20, atr_n=class="n">14,
stop_mult=class="n">2.0, target_mult=class="n">4.0, max_bars=class="n">30) -> pd.DataFrame:
class="s">""class="s">"Long-only breakout with a complete exit stack:
structural entry, ATR stop, 2R target, trailing stop, time stop."class="s">""
out = df.copy()
out[class="s">"upper"] = out[class="s">"high"].rolling(entry).max().shift(class="n">1)
out[class="s">"atr"] = atr(out, atr_n)
close, high, low = out[class="s">"close"].values, out[class="s">"high"].values, out[class="s">"low"].values
upper, atr_v = out[class="s">"upper"].values, out[class="s">"atr"].values
position = np.zeros(len(out))
holding = class="n">0
entry_px = stop = target = trail = np.nan
bars_held = class="n">0
for i in range(len(out)):
if holding == class="n">0:
class="c"># ENTRY: close breaks the N-bar high, with valid ATR
if close[i] > upper[i] and not np.isnan(atr_v[i]):
holding = class="n">1
entry_px = close[i]
stop = entry_px - stop_mult * atr_v[i] class="c"># initial stop
target = entry_px + target_mult * atr_v[i] class="c"># 2R-style target
trail = stop
bars_held = class="n">0
else:
bars_held += class="n">1
class="c"># Ratchet the trailing stop up (never down) as price makes highs
trail = max(trail, high[i] - stop_mult * atr_v[i])
hard_stop = max(stop, trail)
hit_stop = low[i] <= hard_stop class="c"># stopped out
hit_target = high[i] >= target class="c"># target reached
timed_out = bars_held >= max_bars class="c"># thesis expired
if hit_stop or hit_target or timed_out:
holding = class="n">0 class="c"># any exit closes the trade
position[i] = holding
class="c"># Shift so we act on the bar AFTER the signal — no look-ahead.
out[class="s">"position"] = pd.Series(position, index=out.index).shift(class="n">1).fillna(class="n">0)
return out
What you now know
You can state a hypothesis as a real edge, compile it into precise boolean conditions, and build both trend and mean-reversion strategies in working pandas. You can filter a raw signal by regime, volatility and session so it only fires where its edge lives, resolve conflicting rules into a single target position, and — most importantly — run a complete trade with a stop, a target, a trailing stop and a time exit that together decide how much of your edge you actually keep.
You have a strategy. The unanswered question is whether it is *real* or whether you have fooled yourself. Next up, Part 5: building a backtester that tells you the truth — with costs, slippage, and none of the lies a naive simulation whispers in your ear.