"$errstr in $errfile on line $errline"]); exit; } set_error_handler("errorHandler"); define('SYMBOL_RE', '[A-Z]{1,6}(?:-[A-Z]{1,3})?'); $ajaxMode = $_GET['ajax'] ?? ''; $cacheFile = __DIR__ . '/symbols_cache.json'; $notAStockFile = __DIR__ . '/notastock2.json'; $stocksFile = __DIR__ . '/stocks.json'; $progressFile = __DIR__ . '/progress.json'; $foundFile = __DIR__ . '/found_prebreakout.json'; if ($ajaxMode === 'start') { header('Content-Type: application/json'); $maxPrice = isset($_GET['maxPrice']) ? (float)$_GET['maxPrice'] : 4.0; file_put_contents($progressFile, json_encode([ 'status' => 'starting', 'checked' => 0, 'validFound' => 0, 'notReady' => 0, 'invalid' => 0, 'currentSymbol' => '', 'done' => false, 'result' => null, 'maxPrice' => $maxPrice, 'started' => time() ])); echo json_encode(['ok' => true]); exit; } if ($ajaxMode === 'poll') { header('Content-Type: application/json'); if (!file_exists($progressFile)) { echo json_encode(['error' => 'Progress file not found']); exit; } readfile($progressFile); exit; } if ($ajaxMode === 'run') { ignore_user_abort(true); set_time_limit(0); header('Content-Type: text/plain'); echo "0\n"; ob_flush(); flush(); runPreBreakoutSearch($progressFile); exit; } if ($ajaxMode === 'foundlist') { header('Content-Type: application/json'); if (!file_exists($foundFile)) { echo json_encode(['found' => []]); exit; } $data = json_decode(file_get_contents($foundFile), true); echo json_encode(['found' => $data ?: []]); exit; } if ($ajaxMode === 'clearfound') { header('Content-Type: application/json'); if (file_exists($foundFile)) unlink($foundFile); echo json_encode(['ok' => true]); exit; } ?> Pre-Breakout Scanner

Pre-Breakout Scanner

Finding stocks coiling near resistance with rising volume
Ready to scan
0
Checked
0
Valid
0
Not Ready
0
Invalid
Status: Idle
Current:

Found Stocks 0

No stocks found yet. Start scanning to discover pre-breakout candidates.
$v) $excludeSet[$ticker] = 1; $progress = json_decode(file_get_contents($progressFile), true); $maxPrice = isset($progress['maxPrice']) ? (float)$progress['maxPrice'] : 4.0; $validSymbol = null; $validTicker = null; $validPrice = null; $validVolRatio = null; $validSparkline = []; $checkedSymbols = []; $invalidFound = []; $notReadySymbols = []; $validFound = 0; $maxTotal = 3000; $batchSize = 40; $sampleSize = 200; $invalidTypes = ['ETF','MUTUALFUND','INDEX','CRYPTOCURRENCY','FUTURE','OPTION','BOND','PENNY_STOCK','PREFERRED_STOCK','REIT','UNIT','RIGHT','WARRANT','STRUCTURED','CURRENCY']; writeProgress($progressFile, [ 'status' => 'searching', 'checked' => 0, 'validFound' => 0, 'notReady' => 0, 'invalid' => 0, 'currentSymbol' => '', 'done' => false, 'result' => null ]); while (!$validSymbol && count($checkedSymbols) < $maxTotal) { $candidates = streamSampleSymbolsFast($cacheFile, $excludeSet, $sampleSize); if (empty($candidates)) { $candidates = streamSampleSymbolsFast($cacheFile, [], $sampleSize); if (empty($candidates)) break; } $offset = 0; while ($offset < count($candidates) && !$validSymbol) { $batch = array_slice($candidates, $offset, $batchSize); $offset += $batchSize; $batch = array_filter($batch, function($symbol) use ($checkedSymbols) { return !in_array($symbol, $checkedSymbols); }); if (empty($batch)) continue; array_push($checkedSymbols, ...$batch); writeProgress($progressFile, [ 'checked' => count($checkedSymbols), 'currentSymbol' => $batch[0] ?? '' ]); $mh = curl_multi_init(); $handles = []; foreach ($batch as $symbol) { $ch = curl_init("https://query1.finance.yahoo.com/v1/finance/search?q=" . urlencode($symbol) . ""esCount=1&newsCount=0"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CONNECTTIMEOUT => 3, CURLOPT_TIMEOUT => 10, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_USERAGENT => 'Mozilla/5.0', CURLOPT_FOLLOWLOCATION => true ]); curl_multi_add_handle($mh, $ch); $handles[$symbol] = $ch; } do { curl_multi_exec($mh, $running); if ($running > 0) curl_multi_select($mh, 0.05); } while ($running > 0); $validBatch = []; $newInvalid = []; foreach ($handles as $symbol => $ch) { $body = curl_multi_getcontent($ch); curl_multi_remove_handle($mh, $ch); curl_close($ch); $data = json_decode($body, true); $quote = $data['quotes'][0] ?? null; $quoteType = $quote['quoteType'] ?? ''; if (!$quote || $quoteType !== 'EQUITY' || in_array($quoteType, $invalidTypes)) { $newInvalid[] = $symbol; $invalidFound[] = $symbol; continue; } $ticker = $quote['symbol'] ?? null; if ($ticker) $validBatch[$symbol] = $ticker; } curl_multi_close($mh); if (!empty($newInvalid)) { appendToNotAStock($notAStockFile, $newInvalid); foreach ($newInvalid as $symbol) $excludeSet[$symbol] = 1; } writeProgress($progressFile, ['invalid' => count($invalidFound)]); if (empty($validBatch)) continue; $validFound += count($validBatch); writeProgress($progressFile, ['validFound' => $validFound]); $mh2 = curl_multi_init(); $chartHandles = []; foreach ($validBatch as $symbol => $ticker) { $ch = curl_init("https://query1.finance.yahoo.com/v8/finance/chart/{$ticker}?interval=1d&range=3mo"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CONNECTTIMEOUT => 3, CURLOPT_TIMEOUT => 12, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_USERAGENT => 'Mozilla/5.0', CURLOPT_FOLLOWLOCATION => true ]); curl_multi_add_handle($mh2, $ch); $chartHandles[$symbol] = ['ch' => $ch, 'ticker' => $ticker]; } do { curl_multi_exec($mh2, $running2); if ($running2 > 0) curl_multi_select($mh2, 0.05); } while ($running2 > 0); foreach ($chartHandles as $symbol => $info) { $body = curl_multi_getcontent($info['ch']); curl_multi_remove_handle($mh2, $info['ch']); curl_close($info['ch']); if (!$validSymbol) { $result = hasPreBreakoutFast($body, $maxPrice); if ($result) { $validSymbol = $symbol; $validTicker = $info['ticker']; $validPrice = $result['price']; $validVolRatio = $result['volRatio']; $validSparkline = $result['sparkline']; // Save to found file $foundEntry = [ 'ticker' => $validTicker, 'price' => $validPrice, 'volRatio' => round($validVolRatio, 2), 'sparklinePrices' => $validSparkline, 'foundAt' => time() ]; $foundData = []; if (file_exists($foundFile)) { $foundData = json_decode(file_get_contents($foundFile), true) ?: []; } $foundData[] = $foundEntry; file_put_contents($foundFile, json_encode($foundData), LOCK_EX); writeProgress($progressFile, [ 'status' => 'found', 'result' => [ 'ticker' => $validTicker, 'price' => $validPrice, 'volRatio' => round($validVolRatio, 2), 'sparklinePrices' => $validSparkline ] ]); curl_multi_close($mh2); break 3; } else { $notReadySymbols[] = $symbol; } } else { $notReadySymbols[] = $symbol; } } curl_multi_close($mh2); writeProgress($progressFile, ['notReady' => count($notReadySymbols)]); } unset($candidates); } if (!$validSymbol) { writeProgress($progressFile, [ 'done' => true, 'status' => 'notfound', 'message' => "No pre-breakout stock found after checking " . count($checkedSymbols) . " candidates. " . count($notReadySymbols) . " valid symbols were not in pre-breakout setup." ]); } } function writeProgress($file, array $updates) { $data = []; if (file_exists($file)) $data = json_decode(file_get_contents($file), true) ?: []; $data = array_merge($data, $updates); file_put_contents($file, json_encode($data), LOCK_EX); } function loadExcludeSet($stocksFile, $notAStockFile) { $excludeSet = []; if (file_exists($stocksFile)) { $fh = fopen($stocksFile, 'r'); $buf = ''; while (!feof($fh)) { $buf .= fread($fh, 8192); preg_match_all('/"symbol"\s*:\s*"(' . SYMBOL_RE . ')"/', $buf, $m); foreach ($m[1] as $symbol) $excludeSet[$symbol] = 1; $buf = substr($buf, -50); } fclose($fh); } if (file_exists($notAStockFile)) { $fh = fopen($notAStockFile, 'r'); $buf = ''; while (!feof($fh)) { $buf .= fread($fh, 8192); preg_match_all('/(?:[\[,])\s*"(' . SYMBOL_RE . ')"\s*(?:,|\])/', $buf, $m); foreach ($m[1] as $symbol) $excludeSet[$symbol] = 1; $buf = substr($buf, -60); } fclose($fh); } return $excludeSet; } function streamSampleSymbolsFast($cacheFile, array $excludeSet, $sampleSize = 200) { if (!file_exists($cacheFile)) return []; $exclude = $excludeSet; $fh = fopen($cacheFile, 'r'); if (!$fh) return []; $reservoir = []; $count = 0; $buf = ''; while (!feof($fh)) { $buf .= fread($fh, 8192); preg_match_all('/(?:[\[,])\s*"(' . SYMBOL_RE . ')"\s*(?:,|\])/', $buf, $matches); foreach ($matches[1] as $symbol) { if (isset($exclude[$symbol])) continue; $count++; if (count($reservoir) < $sampleSize) { $reservoir[] = $symbol; } else { $j = random_int(0, $count - 1); if ($j < $sampleSize) $reservoir[$j] = $symbol; } } $buf = substr($buf, -60); } fclose($fh); return $reservoir; } function hasPreBreakoutFast($responseBody, $maxPrice = 4.0) { if (!$responseBody) return false; $data = json_decode($responseBody, true); $result = $data['chart']['result'][0] ?? null; if (!$result) return false; $closes = $result['indicators']['quote'][0]['close'] ?? []; $volumes = $result['indicators']['quote'][0]['volume'] ?? []; $prices = []; $vols = []; foreach ($closes as $i => $c) { if (isset($c) && $c > 0) { $prices[] = $c; $vols[] = isset($volumes[$i]) ? (int)$volumes[$i] : 0; } } $n = count($prices); if ($n < 40) return false; $currentPrice = $prices[$n - 1]; // Max price filter if ($currentPrice > $maxPrice) return false; // 1. NOT already gone off: no +50% in 3 months $totalReturn = (($currentPrice - $prices[0]) / $prices[0]) * 100; if ($totalReturn > 50) return false; // 2. NOT recently spiked: no +15% in last 7 days $last7 = array_slice($prices, -7); $before7 = array_slice($prices, 0, -7); if (!empty($before7)) { $avgBefore7 = array_sum($before7) / count($before7); $maxLast7 = max($last7); $spikePct = (($maxLast7 - $avgBefore7) / $avgBefore7) * 100; if ($spikePct > 15) return false; } // 3. Volume building in recent window $recentWindow = min(5, $n); $quietWindow = $n - $recentWindow; if ($quietWindow < 10) return false; $recentVols = array_slice($vols, -$recentWindow); $quietVols = array_slice($vols, 0, $quietWindow); $recentAvgVol = array_sum($recentVols) / count($recentVols); $quietAvgVol = array_sum($quietVols) / count($quietVols); $volRatio = $quietAvgVol > 0 ? ($recentAvgVol / $quietAvgVol) : 0; if ($volRatio < 2.0) return false; // 4. Price near recent highs (pressing resistance) $recentPrices = array_slice($prices, -$recentWindow); $recentHigh = max($recentPrices); $recentLow = min($recentPrices); $distanceFromHigh = $recentHigh > 0 ? (($recentHigh - $currentPrice) / $recentHigh) * 100 : 100; if ($distanceFromHigh > 5) return false; // 5. Tight consolidation $recentRangePct = $recentLow > 0 ? (($recentHigh - $recentLow) / $recentLow) * 100 : 100; if ($recentRangePct > 12) return false; // 6. Slight upward tilt (accumulation, not distribution) $quietPrices = array_slice($prices, 0, $quietWindow); $quietMid = (int)(count($quietPrices) / 2); $quietFirstHalf = array_slice($quietPrices, 0, $quietMid); $quietSecondHalf = array_slice($quietPrices, $quietMid); $avgFirst = array_sum($quietFirstHalf) / max(1, count($quietFirstHalf)); $avgSecond = array_sum($quietSecondHalf) / max(1, count($quietSecondHalf)); if ($avgFirst > 0 && (($avgSecond - $avgFirst) / $avgFirst) * 100 < -5) return false; // 7. Recent days trending up $last3 = array_slice($prices, -3); if (count($last3) >= 3) { $trendingUp = ($last3[2] > $last3[0]); if (!$trendingUp) return false; } // 8. Minimum volume if ($recentAvgVol < 50000) return false; // 9. Price floor if ($currentPrice < 0.5) return false; // Build sparkline data (last 30 days) $sparklinePrices = array_slice($prices, -30); return [ 'price' => $currentPrice, 'volRatio' => $volRatio, 'sparkline' => $sparklinePrices ]; } function appendToNotAStock($file, array $newSymbols) { if (empty($newSymbols)) return; if (!file_exists($file) || filesize($file) < 3) { $fh = fopen($file, 'w'); fwrite($fh, '["' . implode('","', $newSymbols) . '"]'); fclose($fh); return; } $fh = fopen($file, 'r+'); if (!$fh) return; $size = filesize($file); $pos = $size - 1; while ($pos >= 0) { fseek($fh, $pos); $c = fread($fh, 1); if ($c === ']') break; $pos--; } fseek($fh, $pos); fwrite($fh, ',"' . implode('","', $newSymbols) . '"]'); fclose($fh); } function updateAllSymbolsCache() { $cacheFile = __DIR__ . '/symbols_cache.json'; $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => 'https://www.sec.gov/files/company_tickers.json', CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_USERAGENT => 'StockScanner research-tool contact@example.com', CURLOPT_TIMEOUT => 30 ]); $json = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode !== 200 || !$json) return false; $data = json_decode($json, true); if (!is_array($data)) return false; $seen = []; $symbols = []; foreach ($data as $row) { $symbol = strtoupper(trim($row['ticker'] ?? '')); if ($symbol === '' || !preg_match('/^' . SYMBOL_RE . '$/', $symbol)) continue; if (isset($seen[$symbol])) continue; $seen[$symbol] = 1; $symbols[] = $symbol; } $total = count($symbols); $outFh = fopen($cacheFile, 'w'); fwrite($outFh, '{"timestamp":' . time() . ',"count":' . $total . ',"symbols":['); foreach ($symbols as $i => $s) { fwrite($outFh, ($i === 0 ? '' : ',') . '"' . $s . '"'); } fwrite($outFh, ']}'); fclose($outFh); return true; }