first comit
This commit is contained in:
621
src/indicators.rs
Normal file
621
src/indicators.rs
Normal file
@@ -0,0 +1,621 @@
|
||||
//! Technical indicator calculations.
|
||||
|
||||
use crate::config::{
|
||||
IndicatorParams, ADX_STRONG, ADX_THRESHOLD, BB_STD, RSI_OVERBOUGHT, RSI_OVERSOLD,
|
||||
RSI_PULLBACK_HIGH, RSI_PULLBACK_LOW, VOLUME_THRESHOLD,
|
||||
};
|
||||
use crate::types::{Bar, IndicatorRow, Signal, TradeSignal};
|
||||
|
||||
/// Calculate Exponential Moving Average (EMA).
|
||||
pub fn calculate_ema(data: &[f64], period: usize) -> Vec<f64> {
|
||||
if data.is_empty() || period == 0 {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let mut ema = vec![f64::NAN; data.len()];
|
||||
let multiplier = 2.0 / (period as f64 + 1.0);
|
||||
|
||||
// Start with SMA for the first period values
|
||||
if data.len() >= period {
|
||||
let sma: f64 = data[..period].iter().sum::<f64>() / period as f64;
|
||||
ema[period - 1] = sma;
|
||||
|
||||
for i in period..data.len() {
|
||||
ema[i] = (data[i] - ema[i - 1]) * multiplier + ema[i - 1];
|
||||
}
|
||||
}
|
||||
|
||||
ema
|
||||
}
|
||||
|
||||
/// Calculate Simple Moving Average (SMA).
|
||||
pub fn calculate_sma(data: &[f64], period: usize) -> Vec<f64> {
|
||||
if data.is_empty() || period == 0 {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let mut sma = vec![f64::NAN; data.len()];
|
||||
|
||||
for i in (period - 1)..data.len() {
|
||||
let sum: f64 = data[(i + 1 - period)..=i].iter().sum();
|
||||
sma[i] = sum / period as f64;
|
||||
}
|
||||
|
||||
sma
|
||||
}
|
||||
|
||||
/// Calculate standard deviation over a rolling window.
|
||||
pub fn calculate_rolling_std(data: &[f64], period: usize) -> Vec<f64> {
|
||||
if data.is_empty() || period == 0 {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let mut std = vec![f64::NAN; data.len()];
|
||||
|
||||
for i in (period - 1)..data.len() {
|
||||
let window = &data[(i + 1 - period)..=i];
|
||||
let mean: f64 = window.iter().sum::<f64>() / period as f64;
|
||||
let variance: f64 = window.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / period as f64;
|
||||
std[i] = variance.sqrt();
|
||||
}
|
||||
|
||||
std
|
||||
}
|
||||
|
||||
/// Calculate Relative Strength Index (RSI).
|
||||
pub fn calculate_rsi(closes: &[f64], period: usize) -> Vec<f64> {
|
||||
if closes.len() < 2 || period == 0 {
|
||||
return vec![f64::NAN; closes.len()];
|
||||
}
|
||||
|
||||
let mut rsi = vec![f64::NAN; closes.len()];
|
||||
|
||||
// Calculate price changes
|
||||
let mut gains = vec![0.0; closes.len()];
|
||||
let mut losses = vec![0.0; closes.len()];
|
||||
|
||||
for i in 1..closes.len() {
|
||||
let change = closes[i] - closes[i - 1];
|
||||
if change > 0.0 {
|
||||
gains[i] = change;
|
||||
} else {
|
||||
losses[i] = -change;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate initial average gain/loss
|
||||
if closes.len() > period {
|
||||
let mut avg_gain: f64 = gains[1..=period].iter().sum::<f64>() / period as f64;
|
||||
let mut avg_loss: f64 = losses[1..=period].iter().sum::<f64>() / period as f64;
|
||||
|
||||
if avg_loss == 0.0 {
|
||||
rsi[period] = 100.0;
|
||||
} else {
|
||||
let rs = avg_gain / avg_loss;
|
||||
rsi[period] = 100.0 - (100.0 / (1.0 + rs));
|
||||
}
|
||||
|
||||
// Smoothed RSI calculation
|
||||
for i in (period + 1)..closes.len() {
|
||||
avg_gain = (avg_gain * (period - 1) as f64 + gains[i]) / period as f64;
|
||||
avg_loss = (avg_loss * (period - 1) as f64 + losses[i]) / period as f64;
|
||||
|
||||
if avg_loss == 0.0 {
|
||||
rsi[i] = 100.0;
|
||||
} else {
|
||||
let rs = avg_gain / avg_loss;
|
||||
rsi[i] = 100.0 - (100.0 / (1.0 + rs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rsi
|
||||
}
|
||||
|
||||
/// Calculate MACD (Moving Average Convergence Divergence).
|
||||
pub fn calculate_macd(
|
||||
closes: &[f64],
|
||||
fast_period: usize,
|
||||
slow_period: usize,
|
||||
signal_period: usize,
|
||||
) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
|
||||
let fast_ema = calculate_ema(closes, fast_period);
|
||||
let slow_ema = calculate_ema(closes, slow_period);
|
||||
|
||||
let mut macd_line = vec![f64::NAN; closes.len()];
|
||||
for i in 0..closes.len() {
|
||||
if !fast_ema[i].is_nan() && !slow_ema[i].is_nan() {
|
||||
macd_line[i] = fast_ema[i] - slow_ema[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate signal line from MACD line (excluding NaN values)
|
||||
let valid_macd: Vec<f64> = macd_line.iter().copied().filter(|x| !x.is_nan()).collect();
|
||||
let signal_ema = calculate_ema(&valid_macd, signal_period);
|
||||
|
||||
// Map signal EMA back to original indices
|
||||
let mut signal_line = vec![f64::NAN; closes.len()];
|
||||
let mut valid_idx = 0;
|
||||
for i in 0..closes.len() {
|
||||
if !macd_line[i].is_nan() {
|
||||
if valid_idx < signal_ema.len() {
|
||||
signal_line[i] = signal_ema[valid_idx];
|
||||
}
|
||||
valid_idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate histogram
|
||||
let mut histogram = vec![f64::NAN; closes.len()];
|
||||
for i in 0..closes.len() {
|
||||
if !macd_line[i].is_nan() && !signal_line[i].is_nan() {
|
||||
histogram[i] = macd_line[i] - signal_line[i];
|
||||
}
|
||||
}
|
||||
|
||||
(macd_line, signal_line, histogram)
|
||||
}
|
||||
|
||||
/// Calculate Rate of Change (Momentum).
|
||||
pub fn calculate_roc(closes: &[f64], period: usize) -> Vec<f64> {
|
||||
if closes.is_empty() || period == 0 {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let mut roc = vec![f64::NAN; closes.len()];
|
||||
|
||||
for i in period..closes.len() {
|
||||
if closes[i - period] != 0.0 {
|
||||
roc[i] = ((closes[i] - closes[i - period]) / closes[i - period]) * 100.0;
|
||||
}
|
||||
}
|
||||
|
||||
roc
|
||||
}
|
||||
|
||||
/// Calculate True Range.
|
||||
fn calculate_true_range(highs: &[f64], lows: &[f64], closes: &[f64]) -> Vec<f64> {
|
||||
let mut tr = vec![f64::NAN; highs.len()];
|
||||
|
||||
if !highs.is_empty() {
|
||||
tr[0] = highs[0] - lows[0];
|
||||
}
|
||||
|
||||
for i in 1..highs.len() {
|
||||
let hl = highs[i] - lows[i];
|
||||
let hc = (highs[i] - closes[i - 1]).abs();
|
||||
let lc = (lows[i] - closes[i - 1]).abs();
|
||||
tr[i] = hl.max(hc).max(lc);
|
||||
}
|
||||
|
||||
tr
|
||||
}
|
||||
|
||||
/// Calculate Average True Range (ATR).
|
||||
pub fn calculate_atr(highs: &[f64], lows: &[f64], closes: &[f64], period: usize) -> Vec<f64> {
|
||||
let tr = calculate_true_range(highs, lows, closes);
|
||||
|
||||
// Use Wilder's smoothing (similar to EMA but with different multiplier)
|
||||
let mut atr = vec![f64::NAN; tr.len()];
|
||||
|
||||
if tr.len() >= period {
|
||||
// First ATR is simple average
|
||||
let first_atr: f64 = tr[..period].iter().filter(|x| !x.is_nan()).sum::<f64>() / period as f64;
|
||||
atr[period - 1] = first_atr;
|
||||
|
||||
// Subsequent ATR values use smoothing
|
||||
for i in period..tr.len() {
|
||||
atr[i] = (atr[i - 1] * (period - 1) as f64 + tr[i]) / period as f64;
|
||||
}
|
||||
}
|
||||
|
||||
atr
|
||||
}
|
||||
|
||||
/// Calculate ADX (Average Directional Index) along with DI+ and DI-.
|
||||
pub fn calculate_adx(
|
||||
highs: &[f64],
|
||||
lows: &[f64],
|
||||
closes: &[f64],
|
||||
period: usize,
|
||||
) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
|
||||
let len = highs.len();
|
||||
if len < 2 {
|
||||
return (vec![f64::NAN; len], vec![f64::NAN; len], vec![f64::NAN; len]);
|
||||
}
|
||||
|
||||
let mut plus_dm = vec![0.0; len];
|
||||
let mut minus_dm = vec![0.0; len];
|
||||
|
||||
// Calculate +DM and -DM
|
||||
for i in 1..len {
|
||||
let up_move = highs[i] - highs[i - 1];
|
||||
let down_move = lows[i - 1] - lows[i];
|
||||
|
||||
if up_move > down_move && up_move > 0.0 {
|
||||
plus_dm[i] = up_move;
|
||||
}
|
||||
if down_move > up_move && down_move > 0.0 {
|
||||
minus_dm[i] = down_move;
|
||||
}
|
||||
}
|
||||
|
||||
let tr = calculate_true_range(highs, lows, closes);
|
||||
|
||||
// Smooth the values using Wilder's smoothing
|
||||
let mut smoothed_plus_dm = vec![f64::NAN; len];
|
||||
let mut smoothed_minus_dm = vec![f64::NAN; len];
|
||||
let mut smoothed_tr = vec![f64::NAN; len];
|
||||
|
||||
if len >= period {
|
||||
smoothed_plus_dm[period - 1] = plus_dm[..period].iter().sum();
|
||||
smoothed_minus_dm[period - 1] = minus_dm[..period].iter().sum();
|
||||
smoothed_tr[period - 1] = tr[..period].iter().filter(|x| !x.is_nan()).sum();
|
||||
|
||||
for i in period..len {
|
||||
smoothed_plus_dm[i] =
|
||||
smoothed_plus_dm[i - 1] - (smoothed_plus_dm[i - 1] / period as f64) + plus_dm[i];
|
||||
smoothed_minus_dm[i] =
|
||||
smoothed_minus_dm[i - 1] - (smoothed_minus_dm[i - 1] / period as f64) + minus_dm[i];
|
||||
smoothed_tr[i] =
|
||||
smoothed_tr[i - 1] - (smoothed_tr[i - 1] / period as f64) + tr[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate DI+ and DI-
|
||||
let mut di_plus = vec![f64::NAN; len];
|
||||
let mut di_minus = vec![f64::NAN; len];
|
||||
|
||||
for i in 0..len {
|
||||
if !smoothed_tr[i].is_nan() && smoothed_tr[i] != 0.0 {
|
||||
di_plus[i] = (smoothed_plus_dm[i] / smoothed_tr[i]) * 100.0;
|
||||
di_minus[i] = (smoothed_minus_dm[i] / smoothed_tr[i]) * 100.0;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate DX
|
||||
let mut dx = vec![f64::NAN; len];
|
||||
for i in 0..len {
|
||||
if !di_plus[i].is_nan() && !di_minus[i].is_nan() {
|
||||
let di_sum = di_plus[i] + di_minus[i];
|
||||
if di_sum != 0.0 {
|
||||
dx[i] = ((di_plus[i] - di_minus[i]).abs() / di_sum) * 100.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate ADX (smoothed DX)
|
||||
let mut adx = vec![f64::NAN; len];
|
||||
let adx_start = period * 2 - 1;
|
||||
|
||||
if len > adx_start {
|
||||
// First ADX is simple average of DX
|
||||
let first_adx: f64 = dx[(period - 1)..adx_start]
|
||||
.iter()
|
||||
.filter(|x| !x.is_nan())
|
||||
.sum::<f64>()
|
||||
/ period as f64;
|
||||
adx[adx_start - 1] = first_adx;
|
||||
|
||||
for i in adx_start..len {
|
||||
if !dx[i].is_nan() {
|
||||
adx[i] = (adx[i - 1] * (period - 1) as f64 + dx[i]) / period as f64;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(adx, di_plus, di_minus)
|
||||
}
|
||||
|
||||
/// Calculate Bollinger Bands.
|
||||
pub fn calculate_bollinger_bands(
|
||||
closes: &[f64],
|
||||
period: usize,
|
||||
std_dev: f64,
|
||||
) -> (Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>) {
|
||||
let middle = calculate_sma(closes, period);
|
||||
let std = calculate_rolling_std(closes, period);
|
||||
|
||||
let mut upper = vec![f64::NAN; closes.len()];
|
||||
let mut lower = vec![f64::NAN; closes.len()];
|
||||
let mut pct_b = vec![f64::NAN; closes.len()];
|
||||
|
||||
for i in 0..closes.len() {
|
||||
if !middle[i].is_nan() && !std[i].is_nan() {
|
||||
upper[i] = middle[i] + std_dev * std[i];
|
||||
lower[i] = middle[i] - std_dev * std[i];
|
||||
|
||||
let band_width = upper[i] - lower[i];
|
||||
if band_width != 0.0 {
|
||||
pct_b[i] = (closes[i] - lower[i]) / band_width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(upper, middle, lower, pct_b)
|
||||
}
|
||||
|
||||
/// Calculate all technical indicators for a series of bars.
|
||||
pub fn calculate_all_indicators(bars: &[Bar], params: &IndicatorParams) -> Vec<IndicatorRow> {
|
||||
if bars.is_empty() {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let closes: Vec<f64> = bars.iter().map(|b| b.close).collect();
|
||||
let highs: Vec<f64> = bars.iter().map(|b| b.high).collect();
|
||||
let lows: Vec<f64> = bars.iter().map(|b| b.low).collect();
|
||||
let volumes: Vec<f64> = bars.iter().map(|b| b.volume).collect();
|
||||
|
||||
// Calculate all indicators
|
||||
let rsi = calculate_rsi(&closes, params.rsi_period);
|
||||
let (macd, macd_signal, macd_histogram) =
|
||||
calculate_macd(&closes, params.macd_fast, params.macd_slow, params.macd_signal);
|
||||
let momentum = calculate_roc(&closes, params.momentum_period);
|
||||
let ema_short = calculate_ema(&closes, params.ema_short);
|
||||
let ema_long = calculate_ema(&closes, params.ema_long);
|
||||
let ema_trend = calculate_ema(&closes, params.ema_trend);
|
||||
let atr = calculate_atr(&highs, &lows, &closes, params.atr_period);
|
||||
let (adx, di_plus, di_minus) = calculate_adx(&highs, &lows, &closes, params.adx_period);
|
||||
let (bb_upper, bb_middle, bb_lower, bb_pct) =
|
||||
calculate_bollinger_bands(&closes, params.bb_period, BB_STD);
|
||||
let volume_ma = calculate_sma(&volumes, params.volume_ma_period);
|
||||
|
||||
// Build indicator rows
|
||||
let mut rows = Vec::with_capacity(bars.len());
|
||||
|
||||
for i in 0..bars.len() {
|
||||
let bar = &bars[i];
|
||||
|
||||
let vol_ratio = if !volume_ma[i].is_nan() && volume_ma[i] != 0.0 {
|
||||
bar.volume / volume_ma[i]
|
||||
} else {
|
||||
f64::NAN
|
||||
};
|
||||
|
||||
let ema_dist = if !ema_trend[i].is_nan() && ema_trend[i] != 0.0 {
|
||||
(bar.close - ema_trend[i]) / ema_trend[i]
|
||||
} else {
|
||||
f64::NAN
|
||||
};
|
||||
|
||||
let atr_pct_val = if !atr[i].is_nan() && bar.close != 0.0 {
|
||||
atr[i] / bar.close
|
||||
} else {
|
||||
f64::NAN
|
||||
};
|
||||
|
||||
rows.push(IndicatorRow {
|
||||
timestamp: bar.timestamp,
|
||||
open: bar.open,
|
||||
high: bar.high,
|
||||
low: bar.low,
|
||||
close: bar.close,
|
||||
volume: bar.volume,
|
||||
rsi: rsi[i],
|
||||
macd: macd[i],
|
||||
macd_signal: macd_signal[i],
|
||||
macd_histogram: macd_histogram[i],
|
||||
momentum: momentum[i],
|
||||
ema_short: ema_short[i],
|
||||
ema_long: ema_long[i],
|
||||
ema_trend: ema_trend[i],
|
||||
ema_bullish: !ema_short[i].is_nan()
|
||||
&& !ema_long[i].is_nan()
|
||||
&& ema_short[i] > ema_long[i],
|
||||
trend_bullish: !ema_trend[i].is_nan() && bar.close > ema_trend[i],
|
||||
atr: atr[i],
|
||||
atr_pct: atr_pct_val,
|
||||
adx: adx[i],
|
||||
di_plus: di_plus[i],
|
||||
di_minus: di_minus[i],
|
||||
bb_upper: bb_upper[i],
|
||||
bb_middle: bb_middle[i],
|
||||
bb_lower: bb_lower[i],
|
||||
bb_pct: bb_pct[i],
|
||||
volume_ma: volume_ma[i],
|
||||
volume_ratio: vol_ratio,
|
||||
ema_distance: ema_dist,
|
||||
});
|
||||
}
|
||||
|
||||
rows
|
||||
}
|
||||
|
||||
/// Generate trading signal from current and previous indicator rows.
|
||||
pub fn generate_signal(symbol: &str, current: &IndicatorRow, previous: &IndicatorRow) -> TradeSignal {
|
||||
let rsi = current.rsi;
|
||||
let macd = current.macd;
|
||||
let macd_signal_val = current.macd_signal;
|
||||
let macd_hist = current.macd_histogram;
|
||||
let momentum = current.momentum;
|
||||
let ema_short = current.ema_short;
|
||||
let ema_long = current.ema_long;
|
||||
let current_price = current.close;
|
||||
|
||||
// Advanced indicators
|
||||
let trend_bullish = current.trend_bullish;
|
||||
let volume_ratio = if current.volume_ratio.is_nan() {
|
||||
1.0
|
||||
} else {
|
||||
current.volume_ratio
|
||||
};
|
||||
let adx = if current.adx.is_nan() { 25.0 } else { current.adx };
|
||||
let di_plus = if current.di_plus.is_nan() {
|
||||
25.0
|
||||
} else {
|
||||
current.di_plus
|
||||
};
|
||||
let di_minus = if current.di_minus.is_nan() {
|
||||
25.0
|
||||
} else {
|
||||
current.di_minus
|
||||
};
|
||||
let bb_pct = if current.bb_pct.is_nan() {
|
||||
0.5
|
||||
} else {
|
||||
current.bb_pct
|
||||
};
|
||||
let ema_distance = if current.ema_distance.is_nan() {
|
||||
0.0
|
||||
} else {
|
||||
current.ema_distance
|
||||
};
|
||||
|
||||
// MACD crossover detection
|
||||
let macd_crossed_up = !previous.macd.is_nan()
|
||||
&& !previous.macd_signal.is_nan()
|
||||
&& !macd.is_nan()
|
||||
&& !macd_signal_val.is_nan()
|
||||
&& previous.macd < previous.macd_signal
|
||||
&& macd > macd_signal_val;
|
||||
|
||||
let macd_crossed_down = !previous.macd.is_nan()
|
||||
&& !previous.macd_signal.is_nan()
|
||||
&& !macd.is_nan()
|
||||
&& !macd_signal_val.is_nan()
|
||||
&& previous.macd > previous.macd_signal
|
||||
&& macd < macd_signal_val;
|
||||
|
||||
// EMA trend
|
||||
let ema_bullish = !ema_short.is_nan() && !ema_long.is_nan() && ema_short > ema_long;
|
||||
|
||||
// ADX trend strength
|
||||
let is_trending = adx > ADX_THRESHOLD;
|
||||
let strong_trend = adx > ADX_STRONG;
|
||||
let trend_up = di_plus > di_minus;
|
||||
|
||||
// Calculate scores
|
||||
let mut buy_score: f64 = 0.0;
|
||||
let mut sell_score: f64 = 0.0;
|
||||
|
||||
// TREND STRENGTH FILTER
|
||||
if is_trending {
|
||||
if trend_up && trend_bullish {
|
||||
buy_score += 3.0;
|
||||
} else if !trend_up && !trend_bullish {
|
||||
sell_score += 3.0;
|
||||
}
|
||||
} else {
|
||||
// Ranging market - use mean reversion
|
||||
if bb_pct < 0.1 {
|
||||
buy_score += 2.0;
|
||||
} else if bb_pct > 0.9 {
|
||||
sell_score += 2.0;
|
||||
}
|
||||
}
|
||||
|
||||
// PULLBACK ENTRY
|
||||
if trend_bullish && ema_bullish {
|
||||
if !rsi.is_nan() && rsi > RSI_PULLBACK_LOW && rsi < RSI_PULLBACK_HIGH {
|
||||
buy_score += 3.0;
|
||||
}
|
||||
if ema_distance > 0.0 && ema_distance < 0.03 {
|
||||
buy_score += 1.5;
|
||||
}
|
||||
if bb_pct < 0.3 {
|
||||
buy_score += 2.0;
|
||||
}
|
||||
}
|
||||
|
||||
// OVERSOLD/OVERBOUGHT
|
||||
if !rsi.is_nan() {
|
||||
if rsi < RSI_OVERSOLD {
|
||||
if trend_bullish {
|
||||
buy_score += 4.0;
|
||||
} else {
|
||||
buy_score += 2.0;
|
||||
}
|
||||
} else if rsi > RSI_OVERBOUGHT {
|
||||
sell_score += 3.0;
|
||||
}
|
||||
}
|
||||
|
||||
// MACD MOMENTUM
|
||||
if macd_crossed_up {
|
||||
buy_score += 2.5;
|
||||
if strong_trend && trend_up {
|
||||
buy_score += 1.0;
|
||||
}
|
||||
} else if macd_crossed_down {
|
||||
sell_score += 2.5;
|
||||
} else if !macd_hist.is_nan() {
|
||||
if macd_hist > 0.0 {
|
||||
buy_score += 0.5;
|
||||
} else if macd_hist < 0.0 {
|
||||
sell_score += 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
// MOMENTUM
|
||||
if !momentum.is_nan() {
|
||||
if momentum > 5.0 {
|
||||
buy_score += 2.0;
|
||||
} else if momentum > 2.0 {
|
||||
buy_score += 1.0;
|
||||
} else if momentum < -5.0 {
|
||||
sell_score += 2.0;
|
||||
} else if momentum < -2.0 {
|
||||
sell_score += 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
// EMA CROSSOVER
|
||||
let prev_ema_bullish = !previous.ema_short.is_nan()
|
||||
&& !previous.ema_long.is_nan()
|
||||
&& previous.ema_short > previous.ema_long;
|
||||
|
||||
if ema_bullish && !prev_ema_bullish {
|
||||
buy_score += 2.0;
|
||||
} else if !ema_bullish && prev_ema_bullish {
|
||||
sell_score += 2.0;
|
||||
} else if ema_bullish {
|
||||
buy_score += 0.5;
|
||||
} else {
|
||||
sell_score += 0.5;
|
||||
}
|
||||
|
||||
// VOLUME CONFIRMATION
|
||||
let has_volume = volume_ratio >= VOLUME_THRESHOLD;
|
||||
if has_volume && volume_ratio > 1.5 {
|
||||
if buy_score > sell_score {
|
||||
buy_score += 1.0;
|
||||
} else if sell_score > buy_score {
|
||||
sell_score += 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
// DETERMINE SIGNAL
|
||||
let total_score = buy_score - sell_score;
|
||||
|
||||
let signal = if total_score >= 6.0 {
|
||||
Signal::StrongBuy
|
||||
} else if total_score >= 3.5 {
|
||||
Signal::Buy
|
||||
} else if total_score <= -6.0 {
|
||||
Signal::StrongSell
|
||||
} else if total_score <= -3.5 {
|
||||
Signal::Sell
|
||||
} else {
|
||||
Signal::Hold
|
||||
};
|
||||
|
||||
let confidence = (total_score.abs() / 10.0).min(1.0);
|
||||
|
||||
TradeSignal {
|
||||
symbol: symbol.to_string(),
|
||||
signal,
|
||||
rsi: if rsi.is_nan() { 0.0 } else { rsi },
|
||||
macd: if macd.is_nan() { 0.0 } else { macd },
|
||||
macd_signal: if macd_signal_val.is_nan() {
|
||||
0.0
|
||||
} else {
|
||||
macd_signal_val
|
||||
},
|
||||
macd_histogram: if macd_hist.is_nan() { 0.0 } else { macd_hist },
|
||||
momentum: if momentum.is_nan() { 0.0 } else { momentum },
|
||||
ema_short: if ema_short.is_nan() { 0.0 } else { ema_short },
|
||||
ema_long: if ema_long.is_nan() { 0.0 } else { ema_long },
|
||||
current_price,
|
||||
confidence,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user