we ball again

This commit is contained in:
zastian-dev
2026-02-12 12:27:34 +00:00
parent 9cca8b3db8
commit 7c94b0f422
4 changed files with 366 additions and 213 deletions

View File

@@ -1,8 +1,8 @@
//! 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,
IndicatorParams, ADX_RANGE_THRESHOLD, ADX_STRONG, ADX_TREND_THRESHOLD, BB_STD,
RSI2_OVERBOUGHT, RSI2_OVERSOLD, RSI_OVERBOUGHT, RSI_OVERSOLD, VOLUME_THRESHOLD,
};
use crate::types::{Bar, IndicatorRow, Signal, TradeSignal};
@@ -348,6 +348,7 @@ pub fn calculate_all_indicators(bars: &[Bar], params: &IndicatorParams) -> Vec<I
// Calculate all indicators
let rsi = calculate_rsi(&closes, params.rsi_period);
let rsi_short = calculate_rsi(&closes, params.rsi_short_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);
@@ -392,6 +393,7 @@ pub fn calculate_all_indicators(bars: &[Bar], params: &IndicatorParams) -> Vec<I
close: bar.close,
volume: bar.volume,
rsi: rsi[i],
rsi_short: rsi_short[i],
macd: macd[i],
macd_signal: macd_signal[i],
macd_histogram: macd_histogram[i],
@@ -421,165 +423,180 @@ pub fn calculate_all_indicators(bars: &[Bar], params: &IndicatorParams) -> Vec<I
rows
}
/// Generate trading signal from current and previous indicator rows.
/// Generate trading signal using regime-adaptive dual strategy.
///
/// REGIME DETECTION (via ADX):
/// - ADX < 20: Range-bound → use Connors RSI-2 mean reversion
/// - ADX > 25: Trending → use momentum pullback entries
/// - 20-25: Transition → require extra confirmation
///
/// MEAN REVERSION (ranging markets):
/// - Buy when RSI-2 < 10 AND price above 200 EMA (long-term uptrend filter)
/// - Sell when RSI-2 > 90 (take profit at mean)
/// - Bollinger Band extremes add conviction
///
/// TREND FOLLOWING (trending markets):
/// - Buy pullbacks in uptrends: RSI-14 dips + EMA support + MACD confirming
/// - Sell when trend breaks: EMA crossover down + momentum loss
/// - Strong trend bonus for high ADX
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 rsi2 = current.rsi_short;
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
// Safe NaN handling
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
};
let volume_ratio = if current.volume_ratio.is_nan() { 1.0 } else { current.volume_ratio };
let adx = if current.adx.is_nan() { 22.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 };
// REGIME DETECTION
let is_ranging = adx < ADX_RANGE_THRESHOLD;
let is_trending = adx > ADX_TREND_THRESHOLD;
let strong_trend = adx > ADX_STRONG;
let trend_up = di_plus > di_minus;
// EMA state
let ema_bullish = !ema_short.is_nan() && !ema_long.is_nan() && ema_short > ema_long;
// MACD crossover detection
let macd_crossed_up = !previous.macd.is_nan()
&& !previous.macd_signal.is_nan()
&& !macd.is_nan()
&& !macd_signal_val.is_nan()
&& !current.macd.is_nan()
&& !current.macd_signal.is_nan()
&& previous.macd < previous.macd_signal
&& macd > macd_signal_val;
&& current.macd > current.macd_signal;
let macd_crossed_down = !previous.macd.is_nan()
&& !previous.macd_signal.is_nan()
&& !macd.is_nan()
&& !macd_signal_val.is_nan()
&& !current.macd.is_nan()
&& !current.macd_signal.is_nan()
&& previous.macd > previous.macd_signal
&& macd < macd_signal_val;
&& current.macd < current.macd_signal;
// 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 (buy-side)
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;
}
}
// PULLBACK EXIT (sell-side symmetry — bearish trend with RSI bounce)
if !trend_bullish && !ema_bullish {
if !rsi.is_nan() && rsi > (100.0 - RSI_PULLBACK_HIGH) && rsi < (100.0 - RSI_PULLBACK_LOW) {
sell_score += 3.0;
}
if ema_distance < 0.0 && ema_distance > -0.03 {
sell_score += 1.5;
}
if bb_pct > 0.7 {
sell_score += 2.0;
}
}
// OVERSOLD/OVERBOUGHT (symmetrized)
if !rsi.is_nan() {
if rsi < RSI_OVERSOLD {
if trend_bullish {
buy_score += 4.0;
} else {
buy_score += 2.0;
// ═══════════════════════════════════════════════════════════════
// REGIME 1: MEAN REVERSION (ranging market, ADX < 20)
// ═══════════════════════════════════════════════════════════════
if is_ranging {
// Connors RSI-2 mean reversion: buy extreme oversold in uptrend context
if !rsi2.is_nan() {
// Buy: RSI-2 extremely oversold + long-term trend intact
if rsi2 < RSI2_OVERSOLD {
buy_score += 5.0; // Strong mean reversion signal
if trend_bullish {
buy_score += 3.0; // With-trend mean reversion = highest conviction
}
if bb_pct < 0.05 {
buy_score += 2.0; // Price at/below lower BB
}
} else if rsi2 < 20.0 {
buy_score += 2.5;
if trend_bullish {
buy_score += 1.5;
}
}
} else if rsi > RSI_OVERBOUGHT {
if !trend_bullish {
// Sell: RSI-2 overbought = take profit on mean reversion
if rsi2 > RSI2_OVERBOUGHT {
sell_score += 4.0;
} else {
if !trend_bullish {
sell_score += 2.0;
}
} else if rsi2 > 80.0 && !trend_bullish {
sell_score += 2.0;
}
}
// Bollinger Band extremes in range
if bb_pct < 0.0 {
buy_score += 2.0; // Below lower band
} else if bb_pct > 1.0 {
sell_score += 2.0; // Above upper band
}
}
// MACD MOMENTUM (symmetrized)
// ═══════════════════════════════════════════════════════════════
// REGIME 2: TREND FOLLOWING (trending market, ADX > 25)
// ═══════════════════════════════════════════════════════════════
if is_trending {
// Trend direction confirmation
if trend_up && trend_bullish {
buy_score += 3.0;
// Pullback entry: price dipped but trend intact
if !rsi.is_nan() && rsi < 40.0 && rsi > 25.0 {
buy_score += 3.0; // Pullback in uptrend
}
if ema_distance > 0.0 && ema_distance < 0.02 {
buy_score += 2.0; // Near EMA support
}
if strong_trend {
buy_score += 1.5; // Strong trend bonus
}
} else if !trend_up && !trend_bullish {
sell_score += 3.0;
if !rsi.is_nan() && rsi > 60.0 && rsi < 75.0 {
sell_score += 3.0; // Bounce in downtrend
}
if ema_distance < 0.0 && ema_distance > -0.02 {
sell_score += 2.0; // Near EMA resistance
}
if strong_trend {
sell_score += 1.5;
}
}
}
// ═══════════════════════════════════════════════════════════════
// UNIVERSAL SIGNALS (both regimes)
// ═══════════════════════════════════════════════════════════════
// RSI-14 extremes (strong conviction regardless of regime)
if !rsi.is_nan() {
if rsi < RSI_OVERSOLD && trend_bullish {
buy_score += 3.0; // Oversold in uptrend = strong buy
} else if rsi > RSI_OVERBOUGHT && !trend_bullish {
sell_score += 3.0; // Overbought in downtrend = strong sell
}
}
// MACD crossover
if macd_crossed_up {
buy_score += 2.5;
if strong_trend && trend_up {
buy_score += 1.0;
buy_score += 2.0;
if is_trending && trend_up {
buy_score += 1.0; // Trend-confirming crossover
}
} else if macd_crossed_down {
sell_score += 2.5;
if strong_trend && !trend_up {
sell_score += 2.0;
if is_trending && !trend_up {
sell_score += 1.0;
}
} 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
// MACD histogram direction
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;
}
if momentum > 5.0 { buy_score += 1.5; }
else if momentum > 2.0 { buy_score += 0.5; }
else if momentum < -5.0 { sell_score += 1.5; }
else if momentum < -2.0 { sell_score += 0.5; }
}
// EMA CROSSOVER
// EMA crossover events
let prev_ema_bullish = !previous.ema_short.is_nan()
&& !previous.ema_long.is_nan()
&& previous.ema_short > previous.ema_long;
@@ -588,47 +605,39 @@ pub fn generate_signal(symbol: &str, current: &IndicatorRow, previous: &Indicato
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 GATE — require minimum volume for signal to be actionable
let has_volume = volume_ratio >= VOLUME_THRESHOLD;
if !has_volume {
// Dampen scores when volume is too low
// Volume gate
if volume_ratio < VOLUME_THRESHOLD {
buy_score *= 0.5;
sell_score *= 0.5;
}
// DETERMINE SIGNAL
// ═══════════════════════════════════════════════════════════════
// SIGNAL DETERMINATION
// ═══════════════════════════════════════════════════════════════
let total_score = buy_score - sell_score;
let signal = if total_score >= 6.0 {
let signal = if total_score >= 7.0 {
Signal::StrongBuy
} else if total_score >= 4.5 {
Signal::Buy
} else if total_score <= -6.0 {
} else if total_score <= -7.0 {
Signal::StrongSell
} else if total_score <= -3.5 {
} else if total_score <= -4.0 {
Signal::Sell
} else {
Signal::Hold
};
let confidence = (total_score.abs() / 10.0).min(1.0);
let confidence = (total_score.abs() / 12.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: if current.macd.is_nan() { 0.0 } else { current.macd },
macd_signal: if current.macd_signal.is_nan() { 0.0 } else { current.macd_signal },
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 },