use crate::indicators::market_structure::{Bias, MarketStructureState, PAEvent, PAEventKind};
use crate::regimes::MarketRegime;
#[derive(Debug, Clone, Default)]
pub struct ConfluenceContext {
pub regime_label: Option<u32>,
pub hurst_persistence: Option<f64>,
pub min_hurst: f64,
pub required_regime: Option<u32>,
pub required_bias: Option<u32>,
}
impl ConfluenceContext {
pub fn bull_flag_filter(regime_label: u32, hurst: f64) -> Self {
Self {
regime_label: Some(regime_label),
hurst_persistence: Some(hurst),
min_hurst: 0.5,
required_regime: Some(1),
required_bias: Some(1),
}
}
}
pub fn score_pa_event(event: &PAEvent, ctx: &ConfluenceContext) -> f64 {
let mut score = event.strength * event.confidence;
if let Some(h) = ctx.hurst_persistence {
if h >= ctx.min_hurst {
score *= 1.0 + (h - ctx.min_hurst).min(0.5);
} else {
score *= 0.5;
}
}
if let (Some(req), Some(cur)) = (ctx.required_regime, ctx.regime_label) {
if cur == req {
score *= 1.2;
} else {
score *= 0.3;
}
}
match &event.kind {
PAEventKind::GeometricFlag(f) if f.is_bull => score *= 1.1,
PAEventKind::GeometricFlag(f) if !f.is_bull => score *= 1.05,
PAEventKind::GeometricHs(h) if h.breakout_confirmed => score *= 1.15,
PAEventKind::SrInteraction(sr) => {
score *= 1.0 + (sr.strength / 10.0).min(0.2);
}
_ => {}
}
score
}
pub fn passes_confluence_filter(
event: &PAEvent,
ctx: &ConfluenceContext,
structure: Option<&MarketStructureState>,
) -> bool {
if let Some(req_bias) = ctx.required_bias {
if let Some(st) = structure {
let bias = match st.bias {
Bias::Bullish => 1u32,
Bias::Bearish => 2,
Bias::Neutral => 0,
};
if bias != req_bias && req_bias != 0 {
return false;
}
}
}
if let Some(req) = ctx.required_regime {
if ctx.regime_label != Some(req) {
return false;
}
}
if let Some(h) = ctx.hurst_persistence {
if h < ctx.min_hurst {
return false;
}
}
match &event.kind {
PAEventKind::GeometricFlag(f) => f.breakout_confirmed,
PAEventKind::GeometricHs(h) => h.breakout_confirmed && h.score >= 60.0,
PAEventKind::MarketStructureFlip(_) => true,
PAEventKind::SrInteraction(_) => true,
}
}
pub fn enrich_pa_event(event: &mut PAEvent, ctx: &ConfluenceContext) {
if let Some(label) = ctx.regime_label {
event.regime_at_event = Some(match label {
1 => "Bull".into(),
2 => "Bear".into(),
3 => "Crisis".into(),
_ => "Steady".into(),
});
}
if let Some(h) = ctx.hurst_persistence {
event.feature_values.push(("hurst_persistence".into(), h));
}
event
.feature_values
.push(("confluence_score".into(), score_pa_event(event, ctx)));
}
pub fn filter_confluent_events(
events: Vec<PAEvent>,
ctx: &ConfluenceContext,
structure: Option<&MarketStructureState>,
) -> Vec<PAEvent> {
events
.into_iter()
.filter(|e| passes_confluence_filter(e, ctx, structure))
.map(|mut e| {
enrich_pa_event(&mut e, ctx);
e
})
.collect()
}
pub fn regime_to_label(regime: MarketRegime) -> u32 {
match regime {
MarketRegime::Bull => 1,
MarketRegime::Bear => 2,
MarketRegime::Crisis => 3,
MarketRegime::Steady => 0,
MarketRegime::Cluster(c) => 4 + (c as u32),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::indicators::geometric_patterns::FlagPattern;
use crate::indicators::market_structure::FlipEvent;
fn sample_flag() -> FlagPattern {
FlagPattern {
id: 1,
is_bull: true,
pole_start_bar: 10,
pole_end_bar: 12,
flag_start_bar: 13,
flag_end_bar: 20,
pole_length: 3.0,
pole_length_atr: 2.5,
max_retrace_pct: 40.0,
pullbacks: 2,
pushes: 1,
breakout_confirmed: true,
breakout_price: 105.0,
consolidation_bars: 7,
pole_strength: 2.5,
}
}
#[test]
fn test_bull_flag_passes_confluence() {
let event = PAEvent::from_flag(sample_flag(), 20);
let ctx = ConfluenceContext::bull_flag_filter(1, 0.6);
let st = MarketStructureState {
bias: Bias::Bullish,
last_swing_high: None,
last_swing_low: None,
current_flip: None,
swing_depth_used: 3,
bar_index: 20,
};
assert!(passes_confluence_filter(&event, &ctx, Some(&st)));
assert!(score_pa_event(&event, &ctx) > 0.5);
}
#[test]
fn test_wrong_regime_fails_filter() {
let event = PAEvent::from_flag(sample_flag(), 20);
let ctx = ConfluenceContext::bull_flag_filter(2, 0.6);
let st = MarketStructureState {
bias: Bias::Bullish,
last_swing_high: None,
last_swing_low: None,
current_flip: None,
swing_depth_used: 3,
bar_index: 20,
};
assert!(!passes_confluence_filter(&event, &ctx, Some(&st)));
}
#[test]
fn test_enrich_adds_metadata() {
let mut event = PAEvent::from_market_structure_flip(
FlipEvent {
is_bearish: false,
price: 100.0,
bar: 5,
structure_strength: 3,
},
5,
);
let ctx = ConfluenceContext {
regime_label: Some(1),
hurst_persistence: Some(0.65),
..Default::default()
};
enrich_pa_event(&mut event, &ctx);
assert_eq!(event.regime_at_event, Some("Bull".into()));
assert!(event.feature_values.iter().any(|(k, _)| k == "confluence_score"));
}
}