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 += `
`;
}
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.logo ? `
` : asset.symbol.substring(0, 2)}
${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);
}
});
Name ↕ | Ticker ↕ | WKN ↕ | Price ↕ | Price EUR ⓘ ↕ | Signal ↕ | Perf. (1D) ↕ | Type ↕ | Actions |
---|---|---|---|---|---|---|---|---|
No matching assets... |
Crypto Market Indicators
Stock Market 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
Price Chart
Key Statistics
Latest News
Technical Indicators
${asset.type === 'stock' ? 'Fundamentals' : 'Market Data'}
Analysis
Loading analysis...
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 = `- ';
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 += '
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 = `