adfasd
This commit is contained in:
@@ -400,3 +400,139 @@ $mm->registerFunction($moduleName, 'alpha_vantage_fetch_quote', static function
|
|||||||
'raw' => $quote,
|
'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,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|||||||
@@ -156,6 +156,56 @@
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card" style="margin-top:1rem; background:var(--panel-2);">
|
||||||
|
<strong>API-Symbol suchen</strong>
|
||||||
|
<p class="muted" style="margin-top:.75rem;">
|
||||||
|
Suche nach Firma oder Ticker ueber den Alpha-Vantage-`SYMBOL_SEARCH`-Endpoint und uebernimm einen Treffer direkt ins Positionsformular.
|
||||||
|
</p>
|
||||||
|
<form method="post" style="margin-top:.75rem; display:flex; gap:10px; flex-wrap:wrap; align-items:end;">
|
||||||
|
<input type="hidden" name="action" value="search_symbol">
|
||||||
|
<label class="setup-field muted" style="margin:0; min-width:260px; flex:1;">
|
||||||
|
<span>Suchbegriff</span>
|
||||||
|
<input type="text" name="search_keywords" value="<?= e($symbolSearchKeywords) ?>" placeholder="z.B. Mercedes, AAPL, Allianz" required>
|
||||||
|
</label>
|
||||||
|
<button class="cta-button" type="submit">Suchen</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<?php if ($symbolSearchResults !== []): ?>
|
||||||
|
<div style="overflow:auto; margin-top:1rem;">
|
||||||
|
<table style="width:100%; border-collapse:collapse;">
|
||||||
|
<thead>
|
||||||
|
<tr style="text-align:left; border-bottom:1px solid var(--border);">
|
||||||
|
<th style="padding:8px;">Symbol</th>
|
||||||
|
<th style="padding:8px;">Name</th>
|
||||||
|
<th style="padding:8px;">Typ</th>
|
||||||
|
<th style="padding:8px;">Region</th>
|
||||||
|
<th style="padding:8px;">Waehrung</th>
|
||||||
|
<th style="padding:8px;">Match</th>
|
||||||
|
<th style="padding:8px;">Aktion</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($symbolSearchResults as $result): ?>
|
||||||
|
<tr style="border-bottom:1px solid var(--border); vertical-align:top;">
|
||||||
|
<td style="padding:8px;"><strong><?= e((string) ($result['symbol'] ?? '')) ?></strong></td>
|
||||||
|
<td style="padding:8px;"><?= e((string) ($result['name'] ?? '')) ?></td>
|
||||||
|
<td style="padding:8px;"><?= e((string) ($result['type'] ?? '')) ?></td>
|
||||||
|
<td style="padding:8px;"><?= e((string) ($result['region'] ?? '')) ?></td>
|
||||||
|
<td style="padding:8px;"><?= e((string) ($result['currency'] ?? '')) ?></td>
|
||||||
|
<td style="padding:8px;"><?= e((string) ($result['match_score'] ?? '')) ?></td>
|
||||||
|
<td style="padding:8px;">
|
||||||
|
<a class="nav-link" href="/module/boersenchecker?symbol_candidate=<?= urlencode((string) ($result['symbol'] ?? '')) ?>&instrument_name_candidate=<?= urlencode((string) ($result['name'] ?? '')) ?>&market_candidate=<?= urlencode((string) ($result['region'] ?? '')) ?>"e_currency_candidate=<?= urlencode((string) ($result['currency'] ?? '')) ?>">
|
||||||
|
In Formular uebernehmen
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card" style="margin-top:1rem; background:var(--panel-2);">
|
<div class="card" style="margin-top:1rem; background:var(--panel-2);">
|
||||||
<strong>Manuellen Kurs erfassen</strong>
|
<strong>Manuellen Kurs erfassen</strong>
|
||||||
<?php if ($instrumentList === []): ?>
|
<?php if ($instrumentList === []): ?>
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ final class DashboardPage
|
|||||||
private string $instrumentTable;
|
private string $instrumentTable;
|
||||||
private string $positionTable;
|
private string $positionTable;
|
||||||
private string $quoteTable;
|
private string $quoteTable;
|
||||||
|
private string $symbolSearchKeywords = '';
|
||||||
|
private array $symbolSearchResults = [];
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
@@ -79,6 +81,8 @@ final class DashboardPage
|
|||||||
'defaultReportCurrency' => $this->defaultReportCurrency,
|
'defaultReportCurrency' => $this->defaultReportCurrency,
|
||||||
'fxMaxAgeHours' => $this->fxMaxAgeHours,
|
'fxMaxAgeHours' => $this->fxMaxAgeHours,
|
||||||
'alphaMinIntervalMinutes' => $this->alphaMinIntervalMinutes,
|
'alphaMinIntervalMinutes' => $this->alphaMinIntervalMinutes,
|
||||||
|
'symbolSearchKeywords' => $this->symbolSearchKeywords,
|
||||||
|
'symbolSearchResults' => $this->symbolSearchResults,
|
||||||
'editPortfolio' => $state['editPortfolio'],
|
'editPortfolio' => $state['editPortfolio'],
|
||||||
'editPosition' => $state['editPosition'],
|
'editPosition' => $state['editPosition'],
|
||||||
'portfolios' => $state['portfolios'],
|
'portfolios' => $state['portfolios'],
|
||||||
@@ -105,6 +109,7 @@ final class DashboardPage
|
|||||||
'save_quote' => $this->saveQuote(),
|
'save_quote' => $this->saveQuote(),
|
||||||
'refresh_alpha_vantage_position' => $this->refreshAlphaVantagePosition(),
|
'refresh_alpha_vantage_position' => $this->refreshAlphaVantagePosition(),
|
||||||
'refresh_alpha_vantage_all' => $this->refreshAlphaVantageAll(),
|
'refresh_alpha_vantage_all' => $this->refreshAlphaVantageAll(),
|
||||||
|
'search_symbol' => $this->searchSymbol(),
|
||||||
'delete_quote' => $this->deleteQuote(),
|
'delete_quote' => $this->deleteQuote(),
|
||||||
'refresh_fx' => $this->refreshFx(),
|
'refresh_fx' => $this->refreshFx(),
|
||||||
default => '',
|
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 [
|
return [
|
||||||
'portfolios' => $portfolios,
|
'portfolios' => $portfolios,
|
||||||
'portfolioById' => $portfolioById,
|
'portfolioById' => $portfolioById,
|
||||||
@@ -447,6 +481,20 @@ final class DashboardPage
|
|||||||
return 'Alpha Vantage: ' . $fetched . ' neu, ' . $reused . ' wiederverwendet, ' . $skipped . ' ohne Symbol, ' . $failed . ' Fehler.';
|
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
|
private function deleteQuote(): string
|
||||||
{
|
{
|
||||||
$quoteId = (int) ($_POST['quote_id'] ?? 0);
|
$quoteId = (int) ($_POST['quote_id'] ?? 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user