*/ public function slides(): array { if (! config('market.enabled')) { return []; } /** @var list $slides */ $slides = Cache::get((string) config('market.cache_key'), []); return is_array($slides) ? $slides : []; } /** * @return list */ public function refresh(): array { $slides = []; foreach ((array) config('market.symbols', []) as $config) { $quote = $this->fetchQuote((string) $config['symbol']); if ($quote === null) { continue; } [$price, $previousClose] = $quote; $deltaPercent = $previousClose > 0.0 ? ($price - $previousClose) / $previousClose * 100 : 0.0; $slides[] = [ 'label' => (string) $config['label'], 'value' => number_format($price, (int) ($config['precision'] ?? 2), ',', '.').($config['suffix'] ?? ''), 'delta' => ($deltaPercent >= 0 ? '+' : '').number_format($deltaPercent, 1, ',', '.').'%', 'positive' => $deltaPercent >= 0, ]; } if ($slides === []) { Log::warning('market:refresh lieferte keine Kursdaten — bestehender Cache-Stand bleibt erhalten.'); return $this->slides(); } Cache::put((string) config('market.cache_key'), $slides, (int) config('market.cache_ttl', 86400)); return $slides; } /** * @return array{0: float, 1: float}|null [Kurs, Vortagesschluss] */ private function fetchQuote(string $symbol): ?array { try { $response = Http::timeout(10) ->withHeaders(['User-Agent' => 'Mozilla/5.0 (compatible; PressekontoTicker/1.0)']) ->get(config('market.endpoint').rawurlencode($symbol), [ 'range' => '1d', 'interval' => '1d', ]); } catch (\Throwable $exception) { Log::warning('Kursabruf fehlgeschlagen.', ['symbol' => $symbol, 'error' => $exception->getMessage()]); return null; } if (! $response->successful()) { Log::warning('Kursabruf fehlgeschlagen.', ['symbol' => $symbol, 'status' => $response->status()]); return null; } $meta = $response->json('chart.result.0.meta'); $price = $meta['regularMarketPrice'] ?? null; $previousClose = $meta['chartPreviousClose'] ?? $meta['previousClose'] ?? null; if (! is_numeric($price) || ! is_numeric($previousClose)) { return null; } return [(float) $price, (float) $previousClose]; } }