CryptoFox-Mukan-Edition/src/bot/strategy/mod.rs
2026-04-25 21:42:43 +03:00

165 lines
5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<Self, Self::Err> {
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<f64> {
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<f64> {
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<f64> {
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)
}