08. The Art of Not Going Bankrupt

The Art of Not Going Bankrupt

No Matter How Good the Model Is

When I first saw the backtest results for v2.5, I was fairly confident. Win rate 68.51%, MDD 6.96%. Not bad numbers. But when I put it into live trading, reality told a different story.

Whether the model was right or wrong, a single large loss wiped out ten small wins. When stop-losses triggered in a row, my composure crumbled, and when composure crumbles, you want to shut the system off. Even though it's fully automated.

No matter how good the model is, it will blow up without risk management.

That statement only truly hit home after I watched my own money shrink in real time. In v2.6 I redesigned the risk management system from scratch, and in v2.7 the results proved it worked. MDD 3.62%, 13 consecutive months of positive returns.


Core Principles of Risk Management

Earlier posts covered the mathematical definitions of key metrics like the expected return formula, Sharpe Ratio, MDD, and Profit Factor. This post focuses on application rather than definitions. What I concentrated on when designing the risk management system was one thing:

Not maximizing returns, but building a structure where losses are controllable.

In the expected return formula $E[R] = WR \cdot \overline{Win} - (1-WR) \cdot \overline{Loss}$, $\overline{Win}$ depends on the market, but $\overline{Loss}$ can be controlled by the system. And the smaller the MDD, the easier the recovery, a 3.62% loss only requires 3.76% to recover, while a 50% loss requires 100%. This asymmetry is the reason risk management exists.


Circuit Breaker: Defending Against Consecutive Losses

Just as the stock market has circuit breakers, I built one into my system. The core logic is simple.

Consecutive Loss Cooldown

When 3 consecutive stop-losses trigger, trading is paused. Not just a simple 1-hour break, I applied exponential backoff.

$$T_{cooldown} = T_0 \cdot 2^{n-1}$$

Here, $T_0$ is the base cooldown time (1 hour) and $n$ is the cooldown level. First trigger: 1 hour. If another 3-loss streak happens after cooldown: 2 hours. Another one: 4 hours. Maximum 8 hours.

# Exponential backoff implementation
cooldown_seconds = base_cooldown * (2 ** cooldown_level)
cooldown_seconds = min(cooldown_seconds, 8 * 3600)  # Cap at 8 hours

Why exponential backoff? This concept is borrowed from network collision resolution algorithms (Ethernet's Binary Exponential Backoff). Consecutive losses are almost always a signal that market conditions have turned unfavorable for the model. In these situations, it makes sense to progressively increase rest time and wait for the market to normalize.

Think of it like job interviews. After the first rejection, you take a week off. Another rejection, two weeks. Another, a month. If it keeps not working out, "maybe this just isn't my season", resting longer is the more efficient approach.

The reason I chose exponential growth ($T = T_0 \cdot 2^{n-1}$) over linear growth ($T = T_0 \cdot n$) is that the significance of consecutive failures is nonlinear. A 3-loss streak could be coincidence, but a 6-loss streak (two consecutive cooldown triggers) strongly suggests a regime change in the market. In that case, you need to rest much longer.

The crucial part is that a single win resets everything immediately. If the model starts getting it right again, that means market conditions have returned.

if is_win:
    self.consecutive_losses = 0
    self.cooldown_until = None
    self.cooldown_level = 0  # Full level reset

Daily Loss Limit

A circuit breaker alone isn't enough. Some days, small losses quietly pile up throughout the day.

# 6 daily losses OR cumulative 15% drawdown -> stop trading for the day
V26_DAILY_MAX_LOSSES = 6
V26_DAILY_MAX_LOSS_PCT = 15.0  # Relative to margin

Six losses and you're done for the day. Or if cumulative losses exceed 15% of margin. The counter resets at UTC midnight. These numbers came from my own experience. Getting the 7th trade right on a day where you've already been wrong 6 times in a row is close to a miracle.

In actual backtests, the circuit breaker triggered 98 times. Only 1.4% of total trade rejections, but without those 98 instances, the drawdown would have been much worse.


Trailing Stop: The Art of Protecting Profits

Limiting losses is only half the equation, protecting profits is equally important. The scenario that caused me the most pain:

Position hits +12% unrealized gain, then reverses sharply to a -4% stop-loss.

To prevent this, I added a trailing stop.

# Trailing stop activates at +8% unrealized PnL (relative to margin)
TRAILING_STOP_ACTIVATION = 8.0  # %

# After activation, close position if PnL drops 3% from peak
TRAILING_STOP_DISTANCE = 3.0    # %

Here's how it works:

  1. Enter a position
  2. When PnL reaches +8% (relative to margin), the trailing stop activates
  3. From that point, it continuously tracks the peak PnL
  4. If it drops 3% from the peak, close immediately

For example, if PnL goes +8% -> +12% -> +15%, the peak is 15%. If it then drops to 12% (15 - 12 = 3%), the trailing stop triggers. Profit realized at +12%.

Let's consider the trailing stop's Risk-Reward Ratio. The Risk-Reward Ratio is defined as:

$$RRR = \frac{\text{Expected Profit}}{\text{Maximum Risk}}$$

At the point of trailing stop activation, the minimum profit is $8\% - 3\% = 5\%$ (worst case: price drops 3% immediately after activation at the peak), and the maximum risk is effectively 0 (since you're already in profit territory). After activation, it's a structure that locks in profit with zero risk of loss.

It's like setting up base camps while climbing a mountain. Once you reach the 8,000-foot mark (+8%), you set up camp. You keep climbing higher, but if you drop more than 3,000 feet from your highest point, you descend to that camp. At minimum, you've secured the altitude of 8,000 - 3,000 = 5,000 feet.

In backtests, 298 trades were closed by the trailing stop, with a 100% win rate and average PnL of +5.87%. A total of $270,734 in profits was preserved. Without this feature, a significant portion of those profits would have evaporated.


ATR-Based Dynamic Stop-Loss

The problem with a fixed stop-loss is straightforward. Using the same percentage regardless of market volatility means you get stopped out too easily on volatile days and too late on quiet days.

That's why I designed a dynamic stop-loss based on ATR (Average True Range). Since I covered the mathematical definition of ATR in Part 7, I'll focus on the application here.

Mathematical definition of the dynamic stop-loss:

$$SL_{dynamic} = \text{clamp}\left(ATR \times M, \; SL_{floor}, \; SL_{cap}\right)$$

Here, $M$ is the ATR multiplier, $SL_{floor}$ is the floor (4%), and $SL_{cap}$ is the ceiling. The clamp function constrains the value within a range.

Specifically:

$$M \in [M_{min}, M_{max}] = [0.75, 2.0]$$

$$SL_{dynamic} = \max\left(ATR\% \times M, \; 4.0\%\right)$$

ATR_SL_MIN_MULTIPLIER = 0.75   # Minimum ATR 0.75x
ATR_SL_MAX_MULTIPLIER = 2.0    # Maximum ATR 2.0x
ATR_SL_BASE_PERCENT = 0.01     # Base value 1%
ATR_SL_HARD_FLOOR = 4.0        # Absolute minimum 4% (relative to margin)

When ATR is high (volatile market), the stop-loss widens; when ATR is low (quiet market), it tightens. But it never goes below 4% of margin. Without this hard floor, you'd get stopped out by micro noise in very quiet markets.

Think of it like packing an umbrella. On days with a high chance of rain, you bring a big umbrella (wide SL), and on clear days, a compact one (tight SL). But even on the sunniest day, you still keep a folding umbrella in your bag. That's the 4% hard floor.

In the initial v2.6, I had the minimum multiplier set to 0.5, but the SL was so tight that normal price movements kept triggering it. This can be explained statistically. Assuming 5-minute candle price movements roughly follow a normal distribution, an SL of $0.5 \times ATR$ (about $0.5\sigma$) has approximately a 38% chance of getting clipped by normal noise. Raising it to $0.75 \times ATR$ reduces that probability to about 23%. Things only stabilized after I raised it to 0.75 and added the 4% hard floor.


Hard Floor and Hard Cap

Every dynamic system needs safety guardrails.

# Stop-loss hard floor: minimum 4% under all circumstances
ATR_SL_HARD_FLOOR = 4.0

# Take-profit hard cap: force close at 30% of margin
TP_HOLD_HARD_CAP = 30.0

# Profit floor: close if profit drops below 10%
TP_HOLD_FLOOR = 10.0

# Maximum holding time: 120 minutes
TP_HOLD_MAX_DURATION = 120

The 30% hard cap is a "don't get greedy" safety net. In backtests, 2 trades actually hit the hard cap, with an average PnL of +29.2%. A position that's climbed to 30% can always reverse, so locking in profits here is the right call.


Position Sizing and Time Filters

Conservative Sizing and the Kelly Criterion

POSITION_SIZE = 10      # 10% of margin
MAX_POSITIONS = 1       # 1 position at a time
LEVERAGE = 10           # 10x leverage

I use only 10% of margin, with a maximum of 1 simultaneous position. At 10x leverage with 10% margin, I'm effectively risking 1% of total capital per trade. Conservative, yes, but this conservatism is what produced an MDD of 3.62%.

The theoretical optimum for position sizing is given by the Kelly Criterion.

$$f^* = \frac{p \cdot b - q}{b}$$

Here, $f^*$ is the optimal bet fraction (of total capital), $p$ is the win rate, $q = 1 - p$ is the loss rate, and $b$ is the ratio of win size to loss size (the odds).

Plugging in v2.7's numbers: $p = 0.735$, $q = 0.265$, $b = \frac{\overline{Win}}{\overline{Loss}} \approx \frac{5.87}{3.2} \approx 1.83$.

$$f^* = \frac{0.735 \times 1.83 - 0.265}{1.83} \approx \frac{1.346 - 0.265}{1.83} \approx 0.59$$

The Kelly Criterion says to bet 59% of your capital. But in practice, nobody actually uses the full Kelly fraction.

It's like loading your plate at an all-you-can-eat buffet. Kelly says "fill up to 59% of your stomach capacity," but if you actually do that, you'll get a stomachache. What if your actual capacity is smaller than you estimated? Going half (Half Kelly) is the realistic approach.

There are two reasons for this.

First, Kelly assumes you know $p$ and $b$ exactly, but in practice they're just estimates. When there's estimation error, the full Kelly fraction leads to excessive risk.

Second, Kelly optimizes the expected value (expected log return), but ignores the drawdowns that occur along the way. Using Full Kelly maximizes long-term returns, but you might experience drawdowns of -50% or worse in the process.

In practice, most people use Half Kelly ($f = f^*/2$) or Quarter Kelly ($f = f^*/4$). My system's effective bet fraction of about 1% (10% margin x leverage adjustment) is roughly 1/60th of Kelly. Extremely conservative, but this conservatism produced the 3.62% MDD.

The single-position limit seems simple but is powerful. It completely eliminates the scenario of two highly correlated positions getting stopped out simultaneously.

Time Filter

Not all hours are suitable for trading.

# Exclude UTC 0-3 and 20-23
EXCLUDED_HOURS_UTC = [0, 1, 2, 3, 20, 21, 22, 23]

UTC 0-3 (KST 9 AM-12 PM) and 20-23 (KST 5-8 AM) are off-limits. These hours have low liquidity, wider spreads, and erratic price movements.

In backtests, 2,781 trades (39.2%) were rejected by the time filter, the second-largest reason for trade rejection. That's essentially sitting out a third of each day, but that third prevents unnecessary losses.


Defense Layer Summary

My risk management system is composed of multiple layers of defense. This follows the Defense in Depth principle from software engineering. If any single layer fails, the next one catches it.

Layer Mechanism Role Mathematical Basis
1 Time filter Avoid low-liquidity hours Time-of-day distribution of liquidity and spreads
2 Circuit breaker Defend against streaks / daily limits Exponential backoff $T_0 \cdot 2^{n-1}$
3 ATR dynamic SL Volatility-adaptive stop-loss $SL = \max(ATR \times M, 4\%)$
4 Hard floor SL Guarantee minimum stop-loss width Noise filtering probability threshold
5 Trailing stop Preserve profits Guaranteed RRR > 1 after activation
6 Hard cap TP Force take-profit at extremes Tail risk limitation
7 Position sizing Limit single-trade risk Conservative application of Kelly Criterion

What I'm truly proud of from the v2.7 results shown in Part 6 is the 3.62% MDD. More than the 73.5% win rate, a maximum drawdown under 4% is the real achievement of the risk management system.


What I Learned

The biggest lesson from designing the risk management system was that minimizing losses matters more than maximizing gains.

The math backs this up. Looking at the expected return formula $E[R] = WR \cdot \overline{Win} - (1-WR) \cdot \overline{Loss}$, there are two ways to increase $E[R]$: increase $\overline{Win}$ or decrease $\overline{Loss}$. But in practice, reducing $\overline{Loss}$ is far more reliable. $\overline{Win}$ depends heavily on market conditions, while $\overline{Loss}$ can be controlled by the system.

And risk management isn't a one-and-done exercise. Most of the initial parameter values were wrong. It took multiple backtests to discover that the ATR minimum multiplier of 0.5 was too tight, and the 4% hard floor number was also the product of trial and error.

Building the system takes a month, but tuning the parameters never ends. Still, this work is the most important part. When the model gets it wrong, what protects you in the end is risk management.


Lightweighting for Production: A Model That Runs on a Celeron

After completing the risk management system, a new problem emerged. The production server was too weak.

The only machine I could dedicate to running an automated trading system 24/7 was a Celeron J4125 mini PC sitting in the corner. The LSTM ensemble with TensorFlow took several seconds per prediction on this hardware. In 5-minute candle trading, a multi-second delay is fatal.

So I built v2.5 Lite. I completely removed TensorFlow and kept only the XGBoost + LightGBM ensemble. The "paradox of model simplification" from Part 6 was proven in production. Two tree models were more than enough without LSTM. In fact, prediction speed improved 10x, reducing latency and slippage.

A good model doesn't make money. A model that answers on time does.

During this lightweighting process, I also readjusted the risk management parameters. With a weak server, the probability of system failures is higher, so I made position sizing even more conservative and the circuit breaker more sensitive. The irony was that hardware constraints ended up producing a safer system.