Stock Portfolio
Live prices vs saved values
['symbol' => '$', 'code' => 'USD', 'decimals' => 2],
'EUR' => ['symbol' => '€', 'code' => 'EUR', 'decimals' => 2],
'JPY' => ['symbol' => '¥', 'code' => 'JPY', 'decimals' => 0],
'GBP' => ['symbol' => '£', 'code' => 'GBP', 'decimals' => 2],
'HKD' => ['symbol' => 'HK$', 'code' => 'HKD', 'decimals' => 2],
'CNY' => ['symbol' => 'CN¥', 'code' => 'CNY', 'decimals' => 2],
'SEK' => ['symbol' => 'kr', 'code' => 'SEK', 'decimals' => 2],
'BRL' => ['symbol' => 'R$', 'code' => 'BRL', 'decimals' => 2],
'MXN' => ['symbol' => 'MX$', 'code' => 'MXN', 'decimals' => 2]
];
$DEFAULT_RATES = [
'USD' => 1, 'EUR' => 0.92, 'JPY' => 148.5, 'GBP' => 0.79, 'HKD' => 7.82,
'CNY' => 7.24, 'SEK' => 10.45, 'MXN' => 16.80, 'BRL' => 5.80
];
function curl_get($url) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
function getExchangeRates() {
global $DEFAULT_RATES;
$rateUrl = "https://api.exchangerate-api.com/v4/latest/USD";
$response = curl_get($rateUrl);
if ($response) {
$data = json_decode($response, true);
if (isset($data['rates'])) {
$rates = [];
foreach (array_keys($GLOBALS['DEFAULT_RATES']) as $code) {
$rates[$code] = $data['rates'][$code] ?? $GLOBALS['DEFAULT_RATES'][$code];
}
return $rates;
}
}
return $GLOBALS['DEFAULT_RATES'];
}
function getCurrencyInfo($currencyCode) {
global $CURRENCY_CONFIG;
$symbolMap = [
'$' => 'USD', '€' => 'EUR', '¥' => 'JPY', '£' => 'GBP',
'HK$' => 'HKD', 'CN¥' => 'CNY', 'kr' => 'SEK', 'MX$' => 'MXN'
];
if (isset($symbolMap[$currencyCode])) {
$currencyCode = $symbolMap[$currencyCode];
}
if (isset($CURRENCY_CONFIG[$currencyCode])) {
return $CURRENCY_CONFIG[$currencyCode];
}
if ($currencyCode === 'kr') {
return $CURRENCY_CONFIG['SEK'];
}
return ['symbol' => '$', 'code' => 'USD', 'decimals' => 2];
}
function convertCurrency($amount, $fromCurrency, $toCurrency, $exchangeRates) {
$fromInfo = getCurrencyInfo($fromCurrency);
$toInfo = getCurrencyInfo($toCurrency);
$amountUSD = $amount / $exchangeRates[$fromInfo['code']];
$result = $amountUSD * $exchangeRates[$toInfo['code']];
return $result;
}
function formatCurrency($amount, $currency, $exchangeRates) {
$info = getCurrencyInfo($currency);
$symbol = $info['symbol'];
$decimals = $info['decimals'];
return $symbol . number_format($amount, $decimals);
}
function getLivePriceWithCurrency($symbol, $exchangeRates) {
$url = "https://query1.finance.yahoo.com/v8/finance/chart/" . urlencode($symbol);
$response = curl_get($url);
if ($response) {
$data = json_decode($response, true);
if (isset($data['chart']['result'][0]['meta']['regularMarketPrice'])) {
$price = $data['chart']['result'][0]['meta']['regularMarketPrice'];
$currency = strtoupper($data['chart']['result'][0]['meta']['currency'] ?? 'USD');
$priceUSD = $price;
if ($currency !== 'USD' && isset($exchangeRates[$currency]) && $exchangeRates[$currency] > 0) {
$priceUSD = $price / $exchangeRates[$currency];
} elseif ($currency !== 'USD' && !isset($exchangeRates[$currency])) {
error_log("Unknown currency: $currency for symbol $symbol");
$priceUSD = $price;
$currency = 'USD';
}
return [
'price_usd' => $priceUSD,
'original_currency' => $currency,
'original_price' => $price
];
}
}
return null;
}
function get7DayTrend($symbol, $exchangeRates) {
$url = "https://query1.finance.yahoo.com/v8/finance/chart/" . urlencode($symbol) . "?range=7d&interval=1d";
$response = curl_get($url);
if (!$response) return null;
$data = json_decode($response, true);
if (!isset($data['chart']['result'][0])) return null;
$result = $data['chart']['result'][0];
$closes = $result['indicators']['quote'][0]['close'] ?? [];
$validCloses = array_values(array_filter($closes, function($v) { return $v !== null; }));
if (count($validCloses) < 2) return null;
$oldPrice = $validCloses[0];
$newPrice = $validCloses[count($validCloses) - 1];
if ($oldPrice <= 0) return null;
$change = $newPrice - $oldPrice;
$changePct = ($change / $oldPrice) * 100;
$trend = $change > 0 ? 'up' : ($change < 0 ? 'down' : 'flat');
$min = min($validCloses);
$max = max($validCloses);
$range = $max - $min ?: 1;
$height = 30;
$width = 100;
$step = $width / (count($validCloses) - 1);
$points = [];
foreach ($validCloses as $i => $value) {
$x = $i * $step;
$y = $height - (($value - $min) / $range * $height);
$points[] = "$x,$y";
}
$color = $trend === 'up' ? '#28a745' : ($trend === 'down' ? '#dc3545' : '#6c757d');
$sparkline = '
';
return [
'change' => $change,
'change_pct' => $changePct,
'trend' => $trend,
'sparkline' => $sparkline,
'trend_text' => $trend === 'up' ? '📈 UP' : ($trend === 'down' ? '📉 DOWN' : '➡️ FLAT')
];
}
function fetchExchangeInfo($symbol) {
$symbolUpper = strtoupper($symbol);
if (substr($symbolUpper, -3) == '.HK') {
return ['display' => 'Hong Kong', 'code' => 'HKG'];
}
if (substr($symbolUpper, -3) == '.SA') {
return ['display' => 'B3 - São Paulo', 'code' => 'BVMF'];
}
if (substr($symbolUpper, -3) == '.L') {
return ['display' => 'London', 'code' => 'LON'];
}
if (substr($symbolUpper, -3) == '.T' || substr($symbolUpper, -2) == '.T') {
return ['display' => 'Tokyo', 'code' => 'TYO'];
}
if (substr($symbolUpper, -3) == '.DE') {
return ['display' => 'XETRA', 'code' => 'FRA'];
}
if (substr($symbolUpper, -3) == '.MX') {
return ['display' => 'Mexico', 'code' => 'MEX'];
}
if (strpos($symbolUpper, '.BMV') !== false || strpos($symbolUpper, ':BMV') !== false) {
return ['display' => 'Mexico', 'code' => 'MEX'];
}
$url = "https://query1.finance.yahoo.com/v1/finance/search?q=" . urlencode($symbol) . ""esCount=5&newsCount=0";
$response = curl_get($url);
$exchangeDisplay = 'NASDAQ';
$exchangeCode = 'NASDAQ';
if ($response) {
$data = json_decode($response, true);
if (isset($data['quotes']) && count($data['quotes']) > 0) {
foreach ($data['quotes'] as $quote) {
if (isset($quote['quoteType']) && $quote['quoteType'] === 'EQUITY') {
$rawExchange = $quote['exchange'] ?? 'NMS';
$exchangeDisplay = $quote['exchDisp'] ?? $rawExchange;
$exchangeMap = [
'PNK' => 'OTCMKTS', 'NYQ' => 'NYSE', 'NYM' => 'NYSE',
'ASE' => 'AMEX', 'TYO' => 'TYO', 'LON' => 'LON',
'FRA' => 'FRA', 'HKG' => 'HKG', 'MEX' => 'MEX',
'BMV' => 'MEX', 'HKE' => 'HKG',
];
$exchangeCode = $exchangeMap[$rawExchange] ?? $rawExchange;
if (strpos($exchangeDisplay, 'Hong Kong') !== false || $exchangeCode == 'HKG') {
$exchangeDisplay = 'Hong Kong';
$exchangeCode = 'HKG';
}
if (strpos($exchangeDisplay, 'Mexico') !== false || $exchangeCode == 'MEX' || $rawExchange == 'MEX') {
$exchangeDisplay = 'Mexico';
$exchangeCode = 'MEX';
}
break;
}
}
}
}
return ['display' => $exchangeDisplay, 'code' => $exchangeCode];
}
function getSearchButtons() {
$txtFile = __DIR__ . '/searchengs.txt';
$buttons = [];
if (file_exists($txtFile)) {
$lines = file($txtFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
$parts = explode('#', trim($line), 2);
if (count($parts) === 2) {
$buttons[] = [
'file' => trim($parts[0]),
'label' => trim($parts[1])
];
}
}
}
return $buttons;
}
function getDepotIcon($depotName) {
if (empty($depotName)) return '';
$depotIcons = [
'comdirect' => 'https://res.cloudinary.com/apideck/image/upload/v1594331712/icons/comdirect-de.jpg',
'ingdiba' => 'https://e7.pngegg.com/pngimages/396/711/png-clipart-ing-group-ing-vysya-bank-ing-belgium-ing-bank-slaski-bank-mammal-cat-like-mammal-thumbnail.png'
];
$depotKey = strtolower(trim($depotName));
if (isset($depotIcons[$depotKey])) {
return '
 . ')
';
}
return '';
}
$exchangeRates = getExchangeRates();
$jsonFile = 'stocks.json';
if (!file_exists($jsonFile)) {
echo '
No Stocks Yet
No stocks have been saved.
';
exit;
}
$stocks = json_decode(file_get_contents($jsonFile), true);
if (empty($stocks)) {
echo '
No Stocks Found
stocks.json is empty.
';
exit;
}
usort($stocks, function($a, $b) {
return $b['saved_at'] - $a['saved_at'];
});
// Build fetch order: isin+nrbght first, then rest
$fetchOrderPriority = [];
$fetchOrderNormal = [];
$seenOrder = [];
foreach ($stocks as $_s) {
$sym = $_s['stock'];
if (isset($seenOrder[$sym])) continue;
$seenOrder[$sym] = true;
if (!empty($_s['isin']) && !empty($_s['nrbght']) && $_s['nrbght'] > 0)
$fetchOrderPriority[] = $_s;
else
$fetchOrderNormal[] = $_s;
}
$fetchQueue = array_merge($fetchOrderPriority, $fetchOrderNormal);
$marketToCode = [
'OTC Markets' => 'OTCMKTS', 'NASDAQ' => 'NASDAQ', 'NYSE' => 'NYSE',
'Tokyo' => 'TYO', 'London' => 'LON', 'XETRA' => 'FRA',
'Hong Kong' => 'HKG', 'HongKong' => 'HKG', 'Mexico' => 'MEX',
'B3 - São Paulo' => 'BVMF'
];
?>
All Saved Stocks - [BOUGHT]
0) {
echo '
🔍 Custom search buttons loaded: ' . count($debugButtons) . ' (' . htmlspecialchars($debugButtons[0]['label']) . ')
';
} else {
echo '
⚠️ No searchengs.txt found or empty. Create file with format: filename.php#buttonlabel
';
}
?>
| Stock |
Saved / Purchase Value |
Live Price |
Change (Value) |
P&L (Total) |
7-Day Trend |
Date |
Exchange |
Actions |
0;
$hasPurchaseData = $hasIsin && $hasQuantity;
$quantity = $hasQuantity ? $stock['nrbght'] : 0;
$isin = $hasIsin ? $stock['isin'] : '';
$depotName = isset($stock['depot']) ? $stock['depot'] : '';
$depotIconHtml = getDepotIcon($depotName);
$exchangeCode = '';
$exchangeDisplay = '';
if (!$hasExchange) {
$exchangeInfo = fetchExchangeInfo($symbol);
$exchangeDisplay = $exchangeInfo['display'];
$exchangeCode = $exchangeInfo['code'];
} else {
$exchangeDisplay = $exchangeMarket;
if (isset($marketToCode[$exchangeMarket])) {
$exchangeCode = $marketToCode[$exchangeMarket];
} else {
$exchangeCode = $exchangeMarket;
}
}
$rowClass = $hasPurchaseData ? 'stock-with-data' : '';
$googleSymbol = preg_replace('/\.[^.]+$/', '', $symbol);
?>
|
x
()
|
(saved on )
Purchase: x =
|
⏳ |
⏳ |
(use "buy")⏳ |
⏳ |
|
(temp)
|
|
0;
$qty = $hasQty ? (int)$qStock['nrbght'] : 0;
$idSym = htmlspecialchars($sym, ENT_QUOTES);
// Live price
$priceData = getLivePriceWithCurrency($sym, $exchangeRates);
if ($priceData) {
$priceUSD = $priceData['price_usd'];
$origCurrency = $priceData['original_currency'];
$origPrice = $priceData['original_price'];
$liveConverted = convertCurrency($priceUSD, 'USD', $savedCurrency, $exchangeRates);
$diff = $liveConverted - $savedPrice;
$diffPct = $savedPrice > 0 ? ($diff / $savedPrice) * 100 : 0;
$diffClass = $diff > 0 ? 'price-up' : ($diff < 0 ? 'price-down' : 'price-neutral');
$diffSign = $diff > 0 ? '+ ' : ($diff < 0 ? '- ' : '');
$origInfo = getCurrencyInfo($origCurrency);
$lpHtml = formatCurrency($liveConverted, $savedCurrency, $exchangeRates)
. ' No winners yet.
';
$losersHtml = '';
foreach (array_slice($losers, 0, 5) as $l) {
$losersHtml .= 'No losers yet.
';
$wJs = json_encode($winnersHtml);
$lJs = json_encode($losersHtml);
echo "\n";
flush();
?>