/home/devscfvi/crypto.devsquantum.com/js/goals.js
// ============================================
// GOALS SYSTEM MODULE
// ============================================
// Render Goals Dashboard
function renderGoals() {
const goals = state.goals || [];
const stats = state.stats || {};
const hasGoals = goals.length > 0;
return `
<div class="min-h-screen p-4">
<div class="max-w-4xl mx-auto">
<!-- Header -->
<div class="mb-6 pt-4 flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold text-white mb-1">Trading Goals</h1>
<p class="text-sm text-violet-300">Set targets and track progress</p>
</div>
<button onclick="openNewGoalModal()"
class="gradient-bg text-white px-4 py-2 rounded-xl font-semibold flex items-center gap-2 active:scale-95 transition">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
</svg>
New Goal
</button>
</div>
${!hasGoals ? renderEmptyGoals() : `
<!-- Current Stats Summary -->
<div class="glass rounded-2xl p-4 mb-6 border border-violet-500/20">
<p class="text-sm text-violet-300 mb-3">Current Performance</p>
<div class="grid grid-cols-4 gap-3">
<div class="text-center">
<p class="text-2xl font-bold text-white">${stats.total_trades || 0}</p>
<p class="text-xs text-slate-400">Trades</p>
</div>
<div class="text-center">
<p class="text-2xl font-bold ${parseFloat(stats.win_rate || 0) >= 50 ? 'text-green-400' : 'text-red-400'}">
${stats.win_rate || 0}%
</p>
<p class="text-xs text-slate-400">Win Rate</p>
</div>
<div class="text-center">
<p class="text-2xl font-bold ${parseFloat(stats.net_pnl || 0) >= 0 ? 'text-green-400' : 'text-red-400'}">
${parseFloat(stats.net_pnl || 0) >= 0 ? '+' : ''}${formatCurrency(stats.net_pnl || 0)}
</p>
<p class="text-xs text-slate-400">Net P&L</p>
</div>
<div class="text-center">
<p class="text-2xl font-bold text-violet-400">1:${parseFloat(stats.avg_rr || 0).toFixed(2)}</p>
<p class="text-xs text-slate-400">Avg R:R</p>
</div>
</div>
</div>
<!-- Active Goals -->
<div class="space-y-4">
${goals.map(goal => renderGoalCard(goal)).join('')}
</div>
`}
</div>
</div>
`;
}
// Empty state
function renderEmptyGoals() {
return `
<div class="glass rounded-3xl p-12 text-center border border-violet-500/20">
<svg class="w-16 h-16 mx-auto text-violet-400/50 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"></path>
</svg>
<p class="text-lg text-white mb-2">No Goals Set</p>
<p class="text-sm text-slate-400 mb-6">Set targets to stay motivated and track progress</p>
<button onclick="openNewGoalModal()"
class="gradient-bg text-white px-6 py-3 rounded-xl font-semibold active:scale-95 transition">
Create Your First Goal
</button>
</div>
`;
}
// Render individual goal card
function renderGoalCard(goal) {
const stats = state.stats || {};
const progress = calculateGoalProgress(goal, stats);
const daysLeft = calculateDaysLeft(goal.period_end);
const isExpired = daysLeft < 0;
const isCompleted = progress >= 100;
return `
<div class="glass rounded-3xl p-6 border border-violet-500/20 ${isCompleted ? 'border-green-500/30 bg-green-500/5' : isExpired ? 'opacity-60' : ''}">
<!-- Header -->
<div class="flex items-start justify-between mb-4">
<div class="flex-1">
<div class="flex items-center gap-2 mb-2">
<span class="text-xs px-3 py-1 rounded-full ${getGoalTypeColor(goal.goal_type)}">
${goal.goal_type.toUpperCase()}
</span>
${isCompleted ? '<span class="text-xs px-3 py-1 rounded-full bg-green-500/20 text-green-400 border border-green-500/30">✓ COMPLETED</span>' : ''}
${isExpired && !isCompleted ? '<span class="text-xs px-3 py-1 rounded-full bg-red-500/20 text-red-400 border border-red-500/30">EXPIRED</span>' : ''}
</div>
<h3 class="text-lg font-semibold text-white">${getGoalTitle(goal)}</h3>
<p class="text-sm text-slate-400 mt-1">
${formatDate(goal.period_start)} - ${formatDate(goal.period_end)}
${!isExpired ? `<span class="text-violet-400">(${daysLeft} days left)</span>` : ''}
</p>
</div>
<button onclick="deleteGoal(${goal.id})"
class="text-slate-400 hover:text-red-400 p-2 transition">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
</svg>
</button>
</div>
<!-- Progress -->
${renderGoalProgress(goal, stats, progress)}
<!-- Notes -->
${goal.notes ? `
<div class="mt-4 p-3 bg-blue-500/10 rounded-lg border border-blue-500/20">
<p class="text-xs text-blue-300 mb-1">📝 Notes:</p>
<p class="text-sm text-white">${goal.notes}</p>
</div>
` : ''}
</div>
`;
}
// Render goal progress based on type
function renderGoalProgress(goal, stats, overallProgress) {
let progressHTML = '';
// Target Profit
if (goal.target_profit) {
const current = parseFloat(stats.net_pnl || 0);
const target = parseFloat(goal.target_profit);
const progress = Math.min(100, (current / target) * 100);
progressHTML += `
<div class="mb-4">
<div class="flex items-center justify-between mb-2">
<span class="text-sm text-slate-300">Profit Target</span>
<span class="text-sm font-semibold ${current >= target ? 'text-green-400' : 'text-white'}">
${formatCurrency(current)} / ${formatCurrency(target)}
</span>
</div>
<div class="w-full bg-slate-800 rounded-full h-3">
<div class="h-3 rounded-full ${current >= target ? 'bg-green-500' : 'bg-violet-500'} transition-all"
style="width: ${Math.max(0, progress)}%"></div>
</div>
</div>
`;
}
// Target Win Rate
if (goal.target_win_rate) {
const current = parseFloat(stats.win_rate || 0);
const target = parseFloat(goal.target_win_rate);
const progress = Math.min(100, (current / target) * 100);
progressHTML += `
<div class="mb-4">
<div class="flex items-center justify-between mb-2">
<span class="text-sm text-slate-300">Win Rate Target</span>
<span class="text-sm font-semibold ${current >= target ? 'text-green-400' : 'text-white'}">
${current}% / ${target}%
</span>
</div>
<div class="w-full bg-slate-800 rounded-full h-3">
<div class="h-3 rounded-full ${current >= target ? 'bg-green-500' : 'bg-blue-500'} transition-all"
style="width: ${Math.max(0, progress)}%"></div>
</div>
</div>
`;
}
// Min Risk/Reward
if (goal.min_risk_reward) {
const current = parseFloat(stats.avg_rr || 0);
const target = parseFloat(goal.min_risk_reward);
const progress = Math.min(100, (current / target) * 100);
progressHTML += `
<div class="mb-4">
<div class="flex items-center justify-between mb-2">
<span class="text-sm text-slate-300">Min R:R Target</span>
<span class="text-sm font-semibold ${current >= target ? 'text-green-400' : 'text-white'}">
1:${current.toFixed(2)} / 1:${target.toFixed(2)}
</span>
</div>
<div class="w-full bg-slate-800 rounded-full h-3">
<div class="h-3 rounded-full ${current >= target ? 'bg-green-500' : 'bg-yellow-500'} transition-all"
style="width: ${Math.max(0, progress)}%"></div>
</div>
</div>
`;
}
// Max Daily Loss
if (goal.max_daily_loss) {
const target = parseFloat(goal.max_daily_loss);
progressHTML += `
<div class="mb-4">
<div class="p-3 bg-red-500/10 rounded-lg border border-red-500/20">
<div class="flex items-center justify-between">
<span class="text-sm text-red-300">⚠️ Max Daily Loss Limit</span>
<span class="text-sm font-semibold text-red-400">-${formatCurrency(target)}</span>
</div>
<p class="text-xs text-slate-400 mt-1">Stop trading if you hit this limit</p>
</div>
</div>
`;
}
return progressHTML;
}
// Calculate overall goal progress
function calculateGoalProgress(goal, stats) {
let completedTargets = 0;
let totalTargets = 0;
if (goal.target_profit) {
totalTargets++;
if (parseFloat(stats.net_pnl || 0) >= parseFloat(goal.target_profit)) {
completedTargets++;
}
}
if (goal.target_win_rate) {
totalTargets++;
if (parseFloat(stats.win_rate || 0) >= parseFloat(goal.target_win_rate)) {
completedTargets++;
}
}
if (goal.min_risk_reward) {
totalTargets++;
if (parseFloat(stats.avg_rr || 0) >= parseFloat(goal.min_risk_reward)) {
completedTargets++;
}
}
return totalTargets > 0 ? (completedTargets / totalTargets) * 100 : 0;
}
// Calculate days left
function calculateDaysLeft(endDate) {
const end = new Date(endDate);
const today = new Date();
today.setHours(0, 0, 0, 0);
const diffTime = end - today;
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return diffDays;
}
// Get goal type color
function getGoalTypeColor(type) {
const colors = {
'daily': 'bg-blue-500/20 text-blue-400 border border-blue-500/30',
'weekly': 'bg-green-500/20 text-green-400 border border-green-500/30',
'monthly': 'bg-purple-500/20 text-purple-400 border border-purple-500/30',
'yearly': 'bg-yellow-500/20 text-yellow-400 border border-yellow-500/30'
};
return colors[type] || colors['daily'];
}
// Get goal title
function getGoalTitle(goal) {
const targets = [];
if (goal.target_profit) targets.push(`$${goal.target_profit} profit`);
if (goal.target_win_rate) targets.push(`${goal.target_win_rate}% win rate`);
if (goal.min_risk_reward) targets.push(`1:${goal.min_risk_reward} R:R`);
if (goal.max_daily_loss) targets.push(`Max -$${goal.max_daily_loss} daily loss`);
return targets.join(' • ') || 'Trading Goal';
}
// Open new goal modal
function openNewGoalModal() {
const today = new Date().toISOString().split('T')[0];
const weekLater = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
const modalContent = `
<div class="p-6">
<h2 class="text-2xl font-bold text-white mb-6">Create New Goal</h2>
<!-- Goal Type -->
<div class="mb-4">
<label class="block text-sm font-medium text-violet-200 mb-2">Goal Period</label>
<select id="goal-type" onchange="updateGoalDates()"
class="w-full px-4 py-3 bg-slate-900/50 border border-violet-500/30 rounded-xl text-white">
<option value="daily">Daily</option>
<option value="weekly" selected>Weekly</option>
<option value="monthly">Monthly</option>
<option value="yearly">Yearly</option>
</select>
</div>
<!-- Date Range -->
<div class="grid grid-cols-2 gap-4 mb-4">
<div>
<label class="block text-sm font-medium text-violet-200 mb-2">Start Date</label>
<input type="date" id="goal-start" value="${today}"
class="w-full px-4 py-3 bg-slate-900/50 border border-violet-500/30 rounded-xl text-white">
</div>
<div>
<label class="block text-sm font-medium text-violet-200 mb-2">End Date</label>
<input type="date" id="goal-end" value="${weekLater}"
class="w-full px-4 py-3 bg-slate-900/50 border border-violet-500/30 rounded-xl text-white">
</div>
</div>
<!-- Targets -->
<div class="mb-4">
<label class="block text-sm font-medium text-violet-200 mb-2">Target Profit ($)</label>
<input type="number" id="goal-profit" step="0.01" placeholder="e.g., 1000"
class="w-full px-4 py-3 bg-slate-900/50 border border-violet-500/30 rounded-xl text-white">
</div>
<div class="mb-4">
<label class="block text-sm font-medium text-violet-200 mb-2">Target Win Rate (%)</label>
<input type="number" id="goal-winrate" step="0.01" placeholder="e.g., 70"
class="w-full px-4 py-3 bg-slate-900/50 border border-violet-500/30 rounded-xl text-white">
</div>
<div class="mb-4">
<label class="block text-sm font-medium text-violet-200 mb-2">Min Risk/Reward Ratio</label>
<input type="number" id="goal-rr" step="0.01" placeholder="e.g., 2.5"
class="w-full px-4 py-3 bg-slate-900/50 border border-violet-500/30 rounded-xl text-white">
</div>
<div class="mb-4">
<label class="block text-sm font-medium text-violet-200 mb-2">Max Daily Loss ($)</label>
<input type="number" id="goal-maxloss" step="0.01" placeholder="e.g., 100"
class="w-full px-4 py-3 bg-slate-900/50 border border-violet-500/30 rounded-xl text-white">
</div>
<!-- Notes -->
<div class="mb-6">
<label class="block text-sm font-medium text-violet-200 mb-2">Notes (optional)</label>
<textarea id="goal-notes" rows="2"
class="w-full px-4 py-3 bg-slate-900/50 border border-violet-500/30 rounded-xl text-white"
placeholder="Why this goal? What will you focus on?"></textarea>
</div>
<p class="text-xs text-slate-400 mb-4">💡 Tip: Set at least one target (profit, win rate, or R:R)</p>
<!-- Buttons -->
<button onclick="saveNewGoal()"
class="w-full gradient-bg text-white py-4 rounded-xl font-bold text-lg shadow-lg active:scale-95 transition mb-3">
Create Goal
</button>
<button onclick="closeAllModals()"
class="w-full bg-slate-700 hover:bg-slate-600 text-white py-3 rounded-xl font-semibold active:scale-95 transition">
Cancel
</button>
</div>
`;
createModal(modalContent);
}
// Update goal dates based on type
function updateGoalDates() {
const type = document.getElementById('goal-type').value;
const startInput = document.getElementById('goal-start');
const endInput = document.getElementById('goal-end');
const start = new Date(startInput.value || new Date());
let end = new Date(start);
switch(type) {
case 'daily':
end = new Date(start);
break;
case 'weekly':
end.setDate(start.getDate() + 7);
break;
case 'monthly':
end.setMonth(start.getMonth() + 1);
break;
case 'yearly':
end.setFullYear(start.getFullYear() + 1);
break;
}
endInput.value = end.toISOString().split('T')[0];
}
// Save new goal
async function saveNewGoal() {
const goalData = {
goalType: document.getElementById('goal-type').value,
periodStart: document.getElementById('goal-start').value,
periodEnd: document.getElementById('goal-end').value,
targetProfit: document.getElementById('goal-profit').value || null,
targetWinRate: document.getElementById('goal-winrate').value || null,
minRiskReward: document.getElementById('goal-rr').value || null,
maxDailyLoss: document.getElementById('goal-maxloss').value || null,
notes: document.getElementById('goal-notes').value || ''
};
// Validation
if (!goalData.targetProfit && !goalData.targetWinRate && !goalData.minRiskReward && !goalData.maxDailyLoss) {
showToast('Please set at least one target', 'error');
return;
}
const result = await apiPost('saveGoal', goalData);
if (result.success) {
showToast('Goal created successfully! 🎯', 'success');
closeAllModals();
loadGoals();
} else {
showToast('Failed to create goal', 'error');
}
}
// Delete goal
async function deleteGoal(id) {
if (!confirm('Delete this goal?')) return;
const result = await apiPost('deleteGoal', { id });
if (result.success) {
showToast('Goal deleted', 'success');
loadGoals();
} else {
showToast('Failed to delete goal', 'error');
}
}
// Initialize Goals
function initGoalsCharts() {
// Goals don't have charts yet, but placeholder for future
}
// Make functions global
window.renderGoals = renderGoals;
window.initGoalsCharts = initGoalsCharts;
window.openNewGoalModal = openNewGoalModal;
window.updateGoalDates = updateGoalDates;
window.saveNewGoal = saveNewGoal;
window.deleteGoal = deleteGoal;