diff --git a/modules/boersenchecker/partials/home.php b/modules/boersenchecker/partials/home.php
index bfe6d98..1b966d4 100644
--- a/modules/boersenchecker/partials/home.php
+++ b/modules/boersenchecker/partials/home.php
@@ -68,7 +68,7 @@
Marktdaten
-
Aktuelle Kurse fuer das gewaehlte Depot ueber Bavest per ISIN abrufen.
+
Aktuelle Kurse fuer das gewaehlte Depot ueber Alpha Vantage anhand des hinterlegten Symbols abrufen.
0 ? '' : 'disabled' ?>>Aktuelle Kurse abrufen
diff --git a/modules/boersenchecker/partials/instruments.php b/modules/boersenchecker/partials/instruments.php
index 4831a06..935a8c3 100644
--- a/modules/boersenchecker/partials/instruments.php
+++ b/modules/boersenchecker/partials/instruments.php
@@ -37,7 +37,7 @@
Wertpapiersuche
-
Bavest-Suchergebnisse finden und direkt fuer die Aktie uebernehmen.
+
Alpha-Vantage-Suchergebnisse finden und direkt fuer die Aktie uebernehmen.
diff --git a/modules/boersenchecker/src/Support/DashboardPage.php b/modules/boersenchecker/src/Support/DashboardPage.php
index 1e245a5..0eefdeb 100644
--- a/modules/boersenchecker/src/Support/DashboardPage.php
+++ b/modules/boersenchecker/src/Support/DashboardPage.php
@@ -49,7 +49,7 @@ final class DashboardPage
$this->fxMaxAgeHours = 6.0;
}
- $this->marketDataMinIntervalMinutes = (int) ($this->moduleSettings['bavest_min_interval_minutes'] ?? 60);
+ $this->marketDataMinIntervalMinutes = (int) (($this->moduleSettings['alpha_vantage_min_interval_minutes'] ?? null) ?: 60);
if ($this->marketDataMinIntervalMinutes <= 0) {
$this->marketDataMinIntervalMinutes = 60;
}
@@ -410,22 +410,31 @@ final class DashboardPage
}
$instrumentId = (int) $row['instrument_id'];
- $isin = strtoupper(trim((string) ($row['isin'] ?? '')));
+ $symbol = strtoupper(trim((string) ($row['symbol'] ?? '')));
$quoteCurrency = $this->normalizeCurrency((string) ($row['quote_currency'] ?? $this->defaultReportCurrency));
- if ($isin === '') {
- throw new RuntimeException('Fuer diese Aktie ist noch keine ISIN hinterlegt.');
+ if ($symbol === '') {
+ throw new RuntimeException('Fuer diese Aktie ist noch kein Symbol hinterlegt.');
}
$latestApiQuote = $this->latestApiQuoteForInstrument($instrumentId);
$latestTimestamp = is_array($latestApiQuote) ? strtotime((string) ($latestApiQuote['quoted_at'] ?? '')) : false;
if ($latestTimestamp !== false && (time() - $latestTimestamp) < ($this->marketDataMinIntervalMinutes * 60)) {
- return 'Vorhandener Bavest-Kurs fuer ' . (string) $row['instrument_name'] . ' wiederverwendet.';
+ return 'Vorhandener Alpha-Vantage-Kurs fuer ' . (string) $row['instrument_name'] . ' wiederverwendet.';
}
- $apiResult = \module_fn('boersenchecker', 'bavest_fetch_quote_by_isin', $isin);
+ $apiResult = \module_fn('boersenchecker', 'alpha_vantage_fetch_quote_by_symbol', $symbol);
if (empty($apiResult['ok'])) {
throw new RuntimeException((string) ($apiResult['message'] ?? 'API-Abruf fehlgeschlagen.'));
}
+ if ($this->isApiSnapshotStale($latestApiQuote, (string) ($apiResult['fetched_at'] ?? ''))) {
+ $displayTime = (string) \module_fn(
+ 'boersenchecker',
+ 'format_datetime_for_display',
+ (string) ($apiResult['fetched_at'] ?? ''),
+ (string) ($apiResult['source'] ?? 'alphavantage:global_quote')
+ );
+ return 'Alpha Vantage lieferte fuer ' . (string) $row['instrument_name'] . ' keinen neueren Snapshot als ' . $displayTime . '.';
+ }
$storeResult = \module_fn(
'boersenchecker',
@@ -437,9 +446,9 @@ final class DashboardPage
(string) $apiResult['source']
);
if (!empty($storeResult['inserted'])) {
- return 'Bavest-Kurs fuer ' . (string) $row['instrument_name'] . ' gespeichert.';
+ return 'Alpha-Vantage-Kurs fuer ' . (string) $row['instrument_name'] . ' gespeichert.';
}
- return 'Vorhandener Bavest-Snapshot fuer ' . (string) $row['instrument_name'] . ' wiederverwendet.';
+ return 'Vorhandener Alpha-Vantage-Snapshot fuer ' . (string) $row['instrument_name'] . ' wiederverwendet.';
}
private function refreshMarketDataAll(): string
@@ -464,6 +473,7 @@ final class DashboardPage
$fetched = 0;
$reused = 0;
+ $stale = 0;
$skipped = 0;
$failed = 0;
$errors = [];
@@ -471,8 +481,8 @@ final class DashboardPage
$bulkRows = [];
foreach ($rows as $row) {
$instrumentId = (int) ($row['id'] ?? 0);
- $isin = strtoupper(trim((string) ($row['isin'] ?? '')));
- if ($instrumentId <= 0 || $isin === '') {
+ $symbol = strtoupper(trim((string) ($row['symbol'] ?? '')));
+ if ($instrumentId <= 0 || $symbol === '') {
$skipped++;
continue;
}
@@ -488,9 +498,9 @@ final class DashboardPage
}
if ($bulkRows !== []) {
- $bulkResult = \module_fn('boersenchecker', 'bavest_fetch_bulk_quotes', $bulkRows);
+ $bulkResult = \module_fn('boersenchecker', 'alpha_vantage_fetch_quotes', $bulkRows);
if (empty($bulkResult['ok'])) {
- throw new RuntimeException((string) ($bulkResult['message'] ?? 'Bavest Bulk-Abruf fehlgeschlagen.'));
+ throw new RuntimeException((string) ($bulkResult['message'] ?? 'Alpha-Vantage-Abruf fehlgeschlagen.'));
}
$bulkQuotes = is_array($bulkResult['quotes'] ?? null) ? $bulkResult['quotes'] : [];
@@ -500,7 +510,12 @@ final class DashboardPage
$apiResult = $bulkQuotes[$instrumentId] ?? null;
if (!is_array($apiResult) || !is_numeric($apiResult['price'] ?? null)) {
$failed++;
- $errors[] = (string) ($row['name'] ?? $instrumentId) . ': kein Preis in der Bavest-Antwort.';
+ $errors[] = (string) ($row['name'] ?? $instrumentId) . ': kein Preis in der Alpha-Vantage-Antwort.';
+ continue;
+ }
+ $latestApiQuote = $this->latestApiQuoteForInstrument($instrumentId);
+ if ($this->isApiSnapshotStale($latestApiQuote, (string) ($apiResult['fetched_at'] ?? ''))) {
+ $stale++;
continue;
}
@@ -523,19 +538,19 @@ final class DashboardPage
if ($errors !== []) {
throw new RuntimeException(
- 'Bavest: ' . $fetched . ' neu, ' . $reused . ' wiederverwendet, ' . $skipped . ' ohne ISIN, ' . $failed . ' Fehler. '
+ 'Alpha Vantage: ' . $fetched . ' neu, ' . $reused . ' wiederverwendet, ' . $stale . ' nicht neuer, ' . $skipped . ' ohne Symbol, ' . $failed . ' Fehler. '
. implode(' | ', array_slice($errors, 0, 3))
);
}
- return 'Bavest: ' . $fetched . ' neu, ' . $reused . ' wiederverwendet, ' . $skipped . ' ohne ISIN, ' . $failed . ' Fehler.';
+ return 'Alpha Vantage: ' . $fetched . ' neu, ' . $reused . ' wiederverwendet, ' . $stale . ' nicht neuer, ' . $skipped . ' ohne Symbol, ' . $failed . ' Fehler.';
}
private function searchSymbol(): string
{
$keywords = trim((string) ($_POST['search_keywords'] ?? ''));
$this->symbolSearchKeywords = $keywords;
- $result = \module_fn('boersenchecker', 'bavest_search_symbols', $keywords);
+ $result = \module_fn('boersenchecker', 'alpha_vantage_search_symbols', $keywords);
$this->symbolSearchResults = is_array($result['results'] ?? null) ? $result['results'] : [];
if (empty($result['ok'])) {
@@ -740,12 +755,27 @@ final class DashboardPage
);
$stmt->execute([
'instrument_id' => $instrumentId,
- 'source' => 'bavest:%',
+ 'source' => 'alphavantage:%',
]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return is_array($row) ? $row : null;
}
+ private function isApiSnapshotStale(?array $latestQuote, string $incomingQuotedAt): bool
+ {
+ if (!is_array($latestQuote)) {
+ return false;
+ }
+
+ $latestTimestamp = strtotime((string) ($latestQuote['quoted_at'] ?? ''));
+ $incomingTimestamp = strtotime(trim($incomingQuotedAt));
+ if ($latestTimestamp === false || $incomingTimestamp === false) {
+ return false;
+ }
+
+ return $incomingTimestamp <= $latestTimestamp;
+ }
+
private function upsertInstrument(array $payload): int
{
return $this->instrumentRegistry->save($payload);
diff --git a/modules/boersenchecker/src/Support/HomePage.php b/modules/boersenchecker/src/Support/HomePage.php
index c761e1d..760ee6b 100644
--- a/modules/boersenchecker/src/Support/HomePage.php
+++ b/modules/boersenchecker/src/Support/HomePage.php
@@ -26,7 +26,7 @@ final class HomePage
$settings = \modules()->settings('boersenchecker');
$this->defaultReportCurrency = strtoupper(trim((string) ($settings['report_currency'] ?? 'EUR'))) ?: 'EUR';
- $this->marketDataMinIntervalMinutes = (int) ($settings['bavest_min_interval_minutes'] ?? 60);
+ $this->marketDataMinIntervalMinutes = (int) (($settings['alpha_vantage_min_interval_minutes'] ?? null) ?: 60);
if ($this->marketDataMinIntervalMinutes <= 0) {
$this->marketDataMinIntervalMinutes = 60;
}
@@ -136,11 +136,12 @@ final class HomePage
$updated = 0;
$reused = 0;
+ $stale = 0;
$bulkCandidates = [];
foreach ($rows as $row) {
$instrumentId = (int) ($row['id'] ?? 0);
- $isin = strtoupper(trim((string) ($row['isin'] ?? '')));
- if ($instrumentId <= 0 || $isin === '') {
+ $symbol = strtoupper(trim((string) ($row['symbol'] ?? '')));
+ if ($instrumentId <= 0 || $symbol === '') {
continue;
}
@@ -155,7 +156,7 @@ final class HomePage
}
if ($bulkCandidates !== []) {
- $bulkResult = \module_fn('boersenchecker', 'bavest_fetch_bulk_quotes', $bulkCandidates);
+ $bulkResult = \module_fn('boersenchecker', 'alpha_vantage_fetch_quotes', $bulkCandidates);
$quotes = is_array($bulkResult['quotes'] ?? null) ? $bulkResult['quotes'] : [];
foreach ($bulkCandidates as $row) {
$instrumentId = (int) ($row['id'] ?? 0);
@@ -163,6 +164,11 @@ final class HomePage
if (!is_array($quote) || !is_numeric($quote['price'] ?? null)) {
continue;
}
+ $latest = $this->latestApiQuoteForInstrument($instrumentId);
+ if ($this->isApiSnapshotStale($latest, (string) ($quote['fetched_at'] ?? ''))) {
+ $stale++;
+ continue;
+ }
$storeResult = \module_fn(
'boersenchecker',
'store_market_quote',
@@ -170,7 +176,7 @@ final class HomePage
(float) $quote['price'],
strtoupper(trim((string) ($quote['currency'] ?? $row['quote_currency'] ?? $this->defaultReportCurrency))) ?: $this->defaultReportCurrency,
(string) ($quote['fetched_at'] ?? gmdate('Y-m-d H:i:s')),
- (string) ($quote['source'] ?? 'bavest:quote')
+ (string) ($quote['source'] ?? 'alphavantage:global_quote')
);
if (!empty($storeResult['inserted'])) {
$updated++;
@@ -180,7 +186,7 @@ final class HomePage
}
}
- return 'Aktuelle Kurse: ' . $updated . ' aktualisiert, ' . $reused . ' wiederverwendet.';
+ return 'Aktuelle Kurse: ' . $updated . ' aktualisiert, ' . $reused . ' wiederverwendet, ' . $stale . ' API-Snapshots waren nicht neuer.';
}
private function fetchPortfolios(): array
@@ -254,12 +260,27 @@ final class HomePage
);
$stmt->execute([
'instrument_id' => $instrumentId,
- 'source' => 'bavest:%',
+ 'source' => 'alphavantage:%',
]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return is_array($row) ? $row : null;
}
+ private function isApiSnapshotStale(?array $latestQuote, string $incomingQuotedAt): bool
+ {
+ if (!is_array($latestQuote)) {
+ return false;
+ }
+
+ $latestTimestamp = strtotime((string) ($latestQuote['quoted_at'] ?? ''));
+ $incomingTimestamp = strtotime(trim($incomingQuotedAt));
+ if ($latestTimestamp === false || $incomingTimestamp === false) {
+ return false;
+ }
+
+ return $incomingTimestamp <= $latestTimestamp;
+ }
+
private function buildSummary(array $positions): array
{
$invested = 0.0;
diff --git a/modules/boersenchecker/src/Support/InstrumentPage.php b/modules/boersenchecker/src/Support/InstrumentPage.php
index 3e3c384..890361a 100644
--- a/modules/boersenchecker/src/Support/InstrumentPage.php
+++ b/modules/boersenchecker/src/Support/InstrumentPage.php
@@ -214,15 +214,25 @@ final class InstrumentPage
{
$instrumentId = (int) ($_POST['instrument_id'] ?? 0);
$instrument = $this->assertInstrumentAccessible($instrumentId);
- $isin = strtoupper(trim((string) ($instrument['isin'] ?? '')));
- if ($isin === '') {
- throw new RuntimeException('Fuer diese Aktie ist keine ISIN hinterlegt.');
+ $symbol = strtoupper(trim((string) ($instrument['symbol'] ?? '')));
+ if ($symbol === '') {
+ throw new RuntimeException('Fuer diese Aktie ist kein Symbol hinterlegt.');
}
- $apiResult = \module_fn('boersenchecker', 'bavest_fetch_quote_by_isin', $isin);
+ $apiResult = \module_fn('boersenchecker', 'alpha_vantage_fetch_quote_by_symbol', $symbol);
if (empty($apiResult['ok'])) {
throw new RuntimeException((string) ($apiResult['message'] ?? 'API-Abruf fehlgeschlagen.'));
}
+ $latestApiQuote = $this->latestApiQuoteForInstrument($instrumentId);
+ if ($this->isApiSnapshotStale($latestApiQuote, (string) ($apiResult['fetched_at'] ?? ''))) {
+ $displayTime = (string) \module_fn(
+ 'boersenchecker',
+ 'format_datetime_for_display',
+ (string) ($apiResult['fetched_at'] ?? ''),
+ (string) ($apiResult['source'] ?? 'alphavantage:global_quote')
+ );
+ return 'Alpha Vantage lieferte keinen neueren Snapshot als ' . $displayTime . '.';
+ }
$storeResult = \module_fn(
'boersenchecker',
@@ -235,14 +245,14 @@ final class InstrumentPage
);
return !empty($storeResult['inserted'])
- ? 'Bavest-Kurs gespeichert.'
- : 'Vorhandener Bavest-Snapshot wiederverwendet.';
+ ? 'Alpha-Vantage-Kurs gespeichert.'
+ : 'Vorhandener Alpha-Vantage-Snapshot wiederverwendet.';
}
private function searchSymbol(): string
{
$this->searchKeywords = trim((string) ($_POST['search_keywords'] ?? ''));
- $result = \module_fn('boersenchecker', 'bavest_search_symbols', $this->searchKeywords);
+ $result = \module_fn('boersenchecker', 'alpha_vantage_search_symbols', $this->searchKeywords);
$this->searchResults = is_array($result['results'] ?? null) ? $result['results'] : [];
if (empty($result['ok'])) {
throw new RuntimeException((string) ($result['message'] ?? 'Symbolsuche fehlgeschlagen.'));
@@ -294,4 +304,37 @@ final class InstrumentPage
return (new \DateTimeImmutable('now', $timezone))->format('Y-m-d H:i:s');
}
}
+
+ private function latestApiQuoteForInstrument(int $instrumentId): ?array
+ {
+ $stmt = $this->pdo->prepare(
+ 'SELECT *
+ FROM ' . $this->quoteTable . '
+ WHERE instrument_id = :instrument_id
+ AND source LIKE :source
+ ORDER BY quoted_at DESC, created_at DESC, id DESC
+ LIMIT 1'
+ );
+ $stmt->execute([
+ 'instrument_id' => $instrumentId,
+ 'source' => 'alphavantage:%',
+ ]);
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
+ return is_array($row) ? $row : null;
+ }
+
+ private function isApiSnapshotStale(?array $latestQuote, string $incomingQuotedAt): bool
+ {
+ if (!is_array($latestQuote)) {
+ return false;
+ }
+
+ $latestTimestamp = strtotime((string) ($latestQuote['quoted_at'] ?? ''));
+ $incomingTimestamp = strtotime(trim($incomingQuotedAt));
+ if ($latestTimestamp === false || $incomingTimestamp === false) {
+ return false;
+ }
+
+ return $incomingTimestamp <= $latestTimestamp;
+ }
}
diff --git a/partials/landingpages/modules/setup.php b/partials/landingpages/modules/setup.php
index 8fa80a1..dfa7ef9 100644
--- a/partials/landingpages/modules/setup.php
+++ b/partials/landingpages/modules/setup.php
@@ -398,44 +398,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
$moduleStatusPanel = null;
-if ($moduleName === 'boersenchecker') {
- $quotaApiKey = trim((string) ($current['bavest_api_key'] ?? ''));
- if ($quotaApiKey === '') {
- $moduleStatusPanel = [
- 'title' => 'Bavest Quota',
- 'type' => 'hint',
- 'text' => 'Kein Bavest API Key hinterlegt. Nach dem Speichern wird hier die aktuelle Quota automatisch angezeigt.',
- 'stats' => [],
- ];
- } else {
- $quotaTimeout = (int) ($current['bavest_timeout_sec'] ?? 10);
- $quotaTimeout = $quotaTimeout > 0 ? $quotaTimeout : 10;
- $quotaResult = $fetchJsonWithApiKey('https://api.bavest.co/v2/account/quota', $quotaApiKey, $quotaTimeout);
-
- if (!empty($quotaResult['ok'])) {
- $quotaData = is_array($quotaResult['data']['data'] ?? null) ? $quotaResult['data']['data'] : [];
- $moduleStatusPanel = [
- 'title' => 'Bavest Quota',
- 'type' => 'success',
- 'text' => 'Quota erfolgreich geprueft.',
- 'stats' => [
- ['label' => 'Periode', 'value' => (string) ($quotaData['period'] ?? '-')],
- ['label' => 'Verbrauch', 'value' => (string) ($quotaData['usage'] ?? '0')],
- ['label' => 'Limit', 'value' => ($quotaData['limit'] ?? null) !== null ? (string) $quotaData['limit'] : 'unbegrenzt'],
- ['label' => 'Verbleibend', 'value' => ($quotaData['remaining'] ?? null) !== null ? (string) $quotaData['remaining'] : 'unbegrenzt'],
- ['label' => 'API-Key ID', 'value' => (string) ($quotaData['apiKey'] ?? '-')],
- ],
- ];
- } else {
- $moduleStatusPanel = [
- 'title' => 'Bavest Quota',
- 'type' => 'error',
- 'text' => 'Quota konnte nicht geprueft werden: ' . (string) ($quotaResult['message'] ?? 'Unbekannter Fehler'),
- 'stats' => [],
- ];
- }
- }
-}
$activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
? $testGroup