Skill: Real-Time Risk Monitor | Domain: trading/risk-and-portfolio | Category: risk | Level: expert Tags:
monitoring,real-time,alerts,kill-switch,exposure,live
Real-Time Risk Monitor
1. Architecture
┌─────────────────────────────────────────────┐
│ RISK MONITOR │
├─────────────────────────────────────────────┤
│ │
│ Data Layer │
│ ├── MT5 account state (positions, balance) │
│ ├── Market data feed (prices, spreads) │
│ ├── Volatility feed (VIX, ATR) │
│ └── Correlation matrix (rolling) │
│ │
│ Computation Layer │
│ ├── Real-time P&L per position │
│ ├── Portfolio exposure by: │
│ │ ├── Asset class │
│ │ ├── Direction (net long/short) │
│ │ ├── Correlation cluster │
│ │ └── Strategy │
│ ├── Drawdown tracker (peak-to-current) │
│ ├── Daily/weekly/monthly P&L vs limits │
│ └── Margin utilization │
│ │
│ Alert Layer │
│ ├── Threshold alerts (configurable) │
│ ├── Anomaly detection (unusual patterns) │
│ └── Kill switches (automated position exit) │
│ │
│ Output Layer │
│ ├── Dashboard (real-time display) │
│ ├── Notifications (Telegram/email/SMS) │
│ └── Logging (all state changes) │
│ │
└─────────────────────────────────────────────┘
2. Core Metrics (Real-Time)
from dataclasses import dataclass
from datetime import datetime
@dataclass
class RiskSnapshot:
timestamp: datetime
# Account
balance: float
equity: float
margin_used: float
free_margin: float
margin_level_pct: float # equity / margin × 100
# Exposure
num_open_positions: int
total_exposure_usd: float
net_direction: float # +1 = fully long, -1 = fully short
gross_exposure_pct: float # total_exposure / equity
# P&L
unrealized_pnl: float
realized_pnl_today: float
realized_pnl_week: float
realized_pnl_month: float
# Drawdown
equity_peak: float
current_drawdown_pct: float # (peak - equity) / peak
drawdown_duration_hours: float
drawdown_level: int # 0=normal, 1=caution, 2=warning, 3=critical, 4=emergency
# Risk
total_risk_pct: float # sum of all position risk / equity
largest_position_risk_pct: float
correlation_cluster_risk: float
# Volatility context
current_vix: float
avg_position_atr_pct: float
class RiskMonitor:
def __init__(self, config: RiskConfig):
self.config = config
self.peak_equity = config.starting_balance
self.alerts_sent: list[Alert] = []
def compute_snapshot(self, account, positions, market_data) -> RiskSnapshot:
equity = account.equity
# Track peak
if equity > self.peak_equity:
self.peak_equity = equity
dd_pct = (self.peak_equity - equity) / self.peak_equity
# Compute exposure
total_exposure = sum(abs(p.volume * p.current_price) for p in positions)
net_exposure = sum(
p.volume * p.current_price * (1 if p.type == 'buy' else -1)
for p in positions
)
# Compute total risk
total_risk = sum(
abs(p.current_price - p.sl) * p.volume / equity
for p in positions if p.sl
)
# Drawdown level
dd_level = self._classify_drawdown(dd_pct)
return RiskSnapshot(
timestamp=datetime.now(),
balance=account.balance,
equity=equity,
margin_used=account.margin,
free_margin=account.free_margin,
margin_level_pct=account.margin_level,
num_open_positions=len(positions),
total_exposure_usd=total_exposure,
net_direction=net_exposure / total_exposure if total_exposure else 0,
gross_exposure_pct=total_exposure / equity * 100,
unrealized_pnl=sum(p.profit for p in positions),
realized_pnl_today=self._get_realized_pnl('today'),
realized_pnl_week=self._get_realized_pnl('week'),
realized_pnl_month=self._get_realized_pnl('month'),
equity_peak=self.peak_equity,
current_drawdown_pct=dd_pct * 100,
drawdown_duration_hours=self._dd_duration(),
drawdown_level=dd_level,
total_risk_pct=total_risk * 100,
largest_position_risk_pct=max(
abs(p.current_price - p.sl) * p.volume / equity * 100
for p in positions if p.sl
) if positions else 0,
correlation_cluster_risk=self._compute_cluster_risk(positions),
current_vix=market_data.get('VIX', 0),
avg_position_atr_pct=self._avg_atr_pct(positions, market_data),
)
3. Alert Thresholds
# risk-config.yaml
thresholds:
# Drawdown levels (matches drawdown-playbook)
drawdown:
caution: 3.0 # % — reduce size 25%
warning: 5.0 # % — reduce size 50%
critical: 10.0 # % — minimum size only
emergency: 15.0 # % — KILL SWITCH
# Daily limits
daily:
max_loss: 4.0 # % of account
max_trades: 10
max_loss_streak: 5 # consecutive losses
# Exposure limits
exposure:
max_gross: 300 # % (3:1 leverage max)
max_single_position: 2.0 # % risk per position
max_total_risk: 6.0 # % total open risk
max_correlated: 4.0 # % risk in correlated cluster
min_margin_level: 200 # % — below = too leveraged
# Volatility adjustments
volatility:
vix_reduce_25: 25 # At VIX > 25, reduce size 25%
vix_reduce_50: 35 # At VIX > 35, reduce size 50%
vix_stop: 45 # At VIX > 45, no new positions
kill_switches:
margin_level_below: 150 # Auto-close largest loser
drawdown_above: 15.0 # Auto-close ALL positions
daily_loss_above: 5.0 # Auto-close ALL, lock for day
4. Kill Switch Implementation
class KillSwitch:
"""Automated position closure for extreme scenarios."""
def __init__(self, mt5_connection, config: dict):
self.mt5 = mt5_connection
self.config = config
self.triggered = False
self.trigger_log: list = []
def evaluate(self, snapshot: RiskSnapshot) -> list[Action]:
actions = []
# Kill Switch 1: Margin crisis
if snapshot.margin_level_pct < self.config['margin_level_below']:
actions.append(Action(
type='CLOSE_LARGEST_LOSER',
reason=f'Margin level {snapshot.margin_level_pct:.0f}% < {self.config["margin_level_below"]}%',
severity='CRITICAL'
))
# Kill Switch 2: Emergency drawdown
if snapshot.current_drawdown_pct > self.config['drawdown_above']:
actions.append(Action(
type='CLOSE_ALL',
reason=f'Drawdown {snapshot.current_drawdown_pct:.1f}% > {self.config["drawdown_above"]}%',
severity='EMERGENCY'
))
# Kill Switch 3: Daily loss limit
daily_loss_pct = abs(min(0, snapshot.realized_pnl_today)) / snapshot.equity_peak * 100
if daily_loss_pct > self.config['daily_loss_above']:
actions.append(Action(
type='CLOSE_ALL_AND_LOCK',
reason=f'Daily loss {daily_loss_pct:.1f}% > {self.config["daily_loss_above"]}%',
severity='CRITICAL',
lock_duration_hours=24
))
# Execute actions
for action in actions:
self._execute(action)
self._notify(action)
self.trigger_log.append((datetime.now(), action))
return actions
def _execute(self, action: Action):
if action.type == 'CLOSE_ALL':
positions = self.mt5.positions_get()
for pos in positions:
self.mt5.close_position(pos.ticket)
self.triggered = True
elif action.type == 'CLOSE_LARGEST_LOSER':
positions = self.mt5.positions_get()
worst = min(positions, key=lambda p: p.profit)
self.mt5.close_position(worst.ticket)
elif action.type == 'CLOSE_ALL_AND_LOCK':
self._execute(Action(type='CLOSE_ALL'))
self._set_trading_lock(action.lock_duration_hours)
5. Dashboard Display
╔══════════════════════════════════════════════════════════╗
║ RISK MONITOR 2025-01-15 14:32 ║
╠══════════════════════════════════════════════════════════╣
║ ║
║ ACCOUNT DRAWDOWN EXPOSURE ║
║ Balance: $52,340 Current: 2.1% Gross: 142% ║
║ Equity: $51,890 Peak: $53,000 Net Long: 67% ║
║ Margin: $12,400 Duration: 3.2h Positions: 4 ║
║ Free: $39,490 Level: NORMAL Corr Risk: 3.1% ║
║ ║
║ P&L TODAY LIMITS ║
║ Realized: -$340 Daily: 34% of limit ║
║ Unreal: -$450 Weekly: 21% of limit ║
║ Total: -$790 Monthly: 12% of limit ║
║ ║
║ POSITIONS ║
║ EURUSD BUY 0.5L +$120 Risk: 0.8% ║
║ GBPUSD BUY 0.3L -$280 Risk: 1.2% ║
║ USDJPY SELL 0.4L -$190 Risk: 0.7% ║
║ XAUUSD BUY 0.1L -$100 Risk: 0.4% ║
║ Total: 3.1% ║
║ ║
║ VIX: 18.2 (Normal) Spread Alert: None ║
║ Kill Switch: ARMED Last Trigger: Never ║
║ ║
╚══════════════════════════════════════════════════════════╝
6. Monitoring Loop
import asyncio
async def risk_monitoring_loop(
monitor: RiskMonitor,
kill_switch: KillSwitch,
notifier: Notifier,
interval_seconds: int = 5
):
"""Main monitoring loop. Runs continuously during trading hours."""
while is_trading_hours():
try:
# Get current state
account = mt5.account_info()
positions = mt5.positions_get()
market_data = get_market_data()
# Compute risk snapshot
snapshot = monitor.compute_snapshot(account, positions, market_data)
# Check kill switches (highest priority)
kill_actions = kill_switch.evaluate(snapshot)
# Check alerts
alerts = monitor.check_thresholds(snapshot)
for alert in alerts:
if not monitor.recently_alerted(alert):
await notifier.send(alert)
# Log snapshot
monitor.log_snapshot(snapshot)
# Broadcast to dashboard
await dashboard.update(snapshot)
except Exception as e:
await notifier.send(Alert(
level='ERROR',
message=f'Risk monitor error: {e}',
))
await asyncio.sleep(interval_seconds)
Related Skills
- Drawdown Playbook
- Correlation Crisis
- Risk And Portfolio
- Trading Brain Orchestrator
- Multi-Strategy Orchestration