use super::config::{StopConfig, StopFill};
fn gap_fill(open: f64, level: f64, dir: f64, adverse: bool) -> f64 {
if open.is_nan() {
return level;
}
let long = dir >= 0.0;
if adverse == long {
open.min(level)
} else {
open.max(level)
}
}
fn first_touched(a: Option<f64>, b: f64, dir: f64) -> f64 {
match a {
None => b,
Some(a) if dir >= 0.0 => a.max(b),
Some(a) => a.min(b),
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn check_stop(
entry: f64,
dir: f64,
peak: &mut f64,
o: f64,
h: f64,
l: f64,
c: f64,
cfg: &StopConfig,
) -> Option<f64> {
if entry == 0.0 || entry.is_nan() {
return None;
}
let signed = |p: f64| dir * (p / entry - 1.0); let sl_on = cfg.stop_loss != f64::NEG_INFINITY;
let tp_on = cfg.take_profit != f64::INFINITY;
let tr_on = cfg.trail_stop != f64::INFINITY;
match cfg.fill {
StopFill::Close => {
if c.is_nan() {
return None;
}
let rc = signed(c);
*peak = peak.max(rc);
let hit = (sl_on && rc <= -cfg.stop_loss.abs())
|| (tp_on && rc >= cfg.take_profit.abs())
|| (tr_on
&& *peak >= cfg.trail_stop_activation.abs()
&& rc <= *peak - cfg.trail_stop.abs());
hit.then_some(c)
}
StopFill::Touched => {
if h.is_nan() || l.is_nan() {
return None;
}
let (fav_price, adv_price) = if dir >= 0.0 { (h, l) } else { (l, h) };
let best = signed(fav_price);
let worst = signed(adv_price);
let prior_peak = *peak;
*peak = peak.max(best);
let level = |t: f64| entry * (1.0 + dir * t);
let mut adverse: Option<f64> = None;
if sl_on && worst <= -cfg.stop_loss.abs() {
let f = gap_fill(o, level(-cfg.stop_loss.abs()), dir, true);
adverse = Some(first_touched(adverse, f, dir));
}
if tr_on
&& prior_peak >= cfg.trail_stop_activation.abs()
&& worst <= prior_peak - cfg.trail_stop.abs()
{
let f = gap_fill(o, level(prior_peak - cfg.trail_stop.abs()), dir, true);
adverse = Some(first_touched(adverse, f, dir));
}
if adverse.is_some() {
return adverse;
}
if tp_on && best >= cfg.take_profit.abs() {
return Some(gap_fill(o, level(cfg.take_profit.abs()), dir, false));
}
None
}
}
}