false, 'message' => 'instrument_id fehlt.'], JSON_UNESCAPED_UNICODE); exit; } $pdo = module_fn('boersenchecker', 'pdo'); module_fn('boersenchecker', 'ensure_schema'); $instrumentTable = module_fn('boersenchecker', 'table', 'instruments'); $positionTable = module_fn('boersenchecker', 'table', 'positions'); $quoteTable = module_fn('boersenchecker', 'table', 'quotes'); $stmt = $pdo->prepare( 'SELECT i.id, i.name, i.symbol, i.isin, i.quote_currency FROM ' . $instrumentTable . ' i INNER JOIN ' . $positionTable . ' p ON p.instrument_id = i.id WHERE i.id = :id AND p.owner_sub = :owner_sub LIMIT 1' ); $stmt->execute([ 'id' => $instrumentId, 'owner_sub' => $ownerSub, ]); $instrument = $stmt->fetch(PDO::FETCH_ASSOC); header('Content-Type: application/json; charset=utf-8'); if (!is_array($instrument)) { echo json_encode(['ok' => false, 'message' => 'Aktie nicht verfuegbar.'], JSON_UNESCAPED_UNICODE); exit; } $quoteStmt = $pdo->prepare( 'SELECT id, price, currency, quoted_at, source, created_at FROM ' . $quoteTable . ' WHERE instrument_id = :instrument_id ORDER BY quoted_at ASC, created_at ASC, id ASC' ); $quoteStmt->execute([ 'instrument_id' => $instrumentId, ]); $quotes = $quoteStmt->fetchAll(PDO::FETCH_ASSOC) ?: []; if ($quotes === []) { echo json_encode([ 'ok' => false, 'message' => 'Keine lokalen Kursdaten fuer diese Aktie vorhanden.', ], JSON_UNESCAPED_UNICODE); exit; } $dailyMap = []; foreach ($quotes as $quote) { $localDate = trim((string) module_fn( 'boersenchecker', 'format_datetime_for_display', (string) ($quote['quoted_at'] ?? ''), (string) ($quote['source'] ?? ''), 'Y-m-d' )); $localDateTime = trim((string) module_fn( 'boersenchecker', 'format_datetime_for_display', (string) ($quote['quoted_at'] ?? ''), (string) ($quote['source'] ?? ''), 'Y-m-d H:i:s' )); if ($localDate === '' || !is_numeric($quote['price'] ?? null)) { continue; } $point = [ 'date' => $localDate, 'close' => (float) $quote['price'], 'currency' => strtoupper(trim((string) ($quote['currency'] ?? ''))), 'quoted_at' => $localDateTime, 'source' => (string) ($quote['source'] ?? ''), ]; if (!isset($dailyMap[$localDate]) || strcmp($localDateTime, (string) ($dailyMap[$localDate]['quoted_at'] ?? '')) >= 0) { $dailyMap[$localDate] = $point; } } $daily = array_values($dailyMap); usort($daily, static fn (array $left, array $right): int => strcmp((string) $left['date'], (string) $right['date'])); if ($daily === []) { echo json_encode([ 'ok' => false, 'message' => 'Keine gueltigen lokalen Schlusskurse fuer diese Aktie vorhanden.', ], JSON_UNESCAPED_UNICODE); exit; } $aggregate = static function (array $points, string $format): array { $result = []; $timezone = new DateTimeZone('Europe/Berlin'); foreach ($points as $point) { $date = DateTimeImmutable::createFromFormat('Y-m-d', (string) ($point['date'] ?? ''), $timezone); if (!$date instanceof DateTimeImmutable) { continue; } $bucket = $date->format($format); $result[$bucket] = $point; } return array_values($result); }; echo json_encode([ 'ok' => true, 'symbol' => strtoupper(trim((string) ($instrument['symbol'] ?? ''))), 'isin' => strtoupper(trim((string) ($instrument['isin'] ?? ''))), 'instrument_name' => (string) ($instrument['name'] ?? ''), 'currency' => strtoupper(trim((string) ($instrument['quote_currency'] ?? ''))), 'daily' => $daily, 'weekly' => $aggregate($daily, 'o-W'), 'monthly' => $aggregate($daily, 'Y-m'), 'source' => 'database:quotes', 'source_label' => 'Lokale Kurshistorie', ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); exit;