V1.15
This commit is contained in:
parent
ffa84da26a
commit
a116dc2b1f
122
api/alerts.php
122
api/alerts.php
@ -1,75 +1,77 @@
|
|||||||
<?php
|
<?php
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
require_once __DIR__ . '/../db/config.php';
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
require_once __DIR__ . '/analysis.php';
|
||||||
|
|
||||||
// --- Configuration ---
|
// Example: /api/alerts.php?symbol=BTCUSDT
|
||||||
$symbol = $_GET['symbol'] ?? 'BTCUSDT';
|
$symbol = $_GET['symbol'] ?? 'BTCUSDT';
|
||||||
$time_window_minutes = 30;
|
|
||||||
$crash_threshold = -20.0; // -20%
|
|
||||||
$pump_threshold = 100.0; // +100%
|
|
||||||
|
|
||||||
// --- Response Structure ---
|
function check_bearish_alerts($symbol) {
|
||||||
$response = [
|
$alerts = [];
|
||||||
'status' => 'Nominal',
|
|
||||||
'symbol' => $symbol,
|
|
||||||
'change_percent' => 0,
|
|
||||||
'details' => 'No significant price change detected.'
|
|
||||||
];
|
|
||||||
|
|
||||||
try {
|
// Fetch latest price
|
||||||
$pdo = db();
|
$pdo = db();
|
||||||
|
$stmt = $pdo->prepare("SELECT close FROM candlestick_data WHERE symbol = :symbol ORDER BY timestamp DESC LIMIT 1");
|
||||||
|
$stmt->bindParam(':symbol', $symbol, PDO::PARAM_STR);
|
||||||
|
$stmt->execute();
|
||||||
|
$current_price = $stmt->fetchColumn();
|
||||||
|
|
||||||
// 1. Get the most recent price
|
if (!$current_price) {
|
||||||
$stmt_latest = $pdo->prepare("SELECT price FROM price_history WHERE symbol = :symbol ORDER BY timestamp DESC LIMIT 1");
|
return [];
|
||||||
$stmt_latest->execute([':symbol' => $symbol]);
|
|
||||||
$latest_price_row = $stmt_latest->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if (!$latest_price_row) {
|
|
||||||
$response['details'] = 'No recent price data available for this symbol.';
|
|
||||||
echo json_encode($response);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
$latest_price = $latest_price_row['price'];
|
|
||||||
|
|
||||||
// 2. Get the oldest price from the 30-minute window
|
|
||||||
$stmt_oldest = $pdo->prepare(
|
|
||||||
"SELECT price FROM price_history
|
|
||||||
WHERE symbol = :symbol AND timestamp >= NOW() - INTERVAL :minutes MINUTE
|
|
||||||
ORDER BY timestamp ASC LIMIT 1"
|
|
||||||
);
|
|
||||||
$stmt_oldest->execute([':symbol' => $symbol, ':minutes' => $time_window_minutes]);
|
|
||||||
$oldest_price_row = $stmt_oldest->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if (!$oldest_price_row) {
|
|
||||||
$response['details'] = 'Not enough historical data in the last 30 minutes to calculate change.';
|
|
||||||
echo json_encode($response);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
$oldest_price = $oldest_price_row['price'];
|
|
||||||
|
|
||||||
// 3. Calculate percentage change
|
|
||||||
if ($oldest_price > 0) {
|
|
||||||
$change_percent = (($latest_price - $oldest_price) / $oldest_price) * 100;
|
|
||||||
$response['change_percent'] = round($change_percent, 2);
|
|
||||||
} else {
|
|
||||||
$change_percent = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Determine status
|
// Fetch analysis data
|
||||||
if ($change_percent <= $crash_threshold) {
|
$sma = calculate_sma($symbol, 20);
|
||||||
$response['status'] = 'Crash Alert';
|
$rsi = calculate_rsi($symbol, 14);
|
||||||
$response['details'] = "Price dropped by " . $response['change_percent'] . "% in the last $time_window_minutes minutes.";
|
$macd_data = calculate_macd($symbol);
|
||||||
} elseif ($change_percent >= $pump_threshold) {
|
$patterns = calculate_patterns($symbol);
|
||||||
$response['status'] = 'Pump Alert';
|
|
||||||
$response['details'] = "Price surged by " . $response['change_percent'] . "% in the last $time_window_minutes minutes.";
|
// Condition 1: Price vs. SMA
|
||||||
} else {
|
$price_below_sma = $current_price < $sma;
|
||||||
$response['details'] = "Price change of " . $response['change_percent'] . "% is within normal limits.";
|
|
||||||
|
// Condition 2: RSI indicates weakening momentum (e.g., dropping from overbought)
|
||||||
|
// For simplicity, we'll check if RSI is above a certain threshold and not rising.
|
||||||
|
// A more complex implementation would track RSI changes over time.
|
||||||
|
$rsi_weakening = $rsi > 50; // Simplified: RSI is in the upper range, potential for reversal
|
||||||
|
|
||||||
|
// Condition 3: MACD Bearish Crossover
|
||||||
|
$macd_bearish_crossover = false;
|
||||||
|
if ($macd_data && $macd_data['macd'] < $macd_data['signal']) {
|
||||||
|
// Check if it just crossed
|
||||||
|
// This requires historical data, for now, we check the current state
|
||||||
|
$macd_bearish_crossover = true; // Simplified: MACD is below signal line
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (PDOException $e) {
|
// Check original combo
|
||||||
http_response_code(500);
|
if ($price_below_sma && $rsi_weakening && $macd_bearish_crossover) {
|
||||||
$response['status'] = 'Error';
|
$alerts[] = [
|
||||||
$response['details'] = 'Database error: ' . $e->getMessage();
|
'type' => 'Strong Bearish Signal',
|
||||||
|
'indicator' => 'SMA, RSI, MACD Combination',
|
||||||
|
'message' => 'Price dropped below 20-period SMA, RSI is high, and MACD shows a bearish state.'
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
echo json_encode($response);
|
// Check for candlestick patterns
|
||||||
|
if (!empty($patterns)) {
|
||||||
|
foreach ($patterns as $pattern_name => $detected) {
|
||||||
|
if ($detected) {
|
||||||
|
$alerts[] = [
|
||||||
|
'type' => 'Strong Bearish Signal',
|
||||||
|
'indicator' => 'Candlestick Pattern',
|
||||||
|
'message' => 'Detected bearish pattern: ' . ucwords(str_replace('_', ' ', $pattern_name))
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $alerts;
|
||||||
|
}
|
||||||
|
|
||||||
|
$alerts = check_bearish_alerts($symbol);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'symbol' => $symbol,
|
||||||
|
'alerts' => $alerts,
|
||||||
|
'timestamp' => time()
|
||||||
|
]);
|
||||||
227
api/analysis.php
227
api/analysis.php
@ -3,10 +3,10 @@ header('Content-Type: application/json');
|
|||||||
|
|
||||||
require_once __DIR__ . '/../db/config.php';
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
|
||||||
// --- Parameters ---
|
// Example: /api/analysis.php?symbol=BTCUSDT&indicators=sma,rsi,macd,patterns&period=20
|
||||||
// Example: /api/analysis.php?symbol=BTCUSDT&indicator=sma&period=20
|
|
||||||
$symbol = $_GET['symbol'] ?? 'BTCUSDT';
|
$symbol = $_GET['symbol'] ?? 'BTCUSDT';
|
||||||
$indicator = $_GET['indicator'] ?? 'sma';
|
$indicators_str = $_GET['indicators'] ?? 'sma';
|
||||||
|
$indicators = array_map('trim', explode(',', strtolower($indicators_str)));
|
||||||
$period = (int)($_GET['period'] ?? 20);
|
$period = (int)($_GET['period'] ?? 20);
|
||||||
|
|
||||||
if ($period <= 0) {
|
if ($period <= 0) {
|
||||||
@ -16,45 +16,218 @@ if ($period <= 0) {
|
|||||||
|
|
||||||
function calculate_sma($symbol, $period) {
|
function calculate_sma($symbol, $period) {
|
||||||
$pdo = db();
|
$pdo = db();
|
||||||
|
$stmt = $pdo->prepare("SELECT close FROM candlestick_data WHERE symbol = :symbol ORDER BY timestamp DESC LIMIT :period");
|
||||||
// Fetch the last 'period' number of closing prices for the given symbol, most recent first.
|
|
||||||
$stmt = $pdo->prepare(
|
|
||||||
"SELECT close FROM candlestick_data
|
|
||||||
WHERE symbol = :symbol
|
|
||||||
ORDER BY timestamp DESC
|
|
||||||
LIMIT :period"
|
|
||||||
);
|
|
||||||
|
|
||||||
$stmt->bindParam(':symbol', $symbol, PDO::PARAM_STR);
|
$stmt->bindParam(':symbol', $symbol, PDO::PARAM_STR);
|
||||||
$stmt->bindParam(':period', $period, PDO::PARAM_INT);
|
$stmt->bindParam(':period', $period, PDO::PARAM_INT);
|
||||||
|
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
|
$closes = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
if (count($closes) < $period) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return array_sum($closes) / $period;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculate_rsi($symbol, $period = 14) {
|
||||||
|
$pdo = db();
|
||||||
|
$limit = $period + 1;
|
||||||
|
$stmt = $pdo->prepare("SELECT close FROM candlestick_data WHERE symbol = :symbol ORDER BY timestamp DESC LIMIT :limit");
|
||||||
|
$stmt->bindParam(':symbol', $symbol, PDO::PARAM_STR);
|
||||||
|
$stmt->bindParam(':limit', $limit, PDO::PARAM_INT);
|
||||||
|
$stmt->execute();
|
||||||
|
$closes = array_reverse($stmt->fetchAll(PDO::FETCH_COLUMN));
|
||||||
|
if (count($closes) < $limit) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$gains = 0;
|
||||||
|
$losses = 0;
|
||||||
|
for ($i = 1; $i < count($closes); $i++) {
|
||||||
|
$change = $closes[$i] - $closes[$i - 1];
|
||||||
|
if ($change > 0) {
|
||||||
|
$gains += $change;
|
||||||
|
} else {
|
||||||
|
$losses += abs($change);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($period == 0) return 100;
|
||||||
|
$avg_gain = $gains / $period;
|
||||||
|
$avg_loss = $losses / $period;
|
||||||
|
if ($avg_loss == 0) {
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
$rs = $avg_gain / $avg_loss;
|
||||||
|
return 100 - (100 / (1 + $rs));
|
||||||
|
}
|
||||||
|
|
||||||
|
function _calculate_ema_series($prices, $period) {
|
||||||
|
if (count($prices) < $period) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
$smoothing_factor = 2 / ($period + 1);
|
||||||
|
$emas = [];
|
||||||
|
$initial_prices = array_slice($prices, 0, $period);
|
||||||
|
$sma = array_sum($initial_prices) / count($initial_prices);
|
||||||
|
$emas[] = $sma;
|
||||||
|
$remaining_prices = array_slice($prices, $period);
|
||||||
|
$last_ema = $sma;
|
||||||
|
foreach ($remaining_prices as $price) {
|
||||||
|
$current_ema = ($price - $last_ema) * $smoothing_factor + $last_ema;
|
||||||
|
$emas[] = $current_ema;
|
||||||
|
$last_ema = $current_ema;
|
||||||
|
}
|
||||||
|
return $emas;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculate_macd($symbol, $fast_period = 12, $slow_period = 26, $signal_period = 9) {
|
||||||
|
$pdo = db();
|
||||||
|
$limit = $slow_period + $signal_period + 50; // Fetch extra data for stability
|
||||||
|
$stmt = $pdo->prepare("SELECT close FROM candlestick_data WHERE symbol = :symbol ORDER BY timestamp ASC LIMIT :limit");
|
||||||
|
$stmt->bindParam(':symbol', $symbol, PDO::PARAM_STR);
|
||||||
|
$stmt->bindParam(':limit', $limit, PDO::PARAM_INT);
|
||||||
|
$stmt->execute();
|
||||||
$closes = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
$closes = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
if (count($closes) < $period) {
|
if (count($closes) < $slow_period + $signal_period) {
|
||||||
return null; // Not enough data to calculate SMA
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$sum = array_sum($closes);
|
$ema_fast_all = _calculate_ema_series($closes, $fast_period);
|
||||||
$sma = $sum / $period;
|
$ema_slow_all = _calculate_ema_series($closes, $slow_period);
|
||||||
|
|
||||||
return $sma;
|
$ema_fast_aligned = array_slice($ema_fast_all, count($ema_fast_all) - count($ema_slow_all));
|
||||||
|
|
||||||
|
$macd_line_series = [];
|
||||||
|
for ($i = 0; $i < count($ema_slow_all); $i++) {
|
||||||
|
$macd_line_series[] = $ema_fast_aligned[$i] - $ema_slow_all[$i];
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = null;
|
if (count($macd_line_series) < $signal_period) {
|
||||||
if (strtolower($indicator) === 'sma') {
|
return null;
|
||||||
$result = calculate_sma($symbol, $period);
|
}
|
||||||
} else {
|
|
||||||
echo json_encode(['error' => 'Invalid indicator specified.']);
|
$signal_line_series = _calculate_ema_series($macd_line_series, $signal_period);
|
||||||
exit;
|
|
||||||
|
$macd_line = end($macd_line_series);
|
||||||
|
$signal_line = end($signal_line_series);
|
||||||
|
$histogram = $macd_line - $signal_line;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'macd' => $macd_line,
|
||||||
|
'signal' => $signal_line,
|
||||||
|
'histogram' => $histogram
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculate_patterns($symbol) {
|
||||||
|
$pdo = db();
|
||||||
|
// Fetch last 30 candles for pattern recognition
|
||||||
|
$stmt = $pdo->prepare("SELECT open, high, low, close, timestamp FROM candlestick_data WHERE symbol = :symbol ORDER BY timestamp DESC LIMIT 30");
|
||||||
|
$stmt->bindParam(':symbol', $symbol, PDO::PARAM_STR);
|
||||||
|
$stmt->execute();
|
||||||
|
$candles = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (count($candles) < 5) { // Need at least a few candles for any pattern
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$patterns = [];
|
||||||
|
$latest_candle = $candles[0];
|
||||||
|
$prev_candle = $candles[1];
|
||||||
|
|
||||||
|
// 1. Shooting Star
|
||||||
|
$body_size = abs($latest_candle['open'] - $latest_candle['close']);
|
||||||
|
$upper_wick = $latest_candle['high'] - max($latest_candle['open'], $latest_candle['close']);
|
||||||
|
$lower_wick = min($latest_candle['open'], $latest_candle['close']) - $latest_candle['low'];
|
||||||
|
if ($upper_wick > $body_size * 2 && $lower_wick < $body_size) {
|
||||||
|
$patterns['shooting_star'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Bearish Engulfing
|
||||||
|
if ($prev_candle['close'] > $prev_candle['open'] && // Previous candle is bullish
|
||||||
|
$latest_candle['open'] > $prev_candle['close'] && // Current candle opens above previous close
|
||||||
|
$latest_candle['close'] < $prev_candle['open']) { // Current candle closes below previous open
|
||||||
|
$patterns['bearish_engulfing'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Gravestone Doji
|
||||||
|
$is_doji = $body_size <= ($latest_candle['high'] - $latest_candle['low']) * 0.1;
|
||||||
|
if ($is_doji && $lower_wick < $body_size && $upper_wick > $body_size * 3) {
|
||||||
|
$patterns['gravestone_doji'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Evening Star (3-candle pattern)
|
||||||
|
if (count($candles) >= 3) {
|
||||||
|
$c1 = $candles[2]; // Large bullish candle
|
||||||
|
$c2 = $candles[1]; // Small body candle (star)
|
||||||
|
$c3 = $candles[0]; // Bearish candle
|
||||||
|
if (($c1['close'] > $c1['open']) && (abs($c1['close'] - $c1['open']) > ($c1['high'] - $c1['low']) * 0.7) && // c1 is strong bullish
|
||||||
|
($c2['open'] > $c1['close']) && (abs($c2['close'] - $c2['open']) < ($c2['high'] - $c2['low']) * 0.3) && // c2 is small body, gapped up
|
||||||
|
($c3['close'] < $c3['open']) && ($c3['open'] < $c2['open']) && ($c3['close'] < $c1['close'])) { // c3 is bearish, closes below midpoint of c1
|
||||||
|
$patterns['evening_star'] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Double Top (Structural)
|
||||||
|
// This is a more complex pattern requiring finding two distinct peaks.
|
||||||
|
// We'll look for two highs that are close in price, separated by a valley.
|
||||||
|
if(count($candles) >= 15) {
|
||||||
|
$recent_highs = array_map(function($c) { return $c['high']; }, array_slice($candles, 0, 15));
|
||||||
|
$max_high = max($recent_highs);
|
||||||
|
$peaks = [];
|
||||||
|
foreach($candles as $i => $c) {
|
||||||
|
if ($i > 1 && $i < count($candles) -1 && $c['high'] >= $max_high * 0.98) {
|
||||||
|
if($candles[$i-1]['high'] < $c['high'] && $candles[$i+1]['high'] < $c['high']) {
|
||||||
|
$peaks[] = ['index' => $i, 'price' => $c['high']];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($peaks) >= 2) {
|
||||||
|
// Check if the two most recent peaks form a double top
|
||||||
|
$peak1 = $peaks[1]; // More recent peak in time, but second in array
|
||||||
|
$peak2 = $peaks[0]; // Older peak
|
||||||
|
|
||||||
|
$price_diff = abs($peak1['price'] - $peak2['price']) / $peak2['price'];
|
||||||
|
$time_diff = $peak1['index'] - $peak2['index'];
|
||||||
|
|
||||||
|
// Peaks should be close in price, but not too close in time
|
||||||
|
if ($price_diff < 0.02 && $time_diff > 5) {
|
||||||
|
// Find the low point (valley) between the peaks
|
||||||
|
$valley_slice = array_slice($candles, $peak2['index'] + 1, $time_diff -1);
|
||||||
|
$valley_low = min(array_map(function($c){ return $c['low']; }, $valley_slice));
|
||||||
|
|
||||||
|
// Check if current price has broken below the valley low (neckline)
|
||||||
|
if($latest_candle['close'] < $valley_low) {
|
||||||
|
$patterns['double_top'] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $patterns;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$results = [];
|
||||||
|
foreach ($indicators as $indicator) {
|
||||||
|
switch ($indicator) {
|
||||||
|
case 'sma':
|
||||||
|
$results['sma'] = calculate_sma($symbol, $period);
|
||||||
|
break;
|
||||||
|
case 'rsi':
|
||||||
|
$results['rsi'] = calculate_rsi($symbol, 14);
|
||||||
|
break;
|
||||||
|
case 'macd':
|
||||||
|
$results['macd'] = calculate_macd($symbol);
|
||||||
|
break;
|
||||||
|
case 'patterns':
|
||||||
|
$results['patterns'] = calculate_patterns($symbol);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$response = [
|
$response = [
|
||||||
'symbol' => $symbol,
|
'symbol' => $symbol,
|
||||||
'indicator' => 'SMA',
|
'values' => $results,
|
||||||
'period' => $period,
|
|
||||||
'value' => $result,
|
|
||||||
'timestamp' => time()
|
'timestamp' => time()
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
const POLLING_INTERVAL = 3000; // 3 seconds
|
const POLLING_INTERVAL = 3000; // 3 seconds for price/analysis
|
||||||
|
const ALERT_POLLING_INTERVAL = 10000; // 10 seconds for alerts
|
||||||
|
|
||||||
const liveTickers = [
|
const liveTickers = [
|
||||||
{ symbol: 'BTCUSDT', elementId: 'live-crypto-row-btc', lastPrice: 0 },
|
{ symbol: 'BTCUSDT', elementId: 'live-crypto-row-btc', lastPrice: 0 },
|
||||||
@ -46,26 +47,32 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
|
|
||||||
async function fetchAnalysis(ticker, rowElement) {
|
async function fetchAnalysis(ticker, rowElement) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`api/analysis.php?symbol=${ticker.symbol}&period=20`);
|
const response = await fetch(`api/analysis.php?symbol=${ticker.symbol}&indicators=sma,rsi,macd&period=20`);
|
||||||
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
|
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data.error) throw new Error(`API Error: ${data.error}`);
|
if (data.error) throw new Error(`API Error: ${data.error}`);
|
||||||
updateAnalysis(data, rowElement, ticker.lastPrice);
|
updateAnalysisData(data, rowElement, ticker.lastPrice);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to fetch analysis for ${ticker.symbol}:`, error);
|
console.error(`Failed to fetch analysis for ${ticker.symbol}:`, error);
|
||||||
const smaCell = rowElement.querySelector('.sma');
|
rowElement.querySelector('.sma').textContent = 'Error';
|
||||||
if (smaCell) smaCell.textContent = 'Error';
|
rowElement.querySelector('.rsi').textContent = 'Error';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateAnalysis(data, rowElement, currentPrice) {
|
function updateAnalysisData(data, rowElement, currentPrice) {
|
||||||
const smaCell = rowElement.querySelector('.sma');
|
const smaCell = rowElement.querySelector('.sma');
|
||||||
const signalCell = rowElement.querySelector('.signal');
|
const signalCell = rowElement.querySelector('.signal');
|
||||||
if (!smaCell || !signalCell) return;
|
const rsiCell = rowElement.querySelector('.rsi');
|
||||||
|
const macdCell = rowElement.querySelector('.macd');
|
||||||
|
const macdSignalCell = rowElement.querySelector('.macd-signal');
|
||||||
|
if (!smaCell || !signalCell || !rsiCell || !macdCell || !macdSignalCell) return;
|
||||||
|
|
||||||
const sma = parseFloat(data.sma);
|
const sma = parseFloat(data.values.sma);
|
||||||
|
const rsi = parseFloat(data.values.rsi);
|
||||||
|
const macdData = data.values.macd;
|
||||||
|
|
||||||
|
if (!isNaN(sma)) {
|
||||||
smaCell.textContent = sma.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
smaCell.textContent = sma.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||||
|
|
||||||
if (currentPrice > sma) {
|
if (currentPrice > sma) {
|
||||||
signalCell.innerHTML = '<span class="badge bg-success-subtle text-success-emphasis">Above SMA</span>';
|
signalCell.innerHTML = '<span class="badge bg-success-subtle text-success-emphasis">Above SMA</span>';
|
||||||
} else if (currentPrice < sma) {
|
} else if (currentPrice < sma) {
|
||||||
@ -73,32 +80,69 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
} else {
|
} else {
|
||||||
signalCell.innerHTML = '<span class="badge bg-secondary-subtle text-secondary-emphasis">At SMA</span>';
|
signalCell.innerHTML = '<span class="badge bg-secondary-subtle text-secondary-emphasis">At SMA</span>';
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
smaCell.textContent = '-';
|
||||||
|
signalCell.textContent = '-';
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchAlert(ticker, rowElement) {
|
if (!isNaN(rsi)) {
|
||||||
|
rsiCell.textContent = rsi.toFixed(2);
|
||||||
|
} else {
|
||||||
|
rsiCell.textContent = '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (macdData && typeof macdData.macd !== 'undefined' && typeof macdData.signal !== 'undefined') {
|
||||||
|
const macd = parseFloat(macdData.macd);
|
||||||
|
const macdSignal = parseFloat(macdData.signal);
|
||||||
|
if (!isNaN(macd)) {
|
||||||
|
macdCell.textContent = macd.toFixed(4);
|
||||||
|
}
|
||||||
|
if (!isNaN(macdSignal)) {
|
||||||
|
macdSignalCell.textContent = macdSignal.toFixed(4);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
macdCell.textContent = '-';
|
||||||
|
macdSignalCell.textContent = '-';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchAndDisplayAlerts() {
|
||||||
|
const alertsContainer = document.getElementById('alerts-container');
|
||||||
|
if (!alertsContainer) return;
|
||||||
|
|
||||||
|
let allAlerts = [];
|
||||||
|
|
||||||
|
for (const ticker of liveTickers) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`api/alerts.php?symbol=${ticker.symbol}`);
|
const response = await fetch(`api/alerts.php?symbol=${ticker.symbol}`);
|
||||||
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
|
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
updateAlertStatus(data.status, rowElement);
|
if (data.alerts && data.alerts.length > 0) {
|
||||||
|
data.alerts.forEach(alert => {
|
||||||
|
allAlerts.push({ symbol: data.symbol, ...alert });
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to fetch alert for ${ticker.symbol}:`, error);
|
console.error(`Failed to fetch alerts for ${ticker.symbol}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateAlertStatus(status, rowElement) {
|
if (allAlerts.length > 0) {
|
||||||
const statusCell = rowElement.querySelector('.status');
|
alertsContainer.innerHTML = ''; // Clear previous alerts
|
||||||
if (!statusCell) return;
|
allAlerts.forEach(alert => {
|
||||||
|
const alertEl = document.createElement('div');
|
||||||
rowElement.classList.remove('flash-alert');
|
alertEl.className = 'alert alert-danger d-flex align-items-center';
|
||||||
statusCell.innerHTML = '-';
|
alertEl.innerHTML = `
|
||||||
|
<i class="bi bi-exclamation-triangle-fill me-3"></i>
|
||||||
if (status === 'Crash Alert') {
|
<div>
|
||||||
statusCell.innerHTML = '<span class="badge bg-danger-subtle text-danger-emphasis">Crash Alert</span>';
|
<strong class="d-block">${alert.type} on ${alert.symbol}</strong>
|
||||||
rowElement.classList.add('flash-alert');
|
${alert.message}
|
||||||
} else if (status === 'Pump Alert') {
|
</div>
|
||||||
statusCell.innerHTML = '<span class="badge bg-success-subtle text-success-emphasis">Pump Alert</span>';
|
`;
|
||||||
rowElement.classList.add('flash-alert');
|
alertsContainer.appendChild(alertEl);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
alertsContainer.innerHTML = '<p class="text-muted">No alerts triggered yet. The system is monitoring the market.</p>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,12 +158,15 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
fetchAndupdate(ticker, rowElement).then(() => {
|
fetchAndupdate(ticker, rowElement).then(() => {
|
||||||
fetchAnalysis(ticker, rowElement);
|
fetchAnalysis(ticker, rowElement);
|
||||||
});
|
});
|
||||||
fetchAlert(ticker, rowElement);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchAll();
|
fetchAll();
|
||||||
setInterval(fetchAll, POLLING_INTERVAL);
|
setInterval(fetchAll, POLLING_INTERVAL);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Setup alert polling
|
||||||
|
fetchAndDisplayAlerts();
|
||||||
|
setInterval(fetchAndDisplayAlerts, ALERT_POLLING_INTERVAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeLiveTickers();
|
initializeLiveTickers();
|
||||||
|
|||||||
19
index.php
19
index.php
@ -51,6 +51,9 @@
|
|||||||
<th scope="col">PRICE</th>
|
<th scope="col">PRICE</th>
|
||||||
<th scope="col">SMA (20m)</th>
|
<th scope="col">SMA (20m)</th>
|
||||||
<th scope="col">SIGNAL</th>
|
<th scope="col">SIGNAL</th>
|
||||||
|
<th scope="col">RSI (14m)</th>
|
||||||
|
<th scope="col">MACD</th>
|
||||||
|
<th scope="col">MACD Signal</th>
|
||||||
<th scope="col" class="text-end">STATUS</th>
|
<th scope="col" class="text-end">STATUS</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -67,6 +70,9 @@
|
|||||||
<td class="fw-bold fs-5 price">$0.00</td>
|
<td class="fw-bold fs-5 price">$0.00</td>
|
||||||
<td class="fw-medium sma">-</td>
|
<td class="fw-medium sma">-</td>
|
||||||
<td class="fw-medium signal">-</td>
|
<td class="fw-medium signal">-</td>
|
||||||
|
<td class="fw-medium rsi">-</td>
|
||||||
|
<td class="fw-medium macd">-</td>
|
||||||
|
<td class="fw-medium macd-signal">-</td>
|
||||||
<td class="text-end status">-</td>
|
<td class="text-end status">-</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
@ -82,6 +88,9 @@
|
|||||||
<td class="fw-bold fs-5 price">$0.00</td>
|
<td class="fw-bold fs-5 price">$0.00</td>
|
||||||
<td class="fw-medium sma">-</td>
|
<td class="fw-medium sma">-</td>
|
||||||
<td class="fw-medium signal">-</td>
|
<td class="fw-medium signal">-</td>
|
||||||
|
<td class="fw-medium rsi">-</td>
|
||||||
|
<td class="fw-medium macd">-</td>
|
||||||
|
<td class="fw-medium macd-signal">-</td>
|
||||||
<td class="text-end status">-</td>
|
<td class="text-end status">-</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -89,6 +98,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card mt-4">
|
||||||
|
<div class="card-header bg-white border-bottom-0 pt-4 pb-0">
|
||||||
|
<h4 class="card-title fw-bold">Triggered Alerts</h4>
|
||||||
|
</div>
|
||||||
|
<div class="card-body" id="alerts-container">
|
||||||
|
<p class="text-muted">No alerts triggered yet. The system is monitoring the market.</p>
|
||||||
|
<!-- Alerts will be dynamically inserted here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer class="text-center text-muted py-4">
|
<footer class="text-center text-muted py-4">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user