use crate::binance::models::Kline; pub mod red_candle; pub mod ema_cross; pub mod bollinger_rsi; pub mod macd; pub mod rsi; pub mod supertrend; /// Strateji sinyali #[derive(Debug, Clone, PartialEq)] pub enum Signal { Buy, Sell, Hold, } /// Tüm stratejilerin uygulaması gereken trait pub trait Strategy: Send + Sync { /// Son kapanmış mumu içeren kline dizisini alır. /// candles.last() = son kapanmış mum (açık mum değil) fn generate_signal(&self, candles: &[Kline]) -> Signal; /// Alış fiyatından limit satış fiyatı hesaplar fn sell_price(&self, buy_price: f64) -> f64; /// Stop loss fiyatı (0.0 = stop loss yok) fn stop_price(&self, buy_price: f64) -> f64; /// Sinyal üretmek için gereken minimum mum sayısı fn min_candles(&self) -> usize; } /// Strateji tipi — DB'de saklanır, config'ten okunur #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "snake_case")] pub enum StrategyKind { RedCandle, EmaCross, BollingerRsi, Macd, Rsi, Supertrend, } impl StrategyKind { pub fn as_str(&self) -> &'static str { match self { StrategyKind::RedCandle => "red_candle", StrategyKind::EmaCross => "ema_cross", StrategyKind::BollingerRsi => "bollinger_rsi", StrategyKind::Macd => "macd", StrategyKind::Rsi => "rsi", StrategyKind::Supertrend => "supertrend", } } pub fn display_name(&self) -> &'static str { match self { StrategyKind::RedCandle => "Kırmızı Mum", StrategyKind::EmaCross => "EMA Crossover", StrategyKind::BollingerRsi => "Bollinger + RSI", StrategyKind::Macd => "MACD", StrategyKind::Rsi => "RSI Aşırı Satım", StrategyKind::Supertrend => "Supertrend", } } } impl std::fmt::Display for StrategyKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.as_str()) } } impl std::str::FromStr for StrategyKind { type Err = (); fn from_str(s: &str) -> Result { match s { "red_candle" => Ok(StrategyKind::RedCandle), "ema_cross" => Ok(StrategyKind::EmaCross), "bollinger_rsi" => Ok(StrategyKind::BollingerRsi), "macd" => Ok(StrategyKind::Macd), "rsi" => Ok(StrategyKind::Rsi), "supertrend" => Ok(StrategyKind::Supertrend), _ => Err(()), } } } /// JSON params'tan f64 çeker, yoksa default döner pub fn param_f64(params: &serde_json::Value, key: &str, default: f64) -> f64 { params.get(key).and_then(|v| v.as_f64()).unwrap_or(default) } pub fn param_i64(params: &serde_json::Value, key: &str, default: i64) -> i64 { params.get(key).and_then(|v| v.as_i64()).unwrap_or(default) } /// EMA hesabı (exponential moving average, span parametreli) pub fn ema(values: &[f64], span: usize) -> Vec { if values.is_empty() || span == 0 { return vec![]; } let k = 2.0 / (span as f64 + 1.0); let mut result = vec![0.0f64; values.len()]; result[0] = values[0]; for i in 1..values.len() { result[i] = values[i] * k + result[i - 1] * (1.0 - k); } result } /// RSI hesabı (Wilder's smoothing = ewm com=period-1) pub fn rsi(closes: &[f64], period: usize) -> Vec { if closes.len() < period + 1 { return vec![50.0; closes.len()]; } let k = 1.0 / period as f64; // com = period-1 → alpha = 1/period let mut avg_gain = 0.0f64; let mut avg_loss = 0.0f64; for i in 1..=period { let diff = closes[i] - closes[i - 1]; if diff > 0.0 { avg_gain += diff; } else { avg_loss += diff.abs(); } } avg_gain /= period as f64; avg_loss /= period as f64; let mut result = vec![50.0f64; closes.len()]; if avg_loss == 0.0 { result[period] = 100.0; } else { result[period] = 100.0 - 100.0 / (1.0 + avg_gain / avg_loss); } for i in (period + 1)..closes.len() { let diff = closes[i] - closes[i - 1]; let gain = if diff > 0.0 { diff } else { 0.0 }; let loss = if diff < 0.0 { diff.abs() } else { 0.0 }; avg_gain = avg_gain * (1.0 - k) + gain * k; avg_loss = avg_loss * (1.0 - k) + loss * k; result[i] = if avg_loss == 0.0 { 100.0 } else { 100.0 - 100.0 / (1.0 + avg_gain / avg_loss) }; } result } /// ATR hesabı pub fn atr(candles: &[Kline], period: usize) -> Vec { if candles.len() < 2 { return vec![0.0; candles.len()]; } let mut tr_vals = vec![0.0f64; candles.len()]; for i in 1..candles.len() { let h = candles[i].high; let l = candles[i].low; let pc = candles[i - 1].close; tr_vals[i] = (h - l).max((h - pc).abs()).max((l - pc).abs()); } ema(&tr_vals, period) }