234 lines
8.8 KiB
PHP
234 lines
8.8 KiB
PHP
<?php
|
|
header('Content-Type: application/json');
|
|
|
|
require_once __DIR__ . '/../db/config.php';
|
|
|
|
// Example: /api/analysis.php?symbol=BTCUSDT&indicators=sma,rsi,macd,patterns&period=20
|
|
$symbol = $_GET['symbol'] ?? 'BTCUSDT';
|
|
$indicators_str = $_GET['indicators'] ?? 'sma';
|
|
$indicators = array_map('trim', explode(',', strtolower($indicators_str)));
|
|
$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();
|
|
$stmt = $pdo->prepare("SELECT close_price FROM candlestick_data WHERE symbol = :symbol ORDER BY open_time 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;
|
|
}
|
|
return array_sum($closes) / $period;
|
|
}
|
|
|
|
function calculate_rsi($symbol, $period = 14) {
|
|
$pdo = db();
|
|
$limit = $period + 1;
|
|
$stmt = $pdo->prepare("SELECT close_price FROM candlestick_data WHERE symbol = :symbol ORDER BY open_time 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_price FROM candlestick_data WHERE symbol = :symbol ORDER BY open_time 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);
|
|
|
|
if (count($closes) < $slow_period + $signal_period) {
|
|
return null;
|
|
}
|
|
|
|
$ema_fast_all = _calculate_ema_series($closes, $fast_period);
|
|
$ema_slow_all = _calculate_ema_series($closes, $slow_period);
|
|
|
|
$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];
|
|
}
|
|
|
|
if (count($macd_line_series) < $signal_period) {
|
|
return null;
|
|
}
|
|
|
|
$signal_line_series = _calculate_ema_series($macd_line_series, $signal_period);
|
|
|
|
$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_price, high_price, low_price, close_price, open_time FROM candlestick_data WHERE symbol = :symbol ORDER BY open_time 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_price'] - $latest_candle['close_price']);
|
|
$upper_wick = $latest_candle['high_price'] - max($latest_candle['open_price'], $latest_candle['close_price']);
|
|
$lower_wick = min($latest_candle['open_price'], $latest_candle['close_price']) - $latest_candle['low_price'];
|
|
if ($upper_wick > $body_size * 2 && $lower_wick < $body_size) {
|
|
$patterns['shooting_star'] = true;
|
|
}
|
|
|
|
// 2. Bearish Engulfing
|
|
if ($prev_candle['close_price'] > $prev_candle['open_price'] && // Previous candle is bullish
|
|
$latest_candle['open_price'] > $prev_candle['close_price'] && // Current candle opens above previous close
|
|
$latest_candle['close_price'] < $prev_candle['open_price']) { // Current candle closes below previous open
|
|
$patterns['bearish_engulfing'] = true;
|
|
}
|
|
|
|
// 3. Gravestone Doji
|
|
$is_doji = $body_size <= ($latest_candle['high_price'] - $latest_candle['low_price']) * 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_price'] > $c1['open_price']) && (abs($c1['close_price'] - $c1['open_price']) > ($c1['high_price'] - $c1['low_price']) * 0.7) && // c1 is strong bullish
|
|
($c2['open_price'] > $c1['close_price']) && (abs($c2['close_price'] - $c2['open_price']) < ($c2['high_price'] - $c2['low_price']) * 0.3) && // c2 is small body, gapped up
|
|
($c3['close_price'] < $c3['open_price']) && ($c3['open_price'] < $c2['open_price']) && ($c3['close_price'] < $c1['close_price'])) { // 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_price']; }, 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_price'] >= $max_high * 0.98) {
|
|
if($candles[$i-1]['high_price'] < $c['high_price'] && $candles[$i+1]['high_price'] < $c['high_price']) {
|
|
$peaks[] = ['index' => $i, 'price' => $c['high_price']];
|
|
}
|
|
}
|
|
}
|
|
|
|
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_price']; }, $valley_slice));
|
|
|
|
// Check if current price has broken below the valley low (neckline)
|
|
if($latest_candle['close_price'] < $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 = [
|
|
'symbol' => $symbol,
|
|
'values' => $results,
|
|
'timestamp' => time()
|
|
];
|
|
|
|
echo json_encode($response); |