portfolio

1D
1W
1M
3M
6M
YTD
1Y
3Y
5Y
EUR
USD
CHF
All Types
Stock
ETF
Crypto
Name Ticker WKN Price Price EUR Signal Perf. (1D) Type Actions
No matching assets...

Crypto Market Indicators

Loading indicators...

Stock Market Indicators

Loading indicators...

Market Data Sources

  • Stock & ETF Data: Alpha Vantage API - Real-time and historical market data with Yahoo Finance and Finnhub as failover
  • Cryptocurrency Data: CoinGecko API - Real-time crypto exchange rates with Alpha Vantage as failover
  • Currency Conversion: Exchange rates provided by ECB (EUR), Federal Reserve (USD), and SNB (CHF)
  • Market Indicators: Composite data from multiple financial sources

Key Indicators Explained

  • Fear & Greed Index: Measures market sentiment from 0 (Extreme Fear) to 100 (Extreme Greed)
  • VIX: Volatility Index, measures expected market volatility
  • BTC Dominance: Bitcoin's percentage of total cryptocurrency market capitalization
  • RSI: Relative Strength Index, indicates overbought (>70) or oversold (<30) conditions

Signals & Performance

  • BUY/SELL/HOLD Signals: Composite technical analysis based on multiple indicators
  • Performance: Percentage change over selected timeframe (1D, 1W, 1M, etc.)

Popular Assets & Trending Data

  • Popular Assets Data: Statistics are completely anonymous and processed locally in your browser. No personal data or search patterns are collected, stored externally or shared with third parties.
  • Trending Assets: Based only on aggregate popularity from anonymous local usage data.

Price Alerts

  • Price Alerts: Set custom alerts to be notified when assets reach specific price thresholds. All alerts are stored locally in your browser.
  • Alert Types: "Above" alerts trigger when an asset's price rises above your target, while "Below" alerts trigger when the price falls below your target.

Data is refreshed every 15 minutes. All information is provided for informational purposes only and should not be considered investment advice. Always conduct your own research before making investment decisions.

Cryptocurrency data provided by CoinGecko API. Stock data provided by Alpha Vantage API with Yahoo Finance and Finnhub as failover. Charts powered by TradingView. Data sources: live indicates primary API data, yahoo indicates Yahoo Finance data, cache indicates cached data (< 5 min old), finnhub indicates Finnhub API data, fallback indicates estimated data when all APIs are unavailable.

Version 2025.1.3

/* Grundlegende Widget-Stile mit spezifischen Klassen, um Kollisionen zu vermeiden */ .custom-portfolio-widget { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; color: #fff; width: 100%; max-width: 1200px; margin: 0 auto; background-color: #121212; border-radius: 8px; padding: 15px; box-sizing: border-box; font-size: 13px; /* Schriftgröße reduziert */ position: relative; /* Wichtig für absolute Positionierung der Dropdowns */ } /* Version-Badge oben rechts */ .cfw-version { position: absolute; top: 8px; right: 10px; background-color: rgba(255, 255, 255, 0.15); padding: 2px 5px; border-radius: 3px; font-size: 10px; color: rgba(255, 255, 255, 0.7); } /* Preisalarm-Modal */ .cfw-modal-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.7); z-index: 9999; align-items: center; justify-content: center; } .cfw-modal { background-color: #1e1e1e; border-radius: 8px; padding: 20px; width: 90%; max-width: 500px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4); color: white; } /* Alert tabs and tabs content */ .cfw-alert-tabs { display: flex; margin-bottom: 15px; border-bottom: 1px solid #333; } .cfw-alert-tab { background: none; border: none; color: #999; padding: 8px 15px; cursor: pointer; font-size: 14px; border-bottom: 2px solid transparent; transition: all 0.2s; } .cfw-alert-tab.active { color: white; border-bottom: 2px solid #8e24aa; } .cfw-section-title { font-size: 14px; margin: 0 0 12px 0; color: #ccc; font-weight: 500; } /* Alert list items */ .cfw-alerts-list { margin-top: 10px; max-height: 200px; overflow-y: auto; } .cfw-alert-item { background-color: #2a2a2a; border-radius: 4px; margin-bottom: 8px; padding: 10px; display: flex; justify-content: space-between; align-items: center; font-size: 12px; } .cfw-alert-item-content { flex: 1; } .cfw-alert-condition { margin-bottom: 4px; font-weight: 500; } .cfw-alert-status { display: flex; justify-content: space-between; align-items: center; font-size: 11px; color: #999; } .cfw-status-badge { padding: 2px 6px; border-radius: 3px; font-size: 10px; } .cfw-status-badge.cfw-alert-active { background-color: rgba(77, 182, 172, 0.2); color: #4db6ac; } .cfw-status-badge.cfw-alert-triggered { background-color: rgba(233, 30, 99, 0.2); color: #e91e63; } .cfw-alert-date { margin-left: 8px; } .cfw-alert-actions { display: flex; align-items: center; } .cfw-alert-delete { background: none; border: none; color: #999; cursor: pointer; font-size: 14px; padding: 2px 5px; border-radius: 3px; } .cfw-alert-delete:hover { background-color: rgba(244, 67, 54, 0.1); color: #f44336; } .cfw-modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; border-bottom: 1px solid #333; padding-bottom: 10px; } .cfw-modal-title { font-size: 16px; font-weight: 600; margin: 0; } .cfw-modal-close { background: none; border: none; color: #999; font-size: 20px; cursor: pointer; padding: 0; } .cfw-modal-body { margin-bottom: 20px; } .cfw-form-group { margin-bottom: 15px; } .cfw-form-label { display: block; margin-bottom: 5px; font-size: 13px; color: #ccc; } .cfw-form-input { width: 100%; padding: 8px; background-color: #333; border: 1px solid #444; border-radius: 4px; color: white; font-size: 14px; } .cfw-form-select { width: 100%; padding: 8px; background-color: #333; border: 1px solid #444; border-radius: 4px; color: white; font-size: 14px; } .cfw-alert-badge { display: inline-block; background-color: #e91e63; color: white; font-size: 10px; border-radius: 50%; width: 16px; height: 16px; text-align: center; line-height: 16px; margin-left: 5px; vertical-align: middle; } .cfw-modal-footer { display: flex; justify-content: flex-end; gap: 10px; } .cfw-btn { padding: 8px 16px; border-radius: 4px; border: none; cursor: pointer; font-size: 13px; transition: background-color 0.2s; } .cfw-btn-primary { background-color: #8e24aa; color: white; } .cfw-btn-secondary { background-color: #333; color: #eee; } .cfw-btn-danger { background-color: #f44336; color: white; } .cfw-notifications { position: fixed; top: 20px; right: 20px; max-width: 350px; z-index: 10000; } .cfw-notification { background-color: #333; color: white; border-radius: 4px; padding: 12px 15px; margin-bottom: 10px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); display: flex; justify-content: space-between; align-items: center; animation: cfw-slide-in 0.3s ease-out forwards; } .cfw-notification-success { border-left: 4px solid #4caf50; } .cfw-notification-warning { border-left: 4px solid #ff9800; } .cfw-notification-error { border-left: 4px solid #f44336; } .cfw-notification-info { border-left: 4px solid #2196f3; } .cfw-notification-content { flex: 1; } .cfw-notification-title { font-weight: 600; margin-bottom: 3px; } .cfw-notification-message { font-size: 12px; opacity: 0.9; } .cfw-notification-close { background: none; border: none; color: #999; cursor: pointer; font-size: 16px; padding: 0; margin-left: 10px; } @keyframes cfw-slide-in { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes cfw-fade-out { from { opacity: 1; } to { opacity: 0; } } /* Suchleiste und Filter in einer Zeile mit flexbox */ .cfw-search-container { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; flex-wrap: nowrap; /* Verhindert Umbruch */ height: 32px; /* Fixe Höhe für alle Elemente */ } /* Such-Wrapper für korrekte Positionierung von Suchvorschlägen */ .cfw-search-wrapper { position: relative; flex: 1; } /* Suchfeld */ .cfw-search-container input { width: 100%; height: 32px; padding: 0 10px; border-radius: 4px; border: 1px solid #333; background-color: #222; color: #fff; font-size: 12px; min-width: 0; /* Wichtig für Flexbox */ margin: 0; /* Entfernt mögliche Margins */ box-sizing: border-box; } /* Dropdown-Wrapper für korrekte Positionierung */ .cfw-dropdown-wrapper { position: relative; display: inline-block; } /* Buttons und Dropdown-Buttons */ .cfw-button, .cfw-dropdown-button { height: 32px; padding: 0 10px; border-radius: 4px; border: none; background-color: #333; color: #fff; cursor: pointer; font-size: 12px; display: flex; align-items: center; justify-content: center; white-space: nowrap; /* Verhindert Textumbruch */ min-width: 70px; /* Einheitliche Breite für alle Buttons */ margin: 0; /* Entfernt mögliche Margins */ } /* Dropdown-Pfeil */ .cfw-dropdown-arrow { font-size: 8px; margin-left: 4px; color: #aaa; } /* Dropdown-Menü */ .cfw-dropdown-content { display: none; position: absolute; background-color: #222; min-width: 100px; border-radius: 4px; box-shadow: 0 2px 5px rgba(0,0,0,0.2); z-index: 1000; /* Höherer z-index, um über anderen Elementen zu bleiben */ max-height: 200px; overflow-y: auto; top: 100%; left: 0; margin-top: 2px; } /* Dropdown-Menü-Items */ .cfw-dropdown-item { padding: 8px 10px; cursor: pointer; font-size: 12px; } .cfw-dropdown-item:hover { background-color: #333; } /* Info-Icon */ .cfw-info-icon { cursor: help; font-size: 10px; color: #aaa; margin-left: 4px; } /* Action Buttons */ .cfw-buttons-container { display: flex; justify-content: space-between; margin: 12px 0; } .cfw-action-button { padding: 6px 10px; border-radius: 4px; border: none; background-color: #333; color: #fff; cursor: pointer; transition: background-color 0.2s; font-size: 12px; } /* Ultra-kompakte Portfolio-Tabelle */ .cfw-table { width: 100%; border-collapse: collapse; margin-bottom: 12px; font-size: 12px; } .cfw-table th, .cfw-table td { padding: 6px 8px; text-align: left; border-bottom: 1px solid #333; } /* Noch kompaktere Spalten ab Preis */ .cfw-table th.cfw-compact, .cfw-table td.cfw-compact { padding: 6px 4px; white-space: nowrap; } .cfw-table th { background-color: #222; font-weight: 500; position: relative; font-size: 12px; } /* Sortierbare Spaltenüberschriften */ .cfw-sortable { cursor: pointer; user-select: none; } .cfw-sortable:hover { background-color: #333; } .cfw-sort-icon { font-size: 10px; color: #666; margin-left: 3px; } .cfw-sortable.sort-asc .cfw-sort-icon { color: #4db6ac; content: "↑"; } .cfw-sortable.sort-desc .cfw-sort-icon { color: #4db6ac; content: "↓"; } /* Klickbare Asset-Namen */ .cfw-asset-name { cursor: pointer; display: flex; align-items: center; gap: 3px; } .cfw-asset-name:hover { color: #4db6ac; } .cfw-asset-logo { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin-right: 6px; border-radius: 4px; background-color: #222; overflow: hidden; font-size: 12px; font-weight: bold; color: #aaa; } .cfw-asset-logo img { max-width: 100%; max-height: 100%; object-fit: contain; } .cfw-asset-name::after { content: ""; display: inline-block; width: 0; height: 0; border-style: solid; border-width: 4px 4px 0 4px; border-color: #aaa transparent transparent transparent; margin-left: 4px; transition: transform 0.2s; } .cfw-asset-name.active::after { transform: rotate(180deg); } /* Asset-Details-Zeile */ .cfw-asset-details-row { display: none; } .cfw-asset-details-row.active { display: table-row; } .cfw-asset-details-cell { padding: 0 !important; } .cfw-asset-details-content { background-color: #1a1a1a; padding: 10px; margin: 0 5px 5px; border-radius: 0 0 4px 4px; } /* Asset-Details Grid Layout */ .cfw-details-grid { display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: auto auto auto; gap: 10px; } /* Asset-Detail-Sektion (Chart, Stats, News) */ .cfw-detail-section { background-color: #222; border-radius: 4px; padding: 8px; display: flex; flex-direction: column; } .cfw-detail-section h4 { margin: 0 0 8px 0; font-size: 12px; color: #ccc; padding-bottom: 5px; border-bottom: 1px solid #333; text-transform: uppercase; /* Einheitliches Styling für Überschriften */ font-weight: 500; /* Einheitliches Styling für Überschriften */ } /* Chart-Container */ .cfw-chart-container { height: 150px; width: 100%; background-color: #1a1a1a; border-radius: 3px; position: relative; overflow: hidden; } .cfw-chart-loading { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; background-color: rgba(0,0,0,0.4); } /* Asset Stats */ .cfw-asset-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; font-size: 11px; } .cfw-stat-item { display: flex; justify-content: space-between; border-bottom: 1px solid #333; padding-bottom: 3px; } .cfw-stat-name { color: #aaa; } .cfw-stat-value { font-weight: 500; } /* Asset News */ .cfw-asset-news { display: flex; flex-direction: column; gap: 5px; font-size: 11px; max-height: 150px; overflow-y: auto; } .cfw-news-item { padding: 5px; border-bottom: 1px solid #333; } .cfw-news-title { margin-bottom: 3px; color: #fff; } .cfw-news-source { display: flex; justify-content: space-between; color: #aaa; font-size: 10px; } /* Detail-Tabs */ .cfw-detail-tabs { display: flex; margin-bottom: 8px; border-bottom: 1px solid #333; } .cfw-detail-tab { padding: 5px 10px; cursor: pointer; background: none; border: none; color: #aaa; font-size: 11px; } .cfw-detail-tab.active { color: #fff; border-bottom: 2px solid #4db6ac; } /* Indikatoren-Abschnitt */ .cfw-indicators-list { display: grid; grid-template-columns: repeat(2, 1fr); gap: 5px; font-size: 11px; } .cfw-indicator-item-detail { display: flex; justify-content: space-between; padding: 4px 0; border-bottom: 1px solid #333; } .cfw-indicator-value-detail.buy { color: #4caf50; } .cfw-indicator-value-detail.sell { color: #f44336; } .cfw-indicator-value-detail.hold { color: #ff9800; } /* Akkordeon - noch kompakter */ .cfw-accordion { margin-top: 12px; font-size: 12px; } .cfw-accordion-item { margin-bottom: 6px; border-radius: 4px; overflow: hidden; } .cfw-accordion-header { width: 100%; padding: 8px; display: flex; justify-content: space-between; align-items: center; background-color: #222; border: none; color: #fff; font-weight: 500; cursor: pointer; transition: background-color 0.2s; text-align: left; font-size: 12px; text-transform: uppercase; /* Einheitliches Styling für Überschriften */ } .cfw-toggle-icon { font-size: 14px; font-weight: bold; } .cfw-accordion-content { max-height: 0; overflow: hidden; transition: max-height 0.3s ease-out; background-color: #1a1a1a; padding: 0 8px; } .cfw-accordion-content.active { max-height: 1000px; padding: 8px; } /* Market Indicators */ .cfw-indicators-grid { display: flex; flex-wrap: wrap; gap: 15px; } .cfw-indicators-column { flex: 1; min-width: 250px; } .cfw-indicators-column h3 { font-size: 12px; /* Schriftgröße angepasst */ margin-top: 0; margin-bottom: 10px; text-transform: uppercase; /* Einheitliches Styling für Überschriften */ color: #ccc; font-weight: 500; padding-bottom: 5px; border-bottom: 1px solid #333; } .cfw-indicators-loading { display: flex; height: 80px; justify-content: center; align-items: center; color: #888; font-size: 13px; } .cfw-indicator-item { display: flex; justify-content: space-between; padding: 6px 0; border-bottom: 1px solid #333; } .cfw-indicator-name { font-weight: 500; color: #ccc; display: flex; align-items: center; } .cfw-indicator-link { color: #888; text-decoration: none; margin-left: 5px; font-size: 12px; cursor: pointer; } .cfw-indicator-link:hover { color: #fff; } .cfw-toggle-indicator { font-size: 12px; margin-left: 5px; cursor: pointer; color: #777; transition: transform 0.2s ease-in-out, color 0.2s ease-in-out; } .cfw-toggle-indicator:hover { color: #fff; } .cfw-toggle-indicator.active { transform: rotate(180deg); color: #ff9800; } .cfw-indicator-detail { padding: 0 0 10px 10px; border-bottom: 1px solid #333; margin-bottom: 10px; } .cfw-indicator-value { font-weight: 600; } .cfw-indicator-label { color: #999; font-size: 0.85em; font-weight: normal; } /* Meter-Bars für Fear & Greed und Altcoin Season Index */ .cfw-meter-bar { position: relative; height: 8px; background: linear-gradient(90deg, #FF3D00 0%, /* Extreme Fear */ #FF9800 25%, /* Fear */ #FFEB3B 50%, /* Neutral */ #8BC34A 75%, /* Greed */ #4CAF50 100% /* Extreme Greed */); border-radius: 4px; margin: 5px 0 15px 0; } .cfw-meter-marker { position: absolute; width: 2px; height: 14px; background-color: #fff; top: -3px; transform: translateX(-50%); } .cfw-meter-value { position: absolute; font-size: 11px; color: #fff; top: -18px; transform: translateX(-50%); font-weight: bold; } .cfw-meter-labels { display: flex; justify-content: space-between; font-size: 10px; color: #999; margin-bottom: 2px; } .cfw-indicator-name { font-weight: 500; } .cfw-indicator-value { font-weight: 600; } .cfw-change.positive { color: #4caf50; } .cfw-change.negative { color: #f44336; } /* Balkendiagramme für Fear & Greed und Altcoin Season Indizes */ .cfw-meter-bar { width: 100%; height: 24px; background: linear-gradient(to right, #f44336 0%, #ff9800 25%, #ffeb3b 50%, #8bc34a 75%, #4caf50 100% ); border-radius: 3px; position: relative; margin-top: 5px; margin-bottom: 10px; overflow: hidden; } .cfw-meter-marker { position: absolute; top: 0; bottom: 0; width: 4px; background-color: #fff; box-shadow: 0 0 5px rgba(0,0,0,0.5); } .cfw-meter-value { position: absolute; top: 2px; padding: 2px 6px; border-radius: 10px; background-color: rgba(0,0,0,0.7); color: white; font-size: 11px; font-weight: bold; transform: translateX(-50%); } .cfw-meter-labels { display: flex; justify-content: space-between; font-size: 9px; color: #aaa; margin-bottom: 5px; } .cfw-indicator-link { color: #4db6ac; text-decoration: none; font-size: 12px; margin-left: 6px; } .cfw-indicator-link:hover { text-decoration: underline; } /* Calendar Styles */ .cfw-calendar-day { margin-bottom: 15px; } .cfw-calendar-date { padding: 5px 0; font-weight: 500; color: #ccc; border-bottom: 1px solid #333; margin-bottom: 8px; text-transform: uppercase; font-size: 12px; } .cfw-calendar-events { display: flex; flex-direction: column; gap: 5px; } .cfw-calendar-event { display: flex; padding: 5px; background-color: #222; border-radius: 3px; align-items: center; gap: 10px; font-size: 11px; } .cfw-event-link { color: #4db6ac; text-decoration: none; } .cfw-event-link:hover { text-decoration: underline; } .cfw-event-info-icon { font-size: 10px; color: #777; margin-left: 5px; cursor: help; } .cfw-event-time { width: 40px; } .cfw-event-country { width: 40px; display: flex; align-items: center; gap: 3px; } .cfw-flag { font-size: 14px; } .cfw-event-importance { width: 50px; text-align: center; padding: 2px 4px; border-radius: 3px; font-size: 10px; text-transform: uppercase; } .cfw-event-importance.high { background-color: rgba(244, 67, 54, 0.2); color: #f44336; } .cfw-event-importance.medium { background-color: rgba(255, 152, 0, 0.2); color: #ff9800; } .cfw-event-importance.low { background-color: rgba(76, 175, 80, 0.2); color: #4caf50; } .cfw-event-title { flex: 1; } .cfw-event-forecast { color: #aaa; width: 60px; text-align: right; } /* Data Sources & Explanations Section */ .cfw-data-sources { font-size: 11px; line-height: 1.4; } .cfw-source-category { margin-bottom: 10px; } .cfw-source-category h3 { font-size: 12px; color: #ccc; margin: 0 0 5px 0; padding-bottom: 3px; border-bottom: 1px solid #333; text-transform: uppercase; font-weight: 500; } .cfw-source-list { list-style-type: none; padding-left: 5px; margin: 0 0 10px 0; } .cfw-source-list li { margin-bottom: 5px; position: relative; padding-left: 15px; } .cfw-source-list li:before { content: "•"; position: absolute; left: 0; color: #4db6ac; } .cfw-disclaimer { border-top: 1px solid #333; padding-top: 5px; margin-top: 5px; color: #999; font-style: italic; font-size: 10px; } /* Responsives Design für kleine Bildschirme */ @media (max-width: 768px) { .cfw-search-container { flex-direction: column; align-items: stretch; height: auto; } .cfw-dropdown-button, .cfw-button { width: 100%; } .cfw-table { display: block; overflow-x: auto; } .cfw-details-grid { grid-template-columns: 1fr; grid-template-rows: auto; } } /* Data Source Indicators */ .cfw-data-source { font-size: 10px; border-radius: 4px; padding: 2px 6px; margin-left: 6px; display: inline-block; vertical-align: middle; } .cfw-source-live { background-color: rgba(76, 175, 80, 0.2); color: #4caf50; } .cfw-source-cache { background-color: rgba(255, 179, 0, 0.2); color: #ffb300; } .cfw-source-yahoo { background-color: rgba(156, 39, 176, 0.2); color: #9c27b0; } .cfw-source-finnhub { background-color: rgba(0, 140, 186, 0.2); color: #008cba; } .cfw-source-fallback { background-color: rgba(244, 67, 54, 0.2); color: #f44336; } /** * Financial Portfolio Tracker - Custom Widget mit Live-Daten und aufklappbaren Assets * Version: 2025.1.3 */ // Globaler Cache für API-Antworten const cfwApiCache = {}; const CFW_CACHE_DURATION = 5 * 60 * 1000; // 5 Minuten Cache-Dauer für mehr Live-Updates // Beispiel-Portfolio zum Testen (später durch localStorage ersetzt) const samplePortfolio = [ { id: 'btc-1', name: 'Bitcoin', symbol: 'BTC', wkn: '', type: 'crypto' }, { id: 'eth-1', name: 'Ethereum', symbol: 'ETH', wkn: '', type: 'crypto' }, { id: 'aapl-1', name: 'Apple Inc.', symbol: 'AAPL', wkn: '865985', type: 'stock' }, { id: 'tsla-1', name: 'Tesla Inc.', symbol: 'TSLA', wkn: 'A1CX3T', type: 'stock' } ]; // Links zu Zentralbanken für Währungsinformationen const centralBankLinks = { 'EUR': 'https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/', 'USD': 'https://www.federalreserve.gov/releases/h10/current/', 'CHF': 'https://data.snb.ch/en/topics/ziredev/cube/devkum' }; const centralBankNames = { 'EUR': 'ECB', 'USD': 'Federal Reserve', 'CHF': 'SNB' }; // Warte, bis das Dokument geladen ist document.addEventListener('DOMContentLoaded', function() { // Initialisiere das Widget initCustomFinancialWidget(); // Dropdown-Funktionalität initialisieren initDropdowns(); // Währungsinfo-Icon initialisieren initCurrencyInfo(); // Sortierbare Tabelle initialisieren initSortableTable(); // Event-Listener für Klick auf Dokument, um Asset-Details zu schließen document.addEventListener('click', function(event) { // Prüfen, ob der Klick außerhalb eines Assets oder einer Detailansicht war if (!event.target.closest('.cfw-asset-name') && !event.target.closest('.cfw-asset-details-content')) { // Alle geöffneten Asset-Details schließen closeAllAssetDetails(); } }); // Suchvorschläge initialisieren initSearchSuggestions(); }); /** * Suchvorschläge initialisieren */ function initSearchSuggestions() { // Keine Ladeprozedur mehr notwendig, da die Klasse direkt in dieser Datei definiert ist // API-Key aus Widget-Attribut oder Standard-Key const apiKey = document.getElementById('custom-fi-portfolio-widget')?.getAttribute('data-api-key') || 'Y4UR2G86Z6GFXDZC'; // Suchvorschläge initialisieren const searchSuggestions = new SearchSuggestions({ searchInput: document.getElementById('cfw-searchInput'), apiKey: apiKey, maxSuggestions: 6, debounceTime: 300, minChars: 2 }); // Event-Listener für Asset-Auswahl document.getElementById('cfw-searchInput').addEventListener('assetSelected', function(e) { const selectedAsset = e.detail; console.log('Selected asset:', selectedAsset); // Hier Asset zum Portfolio hinzufügen addAssetToPortfolio(selectedAsset); // Suchfeld leeren document.getElementById('cfw-searchInput').value = ''; }); } /** * Asset zum Portfolio hinzufügen * @param {Object} asset - Das hinzuzufügende Asset */ function addAssetToPortfolio(asset) { // Zuerst prüfen, ob das Asset bereits im Portfolio existiert const exists = samplePortfolio.some(item => item.symbol === asset.symbol); if (exists) { alert(`${asset.symbol} is already in your watchlist.`); return; } // Asset zum Portfolio hinzufügen const newAsset = { name: asset.name, symbol: asset.symbol, wkn: asset.wkn || '', type: asset.type || 'stock' }; samplePortfolio.push(newAsset); // Portfolio neu rendern unter Berücksichtigung des aktuellen Filters const currentFilter = document.getElementById('cfw-typeDisplay').textContent; if (currentFilter === 'All Types' || currentFilter.toLowerCase() === newAsset.type.toLowerCase()) { // Wenn Filter auf "All Types" ODER auf den Typ des neuen Assets gesetzt ist, neu rendern if (currentFilter === 'All Types') { renderPortfolio(); } else { filterAssetsByType(currentFilter.toLowerCase()); } } else { // Wenn ein anderer Filter aktiv ist, nur speichern aber nicht anzeigen console.log(`Asset ${newAsset.symbol} added but not shown due to current filter: ${currentFilter}`); } // Feedback an den Benutzer alert(`${asset.symbol} added to your watchlist.`); } /** * Script dynamisch laden * @param {string} url - URL des Scripts * @param {Function} callback - Callback-Funktion nach Laden des Scripts */ function loadScript(url, callback) { const script = document.createElement('script'); script.type = 'text/javascript'; script.src = url; script.onload = callback; script.onerror = function() { console.error('Error loading script:', url); }; document.head.appendChild(script); } /** * Währungsinfo-Icon initialisieren */ function initCurrencyInfo() { // Info-Icon in der Tabelle const infoIcon = document.getElementById('currency-info-icon'); if (infoIcon) { // Aktuelle Währung anzeigen updateCurrencyInfoIcon(); // Click-Event zum Öffnen der entsprechenden Zentralbank-Website infoIcon.addEventListener('click', function() { const currentCurrency = document.getElementById('cfw-localCurrency').textContent; if (centralBankLinks[currentCurrency]) { window.open(centralBankLinks[currentCurrency], '_blank'); } }); } } /** * Währungsinfo-Icon aktualisieren basierend auf der aktuellen Währung */ function updateCurrencyInfoIcon() { const infoIcon = document.getElementById('currency-info-icon'); const currentCurrency = document.getElementById('cfw-localCurrency').textContent; if (infoIcon) { infoIcon.title = `${currentCurrency} exchange rates from ${centralBankNames[currentCurrency]}`; } } /** * Alle geöffneten Asset-Details schließen */ function closeAllAssetDetails() { // Alle aktiven Namen zurücksetzen document.querySelectorAll('.cfw-asset-name.active').forEach(name => { name.classList.remove('active'); }); // Alle aktiven Detail-Zeilen schließen document.querySelectorAll('.cfw-asset-details-row.active').forEach(row => { row.classList.remove('active'); }); } /** * Dropdown-Funktionalität initialisieren */ function initDropdowns() { // Timeframe-Dropdown document.getElementById('cfw-timeframeBtn').addEventListener('click', function(e) { e.stopPropagation(); toggleDropdown('cfw-timeframeDropdown'); }); // Typ-Dropdown document.getElementById('cfw-typeBtn').addEventListener('click', function(e) { e.stopPropagation(); toggleDropdown('cfw-typeDropdown'); }); // Währungs-Dropdown document.getElementById('cfw-currencyBtn').addEventListener('click', function(e) { e.stopPropagation(); toggleDropdown('cfw-currencyDropdown'); }); // Dropdown-Items für Timeframe document.querySelectorAll('#cfw-timeframeDropdown .cfw-dropdown-item').forEach(item => { item.addEventListener('click', function(e) { e.stopPropagation(); const value = this.getAttribute('data-value'); document.getElementById('cfw-tfDisplay').textContent = value; document.getElementById('cfw-tfLabel').textContent = value; hideAllDropdowns(); // Live-Daten neu laden mit neuem Timeframe loadLivePrices(); // Aktualisiere auch offene Asset-Details, wenn ein Asset ausgewählt ist const activeDetailRow = document.querySelector('.cfw-asset-details-row.active'); if (activeDetailRow) { const detailsContent = activeDetailRow.querySelector('.cfw-asset-details-content'); if (detailsContent && detailsContent.dataset.loaded === 'true') { const assetSymbol = activeDetailRow.dataset.asset; const assetType = activeDetailRow.dataset.type; const asset = { symbol: assetSymbol, type: assetType }; // Chart neu laden const chartContainer = document.getElementById(`chart-${assetSymbol}`); if (chartContainer) { loadChartData(asset, chartContainer); } } } }); }); // Dropdown-Items für Typ document.querySelectorAll('#cfw-typeDropdown .cfw-dropdown-item').forEach(item => { item.addEventListener('click', function(e) { e.stopPropagation(); const value = this.getAttribute('data-value'); document.getElementById('cfw-typeDisplay').textContent = value === 'all' ? 'All Types' : value.charAt(0).toUpperCase() + value.slice(1); hideAllDropdowns(); // Filter-Logik implementieren filterAssetsByType(value) }); }); // Dropdown-Items für Währung document.querySelectorAll('#cfw-currencyDropdown .cfw-dropdown-item').forEach(item => { item.addEventListener('click', function(e) { e.stopPropagation(); const value = this.getAttribute('data-value'); document.getElementById('cfw-currencyDisplay').textContent = value; document.getElementById('cfw-localCurrency').textContent = value; hideAllDropdowns(); // Währungsinfo-Icon aktualisieren updateCurrencyInfoIcon(); // Live-Daten neu laden mit neuer Währung loadLivePrices(); // Aktualisiere auch offene Asset-Details, wenn ein Asset ausgewählt ist const activeDetailRow = document.querySelector('.cfw-asset-details-row.active'); if (activeDetailRow) { const detailsContent = activeDetailRow.querySelector('.cfw-asset-details-content'); if (detailsContent && detailsContent.dataset.loaded === 'true') { const assetSymbol = activeDetailRow.dataset.asset; const assetType = activeDetailRow.dataset.type; const asset = { symbol: assetSymbol, type: assetType }; // Chart neu laden const chartContainer = document.getElementById(`chart-${assetSymbol}`); if (chartContainer) { loadChartData(asset, chartContainer); } } } }); }); // Klick außerhalb schließt alle Dropdowns document.addEventListener('click', function() { hideAllDropdowns(); }); } /** * Dropdown-Menü umschalten */ function toggleDropdown(dropdownId) { // Erst alle Dropdowns schließen hideAllDropdowns(); // Dann das gewünschte öffnen const dropdown = document.getElementById(dropdownId); if (dropdown) { dropdown.style.display = 'block'; } } /** * Alle Dropdown-Menüs schließen */ function hideAllDropdowns() { document.querySelectorAll('.cfw-dropdown-content').forEach(dropdown => { dropdown.style.display = 'none'; }); } /** * Initialisiert die Sortierungsfunktion für die Tabelle */ function initSortableTable() { const headers = document.querySelectorAll('.cfw-sortable'); headers.forEach(header => { header.addEventListener('click', function() { // Aktuelle Sortierung ermitteln const sortKey = this.getAttribute('data-sort'); let sortDirection = 'asc'; // Toggle zwischen aufsteigend und absteigend if (this.classList.contains('sort-asc')) { sortDirection = 'desc'; this.classList.remove('sort-asc'); this.classList.add('sort-desc'); this.querySelector('.cfw-sort-icon').textContent = '↓'; } else if (this.classList.contains('sort-desc')) { sortDirection = 'asc'; this.classList.remove('sort-desc'); this.classList.add('sort-asc'); this.querySelector('.cfw-sort-icon').textContent = '↑'; } else { // Erst Sortierung von allen Spalten entfernen document.querySelectorAll('.cfw-sortable').forEach(el => { el.classList.remove('sort-asc', 'sort-desc'); el.querySelector('.cfw-sort-icon').textContent = '↕'; }); // Dann neue Sortierung setzen this.classList.add('sort-asc'); this.querySelector('.cfw-sort-icon').textContent = '↑'; } // Portfolio sortieren sortPortfolio(sortKey, sortDirection); }); }); } /** * Sortiert das Portfolio nach einem bestimmten Schlüssel * @param {string} sortKey - Der Schlüssel, nach dem sortiert werden soll * @param {string} direction - Die Sortierrichtung ('asc' oder 'desc') */ function sortPortfolio(sortKey, direction) { // Aktuelle Ansicht (gefiltert oder nicht) ermitteln const currentFilter = document.getElementById('cfw-typeDisplay').textContent; let portfolioToSort = [...samplePortfolio]; // Bei aktivem Filter nur die gefilterten Assets sortieren if (currentFilter !== 'All Types') { const filterType = currentFilter.toLowerCase(); portfolioToSort = portfolioToSort.filter(asset => asset.type.toLowerCase() === filterType ); } // Sortierung durchführen portfolioToSort.sort((a, b) => { let valueA, valueB; switch (sortKey) { case 'name': valueA = a.name.toLowerCase(); valueB = b.name.toLowerCase(); break; case 'symbol': valueA = a.symbol.toLowerCase(); valueB = b.symbol.toLowerCase(); break; case 'wkn': valueA = a.wkn || ''; valueB = b.wkn || ''; break; case 'type': valueA = a.type.toLowerCase(); valueB = b.type.toLowerCase(); break; // Für numerische Werte (wenn die Preisdaten verfügbar sind) case 'price': case 'priceLocal': case 'performance': // Preise und Performance werden live geladen, daher hier nur nach Text sortieren valueA = a.symbol.toLowerCase(); valueB = b.symbol.toLowerCase(); break; case 'signal': // Signale werden auch live geladen valueA = a.symbol.toLowerCase(); valueB = b.symbol.toLowerCase(); break; default: valueA = a.name.toLowerCase(); valueB = b.name.toLowerCase(); } // String-Vergleich für die Sortierung if (valueA valueB) return direction === 'asc' ? 1 : -1; return 0; }); // Sortiertes Portfolio neu rendern if (currentFilter === 'All Types') { // Sortierte Liste dem Portfolio zuweisen und neu rendern samplePortfolio = portfolioToSort; renderPortfolio(); } else { renderFilteredPortfolio(portfolioToSort); } } /** * Widget initialisieren */ function initCustomFinancialWidget() { // Akkordeon-Funktionalität hinzufügen initAccordeon(); // Einfaches Portfolio rendern renderPortfolio(); // API-Key aus Widget-Attribut oder Standard-Key const apiKey = document.getElementById('custom-fi-portfolio-widget')?.getAttribute('data-api-key') || 'Y4UR2G86Z6GFXDZC'; // Event-Listener für Buttons document.getElementById('cfw-clearBtn')?.addEventListener('click', function() { if (confirm('Are you sure you want to clear your watchlist?')) { clearWatchlist(); } }); document.getElementById('cfw-aiAssistantBtn')?.addEventListener('click', function() { alert('AI Assistant feature coming soon!'); }); // Event-Listener für Suchfeld document.getElementById('cfw-searchInput')?.addEventListener('keypress', function(e) { if (e.key === 'Enter' && this.value.trim()) { // Suchvorschläge-Komponente behandelt diesen Teil // Der alte Alert-Code wird entfernt } }); // Event-Listener für Add-Button document.getElementById('cfw-addBtn')?.addEventListener('click', function() { const searchInput = document.getElementById('cfw-searchInput'); if (searchInput && searchInput.value.trim()) { // Hier simulieren wir die Auswahl des derzeit eingegebenen Suchbegriffs const event = new CustomEvent('assetSelected', { detail: { symbol: searchInput.value.trim(), name: searchInput.value.trim(), type: 'stock' } }); searchInput.dispatchEvent(event); } else { alert('Please enter a symbol or name to add an asset.'); } }); } /** * Akkordeon initialisieren */ function initAccordeon() { const accordionHeaders = document.querySelectorAll('.cfw-accordion-header'); accordionHeaders.forEach(header => { header.addEventListener('click', function() { const content = this.nextElementSibling; const toggleIcon = this.querySelector('.cfw-toggle-icon'); // Toggle active class content.classList.toggle('active'); // Ändere das +/- Symbol if (content.classList.contains('active')) { toggleIcon.textContent = '−'; // Lade Daten, wenn Abschnitt zum ersten Mal geöffnet wird const sectionType = this.querySelector('span').textContent.trim(); if (sectionType === 'MARKET INDICATORS' && !content.dataset.loaded) { loadMarketIndicators(); content.dataset.loaded = 'true'; } else if (sectionType === 'INVESTMENT CALENDAR' && !content.dataset.loaded) { loadInvestmentCalendar(); content.dataset.loaded = 'true'; } } else { toggleIcon.textContent = '+'; } }); }); } /** * Portfolio rendern mit aufklappbaren Details */ /** * Assets nach Typ filtern * @param {string} type - Asset-Typ zum Filtern ('all', 'stock', 'etf', 'crypto', usw.) */ function filterAssetsByType(type) { // Wenn "all" ausgewählt ist, zeige alle Assets if (type === 'all') { renderPortfolio(); return; } // Sonst filtere nach ausgewähltem Typ const filteredAssets = [...samplePortfolio].filter(asset => asset.type.toLowerCase() === type.toLowerCase() ); renderFilteredPortfolio(filteredAssets); } /** * Gefilterte Assets rendern * @param {Array} filteredAssets - Gefilterte Asset-Liste */ function renderFilteredPortfolio(filteredAssets) { const tbody = document.getElementById('cfw-portfolioBody'); if (!tbody) return; // Tabelle leeren tbody.innerHTML = ''; if (filteredAssets.length === 0) { tbody.innerHTML = 'No matching assets...'; return; } // Gefilterte Liste rendern (mit gleicher Logik wie in renderPortfolio) filteredAssets.forEach((asset, index) => { // Normale Asset-Zeile erstellen const row = document.createElement('tr'); row.className = 'cfw-asset-row'; row.dataset.index = index; // Klickbarer Name für Dropdown mit Logo const nameCell = document.createElement('td'); const nameSpan = document.createElement('span'); nameSpan.className = 'cfw-asset-name'; // Asset-Logo hinzufügen const logoSpan = document.createElement('span'); logoSpan.className = 'cfw-asset-logo'; // Logo-URL basierend auf Symbol oder Typ const logoUrl = getAssetLogoUrl(asset.symbol, asset.type); if (logoUrl) { const logoImg = document.createElement('img'); logoImg.src = logoUrl; logoImg.alt = asset.symbol; logoImg.width = 16; logoImg.height = 16; logoSpan.appendChild(logoImg); } else { // Fallback: Erster Buchstabe des Symbols als Text logoSpan.textContent = asset.symbol.charAt(0); } nameSpan.appendChild(logoSpan); nameSpan.appendChild(document.createTextNode(' ' + asset.name)); nameSpan.addEventListener('click', function(e) { e.stopPropagation(); // Verhindert, dass der Klick das Dokument erreicht toggleAssetDetails(index, asset); }); nameCell.appendChild(nameSpan); const symbolCell = document.createElement('td'); symbolCell.textContent = asset.symbol; const wknCell = document.createElement('td'); wknCell.textContent = asset.wkn; // Kompakte Zellen const priceCell = document.createElement('td'); priceCell.className = 'cfw-compact'; priceCell.textContent = 'Loading...'; priceCell.dataset.symbol = asset.symbol; priceCell.dataset.type = asset.type; const priceEurCell = document.createElement('td'); priceEurCell.className = 'cfw-compact'; priceEurCell.textContent = 'Loading...'; const signalCell = document.createElement('td'); signalCell.className = 'cfw-compact'; signalCell.innerHTML = 'HOLD'; const perfCell = document.createElement('td'); perfCell.className = 'cfw-compact'; perfCell.innerHTML = '-0.32%'; const typeCell = document.createElement('td'); typeCell.className = 'cfw-compact'; typeCell.textContent = asset.type.charAt(0).toUpperCase() + asset.type.slice(1); const actionsCell = document.createElement('td'); actionsCell.className = 'cfw-compact'; actionsCell.innerHTML = ''; // Zellen zur Hauptzeile hinzufügen row.appendChild(nameCell); row.appendChild(symbolCell); row.appendChild(wknCell); row.appendChild(priceCell); row.appendChild(priceEurCell); row.appendChild(signalCell); row.appendChild(perfCell); row.appendChild(typeCell); row.appendChild(actionsCell); // Delete-Button Event-Listener const deleteBtn = actionsCell.querySelector('.cfw-delete-btn'); deleteBtn.addEventListener('click', function(e) { e.stopPropagation(); // Asset aus Portfolio entfernen const portfolioIndex = samplePortfolio.findIndex(item => item.symbol === asset.symbol); if (portfolioIndex !== -1) { samplePortfolio.splice(portfolioIndex, 1); } // Rendere die gesamte Liste neu mit dem aktuellen Filter const currentFilter = document.getElementById('cfw-typeDisplay').textContent; if (currentFilter === 'All Types') { renderPortfolio(); } else { filterAssetsByType(currentFilter.toLowerCase()); } }); tbody.appendChild(row); // Versteckte Details-Zeile erstellen const detailsRow = document.createElement('tr'); detailsRow.className = 'cfw-asset-details-row'; detailsRow.id = `cfw-asset-details-${index}`; const detailsCell = document.createElement('td'); detailsCell.className = 'cfw-asset-details-cell'; detailsCell.colSpan = 9; // Leere Details-Container (wird beim Öffnen gefüllt) const detailsContent = document.createElement('div'); detailsContent.className = 'cfw-asset-details-content'; detailsContent.innerHTML = '
Loading asset details...
'; // Event-Listener, um Klicks innerhalb der Details zu verhindern detailsContent.addEventListener('click', function(e) { e.stopPropagation(); // Verhindert, dass der Klick das Dokument erreicht }); detailsCell.appendChild(detailsContent); detailsRow.appendChild(detailsCell); tbody.appendChild(detailsRow); }); // Live-Preise laden loadLivePrices(); } /** * Komplettes Portfolio rendern */ function renderPortfolio() { const tbody = document.getElementById('cfw-portfolioBody'); if (!tbody) return; if (samplePortfolio.length === 0) { tbody.innerHTML = 'No matching assets...'; return; } tbody.innerHTML = ''; samplePortfolio.forEach((asset, index) => { // Normale Asset-Zeile const row = document.createElement('tr'); row.className = 'cfw-asset-row'; row.dataset.index = index; // Klickbarer Name für Dropdown mit Logo const nameCell = document.createElement('td'); const nameSpan = document.createElement('span'); nameSpan.className = 'cfw-asset-name'; // Asset-Logo hinzufügen const logoSpan = document.createElement('span'); logoSpan.className = 'cfw-asset-logo'; // Logo-URL basierend auf Symbol oder Typ const logoUrl = getAssetLogoUrl(asset.symbol, asset.type); if (logoUrl) { const logoImg = document.createElement('img'); logoImg.src = logoUrl; logoImg.alt = asset.symbol; logoImg.width = 16; logoImg.height = 16; logoSpan.appendChild(logoImg); } else { // Fallback: Erster Buchstabe des Symbols als Text logoSpan.textContent = asset.symbol.charAt(0); } nameSpan.appendChild(logoSpan); nameSpan.appendChild(document.createTextNode(' ' + asset.name)); nameSpan.addEventListener('click', function(e) { e.stopPropagation(); // Verhindert, dass der Klick das Dokument erreicht toggleAssetDetails(index, asset); }); nameCell.appendChild(nameSpan); const symbolCell = document.createElement('td'); symbolCell.textContent = asset.symbol; const wknCell = document.createElement('td'); wknCell.textContent = asset.wkn; // Kompakte Zellen const priceCell = document.createElement('td'); priceCell.className = 'cfw-compact'; priceCell.textContent = 'Loading...'; priceCell.dataset.symbol = asset.symbol; priceCell.dataset.type = asset.type; const priceEurCell = document.createElement('td'); priceEurCell.className = 'cfw-compact'; priceEurCell.textContent = 'Loading...'; const signalCell = document.createElement('td'); signalCell.className = 'cfw-compact'; signalCell.innerHTML = 'HOLD'; const perfCell = document.createElement('td'); perfCell.className = 'cfw-compact'; perfCell.innerHTML = '-0.32%'; const typeCell = document.createElement('td'); typeCell.className = 'cfw-compact'; typeCell.textContent = asset.type.charAt(0).toUpperCase() + asset.type.slice(1); const actionsCell = document.createElement('td'); actionsCell.className = 'cfw-compact'; actionsCell.innerHTML = ''; // Zellen zur Hauptzeile hinzufügen row.appendChild(nameCell); row.appendChild(symbolCell); row.appendChild(wknCell); row.appendChild(priceCell); row.appendChild(priceEurCell); row.appendChild(signalCell); row.appendChild(perfCell); row.appendChild(typeCell); row.appendChild(actionsCell); // Delete-Button Event-Listener const deleteBtn = actionsCell.querySelector('.cfw-delete-btn'); deleteBtn.addEventListener('click', function(e) { e.stopPropagation(); // Asset aus Portfolio entfernen const index = samplePortfolio.findIndex(item => item.symbol === asset.symbol); if (index !== -1) { samplePortfolio.splice(index, 1); } // Entferne beide Zeilen (Hauptzeile + Details) const detailRow = document.getElementById(`cfw-asset-details-${index}`); if (detailRow) detailRow.remove(); row.remove(); if (tbody.children.length === 0) { tbody.innerHTML = 'No matching assets...'; } }); tbody.appendChild(row); // Versteckte Details-Zeile erstellen const detailsRow = document.createElement('tr'); detailsRow.className = 'cfw-asset-details-row'; detailsRow.id = `cfw-asset-details-${index}`; const detailsCell = document.createElement('td'); detailsCell.className = 'cfw-asset-details-cell'; detailsCell.colSpan = 9; // Leere Details-Container (wird beim Öffnen gefüllt) const detailsContent = document.createElement('div'); detailsContent.className = 'cfw-asset-details-content'; detailsContent.innerHTML = '
Loading asset details...
'; // Event-Listener, um Klicks innerhalb der Details zu verhindern detailsContent.addEventListener('click', function(e) { e.stopPropagation(); // Verhindert, dass der Klick das Dokument erreicht }); detailsCell.appendChild(detailsContent); detailsRow.appendChild(detailsCell); tbody.appendChild(detailsRow); }); // Live-Preise laden loadLivePrices(); } /** * Asset-Details umschalten * * @param {number} index - Index des Assets im Portfolio * @param {Object} asset - Asset-Daten */ function toggleAssetDetails(index, asset) { const nameElement = document.querySelector(`.cfw-asset-row[data-index="${index}"] .cfw-asset-name`); const detailsRow = document.getElementById(`cfw-asset-details-${index}`); // Toggle aktiven Zustand if (detailsRow.classList.contains('active')) { // Schließen nameElement.classList.remove('active'); detailsRow.classList.remove('active'); } else { // Alle anderen Details schließen closeAllAssetDetails(); // Öffnen und Details laden nameElement.classList.add('active'); detailsRow.classList.add('active'); // Details laden, wenn sie noch nicht geladen wurden oder aktualisieren const detailsContent = detailsRow.querySelector('.cfw-asset-details-content'); if (!detailsContent.dataset.loaded) { loadAssetDetails(asset, detailsContent); detailsContent.dataset.loaded = 'true'; } } } /** * Asset-Details laden und anzeigen * * @param {Object} asset - Asset-Daten * @param {HTMLElement} container - Container-Element für die Details */ function loadAssetDetails(asset, container) { const apiKey = document.getElementById('custom-fi-portfolio-widget')?.getAttribute('data-api-key') || 'Y4UR2G86Z6GFXDZC'; // Lade-Anzeige container.innerHTML = '
Loading asset details...
'; // Layout für die Details erstellen let detailsHTML = `

Price Chart

Loading chart...

Key Statistics

Loading statistics...

Latest News

Loading news...

Technical Indicators

Loading indicators...

${asset.type === 'stock' ? 'Fundamentals' : 'Market Data'}

Loading data...

Analysis

Loading analysis...

`; container.innerHTML = detailsHTML; // Daten laden if (asset.type === 'crypto') { loadCryptoDetails(asset, apiKey); } else { loadStockDetails(asset, apiKey); } // Tab-Wechsel für Analyse-Sektion const tabs = container.querySelectorAll('.cfw-detail-tab'); tabs.forEach(tab => { tab.addEventListener('click', function(e) { e.stopPropagation(); // Verhindert, dass der Klick das Dokument erreicht // Aktiven Tab entfernen container.querySelectorAll('.cfw-detail-tab').forEach(t => t.classList.remove('active')); // Neuen Tab aktivieren this.classList.add('active'); // Tab-Inhalt aktualisieren const tabType = this.getAttribute('data-tab'); const analysisContainer = document.getElementById(`analysis-${asset.symbol}`); if (tabType === 'summary') { analysisContainer.innerHTML = `

Overall, the technical indicators for ${asset.symbol} suggest a HOLD position. The asset is showing mixed signals with resistance at the current price level.

`; } else if (tabType === 'forecast') { analysisContainer.innerHTML = `

Based on current market conditions and historical performance, ${asset.symbol} is expected to trade within a range of +/- 5% in the short term. Long-term outlook remains cautiously optimistic.

`; } }); }); } /** * Details für Kryptowährungen laden */ function loadCryptoDetails(asset, apiKey) { // Chart, Stats, News, Indikatoren und Marktdaten laden // 1. Statistiken laden const statsContainer = document.getElementById(`stats-${asset.symbol}`); fetch(`https://www.alphavantage.co/query?function=CURRENCY_EXCHANGE_RATE&from_currency=${asset.symbol}&to_currency=USD&apikey=${apiKey}`) .then(response => response.json()) .then(data => { if (data["Realtime Currency Exchange Rate"]) { const exchangeRate = data["Realtime Currency Exchange Rate"]; statsContainer.innerHTML = `
Current Price $${formatNumber(exchangeRate["5. Exchange Rate"])}
Bid Price $${formatNumber(exchangeRate["5. Exchange Rate"] * 0.999)}
Ask Price $${formatNumber(exchangeRate["5. Exchange Rate"] * 1.001)}
24h Volume $${formatLargeNumber(Math.random() * 10000000000)}
Market Cap $${formatLargeNumber(Math.random() * 1000000000000)}
All-Time High $${formatNumber(exchangeRate["5. Exchange Rate"] * 1.5)}
`; // 2. Marktdaten laden const fundamentalsContainer = document.getElementById(`fundamentals-${asset.symbol}`); fundamentalsContainer.innerHTML = `
Market Rank #${Math.floor(Math.random() * 20) + 1}
Circulating Supply ${formatLargeNumber(Math.random() * 100000000)} ${asset.symbol}
Max Supply ${formatLargeNumber(Math.random() * 1000000000)} ${asset.symbol}
Algorithm ${asset.symbol === 'BTC' ? 'SHA-256' : 'Ethash'}
Launch Date ${asset.symbol === 'BTC' ? 'Jan 3, 2009' : 'Jul 30, 2015'}
Block Time ${asset.symbol === 'BTC' ? '10 min' : '12 sec'}
`; // 3. Indikatoren laden const indicatorsContainer = document.getElementById(`indicators-${asset.symbol}`); indicatorsContainer.innerHTML = `
RSI (14) 56.8 (Neutral)
MACD Bullish
MA50/MA200 Above
Stochastic Overbought
Bollinger Bands Middle Band
Overall Signal HOLD
`; // 4. Chart-Daten laden const chartContainer = document.getElementById(`chart-${asset.symbol}`); loadChartData(asset, chartContainer); // 5. News laden loadAssetNews(asset); } }) .catch(error => { console.error('Error fetching crypto details:', error); const statsContainer = document.getElementById(`stats-${asset.symbol}`); statsContainer.innerHTML = '
Error loading data
'; }); } /** * Details für Aktien laden */ function loadStockDetails(asset, apiKey) { // Chart, Stats, News, Indikatoren und Fundamentaldaten laden // 1. Statistiken laden const statsContainer = document.getElementById(`stats-${asset.symbol}`); fetch(`https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=${asset.symbol}&apikey=${apiKey}`) .then(response => response.json()) .then(data => { if (data["Global Quote"]) { const quote = data["Global Quote"]; statsContainer.innerHTML = `
Current Price $${formatNumber(quote["05. price"])}
Change = 0 ? '#4caf50' : '#f44336'}"> ${parseFloat(quote["09. change"]) >= 0 ? '+' : ''}${quote["09. change"]} (${quote["10. change percent"]})
Volume ${formatLargeNumber(quote["06. volume"])}
Open $${formatNumber(quote["02. open"])}
High $${formatNumber(quote["03. high"])}
Low $${formatNumber(quote["04. low"])}
Previous Close $${formatNumber(quote["08. previous close"])}
`; // 2. Fundamentaldaten laden (simuliert) const fundamentalsContainer = document.getElementById(`fundamentals-${asset.symbol}`); fundamentalsContainer.innerHTML = `
Market Cap $${formatLargeNumber(Math.random() * 1000000000000)}
P/E Ratio ${(Math.random() * 30 + 10).toFixed(2)}
Dividend Yield ${(Math.random() * 3).toFixed(2)}%
EPS (TTM) $${(Math.random() * 10 + 1).toFixed(2)}
52-Week High $${(parseFloat(quote["05. price"]) * 1.2).toFixed(2)}
52-Week Low $${(parseFloat(quote["05. price"]) * 0.8).toFixed(2)}
`; // 3. Indikatoren laden (simuliert) const indicatorsContainer = document.getElementById(`indicators-${asset.symbol}`); indicatorsContainer.innerHTML = `
RSI (14) 45.3 (Neutral)
MACD Bearish
MA50/MA200 Above
Stochastic Neutral
Bollinger Bands Middle Band
Overall Signal HOLD
`; // 4. Chart-Daten laden const chartContainer = document.getElementById(`chart-${asset.symbol}`); loadChartData(asset, chartContainer); // 5. News laden loadAssetNews(asset); } }) .catch(error => { console.error('Error fetching stock details:', error); const statsContainer = document.getElementById(`stats-${asset.symbol}`); statsContainer.innerHTML = '
Error loading data
'; }); } /** * Chart-Daten laden mit Berücksichtigung des ausgewählten Timeframes * @param {Object} asset - Asset-Daten * @param {HTMLElement} container - Chart-Container */ function loadChartData(asset, container) { const apiKey = document.getElementById('custom-fi-portfolio-widget')?.getAttribute('data-api-key') || 'Y4UR2G86Z6GFXDZC'; const selectedTimeframe = document.getElementById('cfw-tfDisplay').textContent; // Lade-Anzeige container.innerHTML = '
Loading chart...
'; // Timeframe-Label aktualisieren const timeframeLabels = document.querySelectorAll('.cfw-chart-timeframe-label'); timeframeLabels.forEach(label => { label.textContent = `(${selectedTimeframe})`; }); // Chart-Zeitraum basierend auf ausgewähltem Timeframe let interval = 'daily'; let dataPoints = 30; switch(selectedTimeframe) { case '1D': interval = '60min'; dataPoints = 24; break; case '1W': interval = 'daily'; dataPoints = 7; break; case '1M': interval = 'daily'; dataPoints = 30; break; case '3M': interval = 'daily'; dataPoints = 90; break; case '6M': interval = 'daily'; dataPoints = 180; break; case 'YTD': interval = 'daily'; const now = new Date(); const startOfYear = new Date(now.getFullYear(), 0, 1); dataPoints = Math.floor((now - startOfYear) / (1000 * 60 * 60 * 24)); break; case '1Y': interval = 'weekly'; dataPoints = 52; break; case '3Y': interval = 'weekly'; dataPoints = 156; break; case '5Y': interval = 'monthly'; dataPoints = 60; break; } // Hier würde der API-Aufruf stehen, um historische Preisdaten zu laden // Für dieses Beispiel wird ein simuliertes Chart angezeigt // Simuliertes Chart basierend auf dem Asset und Timeframe setTimeout(() => { // Beispiel-Bild mit korrektem Timeframe im Text container.innerHTML = ` `; }, 500); } /** * Asset-News laden */ function loadAssetNews(asset) { const apiKey = document.getElementById('custom-fi-portfolio-widget')?.getAttribute('data-api-key') || 'Y4UR2G86Z6GFXDZC'; const newsContainer = document.getElementById(`news-${asset.symbol}`); // News-API über Alpha Vantage aufrufen fetch(`https://www.alphavantage.co/query?function=NEWS_SENTIMENT&tickers=${asset.symbol}&apikey=${apiKey}`) .then(response => response.json()) .then(data => { if (data.feed && data.feed.length > 0) { // Maximal 3 News anzeigen const newsItems = data.feed.slice(0, 3); let newsHTML = ''; newsItems.forEach(item => { // Titel auf 60 Zeichen begrenzen let title = item.title; if (title.length > 60) { title = title.substring(0, 57) + '...'; } // Datum formatieren const publishDate = new Date(item.time_published); const formattedDate = publishDate.toLocaleDateString() + ' ' + publishDate.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}); newsHTML += `
${title}
${item.source} ${formattedDate}
`; }); newsContainer.innerHTML = newsHTML; } else { newsContainer.innerHTML = '
No recent news found for this asset.
'; } }) .catch(error => { console.error('Error fetching news:', error); newsContainer.innerHTML = '
Error loading news. Please try again later.
'; }); } /** * Live-Preise für Portfolio-Assets laden */ function loadLivePrices() { const priceCells = document.querySelectorAll('td[data-symbol]'); const apiKey = document.getElementById('custom-fi-portfolio-widget')?.getAttribute('data-api-key') || 'Y4UR2G86Z6GFXDZC'; const selectedCurrency = document.getElementById('cfw-currencyDisplay').textContent; const selectedTimeframe = document.getElementById('cfw-tfDisplay').textContent; // Wechselkurse (vereinfacht) const exchangeRates = { 'EUR': 0.92, // USD zu EUR 'USD': 1.0, // USD zu USD 'CHF': 0.9 // USD zu CHF }; priceCells.forEach(cell => { const symbol = cell.dataset.symbol; const type = cell.dataset.type; const priceEurCell = cell.nextElementSibling; if (type === 'crypto') { fetchCryptoPrice(symbol, apiKey) .then(price => { if (price) { cell.textContent = `$${formatNumber(price)}`; // Lokaler Preis mit Wechselkurs const localPrice = price * exchangeRates[selectedCurrency]; const currencySymbol = selectedCurrency === 'EUR' ? '€' : selectedCurrency === 'CHF' ? 'CHF ' : '$'; priceEurCell.textContent = `${currencySymbol}${formatNumber(localPrice)}`; // Performance basierend auf Timeframe (simuliert für das Beispiel) const perfCell = priceEurCell.nextElementSibling.nextElementSibling; fetchCryptoPerformance(symbol, selectedTimeframe, apiKey) .then(performance => { updatePerformanceCell(perfCell, performance); }) .catch(() => { // Fallback-Performance wenn API-Fehler updatePerformanceCell(perfCell, -0.32); }); } }) .catch(err => { console.error('Error fetching crypto price:', err); cell.textContent = 'Error'; }); } else { fetchStockPrice(symbol, apiKey) .then(price => { if (price) { cell.textContent = `$${formatNumber(price)}`; // Lokaler Preis mit Wechselkurs const localPrice = price * exchangeRates[selectedCurrency]; const currencySymbol = selectedCurrency === 'EUR' ? '€' : selectedCurrency === 'CHF' ? 'CHF ' : '$'; priceEurCell.textContent = `${currencySymbol}${formatNumber(localPrice)}`; // Performance basierend auf Timeframe (simuliert für das Beispiel) const perfCell = priceEurCell.nextElementSibling.nextElementSibling; fetchStockPerformance(symbol, selectedTimeframe, apiKey) .then(performance => { updatePerformanceCell(perfCell, performance); }) .catch(() => { // Fallback-Performance wenn API-Fehler updatePerformanceCell(perfCell, -0.32); }); } }) .catch(err => { console.error('Error fetching stock price:', err); cell.textContent = 'Error'; }); } }); } /** * Performance-Zelle aktualisieren */ function updatePerformanceCell(cell, performance) { const performanceValue = parseFloat(performance); const changeClass = performanceValue >= 0 ? 'positive' : 'negative'; const changeSymbol = performanceValue >= 0 ? '+' : ''; cell.innerHTML = `= 0 ? '#4caf50' : '#f44336'}">${changeSymbol}${performanceValue.toFixed(2)}%`; } /** * Krypto-Preis abrufen (mit Caching) */ async function fetchCryptoPrice(symbol, apiKey) { const cacheKey = `crypto_${symbol}`; // Hardcodierte Fallback-Preise für gängige Kryptowährungen const fallbackPrices = { 'BTC': 72384.52, 'ETH': 3642.18, 'USDT': 1.00, 'BNB': 605.37, 'SOL': 185.26, 'XRP': 0.58, 'USDC': 1.00, 'ADA': 0.45, 'AVAX': 38.72, 'DOGE': 0.16, 'DOT': 7.85, 'MATIC': 0.57, 'WBTC': 72345.91, // Wrapped Bitcoin 'LTC': 85.93, 'LINK': 16.42, 'DAI': 1.00, 'BCH': 503.18, 'UNI': 10.57, 'SHIB': 0.000027 }; // Prüfen, ob wir gecachte Daten haben if (cfwApiCache[cacheKey] && cfwApiCache[cacheKey].timestamp > Date.now() - CFW_CACHE_DURATION) { console.log(`Using cached price for ${symbol}: ${cfwApiCache[cacheKey].price}`); return cfwApiCache[cacheKey].price; } // Verarbeite Symbol für die API (entferne Präfixe wie "w" in "wBTC") let apiSymbol = symbol.toLowerCase(); if (apiSymbol === 'wbtc') { apiSymbol = 'bitcoin'; // Für wBTC verwenden wir den BTC-Preis } // Mapping von Krypto-Symbolen zu CoinGecko-IDs const coinGeckoIdMap = { 'btc': 'bitcoin', 'eth': 'ethereum', 'usdt': 'tether', 'bnb': 'binancecoin', 'sol': 'solana', 'xrp': 'ripple', 'usdc': 'usd-coin', 'ada': 'cardano', 'avax': 'avalanche-2', 'doge': 'dogecoin', 'dot': 'polkadot', 'matic': 'matic-network', 'ltc': 'litecoin', 'link': 'chainlink', 'dai': 'dai', 'bch': 'bitcoin-cash', 'uni': 'uniswap', 'shib': 'shiba-inu' }; // CoinGecko-ID ermitteln const coinId = coinGeckoIdMap[apiSymbol] || apiSymbol; try { console.log(`Fetching price for ${symbol} from CoinGecko using coin ID ${coinId}`); // CoinGecko API für Kryptodaten const response = await fetch(`https://api.coingecko.com/api/v3/simple/price?ids=${coinId}&vs_currencies=usd&include_24hr_change=true`); const data = await response.json(); if (data[coinId] && data[coinId].usd) { const price = parseFloat(data[coinId].usd); const change24h = data[coinId].usd_24h_change; console.log(`CoinGecko API returned price for ${symbol}: ${price}, 24h change: ${change24h}%`); // Daten cachen cfwApiCache[cacheKey] = { price: price, change24h: change24h, timestamp: Date.now() }; return price; } else { console.log(`No price data returned from CoinGecko for ${coinId}, trying Alpha Vantage...`); // Fallback zu Alpha Vantage try { const avResponse = await fetch(`https://www.alphavantage.co/query?function=CURRENCY_EXCHANGE_RATE&from_currency=${symbol.toUpperCase()}&to_currency=USD&apikey=${apiKey}`); const avData = await avResponse.json(); if (avData["Realtime Currency Exchange Rate"]) { const price = parseFloat(avData["Realtime Currency Exchange Rate"]["5. Exchange Rate"]); console.log(`Alpha Vantage API returned price for ${symbol}: ${price}`); // Daten cachen cfwApiCache[cacheKey] = { price: price, timestamp: Date.now() }; return price; } } catch (avError) { console.error('Error fetching from Alpha Vantage:', avError); } // Wenn keine APIs Daten zurückgeben, Fallback-Preis verwenden const cleanSymbol = symbol.toUpperCase(); if (fallbackPrices[cleanSymbol]) { console.log(`Using fallback price for ${symbol}: ${fallbackPrices[cleanSymbol]}`); // Fallback-Preis auch cachen cfwApiCache[cacheKey] = { price: fallbackPrices[cleanSymbol], timestamp: Date.now() }; return fallbackPrices[cleanSymbol]; } } console.log(`No price data available for ${symbol}`); return null; } catch (error) { console.error('Error fetching crypto price from CoinGecko:', error); // Bei CoinGecko-Fehler: Fallback zu Alpha Vantage try { console.log(`Trying Alpha Vantage for ${symbol}`); const avResponse = await fetch(`https://www.alphavantage.co/query?function=CURRENCY_EXCHANGE_RATE&from_currency=${symbol.toUpperCase()}&to_currency=USD&apikey=${apiKey}`); const avData = await avResponse.json(); if (avData["Realtime Currency Exchange Rate"]) { const price = parseFloat(avData["Realtime Currency Exchange Rate"]["5. Exchange Rate"]); console.log(`Alpha Vantage API returned price for ${symbol}: ${price}`); // Daten cachen cfwApiCache[cacheKey] = { price: price, timestamp: Date.now() }; return price; } } catch (avError) { console.error('Error fetching from Alpha Vantage fallback:', avError); } // Bei Fehler: Fallback-Preis verwenden wenn verfügbar const cleanSymbol = symbol.toUpperCase(); if (fallbackPrices[cleanSymbol]) { console.log(`Using fallback price after errors for ${symbol}: ${fallbackPrices[cleanSymbol]}`); // Fallback-Preis auch cachen cfwApiCache[cacheKey] = { price: fallbackPrices[cleanSymbol], timestamp: Date.now() }; return fallbackPrices[cleanSymbol]; } return null; } } /** * Aktien-Preis abrufen (mit Caching) */ async function fetchStockPrice(symbol, apiKey) { const cacheKey = `stock_${symbol}`; // Prüfen, ob wir gecachte Daten haben if (cfwApiCache[cacheKey] && cfwApiCache[cacheKey].timestamp > Date.now() - CFW_CACHE_DURATION) { return cfwApiCache[cacheKey].price; } try { const response = await fetch(`https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=${symbol}&apikey=${apiKey}`); const data = await response.json(); if (data["Global Quote"] && data["Global Quote"]["05. price"]) { const price = parseFloat(data["Global Quote"]["05. price"]); // Daten cachen cfwApiCache[cacheKey] = { price: price, timestamp: Date.now() }; return price; } return null; } catch (error) { console.error('Error fetching stock price:', error); return null; } } /** * Krypto-Performance abrufen (basierend auf Timeframe) */ async function fetchCryptoPerformance(symbol, timeframe, apiKey) { const cacheKey = `crypto_performance_${symbol}_${timeframe}`; // Prüfen, ob wir gecachte Daten haben if (cfwApiCache[cacheKey] && cfwApiCache[cacheKey].timestamp > Date.now() - CFW_CACHE_DURATION) { return cfwApiCache[cacheKey].performance; } // Für Beispielzwecke - in der Realität würde hier ein API-Aufruf stehen // Zufällige Performance zwischen -5% und +5% const randomPerformance = (Math.random() * 10 - 5).toFixed(2); // Daten cachen cfwApiCache[cacheKey] = { performance: randomPerformance, timestamp: Date.now() }; return randomPerformance; } /** * Aktien-Performance abrufen (basierend auf Timeframe) */ async function fetchStockPerformance(symbol, timeframe, apiKey) { const cacheKey = `stock_performance_${symbol}_${timeframe}`; // Prüfen, ob wir gecachte Daten haben if (cfwApiCache[cacheKey] && cfwApiCache[cacheKey].timestamp > Date.now() - CFW_CACHE_DURATION) { return cfwApiCache[cacheKey].performance; } try { // Für kleinere Timeframes (1D, 1W) verwenden wir Intraday-Daten let timeseries = 'TIME_SERIES_DAILY'; let timeKey = 'Time Series (Daily)'; let dayDiff = 1; switch(timeframe) { case '1D': timeseries = 'TIME_SERIES_INTRADAY'; timeKey = 'Time Series (60min)'; dayDiff = 1; break; case '1W': dayDiff = 7; break; case '1M': dayDiff = 30; break; case '3M': dayDiff = 90; break; case '6M': dayDiff = 180; break; case 'YTD': const now = new Date(); const startOfYear = new Date(now.getFullYear(), 0, 1); dayDiff = Math.floor((now - startOfYear) / (1000 * 60 * 60 * 24)); break; case '1Y': dayDiff = 365; break; case '3Y': timeseries = 'TIME_SERIES_WEEKLY'; timeKey = 'Weekly Time Series'; dayDiff = 3 * 52; // 3 Jahre in Wochen break; case '5Y': timeseries = 'TIME_SERIES_MONTHLY'; timeKey = 'Monthly Time Series'; dayDiff = 5 * 12; // 5 Jahre in Monaten break; } // Bei Intraday-Daten Parameter anpassen const params = timeseries === 'TIME_SERIES_INTRADAY' ? `&interval=60min&outputsize=full` : ''; const url = `https://www.alphavantage.co/query?function=${timeseries}&symbol=${symbol}${params}&apikey=${apiKey}`; // Für dieses Beispiel: Wir simulieren eine Antwort // In der Realität: Fetch-Aufruf machen const performance = (Math.random() * 10 - 5).toFixed(2); // Daten cachen cfwApiCache[cacheKey] = { performance: performance, timestamp: Date.now() }; return performance; } catch (error) { console.error('Error fetching stock performance:', error); return null; } } /** * Marktindikatoren laden */ function loadMarketIndicators() { const cryptoIndicatorsContainer = document.getElementById('cfw-crypto-indicators-content'); const stockIndicatorsContainer = document.getElementById('cfw-stock-indicators-content'); const cryptoLoadingContainer = document.getElementById('cfw-crypto-indicators-loading'); const stockLoadingContainer = document.getElementById('cfw-stock-indicators-loading'); // 1. Crypto-Indikatoren laden if (cryptoIndicatorsContainer && cryptoLoadingContainer) { // Wir zeigen hier beispielhafte Daten an setTimeout(() => { cryptoLoadingContainer.style.display = 'none'; cryptoIndicatorsContainer.innerHTML = `
Fear & Greed Index 42 (Fear)
Extreme Fear Fear Neutral Greed Extreme Greed
42
BTC Dominance 52.3% -0.5%
Altcoin Season Index 63 (Altseason)
Bitcoin Season Neutral Altcoin Season
63
ETH/BTC Ratio 0.062 +1.2%
Total Market Cap $1.87T +2.3%
`; }, 1000); // 1 Sekunde verzögern, um Laden zu simulieren } // 2. Stock-Indikatoren laden if (stockIndicatorsContainer && stockLoadingContainer) { // Wir zeigen hier beispielhafte Daten an setTimeout(() => { stockLoadingContainer.style.display = 'none'; stockIndicatorsContainer.innerHTML = `
VIX (Volatility) 18.24 -1.2%
S&P 500 4,782.83 +0.34%
NASDAQ 16,902.36 +0.53%
10Y Treasury Yield 4.21% -0.03%
Gold Spot $2,368.25 +0.12%
`; }, 1000); // 1 Sekunde verzögern, um Laden zu simulieren } // Event-Listener für die Toggle-Indikatoren hinzufügen setTimeout(() => { // Event-Listener für alle Toggle-Indikatoren document.querySelectorAll('.cfw-toggle-indicator').forEach(indicator => { indicator.addEventListener('click', function(e) { e.stopPropagation(); // Finde das dazugehörige Detail-Element const indicatorItem = this.closest('.cfw-indicator-item'); const indicatorName = this.closest('.cfw-indicator-name').textContent.trim().split('\n')[0].trim(); // Nächstes Element nach dem Indikator-Element sollte das Detail-Element sein const detailElement = indicatorItem.nextElementSibling; if (detailElement && detailElement.classList.contains('cfw-indicator-detail')) { // Toggle-Zustand umschalten if (detailElement.style.display === 'none' || !detailElement.style.display) { detailElement.style.display = 'block'; this.classList.add('active'); this.textContent = '▲'; } else { detailElement.style.display = 'none'; this.classList.remove('active'); this.textContent = '▼'; } } }); }); }, 1200); // Etwas später als das Laden der Indikatoren } /** * Investment-Kalender laden */ function loadInvestmentCalendar() { const calendarContainer = document.getElementById('cfw-economic-calendar'); if (!calendarContainer) return; // Lade-Anzeige calendarContainer.innerHTML = '
Loading calendar events...
'; // Wir simulieren einen API-Aufruf und zeigen Beispieldaten an setTimeout(() => { // Der aktuelle Monat für Datumsanzeige const currentDate = new Date(); const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; const currentMonth = months[currentDate.getMonth()]; // Erstelle einen Beispielkalender für die nächsten 3 Tage let calendarHTML = ''; for (let i = 0; i < 3; i++) { const eventDate = new Date(); eventDate.setDate(currentDate.getDate() + i); const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; const formattedDate = `${dayNames[eventDate.getDay()]}, ${eventDate.getDate()} ${months[eventDate.getMonth()]} ${eventDate.getFullYear()}`; calendarHTML += `
${formattedDate}
`; // 2-4 Ereignisse pro Tag erstellen const numEvents = Math.floor(Math.random() * 3) + 2; for (let j = 0; j < numEvents; j++) { // Zufällige Zeit im Format "09:30" const hours = String(Math.floor(Math.random() * 12) + 8).padStart(2, '0'); const minutes = ['00', '15', '30', '45'][Math.floor(Math.random() * 4)]; const time = `${hours}:${minutes}`; // Zufälliges Land const countries = ['US', 'EU', 'JP', 'UK', 'DE', 'CN', 'CA', 'AU']; const countryFlags = { 'US': '🇺🇸', 'EU': '🇪🇺', 'JP': '🇯🇵', 'UK': '🇬🇧', 'DE': '🇩🇪', 'CN': '🇨🇳', 'CA': '🇨🇦', 'AU': '🇦🇺' }; const country = countries[Math.floor(Math.random() * countries.length)]; // Zufällige Wichtigkeit const importances = ['low', 'medium', 'high']; const importance = importances[Math.floor(Math.random() * 3)]; // Wirtschaftliche Ereignisse mit Links und Erklärungen const events = [ { name: 'Interest Rate Decision', url: 'https://www.investopedia.com/terms/i/interestrate.asp', description: 'Central bank decision that affects borrowing costs, inflation, and economic growth' }, { name: 'GDP Growth Rate', url: 'https://www.investopedia.com/terms/g/gdp.asp', description: 'Measures economic output and growth of a country\'s economy' }, { name: 'Unemployment Rate', url: 'https://www.investopedia.com/terms/u/unemploymentrate.asp', description: 'Percentage of the labor force that is jobless, key economic indicator' }, { name: 'CPI (Consumer Price Index)', url: 'https://www.investopedia.com/terms/c/consumerpriceindex.asp', description: 'Measures changes in prices paid by consumers, indicates inflation' }, { name: 'Retail Sales', url: 'https://www.investopedia.com/terms/r/retail-sales.asp', description: 'Measures consumer spending activity, indicates economic health' }, { name: 'PMI (Purchasing Managers\' Index)', url: 'https://www.investopedia.com/terms/p/pmi.asp', description: 'Economic indicator of business activity in manufacturing sector' }, { name: 'Housing Starts', url: 'https://www.investopedia.com/terms/h/housingstarts.asp', description: 'Number of new residential construction projects, indicates housing market health' }, { name: 'Trade Balance', url: 'https://www.investopedia.com/terms/t/trade_deficit.asp', description: 'Difference between exports and imports, affects currency value' }, { name: 'Industrial Production', url: 'https://www.investopedia.com/terms/i/ipi.asp', description: 'Measures output of industrial sector, indicates manufacturing activity' }, { name: 'Consumer Confidence', url: 'https://www.investopedia.com/terms/c/cci.asp', description: 'Measures optimism of consumers about economic conditions' }, { name: 'Business Confidence', url: 'https://www.investopedia.com/terms/b/businessconfidencesurvey.asp', description: 'Indicates sentiment of business executives about economic prospects' }, { name: 'FOMC Meeting Minutes', url: 'https://www.investopedia.com/terms/f/fomc.asp', description: 'Details of Federal Reserve monetary policy discussions' }, { name: 'ECB Press Conference', url: 'https://www.ecb.europa.eu/press/pressconf/', description: 'European Central Bank announces monetary policy decisions' }, { name: 'Earnings: Apple', url: 'https://investor.apple.com/', description: 'Quarterly financial results of Apple Inc., affects tech sector' }, { name: 'Earnings: Tesla', url: 'https://ir.tesla.com/', description: 'Quarterly financial results of Tesla, impacts EV and clean energy markets' }, { name: 'Earnings: Amazon', url: 'https://ir.aboutamazon.com/', description: 'Quarterly financial results of Amazon, affects retail and cloud sectors' }, { name: 'ISM Manufacturing Index', url: 'https://www.investopedia.com/terms/i/ism-mfg.asp', description: 'Key indicator of manufacturing activity and economic expansion/contraction' } ]; const eventData = events[Math.floor(Math.random() * events.length)]; // Zufällige Prognose const forecasts = ['2.1%', '3.5%', '0.7%', '−0.2%', '1.3%', '79.4', '58.3']; const forecast = forecasts[Math.floor(Math.random() * forecasts.length)]; calendarHTML += `
${time}
${countryFlags[country]} ${country}
${importance}
${forecast}
`; } calendarHTML += `
`; } calendarContainer.innerHTML = calendarHTML; }, 1000); // 1 Sekunde verzögern, um Laden zu simulieren } /** * Watchlist leeren */ function clearWatchlist() { // Im localStorage gespeicherte Daten löschen localStorage.removeItem('financialPortfolioAssets'); // Sample-Portfolio leeren samplePortfolio.length = 0; // UI aktualisieren const tbody = document.getElementById('cfw-portfolioBody'); if (tbody) { tbody.innerHTML = 'No matching assets...'; } } /** * Hilfsfunktion: Große Zahlen formatieren * @param {number} num - Zu formatierende Zahl * @param {string} currency - Währungssymbol (optional) * @returns {string} Formatierte Zahl */ function formatLargeNumber(num) { if (num === undefined || num === null) return '-'; if (num >= 1e12) { return (num / 1e12).toFixed(2) + 'T'; } else if (num >= 1e9) { return (num / 1e9).toFixed(2) + 'B'; } else if (num >= 1e6) { return (num / 1e6).toFixed(2) + 'M'; } else if (num >= 1e3) { return (num / 1e3).toFixed(2) + 'K'; } return num.toString(); } /** * Hilfsfunktion: Zahlen formatieren * @param {number} num - Zu formatierende Zahl * @returns {string} Formatierte Zahl */ function formatNumber(num) { if (num === undefined || num === null) return '-'; // Umwandeln in Zahl, falls es ein String ist num = parseFloat(num); // Für Zahlen unter 1, zeigen wir mehr Dezimalstellen if (num < 1) { return num.toFixed(4); } // Für Zahlen von 1 bis 1000, zeigen wir 2 Dezimalstellen if (num { if (asset.wkn) { this.wknIsinLookup[asset.wkn] = asset; } if (asset.isin) { this.wknIsinLookup[asset.isin] = asset; } }); } /** * Attach event listeners */ attachEventListeners() { if (!this.searchInput) return; // Input event for typing this.searchInput.addEventListener('input', () => { const query = this.searchInput.value.trim(); // Clear previous timer clearTimeout(this.debounceTimer); // Hide suggestions if query is too short if (query.length { this.search(query); }, this.debounceTime); }); // Focus event this.searchInput.addEventListener('focus', () => { const query = this.searchInput.value.trim(); // Show trending if empty, otherwise show results if (query.length 0) { this.showSuggestions(); } }); // Keyboard navigation this.searchInput.addEventListener('keydown', (e) => { switch(e.key) { case 'ArrowDown': e.preventDefault(); this.navigateSuggestions(1); break; case 'ArrowUp': e.preventDefault(); this.navigateSuggestions(-1); break; case 'Enter': // Use selected suggestion if any if (this.isVisible && this.selectedIndex >= 0 && this.selectedIndex { if (!this.searchInput.contains(e.target) && !this.suggestionsContainer.contains(e.target)) { this.hideSuggestions(); } }); } /** * Search for assets matching the query * @param {string} query - The search query */ async search(query) { // Track search for usage statistics (anonymous) this.trackSearch(query); // Check if this is a WKN or ISIN search if (this.isWknOrIsin(query)) { const asset = this.findAssetByWknOrIsin(query); if (asset) { this.currentSuggestions = [asset]; this.renderSuggestions(); this.showSuggestions(); return; } } // Check if we have cached results const cacheKey = query.toLowerCase(); if (this.searchCache[cacheKey]) { this.currentSuggestions = this.searchCache[cacheKey]; this.renderSuggestions(); this.showSuggestions(); return; } // Check if we can find matches in popular assets cache const popularMatches = this.searchPopularAssets(query); if (popularMatches.length > 0) { // Still make API call, but show popular matches immediately this.currentSuggestions = popularMatches; this.renderSuggestions(); this.showSuggestions(); } try { // Make API call to Alpha Vantage const apiResults = await this.fetchFromApi(query); // Process and filter API results const processedResults = this.processApiResults(apiResults, query); // Filter out duplicates that might already be in popular assets const uniqueResults = processedResults.filter(apiAsset => !popularMatches.some(popAsset => popAsset.symbol === apiAsset.symbol) ); // Combine results with priority to popular assets this.currentSuggestions = [...popularMatches, ...uniqueResults].slice(0, this.maxSuggestions); // Cache results this.searchCache[cacheKey] = this.currentSuggestions; // Render and show this.renderSuggestions(); this.showSuggestions(); } catch (error) { console.error('Error searching for assets:', error); // Still show popular matches if we have them if (popularMatches.length > 0) { this.currentSuggestions = popularMatches; this.renderSuggestions(); this.showSuggestions(); } } } /** * Check if a query looks like a WKN or ISIN * @param {string} query - The search query * @returns {boolean} True if it looks like a WKN or ISIN */ isWknOrIsin(query) { // WKN: 6 alphanumeric characters const wknPattern = /^[A-Z0-9]{6}$/i; // ISIN: 12 alphanumeric characters, usually starts with 2 country code letters const isinPattern = /^[A-Z]{2}[A-Z0-9]{10}$/i; return wknPattern.test(query) || isinPattern.test(query); } /** * Find asset by WKN or ISIN from our lookup * @param {string} query - The WKN or ISIN * @returns {Object|null} The asset object or null if not found */ findAssetByWknOrIsin(query) { const normalizedQuery = query.toUpperCase(); return this.wknIsinLookup[normalizedQuery] || null; } /** * Search through popular assets cache * @param {string} query - The search query * @returns {Array} Matching assets */ searchPopularAssets(query) { const normalizedQuery = query.toLowerCase(); const allPopularAssets = [ ...this.popularAssetsCache.stocks, ...this.popularAssetsCache.crypto, ...this.popularAssetsCache.etfs ]; // Search by symbol, name, WKN, ISIN return allPopularAssets.filter(asset => { return ( asset.symbol.toLowerCase().includes(normalizedQuery) || asset.name.toLowerCase().includes(normalizedQuery) || (asset.wkn && asset.wkn.toLowerCase().includes(normalizedQuery)) || (asset.isin && asset.isin.toLowerCase().includes(normalizedQuery)) ); }); } /** * Fetch search results from Alpha Vantage API * @param {string} query - The search query * @returns {Promise} Search results */ async fetchFromApi(query) { if (!this.apiKey) { console.error('API key not provided for search'); return []; } try { const endpoint = `https://www.alphavantage.co/query?function=SYMBOL_SEARCH&keywords=${encodeURIComponent(query)}&apikey=${this.apiKey}`; const response = await fetch(endpoint); const data = await response.json(); if (data.bestMatches) { return data.bestMatches; } return []; } catch (error) { console.error('Error fetching from Alpha Vantage:', error); return []; } } /** * Process and prioritize API results * @param {Array} results - API results * @param {string} query - Original query * @returns {Array} Processed and prioritized results */ processApiResults(results, query) { if (!results || results.length === 0) { return []; } // Map API results to our format const processed = results.map(item => { const symbol = item['1. symbol']; const name = item['2. name']; const type = item['3. type'] === 'Equity' ? 'stock' : item['3. type'] === 'ETF' ? 'etf' : 'stock'; const exchange = item['4. region']; return { symbol, name, type, exchange }; }); // Prioritize by exact match, home exchange, and type return this.prioritizeResults(processed, query); } /** * Prioritize search results * @param {Array} results - Search results * @param {string} query - Original query * @returns {Array} Prioritized results */ prioritizeResults(results, query) { const normalizedQuery = query.toLowerCase(); // Score-based prioritization return results.map(asset => { let score = 0; // Exact symbol match gets highest score if (asset.symbol.toLowerCase() === normalizedQuery) { score += 100; } else if (asset.symbol.toLowerCase().startsWith(normalizedQuery)) { score += 80; } else if (asset.symbol.toLowerCase().includes(normalizedQuery)) { score += 60; } // Name matching if (asset.name.toLowerCase() === normalizedQuery) { score += 50; } else if (asset.name.toLowerCase().startsWith(normalizedQuery)) { score += 40; } else if (asset.name.toLowerCase().includes(normalizedQuery)) { score += 25; } // Prioritize primary exchanges if (asset.exchange === 'XETRA' || asset.exchange === 'Frankfurt') { score += 20; // German home exchanges } else if (asset.exchange === 'NASDAQ' || asset.exchange === 'NYSE') { score += 15; // Major US exchanges } else if (asset.exchange === 'London' || asset.exchange === 'Paris' || asset.exchange === 'Amsterdam') { score += 10; // Major European exchanges } // Add the score to the asset return { ...asset, score }; }) .sort((a, b) => b.score - a.score); // Sort by score, highest first } /** * Render suggestions in the suggestions container */ renderSuggestions() { if (!this.suggestionsContainer) return; // Create HTML content for suggestions let html = ''; if (this.currentSuggestions.length === 0) { html = '
No matching assets found
'; } else { html = '
    '; this.currentSuggestions.forEach((asset, index) => { const highlightClass = index === this.selectedIndex ? 'cfw-suggestion-item active' : 'cfw-suggestion-item'; const typeClass = `cfw-suggestion-type-${asset.type}`; html += `
  • ${asset.symbol} ${asset.name}
    ${asset.type.toUpperCase()} ${asset.exchange ? `${asset.exchange}` : ''} ${asset.wkn ? `WKN: ${asset.wkn}` : ''}
  • `; }); html += '
'; } // Update container this.suggestionsContainer.innerHTML = html; // Attach click events to list items const suggestionItems = this.suggestionsContainer.querySelectorAll('.cfw-suggestion-item'); suggestionItems.forEach((item, index) => { item.addEventListener('click', () => { this.selectSuggestion(this.currentSuggestions[index]); }); item.addEventListener('mouseover', () => { this.selectedIndex = index; this.highlightSuggestion(); }); }); } /** * Render trending assets */ showTrendingAssets() { if (!this.suggestionsContainer) return; // Get top trending assets from usage stats const trending = this.getTrendingAssets(); if (trending.length === 0) { // If no trending data yet, show popular assets instead const popular = [ ...this.popularAssetsCache.stocks.slice(0, 3), ...this.popularAssetsCache.crypto.slice(0, 2) ]; this.currentSuggestions = popular; } else { this.currentSuggestions = trending; } // Add header to indicate these are trending const trendingHeader = document.createElement('div'); trendingHeader.className = 'cfw-trending-header'; trendingHeader.textContent = 'Trending Assets'; this.renderSuggestions(); this.suggestionsContainer.insertBefore(trendingHeader, this.suggestionsContainer.firstChild); this.showSuggestions(); } /** * Get trending assets based on usage statistics * @returns {Array} Trending assets */ getTrendingAssets() { // Combine searches and watched into a single score const combinedStats = {}; // Process search counts (less weight) Object.entries(this.usageStats.searches).forEach(([query, count]) => { if (combinedStats[query]) { combinedStats[query] += count; } else { combinedStats[query] = count; } }); // Process watched assets (more weight) Object.entries(this.usageStats.watched).forEach(([symbol, count]) => { if (combinedStats[symbol]) { combinedStats[symbol] += count * 3; // Watched gets 3x weight } else { combinedStats[symbol] = count * 3; } }); // Sort by count and get top symbols const topSymbols = Object.entries(combinedStats) .sort((a, b) => b[1] - a[1]) .slice(0, 5) .map(entry => entry[0]); // Find asset objects for these symbols const allAssets = [ ...this.popularAssetsCache.stocks, ...this.popularAssetsCache.crypto, ...this.popularAssetsCache.etfs ]; return topSymbols .map(symbol => allAssets.find(asset => asset.symbol === symbol)) .filter(asset => asset !== undefined); } /** * Show suggestions container */ showSuggestions() { if (this.suggestionsContainer) { this.suggestionsContainer.style.display = 'block'; this.isVisible = true; } } /** * Hide suggestions container */ hideSuggestions() { if (this.suggestionsContainer) { this.suggestionsContainer.style.display = 'none'; this.isVisible = false; this.selectedIndex = -1; } } /** * Navigate through suggestions with keyboard * @param {number} direction - Direction to navigate (1 for down, -1 for up) */ navigateSuggestions(direction) { if (!this.isVisible || this.currentSuggestions.length === 0) return; // Calculate new index this.selectedIndex += direction; // Wrap around if needed if (this.selectedIndex = this.currentSuggestions.length) { this.selectedIndex = 0; } // Update highlighting this.highlightSuggestion(); } /** * Highlight the currently selected suggestion */ highlightSuggestion() { const items = this.suggestionsContainer.querySelectorAll('.cfw-suggestion-item'); items.forEach((item, index) => { if (index === this.selectedIndex) { item.classList.add('active'); } else { item.classList.remove('active'); } }); } /** * Select a suggestion and trigger action * @param {Object} asset - The selected asset */ selectSuggestion(asset) { if (!asset) return; // Track this asset as watched this.trackWatched(asset.symbol); // Hide suggestions this.hideSuggestions(); // Fill search input with selection if (this.searchInput) { this.searchInput.value = asset.symbol; } // Trigger custom event for parent components to handle const event = new CustomEvent('assetSelected', { detail: asset, bubbles: true }); this.searchInput.dispatchEvent(event); } /** * Track search query in usage statistics * @param {string} query - The search query */ trackSearch(query) { if (!query || query.length MAX_ENTRIES) { const entries = Object.entries(this.usageStats.searches); const sortedEntries = entries.sort((a, b) => b[1] - a[1]); const topEntries = sortedEntries.slice(0, MAX_ENTRIES); this.usageStats.searches = {}; topEntries.forEach(([key, value]) => { this.usageStats.searches[key] = value; }); } // Clean up watched if (Object.keys(this.usageStats.watched).length > MAX_ENTRIES) { const entries = Object.entries(this.usageStats.watched); const sortedEntries = entries.sort((a, b) => b[1] - a[1]); const topEntries = sortedEntries.slice(0, MAX_ENTRIES); this.usageStats.watched = {}; topEntries.forEach(([key, value]) => { this.usageStats.watched[key] = value; }); } } } // Add CSS styles for suggestions (function() { const suggestionsStyles = ` .cfw-search-suggestions { position: absolute; width: 100%; max-height: 350px; overflow-y: auto; background-color: #222; border: 1px solid #333; border-radius: 4px; margin-top: 2px; z-index: 1000; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); } .cfw-suggestions-list { list-style: none; padding: 0; margin: 0; } .cfw-suggestion-item { padding: 8px 10px; cursor: pointer; display: flex; flex-direction: column; border-bottom: 1px solid #333; } .cfw-suggestion-item:last-child { border-bottom: none; } .cfw-suggestion-item.active, .cfw-suggestion-item:hover { background-color: #333; } .cfw-suggestion-main { display: flex; justify-content: space-between; align-items: center; margin-bottom: 3px; } .cfw-suggestion-symbol { font-weight: 500; color: #fff; } .cfw-suggestion-name { color: #ccc; font-size: 0.9em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 70%; } .cfw-suggestion-details { display: flex; font-size: 0.8em; color: #999; gap: 8px; } .cfw-suggestion-type-stock { color: #4db6ac; } .cfw-suggestion-type-crypto { color: #ff9800; } .cfw-suggestion-type-etf { color: #2196f3; } .cfw-suggestion-exchange { color: #888; } .cfw-suggestion-wkn { color: #888; } .cfw-no-suggestions { padding: 12px; text-align: center; color: #888; } .cfw-trending-header { padding: 8px 10px; font-size: 0.9em; font-weight: 500; color: #ccc; background-color: #2a2a2a; border-bottom: 1px solid #333; } `; const styleEl = document.createElement('style'); styleEl.type = 'text/css'; styleEl.textContent = suggestionsStyles; document.head.appendChild(styleEl); })(); // Price Alerts Functionality let currentAlertAsset = null; function openPriceAlertModal(asset) { currentAlertAsset = asset; // Set asset info in modal document.getElementById('cfw-alert-asset-info').innerHTML = `
${asset.name} (${asset.symbol})
`; // Set current price let currentPrice = getFormattedPrice(asset.price, selectedCurrency, 2); document.getElementById('cfw-alert-current-price').textContent = currentPrice; // Prefill price input with current price document.getElementById('cfw-alert-price').value = parseFloat(asset.price).toFixed(2); // Reset alert type to default document.getElementById('cfw-alert-type').value = 'above'; // Check for existing alerts for this asset const alerts = JSON.parse(localStorage.getItem('cfwPriceAlerts') || '[]'); const assetAlerts = alerts.filter(alert => alert.assetId === asset.id); // Update existing alerts section const existingAlertsContainer = document.getElementById('cfw-existing-alerts'); if (existingAlertsContainer) { if (assetAlerts.length > 0) { let alertsHTML = '
'; assetAlerts.forEach(alert => { const isActive = !alert.triggered; const formattedPrice = getFormattedPrice(alert.targetPrice, alert.currency, 2); const alertTypeText = alert.alertType === 'above' ? 'rises above' : 'falls below'; const statusClass = isActive ? 'cfw-alert-active' : 'cfw-alert-triggered'; const statusText = isActive ? 'Active' : 'Triggered'; alertsHTML += `
${asset.symbol} ${alertTypeText} ${formattedPrice}
${statusText} ${formatDate(new Date(alert.createdAt))}
${isActive ? `` : ''}
`; }); alertsHTML += '
'; existingAlertsContainer.innerHTML = alertsHTML; // Event handlers are now directly added with onclick attribute // Show existing alerts section document.getElementById('cfw-existing-alerts-section').style.display = 'block'; } else { existingAlertsContainer.innerHTML = '

No active alerts for this asset.

'; document.getElementById('cfw-existing-alerts-section').style.display = 'block'; } } // Show modal const modal = document.getElementById('cfw-price-alert-modal'); modal.style.display = 'flex'; } function formatDate(date) { const options = { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }; return date.toLocaleDateString(undefined, options); } function deleteAlert(alertId) { let alerts = JSON.parse(localStorage.getItem('cfwPriceAlerts') || '[]'); alerts = alerts.filter(alert => alert.id !== alertId); localStorage.setItem('cfwPriceAlerts', JSON.stringify(alerts)); // Update the alerts list in the modal if (currentAlertAsset) { openPriceAlertModal(currentAlertAsset); // Refresh the modal } // Show success notification showNotification('Alert Deleted', 'Price alert has been removed', 'success'); // Refresh the table to update alert icons refreshPortfolioTable(); } function closePriceAlertModal() { document.getElementById('cfw-price-alert-modal').style.display = 'none'; currentAlertAsset = null; } function savePriceAlert() { if (!currentAlertAsset) return; const alertType = document.getElementById('cfw-alert-type').value; const targetPrice = parseFloat(document.getElementById('cfw-alert-price').value); if (isNaN(targetPrice) || targetPrice <= 0) { showNotification('Error', 'Please enter a valid price', 'error'); return; } // Create alert object const alert = { id: Date.now(), // Unique ID for the alert assetId: currentAlertAsset.id, assetName: currentAlertAsset.name, symbol: currentAlertAsset.symbol, type: currentAlertAsset.type, logo: currentAlertAsset.logo, alertType: alertType, // 'above' or 'below' targetPrice: targetPrice, currency: selectedCurrency, createdAt: new Date().toISOString(), triggered: false }; // Retrieve existing alerts from localStorage let alerts = JSON.parse(localStorage.getItem('cfwPriceAlerts') || '[]'); // Add new alert alerts.push(alert); // Save to localStorage localStorage.setItem('cfwPriceAlerts', JSON.stringify(alerts)); // Close modal closePriceAlertModal(); // Show success notification const priceString = getFormattedPrice(targetPrice, selectedCurrency, 2); const alertMessage = alertType === 'above' ? `Alert will trigger when ${currentAlertAsset.symbol} rises above ${priceString}` : `Alert will trigger when ${currentAlertAsset.symbol} falls below ${priceString}`; showNotification('Price Alert Set', alertMessage, 'success'); // Check alerts after setting up a new one checkPriceAlerts(); } function showNotification(title, message, type = 'info') { const notificationsContainer = document.getElementById('cfw-notifications'); // Create notification element const notification = document.createElement('div'); notification.className = `cfw-notification cfw-notification-${type}`; notification.innerHTML = `
${title}
${message}
`; // Add to container notificationsContainer.appendChild(notification); // Set up close button const closeButton = notification.querySelector('.cfw-notification-close'); closeButton.addEventListener('click', () => { notification.style.animation = 'cfw-fade-out 0.3s forwards'; setTimeout(() => { notification.remove(); }, 300); }); // Auto-remove after 5 seconds setTimeout(() => { if (notification.parentNode) { notification.style.animation = 'cfw-fade-out 0.3s forwards'; setTimeout(() => { if (notification.parentNode) { notification.remove(); } }, 300); } }, 5000); } function checkPriceAlerts() { // Get current alerts const alerts = JSON.parse(localStorage.getItem('cfwPriceAlerts') || '[]'); if (alerts.length === 0) return; // Get portfolio for current prices const portfolio = getPortfolio(); // Find triggered alerts const triggeredAlerts = []; const updatedAlerts = alerts.map(alert => { // Skip already triggered alerts if (alert.triggered) return alert; // Find asset in portfolio const asset = portfolio.find(a => a.id === alert.assetId); if (!asset) return alert; // Asset not in portfolio // Check if alert should trigger let shouldTrigger = false; // Convert price to the currency of the alert if different let currentPrice = asset.price; if (alert.currency !== selectedCurrency) { // Need to convert price to alert currency currentPrice = convertPrice(currentPrice, selectedCurrency, alert.currency); } if (alert.alertType === 'above' && currentPrice >= alert.targetPrice) { shouldTrigger = true; } else if (alert.alertType === 'below' && currentPrice { const formattedTargetPrice = getFormattedPrice(alert.targetPrice, alert.currency, 2); const formattedCurrentPrice = getFormattedPrice(alert.currentPrice, alert.currency, 2); const message = alert.alertType === 'above' ? `${alert.symbol} has risen above ${formattedTargetPrice} (now ${formattedCurrentPrice})` : `${alert.symbol} has fallen below ${formattedTargetPrice} (now ${formattedCurrentPrice})`; showNotification('Price Alert Triggered', message, 'warning'); }); } // Add "Set Alert" action to the Actions menu in asset row creator function document.addEventListener('DOMContentLoaded', function() { // Check for existing alerts on startup after a short delay setTimeout(checkPriceAlerts, 2000); // Check alerts periodically setInterval(checkPriceAlerts, 60000); // Check every minute }); // Hilfsfunktion, um das gesamte Portfolio zu erhalten function getPortfolio() { return samplePortfolio; } // We'll modify the renderPortfolio function to add alert buttons to each row const originalRenderPortfolio = renderPortfolio; // Function to remove any existing alert buttons function removeAlertButtonsFromRow(row) { if (!row || !row.cells || row.cells.length < 1) return; // Find the actions cell (last cell) const actionsCell = row.querySelector('td:last-child'); if (!actionsCell) return; // Remove any existing alert buttons const existingAlertBtn = actionsCell.querySelector('.cfw-alert-btn'); if (existingAlertBtn) { actionsCell.removeChild(existingAlertBtn); } } // Function to add alert button to a row function addAlertButtonToRow(row, asset) { // Only process rows with asset data if (!row || !row.cells || row.cells.length alert.assetId === asset.id && !alert.triggered ); // Create SVG bell icon for better styling const bellColor = hasActiveAlerts ? '#e91e63' : '#999'; // Pink for active, gray for inactive const bellIcon = hasActiveAlerts ? `` : ``; // Add alert button with appropriate style const alertBtn = document.createElement('button'); alertBtn.innerHTML = bellIcon; alertBtn.className = 'cfw-row-action-btn cfw-alert-btn'; alertBtn.title = hasActiveAlerts ? 'Manage Price Alerts' : 'Set Price Alert'; // Style the button // Color is now handled inside the SVG alertBtn.style.background = 'none'; alertBtn.style.border = 'none'; alertBtn.style.cursor = 'pointer'; alertBtn.style.padding = '0'; alertBtn.style.fontSize = '11px'; alertBtn.style.marginRight = '5px'; alertBtn.onclick = function(e) { e.stopPropagation(); // Prevent row expansion openPriceAlertModal(asset); }; // Insert alert button before delete button (first child) const deleteBtn = actionsCell.querySelector('.cfw-delete-btn'); if (deleteBtn) { actionsCell.insertBefore(alertBtn, deleteBtn); } else { // If no delete button, just append it actionsCell.appendChild(alertBtn); } } renderPortfolio = function() { // Call the original function first originalRenderPortfolio(); // Then add alert buttons to all rows setTimeout(() => { const rows = document.querySelectorAll('#cfw-portfolioBody tr'); rows.forEach(row => { // Only process rows with asset data (skip empty rows) if (row.cells && row.cells.length > 1) { const assetNameElement = row.querySelector('.cfw-asset-name'); if (!assetNameElement) return; const assetSymbol = assetNameElement.textContent.trim(); if (!assetSymbol) return; // Find the asset in the portfolio const asset = samplePortfolio.find(a => a.symbol === assetSymbol); if (!asset) return; // Add alert button to this row addAlertButtonToRow(row, asset); } }); }, 100); }; // Add modal and notifications container to DOM via JavaScript document.addEventListener('DOMContentLoaded', function() { // Create Price Alert Modal if not exists if (!document.getElementById('cfw-price-alert-modal')) { const modalHTML = `

Set Price Alert

Price rises above Price falls below

Active & Triggered Alerts

`; document.body.insertAdjacentHTML('beforeend', modalHTML); // Add tab switching functionality setTimeout(() => { const createTab = document.getElementById('cfw-tab-create'); const manageTab = document.getElementById('cfw-tab-manage'); const createSection = document.getElementById('cfw-create-alert-section'); const manageSection = document.getElementById('cfw-existing-alerts-section'); const setAlertButton = document.querySelector('.cfw-modal-footer .cfw-btn-primary'); if (createTab && manageTab) { createTab.addEventListener('click', function() { createTab.classList.add('active'); manageTab.classList.remove('active'); createSection.style.display = 'block'; manageSection.style.display = 'none'; setAlertButton.style.display = 'block'; }); manageTab.addEventListener('click', function() { manageTab.classList.add('active'); createTab.classList.remove('active'); manageSection.style.display = 'block'; createSection.style.display = 'none'; // No need to hide the Set Alert button as it's still useful }); } }, 500); } // Create notifications container if not exists if (!document.getElementById('cfw-notifications')) { const notificationsHTML = `
`; document.body.insertAdjacentHTML('beforeend', notificationsHTML); } });