umstellung
This commit is contained in:
@@ -15,7 +15,7 @@ final class DashboardPage
|
||||
private array $moduleSettings;
|
||||
private string $defaultReportCurrency;
|
||||
private float $fxMaxAgeHours;
|
||||
private int $alphaMinIntervalMinutes;
|
||||
private int $marketDataMinIntervalMinutes;
|
||||
private int $editPortfolioId;
|
||||
private int $editPositionId;
|
||||
private string $portfolioTable;
|
||||
@@ -49,9 +49,9 @@ final class DashboardPage
|
||||
$this->fxMaxAgeHours = 6.0;
|
||||
}
|
||||
|
||||
$this->alphaMinIntervalMinutes = (int) ($this->moduleSettings['alpha_vantage_min_interval_minutes'] ?? 60);
|
||||
if ($this->alphaMinIntervalMinutes <= 0) {
|
||||
$this->alphaMinIntervalMinutes = 60;
|
||||
$this->marketDataMinIntervalMinutes = (int) ($this->moduleSettings['bavest_min_interval_minutes'] ?? 60);
|
||||
if ($this->marketDataMinIntervalMinutes <= 0) {
|
||||
$this->marketDataMinIntervalMinutes = 60;
|
||||
}
|
||||
|
||||
$this->editPortfolioId = (int) ($_GET['edit_portfolio'] ?? 0);
|
||||
@@ -100,7 +100,7 @@ final class DashboardPage
|
||||
'availableOwners' => array_values($this->availableOwners),
|
||||
'defaultReportCurrency' => $this->defaultReportCurrency,
|
||||
'fxMaxAgeHours' => $this->fxMaxAgeHours,
|
||||
'alphaMinIntervalMinutes' => $this->alphaMinIntervalMinutes,
|
||||
'marketDataMinIntervalMinutes' => $this->marketDataMinIntervalMinutes,
|
||||
'symbolSearchKeywords' => $this->symbolSearchKeywords,
|
||||
'symbolSearchResults' => $this->symbolSearchResults,
|
||||
'editPortfolio' => $state['editPortfolio'],
|
||||
@@ -127,8 +127,8 @@ final class DashboardPage
|
||||
'save_position' => $this->savePosition(),
|
||||
'delete_position' => $this->deletePosition(),
|
||||
'save_quote' => $this->saveQuote(),
|
||||
'refresh_alpha_vantage_position' => $this->refreshAlphaVantagePosition(),
|
||||
'refresh_alpha_vantage_all' => $this->refreshAlphaVantageAll(),
|
||||
'refresh_market_data_position' => $this->refreshMarketDataPosition(),
|
||||
'refresh_market_data_all' => $this->refreshMarketDataAll(),
|
||||
'search_symbol' => $this->searchSymbol(),
|
||||
'delete_quote' => $this->deleteQuote(),
|
||||
'refresh_fx' => $this->refreshFx(),
|
||||
@@ -172,6 +172,7 @@ final class DashboardPage
|
||||
|
||||
$candidateName = trim((string) ($_GET['instrument_name_candidate'] ?? ''));
|
||||
$candidateSymbol = trim((string) ($_GET['symbol_candidate'] ?? ''));
|
||||
$candidateIsin = trim((string) ($_GET['isin_candidate'] ?? ''));
|
||||
$candidateMarket = trim((string) ($_GET['market_candidate'] ?? ''));
|
||||
$candidateCurrency = $this->normalizeCurrency((string) ($_GET['quote_currency_candidate'] ?? $this->defaultReportCurrency));
|
||||
|
||||
@@ -179,6 +180,7 @@ final class DashboardPage
|
||||
$editPosition = [
|
||||
'instrument_name' => $candidateName,
|
||||
'symbol' => $candidateSymbol,
|
||||
'isin' => $candidateIsin,
|
||||
'market' => $candidateMarket,
|
||||
'quote_currency' => $candidateCurrency,
|
||||
'purchase_currency' => $this->defaultReportCurrency,
|
||||
@@ -191,6 +193,9 @@ final class DashboardPage
|
||||
if ($candidateSymbol !== '') {
|
||||
$editPosition['symbol'] = $candidateSymbol;
|
||||
}
|
||||
if ($candidateIsin !== '') {
|
||||
$editPosition['isin'] = $candidateIsin;
|
||||
}
|
||||
if ($candidateMarket !== '') {
|
||||
$editPosition['market'] = $candidateMarket;
|
||||
}
|
||||
@@ -378,7 +383,7 @@ final class DashboardPage
|
||||
return 'Kurs gespeichert.';
|
||||
}
|
||||
|
||||
private function refreshAlphaVantagePosition(): string
|
||||
private function refreshMarketDataPosition(): string
|
||||
{
|
||||
$positionId = (int) ($_POST['position_id'] ?? 0);
|
||||
$stmt = $this->pdo->prepare(
|
||||
@@ -386,6 +391,7 @@ final class DashboardPage
|
||||
p.instrument_id,
|
||||
i.name AS instrument_name,
|
||||
i.symbol,
|
||||
i.isin,
|
||||
i.quote_currency
|
||||
FROM ' . $this->positionTable . ' p
|
||||
INNER JOIN ' . $this->instrumentTable . ' i ON i.id = p.instrument_id
|
||||
@@ -402,19 +408,19 @@ final class DashboardPage
|
||||
}
|
||||
|
||||
$instrumentId = (int) $row['instrument_id'];
|
||||
$symbol = strtoupper(trim((string) ($row['symbol'] ?? '')));
|
||||
$isin = strtoupper(trim((string) ($row['isin'] ?? '')));
|
||||
$quoteCurrency = $this->normalizeCurrency((string) ($row['quote_currency'] ?? $this->defaultReportCurrency));
|
||||
if ($symbol === '') {
|
||||
throw new RuntimeException('Fuer diese Aktie ist noch kein API-Symbol / Ticker hinterlegt.');
|
||||
if ($isin === '') {
|
||||
throw new RuntimeException('Fuer diese Aktie ist noch keine ISIN hinterlegt.');
|
||||
}
|
||||
|
||||
$latestApiQuote = $this->latestApiQuoteForInstrument($instrumentId);
|
||||
$latestTimestamp = is_array($latestApiQuote) ? strtotime((string) ($latestApiQuote['quoted_at'] ?? '')) : false;
|
||||
if ($latestTimestamp !== false && (time() - $latestTimestamp) < ($this->alphaMinIntervalMinutes * 60)) {
|
||||
return 'Vorhandener Alpha-Vantage-Kurs fuer ' . (string) $row['instrument_name'] . ' wiederverwendet.';
|
||||
if ($latestTimestamp !== false && (time() - $latestTimestamp) < ($this->marketDataMinIntervalMinutes * 60)) {
|
||||
return 'Vorhandener Bavest-Kurs fuer ' . (string) $row['instrument_name'] . ' wiederverwendet.';
|
||||
}
|
||||
|
||||
$apiResult = \module_fn('boersenchecker', 'alpha_vantage_fetch_quote', $symbol);
|
||||
$apiResult = \module_fn('boersenchecker', 'bavest_fetch_quote_by_isin', $isin);
|
||||
if (empty($apiResult['ok'])) {
|
||||
throw new RuntimeException((string) ($apiResult['message'] ?? 'API-Abruf fehlgeschlagen.'));
|
||||
}
|
||||
@@ -426,16 +432,17 @@ final class DashboardPage
|
||||
(string) $apiResult['fetched_at'],
|
||||
(string) $apiResult['source']
|
||||
);
|
||||
return 'API-Kurs fuer ' . (string) $row['instrument_name'] . ' gespeichert.';
|
||||
return 'Bavest-Kurs fuer ' . (string) $row['instrument_name'] . ' gespeichert.';
|
||||
}
|
||||
|
||||
private function refreshAlphaVantageAll(): string
|
||||
private function refreshMarketDataAll(): string
|
||||
{
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT DISTINCT
|
||||
i.id,
|
||||
i.name,
|
||||
i.symbol,
|
||||
i.isin,
|
||||
i.quote_currency
|
||||
FROM ' . $this->positionTable . ' p
|
||||
INNER JOIN ' . $this->instrumentTable . ' i ON i.id = p.instrument_id
|
||||
@@ -454,58 +461,68 @@ final class DashboardPage
|
||||
$failed = 0;
|
||||
$errors = [];
|
||||
|
||||
$bulkRows = [];
|
||||
foreach ($rows as $row) {
|
||||
$instrumentId = (int) ($row['id'] ?? 0);
|
||||
$symbol = strtoupper(trim((string) ($row['symbol'] ?? '')));
|
||||
$quoteCurrency = $this->normalizeCurrency((string) ($row['quote_currency'] ?? $this->defaultReportCurrency));
|
||||
|
||||
if ($instrumentId <= 0 || $symbol === '') {
|
||||
$isin = strtoupper(trim((string) ($row['isin'] ?? '')));
|
||||
if ($instrumentId <= 0 || $isin === '') {
|
||||
$skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$latestApiQuote = $this->latestApiQuoteForInstrument($instrumentId);
|
||||
$latestTimestamp = is_array($latestApiQuote) ? strtotime((string) ($latestApiQuote['quoted_at'] ?? '')) : false;
|
||||
if ($latestTimestamp !== false && (time() - $latestTimestamp) < ($this->alphaMinIntervalMinutes * 60)) {
|
||||
if ($latestTimestamp !== false && (time() - $latestTimestamp) < ($this->marketDataMinIntervalMinutes * 60)) {
|
||||
$reused++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$apiResult = \module_fn('boersenchecker', 'alpha_vantage_fetch_quote', $symbol);
|
||||
if (empty($apiResult['ok'])) {
|
||||
$failed++;
|
||||
$errors[] = (string) ($row['name'] ?? $symbol) . ': ' . (string) ($apiResult['message'] ?? 'API-Abruf fehlgeschlagen.');
|
||||
if (stripos((string) ($apiResult['message'] ?? ''), 'limit') !== false) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
$bulkRows[] = $row;
|
||||
}
|
||||
|
||||
if ($bulkRows !== []) {
|
||||
$bulkResult = \module_fn('boersenchecker', 'bavest_fetch_bulk_quotes', $bulkRows);
|
||||
if (empty($bulkResult['ok'])) {
|
||||
throw new RuntimeException((string) ($bulkResult['message'] ?? 'Bavest Bulk-Abruf fehlgeschlagen.'));
|
||||
}
|
||||
|
||||
$this->storeQuote(
|
||||
$instrumentId,
|
||||
(float) $apiResult['price'],
|
||||
$quoteCurrency,
|
||||
(string) $apiResult['fetched_at'],
|
||||
(string) $apiResult['source']
|
||||
);
|
||||
$fetched++;
|
||||
$bulkQuotes = is_array($bulkResult['quotes'] ?? null) ? $bulkResult['quotes'] : [];
|
||||
foreach ($bulkRows as $row) {
|
||||
$instrumentId = (int) ($row['id'] ?? 0);
|
||||
$quoteCurrency = $this->normalizeCurrency((string) ($row['quote_currency'] ?? $this->defaultReportCurrency));
|
||||
$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.';
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->storeQuote(
|
||||
$instrumentId,
|
||||
(float) $apiResult['price'],
|
||||
$quoteCurrency,
|
||||
(string) $apiResult['fetched_at'],
|
||||
(string) $apiResult['source']
|
||||
);
|
||||
$fetched++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($errors !== []) {
|
||||
throw new RuntimeException(
|
||||
'Alpha Vantage: ' . $fetched . ' neu, ' . $reused . ' wiederverwendet, ' . $skipped . ' ohne Symbol, ' . $failed . ' Fehler. '
|
||||
'Bavest: ' . $fetched . ' neu, ' . $reused . ' wiederverwendet, ' . $skipped . ' ohne ISIN, ' . $failed . ' Fehler. '
|
||||
. implode(' | ', array_slice($errors, 0, 3))
|
||||
);
|
||||
}
|
||||
|
||||
return 'Alpha Vantage: ' . $fetched . ' neu, ' . $reused . ' wiederverwendet, ' . $skipped . ' ohne Symbol, ' . $failed . ' Fehler.';
|
||||
return 'Bavest: ' . $fetched . ' neu, ' . $reused . ' wiederverwendet, ' . $skipped . ' ohne ISIN, ' . $failed . ' Fehler.';
|
||||
}
|
||||
|
||||
private function searchSymbol(): string
|
||||
{
|
||||
$keywords = trim((string) ($_POST['search_keywords'] ?? ''));
|
||||
$this->symbolSearchKeywords = $keywords;
|
||||
$result = \module_fn('boersenchecker', 'alpha_vantage_search_symbols', $keywords);
|
||||
$result = \module_fn('boersenchecker', 'bavest_search_symbols', $keywords);
|
||||
$this->symbolSearchResults = is_array($result['results'] ?? null) ? $result['results'] : [];
|
||||
|
||||
if (empty($result['ok'])) {
|
||||
@@ -708,7 +725,7 @@ final class DashboardPage
|
||||
);
|
||||
$stmt->execute([
|
||||
'instrument_id' => $instrumentId,
|
||||
'source' => 'alpha_vantage:%',
|
||||
'source' => 'bavest:%',
|
||||
]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
return is_array($row) ? $row : null;
|
||||
|
||||
@@ -15,7 +15,7 @@ final class HomePage
|
||||
private string $positionTable;
|
||||
private string $quoteTable;
|
||||
private string $defaultReportCurrency;
|
||||
private int $alphaMinIntervalMinutes;
|
||||
private int $marketDataMinIntervalMinutes;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -26,9 +26,9 @@ final class HomePage
|
||||
|
||||
$settings = \modules()->settings('boersenchecker');
|
||||
$this->defaultReportCurrency = strtoupper(trim((string) ($settings['report_currency'] ?? 'EUR'))) ?: 'EUR';
|
||||
$this->alphaMinIntervalMinutes = (int) ($settings['alpha_vantage_min_interval_minutes'] ?? 60);
|
||||
if ($this->alphaMinIntervalMinutes <= 0) {
|
||||
$this->alphaMinIntervalMinutes = 60;
|
||||
$this->marketDataMinIntervalMinutes = (int) ($settings['bavest_min_interval_minutes'] ?? 60);
|
||||
if ($this->marketDataMinIntervalMinutes <= 0) {
|
||||
$this->marketDataMinIntervalMinutes = 60;
|
||||
}
|
||||
|
||||
$table = static fn (string $name): string => \module_fn('boersenchecker', 'table', $name);
|
||||
@@ -118,7 +118,7 @@ final class HomePage
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT DISTINCT i.id, i.name, i.symbol, i.quote_currency
|
||||
'SELECT DISTINCT i.id, i.name, i.symbol, i.isin, i.quote_currency
|
||||
FROM ' . $this->positionTable . ' p
|
||||
INNER JOIN ' . $this->instrumentTable . ' i ON i.id = p.instrument_id
|
||||
WHERE p.owner_sub = :owner_sub AND p.portfolio_id = :portfolio_id'
|
||||
@@ -134,37 +134,47 @@ final class HomePage
|
||||
|
||||
$updated = 0;
|
||||
$reused = 0;
|
||||
$bulkCandidates = [];
|
||||
foreach ($rows as $row) {
|
||||
$instrumentId = (int) ($row['id'] ?? 0);
|
||||
$symbol = strtoupper(trim((string) ($row['symbol'] ?? '')));
|
||||
if ($instrumentId <= 0 || $symbol === '') {
|
||||
$isin = strtoupper(trim((string) ($row['isin'] ?? '')));
|
||||
if ($instrumentId <= 0 || $isin === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$latest = $this->latestApiQuoteForInstrument($instrumentId);
|
||||
$latestTimestamp = is_array($latest) ? strtotime((string) ($latest['quoted_at'] ?? '')) : false;
|
||||
if ($latestTimestamp !== false && (time() - $latestTimestamp) < ($this->alphaMinIntervalMinutes * 60)) {
|
||||
if ($latestTimestamp !== false && (time() - $latestTimestamp) < ($this->marketDataMinIntervalMinutes * 60)) {
|
||||
$reused++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$apiResult = \module_fn('boersenchecker', 'alpha_vantage_fetch_quote', $symbol);
|
||||
if (empty($apiResult['ok'])) {
|
||||
continue;
|
||||
}
|
||||
$bulkCandidates[] = $row;
|
||||
}
|
||||
|
||||
if ($bulkCandidates !== []) {
|
||||
$bulkResult = \module_fn('boersenchecker', 'bavest_fetch_bulk_quotes', $bulkCandidates);
|
||||
$quotes = is_array($bulkResult['quotes'] ?? null) ? $bulkResult['quotes'] : [];
|
||||
$stmtInsert = $this->pdo->prepare(
|
||||
'INSERT INTO ' . $this->quoteTable . ' (instrument_id, price, currency, quoted_at, source)
|
||||
VALUES (:instrument_id, :price, :currency, :quoted_at, :source)'
|
||||
);
|
||||
$stmtInsert->execute([
|
||||
'instrument_id' => $instrumentId,
|
||||
'price' => (float) $apiResult['price'],
|
||||
'currency' => strtoupper(trim((string) ($row['quote_currency'] ?? $this->defaultReportCurrency))) ?: $this->defaultReportCurrency,
|
||||
'quoted_at' => (string) $apiResult['fetched_at'],
|
||||
'source' => (string) $apiResult['source'],
|
||||
]);
|
||||
$updated++;
|
||||
|
||||
foreach ($bulkCandidates as $row) {
|
||||
$instrumentId = (int) ($row['id'] ?? 0);
|
||||
$quote = $quotes[$instrumentId] ?? null;
|
||||
if (!is_array($quote) || !is_numeric($quote['price'] ?? null)) {
|
||||
continue;
|
||||
}
|
||||
$stmtInsert->execute([
|
||||
'instrument_id' => $instrumentId,
|
||||
'price' => (float) $quote['price'],
|
||||
'currency' => strtoupper(trim((string) ($quote['currency'] ?? $row['quote_currency'] ?? $this->defaultReportCurrency))) ?: $this->defaultReportCurrency,
|
||||
'quoted_at' => (string) ($quote['fetched_at'] ?? date('Y-m-d H:i:s')),
|
||||
'source' => (string) ($quote['source'] ?? 'bavest:quote'),
|
||||
]);
|
||||
$updated++;
|
||||
}
|
||||
}
|
||||
|
||||
return 'Aktuelle Kurse: ' . $updated . ' aktualisiert, ' . $reused . ' wiederverwendet.';
|
||||
@@ -241,7 +251,7 @@ final class HomePage
|
||||
);
|
||||
$stmt->execute([
|
||||
'instrument_id' => $instrumentId,
|
||||
'source' => 'alpha_vantage:%',
|
||||
'source' => 'bavest:%',
|
||||
]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
return is_array($row) ? $row : null;
|
||||
|
||||
@@ -72,6 +72,7 @@ final class InstrumentPage
|
||||
|
||||
$candidateName = trim((string) ($_GET['instrument_name_candidate'] ?? ''));
|
||||
$candidateSymbol = trim((string) ($_GET['symbol_candidate'] ?? ''));
|
||||
$candidateIsin = trim((string) ($_GET['isin_candidate'] ?? ''));
|
||||
$candidateMarket = trim((string) ($_GET['market_candidate'] ?? ''));
|
||||
$candidateCurrency = strtoupper(trim((string) ($_GET['quote_currency_candidate'] ?? $this->defaultReportCurrency))) ?: $this->defaultReportCurrency;
|
||||
if ($selectedInstrument === null && ($candidateName !== '' || $candidateSymbol !== '' || $candidateMarket !== '')) {
|
||||
@@ -79,9 +80,9 @@ final class InstrumentPage
|
||||
'id' => 0,
|
||||
'name' => $candidateName,
|
||||
'symbol' => $candidateSymbol,
|
||||
'isin' => $candidateIsin,
|
||||
'market' => $candidateMarket,
|
||||
'quote_currency' => $candidateCurrency,
|
||||
'isin' => '',
|
||||
'wkn' => '',
|
||||
];
|
||||
}
|
||||
@@ -106,7 +107,7 @@ final class InstrumentPage
|
||||
'save_instrument' => $this->saveInstrument(),
|
||||
'save_quote' => $this->saveQuote(),
|
||||
'delete_quote' => $this->deleteQuote(),
|
||||
'refresh_alpha_vantage_instrument' => $this->refreshInstrumentQuote(),
|
||||
'refresh_market_data_instrument' => $this->refreshInstrumentQuote(),
|
||||
'search_symbol' => $this->searchSymbol(),
|
||||
default => '',
|
||||
};
|
||||
@@ -211,12 +212,12 @@ final class InstrumentPage
|
||||
{
|
||||
$instrumentId = (int) ($_POST['instrument_id'] ?? 0);
|
||||
$instrument = $this->assertInstrumentAccessible($instrumentId);
|
||||
$symbol = trim((string) ($instrument['symbol'] ?? ''));
|
||||
if ($symbol === '') {
|
||||
throw new RuntimeException('Fuer diese Aktie ist kein Symbol hinterlegt.');
|
||||
$isin = strtoupper(trim((string) ($instrument['isin'] ?? '')));
|
||||
if ($isin === '') {
|
||||
throw new RuntimeException('Fuer diese Aktie ist keine ISIN hinterlegt.');
|
||||
}
|
||||
|
||||
$apiResult = \module_fn('boersenchecker', 'alpha_vantage_fetch_quote', $symbol);
|
||||
$apiResult = \module_fn('boersenchecker', 'bavest_fetch_quote_by_isin', $isin);
|
||||
if (empty($apiResult['ok'])) {
|
||||
throw new RuntimeException((string) ($apiResult['message'] ?? 'API-Abruf fehlgeschlagen.'));
|
||||
}
|
||||
@@ -233,13 +234,13 @@ final class InstrumentPage
|
||||
'source' => (string) $apiResult['source'],
|
||||
]);
|
||||
|
||||
return 'API-Kurs gespeichert.';
|
||||
return 'Bavest-Kurs gespeichert.';
|
||||
}
|
||||
|
||||
private function searchSymbol(): string
|
||||
{
|
||||
$this->searchKeywords = trim((string) ($_POST['search_keywords'] ?? ''));
|
||||
$result = \module_fn('boersenchecker', 'alpha_vantage_search_symbols', $this->searchKeywords);
|
||||
$result = \module_fn('boersenchecker', 'bavest_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.'));
|
||||
|
||||
Reference in New Issue
Block a user