/** * Validate Personal Configuration Script * Checks config.yaml for errors and completeness * * Usage: npx tsx scripts/validate-config.ts */ import * as fs from 'fs'; import * as path from 'path'; import * as yaml from 'js-yaml'; const CONFIG_PATH = path.join(__dirname, '..', 'config.yaml'); interface ValidationError { path: string; message: string; severity: 'error' | 'warning'; } const errors: ValidationError[] = []; function addError(path: string, message: string, severity: 'error' | 'warning' = 'error'): void { errors.push({ path, message, severity }); } function validateEmail(email: string): boolean { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); } function validateConfig(config: Record): void { // Validate admin section if (!config.admin) { addError('admin', 'Admin section is required'); } else { const admin = config.admin as Record; if (!admin.email || !validateEmail(admin.email as string)) { addError('admin.email', 'Valid email is required'); } if (!admin.role || !['admin', 'premium', 'user'].includes(admin.role as string)) { addError('admin.role', 'Role must be one of: admin, premium, user'); } } // Validate risk profile if (!config.risk_profile) { addError('risk_profile', 'Risk profile section is required'); } else { const rp = config.risk_profile as Record; if (typeof rp.max_risk_percent !== 'number' || rp.max_risk_percent <= 0 || rp.max_risk_percent > 100) { addError('risk_profile.max_risk_percent', 'Must be a number between 0 and 100'); } if (typeof rp.max_drawdown_tolerance !== 'number' || rp.max_drawdown_tolerance <= 0) { addError('risk_profile.max_drawdown_tolerance', 'Must be a positive number'); } } // Validate priority assets if (!config.priority_assets || !Array.isArray(config.priority_assets)) { addError('priority_assets', 'Priority assets array is required'); } else { const assets = config.priority_assets as Record[]; if (assets.length === 0) { addError('priority_assets', 'At least one priority asset is required', 'warning'); } assets.forEach((asset, i) => { if (!asset.symbol) { addError(`priority_assets[${i}].symbol`, 'Symbol is required'); } if (!asset.data_provider) { addError(`priority_assets[${i}].data_provider`, 'Data provider is required'); } if (typeof asset.priority !== 'number') { addError(`priority_assets[${i}].priority`, 'Priority must be a number'); } }); // Check for duplicate symbols const symbols = assets.map(a => a.symbol); const duplicates = symbols.filter((s, i) => symbols.indexOf(s) !== i); if (duplicates.length > 0) { addError('priority_assets', `Duplicate symbols found: ${duplicates.join(', ')}`); } } // Validate trading agents if (!config.trading_agents || !Array.isArray(config.trading_agents)) { addError('trading_agents', 'Trading agents array is required'); } else { const agents = config.trading_agents as Record[]; agents.forEach((agent, i) => { if (!agent.name) { addError(`trading_agents[${i}].name`, 'Name is required'); } if (!agent.slug) { addError(`trading_agents[${i}].slug`, 'Slug is required'); } if (!agent.risk_profile || !['conservative', 'moderate', 'aggressive'].includes(agent.risk_profile as string)) { addError(`trading_agents[${i}].risk_profile`, 'Risk profile must be: conservative, moderate, or aggressive'); } if (typeof agent.min_confidence !== 'number' || (agent.min_confidence as number) < 0 || (agent.min_confidence as number) > 1) { addError(`trading_agents[${i}].min_confidence`, 'Min confidence must be between 0 and 1'); } if (!agent.supported_symbols || !Array.isArray(agent.supported_symbols) || (agent.supported_symbols as string[]).length === 0) { addError(`trading_agents[${i}].supported_symbols`, 'At least one supported symbol is required'); } }); // Check for duplicate slugs const slugs = agents.map(a => a.slug); const dupSlugs = slugs.filter((s, i) => slugs.indexOf(s) !== i); if (dupSlugs.length > 0) { addError('trading_agents', `Duplicate slugs found: ${dupSlugs.join(', ')}`); } } // Validate paper trading if (config.paper_trading) { const pt = config.paper_trading as Record; if (pt.enabled && typeof pt.initial_balance !== 'number') { addError('paper_trading.initial_balance', 'Initial balance must be a number when enabled'); } if (pt.enabled && (pt.initial_balance as number) <= 0) { addError('paper_trading.initial_balance', 'Initial balance must be positive'); } } // Validate ML models if (config.ml_models) { const ml = config.ml_models as Record; const thresholds = ml.thresholds as Record | undefined; if (thresholds) { if (typeof thresholds.min_confidence !== 'number' || thresholds.min_confidence < 0 || thresholds.min_confidence > 1) { addError('ml_models.thresholds.min_confidence', 'Must be between 0 and 1'); } } } // Validate system settings if (config.system) { const sys = config.system as Record; if (sys.auto_trade_enabled && !sys.auto_trade_require_confirmation) { addError('system', 'Auto trade without confirmation is risky', 'warning'); } } } async function main(): Promise { console.log('='.repeat(60)); console.log('OrbiQuant IA - Config Validation'); console.log('='.repeat(60)); console.log(`\nValidating: ${CONFIG_PATH}\n`); try { if (!fs.existsSync(CONFIG_PATH)) { console.error('ERROR: config.yaml not found!'); process.exit(1); } const configFile = fs.readFileSync(CONFIG_PATH, 'utf8'); const config = yaml.load(configFile) as Record; validateConfig(config); const errorCount = errors.filter(e => e.severity === 'error').length; const warningCount = errors.filter(e => e.severity === 'warning').length; if (errors.length > 0) { console.log('Validation Results:\n'); const errorsList = errors.filter(e => e.severity === 'error'); if (errorsList.length > 0) { console.log('ERRORS:'); errorsList.forEach(e => { console.log(` [ERROR] ${e.path}: ${e.message}`); }); } const warningsList = errors.filter(e => e.severity === 'warning'); if (warningsList.length > 0) { console.log('\nWARNINGS:'); warningsList.forEach(e => { console.log(` [WARN] ${e.path}: ${e.message}`); }); } console.log('\n' + '-'.repeat(60)); console.log(`Summary: ${errorCount} error(s), ${warningCount} warning(s)`); if (errorCount > 0) { process.exit(1); } } else { console.log('Config is valid!\n'); // Print summary const admin = config.admin as Record; const assets = config.priority_assets as Record[]; const agents = config.trading_agents as Record[]; console.log('Configuration Summary:'); console.log(` Admin: ${admin.email}`); console.log(` Assets: ${assets.length} priority asset(s)`); console.log(` Agents: ${agents.length} trading agent(s)`); console.log(` Paper Trading: ${(config.paper_trading as Record).enabled ? 'Enabled' : 'Disabled'}`); } } catch (error) { console.error('Validation failed:', (error as Error).message); process.exit(1); } } main();