learning_ai_invt_trdg/backend/seedTwoBestProfiles.ts

231 lines
7.9 KiB
TypeScript

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<string, any>;
};
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<string[]> => {
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<void> => {
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);
});