ML Engine Updates: - Updated BTCUSD with Polygon API data (2024-2025): 215,699 new records - Re-trained all ML models: Attention (R²: 0.223), Base, Metamodel (87.3% confidence) - Backtest results: +176.71R profit with aggressive_filter strategy Documentation Consolidation: - Created docs/99-analisis/_MAP.md index with 13 new analysis documents - Consolidated inventories: removed duplicates from orchestration/inventarios/ - Updated ML_INVENTORY.yml with BTCUSD metrics and training results - Added execution reports: FASE11-BTCUSD, correction issues, alignment validation Architecture & Integration: - Updated all module documentation with NEXUS v3.4 frontmatter - Fixed _MAP.md indexes across all folders - Updated orchestration plans and traces Files: 229 changed, 5064 insertions(+), 1872 deletions(-) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
210 lines
5.7 KiB
TypeScript
210 lines
5.7 KiB
TypeScript
/**
|
|
* Risk Check Middleware
|
|
*
|
|
* Pre-trade risk validation to ensure orders comply with risk limits.
|
|
*
|
|
* @version 1.0.0
|
|
* @author Trading Platform Trading Platform
|
|
*/
|
|
|
|
import { riskConfig } from '../config';
|
|
import { getBinanceClient } from '../services/binance-client';
|
|
import { logger } from '../utils/logger';
|
|
|
|
// ==========================================
|
|
// Types
|
|
// ==========================================
|
|
|
|
export interface RiskCheckParams {
|
|
symbol: string;
|
|
side: 'buy' | 'sell';
|
|
amount: number;
|
|
price?: number;
|
|
}
|
|
|
|
export interface RiskCheckResult {
|
|
allowed: boolean;
|
|
reason?: string;
|
|
warnings?: string[];
|
|
orderValue?: number;
|
|
}
|
|
|
|
// Daily volume tracking (in-memory, resets on restart)
|
|
let dailyVolume = 0;
|
|
let lastVolumeResetDate = new Date().toDateString();
|
|
|
|
// ==========================================
|
|
// Risk Check Functions
|
|
// ==========================================
|
|
|
|
/**
|
|
* Reset daily volume counter at midnight
|
|
*/
|
|
function checkAndResetDailyVolume(): void {
|
|
const today = new Date().toDateString();
|
|
if (today !== lastVolumeResetDate) {
|
|
dailyVolume = 0;
|
|
lastVolumeResetDate = today;
|
|
logger.info('Daily volume counter reset');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the quote asset from a symbol (e.g., USDT from BTCUSDT)
|
|
*/
|
|
function getQuoteAsset(symbol: string): string {
|
|
const stablecoins = ['USDT', 'BUSD', 'USDC', 'TUSD', 'DAI'];
|
|
for (const stable of stablecoins) {
|
|
if (symbol.endsWith(stable)) {
|
|
return stable;
|
|
}
|
|
}
|
|
return 'USDT';
|
|
}
|
|
|
|
/**
|
|
* Perform comprehensive risk check before order execution
|
|
*/
|
|
export async function performRiskCheck(params: RiskCheckParams): Promise<RiskCheckResult> {
|
|
const { symbol, side, amount, price } = params;
|
|
const warnings: string[] = [];
|
|
|
|
try {
|
|
checkAndResetDailyVolume();
|
|
|
|
const client = getBinanceClient();
|
|
|
|
// 1. Get current price if not provided
|
|
let orderPrice = price;
|
|
if (!orderPrice) {
|
|
try {
|
|
orderPrice = await client.getCurrentPrice(symbol);
|
|
} catch (error) {
|
|
logger.warn(`Could not fetch current price for ${symbol}, using amount as value estimate`);
|
|
orderPrice = 1; // Fallback
|
|
}
|
|
}
|
|
|
|
// 2. Calculate order value in quote currency (usually USDT)
|
|
const orderValue = amount * orderPrice;
|
|
|
|
// 3. Check maximum order value
|
|
if (orderValue > riskConfig.maxOrderValueUsdt) {
|
|
return {
|
|
allowed: false,
|
|
reason: `Order value ${orderValue.toFixed(2)} USDT exceeds maximum ${riskConfig.maxOrderValueUsdt} USDT`,
|
|
orderValue,
|
|
};
|
|
}
|
|
|
|
// 4. Check daily volume limit
|
|
if (dailyVolume + orderValue > riskConfig.maxDailyVolumeUsdt) {
|
|
return {
|
|
allowed: false,
|
|
reason: `Daily volume limit reached. Current: ${dailyVolume.toFixed(2)} USDT, Limit: ${riskConfig.maxDailyVolumeUsdt} USDT`,
|
|
orderValue,
|
|
};
|
|
}
|
|
|
|
// 5. Check if API keys are configured for trading
|
|
if (!client.isConfigured()) {
|
|
return {
|
|
allowed: false,
|
|
reason: 'Binance API keys are not configured. Cannot execute trades.',
|
|
orderValue,
|
|
};
|
|
}
|
|
|
|
// 6. Verify we can connect to Binance
|
|
const connected = await client.isConnected();
|
|
if (!connected) {
|
|
return {
|
|
allowed: false,
|
|
reason: 'Cannot connect to Binance. Please check your network and API configuration.',
|
|
orderValue,
|
|
};
|
|
}
|
|
|
|
// 7. Check balance for buy orders (if we have account access)
|
|
if (side === 'buy') {
|
|
try {
|
|
const account = await client.getAccount();
|
|
const quoteAsset = getQuoteAsset(symbol);
|
|
const quoteBalance = account.balances.find(b => b.asset === quoteAsset);
|
|
const available = quoteBalance?.free ?? 0;
|
|
|
|
if (available < orderValue) {
|
|
return {
|
|
allowed: false,
|
|
reason: `Insufficient ${quoteAsset} balance. Required: ${orderValue.toFixed(2)}, Available: ${available.toFixed(2)}`,
|
|
orderValue,
|
|
};
|
|
}
|
|
|
|
// Warning if using more than 50% of available balance
|
|
if (orderValue > available * 0.5) {
|
|
warnings.push(`This order uses ${((orderValue / available) * 100).toFixed(1)}% of your available ${quoteAsset}`);
|
|
}
|
|
} catch (error) {
|
|
warnings.push('Could not verify account balance');
|
|
logger.warn('Balance check failed', { error });
|
|
}
|
|
}
|
|
|
|
// 8. Check for large order warning
|
|
if (orderValue > riskConfig.maxOrderValueUsdt * 0.5) {
|
|
warnings.push(`Large order: ${orderValue.toFixed(2)} USDT (${((orderValue / riskConfig.maxOrderValueUsdt) * 100).toFixed(0)}% of max)`);
|
|
}
|
|
|
|
// All checks passed
|
|
return {
|
|
allowed: true,
|
|
orderValue,
|
|
warnings: warnings.length > 0 ? warnings : undefined,
|
|
};
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
logger.error('Risk check failed', { error, params });
|
|
return {
|
|
allowed: false,
|
|
reason: `Risk check error: ${message}`,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Record executed trade volume
|
|
*/
|
|
export function recordTradeVolume(orderValue: number): void {
|
|
checkAndResetDailyVolume();
|
|
dailyVolume += orderValue;
|
|
logger.info(`Trade recorded. Daily volume: ${dailyVolume.toFixed(2)} USDT`);
|
|
}
|
|
|
|
/**
|
|
* Get current daily volume
|
|
*/
|
|
export function getDailyVolume(): number {
|
|
checkAndResetDailyVolume();
|
|
return dailyVolume;
|
|
}
|
|
|
|
/**
|
|
* Get remaining daily volume allowance
|
|
*/
|
|
export function getRemainingDailyVolume(): number {
|
|
checkAndResetDailyVolume();
|
|
return Math.max(0, riskConfig.maxDailyVolumeUsdt - dailyVolume);
|
|
}
|
|
|
|
// ==========================================
|
|
// Exports
|
|
// ==========================================
|
|
|
|
export default {
|
|
performRiskCheck,
|
|
recordTradeVolume,
|
|
getDailyVolume,
|
|
getRemainingDailyVolume,
|
|
};
|