kestrel_chartkit/engine/
location_quality.rs1#[cfg(feature = "serde")]
2use serde::{Deserialize, Serialize};
3
4use crate::engine::acceptance_detector::AcceptanceDetectorOutput;
5use crate::engine::market_context::VolumeNodeKind;
6use crate::model::MarketRegime;
7
8#[derive(Debug, Clone, Copy, PartialEq)]
10#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
11pub struct LocationQualityScore {
12 pub score: f64, pub distance_score: f64,
14 pub regime_alignment_score: f64,
15 pub acceptance_score: f64,
16}
17
18pub fn calculate_location_quality(
19 price: f64,
20 key_level: f64,
21 atr_raw: f64,
22 volume_node: VolumeNodeKind,
23 regime: MarketRegime,
24 acceptance: &AcceptanceDetectorOutput,
25 is_long: bool,
26) -> LocationQualityScore {
27 let atr = atr_raw.max(1e-8);
28 let dist_atr = (price - key_level).abs() / atr;
29
30 let distance_score = (1.0 - (dist_atr / 2.0).min(1.0)) * 40.0;
32
33 let node_score = match volume_node {
35 VolumeNodeKind::Lvn => 30.0, VolumeNodeKind::Neutral => 20.0,
37 VolumeNodeKind::Hvn => 10.0, };
39
40 let regime_boost = match (regime, is_long) {
41 (MarketRegime::BullishExpansion, true) => 10.0,
42 (MarketRegime::BearishExpansion, false) => 10.0,
43 (MarketRegime::Consolidation, _) => 5.0,
44 _ => 0.0,
45 };
46 let regime_alignment_score = node_score + regime_boost;
47
48 let acceptance_score = match acceptance.rejection_kind {
50 crate::engine::acceptance_detector::RejectionKind::RejectionLow if is_long => 20.0,
51 crate::engine::acceptance_detector::RejectionKind::RejectionHigh if !is_long => 20.0,
52 _ => match acceptance.acceptance_level {
53 crate::engine::market_context::AcceptanceLevel::High if is_long => 15.0,
54 crate::engine::market_context::AcceptanceLevel::Low if !is_long => 15.0,
55 _ => 5.0,
56 },
57 };
58
59 let total_score =
60 (distance_score + regime_alignment_score + acceptance_score).clamp(0.0, 100.0);
61
62 LocationQualityScore {
63 score: total_score,
64 distance_score,
65 regime_alignment_score,
66 acceptance_score,
67 }
68}