Skip to content
Back to App

Risk Management in Code

Risk management is the single most important component of any trading strategy. A mediocre entry signal with excellent risk management will outperform a brilliant entry signal with poor risk management every time. This tutorial shows you how to implement professional-grade risk controls in Python.

The Risk Management Stack

Professional algorithmic traders implement risk controls at multiple layers:

  1. Per-trade risk — Stop loss and take profit on every position
  2. Daily limits — Maximum loss per day, maximum trades per day
  3. Streak protection — Stop after N consecutive losses
  4. Position sizing — Risk a fixed percentage of capital per trade
  5. Session filters — Only trade during high-liquidity sessions

We will implement each one.


1. Stop Loss and Take Profit

The most basic risk control: set a maximum loss and profit target for every trade.

Fixed-Tick Stop Loss

SL_TICKS = 20 # 5 points on NQ (1 tick = 0.25 points)
TP_RATIO = 2.0 # Risk:reward ratio
# On entry:
entry_price = bar["c"]
if position == "LONG":
stop_loss = entry_price - (SL_TICKS * 0.25) # 5 points below entry
take_profit = entry_price + (SL_TICKS * 0.25 * TP_RATIO) # 10 points above entry
elif position == "SHORT":
stop_loss = entry_price + (SL_TICKS * 0.25)
take_profit = entry_price - (SL_TICKS * 0.25 * TP_RATIO)

Checking SL/TP in the Loop

# Inside the main loop, BEFORE entry logic:
if position == "LONG":
if bar["l"] <= stop_loss:
place_order(ACCOUNT_ID, CONTRACT_ID, 1, 1) # Sell to close
pnl = stop_loss - entry_price
print(f"[SL HIT] @ {stop_loss:.2f} | P&L: {pnl:+.2f} pts")
position = None
elif bar["h"] >= take_profit:
place_order(ACCOUNT_ID, CONTRACT_ID, 1, 1)
pnl = take_profit - entry_price
print(f"[TP HIT] @ {take_profit:.2f} | P&L: {pnl:+.2f} pts")
position = None
elif position == "SHORT":
if bar["h"] >= stop_loss:
place_order(ACCOUNT_ID, CONTRACT_ID, 0, 1) # Buy to close
pnl = entry_price - stop_loss
print(f"[SL HIT] @ {stop_loss:.2f} | P&L: {pnl:+.2f} pts")
position = None
elif bar["l"] <= take_profit:
place_order(ACCOUNT_ID, CONTRACT_ID, 0, 1)
pnl = entry_price - take_profit
print(f"[TP HIT] @ {take_profit:.2f} | P&L: {pnl:+.2f} pts")
position = None

TestMax supports bracket orders — attach SL and TP directly to the order so the engine handles execution. This is more accurate than manual checking because the engine evaluates tick-by-tick:

SL_TICKS = 20
TP_TICKS = int(SL_TICKS * TP_RATIO) # 40 ticks = 10 points
result = place_order(
ACCOUNT_ID, CONTRACT_ID, 0, 1, # Buy 1 contract
sl_ticks=SL_TICKS, # Stop loss: 20 ticks from entry
tp_ticks=TP_TICKS # Take profit: 40 ticks from entry
)

For a deep dive on bracket orders, see Bracket Orders (SL/TP).

ATR-Based Stop Loss

Fixed-tick stops do not account for varying volatility. An ATR-based stop adapts:

class ATR:
def __init__(self, period=14):
self.period = period
self.value = None
self._prev_close = None
self._tr_buffer = []
def update(self, high, low, close):
if self._prev_close is not None:
tr = max(high - low, abs(high - self._prev_close), abs(low - self._prev_close))
if self.value is None:
self._tr_buffer.append(tr)
if len(self._tr_buffer) == self.period:
self.value = sum(self._tr_buffer) / self.period
self._tr_buffer = None
else:
self.value = (self.value * (self.period - 1) + tr) / self.period
self._prev_close = close
return self.value
atr = ATR(14)
# In your loop:
atr_val = atr.update(bar["h"], bar["l"], bar["c"])
# Set SL at 1.5x ATR from entry
if atr_val:
sl_distance = atr_val * 1.5
if position == "LONG":
stop_loss = entry_price - sl_distance
elif position == "SHORT":
stop_loss = entry_price + sl_distance

2. Daily Loss Limit

Stop trading for the day if cumulative losses exceed a threshold. This prevents a bad strategy from draining the account on a single day.

MAX_DAILY_LOSS = 1000 # Stop trading if daily losses exceed $1,000
current_day = ""
daily_pnl = 0.0
daily_locked = False
# At the top of your main loop:
bar_day = bar["t"][:10]
if bar_day != current_day:
if current_day:
print(f"[INFO] Day {current_day} ended | P&L: ${daily_pnl:+,.2f}")
current_day = bar_day
daily_pnl = 0.0
daily_locked = False
# After each trade closes:
daily_pnl += trade_pnl_dollars
# Check daily limit:
if daily_pnl <= -MAX_DAILY_LOSS and not daily_locked:
daily_locked = True
if position is not None:
close_all_positions(ACCOUNT_ID, CONTRACT_ID)
position = None
print(f"[RISK] Daily loss limit hit: ${daily_pnl:,.2f} — no more trades today")
# In your entry logic, add this guard:
if position is not None or daily_locked:
continue # Skip entry logic

3. Maximum Trades Per Day

Prevents overtrading. Even if your strategy signals 50 trades in a day, cap it:

MAX_TRADES_PER_DAY = 10
daily_trades = 0
# Reset with daily_pnl at the top of the loop (same day-change logic)
# Before entry:
if daily_trades >= MAX_TRADES_PER_DAY:
if not daily_locked:
daily_locked = True
print(f"[RISK] Max trades ({MAX_TRADES_PER_DAY}) reached for today")
continue
# After each trade:
daily_trades += 1

4. Consecutive Loss Streak Protection

If you lose N trades in a row, something is wrong — the market conditions do not suit your strategy today. Stop and wait for the next day.

MAX_LOSS_STREAK = 3
daily_loss_streak = 0
# After a losing trade:
if trade_pnl < 0:
daily_loss_streak += 1
if daily_loss_streak >= MAX_LOSS_STREAK and not daily_locked:
daily_locked = True
if position is not None:
close_all_positions(ACCOUNT_ID, CONTRACT_ID)
position = None
print(f"[RISK] {daily_loss_streak} consecutive losses — skipping rest of day")
else:
daily_loss_streak = 0 # Reset on a win

5. Position Sizing

Instead of always trading 1 contract, size your position based on how much capital you are willing to risk:

RISK_PER_TRADE = 0.02 # Risk 2% of account per trade
def calculate_position_size(balance, risk_pct, sl_distance_points, tick_value=5.0):
"""Calculate number of contracts based on risk.
For NQ: 1 tick = 0.25 points, $5 per tick, so $20 per point.
For ES: 1 tick = 0.25 points, $12.50 per tick, so $50 per point.
"""
dollar_risk = balance * risk_pct
risk_per_contract = sl_distance_points * (tick_value / 0.25) # Convert points to dollars
if risk_per_contract <= 0:
return 1
size = int(dollar_risk / risk_per_contract)
return max(1, size) # Minimum 1 contract
# Usage:
balance = get_account(ACCOUNT_ID)["balance"]
sl_distance = atr_val * 1.5 # Points
size = calculate_position_size(balance, RISK_PER_TRADE, sl_distance, tick_value=5.0)
result = place_order(ACCOUNT_ID, CONTRACT_ID, 0, size)
print(f"[BUY] {size} contracts | Risk: ${balance * RISK_PER_TRADE:,.0f} ({RISK_PER_TRADE*100:.0f}%)")

Complete Risk-Managed Strategy Template

Here is a full strategy template that includes all five risk controls. Use this as the starting point for every strategy you build:

#!/usr/bin/env python3
"""Risk-Managed Strategy Template — TestMax Algo Playground"""
import os, time
# --- Setup ---
ACCOUNT_ID = int(os.environ.get("ACCOUNT_ID", "0"))
CONTRACT_ID = os.environ.get("CONTRACT_ID", "")
TOTAL_BARS = int(os.environ.get("TOTAL_BARS", "5000"))
STEP_DELAY = float(os.environ.get("STEP_DELAY", "0.02"))
SPEED_FILE = os.environ.get("SPEED_FILE", "")
# --- Risk Parameters ---
SL_TICKS = 20 # Stop loss in ticks
TP_RATIO = 2.0 # Take profit as multiple of SL
MAX_DAILY_LOSS = 1000 # Dollar daily loss limit
MAX_TRADES_PER_DAY = 10 # Max entries per day
MAX_LOSS_STREAK = 3 # Consecutive losses before stopping
print(f"[INFO] Risk Profile: SL={SL_TICKS}t | TP={TP_RATIO}:1 | Max Loss=${MAX_DAILY_LOSS}/day")
print("-" * 50)
# --- State ---
position = None
entry_price = 0.0
trade_count = 0
wins = 0
current_day = ""
daily_pnl = 0.0
daily_trades = 0
daily_locked = False
daily_loss_streak = 0
balance_before_trade = 50000
try:
for i in range(TOTAL_BARS):
bar = get_next_bar()
if bar is None:
break
read_speed()
if STEP_DELAY > 0:
time.sleep(STEP_DELAY)
price = bar["c"]
# --- Daily Reset ---
bar_day = bar["t"][:10]
if bar_day != current_day:
if current_day:
print(f"[DAY] {current_day} | P&L: ${daily_pnl:+,.2f} | Trades: {daily_trades}")
current_day = bar_day
daily_pnl = 0.0
daily_trades = 0
daily_locked = False
daily_loss_streak = 0
# --- Check if bracket order closed our position ---
if position is not None and i % 5 == 0:
positions = get_positions(ACCOUNT_ID)
if len(positions) == 0:
acct = get_account(ACCOUNT_ID)
current_balance = acct["balance"] if acct else balance_before_trade
pnl_usd = current_balance - balance_before_trade
daily_pnl += pnl_usd
trade_count += 1
daily_trades += 1
if pnl_usd >= 0:
wins += 1
daily_loss_streak = 0
print(f"[TP] @ ~{price:.2f} | P&L: ${pnl_usd:+,.0f}")
else:
daily_loss_streak += 1
print(f"[SL] @ ~{price:.2f} | P&L: ${pnl_usd:+,.0f} | Streak: {daily_loss_streak}L")
position = None
# --- Daily Loss Limit ---
if daily_pnl <= -MAX_DAILY_LOSS and not daily_locked:
daily_locked = True
if position:
close_all_positions(ACCOUNT_ID, CONTRACT_ID)
position = None
print(f"[RISK] Daily loss limit: ${daily_pnl:,.2f}")
if daily_trades >= MAX_TRADES_PER_DAY and not daily_locked:
daily_locked = True
print(f"[RISK] Max trades reached: {daily_trades}")
if daily_loss_streak >= MAX_LOSS_STREAK and not daily_locked:
daily_locked = True
if position:
close_all_positions(ACCOUNT_ID, CONTRACT_ID)
position = None
print(f"[RISK] Loss streak: {daily_loss_streak}")
# --- Skip if locked or in position ---
if position is not None or daily_locked:
continue
# ============================================
# YOUR ENTRY LOGIC GOES HERE
# Replace the condition below with your signal
# ============================================
should_buy = False # Replace with your buy condition
should_sell = False # Replace with your sell condition
if should_buy:
tp_ticks = int(SL_TICKS * TP_RATIO)
result = place_order(ACCOUNT_ID, CONTRACT_ID, 0, 1,
sl_ticks=SL_TICKS, tp_ticks=tp_ticks)
if result.get("orderId") or result.get("success"):
position = "LONG"
entry_price = price
acct = get_account(ACCOUNT_ID)
balance_before_trade = acct["balance"] if acct else balance_before_trade
sl = price - SL_TICKS * 0.25
tp = price + tp_ticks * 0.25
print(f"[BUY] Bar {i} @ {price:.2f} | SL: {sl:.2f} TP: {tp:.2f}")
elif should_sell:
tp_ticks = int(SL_TICKS * TP_RATIO)
result = place_order(ACCOUNT_ID, CONTRACT_ID, 1, 1,
sl_ticks=SL_TICKS, tp_ticks=tp_ticks)
if result.get("orderId") or result.get("success"):
position = "SHORT"
entry_price = price
acct = get_account(ACCOUNT_ID)
balance_before_trade = acct["balance"] if acct else balance_before_trade
sl = price + SL_TICKS * 0.25
tp = price - tp_ticks * 0.25
print(f"[SELL] Bar {i} @ {price:.2f} | SL: {sl:.2f} TP: {tp:.2f}")
finally:
try:
close_all_positions(ACCOUNT_ID, CONTRACT_ID)
except:
pass
account = get_account(ACCOUNT_ID)
balance = account["balance"] if account else 50000
pnl = balance - 50000
print("-" * 50)
print(f"[DONE] Final Balance: ${balance:,.2f}")
print(f"[DONE] Net P&L: ${pnl:+,.2f}")
print(f"[DONE] Total Trades: {trade_count}")
if trade_count > 0:
print(f"[DONE] Win Rate: {wins / trade_count * 100:.1f}%")

Risk-Reward Ratios

The relationship between your stop loss and take profit determines how often you need to win to be profitable:

R:R RatioRequired Win Rate to Break Even
1:150%
1.5:140%
2:133%
3:125%

What’s Next