|
|
|
let apiConnected = false; |
|
let affirmationTimerInterval; |
|
let affirmationTimeLeft = 300; |
|
let affirmationTimerRunning = false; |
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
initTabs(); |
|
updateDateTime(); |
|
fetchAndUpdateTicker(); |
|
checkStoredApiKey(); |
|
|
|
|
|
setInterval(updateDateTime, 1000); |
|
setInterval(fetchAndUpdateTicker, 60000); |
|
setInterval(updateActiveTrades, 5000); |
|
|
|
|
|
checkPresetGeminiKey(); |
|
}); |
|
|
|
|
|
function checkPresetGeminiKey() { |
|
fetch('/api/gemini_key') |
|
.then(response => response.json()) |
|
.then(data => { |
|
if (data.status === 'available') { |
|
|
|
document.getElementById('geminiApiKey').value = data.key; |
|
|
|
connectApi(true); |
|
} |
|
}) |
|
.catch(error => console.error('Error checking preset Gemini key:', error)); |
|
} |
|
|
|
|
|
function initTabs() { |
|
const tabs = document.querySelectorAll('.tab'); |
|
|
|
tabs.forEach(tab => { |
|
tab.addEventListener('click', function() { |
|
|
|
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); |
|
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); |
|
|
|
|
|
this.classList.add('active'); |
|
|
|
|
|
const tabId = this.getAttribute('data-tab'); |
|
document.getElementById(tabId).classList.add('active'); |
|
|
|
|
|
if (tabId === 'sessions') { |
|
loadSessionsData(); |
|
} else if (tabId === 'intermarket') { |
|
loadIntermarketData(); |
|
} |
|
}); |
|
}); |
|
} |
|
|
|
|
|
function loadSessionsData() { |
|
|
|
fetch('/api/market_data/asia') |
|
.then(response => response.json()) |
|
.then(data => { |
|
updateMarketMovements('asia', data); |
|
}) |
|
.catch(error => console.error('Error loading Asia data:', error)); |
|
|
|
|
|
fetch('/api/market_data/europe') |
|
.then(response => response.json()) |
|
.then(data => { |
|
updateMarketMovements('europe', data); |
|
}) |
|
.catch(error => console.error('Error loading Europe data:', error)); |
|
|
|
|
|
fetch('/api/market_data/us') |
|
.then(response => response.json()) |
|
.then(data => { |
|
updateMarketMovements('us', data); |
|
}) |
|
.catch(error => console.error('Error loading US data:', error)); |
|
|
|
|
|
fetch('/api/economic_calendar') |
|
.then(response => response.json()) |
|
.then(data => { |
|
updateEconomicCalendar(data); |
|
}) |
|
.catch(error => console.error('Error loading economic calendar:', error)); |
|
} |
|
|
|
|
|
function loadIntermarketData() { |
|
fetch('/api/intermarket') |
|
.then(response => response.json()) |
|
.then(data => { |
|
updateIntermarketDisplay(data); |
|
}) |
|
.catch(error => console.error('Error loading intermarket data:', error)); |
|
} |
|
|
|
|
|
function updateMarketMovements(region, data) { |
|
|
|
let container; |
|
if (region === 'asia') { |
|
container = document.querySelector('#sessions .section:nth-child(1) .widget:nth-child(4)'); |
|
} else if (region === 'europe') { |
|
container = document.querySelector('#sessions .section:nth-child(2) .widget:nth-child(4)'); |
|
} else if (region === 'us') { |
|
container = document.querySelector('#sessions .section:nth-child(3) .widget:nth-child(4)'); |
|
} |
|
|
|
if (!container) return; |
|
|
|
|
|
container.innerHTML = '<div class="widget-title">Marktbewegungen</div>'; |
|
|
|
|
|
data.forEach(market => { |
|
const marketItem = document.createElement('div'); |
|
marketItem.className = 'market-item'; |
|
marketItem.innerHTML = ` |
|
<div class="market-name">${market.name}</div> |
|
<div class="market-value ${market.direction}">${market.direction === 'up' ? '+' : '-'}${market.change_percent.toFixed(1)}%</div> |
|
`; |
|
container.appendChild(marketItem); |
|
}); |
|
} |
|
|
|
|
|
function updateEconomicCalendar(events) { |
|
|
|
const containers = [ |
|
document.querySelector('#sessions .section:nth-child(1) .widget:nth-child(3)'), |
|
document.querySelector('#sessions .section:nth-child(2) .widget:nth-child(3)'), |
|
document.querySelector('#sessions .section:nth-child(3) .widget:nth-child(3)') |
|
]; |
|
|
|
containers.forEach(container => { |
|
if (!container) return; |
|
|
|
|
|
const title = container.querySelector('.widget-title'); |
|
const titleHTML = title ? title.outerHTML : '<div class="widget-title">Wirtschaftskalender</div>'; |
|
|
|
|
|
container.innerHTML = titleHTML; |
|
|
|
|
|
events.slice(0, 3).forEach(event => { |
|
const eventItem = document.createElement('div'); |
|
eventItem.className = 'event-item'; |
|
|
|
|
|
let impactClass = 'low'; |
|
if (event.impact === 'high') impactClass = 'high'; |
|
else if (event.impact === 'medium') impactClass = 'medium'; |
|
|
|
eventItem.innerHTML = ` |
|
<div class="flex justify-between"> |
|
<div> |
|
<span class="event-impact ${impactClass}"></span> |
|
<span class="event-name">${event.country} ${event.event}</span> |
|
</div> |
|
<div class="event-time">${new Date(event.time).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</div> |
|
</div> |
|
<div class="text-sm text-gray-600 mt-1"> |
|
Prognose: ${event.forecast || 'N/A'} | Vorher: ${event.previous || 'N/A'} |
|
</div> |
|
`; |
|
container.appendChild(eventItem); |
|
}); |
|
}); |
|
} |
|
|
|
|
|
function updateIntermarketDisplay(data) { |
|
|
|
const correlationCells = document.querySelectorAll('.correlation-matrix .matrix-table tbody td:not(:first-child)'); |
|
|
|
if (correlationCells.length > 0 && data.correlations) { |
|
|
|
const correlationMap = [ |
|
null, 'stocks_bonds', 'stocks_commodities', 'stocks_dollar', |
|
'stocks_bonds', null, 'bonds_commodities', 'bonds_dollar', |
|
'stocks_commodities', 'bonds_commodities', null, 'commodities_dollar', |
|
'stocks_dollar', 'bonds_dollar', 'commodities_dollar', null |
|
]; |
|
|
|
correlationCells.forEach((cell, index) => { |
|
const key = correlationMap[index]; |
|
if (key && data.correlations[key] !== undefined) { |
|
const value = data.correlations[key]; |
|
cell.textContent = value.toFixed(2); |
|
|
|
|
|
cell.className = ''; |
|
if (value > 0.3) cell.classList.add('correlation-positive'); |
|
else if (value < -0.3) cell.classList.add('correlation-negative'); |
|
else cell.classList.add('correlation-neutral'); |
|
} |
|
}); |
|
} |
|
|
|
|
|
if (data.assets) { |
|
|
|
console.log('Asset data available for further display:', data.assets); |
|
} |
|
} |
|
|
|
|
|
function saveApiKey() { |
|
const keyInput = document.getElementById('geminiApiKey'); |
|
if (keyInput.value.trim()) { |
|
const apiKey = keyInput.value.trim(); |
|
localStorage.setItem('geminiApiKey', apiKey); |
|
|
|
keyInput.style.borderColor = '#2ecc71'; |
|
setTimeout(() => { |
|
keyInput.style.borderColor = ''; |
|
}, 2000); |
|
|
|
return apiKey; |
|
} |
|
return null; |
|
} |
|
|
|
function checkStoredApiKey() { |
|
const storedKey = localStorage.getItem('geminiApiKey'); |
|
if (storedKey) { |
|
document.getElementById('geminiApiKey').value = storedKey; |
|
|
|
|
|
} |
|
} |
|
|
|
function connectApi(isPreset = false) { |
|
let apiKey; |
|
|
|
if (isPreset) { |
|
apiKey = document.getElementById('geminiApiKey').value; |
|
} else { |
|
apiKey = saveApiKey(); |
|
if (!apiKey) { |
|
alert('Bitte gib einen API-Schlüssel ein.'); |
|
return; |
|
} |
|
} |
|
|
|
const connectText = document.getElementById('connectText'); |
|
const apiSpinner = document.getElementById('apiSpinner'); |
|
const apiStatus = document.getElementById('apiStatus'); |
|
|
|
|
|
connectText.textContent = 'Verbinde...'; |
|
apiSpinner.style.display = 'inline-block'; |
|
|
|
|
|
setTimeout(() => { |
|
|
|
apiConnected = true; |
|
connectText.textContent = 'Verbunden'; |
|
apiSpinner.style.display = 'none'; |
|
apiStatus.classList.add('connected'); |
|
|
|
|
|
const connectButton = document.querySelector('button[onclick="connectApi()"]'); |
|
connectButton.style.backgroundColor = '#2ecc71'; |
|
connectButton.disabled = true; |
|
|
|
|
|
addChatMessage('ai', 'API-Verbindung hergestellt! Ich bin jetzt bereit, dir mit KI-gestützten Trading-Analysen zu helfen. Frag mich etwas zu Märkten, Trading-Strategien oder deinen aktuellen Trades.'); |
|
}, 2000); |
|
} |
|
|
|
|
|
function updateDateTime() { |
|
const now = new Date(); |
|
|
|
|
|
updateSessionStatus(now); |
|
|
|
|
|
updateCountdowns(now); |
|
} |
|
|
|
function updateSessionStatus(now) { |
|
const hour = now.getUTCHours(); |
|
const isWeekend = now.getUTCDay() === 0 || now.getUTCDay() === 6; |
|
|
|
|
|
fetch('/api/market_overview') |
|
.then(response => response.json()) |
|
.then(data => { |
|
const marketStatus = data.market_status; |
|
|
|
|
|
if (!isWeekend && marketStatus === 'open') { |
|
|
|
if (hour >= 23 || hour < 8) { |
|
updateSessionElement('asia', 'active'); |
|
updateSessionElement('europe', 'upcoming'); |
|
updateSessionElement('us', 'closed'); |
|
} |
|
|
|
else if (hour >= 8 && hour < 16) { |
|
updateSessionElement('asia', 'closed'); |
|
updateSessionElement('europe', 'active'); |
|
updateSessionElement('us', 'upcoming'); |
|
} |
|
|
|
else if (hour >= 16 && hour < 20) { |
|
updateSessionElement('asia', 'upcoming'); |
|
updateSessionElement('europe', 'closed'); |
|
updateSessionElement('us', 'active'); |
|
} |
|
|
|
else { |
|
updateSessionElement('asia', 'upcoming'); |
|
updateSessionElement('europe', 'closed'); |
|
updateSessionElement('us', 'closed'); |
|
} |
|
} else { |
|
|
|
updateSessionElement('asia', 'closed'); |
|
updateSessionElement('europe', 'closed'); |
|
updateSessionElement('us', 'closed'); |
|
} |
|
}) |
|
.catch(error => { |
|
console.error('Error fetching market status:', error); |
|
|
|
if (!isWeekend) { |
|
if (hour >= 23 || hour < 8) { |
|
updateSessionElement('asia', 'active'); |
|
updateSessionElement('europe', 'upcoming'); |
|
updateSessionElement('us', 'closed'); |
|
} else if (hour >= 8 && hour < 16) { |
|
updateSessionElement('asia', 'closed'); |
|
updateSessionElement('europe', 'active'); |
|
updateSessionElement('us', 'upcoming'); |
|
} else if (hour >= 16 && hour < 20) { |
|
updateSessionElement('asia', 'upcoming'); |
|
updateSessionElement('europe', 'closed'); |
|
updateSessionElement('us', 'active'); |
|
} else { |
|
updateSessionElement('asia', 'upcoming'); |
|
updateSessionElement('europe', 'closed'); |
|
updateSessionElement('us', 'closed'); |
|
} |
|
} else { |
|
updateSessionElement('asia', 'closed'); |
|
updateSessionElement('europe', 'closed'); |
|
updateSessionElement('us', 'closed'); |
|
} |
|
}); |
|
} |
|
|
|
function updateSessionElement(session, status) { |
|
|
|
let sessionCard; |
|
let statusElement; |
|
|
|
if (session === 'asia') { |
|
sessionCard = document.querySelector('#sessions .section:nth-child(1) .session-card'); |
|
statusElement = sessionCard ? sessionCard.querySelector('.session-status') : null; |
|
} else if (session === 'europe') { |
|
sessionCard = document.querySelector('#sessions .section:nth-child(2) .session-card'); |
|
statusElement = sessionCard ? sessionCard.querySelector('.session-status') : null; |
|
} else if (session === 'us') { |
|
sessionCard = document.querySelector('#sessions .section:nth-child(3) .session-card'); |
|
statusElement = sessionCard ? sessionCard.querySelector('.session-status') : null; |
|
} |
|
|
|
if (statusElement) { |
|
|
|
statusElement.classList.remove('bg-green-500', 'bg-yellow-500', 'bg-gray-500'); |
|
|
|
|
|
if (status === 'active') { |
|
statusElement.classList.add('bg-green-500'); |
|
statusElement.textContent = 'Aktiv'; |
|
} else if (status === 'upcoming') { |
|
statusElement.classList.add('bg-yellow-500'); |
|
statusElement.textContent = 'Beginnt bald'; |
|
} else if (status === 'closed') { |
|
statusElement.classList.add('bg-gray-500'); |
|
statusElement.textContent = 'Geschlossen'; |
|
} |
|
} |
|
|
|
|
|
if (sessionCard) { |
|
const timeInfo = sessionCard.querySelector('.mt-4 p.text-sm'); |
|
if (timeInfo) { |
|
const now = new Date(); |
|
const hour = now.getHours(); |
|
const minute = now.getMinutes(); |
|
|
|
if (status === 'active') { |
|
|
|
let hoursLeft = 0; |
|
let minutesLeft = 0; |
|
|
|
if (session === 'asia') { |
|
hoursLeft = 9 - hour; |
|
if (hoursLeft < 0) hoursLeft += 24; |
|
} else if (session === 'europe') { |
|
hoursLeft = 16 - hour; |
|
} else if (session === 'us') { |
|
hoursLeft = 21 - hour; |
|
} |
|
|
|
minutesLeft = 60 - minute; |
|
if (minutesLeft === 60) { |
|
minutesLeft = 0; |
|
} else { |
|
hoursLeft -= 1; |
|
} |
|
|
|
timeInfo.textContent = `Aktive Session läuft noch ${hoursLeft}h ${minutesLeft}min`; |
|
} else if (status === 'upcoming') { |
|
|
|
let hoursLeft = 0; |
|
let minutesLeft = 0; |
|
|
|
if (session === 'asia') { |
|
hoursLeft = 24 - hour; |
|
} else if (session === 'europe') { |
|
hoursLeft = 8 - hour; |
|
if (hoursLeft < 0) hoursLeft += 24; |
|
} else if (session === 'us') { |
|
hoursLeft = 14 - hour; |
|
if (hoursLeft < 0) hoursLeft += 24; |
|
} |
|
|
|
if (minute > 0) { |
|
hoursLeft -= 1; |
|
minutesLeft = 60 - minute; |
|
} |
|
|
|
timeInfo.textContent = `Session beginnt in ${hoursLeft}h ${minutesLeft}min`; |
|
} else { |
|
|
|
let hoursLeft = 0; |
|
let minutesLeft = 0; |
|
|
|
if (session === 'asia') { |
|
hoursLeft = 24 - hour; |
|
} else if (session === 'europe') { |
|
hoursLeft = 8 - hour; |
|
if (hoursLeft < 0) hoursLeft += 24; |
|
} else if (session === 'us') { |
|
hoursLeft = 14 - hour; |
|
if (hoursLeft < 0) hoursLeft += 24; |
|
} |
|
|
|
if (minute > 0) { |
|
hoursLeft -= 1; |
|
minutesLeft = 60 - minute; |
|
} |
|
|
|
timeInfo.textContent = `Nächste Session beginnt in ${hoursLeft}h ${minutesLeft}min`; |
|
} |
|
} |
|
} |
|
} |
|
|
|
function updateCountdowns(now) { |
|
|
|
|
|
} |
|
|
|
|
|
function fetchAndUpdateTicker() { |
|
fetch('/api/ticker?symbols=AAPL,NVDA,ASML,BNBUSDT,INTC,BTCUSD,XAUUSD,USDJPY,GER40,EURUSD') |
|
.then(response => response.json()) |
|
.then(data => { |
|
updateTickerUI(data); |
|
}) |
|
.catch(error => { |
|
console.error('Error fetching ticker data:', error); |
|
|
|
updateTickerWithRandomData(); |
|
}); |
|
} |
|
|
|
|
|
function updateTickerUI(data) { |
|
const tickerContainer = document.querySelector('.ticker-content'); |
|
if (!tickerContainer) return; |
|
|
|
|
|
tickerContainer.innerHTML = ''; |
|
|
|
|
|
data.forEach(item => { |
|
const tickerItem = document.createElement('div'); |
|
tickerItem.className = `ticker-item ${item.direction}`; |
|
|
|
const symbolPrefix = getSymbolPrefix(item.symbol); |
|
|
|
tickerItem.innerHTML = `${symbolPrefix}:${item.symbol} <span>${item.price.toFixed(2)}</span> |
|
<i class="fas fa-caret-${item.direction}"></i> ${item.change_percent.toFixed(1)}%`; |
|
|
|
tickerContainer.appendChild(tickerItem); |
|
}); |
|
} |
|
|
|
|
|
function getSymbolPrefix(symbol) { |
|
const prefixMap = { |
|
'AAPL': 'NASDAQ', |
|
'NVDA': 'NASDAQ', |
|
'ASML': 'NASDAQ', |
|
'INTC': 'NASDAQ', |
|
'BNBUSDT': 'BINANCE', |
|
'BTCUSD': 'CAPITALCOM', |
|
'XAUUSD': 'PEPPERSTONE', |
|
'USDJPY': 'FX', |
|
'GER40': 'PEPPERSTONE', |
|
'EURUSD': 'PEPPERSTONE' |
|
}; |
|
|
|
return prefixMap[symbol] || 'SYMBOL'; |
|
} |
|
|
|
|
|
function updateTickerWithRandomData() { |
|
const tickerItems = document.querySelectorAll('.ticker-item'); |
|
|
|
tickerItems.forEach(item => { |
|
|
|
const randomChange = (Math.random() * 0.2 - 0.1).toFixed(2); |
|
const currentValue = parseFloat(item.querySelector('span').textContent); |
|
const newValue = (currentValue * (1 + randomChange / 100)).toFixed(2); |
|
|
|
item.querySelector('span').textContent = newValue; |
|
|
|
|
|
if (randomChange > 0) { |
|
item.classList.remove('down'); |
|
item.classList.add('up'); |
|
item.querySelector('i').className = 'fas fa-caret-up'; |
|
} else { |
|
item.classList.remove('up'); |
|
item.classList.add('down'); |
|
item.querySelector('i').className = 'fas fa-caret-down'; |
|
} |
|
|
|
|
|
const percentEl = item.querySelector('i').nextSibling; |
|
percentEl.textContent = ` ${Math.abs(randomChange)}%`; |
|
}); |
|
} |
|
|
|
|
|
function updateAffirmation() { |
|
const status = document.getElementById('traderStatus').value; |
|
let affirmationText = ''; |
|
let categoryText = ''; |
|
|
|
switch (status) { |
|
case 'preparing': |
|
affirmationText = 'Ich bereite mich gründlich vor und analysiere den Markt mit Klarheit und Fokus. Ich warte geduldig auf hochwertige Setups und halte mich an meine Handelsstrategie.'; |
|
categoryText = 'Geduld und Vorbereitung'; |
|
break; |
|
case 'active': |
|
affirmationText = 'Ich bleibe ruhig und diszipliniert während mein Trade läuft. Ich halte mich an meinen Plan und lasse keine Emotionen meine Entscheidungen beeinflussen.'; |
|
categoryText = 'Emotionale Kontrolle'; |
|
break; |
|
case 'developing': |
|
affirmationText = 'Ich entwickle meine Strategie mit Klarheit und Weisheit. Ich ziehe Lehren aus vergangenen Trades und verbessere kontinuierlich meine Fähigkeiten.'; |
|
categoryText = 'Strategische Entwicklung'; |
|
break; |
|
case 'break': |
|
affirmationText = 'Ich nehme mir bewusst Zeit für Erholung und Reflexion. Diese Pause stärkt meine Handelsleistung und schärft meinen Fokus.'; |
|
categoryText = 'Erholung und Reflexion'; |
|
break; |
|
} |
|
|
|
document.getElementById('currentAffirmation').textContent = affirmationText; |
|
document.getElementById('affirmationCategory').textContent = categoryText; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function toggleChat() { |
|
const chatPopup = document.getElementById('chatPopup'); |
|
const chevron = document.getElementById('chatChevron'); |
|
|
|
if (chatPopup.classList.contains('minimized')) { |
|
chatPopup.classList.remove('minimized'); |
|
chevron.className = 'fas fa-chevron-down'; |
|
} else { |
|
chatPopup.classList.add('minimized'); |
|
chevron.className = 'fas fa-chevron-up'; |
|
} |
|
} |
|
|
|
function sendChatMessage() { |
|
const chatInput = document.getElementById('chatInput'); |
|
const message = chatInput.value.trim(); |
|
|
|
if (!message) return; |
|
|
|
|
|
addChatMessage('user', message); |
|
|
|
|
|
chatInput.value = ''; |
|
|
|
|
|
if (!apiConnected) { |
|
addChatMessage('ai', 'Bitte verbinde zuerst die Gemini API, um mit dem Chat zu interagieren.'); |
|
return; |
|
} |
|
|
|
|
|
showTypingIndicator(); |
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
|
removeTypingIndicator(); |
|
|
|
|
|
const response = generateChatResponse(message); |
|
|
|
|
|
addChatMessage('ai', response); |
|
|
|
|
|
scrollChatToBottom(); |
|
}, 1500); |
|
} |
|
|
|
|
|
function startAffirmationTimer() { |
|
if (affirmationTimerRunning) return; |
|
|
|
document.getElementById('startTimerBtn').disabled = true; |
|
document.getElementById('pauseTimerBtn').disabled = false; |
|
|
|
affirmationTimerRunning = true; |
|
|
|
affirmationTimerInterval = setInterval(() => { |
|
affirmationTimeLeft--; |
|
|
|
const minutes = Math.floor(affirmationTimeLeft / 60); |
|
const seconds = affirmationTimeLeft % 60; |
|
|
|
document.getElementById('affirmationTimer').textContent = |
|
`${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; |
|
|
|
if (affirmationTimeLeft <= 0) { |
|
clearInterval(affirmationTimerInterval); |
|
affirmationTimerRunning = false; |
|
document.getElementById('startTimerBtn').disabled = false; |
|
document.getElementById('pauseTimerBtn').disabled = true; |
|
document.getElementById('affirmationTimer').textContent = "00:00"; |
|
|
|
|
|
} |
|
}, 1000); |
|
} |
|
|
|
function pauseAffirmationTimer() { |
|
clearInterval(affirmationTimerInterval); |
|
affirmationTimerRunning = false; |
|
document.getElementById('startTimerBtn').disabled = false; |
|
document.getElementById('pauseTimerBtn').disabled = true; |
|
} |
|
|
|
function resetAffirmationTimer() { |
|
clearInterval(affirmationTimerInterval); |
|
affirmationTimerRunning = false; |
|
affirmationTimeLeft = 300; |
|
document.getElementById('affirmationTimer').textContent = "05:00"; |
|
document.getElementById('startTimerBtn').disabled = false; |
|
document.getElementById('pauseTimerBtn').disabled = true; |
|
} |
|
|
|
function saveCurrentAffirmation() { |
|
const affirmationText = document.getElementById('detailedAffirmation').querySelector('.affirmation-text').textContent; |
|
const categoryText = document.getElementById('detailedAffirmation').querySelector('.affirmation-category').textContent; |
|
|
|
|
|
const shortAffirmation = affirmationText.substring(0, 60) + (affirmationText.length > 60 ? '...' : ''); |
|
|
|
|
|
const newSavedAffirmation = document.createElement('div'); |
|
newSavedAffirmation.className = 'event-item'; |
|
newSavedAffirmation.innerHTML = ` |
|
<div class="font-medium">${shortAffirmation}</div> |
|
<div class="text-sm text-gray-600">Kategorie: ${categoryText}</div> |
|
`; |
|
|
|
|
|
const savedAffirmationsContainer = document.getElementById('savedAffirmations'); |
|
savedAffirmationsContainer.prepend(newSavedAffirmation); |
|
|
|
|
|
alert('Affirmation gespeichert!'); |
|
} |
|
|
|
|
|
function updateAffirmationTab() { |
|
const status = document.getElementById('affirmationTraderStatus').value; |
|
const category = document.getElementById('affirmationCategory').value; |
|
|
|
|
|
const affirmations = { |
|
discipline: { |
|
preparing: 'Ich bin ein disziplinierter und geduldiger Trader, der seinem Handelsplan mit unerschütterlichem Engagement folgt. Ich vertraue auf die Wirksamkeit meiner Strategien und warte geduldig auf Setups mit hoher Wahrscheinlichkeit. Ich habe die Selbstkontrolle, mich an meine etablierten Regeln zu halten und impulsive Handlungen zu vermeiden.', |
|
active: 'Während mein Trade aktiv ist, bleibe ich diszipliniert und geduldig. Ich folge meinem Plan und erlaube keiner Emotion, meine Strategie zu untergraben. Ich bin ruhig und kontrolliert, selbst wenn der Markt volatil wird.', |
|
developing: 'Beim Entwickeln meiner Strategie wende ich Disziplin und Geduld an. Ich nehme mir die Zeit, jedes Detail zu durchdenken und teste gründlich, bevor ich handele. Qualität geht vor Schnelligkeit.', |
|
break: 'In dieser Pause pflege ich meine Disziplin und Geduld, indem ich reflektiere und lerne. Ich verstehe, dass Ruhezeiten wesentlich für nachhaltigen Trading-Erfolg sind.' |
|
}, |
|
abundance: { |
|
preparing: 'Ich ziehe reichlich Handelsmöglichkeiten an, die mit meiner Strategie übereinstimmen. Der Markt bietet einen endlosen Strom von Gelegenheiten, und ich bin bereit, sie zu nutzen.', |
|
active: 'Mein aktueller Trade ist eine von vielen Gelegenheiten für Wohlstand. Ich denke in Fülle und weiß, dass unabhängig vom Ausgang dieses Trades weitere profitable Chancen folgen werden.', |
|
developing: 'Ich entwickle meine Strategie mit einer Überfluss-Denkweise. Ich erkenne die unbegrenzten Möglichkeiten des Marktes an und erschaffe einen Ansatz, der diesen Reichtum anzieht.', |
|
break: 'Während dieser Pause ziehe ich neue Erkenntnisse und Möglichkeiten an. Ich nutze diese Zeit, um meine Überfluss-Denkweise zu stärken und mich auf neue Handelschancen vorzubereiten.' |
|
} |
|
|
|
}; |
|
|
|
|
|
let affirmationText = 'Ich handle mit Klarheit, Disziplin und Vertrauen.'; |
|
if (affirmations[category] && affirmations[category][status]) { |
|
affirmationText = affirmations[category][status]; |
|
} |
|
|
|
|
|
document.getElementById('detailedAffirmation').querySelector('.affirmation-text').textContent = affirmationText; |
|
|
|
|
|
let categoryDisplayText = ''; |
|
switch (category) { |
|
case 'discipline': categoryDisplayText = 'Disziplin und Geduld entwickeln'; break; |
|
case 'abundance': categoryDisplayText = 'Überflussdenken fördern'; break; |
|
case 'selection': categoryDisplayText = 'Handelsauswahl verbessern'; break; |
|
case 'burnout': categoryDisplayText = 'Burnout und Erschöpfung überwinden'; break; |
|
case 'bias': categoryDisplayText = 'Bestätigungsfehler überwinden'; break; |
|
case 'paralysis': categoryDisplayText = 'Entscheidungslähmung überwinden'; break; |
|
case 'fomo': categoryDisplayText = 'FOMO überwinden'; break; |
|
case 'losses': categoryDisplayText = 'Verluste schnell akzeptieren'; break; |
|
case 'emotional': categoryDisplayText = 'Emotionale Bindung an Trades lösen'; break; |
|
case 'overtrading': categoryDisplayText = 'Überhandel widerstehen'; break; |
|
case 'patience': categoryDisplayText = 'Geduld bei langsamen Märkten bewahren'; break; |
|
default: categoryDisplayText = 'Allgemeine Trading-Affirmation'; |
|
} |
|
|
|
document.getElementById('detailedAffirmation').querySelector('.affirmation-category').textContent = categoryDisplayText; |
|
} |
|
|
|
|
|
function generateOpportunity() { |
|
const symbol = document.getElementById('opportunitySymbol').value; |
|
|
|
if (!symbol) { |
|
alert('Bitte wähle ein Symbol aus.'); |
|
return; |
|
} |
|
|
|
if (!apiConnected) { |
|
alert('Bitte verbinde zuerst die Gemini API.'); |
|
return; |
|
} |
|
|
|
|
|
const timeframe = document.getElementById('opportunityTimeframe').value; |
|
const style = document.getElementById('opportunityStyle').value; |
|
const instructions = document.getElementById('opportunityInstructions').value; |
|
|
|
|
|
document.getElementById('analyzeText').textContent = 'Analysiere...'; |
|
document.getElementById('opportunitySpinner').style.display = 'inline-block'; |
|
|
|
|
|
setTimeout(() => { |
|
|
|
document.getElementById('analyzeText').textContent = 'Trading Opportunity analysieren'; |
|
document.getElementById('opportunitySpinner').style.display = 'none'; |
|
|
|
|
|
const result = generateOpportunityResult(symbol, timeframe, style, instructions); |
|
|
|
|
|
document.getElementById('opportunityResults').innerHTML = result; |
|
}, 3000); |
|
} |
|
|
|
function generateOpportunityResult(symbol, timeframe, style, instructions) { |
|
|
|
|
|
|
|
const symbolName = symbol.split(':')[1]; |
|
|
|
|
|
const isLong = Math.random() > 0.5; |
|
|
|
|
|
let analysis = ''; |
|
let technicalInsights = ''; |
|
let fundamentalInsights = ''; |
|
let tradeSetup = ''; |
|
|
|
|
|
if (style === 'technical' || style === 'combined') { |
|
const patterns = ['Double Bottom', 'Bull Flag', 'Cup and Handle', 'Descending Triangle', 'Head and Shoulders']; |
|
const indicators = ['RSI', 'MACD', 'Moving Average', 'Bollinger Bands', 'Fibonacci Retracement']; |
|
|
|
const pattern = patterns[Math.floor(Math.random() * patterns.length)]; |
|
const indicator1 = indicators[Math.floor(Math.random() * indicators.length)]; |
|
const indicator2 = indicators[Math.floor(Math.random() * indicators.length)]; |
|
|
|
technicalInsights = `<p class="mb-3"><strong>Technische Analyse:</strong> ${symbolName} zeigt ein ${pattern}-Muster, das auf eine potenzielle ${isLong ? 'Aufwärts' : 'Abwärts'}bewegung hindeutet. Der ${indicator1} ist ${isLong ? 'überverkauft' : 'überkauft'}, während der ${indicator2} ein ${isLong ? 'bullisches' : 'bärisches'} Signal gibt.</p>`; |
|
} |
|
|
|
|
|
if (style === 'fundamental' || style === 'combined') { |
|
fundamentalInsights = `<p class="mb-3"><strong>Fundamentale Analyse:</strong> ${symbolName} ${isLong ? 'profitiert von positiven Branchentrends und zeigt starkes Gewinnwachstum' : 'steht vor Herausforderungen durch Branchendruck und Margenschwäche'}. Bevorstehende Ereignisse könnten ${isLong ? 'positive' : 'negative'} Auswirkungen auf den Kurs haben.</p>`; |
|
} |
|
|
|
|
|
const currentPrice = Math.random() * 1000; |
|
const stopLoss = isLong ? currentPrice * 0.95 : currentPrice * 1.05; |
|
const takeProfit = isLong ? currentPrice * 1.1 : currentPrice * 0.9; |
|
|
|
tradeSetup = ` |
|
<div class="p-3 bg-gray-50 rounded-lg mt-4"> |
|
<h4 class="font-semibold mb-2">Trade Setup</h4> |
|
<div class="grid grid-cols-2 gap-3 text-sm"> |
|
<div> |
|
<div class="text-gray-600">Richtung:</div> |
|
<div class="font-medium">${isLong ? 'Long (Kaufen)' : 'Short (Verkaufen)'}</div> |
|
</div> |
|
<div> |
|
<div class="text-gray-600">Einstieg:</div> |
|
<div class="font-medium">${currentPrice.toFixed(2)}</div> |
|
</div> |
|
<div> |
|
<div class="text-gray-600">Stop-Loss:</div> |
|
<div class="font-medium">${stopLoss.toFixed(2)}</div> |
|
</div> |
|
<div> |
|
<div class="text-gray-600">Take-Profit:</div> |
|
<div class="font-medium">${takeProfit.toFixed(2)}</div> |
|
</div> |
|
<div> |
|
<div class="text-gray-600">Risk/Reward:</div> |
|
<div class="font-medium">1:${isLong ? '2' : '3'}</div> |
|
</div> |
|
<div> |
|
<div class="text-gray-600">Zeitrahmen:</div> |
|
<div class="font-medium">${timeframe === 'shortTerm' ? 'Kurzfristig (1-3 Tage)' : timeframe === 'mediumTerm' ? 'Mittelfristig (1-2 Wochen)' : 'Langfristig (1+ Monate)'}</div> |
|
</div> |
|
</div> |
|
</div> |
|
`; |
|
|
|
|
|
analysis = ` |
|
<div> |
|
<div class="mb-4 flex justify-between items-start"> |
|
<div> |
|
<h3 class="font-semibold text-lg">${symbol}</h3> |
|
<div class="text-sm text-gray-600">${new Date().toLocaleString()}</div> |
|
</div> |
|
<div class="badge ${isLong ? 'badge-success' : 'badge-danger'}">${isLong ? 'Long' : 'Short'}</div> |
|
</div> |
|
|
|
${technicalInsights} |
|
${fundamentalInsights} |
|
|
|
<p class="mb-4"> |
|
<strong>Analyse:</strong> Basierend auf ${style === 'technical' ? 'technischer Analyse' : style === 'fundamental' ? 'fundamentaler Analyse' : 'technischer und fundamentaler Analyse'} |
|
erscheint ${symbolName} als ${isLong ? 'attraktive Long-Opportunity' : 'potentielle Short-Position'} für den ${timeframe === 'shortTerm' ? 'kurzfristigen' : timeframe === 'mediumTerm' ? 'mittelfristigen' : 'langfristigen'} Handel. |
|
</p> |
|
|
|
${tradeSetup} |
|
|
|
<div class="mt-6 flex justify-end gap-3"> |
|
<button class="btn-primary text-sm" onclick="saveOpportunity()">Speichern</button> |
|
<button class="btn-primary text-sm" onclick="createTradeFromOpportunity()">Trade erstellen</button> |
|
</div> |
|
</div> |
|
`; |
|
|
|
return analysis; |
|
} |
|
|
|
function saveOpportunity() { |
|
alert('Trading Opportunity gespeichert!'); |
|
} |
|
|
|
function createTradeFromOpportunity() { |
|
alert('Trade aus dieser Opportunity wird erstellt. Bitte bestätige die Details im "Meine Trades" Bereich.'); |
|
|
|
|
|
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); |
|
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); |
|
|
|
document.querySelector('[data-tab="mytrades"]').classList.add('active'); |
|
document.getElementById('mytrades').classList.add('active'); |
|
} |
|
|
|
|
|
function handleChatKeyPress(event) { |
|
if (event.key === 'Enter') { |
|
sendChatMessage(); |
|
} |
|
} |
|
|
|
function addChatMessage(sender, text) { |
|
const chatBody = document.getElementById('chatBody'); |
|
const messageDiv = document.createElement('div'); |
|
messageDiv.className = `chat-message ${sender}`; |
|
|
|
messageDiv.innerHTML = ` |
|
<div class="message-content">${text}</div> |
|
`; |
|
|
|
chatBody.appendChild(messageDiv); |
|
|
|
|
|
if (document.getElementById('chatPopup').classList.contains('minimized')) { |
|
toggleChat(); |
|
} |
|
|
|
|
|
scrollChatToBottom(); |
|
} |
|
|
|
function scrollChatToBottom() { |
|
const chatBody = document.getElementById('chatBody'); |
|
chatBody.scrollTop = chatBody.scrollHeight; |
|
} |
|
|
|
function showTypingIndicator() { |
|
const chatBody = document.getElementById('chatBody'); |
|
const typingDiv = document.createElement('div'); |
|
typingDiv.className = 'chat-message ai'; |
|
typingDiv.id = 'typingIndicator'; |
|
|
|
typingDiv.innerHTML = ` |
|
<div class="message-content"> |
|
<div class="typing-indicator"> |
|
<span></span> |
|
<span></span> |
|
<span></span> |
|
</div> |
|
</div> |
|
`; |
|
|
|
chatBody.appendChild(typingDiv); |
|
scrollChatToBottom(); |
|
} |
|
|
|
function removeTypingIndicator() { |
|
const typingIndicator = document.getElementById('typingIndicator'); |
|
if (typingIndicator) { |
|
typingIndicator.remove(); |
|
} |
|
} |
|
|
|
function generateChatResponse(message) { |
|
|
|
|
|
|
|
if (message.toLowerCase().includes('hallo') || message.toLowerCase().includes('hi')) { |
|
return 'Hallo! Wie kann ich dir bei deinem Trading heute helfen?'; |
|
} else if (message.toLowerCase().includes('market') || message.toLowerCase().includes('markt')) { |
|
return 'Die Märkte zeigen heute gemischte Signale. US-Indizes sind leicht im Plus, während europäische Märkte sich seitwärts bewegen. Gold steigt aufgrund geopolitischer Spannungen.'; |
|
} else if (message.toLowerCase().includes('affirmation')) { |
|
return 'Affirmationen können deine Trading-Psychologie stark verbessern. Ich empfehle dir, täglich 5 Minuten für Affirmationen einzuplanen, besonders vor Handelssessions oder nach Verlusten.'; |
|
} else if (message.toLowerCase().includes('verlust') || message.toLowerCase().includes('loss')) { |
|
return 'Verluste sind ein normaler Teil des Tradings. Das Wichtigste ist, wie du darauf reagierst. Halte dich an dein Risikomanagement und betrachte jeden Verlust als Lernchance.'; |
|
} else if (message.toLowerCase().includes('strategie') || message.toLowerCase().includes('strategy')) { |
|
return 'Eine erfolgreiche Trading-Strategie sollte klare Ein- und Ausstiegskriterien, Risikomanagement und ein positives Erwartungswert haben. Möchtest du Hilfe bei einem bestimmten Aspekt deiner Strategie?'; |
|
} else { |
|
return 'Danke für deine Nachricht. Ich kann dir bei Trading-Analysen, Affirmationen, Marktinformationen und Handelsstrategien helfen. Was interessiert dich am meisten?'; |
|
} |
|
} |
|
|
|
|
|
function addNewTrade() { |
|
const symbol = document.getElementById('newTradeSymbol').value; |
|
if (!symbol) { |
|
alert('Bitte wähle ein Symbol aus.'); |
|
return; |
|
} |
|
|
|
const entry = document.getElementById('newTradeEntry').value; |
|
if (!entry) { |
|
alert('Bitte gib einen Einstiegspreis ein.'); |
|
return; |
|
} |
|
|
|
const direction = document.getElementById('newTradeDirection').value; |
|
const stop = document.getElementById('newTradeStop').value; |
|
const target = document.getElementById('newTradeTarget').value; |
|
const position = document.getElementById('newTradePosition').value; |
|
const notes = document.getElementById('newTradeNotes').value; |
|
|
|
|
|
const tradeElement = document.createElement('div'); |
|
tradeElement.className = 'trade-item'; |
|
|
|
|
|
const pnl = ((Math.random() * 2) - 0.5).toFixed(2); |
|
const isProfitable = parseFloat(pnl) > 0; |
|
|
|
tradeElement.innerHTML = ` |
|
<div class="trade-symbol">${symbol}</div> |
|
<div class="trade-details">${direction === 'long' ? 'Long' : 'Short'} @ ${entry} • Stop: ${stop} • Target: ${target}</div> |
|
<div class="trade-pnl ${isProfitable ? 'profit' : 'loss'}">${pnl > 0 ? '+' : ''}${pnl}%</div> |
|
<div class="text-sm text-gray-500 mt-2">Eröffnet: ${new Date().toLocaleString()}</div> |
|
${notes ? ` |
|
<div class="mt-3 text-sm"> |
|
<div class="font-medium">Notizen:</div> |
|
<p class="text-gray-600">${notes}</p> |
|
</div> |
|
` : ''} |
|
<div class="trade-actions"> |
|
<button class="btn-primary text-xs py-1">Bearbeiten</button> |
|
<button class="btn-danger text-xs py-1" onclick="removeTrade(this)">Entfernen</button> |
|
</div> |
|
`; |
|
|
|
|
|
document.getElementById('tradesList').prepend(tradeElement); |
|
|
|
|
|
document.getElementById('newTradeSymbol').value = ''; |
|
document.getElementById('newTradeEntry').value = ''; |
|
document.getElementById('newTradeStop').value = ''; |
|
document.getElementById('newTradeTarget').value = ''; |
|
document.getElementById('newTradePosition').value = ''; |
|
document.getElementById('newTradeNotes').value = ''; |
|
|
|
|
|
updateTradeStatistics(); |
|
|
|
|
|
alert('Trade erfolgreich hinzugefügt!'); |
|
} |
|
|
|
function removeTrade(button) { |
|
|
|
const tradeItem = button.closest('.trade-item'); |
|
tradeItem.style.opacity = '0.5'; |
|
|
|
|
|
setTimeout(() => { |
|
tradeItem.remove(); |
|
|
|
updateTradeStatistics(); |
|
|
|
alert('Trade entfernt!'); |
|
}, 500); |
|
} |
|
|
|
function updateTradeStatistics() { |
|
|
|
|
|
const tradeCount = document.querySelectorAll('.trade-item').length; |
|
|
|
|
|
} |
|
|
|
function updateActiveTrades() { |
|
|
|
|
|
|
|
const tradeItems = document.querySelectorAll('.trade-item'); |
|
|
|
tradeItems.forEach(item => { |
|
const pnlElement = item.querySelector('.trade-pnl'); |
|
if (pnlElement) { |
|
|
|
const currentValue = parseFloat(pnlElement.textContent.replace('%', '').replace('+', '')); |
|
|
|
|
|
const change = (Math.random() * 0.2) - 0.1; |
|
const newValue = (currentValue + change).toFixed(2); |
|
|
|
|
|
const isProfit = parseFloat(newValue) > 0; |
|
pnlElement.className = `trade-pnl ${isProfit ? 'profit' : 'loss'}`; |
|
pnlElement.textContent = `${isProfit ? '+' : ''}${newValue}%`; |
|
} |
|
}); |
|
} |
|
|
|
|
|
function sortTrades() { |
|
const sortOption = document.getElementById('tradeSortOption').value; |
|
const tradesList = document.getElementById('tradesList'); |
|
const trades = Array.from(tradesList.querySelectorAll('.trade-item')); |
|
|
|
|
|
tradesList.innerHTML = ''; |
|
|
|
|
|
switch (sortOption) { |
|
case 'newest': |
|
|
|
trades.reverse(); |
|
break; |
|
case 'oldest': |
|
|
|
break; |
|
case 'profit': |
|
trades.sort((a, b) => { |
|
const aPnl = parseFloat(a.querySelector('.trade-pnl').textContent.replace('%', '').replace('+', '')); |
|
const bPnl = parseFloat(b.querySelector('.trade-pnl').textContent.replace('%', '').replace('+', '')); |
|
return bPnl - aPnl; |
|
}); |
|
break; |
|
case 'loss': |
|
trades.sort((a, b) => { |
|
const aPnl = parseFloat(a.querySelector('.trade-pnl').textContent.replace('%', '').replace('+', '')); |
|
const bPnl = parseFloat(b.querySelector('.trade-pnl').textContent.replace('%', '').replace('+', '')); |
|
return aPnl - bPnl; |
|
}); |
|
break; |
|
} |
|
|
|
|
|
trades.forEach(trade => tradesList.appendChild(trade)); |
|
} |
|
|
|
function refreshTrades() { |
|
updateActiveTrades(); |
|
alert('Trades aktualisiert!'); |
|
} |
|
|
|
|
|
function updateIntermarketAnalysis() { |
|
const stockIndex = document.getElementById('stockIndex').value; |
|
const bondIndex = document.getElementById('bondIndex').value; |
|
const commodityIndex = document.getElementById('commodityIndex').value; |
|
const currencyIndex = document.getElementById('currencyIndex').value; |
|
|
|
|
|
alert(`Intermarket-Analyse wird aktualisiert mit: ${stockIndex}, ${bondIndex}, ${commodityIndex}, ${currencyIndex}`); |
|
|
|
|
|
fetch('/api/intermarket') |
|
.then(response => response.json()) |
|
.then(data => { |
|
updateIntermarketDisplay(data); |
|
}) |
|
.catch(error => { |
|
console.error('Error updating intermarket analysis:', error); |
|
}); |
|
} |