Loading navigation...

(+)

Stock Portfolio - Live Values

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 '' . htmlspecialchars($depotKey) . ''; } 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' ]; ?>

Biggest Winners

Loading...

Biggest Losers

Loading...

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
'; } ?>
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); ?>
Stock Saved / Purchase Value Live Price Change (Value) P&L (Total) 7-Day Trend Date Exchange Actions
x ()
(saved on ) Purchase: x =
(use "buy") (temp)
G 💾 Commit Buy
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) . ' (' . $origCurrency . ' ' . $origInfo['symbol'] . number_format($origPrice, $origInfo['decimals']) . ')'; $lcHtml = $diffSign . formatCurrency(abs($diff), $savedCurrency, $exchangeRates) . ' (' . number_format(abs($diffPct), 2) . '%)'; if ($hasQty) { $totalPl = $diff * $qty; if ($totalPl > 0) $plHtml = '+' . formatCurrency($totalPl, $savedCurrency, $exchangeRates) . ' (+' . number_format($diffPct, 2) . '%)'; elseif ($totalPl < 0) $plHtml = '' . formatCurrency($totalPl, $savedCurrency, $exchangeRates) . ' (' . number_format($diffPct, 2) . '%)'; else $plHtml = '' . formatCurrency(0, $savedCurrency, $exchangeRates) . ' (0%)'; } // Accumulate for winners/losers if (!isset($streamedChanges[$sym]) || $qStock['saved_at'] > ($streamedChanges[$sym]['saved_at'] ?? 0)) { $streamedChanges[$sym] = [ 'symbol' => $sym, 'saved_price' => $savedPrice, 'saved_currency' => $savedCurrency, 'live_price' => $liveConverted, 'diff' => $diff, 'diff_percent' => $diffPct, 'saved_at' => $qStock['saved_at'] ?? 0, ]; } } else { $diffClass = 'price-neutral'; $lpHtml = 'N/A'; $lcHtml = '--'; if ($hasQty) $plHtml = 'N/A'; } // 7-day trend $trendData = get7DayTrend($sym, $exchangeRates); if ($trendData) { $trendClass = $trendData['trend'] === 'up' ? 'trend-up' : ($trendData['trend'] === 'down' ? 'trend-down' : ''); $changeSign = $trendData['change'] > 0 ? '+' : ''; $trendHtml = '
' . $trendData['sparkline'] . ' ' . $changeSign . number_format($trendData['change_pct'], 1) . '% ' . $trendData['trend_text'] . '
'; } else { $trendHtml = 'N/A'; } // Emit script to update this row's cells $lpJs = json_encode($lpHtml); $lcJs = json_encode($lcHtml); $plJs = $hasQty ? json_encode($plHtml) : 'null'; $trJs = json_encode($trendHtml); $clsJs = json_encode($diffClass); echo "\n"; flush(); } // Update winners/losers boxes after all symbols are loaded $winners = array_filter($streamedChanges, function($i){ return $i['diff'] > 0; }); $losers = array_filter($streamedChanges, function($i){ return $i['diff'] < 0; }); usort($winners, function($a,$b){ return $b['diff_percent'] <=> $a['diff_percent']; }); usort($losers, function($a,$b){ return $a['diff_percent'] <=> $b['diff_percent']; }); $winnersHtml = ''; foreach (array_slice($winners, 0, 5) as $w) { $winnersHtml .= '
' . htmlspecialchars($w['symbol']) . ' ' . formatCurrency($w['saved_price'], $w['saved_currency'], $exchangeRates) . ' → ' . formatCurrency($w['live_price'], $w['saved_currency'], $exchangeRates) . '
' . '
+' . formatCurrency($w['diff'], $w['saved_currency'], $exchangeRates) . ' (+' . number_format($w['diff_percent'], 2) . '%)
'; } if (!$winnersHtml) $winnersHtml = '
No winners yet.
'; $losersHtml = ''; foreach (array_slice($losers, 0, 5) as $l) { $losersHtml .= '
' . htmlspecialchars($l['symbol']) . ' ' . formatCurrency($l['saved_price'], $l['saved_currency'], $exchangeRates) . ' → ' . formatCurrency($l['live_price'], $l['saved_currency'], $exchangeRates) . '
' . '
' . formatCurrency($l['diff'], $l['saved_currency'], $exchangeRates) . ' (' . number_format($l['diff_percent'], 2) . '%)
'; } if (!$losersHtml) $losersHtml = '
No losers yet.
'; $wJs = json_encode($winnersHtml); $lJs = json_encode($losersHtml); echo "\n"; flush(); ?>