Composite signal
MACD, Vortex and Trend Quality are scored on every bar and aggregated with the current regime, on the same four legs as the regime chart. A marker shows where a Buy or Sell trigger starts, labelled with the composite score. Only a ClearToTrade grade produces a trigger, so a direction against the regime stays silent.
Composite signal
examples/site_showcase.rsfn composite_signal() -> Chart {
let bars = market_legs();
let names = ["macd", "vortex", "trend_quality"];
let mut indicators = names
.iter()
.map(|name| build_checked(name, &HashMap::new()))
.collect::<Result<Vec<_>, _>>()?;
let mut adx = build_checked("adx", &HashMap::new())?;
let mut atr = build_checked("atr", &HashMap::new())?;
let mut markers = Vec::new();
let mut prev = TriggerAction::Hold;
for (i, bar) in bars.iter().enumerate() {
let mut subscores = Vec::new();
for (name, indicator) in names.iter().zip(indicators.iter_mut()) {
if let Some(out) = indicator.on_bar(bar) {
subscores.push(score_indicator(name, &out, &indicator.alerts()));
}
}
let (Some(a), Some(t)) = (adx.on_bar(bar), atr.on_bar(bar)) else {
continue;
};
if subscores.len() < names.len() {
continue;
}
let regime = classify_regime(&bars[..=i], a.value, t.value / 100.0);
let signal = aggregate_subscores(
subscores,
None,
regime,
Vec::new(),
Some(bar),
t.extra["raw"],
);
// Mark the bar where a trigger starts, not every bar it persists.
if signal.trigger != prev && signal.trigger != TriggerAction::Hold {
markers.push(ChartMarkerData {
timestamp: bar.timestamp,
price: bar.close,
label: format!("{:?} {:+.2}", signal.trigger, signal.score),
action: signal.trigger,
});
}
prev = signal.trigger;
}
let data = ChartRenderData {
title: "Composite signal triggers".to_string(),
bars: candles(&bars),
series: Vec::new(),
zones: Vec::new(),
markers,
};
Ok(render_chart_svg(&data, 800, 400))
}