ob_start(); // Performance: Signal to footer.php that we lazy-load 3rd party scripts on this page $GLOBALS['lazy_load_3rd_party'] = true; // =================================================================== // ### BEST PRACTICES: Sikkerhedsheaders ### // =================================================================== header("Strict-Transport-Security: max-age=31536000; includeSubDomains; preload"); header("X-Content-Type-Options: nosniff"); header("X-Frame-Options: SAMEORIGIN"); header("Referrer-Policy: strict-origin-when-cross-origin"); header("Permissions-Policy: camera=(), microphone=(), geolocation=()"); header("Cross-Origin-Opener-Policy: same-origin-allow-popups"); // =================================================================== // ### GLOBAL ERROR HANDLER - Redirect til 404 ved kritiske fejl ### // =================================================================== // Tolerant error handler: Logger warnings/notices, redirecter KUN ved fatale fejl set_error_handler(function($severity, $message, $file, $line) { // Ignorer undertrykte fejl (@) if (!(error_reporting() & $severity)) { return false; } // Log alle fejl til error_log for debugging error_log("Stock.php Error [{$severity}]: $message in $file on line $line"); // Kun redirect ved alvorlige fejl (E_ERROR, E_USER_ERROR) // Warnings og notices logges men siden fortsætter if (in_array($severity, [E_ERROR, E_USER_ERROR])) { header("HTTP/1.1 404 Not Found"); header("Location: /404.php"); exit(); } // Returner true for at forhindre PHP's standard error handler return true; }); // Fang fatale fejl (Division by zero, etc.) register_shutdown_function(function() { $error = error_get_last(); if ($error !== null && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) { // Log fejlen error_log("Stock.php Fatal Error: " . $error['message'] . " in " . $error['file'] . " on line " . $error['line']); // Redirect til 404 (hvis headers ikke allerede er sendt) if (!headers_sent()) { header("HTTP/1.1 404 Not Found"); header("Location: /404.php"); } exit(); } }); // Fang uncaught exceptions set_exception_handler(function($exception) { error_log("Stock.php Exception: " . $exception->getMessage()); header("HTTP/1.1 404 Not Found"); header("Location: /404.php"); exit(); }); // =// =================================================================== // Din eksisterende kode starter her... // =================================================================== require_once($_SERVER["DOCUMENT_ROOT"] . '/includes/main.php'); // Danske månedsnavne (bruges til datoformatering) $danish_months = ['januar', 'februar', 'marts', 'april', 'maj', 'juni', 'juli', 'august', 'september', 'oktober', 'november', 'december']; function clean_company_name($name) { $remove_patterns = [ // Aktieklasser '/\s*Class\s+[A-Z]\s*(Common\s*)?(Stock|Shares?)?/i', '/\s*Ordinary\s+Shares?/i', '/\s*Common\s+Stock/i', '/\s*Preferred\s+Stock/i', // Juridiske suffixer (International) '/\s*,?\s*Inc\.?$/i', '/\s*,?\s*Corp\.?$/i', '/\s*,?\s*Corporation$/i', '/\s*,?\s*Ltd\.?$/i', '/\s*,?\s*Limited$/i', '/\s*,?\s*LLC$/i', '/\s*,?\s*PLC$/i', '/\s*,?\s*S\.?A\.?$/i', '/\s*,?\s*N\.?V\.?$/i', '/\s*,?\s*Co\.?$/i', '/\s*,?\s*Company$/i', '/\s*,?\s*Group$/i', '/\s*,?\s*Holdings?$/i', // Nordiske juridiske suffixer '/\s*\(publ\)\.?$/i', '/\s*publ\.?$/i', '/\s*AB$/i', '/\s*ASA$/i', '/\s*Oyj$/i', '/\s*A\/S$/i', '/\s*ApS$/i', '/\s*HF$/i', '/\s*AG$/i', '/\s*SE$/i', // ADR/ADS '/\s*\(?ADR\)?$/i', '/\s*\(?ADS\)?$/i', '/\s*American\s+Depositary\s+(Shares?|Receipts?)/i', ]; $cleaned = $name; foreach ($remove_patterns as $pattern) { $cleaned = preg_replace($pattern, '', $cleaned); } return trim(preg_replace('/\s+/', ' ', $cleaned)); } function generate_seo_title($company_name, $ticker, $dividend_yield = 0, $max_length = 70, $extra = []) { $clean_name = clean_company_name($company_name); $year = date("Y"); $is_idx = $extra['is_index'] ?? false; $is_cry = $extra['is_crypto'] ?? false; // V2: 'Aktie' flyttet fra suffix til hook – frigør ~6 tegn til '2026' i alle varianter $suffix = " | {$clean_name} ({$ticker})"; $type_label = $is_cry ? 'Krypto' : ($is_idx ? 'Indeks' : 'Aktie'); $analysis_lbl = $is_cry ? 'kryptoanalyse' : ($is_idx ? 'indeksanalyse' : 'aktieanalyse'); // Hent og formater data $rsi = $extra["rsi"] ?? null; $target = $extra["target"] ?? 0; $current_close = $extra["current_close"] ?? 0; $macd = $extra["macd"] ?? null; $macd_signal = $extra["macd_signal"] ?? null; $upside_pct = ($target > 0 && $current_close > 0) ? (($target - $current_close) / $current_close) * 100 : 0; $div_tag = ($dividend_yield > 0) ? number_format($dividend_yield, 1) . "%" : ""; // Hent trend-data $ta_sig = $extra['ta_signal'] ?? null; $ta_sig_en = $extra['ta_signal_en'] ?? null; $in_uptrend = $extra['is_uptrend'] ?? false; $in_downtrend = $extra['is_downtrend'] ?? false; $macd5 = $extra['macd5'] ?? null; $macd5_signal = $extra['macd5_signal'] ?? null; $daily_bull = ($macd !== null && $macd_signal !== null && $macd > $macd_signal); $weekly_bull = ($macd5 !== null && $macd5_signal !== null && $macd5 > $macd5_signal); // Prioriteret hook-logik – KURSMÅL-FIRST baseret på søgeordsanalyse: // 1. Kursmål + upside% (højest søgevolumen) // 2. Udbytte + årstal (høj volumen, stor mulighed) // 3. Trend-signaler (lavere volumen men høj CTR) // 4. RSI (kun i kontekst) // // Stabilitet: vi runder upside/downside til nærmeste 5% så små daglige // kursudsving (fx 32.3% → 32.5%) ikke regenererer titlen hver dag. Det // lader Google akkumulere CTR-data pr title-variant, og reducerer // title-churn som Search Console-støj. $_bucket5 = function($v) { return (int)(round($v / 5) * 5); }; $upside_b = $_bucket5($upside_pct); $downside_b = $_bucket5(abs($upside_pct)); $div_b = (round($dividend_yield * 2) / 2); // nærmeste 0.5% $div_tag_stable = ($dividend_yield > 0) ? number_format($div_b, 1) . "%" : ""; $analyst_total = intval($extra['analyst_total'] ?? 0); $consensus_label = $extra['consensus_label'] ?? ''; $has_consensus = ($analyst_total > 0 && $consensus_label !== ''); // Ændringer baseret på Search Console data: // - "udbytte 2026" threshold sænket fra >5% til >3% (13-20% CTR på pattern) // - "anbefaling" keyword tilføjet til flere hooks (20% CTR på "X aktie anbefaling") // - Ny analytiker-konsensus-hook når vi har Rating-data (selv uden target) // - Tekniske signal-hooks får "+ kursmål" når target > 0 (fanger også kursmål-queries) $hook = ""; // P1: Kursmål med kæmpe upside (>40%) – TOPPRIORITET for søgevolumen if (!$is_idx && $upside_pct > 40 && $target > 0) { $hook = "{$clean_name} kursmål {$year}: {$upside_b}% potentiale – anbefaling og analyse"; // P2: Kursmål med god upside (>15%) – matcher "X kursmål 2026" queries } elseif (!$is_idx && $upside_pct > 15 && $target > 0) { $hook = "{$clean_name} kursmål {$year}: {$upside_b}% potentiale – anbefaling og {$analysis_lbl}"; // P3: Kursmål med downside – matcher "er X overvurderet"-intent } elseif (!$is_idx && $upside_pct < -15 && $target > 0) { $hook = "{$clean_name} kursmål {$year}: {$downside_b}% negativt potentiale – overvurderet?"; // P4: Solidt udbytte (>3%) – udbytte-first title. Data viser 13-20% CTR // på "X udbytte 2026"-queries ned til 3-4% yield, så tærsklen er sænket. } elseif (!$is_idx && $dividend_yield > 3) { $hook = "{$clean_name} udbytte {$year}: {$div_tag_stable} – kursmål og anbefaling"; // P5: Kursmål med moderat upside + udbytte kombineret } elseif (!$is_idx && $target > 0 && $dividend_yield > 2) { $hook = "{$clean_name} kursmål {$year}: {$upside_b}% potentiale + {$div_tag_stable} udbytte"; // P6: Kursmål generisk (har kursmål men lav upside) } elseif (!$is_idx && $target > 0) { $hook = "{$clean_name} kursmål {$year}: {$upside_b}% potentiale – anbefaling og {$analysis_lbl}"; // P7: Moderat udbytte (>2%) uden kursmål } elseif (!$is_idx && $dividend_yield > 2) { $hook = "{$clean_name} udbytte {$year}: {$div_tag_stable} – anbefaling og {$analysis_lbl}"; // P7b: NY – Analytiker-konsensus uden kursmål (f.eks. aktier hvor target mangler // men analytikere har givet anbefaling). Fanger "X aktie anbefaling"-query (20% CTR). } elseif (!$is_idx && $has_consensus) { $hook = "{$clean_name} {$type_label} anbefaling {$year}: {$consensus_label} – {$analysis_lbl}"; // P8: Stærkt Køb signal (optrend + begge MACD positive) } elseif ($ta_sig_en === 'strong_buy') { $hook = "{$clean_name} {$type_label}: Stærkt Køb – anbefaling og analyse {$year}"; // P9: Stærkt Sælg signal } elseif ($ta_sig_en === 'strong_sell') { $hook = "{$clean_name} {$type_label}: Stærkt Sælg – anbefaling og analyse {$year}"; // P10: Købssignal } elseif ($ta_sig_en === 'buy') { $hook = "{$clean_name} {$type_label}: Købssignal – anbefaling og analyse {$year}"; // P11: Salgssignal } elseif ($ta_sig_en === 'sell') { $hook = "{$clean_name} {$type_label}: Salgssignal – anbefaling og analyse {$year}"; // P12: Afvent/blandet } elseif ($ta_sig_en === 'hold') { $hook = "{$clean_name} {$type_label}: Blandet signal – anbefaling og analyse {$year}"; // P13: Udbytte lavt men eksisterende } elseif (!$is_idx && $dividend_yield > 0) { $hook = "{$clean_name} udbytte {$year}: {$div_tag_stable} – anbefaling og {$analysis_lbl}"; // P14: Fallback – inkluder "kursmål" og "anbefaling" for at fange generic long-tail } else { if ($is_idx) { $hook = "{$clean_name} {$type_label}: Kurs og analyse {$year}"; } else { $hook = "{$clean_name} {$type_label} {$year}: kurs, analyse og anbefaling"; } } // Byg og afkort titel $title = $hook . $suffix; if (mb_strlen($title) > $max_length) { // Prøv først uden suffix (hook alene er stadig SEO-stærk) if (mb_strlen($hook) <= $max_length) { $title = $hook; } else { $available_len = $max_length - 3; $title = mb_substr($hook, 0, $available_len) . "..."; } } return $title; } // =================================================================== // DYNAMISK H1-OVERSKRIFT – supplerer title-tag med unik, engagerende overskrift // =================================================================== function generate_h1_title($company_name, $ticker, $dividend_yield = 0, $extra = []) { $clean_name = clean_company_name($company_name); $year = date("Y"); $is_idx = $extra['is_index'] ?? false; $is_cry = $extra['is_crypto'] ?? false; $analysis_label = $is_cry ? 'Kryptoanalyse' : ($is_idx ? 'Indeksanalyse' : 'Aktieanalyse'); $rsi = $extra["rsi"] ?? null; $target = $extra["target"] ?? 0; $current_close = $extra["current_close"] ?? 0; $macd = $extra["macd"] ?? null; $macd_signal = $extra["macd_signal"] ?? null; $upside_pct = ($target > 0 && $current_close > 0) ? (($target - $current_close) / $current_close) * 100 : 0; // Hent trend-data $ta_sig_en = $extra['ta_signal_en'] ?? null; $in_uptrend = $extra['is_uptrend'] ?? false; $in_downtrend = $extra['is_downtrend'] ?? false; $h1 = ""; // Stabiliser upside/downside og udbytte til nærmeste 5% / 0.5% så H1 ikke // muterer dagligt ved små kursudsving – samme logik som i title-funktionen. $upside_b = (int)(round($upside_pct / 5) * 5); $downside_b = (int)(round(abs($upside_pct) / 5) * 5); $div_b = round($dividend_yield * 2) / 2; $div_tag_stable = ($dividend_yield > 0) ? number_format($div_b, 1) . "%" : ""; $analyst_total = intval($extra['analyst_total'] ?? 0); $consensus_label = $extra['consensus_label'] ?? ''; $has_consensus = ($analyst_total > 0 && $consensus_label !== ''); // KURSMÅL-FIRST H1 logik (matcher SEO title prioritering) med "anbefaling" // keyword tilføjet og udbytte-threshold sænket til 3% baseret på CTR-data. if (!$is_idx && $upside_pct > 40 && $target > 0) { $h1 = "{$clean_name} ({$ticker}) kursmål {$year}: {$upside_b}% potentiale – anbefaling og {$analysis_label}"; } elseif (!$is_idx && $upside_pct > 15 && $target > 0) { $h1 = "{$clean_name} ({$ticker}) kursmål {$year}: {$upside_b}% potentiale – anbefaling og {$analysis_label}"; } elseif (!$is_idx && $upside_pct < -15 && $target > 0) { $h1 = "{$clean_name} ({$ticker}) kursmål {$year}: {$downside_b}% negativt potentiale – overvurderet?"; } elseif (!$is_idx && $dividend_yield > 3) { $h1 = "{$clean_name} ({$ticker}) udbytte {$year}: {$div_tag_stable} – kursmål og anbefaling"; } elseif (!$is_idx && $target > 0 && $dividend_yield > 2) { $h1 = "{$clean_name} ({$ticker}) kursmål {$year}: {$upside_b}% potentiale + {$div_tag_stable} udbytte"; } elseif (!$is_idx && $target > 0) { $h1 = "{$clean_name} ({$ticker}) kursmål og anbefaling {$year}"; } elseif (!$is_idx && $dividend_yield > 2) { $h1 = "{$clean_name} ({$ticker}) udbytte {$year}: {$div_tag_stable} – anbefaling og {$analysis_label}"; } elseif (!$is_idx && $has_consensus) { $h1 = "{$clean_name} ({$ticker}) aktie anbefaling {$year}: {$consensus_label} – {$analysis_label}"; } elseif ($ta_sig_en === 'strong_buy') { $h1 = "{$clean_name} ({$ticker}) – Stærkt Køb: anbefaling og {$analysis_label} {$year}"; } elseif ($ta_sig_en === 'strong_sell') { $h1 = "{$clean_name} ({$ticker}) – Stærkt Sælg: anbefaling og {$analysis_label} {$year}"; } elseif ($ta_sig_en === 'buy') { $h1 = "{$clean_name} ({$ticker}) – Købssignal: anbefaling og {$analysis_label} {$year}"; } elseif ($ta_sig_en === 'sell') { $h1 = "{$clean_name} ({$ticker}) – Salgssignal: anbefaling og {$analysis_label} {$year}"; } elseif ($ta_sig_en === 'hold') { $h1 = "{$clean_name} ({$ticker}) – Blandet signal: anbefaling og {$analysis_label} {$year}"; } elseif (!$is_idx && $dividend_yield > 0) { $h1 = "{$clean_name} ({$ticker}) udbytte {$year}: {$div_tag_stable} – anbefaling og {$analysis_label}"; } else { $h1 = "{$clean_name} ({$ticker}) {$analysis_label} {$year}: kurs, kursmål og anbefaling"; } return $h1; } function generate_seo_description($company_name, $ticker, $target, $rsi, $currency, $max_length = 160, $extra = []) { $clean_name = clean_company_name($company_name); $year = date("Y"); $is_idx = $extra['is_index'] ?? false; $is_cry = $extra['is_crypto'] ?? false; $analysis_label = $is_cry ? 'Kryptoanalyse' : ($is_idx ? 'Indeksanalyse' : 'Aktieanalyse'); // Hent og formater data $current_close = $extra["current_close"] ?? 0; $dividend_yield = $extra["dividend_yield"] ?? 0; $macd = $extra["macd"] ?? null; $macd_signal = $extra["macd_signal"] ?? null; $currency_code = rtrim($currency, "."); // Fix for "Kr.." bug // Byg datapunkter $parts = []; // Prefix med kursmål/udbytte/anbefaling-fokus for bedre CTR på søgeresultater. // Stabiliseret til nærmeste 5% så description ikke churner på små kursudsving. $upside_pct = ($target > 0 && $current_close > 0) ? (($target - $current_close) / $current_close) * 100 : 0; $upside_b = (int)(round($upside_pct / 5) * 5); $analyst_total = intval($extra['analyst_total'] ?? 0); $consensus_label = $extra['consensus_label'] ?? ''; if (!$is_idx && $target > 0 && abs($upside_pct) > 10) { $prefix = "{$clean_name} kursmål {$year}: " . $upside_b . "% potentiale. "; } elseif (!$is_idx && $dividend_yield > 3) { $prefix = "{$clean_name} udbytte {$year}: " . number_format($dividend_yield, 1) . "%. "; } elseif (!$is_idx && $analyst_total > 0 && $consensus_label !== '') { // Fanger "X aktie anbefaling"-queries (20% CTR på Ambu/Coloplast/etc.) $prefix = "{$clean_name} aktie anbefaling {$year}: {$consensus_label}. "; } else { $prefix = "{$clean_name} ({$ticker}) {$analysis_label} {$year}: "; } // 1. Kurs if ($current_close > 0) { $parts[] = "Kurs " . number_format($current_close, 2) . " " . $currency_code; } // 2. Kursmål if ($target > 0) { $upside_str = ""; if ($current_close > 0) { $upside_str = " (" . ($upside_pct >= 0 ? "+" : "") . $upside_b . "% potentiale)"; } $parts[] = "Kursmål " . number_format($target, 2) . $upside_str; } // 3. Udbytte if ($dividend_yield > 0) { $parts[] = "Udbytte " . number_format($dividend_yield, 1) . "%"; } // 4. Anbefaling – analytiker-konsensus (fanger "X anbefaling" queries) if ($analyst_total > 0 && $consensus_label !== '') { $parts[] = "Anbefaling: " . $consensus_label; } // 4. RSI if ($rsi && is_numeric($rsi)) { $rsi_status = ($rsi > 70) ? "overkøbt" : (($rsi < 30) ? "oversolgt" : "neutral"); $parts[] = "RSI " . number_format($rsi, 0) . " (" . $rsi_status . ")"; } // 6. MACD trend (nullinje) if ($macd !== null) { $parts[] = "MACD " . ($macd > 0 ? "positiv (bullish trend)" : "negativ (bearish trend)"); } // Byg description med progressiv afkortning (uden fyldord) $description = $prefix; $first = true; foreach ($parts as $part) { $temp_desc = $description . ($first ? "" : " | ") . $part; if (mb_strlen($temp_desc) <= $max_length) { $description = $temp_desc; $first = false; } else { break; // Stop med at tilføje dele, hvis vi overskrider længden } } return $description; } // =================================================================== //Test of push $active_navigation_item = "stock"; $current_page = "stock"; $page_meta_robots_follow = false; $stock_permalink = htmlspecialchars($_GET['permalink'] ?? '', ENT_QUOTES, 'UTF-8'); $page_canonical_url = 'https://'.site_url().'/stock/' . $stock_permalink; $exchange_permalink = $_GET['exchange_permalink'] ?? ""; $sector_permalink = $_GET['sector_permalink'] ?? ""; $country_code = $_GET['country_code'] ?? ""; $is_crypto = boolval($_GET['is_crypto'] ?? 0); $is_index = boolval($_GET['is_index'] ?? 0); // Fallback: detect indeks fra permalink (f.eks. stoxx.indx, sixv.indx) if (!$is_index && str_ends_with(strtoupper($stock_permalink), '.INDX')) { $is_index = true; } $page_meta_robots_follow = true; // =================================================================== // ### ASSET-TYPE TERMINOLOGI – Tilpas sprog til aktie/krypto/indeks ### // =================================================================== if ($is_crypto) { $asset_type = 'kryptovaluta'; $asset_type_bestemt = 'kryptovalutaen'; $asset_suffix = ''; // "Skal man købe Bitcoin nu?" (IKKE "Bitcoin aktien") $asset_suffix_genitive = ''; // "kursmålet for Bitcoin" $analysis_label = 'Kryptoanalyse'; $type_label = 'Krypto'; $asset_kurser_plural = 'kryptokurser'; $asset_en = 'en kryptovaluta'; } elseif ($is_index) { $asset_type = 'indeks'; $asset_type_bestemt = 'indekset'; $asset_suffix = ''; $asset_suffix_genitive = ''; $analysis_label = 'Indeksanalyse'; $type_label = 'Indeks'; $asset_kurser_plural = 'indekskurser'; $asset_en = 'et indeks'; } else { $asset_type = 'aktie'; $asset_type_bestemt = 'aktien'; $asset_suffix = ' aktien'; // "Skal man købe Novo aktien nu?" $asset_suffix_genitive = ' aktien'; // "kursmålet for Novo aktien" $analysis_label = 'Aktieanalyse'; $type_label = 'Aktie'; $asset_kurser_plural = 'aktiekurser'; $asset_en = 'en aktie'; } // =================================================================== // ### SERVER-SIDE HTML CACHE - Reducer TTFB fra ~1500ms til ~50ms ### // =================================================================== // === CACHE MIDLERTIDIGT DEAKTIVERET FOR UDVIKLING === $cache_dir = null; $cache_file = null; $cache_active = false; // =================================================================== $stock_data = array(); $json_result = json_decode(file_get_contents_cus('https://'.site_api_url().'/stocks/details/' . $stock_permalink), true); $stock_data = (array) $json_result['data']; //https://'.site_api_url().'/stocks/details/agf-b if (!(boolval($stock_data['is_active'] ?? false) == true)) { header("HTTP/1.1 404 Not Found"); include('404.php'); exit; } // SEO: Definer renset virksomhedsnavn tidligt $stock_name = clean_company_name($stock_data['name']); $stock_eod_link = $stock_data['eod_import_code']; $target = $stock_data['wallstreettarget'] ?? 0.00; $trading_data = $stock_data['trading'] ?? ['history' => [], 'points' => 0]; $fundamental = $stock_data['fundamental'] ?? []; $calender = $stock_data['calendar_items'] ?? []; $next_earnings_report_date = $stock_data['next_earnings_report_date'] ?? null; // Fallback: Hvis API'en ikke returnerer next_earnings_report_date, brug calendar_items if (empty($next_earnings_report_date) && !empty($stock_data['calendar_items'])) { foreach ($stock_data['calendar_items'] as $cal_item) { $cal_date = $cal_item['report_date'] ?? null; if (!empty($cal_date) && strtotime($cal_date) >= strtotime('today')) { $next_earnings_report_date = $cal_date; break; } } } // Detect: Er regnskabet I DAG? $earnings_is_today = false; if (!empty($next_earnings_report_date)) { $earnings_is_today = (date('Y-m-d', strtotime($next_earnings_report_date)) === date('Y-m-d')); } // Trying to get newest dividend data $dividend_data = $stock_data['dividend_items'][0] ?? NULL; if ($dividend_data !== NULL) { // Access array elements with ['key'] syntax $latest_dividend_date = $dividend_data['dividend_date']; $latest_ex_date = $dividend_data['ex_date']; $latest_payment_date = $dividend_data['payment_date']; $latest_value = $dividend_data['value']; $latest_currency = $dividend_data['currency']; } if (isset($stock_data['calendar_items'][0])) { // Få data om næste regnskab $next_report = $stock_data['calendar_items'][0]; $next_earning_date = $next_report['report_date'] ?? null; $quarter_date = $next_report['date'] ?? null; // Dato for hvilket kvartal regnskabet dækker $before_after = $next_report['before_after'] ?? null; // Konverter kvartalsdatoen til en timestamp for at tjekke hvilken måned $quarter_month = (int)date('m', strtotime($quarter_date)); // Bestem hvilket kvartal der rapporteres for if ($quarter_month >= 1 && $quarter_month <= 3) { $quarter = "første kvartal (Q1)"; } elseif ($quarter_month >= 4 && $quarter_month <= 6) { $quarter = "andet kvartal (Q2)"; } elseif ($quarter_month >= 7 && $quarter_month <= 9) { $quarter = "tredje kvartal (Q3)"; } else { $quarter = "fjerde kvartal (Q4)"; } // Bestem teksten for before/after market $timing_text = ""; // Tjek for null eller tom værdi if (!empty($before_after)) { if (strcasecmp($before_after, "BeforeMarket") === 0) { $timing_text = "før markedets åbning"; } elseif (strcasecmp($before_after, "AfterMarket") === 0) { $timing_text = "efter markedets lukning"; } } else { $timing_text = "ukendt tid"; // Valgfri fallback-tekst } // Generer dynamisk tekst for næste regnskab if (isset($next_earning_date)) { // Tjek om regnskabet er I DAG if ($earnings_is_today) { $Next_earnings = "
" . "⚡ {$stock_name} offentliggør regnskab I DAG!" . ($timing_text && $timing_text !== 'ukendt tid' ? " – " . ucfirst($timing_text) . "." : "") . " Dette regnskab dækker " . $quarter . "." . "
"; $Next_earnings .= "{$stock_name}'s regnskab for " . $quarter . " offentliggøres i dag, d. " . humanReadableDate($next_earning_date) . ($timing_text && $timing_text !== 'ukendt tid' ? " " . $timing_text : "") . ". " . "Regnskabsperioden sluttede d. " . humanReadableDate($quarter_date) . ". " . "Hold øje med kursreaktionen efter offentliggørelsen.

"; } else { $Next_earnings = "{$stock_name}'s næste regnskab bliver offentliggjort d. " . humanReadableDate($next_earning_date) . ($timing_text && $timing_text !== 'ukendt tid' ? " " . $timing_text : "") . ". " . "Dette regnskab dækker " . $quarter . " og sluttede d. " . humanReadableDate($quarter_date) . ".

"; } } else { $Next_earnings = "{$stock_name}'s næste regnskabsdato er endnu ikke tilgængelig. Tjek virksomhedens finansielle kalender for flere detaljer.

"; } } else { // Hvis der ikke er nogen data om næste regnskab — fall back på det seneste // historiske regnskab så siden ikke siger "ingen data" når selskabet netop // har rapporteret. Brug earnings_history (samme kilde som beat/miss-sektionen). $_recent_earning = null; if (!empty($stock_data['earnings_history'])) { foreach ($stock_data['earnings_history'] as $_eh_item) { if (!empty($_eh_item['report_date']) && strtotime($_eh_item['report_date']) <= time()) { $_recent_earning = $_eh_item; break; // history er sorteret DESC, første under-dagens-dato er den seneste } } } if ($_recent_earning) { $_re_date = $_recent_earning['report_date']; $_re_days_ago = (int) floor((time() - strtotime($_re_date)) / 86400); $_re_quarter = $_recent_earning['fiscal_quarter'] ?? ''; $_re_result = $_recent_earning['result'] ?? ''; $_re_when_txt = $_re_days_ago === 0 ? 'i dag' : ($_re_days_ago === 1 ? 'i går' : 'for ' . $_re_days_ago . ' dage siden'); $_re_verdict_txt = ''; if ($_re_result === 'beat') $_re_verdict_txt = ' – og slog analytikernes forventninger'; elseif ($_re_result === 'miss') $_re_verdict_txt = ' – og skuffede ift. analytikernes forventninger'; $Next_earnings = "{$stock_name} aflagde senest regnskab " . $_re_when_txt . " (d. " . humanReadableDate($_re_date) . ($_re_quarter ? ", " . htmlspecialchars($_re_quarter) : '') . ")" . $_re_verdict_txt . ". Den næste regnskabsdato er endnu ikke offentliggjort – tjek virksomhedens finansielle kalender for opdateringer. Se beat/miss-sektionen nedenfor for detaljer på det seneste regnskab.

"; } else { $Next_earnings = "{$stock_name}'s næste regnskabsdato er endnu ikke tilgængelig. Tjek virksomhedens finansielle kalender for flere detaljer.

"; } } // Udskriv den dynamiske tekst $last_trading_data_history = $trading_data['history'][0] ?? null; $last_action = $last_trading_data_history['action'] ?? 'unknown'; $eod_data = $stock_data['eod'] ?? []; $sma20_bb = (($eod_data['SMA20_BB'] ?? '') == "bullish") ? "OP-TREND" : "NED-TREND"; $sma50_bb = (($eod_data['SMA50_BB'] ?? '') == "bullish") ? "OP-TREND" : "NED-TREND"; $sma200_bb = (($eod_data['SMA200_BB'] ?? '') == "bullish") ? "OP-TREND" : "NED-TREND"; $sma20 = $eod_data['SMA20'] ?? 0; $sma50 = $eod_data['SMA50'] ?? 0; $rsi = isset($eod_data['RSI14']) ? $eod_data['RSI14'] : null; $ma50 = isset($eod_data['SMA50']) ? $eod_data['SMA50'] : null; $ma200 = isset($eod_data['SMA200']) ? $eod_data['SMA200'] : null; $macd = isset($eod_data['MACD']) ? $eod_data['MACD'] : null; $macd_signal = isset($eod_data['MACD_signal']) ? $eod_data['MACD_signal'] : null; $std_dev = isset($eod_data['std_dev']) ? $eod_data['std_dev'] : null; $macd5 = isset($eod_data['MACD5']) ? $eod_data['MACD5'] : null; $macd5_signal = isset($eod_data['MACD5_signal']) ? $eod_data['MACD5_signal'] : null; $history = json_decode(file_get_contents_cus('https://api.tradedesk.dk/stocks/data/' . $stock_permalink . '/history'), true); $history_data = $history['data'] ?? null; $last_history_data = !empty($history_data) ? $history_data[count($history_data)-1] : NULL; $last_trade_close = $last_history_data['close'] ?? 0; $current_close = (isset($stock_data['live_close'])) ? $stock_data['live_close'] : $eod_data['close']; // === YTD-AFKAST (year-to-date kursudvikling) === // Finder lukkekurs på sidste handelsdag i forrige år (eller første tilgængelige // dag i indeværende år hvis vi mangler den) og beregner den procentvise ændring // frem til nu. Variablerne bruges af TL;DR-boksen + en synlig YTD-linje under, // og ramler kun ind hvis vi har nok historik. $ytd_pct = null; $ytd_direction = null; // 'steget' eller 'faldet' $_ytd_year = (int) date('Y'); $_ytd_baseline_close = null; // /stocks/data//history returnerer felter 'trade_timestamp' og // 'close'. Vi accepterer både 'trade_timestamp' og 'date' for at være robuste, // hvis API'et skifter format i fremtiden. if (!empty($history_data) && $current_close > 0) { // history_data forventes sorteret kronologisk (ældst først). Vi går // igennem og finder sidste bar i forrige år; hvis vi ikke har det, // falder vi tilbage på første bar i indeværende år. $_prev_year_last = null; $_curr_year_first = null; foreach ($history_data as $_bar) { $_bar_date = $_bar['trade_timestamp'] ?? $_bar['date'] ?? null; if (!$_bar_date || empty($_bar['close']) || $_bar['close'] <= 0) continue; $_bar_year = (int) date('Y', strtotime($_bar_date)); if ($_bar_year === $_ytd_year - 1) { $_prev_year_last = (float) $_bar['close']; // overwrites until we hit the last one } elseif ($_bar_year === $_ytd_year && $_curr_year_first === null) { $_curr_year_first = (float) $_bar['close']; } } $_ytd_baseline_close = $_prev_year_last ?? $_curr_year_first; if ($_ytd_baseline_close !== null && $_ytd_baseline_close > 0) { $ytd_pct = (($current_close - $_ytd_baseline_close) / $_ytd_baseline_close) * 100; $ytd_direction = ($ytd_pct >= 0) ? 'steget' : 'faldet'; } } // === KURSUDVIKLING OVER FLERE TIDSHORISONTER === // SEO/intent: høj dansk søgevolumen på "[aktie] kursudvikling", "[aktie] 1 år", // "[aktie] historisk afkast". Vi beregner 1 uge / 1 måned / 3 måneder / 1 år // ud fra de daglige EOD-lukkekurser fra /eod-endpointet. // Note: "5 år" er bevidst droppet — sjældent relevant for retail-investorer // der vurderer en aktie i dag, og lader os reducere backend-payload til 13 // måneders data (84% mindre transfer). $performance_periods = [ '1 uge' => 7, '1 måned' => 30, '3 måneder' => 91, '1 år' => 365, ]; $performance_data = []; // ['1 uge' => ['pct' => 2.3, 'close' => 173.40], ...] if (!empty($history_data) && $current_close > 0) { // Indekser history_data efter dato for hurtigt opslag. API'et returnerer // 'trade_timestamp' — vi accepterer også 'date' som fallback. $_hist_by_date = []; foreach ($history_data as $_bar) { $_bar_date = $_bar['trade_timestamp'] ?? $_bar['date'] ?? null; if ($_bar_date && !empty($_bar['close']) && $_bar['close'] > 0) { // Normalisér til YYYY-MM-DD så lookup på date('Y-m-d', ts) matcher. $_hist_by_date[date('Y-m-d', strtotime($_bar_date))] = (float) $_bar['close']; } } // Sortér historik-datoer kronologisk (YYYY-MM-DD sorterer som ISO). $_hist_dates_sorted = array_keys($_hist_by_date); sort($_hist_dates_sorted); $_hist_dates_reversed = array_reverse($_hist_dates_sorted); // For hver periode: walk historik baglæns og find seneste bar på eller // før target-datoen. Robust mod manglende bars (weekender, helligdage, // sparsom data). Hvis intet bar findes på eller før target, springer vi // perioden over (fx "5 år" for en aktie der kun har 2 års historik). $_now_ts = time(); foreach ($performance_periods as $_label => $_days_back) { $_target_date = date('Y-m-d', $_now_ts - ($_days_back * 86400)); $_baseline = null; $_baseline_date = null; foreach ($_hist_dates_reversed as $_d) { if ($_d <= $_target_date) { $_baseline = $_hist_by_date[$_d]; $_baseline_date = $_d; break; } } if ($_baseline !== null && $_baseline > 0) { $_pct = (($current_close - $_baseline) / $_baseline) * 100; $performance_data[$_label] = ['pct' => $_pct, 'close' => $_baseline, 'date' => $_baseline_date]; } } } // === SAMLET TEKNISK SIGNAL (trend-first framework) === // Trend-score: +1 for hver optrend-indikator, -1 for nedtrend $trend_score = 0; if ($ma50 !== null && $current_close > 0 && $ma50 > 0) { $trend_score += ($current_close > $ma50) ? 1 : -1; } if ($ma200 !== null && $current_close > 0 && $ma200 > 0) { $trend_score += ($current_close > $ma200) ? 1 : -1; } // MACD-score: baseret på NULLINJEN (trend-retning), ikke signal crossover // MACD > 0 = bullish trend (12-EMA over 26-EMA) // MACD < 0 = bearish trend (12-EMA under 26-EMA) // Signal crossover (MACD vs signal) = momentum-skift, IKKE trend $macd_score = 0; if ($macd !== null) { $macd_score += ($macd > 0) ? 1 : -1; } if ($macd5 !== null) { $macd_score += ($macd5 > 0) ? 1 : -1; } // Momentum-score: baseret på signal crossover (sekundært signal) $momentum_score = 0; if ($macd !== null && $macd_signal !== null) { $momentum_score += ($macd > $macd_signal) ? 1 : -1; } if ($macd5 !== null && $macd5_signal !== null) { $momentum_score += ($macd5 > $macd5_signal) ? 1 : -1; } // Samlet signal: trend (SMA) + MACD trend (nullinje) er primært // Momentum (signal crossover) bruges som sekundær bekræftelse $total_score = $trend_score + $macd_score; // Range: -4 til +4 if ($total_score >= 3) { $ta_signal = 'Stærkt Køb'; $ta_signal_color = '#15a050'; $ta_signal_en = 'strong_buy'; } elseif ($total_score >= 1) { $ta_signal = 'Køb'; $ta_signal_color = '#4caf50'; $ta_signal_en = 'buy'; } elseif ($total_score == 0) { $ta_signal = 'Afvent'; $ta_signal_color = '#ff9800'; $ta_signal_en = 'hold'; } elseif ($total_score >= -2) { $ta_signal = 'Sælg'; $ta_signal_color = '#f44336'; $ta_signal_en = 'sell'; } else { $ta_signal = 'Stærkt Sælg'; $ta_signal_color = '#b71c1c'; $ta_signal_en = 'strong_sell'; } // Er aktien i optrend? (bruges til kontekst-afhængig RSI) $is_uptrend = ($trend_score > 0); $is_downtrend = ($trend_score < 0); $stock_description = $stock_data['description'] ?? ""; $currency_symbol = $stock_data['currency_symbol'] ?? $stock_data['currency_code'] ?? "DKK"; $market_cap = $stock_data['market_cap'] ?? 0; $revenue = $stock_data['revenue'] ?? 0; $industry = $stock_data['industry_name'] ?? "-"; $industry = ($industry == "") ? "-" : $industry; $stock_eod_link = $stock_data['eod_import_code']; // === DYNAMISK REGULATOR-ATTRIBUTION === // Short-interest og insider-data kommer fra forskellige finanstilsyn afhængig // af aktiens noteringsbørs. Vi viser den korrekte kilde i stedet for at // hard-code "Finanstilsynet / SEC" overalt — det var unøjagtigt for svenske // (Finansinspektionen), norske (Finanstilsynet NO), tyske (BaFin) m.fl. $_reg_exchange = strtolower(explode('.', $stock_eod_link)[1] ?? ''); $_reg_country = strtoupper($stock_data['country_code'] ?? ''); $regulator_name = ''; $regulator_url = ''; if ($is_crypto) { // Krypto har ikke en enkelt regulator — undlad attribution } else { $_reg_map = [ // nøgle = exchange-suffix (eod_import_code efter '.') 'us' => ['SEC', 'https://www.sec.gov/'], 'co' => ['Finanstilsynet', 'https://www.finanstilsynet.dk/'], 'st' => ['Finansinspektionen', 'https://www.fi.se/'], 'ol' => ['Finanstilsynet (NO)','https://www.finanstilsynet.no/'], 'xetra' => ['BaFin', 'https://www.bafin.de/'], 'f' => ['BaFin', 'https://www.bafin.de/'], 'lse' => ['FCA', 'https://www.fca.org.uk/'], 'he' => ['Finanssivalvonta', 'https://www.finanssivalvonta.fi/'], 'to' => ['CSA', 'https://www.securities-administrators.ca/'], 'v' => ['CSA', 'https://www.securities-administrators.ca/'], 'pa' => ['AMF', 'https://www.amf-france.org/'], 'as' => ['AFM', 'https://www.afm.nl/'], 'mc' => ['CNMV', 'https://www.cnmv.es/'], 'mi' => ['CONSOB', 'https://www.consob.it/'], 'sw' => ['FINMA', 'https://www.finma.ch/'], ]; if (isset($_reg_map[$_reg_exchange])) { [$regulator_name, $regulator_url] = $_reg_map[$_reg_exchange]; } else { // Fallback til land-kode hvis exchange-suffix er ukendt $_country_map = [ 'US' => ['SEC', 'https://www.sec.gov/'], 'DK' => ['Finanstilsynet', 'https://www.finanstilsynet.dk/'], 'SE' => ['Finansinspektionen', 'https://www.fi.se/'], 'NO' => ['Finanstilsynet (NO)','https://www.finanstilsynet.no/'], 'DE' => ['BaFin', 'https://www.bafin.de/'], 'GB' => ['FCA', 'https://www.fca.org.uk/'], 'FI' => ['Finanssivalvonta', 'https://www.finanssivalvonta.fi/'], ]; if (isset($_country_map[$_reg_country])) { [$regulator_name, $regulator_url] = $_country_map[$_reg_country]; } } } $page_canonical = ""; $change_box = ''; $change_p = $stock_data['live_change_percentage'] ?? 0; $exchange_data = $stock_data['exchanges'] ?? []; if (count($exchange_data) > 0) { $exchange_data = $exchange_data[0]; } $exchange_name = $exchange_data['name'] ?? 'Ukendt'; $stock_flag = "https://".site_url()."/style/assets/flags/flag" . $stock_data['country_id'] . ".png"; if ($stock_data['is_live'] ?? false) { // Change-badge: bevidst NOT "number-of-lines-1" her – den klasse har // word-break:break-all som brækker "10,90%" midt i tallet på smal mobil // og viser "10,9...". Vi bruger nowrap + lidt større font for læsbarhed. $_chg_style = 'white-space: nowrap; font-size: 13px; line-height: 1.3; border-radius: 4px;'; if ($change_p >= 0) { $change_box = '
' . prettyNumber($stock_data['live_change_percentage'], 2) . '%
'; } else if ($change_p < 0) { $change_box = '
' . prettyNumber($stock_data['live_change_percentage'], 2) . '%
'; } } $points = isset($trading_data['points']) ? $trading_data['points'] : 0; if ($points >= 5) { $trend_description = 'I optrend.'; } else if ($points > 2 && $points < 5) { $trend_description = 'Ingen trend.'; } else { $trend_description = 'I nedtrend.'; } // Hent udbytteprocent til title-funktionen $dividend_yield = ($fundamental['dividend_yield'] ?? 0) * 100; // Analytiker-konsensus (bruges i title/H1/description til at fange høj-CTR // queries som "X aktie anbefaling" og "kursmål X 2026"). Dataen er // tilgængelig tidligt via $stock_data['Rating'] + StrongBuy/Buy/Hold/Sell. $_title_analyst_total = intval($stock_data['StrongBuy'] ?? 0) + intval($stock_data['Buy'] ?? 0) + intval($stock_data['Hold'] ?? 0) + intval($stock_data['Sell'] ?? 0) + intval($stock_data['StrongSell'] ?? 0); $_title_analyst_rating = floatval($stock_data['Rating'] ?? 0); $_title_consensus_label = ''; if ($_title_analyst_total > 0 && $_title_analyst_rating > 0) { if ($_title_analyst_rating >= 4.5) $_title_consensus_label = 'Stærkt Køb'; elseif ($_title_analyst_rating >= 3.5) $_title_consensus_label = 'Køb'; elseif ($_title_analyst_rating >= 2.5) $_title_consensus_label = 'Hold'; elseif ($_title_analyst_rating >= 1.5) $_title_consensus_label = 'Sælg'; else $_title_consensus_label = 'Stærkt Sælg'; } // Generer SEO-optimeret title og description med automatisk længdehåndtering $page_title = generate_seo_title($stock_data['name'], $stock_eod_link, $dividend_yield, 70, [ 'rsi' => $rsi, 'target' => $target, 'current_close' => $current_close, 'macd' => $macd, 'macd_signal' => $macd_signal, 'macd5' => $macd5, 'macd5_signal' => $macd5_signal, 'ta_signal' => $ta_signal ?? null, 'ta_signal_en' => $ta_signal_en ?? null, 'is_uptrend' => $is_uptrend ?? false, 'is_downtrend' => $is_downtrend ?? false, 'change_pct' => $stock_data['live_change_percentage'] ?? null, 'is_index' => $is_index, 'is_crypto' => $is_crypto, 'analyst_total' => $_title_analyst_total, 'analyst_rating' => $_title_analyst_rating, 'consensus_label' => $_title_consensus_label ]); $page_description = generate_seo_description($stock_data['name'], $stock_eod_link, $target, $rsi, $currency_symbol, 155, [ 'current_close' => $current_close, 'dividend_yield' => $dividend_yield, 'macd' => $macd, 'macd_signal' => $macd_signal, 'is_index' => $is_index, 'is_crypto' => $is_crypto, 'analyst_total' => $_title_analyst_total, 'consensus_label' => $_title_consensus_label ]); $page_h1 = generate_h1_title($stock_data['name'], $stock_eod_link, $dividend_yield, [ 'rsi' => $rsi, 'target' => $target, 'current_close' => $current_close, 'macd' => $macd, 'macd_signal' => $macd_signal, 'macd5' => $macd5, 'macd5_signal' => $macd5_signal, 'ta_signal' => $ta_signal ?? null, 'ta_signal_en' => $ta_signal_en ?? null, 'is_uptrend' => $is_uptrend ?? false, 'is_downtrend' => $is_downtrend ?? false, 'is_index' => $is_index, 'is_crypto' => $is_crypto, 'analyst_total' => $_title_analyst_total, 'consensus_label' => $_title_consensus_label ]); if (isset($stock_data['logo_image_url'])) { $alt_tekst= $stock_data['name']." virksomhedslogo"; } // =================================================================== // CHART BILLEDER: Dynamiske grafer fra TradingView API // =================================================================== $chart_link = $stock_data['chart_link'] ?? null; // Candlestick + RSI + MACD $chart_link_simple = $stock_data['chart_link_simple'] ?? null; // Simpel lukkekurs-graf // Dynamiske danske månedsnavne til alt-tags $_alt_months = ['januar','februar','marts','april','maj','juni','juli','august','september','oktober','november','december']; $_alt_month = $_alt_months[date('n') - 1]; $_alt_year = date('Y'); // Alt-tag for simpel graf (Billede 1 – øverst) $chart_simple_alt = "Kursudvikling for {$stock_name} ({$stock_eod_link}) – aktiekurs på " . number_format($current_close, 2) . " {$currency_symbol}, {$_alt_month} {$_alt_year}"; // Alt-tag for teknisk analyse graf (Billede 2 – TA-sektion) $_rsi_str = ($rsi !== null) ? "RSI " . number_format($rsi, 0) : ""; $_macd_str = ""; if ($macd !== null) { $_macd_str = ($macd > 0) ? "MACD positiv (bullish)" : "MACD negativ (bearish)"; } $_ta_parts = array_filter([$_rsi_str, $_macd_str]); $_ta_indicators = !empty($_ta_parts) ? implode(', ', $_ta_parts) . ', ' : ''; $chart_ta_alt = "Teknisk analyse af {$stock_name} ({$stock_eod_link}) – {$_ta_indicators}daglig candlestick-graf {$_alt_month} {$_alt_year}"; // OG:image prioritering: TA graf (candlestick) > simpel graf > logo // TA-grafen med candlestick, RSI og MACD er mere engagerende på SOME if (!empty($chart_link)) { $page_image = $chart_link; } elseif (!empty($chart_link_simple)) { $page_image = $chart_link_simple; } elseif (isset($stock_data['logo_image_url'])) { $page_image = $stock_data['logo_image_url']; } ?>