Files

884 lines
28 KiB
JavaScript

/**
* Risk Management Layer
*
* Comprehensive risk controls for trading systems:
* - Position limits (per-asset and portfolio)
* - Stop-loss orders (fixed, trailing, volatility-based)
* - Circuit breakers (drawdown, loss rate, volatility)
* - Exposure management
* - Correlation risk
* - Leverage control
*/
// Risk Management Configuration
const riskConfig = {
// Position limits
positions: {
maxPositionSize: 0.10, // Max 10% per position
maxPositionValue: 50000, // Max $50k per position
minPositionSize: 0.01, // Min 1% position
maxOpenPositions: 20, // Max concurrent positions
maxSectorExposure: 0.30, // Max 30% per sector
maxCorrelatedExposure: 0.40 // Max 40% in correlated assets
},
// Portfolio limits
portfolio: {
maxLongExposure: 1.0, // Max 100% long
maxShortExposure: 0.5, // Max 50% short
maxGrossExposure: 1.5, // Max 150% gross
maxNetExposure: 1.0, // Max 100% net
maxLeverage: 2.0, // Max 2x leverage
minCashReserve: 0.05 // Keep 5% cash
},
// Stop-loss settings
stopLoss: {
defaultType: 'trailing', // fixed, trailing, volatility
fixedPercent: 0.05, // 5% fixed stop
trailingPercent: 0.03, // 3% trailing stop
volatilityMultiplier: 2.0, // 2x ATR for vol stop
maxLossPerTrade: 0.02, // Max 2% loss per trade
maxDailyLoss: 0.05 // Max 5% daily loss
},
// Circuit breakers
circuitBreakers: {
drawdownThreshold: 0.10, // 10% drawdown triggers
drawdownCooldown: 86400000, // 24h cooldown
lossRateThreshold: 0.70, // 70% loss rate in window
lossRateWindow: 20, // 20 trade window
volatilityThreshold: 0.04, // 4% daily vol threshold
volatilityMultiplier: 3.0, // 3x normal vol
consecutiveLosses: 5 // 5 consecutive losses
},
// Risk scoring
scoring: {
updateFrequency: 60000, // Update every minute
historyWindow: 252, // 1 year of daily data
correlationThreshold: 0.7 // High correlation threshold
}
};
/**
* Stop-Loss Manager
*/
class StopLossManager {
constructor(config = riskConfig.stopLoss) {
this.config = config;
this.stops = new Map(); // symbol -> stop config
this.volatility = new Map(); // symbol -> ATR
}
// Set stop-loss for a position
setStop(symbol, entryPrice, type = null, params = {}) {
const stopType = type || this.config.defaultType;
let stopPrice;
switch (stopType) {
case 'fixed':
stopPrice = entryPrice * (1 - this.config.fixedPercent);
break;
case 'trailing':
stopPrice = entryPrice * (1 - this.config.trailingPercent);
break;
case 'volatility':
const atr = this.volatility.get(symbol) || entryPrice * 0.02;
stopPrice = entryPrice - (atr * this.config.volatilityMultiplier);
break;
default:
stopPrice = entryPrice * (1 - this.config.fixedPercent);
}
this.stops.set(symbol, {
type: stopType,
entryPrice,
stopPrice,
highWaterMark: entryPrice,
params,
createdAt: Date.now()
});
return this.stops.get(symbol);
}
// Update trailing stop with new price
updateTrailingStop(symbol, currentPrice) {
const stop = this.stops.get(symbol);
if (!stop || stop.type !== 'trailing') return null;
if (currentPrice > stop.highWaterMark) {
stop.highWaterMark = currentPrice;
stop.stopPrice = currentPrice * (1 - this.config.trailingPercent);
}
return stop;
}
// Check if stop is triggered
checkStop(symbol, currentPrice) {
const stop = this.stops.get(symbol);
if (!stop) return { triggered: false };
// Update trailing stop first
if (stop.type === 'trailing') {
this.updateTrailingStop(symbol, currentPrice);
}
const triggered = currentPrice <= stop.stopPrice;
return {
triggered,
stopPrice: stop.stopPrice,
currentPrice,
loss: triggered ? (stop.entryPrice - currentPrice) / stop.entryPrice : 0,
type: stop.type
};
}
// Set volatility for volatility-based stops
setVolatility(symbol, atr) {
this.volatility.set(symbol, atr);
}
// Remove stop
removeStop(symbol) {
this.stops.delete(symbol);
}
// Get all active stops
getActiveStops() {
return Object.fromEntries(this.stops);
}
}
/**
* Circuit Breaker System
*/
class CircuitBreaker {
constructor(config = riskConfig.circuitBreakers) {
this.config = config;
this.state = {
isTripped: false,
tripReason: null,
tripTime: null,
cooldownUntil: null
};
// Tracking data
this.peakEquity = 0;
this.currentEquity = 0;
this.consecutiveLosses = 0;
// Optimized: Use ring buffers instead of arrays with shift/slice
const tradeWindowSize = config.lossRateWindow * 2;
this._tradeBuffer = new Array(tradeWindowSize);
this._tradeIndex = 0;
this._tradeCount = 0;
this._tradeLossCount = 0; // Track losses incrementally
this._volBuffer = new Array(20);
this._volIndex = 0;
this._volCount = 0;
this._volSum = 0; // Running sum for O(1) average
}
// Update with new equity value
updateEquity(equity) {
this.currentEquity = equity;
if (equity > this.peakEquity) {
this.peakEquity = equity;
}
// Check drawdown breaker
const drawdown = (this.peakEquity - equity) / this.peakEquity;
if (drawdown >= this.config.drawdownThreshold) {
this.trip('drawdown', `Drawdown ${(drawdown * 100).toFixed(1)}% exceeds threshold`);
}
}
// Optimized: Record trade with O(1) ring buffer
recordTrade(profit) {
const bufferSize = this._tradeBuffer.length;
const windowSize = this.config.lossRateWindow;
// If overwriting an old trade, adjust loss count
if (this._tradeCount >= bufferSize) {
const oldTrade = this._tradeBuffer[this._tradeIndex];
if (oldTrade && oldTrade.profit < 0) {
this._tradeLossCount--;
}
}
// Add new trade
this._tradeBuffer[this._tradeIndex] = { profit, timestamp: Date.now() };
if (profit < 0) this._tradeLossCount++;
this._tradeIndex = (this._tradeIndex + 1) % bufferSize;
if (this._tradeCount < bufferSize) this._tradeCount++;
// Update consecutive losses
if (profit < 0) {
this.consecutiveLosses++;
} else {
this.consecutiveLosses = 0;
}
// Check loss rate breaker (O(1) using tracked count)
if (this._tradeCount >= windowSize) {
// Count losses in recent window
let recentLosses = 0;
const startIdx = (this._tradeIndex - windowSize + bufferSize) % bufferSize;
for (let i = 0; i < windowSize; i++) {
const idx = (startIdx + i) % bufferSize;
if (this._tradeBuffer[idx] && this._tradeBuffer[idx].profit < 0) {
recentLosses++;
}
}
const lossRate = recentLosses / windowSize;
if (lossRate >= this.config.lossRateThreshold) {
this.trip('lossRate', `Loss rate ${(lossRate * 100).toFixed(1)}% exceeds threshold`);
}
}
// Check consecutive losses breaker
if (this.consecutiveLosses >= this.config.consecutiveLosses) {
this.trip('consecutiveLosses', `${this.consecutiveLosses} consecutive losses`);
}
}
// Optimized: Update volatility with O(1) ring buffer and running sum
updateVolatility(dailyReturn) {
const absReturn = Math.abs(dailyReturn);
const bufferSize = this._volBuffer.length;
// If overwriting old value, subtract from running sum
if (this._volCount >= bufferSize) {
this._volSum -= this._volBuffer[this._volIndex];
}
// Add new value
this._volBuffer[this._volIndex] = absReturn;
this._volSum += absReturn;
this._volIndex = (this._volIndex + 1) % bufferSize;
if (this._volCount < bufferSize) this._volCount++;
// Check volatility spike (O(1) using running sum)
if (this._volCount >= 5) {
const avgVol = (this._volSum - absReturn) / (this._volCount - 1);
const currentVol = absReturn;
if (currentVol > avgVol * this.config.volatilityMultiplier ||
currentVol > this.config.volatilityThreshold) {
this.trip('volatility', `Volatility spike: ${(currentVol * 100).toFixed(2)}%`);
}
}
}
// Trip the circuit breaker
trip(reason, message) {
if (this.state.isTripped) return; // Already tripped
this.state = {
isTripped: true,
tripReason: reason,
tripMessage: message,
tripTime: Date.now(),
cooldownUntil: Date.now() + this.config.drawdownCooldown
};
console.warn(`🔴 CIRCUIT BREAKER TRIPPED: ${message}`);
}
// Check if trading is allowed
canTrade() {
if (!this.state.isTripped) return { allowed: true };
// Check if cooldown has passed
if (Date.now() >= this.state.cooldownUntil) {
this.reset();
return { allowed: true };
}
return {
allowed: false,
reason: this.state.tripReason,
message: this.state.tripMessage,
cooldownRemaining: this.state.cooldownUntil - Date.now()
};
}
// Reset circuit breaker
reset() {
this.state = {
isTripped: false,
tripReason: null,
tripTime: null,
cooldownUntil: null
};
this.consecutiveLosses = 0;
console.log('🟢 Circuit breaker reset');
}
// Force reset (manual override)
forceReset() {
this.reset();
this.peakEquity = this.currentEquity;
// Reset ring buffers
this._tradeIndex = 0;
this._tradeCount = 0;
this._tradeLossCount = 0;
this._volIndex = 0;
this._volCount = 0;
this._volSum = 0;
}
getState() {
return {
...this.state,
drawdown: this.peakEquity > 0 ? (this.peakEquity - this.currentEquity) / this.peakEquity : 0,
consecutiveLosses: this.consecutiveLosses,
recentLossRate: this.calculateRecentLossRate()
};
}
// Optimized: O(windowSize) but only called for reporting
calculateRecentLossRate() {
const windowSize = this.config.lossRateWindow;
const count = Math.min(this._tradeCount, windowSize);
if (count === 0) return 0;
let losses = 0;
const bufferSize = this._tradeBuffer.length;
const startIdx = (this._tradeIndex - count + bufferSize) % bufferSize;
for (let i = 0; i < count; i++) {
const idx = (startIdx + i) % bufferSize;
if (this._tradeBuffer[idx] && this._tradeBuffer[idx].profit < 0) {
losses++;
}
}
return losses / count;
}
}
/**
* Position Limit Manager
*/
class PositionLimitManager {
constructor(config = riskConfig.positions) {
this.config = config;
this.positions = new Map();
this.sectors = new Map(); // symbol -> sector mapping
}
// Set sector for a symbol
setSector(symbol, sector) {
this.sectors.set(symbol, sector);
}
// Check if position size is allowed
checkPositionSize(symbol, proposedSize, portfolioValue) {
const sizePercent = proposedSize / portfolioValue;
const violations = [];
// Check max position size
if (sizePercent > this.config.maxPositionSize) {
violations.push({
type: 'maxPositionSize',
message: `Position ${(sizePercent * 100).toFixed(1)}% exceeds max ${(this.config.maxPositionSize * 100)}%`,
limit: this.config.maxPositionSize * portfolioValue
});
}
// Check max position value
if (proposedSize > this.config.maxPositionValue) {
violations.push({
type: 'maxPositionValue',
message: `Position $${proposedSize.toFixed(0)} exceeds max $${this.config.maxPositionValue}`,
limit: this.config.maxPositionValue
});
}
// Check min position size
if (sizePercent < this.config.minPositionSize && proposedSize > 0) {
violations.push({
type: 'minPositionSize',
message: `Position ${(sizePercent * 100).toFixed(1)}% below min ${(this.config.minPositionSize * 100)}%`,
limit: this.config.minPositionSize * portfolioValue
});
}
return {
allowed: violations.length === 0,
violations,
adjustedSize: this.adjustPositionSize(proposedSize, portfolioValue)
};
}
// Adjust position size to comply with limits
adjustPositionSize(proposedSize, portfolioValue) {
let adjusted = proposedSize;
// Apply max position size
const maxByPercent = portfolioValue * this.config.maxPositionSize;
adjusted = Math.min(adjusted, maxByPercent);
// Apply max position value
adjusted = Math.min(adjusted, this.config.maxPositionValue);
return adjusted;
}
// Check sector exposure
checkSectorExposure(symbol, proposedSize, currentPositions, portfolioValue) {
const sector = this.sectors.get(symbol);
if (!sector) return { allowed: true };
// Calculate current sector exposure
let sectorExposure = 0;
for (const [sym, pos] of Object.entries(currentPositions)) {
if (this.sectors.get(sym) === sector) {
sectorExposure += Math.abs(pos.value || 0);
}
}
const totalSectorExposure = (sectorExposure + proposedSize) / portfolioValue;
if (totalSectorExposure > this.config.maxSectorExposure) {
return {
allowed: false,
message: `Sector ${sector} exposure ${(totalSectorExposure * 100).toFixed(1)}% exceeds max ${(this.config.maxSectorExposure * 100)}%`,
currentExposure: sectorExposure,
maxAllowed: this.config.maxSectorExposure * portfolioValue - sectorExposure
};
}
return { allowed: true, sectorExposure: totalSectorExposure };
}
// Check number of open positions
checkPositionCount(currentPositions) {
const count = Object.keys(currentPositions).filter(s => currentPositions[s].quantity !== 0).length;
if (count >= this.config.maxOpenPositions) {
return {
allowed: false,
message: `Max open positions (${this.config.maxOpenPositions}) reached`,
currentCount: count
};
}
return { allowed: true, currentCount: count };
}
}
/**
* Exposure Manager
*/
class ExposureManager {
constructor(config = riskConfig.portfolio) {
this.config = config;
}
// Calculate portfolio exposure
calculateExposure(positions, portfolioValue) {
let longExposure = 0;
let shortExposure = 0;
for (const pos of Object.values(positions)) {
const value = pos.value || (pos.quantity * pos.price) || 0;
if (value > 0) {
longExposure += value;
} else {
shortExposure += Math.abs(value);
}
}
const grossExposure = longExposure + shortExposure;
const netExposure = longExposure - shortExposure;
return {
long: longExposure / portfolioValue,
short: shortExposure / portfolioValue,
gross: grossExposure / portfolioValue,
net: netExposure / portfolioValue,
leverage: grossExposure / portfolioValue,
longValue: longExposure,
shortValue: shortExposure
};
}
// Check if trade would violate exposure limits
checkExposure(proposedTrade, currentPositions, portfolioValue) {
// Simulate new exposure
const newPositions = { ...currentPositions };
const symbol = proposedTrade.symbol;
const value = proposedTrade.value || (proposedTrade.quantity * proposedTrade.price);
const side = proposedTrade.side;
newPositions[symbol] = {
...newPositions[symbol],
value: (newPositions[symbol]?.value || 0) + (side === 'buy' ? value : -value)
};
const exposure = this.calculateExposure(newPositions, portfolioValue);
const violations = [];
if (exposure.long > this.config.maxLongExposure) {
violations.push({
type: 'maxLongExposure',
message: `Long exposure ${(exposure.long * 100).toFixed(1)}% exceeds max ${(this.config.maxLongExposure * 100)}%`
});
}
if (exposure.short > this.config.maxShortExposure) {
violations.push({
type: 'maxShortExposure',
message: `Short exposure ${(exposure.short * 100).toFixed(1)}% exceeds max ${(this.config.maxShortExposure * 100)}%`
});
}
if (exposure.gross > this.config.maxGrossExposure) {
violations.push({
type: 'maxGrossExposure',
message: `Gross exposure ${(exposure.gross * 100).toFixed(1)}% exceeds max ${(this.config.maxGrossExposure * 100)}%`
});
}
if (exposure.leverage > this.config.maxLeverage) {
violations.push({
type: 'maxLeverage',
message: `Leverage ${exposure.leverage.toFixed(2)}x exceeds max ${this.config.maxLeverage}x`
});
}
return {
allowed: violations.length === 0,
violations,
currentExposure: this.calculateExposure(currentPositions, portfolioValue),
projectedExposure: exposure
};
}
// Check cash reserve
checkCashReserve(cash, portfolioValue) {
const cashPercent = cash / portfolioValue;
if (cashPercent < this.config.minCashReserve) {
return {
allowed: false,
message: `Cash reserve ${(cashPercent * 100).toFixed(1)}% below min ${(this.config.minCashReserve * 100)}%`,
required: this.config.minCashReserve * portfolioValue
};
}
return { allowed: true, cashPercent };
}
}
/**
* Risk Manager - Main integration class
*/
class RiskManager {
constructor(config = riskConfig) {
this.config = config;
this.stopLossManager = new StopLossManager(config.stopLoss);
this.circuitBreaker = new CircuitBreaker(config.circuitBreakers);
this.positionLimits = new PositionLimitManager(config.positions);
this.exposureManager = new ExposureManager(config.portfolio);
// State
this.blockedSymbols = new Set();
this.dailyLoss = 0;
this.dailyStartEquity = 0;
}
// Initialize for trading day
startDay(equity) {
this.dailyStartEquity = equity;
this.dailyLoss = 0;
}
// Main check - can this trade be executed?
canTrade(symbol, trade, portfolio) {
const results = {
allowed: true,
checks: {},
warnings: [],
adjustments: {}
};
// Check circuit breaker
const circuitCheck = this.circuitBreaker.canTrade();
results.checks.circuitBreaker = circuitCheck;
if (!circuitCheck.allowed) {
results.allowed = false;
return results;
}
// Check if symbol is blocked
if (this.blockedSymbols.has(symbol)) {
results.allowed = false;
results.checks.blocked = { allowed: false, message: `Symbol ${symbol} is blocked` };
return results;
}
// Check position limits
const positionCheck = this.positionLimits.checkPositionSize(
symbol,
trade.value,
portfolio.equity
);
results.checks.positionSize = positionCheck;
if (!positionCheck.allowed) {
results.warnings.push(...positionCheck.violations.map(v => v.message));
results.adjustments.size = positionCheck.adjustedSize;
}
// Check position count
const countCheck = this.positionLimits.checkPositionCount(portfolio.positions);
results.checks.positionCount = countCheck;
if (!countCheck.allowed) {
results.allowed = false;
return results;
}
// Check sector exposure
const sectorCheck = this.positionLimits.checkSectorExposure(
symbol,
trade.value,
portfolio.positions,
portfolio.equity
);
results.checks.sectorExposure = sectorCheck;
if (!sectorCheck.allowed) {
results.warnings.push(sectorCheck.message);
}
// Check portfolio exposure
const exposureCheck = this.exposureManager.checkExposure(
trade,
portfolio.positions,
portfolio.equity
);
results.checks.exposure = exposureCheck;
if (!exposureCheck.allowed) {
results.allowed = false;
return results;
}
// Check cash reserve
const cashAfterTrade = portfolio.cash - trade.value;
const cashCheck = this.exposureManager.checkCashReserve(cashAfterTrade, portfolio.equity);
results.checks.cashReserve = cashCheck;
if (!cashCheck.allowed) {
results.warnings.push(cashCheck.message);
}
// Check daily loss limit
const dailyLossCheck = this.checkDailyLoss(portfolio.equity);
results.checks.dailyLoss = dailyLossCheck;
if (!dailyLossCheck.allowed) {
results.allowed = false;
return results;
}
return results;
}
// Check daily loss limit
checkDailyLoss(currentEquity) {
if (this.dailyStartEquity === 0) return { allowed: true };
const dailyReturn = (currentEquity - this.dailyStartEquity) / this.dailyStartEquity;
if (dailyReturn < -this.config.stopLoss.maxDailyLoss) {
return {
allowed: false,
message: `Daily loss ${(Math.abs(dailyReturn) * 100).toFixed(1)}% exceeds max ${(this.config.stopLoss.maxDailyLoss * 100)}%`,
dailyLoss: dailyReturn
};
}
return { allowed: true, dailyLoss: dailyReturn };
}
// Set stop-loss for a position
setStopLoss(symbol, entryPrice, type, params) {
return this.stopLossManager.setStop(symbol, entryPrice, type, params);
}
// Check all stops
checkAllStops(prices) {
const triggered = [];
for (const [symbol, price] of Object.entries(prices)) {
const check = this.stopLossManager.checkStop(symbol, price);
if (check.triggered) {
triggered.push({ symbol, ...check });
}
}
return triggered;
}
// Update circuit breaker with equity
updateEquity(equity) {
this.circuitBreaker.updateEquity(equity);
}
// Record trade for circuit breaker
recordTrade(profit) {
this.circuitBreaker.recordTrade(profit);
}
// Block a symbol
blockSymbol(symbol, reason) {
this.blockedSymbols.add(symbol);
console.warn(`🚫 Symbol ${symbol} blocked: ${reason}`);
}
// Unblock a symbol
unblockSymbol(symbol) {
this.blockedSymbols.delete(symbol);
}
// Get full risk report
getRiskReport(portfolio) {
const exposure = this.exposureManager.calculateExposure(portfolio.positions, portfolio.equity);
return {
circuitBreaker: this.circuitBreaker.getState(),
exposure,
stops: this.stopLossManager.getActiveStops(),
blockedSymbols: [...this.blockedSymbols],
dailyLoss: this.checkDailyLoss(portfolio.equity),
limits: {
maxPositionSize: this.config.positions.maxPositionSize,
maxLeverage: this.config.portfolio.maxLeverage,
maxDrawdown: this.config.circuitBreakers.drawdownThreshold
}
};
}
}
// Exports
export {
RiskManager,
StopLossManager,
CircuitBreaker,
PositionLimitManager,
ExposureManager,
riskConfig
};
// Demo if run directly
const isMainModule = import.meta.url === `file://${process.argv[1]}`;
if (isMainModule) {
console.log('══════════════════════════════════════════════════════════════════════');
console.log('RISK MANAGEMENT LAYER');
console.log('══════════════════════════════════════════════════════════════════════\n');
const riskManager = new RiskManager();
// Initialize for trading day
const portfolio = {
equity: 100000,
cash: 50000,
positions: {
AAPL: { quantity: 100, price: 150, value: 15000 },
MSFT: { quantity: 50, price: 300, value: 15000 }
}
};
riskManager.startDay(portfolio.equity);
console.log('1. Portfolio Status:');
console.log('──────────────────────────────────────────────────────────────────────');
console.log(` Equity: $${portfolio.equity.toLocaleString()}`);
console.log(` Cash: $${portfolio.cash.toLocaleString()}`);
console.log(` Positions: ${Object.keys(portfolio.positions).length}`);
console.log();
console.log('2. Trade Check - Buy $20,000 GOOGL:');
console.log('──────────────────────────────────────────────────────────────────────');
const trade1 = { symbol: 'GOOGL', side: 'buy', value: 20000, quantity: 100, price: 200 };
const check1 = riskManager.canTrade('GOOGL', trade1, portfolio);
console.log(` Allowed: ${check1.allowed ? '✓ Yes' : '✗ No'}`);
if (check1.warnings.length > 0) {
console.log(` Warnings: ${check1.warnings.join(', ')}`);
}
console.log();
console.log('3. Trade Check - Buy $60,000 TSLA (exceeds limits):');
console.log('──────────────────────────────────────────────────────────────────────');
const trade2 = { symbol: 'TSLA', side: 'buy', value: 60000, quantity: 300, price: 200 };
const check2 = riskManager.canTrade('TSLA', trade2, portfolio);
console.log(` Allowed: ${check2.allowed ? '✓ Yes' : '✗ No'}`);
if (check2.checks.positionSize?.violations) {
for (const v of check2.checks.positionSize.violations) {
console.log(` Violation: ${v.message}`);
}
}
if (check2.adjustments.size) {
console.log(` Adjusted Size: $${check2.adjustments.size.toLocaleString()}`);
}
console.log();
console.log('4. Stop-Loss Management:');
console.log('──────────────────────────────────────────────────────────────────────');
const stop = riskManager.setStopLoss('AAPL', 150, 'trailing');
console.log(` AAPL trailing stop set at $${stop.stopPrice.toFixed(2)}`);
// Simulate price movement
riskManager.stopLossManager.updateTrailingStop('AAPL', 160); // Price went up
const updatedStop = riskManager.stopLossManager.stops.get('AAPL');
console.log(` After price rise to $160: stop at $${updatedStop.stopPrice.toFixed(2)}`);
const stopCheck = riskManager.stopLossManager.checkStop('AAPL', 145); // Price dropped
console.log(` Check at $145: ${stopCheck.triggered ? '🔴 TRIGGERED' : '🟢 OK'}`);
console.log();
console.log('5. Circuit Breaker Test:');
console.log('──────────────────────────────────────────────────────────────────────');
// Simulate losses
for (let i = 0; i < 4; i++) {
riskManager.recordTrade(-500);
}
console.log(` 4 losing trades recorded`);
console.log(` Consecutive losses: ${riskManager.circuitBreaker.consecutiveLosses}`);
riskManager.recordTrade(-500); // 5th loss
const cbState = riskManager.circuitBreaker.getState();
console.log(` 5th loss recorded`);
console.log(` Circuit breaker: ${cbState.isTripped ? '🔴 TRIPPED' : '🟢 OK'}`);
if (cbState.isTripped) {
console.log(` Reason: ${cbState.tripMessage}`);
}
console.log();
console.log('6. Risk Report:');
console.log('──────────────────────────────────────────────────────────────────────');
riskManager.circuitBreaker.forceReset(); // Reset for demo
const report = riskManager.getRiskReport(portfolio);
console.log(` Long Exposure: ${(report.exposure.long * 100).toFixed(1)}%`);
console.log(` Short Exposure: ${(report.exposure.short * 100).toFixed(1)}%`);
console.log(` Gross Exposure: ${(report.exposure.gross * 100).toFixed(1)}%`);
console.log(` Leverage: ${report.exposure.leverage.toFixed(2)}x`);
console.log(` Circuit Breaker: ${report.circuitBreaker.isTripped ? 'TRIPPED' : 'OK'}`);
console.log(` Active Stops: ${Object.keys(report.stops).length}`);
console.log();
console.log('══════════════════════════════════════════════════════════════════════');
console.log('Risk management layer ready');
console.log('══════════════════════════════════════════════════════════════════════');
}