From a1bab34bd341ee6c144c262c83433f14668b6378 Mon Sep 17 00:00:00 2001 From: Lars Gebhardt-Kusche Date: Wed, 22 Apr 2026 01:04:43 +0200 Subject: [PATCH] adfasd --- modules/boersenchecker/bootstrap.php | 136 ++++++++++++++++++ modules/boersenchecker/partials/dashboard.php | 50 +++++++ .../src/Support/DashboardPage.php | 48 +++++++ 3 files changed, 234 insertions(+) diff --git a/modules/boersenchecker/bootstrap.php b/modules/boersenchecker/bootstrap.php index f6e6a9c..9a31fce 100644 --- a/modules/boersenchecker/bootstrap.php +++ b/modules/boersenchecker/bootstrap.php @@ -400,3 +400,139 @@ $mm->registerFunction($moduleName, 'alpha_vantage_fetch_quote', static function 'raw' => $quote, ]; }); + +$mm->registerFunction($moduleName, 'alpha_vantage_search_symbols', static function (string $keywords): array { + $settings = modules()->settings('boersenchecker'); + $apiKey = trim((string) ($settings['alpha_vantage_api_key'] ?? '')); + $timeout = (int) ($settings['alpha_vantage_timeout_sec'] ?? 12); + $timeout = $timeout > 0 ? $timeout : 12; + $keywords = trim($keywords); + + if ($keywords === '') { + return [ + 'ok' => false, + 'message' => 'Bitte Suchbegriff angeben.', + 'results' => [], + ]; + } + + if ($apiKey === '') { + return [ + 'ok' => false, + 'message' => 'Alpha-Vantage-API-Key fehlt. Bitte im Modul-Setup hinterlegen.', + 'results' => [], + ]; + } + + $url = 'https://www.alphavantage.co/query?' . http_build_query([ + 'function' => 'SYMBOL_SEARCH', + 'keywords' => $keywords, + 'apikey' => $apiKey, + ]); + + $responseBody = null; + + if (function_exists('curl_init')) { + $ch = curl_init($url); + if ($ch !== false) { + curl_setopt_array($ch, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_TIMEOUT => $timeout, + CURLOPT_CONNECTTIMEOUT => min(5, $timeout), + CURLOPT_HTTPHEADER => ['Accept: application/json'], + ]); + $responseBody = curl_exec($ch); + $curlError = curl_error($ch); + $httpCode = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE); + curl_close($ch); + + if (!is_string($responseBody) || $responseBody === '') { + return [ + 'ok' => false, + 'message' => 'Alpha Vantage Suche fehlgeschlagen.' + . ($curlError !== '' ? ' ' . $curlError : '') + . ($httpCode > 0 ? ' HTTP ' . $httpCode : ''), + 'results' => [], + ]; + } + } + } + + if (!is_string($responseBody) || $responseBody === '') { + $context = stream_context_create([ + 'http' => [ + 'method' => 'GET', + 'timeout' => $timeout, + 'header' => "Accept: application/json\r\n", + ], + ]); + $responseBody = @file_get_contents($url, false, $context); + if (!is_string($responseBody) || $responseBody === '') { + return [ + 'ok' => false, + 'message' => 'Alpha Vantage Suche lieferte keine Daten.', + 'results' => [], + ]; + } + } + + $decoded = json_decode($responseBody, true); + if (!is_array($decoded)) { + return [ + 'ok' => false, + 'message' => 'Alpha Vantage Suchantwort ist kein gueltiges JSON.', + 'results' => [], + ]; + } + + if (!empty($decoded['Note'])) { + return [ + 'ok' => false, + 'message' => 'Alpha Vantage Limit-Hinweis: ' . trim((string) $decoded['Note']), + 'results' => [], + ]; + } + + if (!empty($decoded['Information'])) { + return [ + 'ok' => false, + 'message' => trim((string) $decoded['Information']), + 'results' => [], + ]; + } + + if (!empty($decoded['Error Message'])) { + return [ + 'ok' => false, + 'message' => trim((string) $decoded['Error Message']), + 'results' => [], + ]; + } + + $matches = is_array($decoded['bestMatches'] ?? null) ? $decoded['bestMatches'] : []; + $results = []; + foreach ($matches as $match) { + if (!is_array($match)) { + continue; + } + + $results[] = [ + 'symbol' => trim((string) ($match['1. symbol'] ?? '')), + 'name' => trim((string) ($match['2. name'] ?? '')), + 'type' => trim((string) ($match['3. type'] ?? '')), + 'region' => trim((string) ($match['4. region'] ?? '')), + 'market_open' => trim((string) ($match['5. marketOpen'] ?? '')), + 'market_close' => trim((string) ($match['6. marketClose'] ?? '')), + 'timezone' => trim((string) ($match['7. timezone'] ?? '')), + 'currency' => trim((string) ($match['8. currency'] ?? '')), + 'match_score' => trim((string) ($match['9. matchScore'] ?? '')), + ]; + } + + return [ + 'ok' => true, + 'message' => count($results) . ' Treffer gefunden.', + 'results' => $results, + ]; +}); diff --git a/modules/boersenchecker/partials/dashboard.php b/modules/boersenchecker/partials/dashboard.php index e92435f..9db5777 100644 --- a/modules/boersenchecker/partials/dashboard.php +++ b/modules/boersenchecker/partials/dashboard.php @@ -156,6 +156,56 @@ +
+ API-Symbol suchen +

+ Suche nach Firma oder Ticker ueber den Alpha-Vantage-`SYMBOL_SEARCH`-Endpoint und uebernimm einen Treffer direkt ins Positionsformular. +

+
+ + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
SymbolNameTypRegionWaehrungMatchAktion
+ + In Formular uebernehmen + +
+
+ +
+
Manuellen Kurs erfassen diff --git a/modules/boersenchecker/src/Support/DashboardPage.php b/modules/boersenchecker/src/Support/DashboardPage.php index d7ece47..27500ea 100644 --- a/modules/boersenchecker/src/Support/DashboardPage.php +++ b/modules/boersenchecker/src/Support/DashboardPage.php @@ -21,6 +21,8 @@ final class DashboardPage private string $instrumentTable; private string $positionTable; private string $quoteTable; + private string $symbolSearchKeywords = ''; + private array $symbolSearchResults = []; public function __construct() { @@ -79,6 +81,8 @@ final class DashboardPage 'defaultReportCurrency' => $this->defaultReportCurrency, 'fxMaxAgeHours' => $this->fxMaxAgeHours, 'alphaMinIntervalMinutes' => $this->alphaMinIntervalMinutes, + 'symbolSearchKeywords' => $this->symbolSearchKeywords, + 'symbolSearchResults' => $this->symbolSearchResults, 'editPortfolio' => $state['editPortfolio'], 'editPosition' => $state['editPosition'], 'portfolios' => $state['portfolios'], @@ -105,6 +109,7 @@ final class DashboardPage 'save_quote' => $this->saveQuote(), 'refresh_alpha_vantage_position' => $this->refreshAlphaVantagePosition(), 'refresh_alpha_vantage_all' => $this->refreshAlphaVantageAll(), + 'search_symbol' => $this->searchSymbol(), 'delete_quote' => $this->deleteQuote(), 'refresh_fx' => $this->refreshFx(), default => '', @@ -145,6 +150,35 @@ final class DashboardPage ); } + $candidateName = trim((string) ($_GET['instrument_name_candidate'] ?? '')); + $candidateSymbol = trim((string) ($_GET['symbol_candidate'] ?? '')); + $candidateMarket = trim((string) ($_GET['market_candidate'] ?? '')); + $candidateCurrency = $this->normalizeCurrency((string) ($_GET['quote_currency_candidate'] ?? $this->defaultReportCurrency)); + + if ($editPosition === null) { + $editPosition = [ + 'instrument_name' => $candidateName, + 'symbol' => $candidateSymbol, + 'market' => $candidateMarket, + 'quote_currency' => $candidateCurrency, + 'purchase_currency' => $this->defaultReportCurrency, + 'purchase_date' => date('Y-m-d'), + ]; + } else { + if ($candidateName !== '') { + $editPosition['instrument_name'] = $candidateName; + } + if ($candidateSymbol !== '') { + $editPosition['symbol'] = $candidateSymbol; + } + if ($candidateMarket !== '') { + $editPosition['market'] = $candidateMarket; + } + if ($candidateCurrency !== '') { + $editPosition['quote_currency'] = $candidateCurrency; + } + } + return [ 'portfolios' => $portfolios, 'portfolioById' => $portfolioById, @@ -447,6 +481,20 @@ final class DashboardPage return 'Alpha Vantage: ' . $fetched . ' neu, ' . $reused . ' wiederverwendet, ' . $skipped . ' ohne Symbol, ' . $failed . ' Fehler.'; } + private function searchSymbol(): string + { + $keywords = trim((string) ($_POST['search_keywords'] ?? '')); + $this->symbolSearchKeywords = $keywords; + $result = \module_fn('boersenchecker', 'alpha_vantage_search_symbols', $keywords); + $this->symbolSearchResults = is_array($result['results'] ?? null) ? $result['results'] : []; + + if (empty($result['ok'])) { + throw new RuntimeException((string) ($result['message'] ?? 'Symbolsuche fehlgeschlagen.')); + } + + return (string) ($result['message'] ?? 'Suche abgeschlossen.'); + } + private function deleteQuote(): string { $quoteId = (int) ($_POST['quote_id'] ?? 0);