V.1.14
This commit is contained in:
parent
a824cabd7e
commit
ffa84da26a
61
api/analysis.php
Normal file
61
api/analysis.php
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
|
||||||
|
// --- Parameters ---
|
||||||
|
// Example: /api/analysis.php?symbol=BTCUSDT&indicator=sma&period=20
|
||||||
|
$symbol = $_GET['symbol'] ?? 'BTCUSDT';
|
||||||
|
$indicator = $_GET['indicator'] ?? 'sma';
|
||||||
|
$period = (int)($_GET['period'] ?? 20);
|
||||||
|
|
||||||
|
if ($period <= 0) {
|
||||||
|
echo json_encode(['error' => 'Period must be a positive integer.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculate_sma($symbol, $period) {
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
// 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(':period', $period, PDO::PARAM_INT);
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$closes = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
if (count($closes) < $period) {
|
||||||
|
return null; // Not enough data to calculate SMA
|
||||||
|
}
|
||||||
|
|
||||||
|
$sum = array_sum($closes);
|
||||||
|
$sma = $sum / $period;
|
||||||
|
|
||||||
|
return $sma;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = null;
|
||||||
|
if (strtolower($indicator) === 'sma') {
|
||||||
|
$result = calculate_sma($symbol, $period);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['error' => 'Invalid indicator specified.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = [
|
||||||
|
'symbol' => $symbol,
|
||||||
|
'indicator' => 'SMA',
|
||||||
|
'period' => $period,
|
||||||
|
'value' => $result,
|
||||||
|
'timestamp' => time()
|
||||||
|
];
|
||||||
|
|
||||||
|
echo json_encode($response);
|
||||||
160
api/ticker.php
160
api/ticker.php
@ -1,85 +1,109 @@
|
|||||||
<?php
|
<?php
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
|
||||||
// --- Configuration ---
|
// --- Configuration ---
|
||||||
// Default to BTCUSDT if no symbol is provided in the URL query string.
|
$symbols = ['BTCUSDT', 'ETHUSDT'];
|
||||||
$symbol = isset($_GET['symbol']) ? strtoupper($_GET['symbol']) : 'BTCUSDT';
|
$exchange = 'Binance';
|
||||||
$api_url = "https://api.bitget.com/api/v2/spot/market/tickers?symbol=" . $symbol;
|
$interval = '1m';
|
||||||
|
|
||||||
// --- cURL Execution ---
|
// --- Data Fetching ---
|
||||||
$ch = curl_init();
|
function fetch_candlestick_data($symbol, $interval) {
|
||||||
curl_setopt_array($ch, [
|
$api_url = sprintf(
|
||||||
CURLOPT_URL => $api_url,
|
"https://api.binance.com/api/v3/klines?symbol=%s&interval=%s&limit=5",
|
||||||
CURLOPT_RETURNTRANSFER => true,
|
$symbol, $interval
|
||||||
CURLOPT_TIMEOUT => 10,
|
);
|
||||||
CURLOPT_HTTPHEADER => [
|
|
||||||
'Accept: application/json'
|
|
||||||
],
|
|
||||||
// It's good practice to set a user-agent
|
|
||||||
CURLOPT_USERAGENT => 'FlatlogicMarketDetector/1.0'
|
|
||||||
]);
|
|
||||||
|
|
||||||
$response = curl_exec($ch);
|
$ch = curl_init();
|
||||||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
curl_setopt_array($ch, [
|
||||||
$error = curl_error($ch);
|
CURLOPT_URL => $api_url,
|
||||||
curl_close($ch);
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TIMEOUT => 10,
|
||||||
|
CURLOPT_USERAGENT => 'FlatlogicMarketDetector/1.0'
|
||||||
|
]);
|
||||||
|
|
||||||
// --- Response Handling ---
|
$response = curl_exec($ch);
|
||||||
if ($error) {
|
curl_close($ch);
|
||||||
http_response_code(500);
|
return json_decode($response, true);
|
||||||
echo json_encode(['error' => 'cURL Error: ' . $error]);
|
|
||||||
exit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($http_code !== 200) {
|
// --- Database Logging ---
|
||||||
http_response_code($http_code);
|
function log_candlestick_data($pdo, $symbol, $exchange, $interval, $kline) {
|
||||||
echo json_encode(['error' => 'Bitget API returned non-200 status', 'details' => json_decode($response)]);
|
$sql = <<<SQL
|
||||||
exit;
|
INSERT INTO candlestick_data (symbol, exchange, interval_time, open_time, open_price, high_price, low_price, close_price, volume, close_time, quote_asset_volume, number_of_trades, taker_buy_base_asset_volume, taker_buy_quote_asset_volume)
|
||||||
|
VALUES (:symbol, :exchange, :interval_time, :open_time, :open_price, :high_price, :low_price, :close_price, :volume, :close_time, :quote_asset_volume, :number_of_trades, :taker_buy_base_asset_volume, :taker_buy_quote_asset_volume)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
close_price = VALUES(close_price), high_price = VALUES(high_price), low_price = VALUES(low_price), volume = VALUES(volume);
|
||||||
|
SQL;
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
':symbol' => $symbol,
|
||||||
|
':exchange' => $exchange,
|
||||||
|
':interval_time' => $interval,
|
||||||
|
':open_time' => $kline[0],
|
||||||
|
':open_price' => $kline[1],
|
||||||
|
':high_price' => $kline[2],
|
||||||
|
':low_price' => $kline[3],
|
||||||
|
':close_price' => $kline[4],
|
||||||
|
':volume' => $kline[5],
|
||||||
|
':close_time' => $kline[6],
|
||||||
|
':quote_asset_volume' => $kline[7],
|
||||||
|
':number_of_trades' => $kline[8],
|
||||||
|
':taker_buy_base_asset_volume' => $kline[9],
|
||||||
|
':taker_buy_quote_asset_volume' => $kline[10],
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = json_decode($response, true);
|
// --- Main Execution ---
|
||||||
|
$latest_tickers = [];
|
||||||
|
|
||||||
// --- Data Validation & Output ---
|
|
||||||
if (json_last_error() !== JSON_ERROR_NONE || empty($data['data'])) {
|
|
||||||
http_response_code(500);
|
|
||||||
echo json_encode(['error' => 'Failed to parse JSON response or data is empty']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the first ticker object from the response data array
|
|
||||||
$ticker_data = $data['data'][0] ?? null;
|
|
||||||
|
|
||||||
if (!$ticker_data) {
|
|
||||||
http_response_code(404);
|
|
||||||
echo json_encode(['error' => 'Ticker data for symbol not found in API response']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Log to Database ---
|
|
||||||
try {
|
try {
|
||||||
require_once __DIR__ . '/../db/config.php';
|
$pdo = db();
|
||||||
$price_to_log = $ticker_data['lastPr'] ?? null;
|
foreach ($symbols as $symbol) {
|
||||||
if ($price_to_log) {
|
$klines = fetch_candlestick_data($symbol, $interval);
|
||||||
$pdo = db();
|
|
||||||
$stmt = $pdo->prepare(
|
if (empty($klines) || !is_array($klines)) {
|
||||||
"INSERT INTO price_history (symbol, price) VALUES (:symbol, :price)"
|
continue; // Skip this symbol if data fetching fails
|
||||||
);
|
}
|
||||||
$stmt->execute(['symbol' => $symbol, 'price' => $price_to_log]);
|
|
||||||
|
foreach ($klines as $kline) {
|
||||||
|
log_candlestick_data($pdo, $symbol, $exchange, $interval, $kline);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For the frontend, provide the most recent ticker data
|
||||||
|
$latest_kline = end($klines);
|
||||||
|
$latest_tickers[] = [
|
||||||
|
'exchange' => $exchange,
|
||||||
|
'symbol' => $symbol,
|
||||||
|
'price' => $latest_kline[4], // Close price
|
||||||
|
'change_24h_percent' => 0, // Placeholder, as kline API doesn't provide this directly
|
||||||
|
'signal' => '-'
|
||||||
|
];
|
||||||
}
|
}
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
// If DB fails, log error but don't crash the API endpoint
|
http_response_code(500);
|
||||||
error_log('DB logging failed in ticker.php: ' . $e->getMessage());
|
// Log error and exit gracefully
|
||||||
|
error_log('Ticker script failed: ' . $e->getMessage());
|
||||||
|
echo json_encode(['error' => 'An internal error occurred.']);
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Sanitize and Structure Output ---
|
// --- Output for Frontend ---
|
||||||
// We select and sanitize the fields we want to send to the frontend.
|
// This part is for the main dashboard display, not the alerts API.
|
||||||
$output = [
|
if (isset($_GET['symbol'])) {
|
||||||
'exchange' => 'Bitget',
|
$symbol_to_find = strtoupper($_GET['symbol']);
|
||||||
'symbol' => $ticker_data['symbol'] ?? 'N/A',
|
foreach ($latest_tickers as $ticker) {
|
||||||
'price' => $ticker_data['lastPr'] ?? '0.00',
|
if ($ticker['symbol'] === $symbol_to_find) {
|
||||||
// Bitget provides the 24h change as a decimal, e.g., 0.025 for +2.5%
|
echo json_encode($ticker);
|
||||||
'change_24h_percent' => isset($ticker_data['priceChangePercent']) ? (float)$ticker_data['priceChangePercent'] * 100 : 0,
|
exit;
|
||||||
'signal' => '-' // Placeholder for future signal detection
|
}
|
||||||
];
|
}
|
||||||
|
// Fallback if the requested symbol wasn't processed
|
||||||
echo json_encode($output);
|
echo json_encode(['error' => 'Data for symbol not found.']);
|
||||||
|
} else {
|
||||||
|
// If no symbol is specified, do not return anything for now.
|
||||||
|
// The frontend will request each symbol individually.
|
||||||
|
echo json_encode(['status' => 'OK', 'message' => 'Data processed.']);
|
||||||
|
}
|
||||||
@ -62,4 +62,14 @@ body {
|
|||||||
|
|
||||||
.flash-danger {
|
.flash-danger {
|
||||||
animation: flash-danger-anim 0.75s ease-out;
|
animation: flash-danger-anim 0.75s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Alert flash animation */
|
||||||
|
@keyframes flash-alert-anim {
|
||||||
|
0%, 100% { background-color: transparent; }
|
||||||
|
50% { background-color: rgba(220, 53, 69, 0.25); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.flash-alert {
|
||||||
|
animation: flash-alert-anim 1.5s infinite;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,100 +1,124 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
const POLLING_INTERVAL = 3000; // 3 seconds
|
const POLLING_INTERVAL = 3000; // 3 seconds
|
||||||
|
|
||||||
// --- Configuration for Live Tickers ---
|
|
||||||
const liveTickers = [
|
const liveTickers = [
|
||||||
{ symbol: 'BTCUSDT', elementId: 'live-crypto-row-btc', lastPrice: 0 },
|
{ symbol: 'BTCUSDT', elementId: 'live-crypto-row-btc', lastPrice: 0 },
|
||||||
{ symbol: 'ETHUSDT', elementId: 'live-crypto-row-eth', lastPrice: 0 }
|
{ symbol: 'ETHUSDT', elementId: 'live-crypto-row-eth', lastPrice: 0 }
|
||||||
];
|
];
|
||||||
|
|
||||||
// --- Generic Fetch and Update Functions ---
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the latest price for a given symbol and updates its corresponding row.
|
|
||||||
* @param {object} ticker - The ticker object from the liveTickers array.
|
|
||||||
* @param {HTMLElement} rowElement - The table row element to update.
|
|
||||||
*/
|
|
||||||
async function fetchAndupdate(ticker, rowElement) {
|
async function fetchAndupdate(ticker, rowElement) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`api/ticker.php?symbol=${ticker.symbol}`);
|
const response = await fetch(`api/ticker.php?symbol=${ticker.symbol}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
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}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateRow(data, ticker, rowElement);
|
updateRow(data, ticker, rowElement);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to fetch live price for ${ticker.symbol}:`, error);
|
console.error(`Failed to fetch live price for ${ticker.symbol}:`, error);
|
||||||
// Optionally, display an error state in the specific row
|
|
||||||
const priceCell = rowElement.querySelector('.price');
|
const priceCell = rowElement.querySelector('.price');
|
||||||
if (priceCell) priceCell.textContent = 'Error';
|
if (priceCell) priceCell.textContent = 'Error';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the DOM of a specific row with new data.
|
|
||||||
* @param {object} data - The data object from the API.
|
|
||||||
* @param {object} ticker - The ticker object being updated.
|
|
||||||
* @param {HTMLElement} rowElement - The table row element.
|
|
||||||
*/
|
|
||||||
function updateRow(data, ticker, rowElement) {
|
function updateRow(data, ticker, rowElement) {
|
||||||
const priceCell = rowElement.querySelector('.price');
|
const priceCell = rowElement.querySelector('.price');
|
||||||
const changeCell = rowElement.querySelector('.change');
|
|
||||||
const symbolCell = rowElement.querySelector('.symbol');
|
const symbolCell = rowElement.querySelector('.symbol');
|
||||||
const exchangeCell = rowElement.querySelector('.exchange');
|
const exchangeCell = rowElement.querySelector('.exchange');
|
||||||
|
|
||||||
if (!priceCell || !changeCell || !symbolCell || !exchangeCell) {
|
if (!priceCell || !symbolCell || !exchangeCell) {
|
||||||
console.error(`One or more required elements not found in row for ${ticker.symbol}`);
|
console.error(`One or more required elements not found in row for ${ticker.symbol}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentPrice = parseFloat(data.price);
|
const currentPrice = parseFloat(data.price);
|
||||||
const priceChange = currentPrice - ticker.lastPrice;
|
priceCell.textContent = `${currentPrice.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
||||||
|
|
||||||
// Update text content
|
|
||||||
priceCell.textContent = `$${currentPrice.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
||||||
symbolCell.textContent = data.symbol.replace('_SPBL', '');
|
symbolCell.textContent = data.symbol.replace('_SPBL', '');
|
||||||
exchangeCell.textContent = data.exchange;
|
exchangeCell.textContent = data.exchange;
|
||||||
|
|
||||||
// Update 24h change
|
if (ticker.lastPrice !== 0 && currentPrice !== ticker.lastPrice) {
|
||||||
const changePercent = parseFloat(data.change_24h_percent);
|
const flashClass = currentPrice > ticker.lastPrice ? 'flash-success' : 'flash-danger';
|
||||||
changeCell.textContent = `${changePercent.toFixed(2)}%`;
|
|
||||||
changeCell.classList.toggle('text-success', changePercent >= 0);
|
|
||||||
changeCell.classList.toggle('text-danger', changePercent < 0);
|
|
||||||
|
|
||||||
// Visual flash on price change
|
|
||||||
if (ticker.lastPrice !== 0 && priceChange !== 0) {
|
|
||||||
const flashClass = priceChange > 0 ? 'flash-success' : 'flash-danger';
|
|
||||||
priceCell.classList.add(flashClass);
|
priceCell.classList.add(flashClass);
|
||||||
setTimeout(() => priceCell.classList.remove(flashClass), 750);
|
setTimeout(() => priceCell.classList.remove(flashClass), 750);
|
||||||
}
|
}
|
||||||
|
|
||||||
ticker.lastPrice = currentPrice; // Update the last price for this specific ticker
|
ticker.lastPrice = currentPrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Initialization ---
|
async function fetchAnalysis(ticker, rowElement) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`api/analysis.php?symbol=${ticker.symbol}&period=20`);
|
||||||
|
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.error) throw new Error(`API Error: ${data.error}`);
|
||||||
|
updateAnalysis(data, rowElement, ticker.lastPrice);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to fetch analysis for ${ticker.symbol}:`, error);
|
||||||
|
const smaCell = rowElement.querySelector('.sma');
|
||||||
|
if (smaCell) smaCell.textContent = 'Error';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAnalysis(data, rowElement, currentPrice) {
|
||||||
|
const smaCell = rowElement.querySelector('.sma');
|
||||||
|
const signalCell = rowElement.querySelector('.signal');
|
||||||
|
if (!smaCell || !signalCell) return;
|
||||||
|
|
||||||
|
const sma = parseFloat(data.sma);
|
||||||
|
smaCell.textContent = sma.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||||
|
|
||||||
|
if (currentPrice > sma) {
|
||||||
|
signalCell.innerHTML = '<span class="badge bg-success-subtle text-success-emphasis">Above SMA</span>';
|
||||||
|
} else if (currentPrice < sma) {
|
||||||
|
signalCell.innerHTML = '<span class="badge bg-danger-subtle text-danger-emphasis">Below SMA</span>';
|
||||||
|
} else {
|
||||||
|
signalCell.innerHTML = '<span class="badge bg-secondary-subtle text-secondary-emphasis">At SMA</span>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchAlert(ticker, rowElement) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`api/alerts.php?symbol=${ticker.symbol}`);
|
||||||
|
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
|
const data = await response.json();
|
||||||
|
updateAlertStatus(data.status, rowElement);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to fetch alert for ${ticker.symbol}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAlertStatus(status, rowElement) {
|
||||||
|
const statusCell = rowElement.querySelector('.status');
|
||||||
|
if (!statusCell) return;
|
||||||
|
|
||||||
|
rowElement.classList.remove('flash-alert');
|
||||||
|
statusCell.innerHTML = '-';
|
||||||
|
|
||||||
|
if (status === 'Crash Alert') {
|
||||||
|
statusCell.innerHTML = '<span class="badge bg-danger-subtle text-danger-emphasis">Crash Alert</span>';
|
||||||
|
rowElement.classList.add('flash-alert');
|
||||||
|
} else if (status === 'Pump Alert') {
|
||||||
|
statusCell.innerHTML = '<span class="badge bg-success-subtle text-success-emphasis">Pump Alert</span>';
|
||||||
|
rowElement.classList.add('flash-alert');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the polling for all configured tickers.
|
|
||||||
*/
|
|
||||||
function initializeLiveTickers() {
|
function initializeLiveTickers() {
|
||||||
liveTickers.forEach(ticker => {
|
liveTickers.forEach(ticker => {
|
||||||
const rowElement = document.getElementById(ticker.elementId);
|
const rowElement = document.getElementById(ticker.elementId);
|
||||||
if (!rowElement) {
|
if (!rowElement) {
|
||||||
console.error(`Element with ID #${ticker.elementId} not found for symbol ${ticker.symbol}.`);
|
console.error(`Element with ID #${ticker.elementId} not found for symbol ${ticker.symbol}.`);
|
||||||
return; // Skip this ticker if its row doesn't exist
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initial fetch
|
const fetchAll = () => {
|
||||||
fetchAndupdate(ticker, rowElement);
|
fetchAndupdate(ticker, rowElement).then(() => {
|
||||||
|
fetchAnalysis(ticker, rowElement);
|
||||||
// Start polling every X seconds
|
});
|
||||||
setInterval(() => fetchAndupdate(ticker, rowElement), POLLING_INTERVAL);
|
fetchAlert(ticker, rowElement);
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchAll();
|
||||||
|
setInterval(fetchAll, POLLING_INTERVAL);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
31
db/migrations/002_create_candlestick_data_table.php
Normal file
31
db/migrations/002_create_candlestick_data_table.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../config.php';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = db();
|
||||||
|
$sql = <<<SQL
|
||||||
|
CREATE TABLE IF NOT EXISTS candlestick_data (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
symbol VARCHAR(20) NOT NULL,
|
||||||
|
exchange VARCHAR(50) NOT NULL,
|
||||||
|
interval_time VARCHAR(10) NOT NULL,
|
||||||
|
open_time BIGINT NOT NULL,
|
||||||
|
open_price DECIMAL(20, 8) NOT NULL,
|
||||||
|
high_price DECIMAL(20, 8) NOT NULL,
|
||||||
|
low_price DECIMAL(20, 8) NOT NULL,
|
||||||
|
close_price DECIMAL(20, 8) NOT NULL,
|
||||||
|
volume DECIMAL(20, 8) NOT NULL,
|
||||||
|
close_time BIGINT NOT NULL,
|
||||||
|
quote_asset_volume DECIMAL(20, 8),
|
||||||
|
number_of_trades INT,
|
||||||
|
taker_buy_base_asset_volume DECIMAL(20, 8),
|
||||||
|
taker_buy_quote_asset_volume DECIMAL(20, 8),
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE KEY `symbol_exchange_interval_time_open_time` (`symbol`, `exchange`, `interval_time`, `open_time`)
|
||||||
|
);
|
||||||
|
SQL;
|
||||||
|
$db->exec($sql);
|
||||||
|
echo "Migration 002 successful: candlestick_data table created or already exists." . PHP_EOL;
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die("Migration 002 failed: " . $e->getMessage() . PHP_EOL);
|
||||||
|
}
|
||||||
70
index.php
70
index.php
@ -49,30 +49,30 @@
|
|||||||
<th scope="col">EXCHANGE</th>
|
<th scope="col">EXCHANGE</th>
|
||||||
<th scope="col">SYMBOL</th>
|
<th scope="col">SYMBOL</th>
|
||||||
<th scope="col">PRICE</th>
|
<th scope="col">PRICE</th>
|
||||||
<th scope="col" class="text-center">24H %</th>
|
<th scope="col">SMA (20m)</th>
|
||||||
<th scope="col" class="text-end">SIGNAL</th>
|
<th scope="col">SIGNAL</th>
|
||||||
|
<th scope="col" class="text-end">STATUS</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<!-- Live Row 1 (Updated by JavaScript) -->
|
<!-- BTC Row (Updated by JavaScript) -->
|
||||||
<tr id="live-crypto-row-btc">
|
<tr id="live-crypto-row-btc">
|
||||||
<td class="fw-medium text-muted exchange">Bitget</td>
|
<td class="fw-medium text-muted exchange">Binance</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<img src="https://via.placeholder.com/24/0d6efd/FFFFFF?Text=B" class="symbol-logo rounded-circle" alt="BTC">
|
<img src="https://via.placeholder.com/24/f0b90b/000000?Text=B" class="symbol-logo rounded-circle" alt="BTC">
|
||||||
<span class="fw-bold symbol">BTC/USDT</span>
|
<span class="fw-bold symbol">BTC/USDT</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="fw-bold fs-5 price">$0.00</td>
|
<td class="fw-bold fs-5 price">$0.00</td>
|
||||||
<td class="text-center">
|
<td class="fw-medium sma">-</td>
|
||||||
<span class="fw-bold change">--%</span>
|
<td class="fw-medium signal">-</td>
|
||||||
</td>
|
<td class="text-end status">-</td>
|
||||||
<td class="text-end signal">-</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<!-- Live Row 2 (Updated by JavaScript) -->
|
<!-- ETH Row (Updated by JavaScript) -->
|
||||||
<tr id="live-crypto-row-eth">
|
<tr id="live-crypto-row-eth">
|
||||||
<td class="fw-medium text-muted exchange">Bitget</td>
|
<td class="fw-medium text-muted exchange">Binance</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<img src="https://via.placeholder.com/24/6f42c1/FFFFFF?Text=E" class="symbol-logo rounded-circle" alt="ETH">
|
<img src="https://via.placeholder.com/24/6f42c1/FFFFFF?Text=E" class="symbol-logo rounded-circle" alt="ETH">
|
||||||
@ -80,52 +80,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="fw-bold fs-5 price">$0.00</td>
|
<td class="fw-bold fs-5 price">$0.00</td>
|
||||||
<td class="text-center">
|
<td class="fw-medium sma">-</td>
|
||||||
<span class="fw-bold change">--%</span>
|
<td class="fw-medium signal">-</td>
|
||||||
</td>
|
<td class="text-end status">-</td>
|
||||||
<td class="text-end signal">-</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<?php
|
|
||||||
// Static mock data for UI variety
|
|
||||||
$mock_data = [
|
|
||||||
['exchange' => 'WEEX', 'symbol' => 'PEPE/USDT', 'price' => '0.00001234', 'change' => 255.1, 'signal' => 'Pump Alert'],
|
|
||||||
['exchange' => 'KuCoin', 'symbol' => 'SOL/USDT', 'price' => '165.80', 'change' => -1.2, 'signal' => 'Bearish Reversal'],
|
|
||||||
['exchange' => 'KCEX', 'symbol' => 'DOGE/USDT', 'price' => '0.1588', 'change' => 5.6, 'signal' => null],
|
|
||||||
['exchange' => 'Bitget', 'symbol' => 'XRP/USDT', 'price' => '0.5210', 'change' => -0.5, 'signal' => null],
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($mock_data as $row):
|
|
||||||
$change_color = $row['change'] >= 0 ? 'success' : 'danger';
|
|
||||||
$change_icon = $row['change'] >= 0 ? 'bi-arrow-up-right' : 'bi-arrow-down-left';
|
|
||||||
|
|
||||||
$signal_badge = '';
|
|
||||||
if ($row['signal'] === 'Crash Alert') {
|
|
||||||
$signal_badge = '<span class="badge bg-danger-subtle text-danger-emphasis">Crash Alert</span>';
|
|
||||||
} elseif ($row['signal'] === 'Pump Alert') {
|
|
||||||
$signal_badge = '<span class="badge bg-success-subtle text-success-emphasis">Pump Alert</span>';
|
|
||||||
} elseif ($row['signal'] === 'Bearish Reversal') {
|
|
||||||
$signal_badge = '<span class="badge bg-warning-subtle text-warning-emphasis">Bearish Reversal</span>';
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<tr>
|
|
||||||
<td class="fw-medium text-muted"><?php echo htmlspecialchars($row['exchange']); ?></td>
|
|
||||||
<td>
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<img src="https://via.placeholder.com/24/0d6efd/FFFFFF?Text=<?php echo substr($row['symbol'], 0, 1); ?>" class="symbol-logo rounded-circle" alt="">
|
|
||||||
<span class="fw-bold"><?php echo htmlspecialchars($row['symbol']); ?></span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="fw-bold fs-5">$<?php echo htmlspecialchars($row['price']); ?></td>
|
|
||||||
<td class="text-center">
|
|
||||||
<span class="fw-bold text-<?php echo $change_color; ?>">
|
|
||||||
<i class="bi <?php echo $change_icon; ?>"></i>
|
|
||||||
<?php echo htmlspecialchars(abs($row['change'])); ?>%
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="text-end"><?php echo $signal_badge; ?></td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user