214 lines
7.6 KiB
TypeScript
214 lines
7.6 KiB
TypeScript
/**
|
|
* 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<string, unknown>): void {
|
|
// Validate admin section
|
|
if (!config.admin) {
|
|
addError('admin', 'Admin section is required');
|
|
} else {
|
|
const admin = config.admin as Record<string, unknown>;
|
|
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<string, unknown>;
|
|
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<string, unknown>[];
|
|
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<string, unknown>[];
|
|
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<string, unknown>;
|
|
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<string, unknown>;
|
|
const thresholds = ml.thresholds as Record<string, number> | 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<string, unknown>;
|
|
if (sys.auto_trade_enabled && !sys.auto_trade_require_confirmation) {
|
|
addError('system', 'Auto trade without confirmation is risky', 'warning');
|
|
}
|
|
}
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
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<string, unknown>;
|
|
|
|
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<string, unknown>;
|
|
const assets = config.priority_assets as Record<string, unknown>[];
|
|
const agents = config.trading_agents as Record<string, unknown>[];
|
|
|
|
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<string, unknown>).enabled ? 'Enabled' : 'Disabled'}`);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Validation failed:', (error as Error).message);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
main();
|