Risk Management Is the Strategy
A mediocre strategy with good risk management will outperform a great strategy with poor risk management. This is the most counterintuitive truth in trading: your edge comes from how much you risk, not from how often you are right.
Max risk per trade
Max total portfolio risk
Max drawdown before halt
Position Sizing with ATR
import requests
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://tickatlas.com/v1"
class RiskManager:
def __init__(self, balance: float, max_risk_pct: float = 1.0,
max_positions: int = 5, max_drawdown_pct: float = 15.0):
self.initial_balance = balance
self.balance = balance
self.max_risk_pct = max_risk_pct
self.max_positions = max_positions
self.max_drawdown_pct = max_drawdown_pct
self.open_positions = []
self.peak_balance = balance
def calculate_position_size(self, symbol: str, entry: float,
stop_loss: float) -> float:
"""Calculate lot size based on fixed-percentage risk."""
risk_amount = self.balance * (self.max_risk_pct / 100)
stop_distance = abs(entry - stop_loss)
if stop_distance == 0:
return 0.0
pip_factor = 100 if "JPY" in symbol else 10000
stop_pips = stop_distance * pip_factor
pip_value = 10.0 # USD per pip per standard lot
lots = risk_amount / (stop_pips * pip_value)
return round(lots, 2)
def can_open_position(self) -> bool:
"""Check if we can open another position."""
if len(self.open_positions) >= self.max_positions:
return False
if self.current_drawdown() >= self.max_drawdown_pct:
return False
return True
def current_drawdown(self) -> float:
"""Calculate current drawdown from peak."""
self.peak_balance = max(self.peak_balance, self.balance)
dd = (self.peak_balance - self.balance) / self.peak_balance * 100
return round(dd, 2)
def total_risk_exposure(self) -> float:
"""Sum of risk across all open positions."""
return sum(p.get("risk_pct", 0) for p in self.open_positions) ATR-Based Dynamic Position Sizing
def dynamic_position_size(self, symbol: str, timeframe: str,
direction: str, atr_multiplier: float = 2.0) -> dict:
"""Calculate position size using live ATR data."""
# Fetch ATR
resp = requests.get(f"{BASE_URL}/indicator",
headers={"X-API-Key": API_KEY},
params={"symbol": symbol, "indicator": "ATR_14", "timeframe": timeframe})
data = resp.json()["data"]
atr = data["value"]
close = data["bid"] # current bid as the reference price
stop_distance = atr * atr_multiplier
if direction == "long":
stop_loss = close - stop_distance
else:
stop_loss = close + stop_distance
lots = self.calculate_position_size(symbol, close, stop_loss)
return {
"symbol": symbol,
"direction": direction,
"entry": close,
"stop_loss": round(stop_loss, 5),
"lots": lots,
"risk_amount": round(self.balance * self.max_risk_pct / 100, 2),
"atr": atr,
} Correlation-Aware Risk
Being long EURUSD, long GBPUSD, and long AUDUSD is not three separate bets -- it is essentially one large bet against the US dollar. Correlation-aware risk management counts correlated positions as partial duplicates.
CORRELATION_GROUPS = {
"USD_WEAK": ["EURUSD", "GBPUSD", "AUDUSD", "NZDUSD"],
"USD_STRONG": ["USDCAD", "USDJPY", "USDCHF"],
"SAFE_HAVEN": ["XAUUSD", "USDJPY"],
}
def check_correlation_risk(self, new_symbol: str, new_direction: str) -> bool:
"""Check if a new position would over-concentrate risk."""
# Find which group the new symbol belongs to
for group_name, symbols in CORRELATION_GROUPS.items():
if new_symbol in symbols:
# Count how many open positions are in the same group
same_group = [p for p in self.open_positions
if p["symbol"] in symbols
and p["direction"] == new_direction]
if len(same_group) >= 2:
return False # Too much exposure to this group
return True Drawdown Circuit Breaker
class DrawdownBreaker:
"""Halt trading when drawdown exceeds thresholds."""
def __init__(self):
self.thresholds = [
(10.0, "REDUCE"), # 10% DD: halve position sizes
(15.0, "PAUSE"), # 15% DD: stop opening new trades
(20.0, "FLATTEN"), # 20% DD: close everything
]
def evaluate(self, current_drawdown: float) -> str:
action = "NORMAL"
for threshold, level in self.thresholds:
if current_drawdown >= threshold:
action = level
return action
# In the main trading loop:
breaker = DrawdownBreaker()
rm = RiskManager(balance=10000)
action = breaker.evaluate(rm.current_drawdown())
if action == "FLATTEN":
close_all_positions()
elif action == "PAUSE":
pass # Do not open new trades
elif action == "REDUCE":
rm.max_risk_pct = 0.5 # Halve risk per trade The Risk Checklist
Every trade has a defined stop loss
No exceptions. If you cannot define the stop before entry, do not take the trade. ATR-based stops are the most reliable approach.
Position size is calculated, not guessed
Use the fixed-percentage model. Risk 1% of account equity per trade. Let the stop distance determine the lot size, not the other way around.
Maximum portfolio heat is enforced
With 5 open positions at 1% risk each, you have 5% portfolio heat. Set a hard cap and enforce it in code, not in your head.
Drawdown triggers are automated
When you are losing money, your judgment is worst. Automate the circuit breakers so they fire without your emotional input.
Run this against live data.
Every account starts pay-as-you-go with $2.50 of credit and no card. Paste the key into the samples above and the requests work unchanged.