539 lines
21 KiB
PHP
539 lines
21 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
use App\ModuleConfigException;
|
|
|
|
spl_autoload_register(static function (string $class): void {
|
|
$prefix = 'Modules\\MiningChecker\\';
|
|
if (!str_starts_with($class, $prefix)) {
|
|
return;
|
|
}
|
|
|
|
$file = dirname(__DIR__) . '/mining-checker/src/' . str_replace('\\', '/', substr($class, strlen($prefix))) . '.php';
|
|
if (is_file($file)) {
|
|
require_once $file;
|
|
}
|
|
});
|
|
|
|
spl_autoload_register(static function (string $class): void {
|
|
$prefix = 'Modules\\Boersenchecker\\';
|
|
if (!str_starts_with($class, $prefix)) {
|
|
return;
|
|
}
|
|
|
|
$file = __DIR__ . '/src/' . str_replace('\\', '/', substr($class, strlen($prefix))) . '.php';
|
|
if (is_file($file)) {
|
|
require_once $file;
|
|
}
|
|
});
|
|
|
|
$moduleName = 'boersenchecker';
|
|
$mm = isset($modules) && $modules instanceof App\ModuleManager ? $modules : modules();
|
|
|
|
$mm->registerFunction($moduleName, 'table', static function (string $name): string {
|
|
$prefix = 'boersencheck_';
|
|
$sanitized = preg_replace('/[^a-zA-Z0-9_]/', '', $name);
|
|
return $prefix . $sanitized;
|
|
});
|
|
|
|
$mm->registerFunction($moduleName, 'pdo', function () use ($moduleName): \PDO {
|
|
$settings = modules()->settings($moduleName);
|
|
$useSeparate = !empty($settings['use_separate_db']);
|
|
|
|
if ($useSeparate) {
|
|
$module = modules()->get($moduleName);
|
|
$fallback = $module['db_defaults'] ?? [];
|
|
return modules()->modulePdo($moduleName, $fallback);
|
|
}
|
|
|
|
$base = app()->basePdo();
|
|
if ($base instanceof \PDO) {
|
|
return $base;
|
|
}
|
|
|
|
throw new ModuleConfigException(
|
|
$moduleName,
|
|
'Base-DB ist deaktiviert. Bitte Base-DB aktivieren oder eine eigene Modul-DB konfigurieren.'
|
|
);
|
|
});
|
|
|
|
$mm->registerFunction($moduleName, 'ensure_schema', function () use ($moduleName): void {
|
|
$pdo = module_fn($moduleName, 'pdo');
|
|
$table = static fn (string $name): string => module_fn($moduleName, 'table', $name);
|
|
$driver = strtolower((string) $pdo->getAttribute(PDO::ATTR_DRIVER_NAME));
|
|
|
|
$portfolioTable = $table('portfolios');
|
|
$instrumentTable = $table('instruments');
|
|
$positionTable = $table('positions');
|
|
$quoteTable = $table('quotes');
|
|
|
|
if ($driver === 'pgsql') {
|
|
$pdo->exec("CREATE TABLE IF NOT EXISTS {$portfolioTable} (
|
|
id SERIAL PRIMARY KEY,
|
|
owner_sub VARCHAR(190) NOT NULL,
|
|
name VARCHAR(190) NOT NULL,
|
|
base_currency VARCHAR(10) NOT NULL DEFAULT 'EUR',
|
|
notes TEXT NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)");
|
|
$pdo->exec("CREATE TABLE IF NOT EXISTS {$instrumentTable} (
|
|
id SERIAL PRIMARY KEY,
|
|
isin VARCHAR(32) NULL,
|
|
wkn VARCHAR(32) NULL,
|
|
symbol VARCHAR(32) NULL,
|
|
name VARCHAR(255) NOT NULL,
|
|
quote_currency VARCHAR(10) NOT NULL DEFAULT 'EUR',
|
|
market VARCHAR(120) NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)");
|
|
$pdo->exec("CREATE TABLE IF NOT EXISTS {$positionTable} (
|
|
id SERIAL PRIMARY KEY,
|
|
owner_sub VARCHAR(190) NOT NULL,
|
|
portfolio_id INTEGER NOT NULL,
|
|
instrument_id INTEGER NOT NULL,
|
|
quantity NUMERIC(20,6) NOT NULL,
|
|
purchase_price NUMERIC(20,8) NOT NULL,
|
|
purchase_currency VARCHAR(10) NOT NULL,
|
|
purchase_date DATE NOT NULL,
|
|
fees NUMERIC(20,8) NULL,
|
|
notes TEXT NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)");
|
|
$pdo->exec("CREATE TABLE IF NOT EXISTS {$quoteTable} (
|
|
id SERIAL PRIMARY KEY,
|
|
instrument_id INTEGER NOT NULL,
|
|
price NUMERIC(20,8) NOT NULL,
|
|
currency VARCHAR(10) NOT NULL,
|
|
quoted_at TIMESTAMP NOT NULL,
|
|
source VARCHAR(64) NOT NULL DEFAULT 'manual',
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)");
|
|
$pdo->exec("CREATE INDEX IF NOT EXISTS {$portfolioTable}_owner_idx ON {$portfolioTable} (owner_sub)");
|
|
$pdo->exec("CREATE UNIQUE INDEX IF NOT EXISTS {$instrumentTable}_isin_uniq ON {$instrumentTable} (isin) WHERE isin IS NOT NULL");
|
|
$pdo->exec("CREATE INDEX IF NOT EXISTS {$instrumentTable}_symbol_idx ON {$instrumentTable} (symbol)");
|
|
$pdo->exec("CREATE INDEX IF NOT EXISTS {$positionTable}_owner_idx ON {$positionTable} (owner_sub)");
|
|
$pdo->exec("CREATE INDEX IF NOT EXISTS {$positionTable}_portfolio_idx ON {$positionTable} (portfolio_id)");
|
|
$pdo->exec("CREATE INDEX IF NOT EXISTS {$positionTable}_instrument_idx ON {$positionTable} (instrument_id)");
|
|
$pdo->exec("CREATE INDEX IF NOT EXISTS {$quoteTable}_instrument_time_idx ON {$quoteTable} (instrument_id, quoted_at DESC)");
|
|
} elseif ($driver === 'mysql') {
|
|
$pdo->exec("CREATE TABLE IF NOT EXISTS {$portfolioTable} (
|
|
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
|
owner_sub VARCHAR(190) NOT NULL,
|
|
name VARCHAR(190) NOT NULL,
|
|
base_currency VARCHAR(10) NOT NULL DEFAULT 'EUR',
|
|
notes TEXT NULL,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
KEY {$portfolioTable}_owner_idx (owner_sub)
|
|
)");
|
|
$pdo->exec("CREATE TABLE IF NOT EXISTS {$instrumentTable} (
|
|
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
|
isin VARCHAR(32) NULL,
|
|
wkn VARCHAR(32) NULL,
|
|
symbol VARCHAR(32) NULL,
|
|
name VARCHAR(255) NOT NULL,
|
|
quote_currency VARCHAR(10) NOT NULL DEFAULT 'EUR',
|
|
market VARCHAR(120) NULL,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE KEY {$instrumentTable}_isin_uniq (isin),
|
|
KEY {$instrumentTable}_symbol_idx (symbol)
|
|
)");
|
|
$pdo->exec("CREATE TABLE IF NOT EXISTS {$positionTable} (
|
|
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
|
owner_sub VARCHAR(190) NOT NULL,
|
|
portfolio_id INTEGER NOT NULL,
|
|
instrument_id INTEGER NOT NULL,
|
|
quantity DECIMAL(20,6) NOT NULL,
|
|
purchase_price DECIMAL(20,8) NOT NULL,
|
|
purchase_currency VARCHAR(10) NOT NULL,
|
|
purchase_date DATE NOT NULL,
|
|
fees DECIMAL(20,8) NULL,
|
|
notes TEXT NULL,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
KEY {$positionTable}_owner_idx (owner_sub),
|
|
KEY {$positionTable}_portfolio_idx (portfolio_id),
|
|
KEY {$positionTable}_instrument_idx (instrument_id)
|
|
)");
|
|
$pdo->exec("CREATE TABLE IF NOT EXISTS {$quoteTable} (
|
|
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
|
instrument_id INTEGER NOT NULL,
|
|
price DECIMAL(20,8) NOT NULL,
|
|
currency VARCHAR(10) NOT NULL,
|
|
quoted_at DATETIME NOT NULL,
|
|
source VARCHAR(64) NOT NULL DEFAULT 'manual',
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
KEY {$quoteTable}_instrument_time_idx (instrument_id, quoted_at)
|
|
)");
|
|
} else {
|
|
$pdo->exec("CREATE TABLE IF NOT EXISTS {$portfolioTable} (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
owner_sub VARCHAR(190) NOT NULL,
|
|
name VARCHAR(190) NOT NULL,
|
|
base_currency VARCHAR(10) NOT NULL DEFAULT 'EUR',
|
|
notes TEXT NULL,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)");
|
|
$pdo->exec("CREATE TABLE IF NOT EXISTS {$instrumentTable} (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
isin VARCHAR(32) NULL,
|
|
wkn VARCHAR(32) NULL,
|
|
symbol VARCHAR(32) NULL,
|
|
name VARCHAR(255) NOT NULL,
|
|
quote_currency VARCHAR(10) NOT NULL DEFAULT 'EUR',
|
|
market VARCHAR(120) NULL,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)");
|
|
$pdo->exec("CREATE TABLE IF NOT EXISTS {$positionTable} (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
owner_sub VARCHAR(190) NOT NULL,
|
|
portfolio_id INTEGER NOT NULL,
|
|
instrument_id INTEGER NOT NULL,
|
|
quantity DECIMAL(20,6) NOT NULL,
|
|
purchase_price DECIMAL(20,8) NOT NULL,
|
|
purchase_currency VARCHAR(10) NOT NULL,
|
|
purchase_date DATE NOT NULL,
|
|
fees DECIMAL(20,8) NULL,
|
|
notes TEXT NULL,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)");
|
|
$pdo->exec("CREATE TABLE IF NOT EXISTS {$quoteTable} (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
instrument_id INTEGER NOT NULL,
|
|
price DECIMAL(20,8) NOT NULL,
|
|
currency VARCHAR(10) NOT NULL,
|
|
quoted_at DATETIME NOT NULL,
|
|
source VARCHAR(64) NOT NULL DEFAULT 'manual',
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)");
|
|
$pdo->exec("CREATE INDEX IF NOT EXISTS {$portfolioTable}_owner_idx ON {$portfolioTable} (owner_sub)");
|
|
$pdo->exec("CREATE UNIQUE INDEX IF NOT EXISTS {$instrumentTable}_isin_uniq ON {$instrumentTable} (isin)");
|
|
$pdo->exec("CREATE INDEX IF NOT EXISTS {$instrumentTable}_symbol_idx ON {$instrumentTable} (symbol)");
|
|
$pdo->exec("CREATE INDEX IF NOT EXISTS {$positionTable}_owner_idx ON {$positionTable} (owner_sub)");
|
|
$pdo->exec("CREATE INDEX IF NOT EXISTS {$positionTable}_portfolio_idx ON {$positionTable} (portfolio_id)");
|
|
$pdo->exec("CREATE INDEX IF NOT EXISTS {$positionTable}_instrument_idx ON {$positionTable} (instrument_id)");
|
|
$pdo->exec("CREATE INDEX IF NOT EXISTS {$quoteTable}_instrument_time_idx ON {$quoteTable} (instrument_id, quoted_at DESC)");
|
|
}
|
|
});
|
|
|
|
$mm->registerFunction($moduleName, 'fx_service', static function (): ?object {
|
|
if (!is_dir(dirname(__DIR__) . '/mining-checker')) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
$config = \Modules\MiningChecker\Infrastructure\ModuleConfig::load(dirname(__DIR__) . '/mining-checker');
|
|
$repo = new \Modules\MiningChecker\Infrastructure\MiningRepository(
|
|
\Modules\MiningChecker\Infrastructure\ConnectionFactory::make($config),
|
|
$config->tablePrefix()
|
|
);
|
|
$fx = $config->fx();
|
|
|
|
return new \Modules\MiningChecker\Domain\FxService(
|
|
$repo,
|
|
(string) ($fx['url'] ?? 'https://currencyapi.net'),
|
|
(string) ($fx['currencies_url'] ?? ($fx['url'] ?? 'https://currencyapi.net')),
|
|
(int) ($fx['timeout'] ?? 10),
|
|
(int) ($fx['cache_ttl'] ?? 21600),
|
|
false,
|
|
(string) ($fx['provider'] ?? 'currencyapi'),
|
|
(string) ($fx['api_key'] ?? '')
|
|
);
|
|
} catch (\Throwable) {
|
|
return null;
|
|
}
|
|
});
|
|
|
|
$mm->registerFunction($moduleName, 'fx_refresh', static function (string $baseCurrency = 'EUR', float $maxAgeHours = 6.0): array {
|
|
$service = module_fn('boersenchecker', 'fx_service');
|
|
if (!$service || !method_exists($service, 'ensureFreshLatestRates')) {
|
|
return [
|
|
'ok' => false,
|
|
'message' => 'Mining-Checker FX-Service ist aktuell nicht verfuegbar.',
|
|
];
|
|
}
|
|
|
|
try {
|
|
$result = $service->ensureFreshLatestRates($maxAgeHours, strtoupper(trim($baseCurrency)) ?: 'EUR');
|
|
return [
|
|
'ok' => true,
|
|
'message' => !empty($result['reused'])
|
|
? 'Vorhandene FX-Daten weiterverwendet.'
|
|
: 'FX-Daten aus dem Mining-Checker aktualisiert.',
|
|
'result' => $result,
|
|
];
|
|
} catch (\Throwable $e) {
|
|
return [
|
|
'ok' => false,
|
|
'message' => 'FX-Aktualisierung fehlgeschlagen: ' . $e->getMessage(),
|
|
];
|
|
}
|
|
});
|
|
|
|
$mm->registerFunction($moduleName, 'alpha_vantage_fetch_quote', static function (string $symbol): 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;
|
|
$symbol = strtoupper(trim($symbol));
|
|
|
|
if ($symbol === '') {
|
|
return [
|
|
'ok' => false,
|
|
'message' => 'Kein API-Symbol hinterlegt.',
|
|
];
|
|
}
|
|
|
|
if ($apiKey === '') {
|
|
return [
|
|
'ok' => false,
|
|
'message' => 'Alpha-Vantage-API-Key fehlt. Bitte im Modul-Setup hinterlegen.',
|
|
];
|
|
}
|
|
|
|
$url = 'https://www.alphavantage.co/query?' . http_build_query([
|
|
'function' => 'GLOBAL_QUOTE',
|
|
'symbol' => $symbol,
|
|
'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 Anfrage fehlgeschlagen.'
|
|
. ($curlError !== '' ? ' ' . $curlError : '')
|
|
. ($httpCode > 0 ? ' HTTP ' . $httpCode : ''),
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
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 Anfrage lieferte keine Daten.',
|
|
];
|
|
}
|
|
}
|
|
|
|
$decoded = json_decode($responseBody, true);
|
|
if (!is_array($decoded)) {
|
|
return [
|
|
'ok' => false,
|
|
'message' => 'Alpha Vantage Antwort ist kein gueltiges JSON.',
|
|
];
|
|
}
|
|
|
|
if (!empty($decoded['Note'])) {
|
|
return [
|
|
'ok' => false,
|
|
'message' => 'Alpha Vantage Limit-Hinweis: ' . trim((string) $decoded['Note']),
|
|
];
|
|
}
|
|
|
|
if (!empty($decoded['Information'])) {
|
|
return [
|
|
'ok' => false,
|
|
'message' => trim((string) $decoded['Information']),
|
|
];
|
|
}
|
|
|
|
if (!empty($decoded['Error Message'])) {
|
|
return [
|
|
'ok' => false,
|
|
'message' => trim((string) $decoded['Error Message']),
|
|
];
|
|
}
|
|
|
|
$quote = is_array($decoded['Global Quote'] ?? null) ? $decoded['Global Quote'] : [];
|
|
$price = $quote['05. price'] ?? null;
|
|
if (!is_numeric($price)) {
|
|
return [
|
|
'ok' => false,
|
|
'message' => 'Alpha Vantage lieferte keinen Preis fuer das Symbol ' . $symbol . '.',
|
|
];
|
|
}
|
|
|
|
return [
|
|
'ok' => true,
|
|
'symbol' => (string) ($quote['01. symbol'] ?? $symbol),
|
|
'price' => (float) $price,
|
|
'latest_trading_day' => (string) ($quote['07. latest trading day'] ?? ''),
|
|
'previous_close' => is_numeric($quote['08. previous close'] ?? null) ? (float) $quote['08. previous close'] : null,
|
|
'change' => is_numeric($quote['09. change'] ?? null) ? (float) $quote['09. change'] : null,
|
|
'change_percent' => (string) ($quote['10. change percent'] ?? ''),
|
|
'fetched_at' => date('Y-m-d H:i:s'),
|
|
'source' => 'alpha_vantage:GLOBAL_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,
|
|
];
|
|
});
|