cron
All checks were successful
Deploy / deploy-staging (push) Successful in 5s
Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-04-27 00:24:30 +02:00
parent 7ce9173d57
commit e7a1878c72
7 changed files with 573 additions and 1 deletions

View File

@@ -567,6 +567,136 @@ $mm->registerFunction($moduleName, 'alpha_vantage_fetch_quotes', static function
];
});
$mm->registerFunction($moduleName, 'scheduled_refresh_quotes', static function (array $context = []): array {
$pdo = module_fn('boersenchecker', 'pdo');
$settings = modules()->settings('boersenchecker');
$instrumentTable = module_fn('boersenchecker', 'table', 'instruments');
$positionTable = module_fn('boersenchecker', 'table', 'positions');
$quoteTable = module_fn('boersenchecker', 'table', 'quotes');
$defaultReportCurrency = strtoupper(trim((string) ($settings['report_currency'] ?? 'EUR'))) ?: 'EUR';
$minIntervalMinutes = (int) (($settings['alpha_vantage_min_interval_minutes'] ?? null) ?: 60);
if ($minIntervalMinutes <= 0) {
$minIntervalMinutes = 60;
}
$stmt = $pdo->query(
'SELECT DISTINCT
i.id,
i.name,
i.symbol,
i.quote_currency
FROM ' . $positionTable . ' p
INNER JOIN ' . $instrumentTable . ' i ON i.id = p.instrument_id
WHERE i.symbol IS NOT NULL
AND i.symbol <> \'\'
ORDER BY i.name ASC'
);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
if ($rows === []) {
return [
'ok' => true,
'message' => 'Kein automatischer Kursabruf: keine Aktien mit Symbol vorhanden.',
];
}
$instrumentIds = array_values(array_map(static fn (array $row): int => (int) ($row['id'] ?? 0), $rows));
$instrumentIds = array_values(array_filter($instrumentIds, static fn (int $id): bool => $id > 0));
$latestQuotes = [];
if ($instrumentIds !== []) {
$placeholders = implode(',', array_fill(0, count($instrumentIds), '?'));
$latestStmt = $pdo->prepare(
'SELECT *
FROM ' . $quoteTable . '
WHERE instrument_id IN (' . $placeholders . ')
AND source LIKE ?
ORDER BY quoted_at DESC, created_at DESC, id DESC'
);
$latestStmt->execute([...$instrumentIds, 'alphavantage:%']);
foreach ($latestStmt->fetchAll(PDO::FETCH_ASSOC) ?: [] as $row) {
$instrumentId = (int) ($row['instrument_id'] ?? 0);
if ($instrumentId > 0 && !isset($latestQuotes[$instrumentId])) {
$latestQuotes[$instrumentId] = $row;
}
}
}
$reused = 0;
$candidates = [];
foreach ($rows as $row) {
$instrumentId = (int) ($row['id'] ?? 0);
$latest = $latestQuotes[$instrumentId] ?? null;
$latestTimestamp = is_array($latest) ? strtotime((string) ($latest['quoted_at'] ?? '')) : false;
if ($latestTimestamp !== false && (time() - $latestTimestamp) < ($minIntervalMinutes * 60)) {
$reused++;
continue;
}
$candidates[] = $row;
}
if ($candidates === []) {
return [
'ok' => true,
'message' => 'Automatischer Kursabruf uebersprungen: alle Kurse liegen noch innerhalb des Mindestabstands.',
];
}
$bulkResult = module_fn('boersenchecker', 'alpha_vantage_fetch_quotes', $candidates);
if (empty($bulkResult['ok'])) {
return [
'ok' => false,
'message' => (string) ($bulkResult['message'] ?? 'Automatischer Alpha-Vantage-Abruf fehlgeschlagen.'),
];
}
$quotes = is_array($bulkResult['quotes'] ?? null) ? $bulkResult['quotes'] : [];
$errors = is_array($bulkResult['errors'] ?? null) ? $bulkResult['errors'] : [];
$updated = 0;
foreach ($candidates as $row) {
$instrumentId = (int) ($row['id'] ?? 0);
$quote = $quotes[$instrumentId] ?? null;
if (!is_array($quote) || !is_numeric($quote['price'] ?? null)) {
continue;
}
$storeResult = module_fn(
'boersenchecker',
'store_market_quote',
$instrumentId,
(float) $quote['price'],
strtoupper(trim((string) ($quote['currency'] ?? $row['quote_currency'] ?? $defaultReportCurrency))) ?: $defaultReportCurrency,
(string) ($quote['fetched_at'] ?? gmdate('Y-m-d H:i:s')),
(string) ($quote['source'] ?? 'alphavantage:global_quote')
);
if (!empty($storeResult['inserted'])) {
$updated++;
} else {
$reused++;
}
}
$message = 'Automatischer Kursabruf: ' . $updated . ' neu, ' . $reused . ' wiederverwendet, ' . count($errors) . ' Fehler.';
if ($errors !== []) {
$message .= ' ' . implode(' | ', array_slice($errors, 0, 3));
}
module_debug_push('boersenchecker', [
'label' => 'Intervall-Aufgabe',
'type' => 'scheduler:run',
'task' => 'auto_refresh_quotes',
'context' => $context,
'message' => $message,
]);
return [
'ok' => $errors === [],
'message' => $message,
'updated' => $updated,
'reused' => $reused,
'errors' => $errors,
];
});
$mm->registerFunction($moduleName, 'alpha_vantage_search_symbols', static function (string $keywords): array {
$keywords = trim($keywords);
if ($keywords === '') {

View File

@@ -17,9 +17,23 @@
{ "name": "fx_max_age_hours", "label": "Maximales FX-Alter (Stunden)", "type": "number", "required": false, "help": "Wird bei manueller Aktualisierung ueber den Mining-Checker genutzt." },
{ "name": "alpha_vantage_api_key", "label": "Alpha Vantage API Key", "type": "password", "required": false, "help": "API Key fuer Aktienkursabrufe und Suche ueber Alpha Vantage." },
{ "name": "alpha_vantage_timeout_sec", "label": "Alpha Vantage Timeout (Sek.)", "type": "number", "required": false, "help": "HTTP-Timeout fuer API-Abrufe." },
{ "name": "alpha_vantage_min_interval_minutes", "label": "Alpha Vantage Mindestabstand (Min.)", "type": "number", "required": false, "help": "Wenn bereits ein frischer Alpha-Vantage-Kurs existiert, wird dieser wiederverwendet statt erneut abzurufen." }
{ "name": "alpha_vantage_min_interval_minutes", "label": "Alpha Vantage Mindestabstand (Min.)", "type": "number", "required": false, "help": "Wenn bereits ein frischer Alpha-Vantage-Kurs existiert, wird dieser wiederverwendet statt erneut abzurufen." },
{ "name": "auto_refresh_quotes_enabled", "label": "Automatischen Kursabruf aktivieren", "type": "checkbox", "required": false, "help": "Fuehrt Kursupdates automatisch beim ersten Modulaufruf nach Ablauf des Intervalls aus." },
{ "name": "auto_refresh_quotes_interval_hours", "label": "Intervall fuer automatischen Kursabruf (Stunden)", "type": "number", "required": false, "help": "Nach Ablauf dieses Intervalls wird beim naechsten Modulaufruf ein automatischer Kursabruf gestartet." }
]
},
"interval_tasks": [
{
"name": "auto_refresh_quotes",
"label": "Automatischer Kursabruf",
"callback": "scheduled_refresh_quotes",
"enabled_setting": "auto_refresh_quotes_enabled",
"interval_setting": "auto_refresh_quotes_interval_hours",
"default_enabled": false,
"default_interval_hours": 6,
"lock_minutes": 20
}
],
"db_defaults": {
"driver": "pgsql",
"host": "localhost",