💡 Domestic note: Binance’s official site isn’t directly accessible from mainland China—you’ll need a VPN or similar network tool. Fiat deposits don’t support bank cards directly; most funding happens through C2C (USDT↔CNY) channels instead.

Trader analyzing stock market data on smartphone and phone

In futures trading, the difference between a profitable strategy and a losing one often isn’t about whether your signals are right—it’s about whether you’re actually implementing risk management.

A solid entry signal paired with poor risk control will give back every penny of gains in a single big swing. This article covers the framework for futures risk management: which layers you need to control, and how to set each one up properly.

Three Levels of Risk Control

Level 1: Position Sizing — how much to open each trade
Level 2: Stop-Loss Setup — maximum loss per individual trade
Level 3: Circuit Breaker — forced pause after consecutive losses

You need all three. Position sizing controls per-trade risk exposure, stop-loss controls your maximum loss on any single trade, and the circuit breaker prevents continuous bleeding when your strategy breaks.

Level 1: Position Sizing

Position size each time = Account total equity × Fixed percentage

def calc_position_size(equity: float, risk_pct: float, leverage: int,
                        entry_price: float) -> float:
    """
    equity: account total equity (USDT)
    risk_pct: per-trade risk ratio, e.g., 0.02 = 2%
    leverage: leverage multiplier
    entry_price: entry price
    
    returns: contract quantity (BTCUSDT each = 0.001 BTC)
    """
    risk_amount = equity * risk_pct         # actual amount willing to lose
    notional = risk_amount * leverage       # corresponding notional value
    qty = notional / entry_price
    return qty

Why fixed ratio instead of fixed amount?

Fixed amounts (e.g., 1000 USDT each time) become riskier as your account shrinks and safer as it grows—backwards. Fixed ratio auto-adjusts with your account size, keeping risk proportional.

Reasonable risk percentages by stage:

Strategy PhaseRecommended Ratio
Testing (validating strategy)0.5% - 1%
Stable operation1% - 2%
High-confidence strategy2% - 3%
Not recommended above5%

At 5%+ per trade, five consecutive losses means 25%+ drawdown—emotionally brutal and hard for a strategy to recover from.

Kelly Criterion (Advanced)

If you have enough historical data, the Kelly Criterion calculates the theoretically optimal position size:

def kelly_fraction(win_rate: float, avg_win: float, avg_loss: float) -> float:
    """
    win_rate: historical win rate, e.g., 0.55
    avg_win: average win (as ratio), e.g., 0.015 = 1.5%
    avg_loss: average loss (as ratio, positive), e.g., 0.01 = 1%
    """
    b = avg_win / avg_loss  # win-to-loss ratio
    p = win_rate
    q = 1 - p
    kelly = (b * p - q) / b
    # use half-Kelly in practice to reduce volatility
    return max(0, kelly * 0.5)

The Kelly Criterion gives the position size that theoretically maximizes compounded growth. In practice, use “half Kelly”—it controls drawdown while still preserving solid growth.

Level 2: Stop-Loss Setup

There are two main approaches to stops:

Price-Based Stop-Loss

Set your stop-loss price at entry; close when price hits it.

def set_stop_loss(symbol: str, side: str, entry_price: float,
                   stop_pct: float, client) -> dict:
    """
    stop_pct: stop-loss distance, e.g., 0.02 = 2% from entry
    """
    if side == "BUY":
        stop_price = entry_price * (1 - stop_pct)
        sl_side = "SELL"
    else:
        stop_price = entry_price * (1 + stop_pct)
        sl_side = "BUY"

    # place STOP_MARKET order (close at market when trigger is hit)
    order = client.futures_create_order(
        symbol=symbol,
        side=sl_side,
        type="STOP_MARKET",
        stopPrice=round(stop_price, 2),
        closePosition=True,    # close entire position
        timeInForce="GTE_GTC"
    )
    return order

How to set stop-loss distance:

Common approaches:

  • ATR-based (Average True Range): stop-loss = 1–2x ATR, adapts to current volatility
  • Support/resistance levels: place at technically meaningful spots
  • Fixed percentage: simple and direct, easy to calculate

Recommend ATR because volatility varies wildly across market conditions. A fixed percentage is too loose in calm markets and too tight in volatile ones.

def calc_atr(highs, lows, closes, period=14):
    """calculate ATR"""
    true_ranges = []
    for i in range(1, len(closes)):
        tr = max(
            highs[i] - lows[i],
            abs(highs[i] - closes[i-1]),
            abs(lows[i] - closes[i-1])
        )
        true_ranges.append(tr)
    return sum(true_ranges[-period:]) / period

Trailing Stop-Loss

Adjust your stop with price movement, locking in floating gains:

def set_trailing_stop(symbol: str, callback_rate: float, client) -> dict:
    """
    callback_rate: callback percentage, e.g., 1.5 = 1.5%
    triggers when price pulls back 1.5% from its peak
    """
    order = client.futures_create_order(
        symbol=symbol,
        side="SELL",
        type="TRAILING_STOP_MARKET",
        callbackRate=callback_rate,
        quantity=your_position_quantity,
        reduceOnly=True
    )
    return order

Trailing stops suit trending markets; regular stops suit ranging markets.

Level 3: Circuit Breaker Mechanism

A circuit breaker forces a pause when your strategy hits consecutive losses, preventing account death-by-a-thousand-cuts.

class CircuitBreaker:
    def __init__(self,
                 max_daily_loss_pct: float = 0.05,    # max daily loss 5%
                 max_drawdown_pct: float = 0.15,       # max drawdown 15%
                 max_consecutive_losses: int = 5):     # max consecutive losses
        self.max_daily_loss_pct = max_daily_loss_pct
        self.max_drawdown_pct = max_drawdown_pct
        self.max_consecutive_losses = max_consecutive_losses
        self.consecutive_losses = 0
        self.peak_equity = None
        self.day_start_equity = None

    def update(self, current_equity: float) -> bool:
        """
        update state; returns True for normal, False for breaker triggered
        """
        if self.peak_equity is None:
            self.peak_equity = current_equity
        if self.day_start_equity is None:
            self.day_start_equity = current_equity

        # update peak
        if current_equity > self.peak_equity:
            self.peak_equity = current_equity

        # check max drawdown
        drawdown = (self.peak_equity - current_equity) / self.peak_equity
        if drawdown >= self.max_drawdown_pct:
            self._trigger("max drawdown triggered", drawdown)
            return False

        # check daily loss
        daily_loss = (self.day_start_equity - current_equity) / self.day_start_equity
        if daily_loss >= self.max_daily_loss_pct:
            self._trigger("daily loss triggered", daily_loss)
            return False

        # check consecutive losses
        if self.consecutive_losses >= self.max_consecutive_losses:
            self._trigger(f"{self.consecutive_losses} consecutive losses")
            return False

        return True

    def record_trade(self, pnl: float):
        """record a trade result"""
        if pnl < 0:
            self.consecutive_losses += 1
        else:
            self.consecutive_losses = 0  # reset on profit

    def _trigger(self, reason: str, value: float = 0):
        print(f"[circuit breaker] {reason}: {value:.2%}")
        # add Telegram alert here

Usage:

cb = CircuitBreaker(
    max_daily_loss_pct=0.05,
    max_drawdown_pct=0.15,
    max_consecutive_losses=5
)

# check before each strategy run
while True:
    equity = get_account_equity()
    if not cb.update(equity):
        send_telegram("strategy breaker triggered, pausing trades")
        break
    
    # execute strategy logic
    signal = strategy.check_signal()
    if signal:
        pnl = execute_trade(signal)
        cb.record_trade(pnl)

Complete Risk Management Integration Example

class RiskManager:
    def __init__(self, client, equity: float):
        self.client = client
        self.equity = equity
        self.circuit_breaker = CircuitBreaker()

    def open_position(self, symbol: str, side: str, entry_price: float,
                       risk_pct: float = 0.01, leverage: int = 5):
        """open position with risk control"""
        # 1. check circuit breaker
        if not self.circuit_breaker.update(self.equity):
            print("breaker triggered, rejecting position")
            return None

        # 2. calculate position size
        qty = calc_position_size(self.equity, risk_pct, leverage, entry_price)

        # 3. open position
        order = self.client.futures_create_order(
            symbol=symbol,
            side=side,
            type="MARKET",
            quantity=qty
        )

        # 4. set stop-loss synchronously (based on ATR)
        atr = self.get_atr(symbol)
        stop_dist_pct = (atr * 1.5) / entry_price
        set_stop_loss(symbol, side, entry_price, stop_dist_pct, self.client)

        return order

Common Risk Control Mistakes

No stop-loss: Most fatal, no debate needed.

Stop too tight: 0.3x ATR means frequent triggers and a falsely inflated win rate.

Stop too loose: 5x ATR means single losses exceed expectation and wreck your risk ratio.

Fixed position size: After losses, you’re not reducing size—losses compound.

Manually overriding the breaker: “Just this once”—everyone thinks that every time. A breaker you manually disable is no breaker at all.


Risk management isn’t a leash on returns—it’s the infrastructure that keeps your strategy alive long enough to run. Build it right, stay in the game long enough, and let any edge you have compound its expected gains over time.

→ See Binance API from signup to first trade → See Quant toolkit: Binance API + Grafana + automated alerts → See Polaris strategy intro


📷 Cover image · Flux Schnell AI generated · panshi.io brand style