import 'dotenv/config'; import { createClient } from '@supabase/supabase-js'; type SeedProfile = { name: string; allocated_capital: number; risk_per_trade_percent: number; symbols: string; is_active: boolean; strategy_config: Record; }; const supabaseUrl = String(process.env.SUPABASE_URL || '').trim(); const supabaseKey = String( process.env.SUPABASE_KEY || process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY || '' ).trim(); if (!supabaseUrl || !supabaseKey) { throw new Error('Missing Supabase credentials. Expected SUPABASE_URL and SUPABASE_KEY/SUPABASE_SERVICE_ROLE_KEY.'); } const supabase = createClient(supabaseUrl, supabaseKey); const args = process.argv.slice(2); const userIdArg = args.find((arg) => arg.startsWith('--user-id='))?.split('=')[1]?.trim(); const dryRun = args.includes('--dry-run'); const PROFILE_SPLIT = { aggressive: 1500, conservative: 1500 }; const buildProfiles = (): SeedProfile[] => { const frequentMomentum: SeedProfile = { name: 'Frequent Momentum Pro', allocated_capital: PROFILE_SPLIT.aggressive, risk_per_trade_percent: 1.2, symbols: 'BTC/USDT, ETH/USDT, SOL/USDT', is_active: true, strategy_config: { rules: [ { ruleId: 'TrendBiasRule', enabled: true, params: { fastPeriod: 20, slowPeriod: 100 } }, { ruleId: 'SessionRule', enabled: true, params: { sessions: ['NY', 'LDN'] } }, { ruleId: 'ZoneRule', enabled: true, params: { timeframe: '15m', zonePercent: 1.2 } }, { ruleId: 'MomentumRule', enabled: true, params: { timeframe: '15m', rsiPeriod: 9, overbought: 72, oversold: 28 } }, { ruleId: 'EntryTriggerRule', enabled: true, params: { timeframe: '15m', wickRatioThreshold: 0.45, enableEmaReclaim: true, enableWickRejection: true } }, { ruleId: 'RiskManagementRule', enabled: true, params: { atrPeriod: 10, slMultiplier: 1.2, maxRisk: 1.2 } }, { ruleId: 'AIAnalysisRule', enabled: false, params: { minConfidence: 70 } } ], riskLimits: { maxDailyLossUsd: 45, maxConsecutiveLosses: 3, maxOpenTrades: 2 }, execution: { orderType: 'market', cooldownMinutes: 8, entryMode: 'both', profitExitPercent: 0.8 } } }; const controlledIntraday: SeedProfile = { name: 'Controlled Intraday Guard', allocated_capital: PROFILE_SPLIT.conservative, risk_per_trade_percent: 0.8, symbols: 'BTC/USDT, ETH/USDT', is_active: true, strategy_config: { rules: [ { ruleId: 'TrendBiasRule', enabled: true, params: { fastPeriod: 50, slowPeriod: 200 } }, { ruleId: 'SessionRule', enabled: true, params: { sessions: ['NY', 'LDN'] } }, { ruleId: 'ZoneRule', enabled: true, params: { timeframe: '1h', zonePercent: 1.0 } }, { ruleId: 'MomentumRule', enabled: true, params: { timeframe: '1h', rsiPeriod: 14, overbought: 68, oversold: 32 } }, { ruleId: 'EntryTriggerRule', enabled: true, params: { timeframe: '1h', wickRatioThreshold: 0.5, enableEmaReclaim: true, enableWickRejection: true } }, { ruleId: 'RiskManagementRule', enabled: true, params: { atrPeriod: 14, slMultiplier: 1.0, maxRisk: 0.9 } }, { ruleId: 'AIAnalysisRule', enabled: false, params: { minConfidence: 75 } } ], riskLimits: { maxDailyLossUsd: 30, maxConsecutiveLosses: 2, maxOpenTrades: 2 }, execution: { orderType: 'market', cooldownMinutes: 18, entryMode: 'long_only', profitExitPercent: 1.0 } } }; return [frequentMomentum, controlledIntraday]; }; const fetchTargetUsers = async (): Promise => { if (userIdArg) return [userIdArg]; const { data, error } = await supabase .from('users') .select('user_id') .eq('trade_enable', true); if (error) { throw new Error(`Failed to fetch active users: ${error.message}`); } const ids = (data || []) .map((row: any) => String(row?.user_id || '').trim()) .filter(Boolean); return Array.from(new Set(ids)); }; const upsertProfileForUser = async (userId: string, profile: SeedProfile): Promise<'inserted' | 'updated'> => { const { data: existing, error: existingError } = await supabase .from('trade_profiles') .select('id') .eq('user_id', userId) .eq('name', profile.name) .order('created_at', { ascending: true }) .limit(1); if (existingError) { throw new Error(`Failed to query existing profile "${profile.name}" for user ${userId}: ${existingError.message}`); } const payload = { user_id: userId, name: profile.name, allocated_capital: profile.allocated_capital, risk_per_trade_percent: profile.risk_per_trade_percent, symbols: profile.symbols, is_active: profile.is_active, strategy_config: profile.strategy_config }; if (existing && existing.length > 0) { if (!dryRun) { const { error: updateError } = await supabase .from('trade_profiles') .update(payload) .eq('id', existing[0].id); if (updateError) { throw new Error(`Failed to update profile "${profile.name}" for user ${userId}: ${updateError.message}`); } } return 'updated'; } if (!dryRun) { const { error: insertError } = await supabase .from('trade_profiles') .insert([payload]); if (insertError) { throw new Error(`Failed to insert profile "${profile.name}" for user ${userId}: ${insertError.message}`); } } return 'inserted'; }; const run = async (): Promise => { const users = await fetchTargetUsers(); const profiles = buildProfiles(); if (!users.length) { console.log(JSON.stringify({ ok: true, dry_run: dryRun, users_targeted: 0, message: 'No active users found.' }, null, 2)); return; } let inserted = 0; let updated = 0; const perUser: Array<{ user_id: string; inserted: number; updated: number }> = []; for (const userId of users) { let userInserted = 0; let userUpdated = 0; for (const profile of profiles) { const result = await upsertProfileForUser(userId, profile); if (result === 'inserted') { inserted += 1; userInserted += 1; } else { updated += 1; userUpdated += 1; } } perUser.push({ user_id: userId, inserted: userInserted, updated: userUpdated }); } console.log(JSON.stringify({ ok: true, dry_run: dryRun, users_targeted: users.length, profiles_per_user: profiles.length, total_allocation_per_user: PROFILE_SPLIT.aggressive + PROFILE_SPLIT.conservative, totals: { inserted, updated }, per_user: perUser }, null, 2)); }; run().catch((error) => { console.error(JSON.stringify({ ok: false, error: error instanceof Error ? error.message : String(error) }, null, 2)); process.exit(1); });