Files
Stonks/highnlows.php
2026-06-04 21:55:15 +02:00

659 lines
25 KiB
PHP

<?php
/**
* Stock High/Low - WITH CHART AND DETAILED LIST
* Compact: Click to open full chart in new tab
* Full: Shows interactive chart + detailed peaks/valleys list
* Usage: stock_filter.php?symbol=MSFT
* stock_filter.php?symbol=MSFT&compact
*/
if (isset($_GET['symbol']) && !empty($_GET['symbol'])) {
$symbol = strtoupper(trim(htmlspecialchars($_GET['symbol'])));
$isCompact = isset($_GET['compact']);
$stockData = getYahooStockDataAllTime($symbol);
if ($stockData && !isset($stockData['error']) && count($stockData['data'] ?? $stockData) > 0) {
if ($isCompact) {
displayCompactWidget($symbol, $stockData);
} else {
displayFullChartWithList($symbol, $stockData);
}
} else {
echo "Error: {$symbol}";
}
} else {
echo "?symbol=REQUIRED";
}
/**
* COMPACT WIDGET - Clickable, opens full view in new tab
*/
function displayCompactWidget($symbol, $stockData) {
$data = $stockData['data'];
if (empty($data)) {
echo "NO DATA";
return;
}
$prices = array_filter(array_column($data, 'close'), function($p) {
return $p > 1;
});
$highest = max($prices);
$lowest = min($prices);
$current = end($data)['close'];
$percentFromHigh = (($highest - $current) / $highest) * 100;
$fullUrl = $_SERVER['SCRIPT_NAME'] . '?symbol=' . urlencode($symbol);
?>
<!DOCTYPE html>
<html>
<head>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: transparent; display: inline-block; margin: 0; padding: 0; }
.widget {
width: 180px;
height: 21px;
background: #1a1a2e;
border-radius: 6px;
padding: 0 8px;
font-family: 'Segoe UI', Arial, sans-serif;
font-size: 10px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: space-between;
border: 1px solid #333;
cursor: pointer;
transition: all 0.2s ease;
text-decoration: none;
color: inherit;
}
.widget:hover {
background: #2a2a3e;
border-color: #ffd700;
transform: scale(1.02);
}
.symbol { color: #ffd700; }
.current { color: white; }
.high { color: #51cf66; }
.low { color: #ff6b6b; }
.separator { color: #444; }
.percent { color: #ff6b6b; font-size: 9px; }
.click-hint { font-size: 7px; color: #555; text-align: center; margin-top: 2px; display: block; }
</style>
</head>
<body>
<a href="<?php echo $fullUrl; ?>" target="_blank" style="text-decoration: none;">
<div class="widget" title="Click to open full chart in new tab">
<span class="symbol"><?php echo $symbol; ?></span>
<span class="current">$<?php echo number_format($current, 0); ?></span>
<span class="high">H:$<?php echo number_format($highest, 0); ?></span>
<span class="low">L:$<?php echo number_format($lowest, 0); ?></span>
<span class="percent">▼<?php echo number_format($percentFromHigh, 0); ?>%</span>
</div>
<div class="click-hint"> </div>
</a>
</body>
</html>
<?php
}
/**
* FULL CHART VIEW with Chart.js AND detailed list
*/
function displayFullChartWithList($symbol, $stockData) {
$data = $stockData['data'];
$currentPrice = end($data)['close'];
// Get last 2 years of data for chart
$twoYearsAgo = strtotime('-2 years');
$chartData = array_filter($data, function($point) use ($twoYearsAgo) {
return strtotime($point['date']) >= $twoYearsAgo;
});
$chartData = array_values($chartData);
// Prepare data for Chart.js
$dates = array_column($chartData, 'date');
$prices = array_column($chartData, 'close');
// Find peaks and valleys for chart
$peaks = [];
$valleys = [];
$window = 10;
for ($i = $window; $i < count($chartData) - $window; $i++) {
$isPeak = true;
$isValley = true;
for ($j = 1; $j <= $window; $j++) {
if ($chartData[$i]['close'] <= $chartData[$i - $j]['close']) $isPeak = false;
if ($chartData[$i]['close'] >= $chartData[$i - $j]['close']) $isValley = false;
if ($chartData[$i]['close'] <= $chartData[$i + $j]['close']) $isPeak = false;
if ($chartData[$i]['close'] >= $chartData[$i + $j]['close']) $isValley = false;
}
if ($isPeak) {
$peaks[] = ['x' => $i, 'y' => $chartData[$i]['close']];
}
if ($isValley) {
$valleys[] = ['x' => $i, 'y' => $chartData[$i]['close']];
}
}
// Create overlay areas
$peakZones = [];
foreach ($peaks as $peak) {
$peakZones[] = ['from' => $peak['y'] * 0.95, 'to' => $peak['y'] * 1.05, 'peak' => $peak['y']];
}
$valleyZones = [];
foreach ($valleys as $valley) {
$valleyZones[] = ['from' => $valley['y'] * 0.95, 'to' => $valley['y'] * 1.05, 'valley' => $valley['y']];
}
// Find ALL peaks and valleys for the list (last 5 years)
$fiveYearsAgo = strtotime('-5 years');
$listData = array_filter($data, function($point) use ($fiveYearsAgo) {
return strtotime($point['date']) >= $fiveYearsAgo;
});
$listData = array_values($listData);
$listPeaks = [];
$listValleys = [];
for ($i = $window; $i < count($listData) - $window; $i++) {
$isPeak = true;
$isValley = true;
for ($j = 1; $j <= $window; $j++) {
if ($listData[$i]['close'] <= $listData[$i - $j]['close']) $isPeak = false;
if ($listData[$i]['close'] >= $listData[$i - $j]['close']) $isValley = false;
if ($listData[$i]['close'] <= $listData[$i + $j]['close']) $isPeak = false;
if ($listData[$i]['close'] >= $listData[$i + $j]['close']) $isValley = false;
}
if ($isPeak) {
$listPeaks[] = [
'price' => round($listData[$i]['close'], 0),
'year' => date('Y', strtotime($listData[$i]['date']))
];
}
if ($isValley) {
$listValleys[] = [
'price' => round($listData[$i]['close'], 0),
'year' => date('Y', strtotime($listData[$i]['date']))
];
}
}
// Deduplicate list
$uniquePeaks = [];
foreach ($listPeaks as $p) {
if (!isset($uniquePeaks[$p['price']])) {
$uniquePeaks[$p['price']] = $p;
}
}
$uniqueValleys = [];
foreach ($listValleys as $v) {
if (!isset($uniqueValleys[$v['price']])) {
$uniqueValleys[$v['price']] = $v;
}
}
$listPeaks = array_values($uniquePeaks);
$listValleys = array_values($uniqueValleys);
rsort($listPeaks);
sort($listValleys);
$listPeaks = array_slice($listPeaks, 0, 8);
$listValleys = array_slice($listValleys, 0, 8);
// Get all-time stats
$allPrices = array_filter(array_column($data, 'close'), function($p) { return $p > 1; });
$allTimeHigh = max($allPrices);
$allTimeLow = min($allPrices);
$athDate = '';
foreach ($data as $point) {
if ($point['close'] == $allTimeHigh) {
$athDate = $point['date'];
break;
}
}
?>
<!DOCTYPE html>
<html>
<head>
<title><?php echo $symbol; ?> Stock Analysis</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-annotation@3.0.1/dist/chartjs-plugin-annotation.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: linear-gradient(135deg, #0f0f23 0%, #1a1a2e 100%);
font-family: 'Segoe UI', Arial, sans-serif;
padding: 20px;
min-height: 100vh;
}
.container {
max-width: 1400px;
margin: 0 auto;
background: #0a0a15;
border-radius: 16px;
padding: 20px;
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
border: 1px solid rgba(255,255,255,0.1);
}
.header {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 10px;
}
.title {
font-size: 28px;
font-weight: bold;
color: #ffd700;
}
.current-price {
font-size: 36px;
font-weight: bold;
color: white;
}
.stats {
display: flex;
gap: 20px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.stat-card {
background: rgba(255,255,255,0.05);
padding: 12px 20px;
border-radius: 8px;
border-left: 3px solid;
}
.stat-card.high { border-left-color: #51cf66; }
.stat-card.low { border-left-color: #ff6b6b; }
.stat-label { font-size: 11px; color: #888; margin-bottom: 4px; }
.stat-value { font-size: 22px; font-weight: bold; }
.stat-card.high .stat-value { color: #51cf66; }
.stat-card.low .stat-value { color: #ff6b6b; }
.stat-date { font-size: 10px; color: #666; margin-top: 4px; }
/* Two column layout */
.two-columns {
display: flex;
gap: 20px;
margin: 20px 0;
flex-wrap: wrap;
}
.chart-column {
flex: 2;
min-width: 300px;
}
.list-column {
flex: 1;
min-width: 250px;
background: rgba(255,255,255,0.03);
border-radius: 12px;
padding: 15px;
}
.chart-container {
position: relative;
height: 450px;
}
.list-section {
margin-bottom: 20px;
}
.list-title {
font-size: 14px;
font-weight: bold;
text-align: center;
margin-bottom: 10px;
padding: 6px;
border-radius: 6px;
}
.list-title.peaks {
background: rgba(81, 207, 102, 0.15);
color: #51cf66;
}
.list-title.valleys {
background: rgba(255, 107, 107, 0.15);
color: #ff6b6b;
}
.list-item {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 11px;
margin: 6px 0;
padding: 6px 10px;
background: rgba(255,255,255,0.03);
border-radius: 6px;
}
.list-item.peaks { border-left: 3px solid #51cf66; }
.list-item.valleys { border-left: 3px solid #ff6b6b; }
.item-price {
font-weight: bold;
font-size: 13px;
}
.list-item.peaks .item-price { color: #51cf66; }
.list-item.valleys .item-price { color: #ff6b6b; }
.item-year {
color: #ffd700;
font-size: 10px;
background: rgba(255,215,0,0.1);
padding: 2px 6px;
border-radius: 10px;
}
.item-range {
color: #666;
font-size: 9px;
}
.range-down { color: #51cf66; }
.range-up { color: #ff6b6b; }
.legend {
display: flex;
gap: 20px;
justify-content: center;
margin-top: 15px;
font-size: 11px;
flex-wrap: wrap;
}
.legend-item {
display: flex;
align-items: center;
gap: 6px;
}
.legend-color {
width: 12px;
height: 12px;
border-radius: 2px;
}
.legend-color.peak { background: #51cf66; }
.legend-color.valley { background: #ff6b6b; }
.legend-color.zone-peak { background: rgba(81, 207, 102, 0.3); width: 20px; }
.legend-color.zone-valley { background: rgba(255, 107, 107, 0.3); width: 20px; }
.note {
text-align: center;
font-size: 10px;
color: #555;
margin-top: 15px;
padding-top: 10px;
border-top: 1px solid rgba(255,255,255,0.1);
}
.compact-link {
text-align: right;
margin-bottom: 10px;
}
.compact-link a {
color: #ffd700;
font-size: 11px;
text-decoration: none;
background: rgba(255,215,0,0.1);
padding: 4px 10px;
border-radius: 4px;
}
.compact-link a:hover {
background: rgba(255,215,0,0.2);
}
</style>
</head>
<body>
<div class="container">
<div class="compact-link">
<a href="?symbol=<?php echo $symbol; ?>&compact" target="_blank">📱 Open Compact Widget</a>
</div>
<div class="header">
<div class="title">📊 <?php echo $symbol; ?></div>
<div class="current-price">$<?php echo number_format($currentPrice, 2); ?></div>
</div>
<div class="stats">
<div class="stat-card high">
<div class="stat-label">ALL-TIME HIGH</div>
<div class="stat-value">$<?php echo number_format($allTimeHigh, 2); ?></div>
<div class="stat-date"><?php echo $athDate; ?></div>
</div>
<div class="stat-card low">
<div class="stat-label">ALL-TIME LOW</div>
<div class="stat-value">$<?php echo number_format($allTimeLow, 2); ?></div>
<div class="stat-date">Since inception</div>
</div>
<div class="stat-card">
<div class="stat-label">DRAWDOWN FROM ATH</div>
<div class="stat-value" style="color: #ff6b6b;"><?php echo number_format((($allTimeHigh - $currentPrice) / $allTimeHigh) * 100, 1); ?>%</div>
</div>
</div>
<div class="two-columns">
<!-- Chart Column -->
<div class="chart-column">
<div class="chart-container">
<canvas id="stockChart"></canvas>
</div>
</div>
<!-- List Column -->
<div class="list-column">
<div class="list-section">
<div class="list-title peaks">🟢 PEAKS (Resistance) ±10%</div>
<?php foreach ($listPeaks as $peak): ?>
<div class="list-item peaks">
<span class="item-price">$<?php echo number_format($peak['price'], 0); ?></span>
<span class="item-year"><?php echo $peak['year']; ?></span>
<span class="item-range">
<span class="range-down">↓$<?php echo number_format($peak['price'] * 0.9, 0); ?></span>
<span class="range-up"> ↑$<?php echo number_format($peak['price'] * 1.1, 0); ?></span>
</span>
</div>
<?php endforeach; ?>
</div>
<div class="list-section">
<div class="list-title valleys">🔴 VALLEYS (Support) ±10%</div>
<?php foreach ($listValleys as $valley): ?>
<div class="list-item valleys">
<span class="item-price">$<?php echo number_format($valley['price'], 0); ?></span>
<span class="item-year"><?php echo $valley['year']; ?></span>
<span class="item-range">
<span class="range-down">↓$<?php echo number_format($valley['price'] * 0.9, 0); ?></span>
<span class="range-up"> ↑$<?php echo number_format($valley['price'] * 1.1, 0); ?></span>
</span>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<div class="legend">
<div class="legend-item"><div class="legend-color" style="background: #4caf50;"></div><span>Price Line</span></div>
<div class="legend-item"><div class="legend-color peak"></div><span>🟢 Peaks (Resistance)</span></div>
<div class="legend-item"><div class="legend-color valley"></div><span>🔴 Valleys (Support)</span></div>
<div class="legend-item"><div class="legend-color zone-peak"></div><span>🟢 Peak Zone (±5%)</span></div>
<div class="legend-item"><div class="legend-color zone-valley"></div><span>🔴 Valley Zone (±5%)</span></div>
</div>
<div class="note">
📅 Chart: Last 2 years | List: Last 5 years | 🟢 Green = Peaks (Resistance) with translucent zone | 🔴 Red = Valleys (Support) with translucent zone | Hover chart for details
</div>
</div>
<script>
const ctx = document.getElementById('stockChart').getContext('2d');
const dates = <?php echo json_encode($dates); ?>;
const prices = <?php echo json_encode($prices); ?>;
const peaks = <?php echo json_encode($peaks); ?>;
const valleys = <?php echo json_encode($valleys); ?>;
const peakZones = <?php echo json_encode($peakZones); ?>;
const valleyZones = <?php echo json_encode($valleyZones); ?>;
new Chart(ctx, {
type: 'line',
data: {
labels: dates,
datasets: [
{
label: '<?php echo $symbol; ?> Price',
data: prices,
borderColor: '#4caf50',
backgroundColor: 'rgba(76, 175, 80, 0.05)',
borderWidth: 2,
fill: true,
tension: 0.1,
pointRadius: 0,
pointHoverRadius: 5
},
{
label: 'Peaks (Resistance) 🟢',
data: peaks.map(p => ({ x: dates[p.x], y: p.y })),
type: 'scatter',
backgroundColor: '#51cf66',
borderColor: '#51cf66',
pointRadius: 6,
pointHoverRadius: 10,
pointBorderWidth: 2,
pointBorderColor: 'white',
showLine: false,
order: 1
},
{
label: 'Valleys (Support) 🔴',
data: valleys.map(v => ({ x: dates[v.x], y: v.y })),
type: 'scatter',
backgroundColor: '#ff6b6b',
borderColor: '#ff6b6b',
pointRadius: 6,
pointHoverRadius: 10,
pointBorderWidth: 2,
pointBorderColor: 'white',
showLine: false,
order: 1
}
]
},
options: {
responsive: true,
maintainAspectRatio: true,
plugins: {
tooltip: {
callbacks: {
label: function(context) {
if (context.dataset.label === 'Peaks (Resistance) 🟢') {
return `🟢 Peak (Resistance): $${context.parsed.y}`;
}
if (context.dataset.label === 'Valleys (Support) 🔴') {
return `🔴 Valley (Support): $${context.parsed.y}`;
}
return `${context.dataset.label}: $${context.parsed.y}`;
}
}
},
legend: {
position: 'top',
labels: { color: '#ccc', font: { size: 11 } }
},
annotation: {
annotations: {
...Object.fromEntries(peakZones.map((zone, i) => [
`peakZone${i}`,
{
type: 'box',
yMin: zone.from,
yMax: zone.to,
backgroundColor: 'rgba(81, 207, 102, 0.15)',
borderWidth: 0
}
])),
...Object.fromEntries(valleyZones.map((zone, i) => [
`valleyZone${i}`,
{
type: 'box',
yMin: zone.from,
yMax: zone.to,
backgroundColor: 'rgba(255, 107, 107, 0.15)',
borderWidth: 0
}
]))
}
}
},
scales: {
x: {
ticks: { color: '#888', font: { size: 9 }, maxRotation: 45, minRotation: 45 },
grid: { color: 'rgba(255,255,255,0.05)' }
},
y: {
ticks: { color: '#ccc', font: { size: 10 }, callback: function(v) { return '$' + v; } },
grid: { color: 'rgba(255,255,255,0.05)' },
title: { display: true, text: 'Price (USD)', color: '#888', font: { size: 10 } }
}
},
interaction: { intersect: false, mode: 'index' }
}
});
</script>
</body>
</html>
<?php
}
function getYahooStockDataAllTime($symbol) {
$startDate = 0;
$endDate = time();
$url = "https://query1.finance.yahoo.com/v8/finance/chart/{$symbol}";
$url .= "?period1={$startDate}&period2={$endDate}&interval=1d";
$url .= "&events=capitalGains|div|split&includeAdjustedClose=false";
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
CURLOPT_HTTPHEADER => ['Accept: application/json'],
CURLOPT_TIMEOUT => 30
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
return ['error' => "HTTP Error"];
}
$data = json_decode($response, true);
if (!$data || !isset($data['chart']['result'][0])) {
return ['error' => "Invalid response"];
}
$result = $data['chart']['result'][0];
$timestamp = $result['timestamp'] ?? [];
$quote = $result['indicators']['quote'][0] ?? [];
$stockData = [];
for ($i = 0; $i < count($timestamp); $i++) {
if (isset($quote['close'][$i]) && $quote['close'][$i] !== null && $quote['close'][$i] > 1) {
$stockData[] = [
'date' => date('Y-m-d', $timestamp[$i]),
'close' => round($quote['close'][$i], 2)
];
}
}
return ['data' => $stockData];
}
?>