'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);