The Quest Begins (The "Why")
Honestly, I still remember the first time I watched my algo blow up a simulated account in under five minutes. I had spent weeks polishing a mean‑reversion strategy, back‑tested it to death, and felt like Neo dodging bullets—until the market decided to throw a curveball that looked nothing like the training data. My position size was a flat 10% of equity on every trade, and my stop loss? A vague “let it run until it hurts” mindset. The result? A cascade of margin calls that left me staring at red numbers and wondering if I’d just invented a new way to lose money faster than a speedrun in Dark Souls.
That painful episode forced me to ask: What if I could control the damage before it even starts? Not by predicting the future (spoiler: we can’t), but by sizing each bet intelligently and placing a hard stop that respects both my capital and my sanity. The answer wasn’t some secret sauce; it was the humble, yet powerful, duo of position sizing and stop‑loss rules.
The Revelation (The Insight)
The breakthrough came when I treated each trade like a small investment in a portfolio, not a gamble. Two ideas clicked:
- Risk per trade – Decide beforehand how much of my equity I’m willing to lose on any single position. A common rule of thumb is 1%–2% per trade.
- Stop‑loss distance – Use the market’s volatility (e.g., ATR) to set a stop that gives the trade room to breathe, yet caps the loss if things go south.
When you combine them, the position size becomes a simple fraction:
position_size = (risk_per_trade * equity) / stop_distance
If the stop is wider (high volatility), you automatically shrink the size; if the market is calm, you can afford a larger stake. This dynamic sizing keeps your risk constant in dollar terms, regardless of how wild the price action gets.
I also learned to avoid the trap of moving stops after entry—the “let’s see if it comes back”—because that turns a predefined risk into an open‑ended gamble. A stop is a contract with yourself; honor it.
Wielding the Power (Code & Examples)
Below is a before‑and‑after snippet in Python (using pandas and ta for ATR). The “before” version uses a fixed fraction and a static stop; the “after” version applies the volatility‑aware formula.
Before: Fixed size, static stop
import pandas as pd
def generate_signals(df):
# Simple mean‑reversion signal: price < SMA(20) → long
df['signal'] = 0
df.loc[df['close'] < df['sma_20'], 'signal'] = 1
return df
def backtest_fixed(df, equity=100_000, fixed_frac=0.10, stop_pct=0.02):
df = generate_signals(df)
df['position'] = 0.0
df['equity'] = equity
df['peak'] = equity
for i in range(1, len(df)):
# entry
if df.loc[i-1, 'signal'] == 1 and df.loc[i-1, 'position'] == 0:
df.loc[i, 'position'] = fixed_frac * equity / df.loc[i, 'close']
entry_price = df.loc[i, 'close']
stop_price = entry_price * (1 - stop_pct)
# exit on stop or signal reversal
if df.loc[i, 'position'] != 0:
# stop hit?
if df.loc[i, 'low'] <= stop_price:
df.loc[i, 'position'] = 0
pnl = (stop_price - entry_price) * df.loc[i-1, 'position']
# signal flips?
elif df.loc[i, 'signal'] == 0:
df.loc[i, 'position'] = 0
pnl = (df.loc[i, 'close'] - entry_price) * df.loc[i-1, 'position']
else:
pnl = 0
df.loc[i, 'equity'] = df.loc[i-1, 'equity'] + pnl
df.loc[i, 'peak'] = max(df.loc[i-1, 'peak'], df.loc[i, 'equity'])
else:
df.loc[i, 'equity'] = df.loc[i-1, 'equity']
df.loc[i, 'peak'] = df.loc[i-1, 'peak']
return df
What’s wrong here?
- The
fixed_fracof 10% ignores volatility; during a choppy session the same dollar amount can swing wildly. - The stop is a fixed 2% below entry, which may be too tight in a high‑volatility market (getting stopped out prematurely) or too loose in a calm one (exposing more capital than needed).
After: Volatility‑adjusted size & ATR stop
import pandas as pd
import ta
def generate_signals(df):
df['signal'] = 0
df.loc[df['close'] < df['sma_20'], 'signal'] = 1
return df
def add_atr(df, period=14):
df['atr'] = ta.volatility.average_true_range(df['high'], df['low'], df['close'], window=period)
return df
def backtest_vol(df, equity=100_000, risk_per_trade=0.01, atr_period=14, atr_multiplier=2.0):
df = generate_signals(df)
df = add_atr(df, atr_period)
df['position'] = 0.0
df['equity'] = equity
df['peak'] = equity
for i in range(1, len(df)):
if df.loc[i-1, 'signal'] == 1 and df.loc[i-1, 'position'] == 0:
entry_price = df.loc[i, 'close']
stop_distance = atr_multiplier * df.loc[i, 'atr'] # e.g., 2 * ATR
stop_price = entry_price - stop_distance
# position size so that loss = risk_per_trade * equity if stop hit
df.loc[i, 'position'] = (risk_per_trade * equity) / stop_distance
# exit logic
if df.loc[i, 'position'] != 0:
entry_price = df.loc[i-1, 'close'] # we stored entry price implicitly via position calc
stop_distance = atr_multiplier * df.loc[i, 'atr']
stop_price = entry_price - stop_distance
if df.loc[i, 'low'] <= stop_price:
df.loc[i, 'position'] = 0
pnl = (stop_price - entry_price) * df.loc[i-1, 'position']
elif df.loc[i, 'signal'] == 0:
df.loc[i, 'position'] = 0
pnl = (df.loc[i, 'close'] - entry_price) * df.loc[i-1, 'position']
else:
pnl = 0
df.loc[i, 'equity'] = df.loc[i-1, 'equity'] + pnl
df.loc[i, 'peak'] = max(df.loc[i-1, 'peak'], df.loc[i, 'equity'])
else:
df.loc[i, 'equity'] = df.loc[i-1, 'equity']
df.loc[i, 'peak'] = df.loc[i-1, 'peak']
return df
Why this feels like leveling up:
- The stop now breaths with the market (ATR), so you’re not getting whipsawed by noise.
- Position size automatically shrinks when volatility spikes, keeping your dollar risk steady at 1% of equity.
- The code is still short enough to drop into a backtest loop, yet it captures the core principle: risk first, size second.
Why This New Power Matters
Adopting this approach transformed my trading from a rollercoaster of emotions to a disciplined process. I could now sleep at night knowing that even a string of losers would only nibble at my equity, not devour it. The equity curve smoothed out, drawdowns shrank, and the Sharpe ratio crept up—all without changing the underlying signal.
More importantly, the mindset shift spreads beyond algo trading. Whenever you’re allocating capital—whether it’s time, money, or effort—thinking in terms of pre‑defined loss and adjusting exposure gives you a safety net that lets you pursue bigger opportunities without fearing catastrophic loss.
Your Turn: Embark on Your Own Quest
Try swapping the fixed fraction in your own strategy for the volatility‑sized version above. Play with the risk_per_trade (0.5 %‑2 %) and atr_multiplier (1.5‑3) to see how the equity curve reacts.
Challenge: Run a walk‑forward analysis on a symbol of your choice, compare the fixed‑size vs. ATR‑sized versions, and share the biggest surprise you discover in the comments.
Happy hunting, and may your stops be ever in your favor!
Top comments (0)