GoshawkVortexAI commited on
Commit
d9b73e4
·
verified ·
1 Parent(s): cfe8ecb

Update scorer.py

Browse files
Files changed (1) hide show
  1. scorer.py +84 -25
scorer.py CHANGED
@@ -1,8 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
1
  from typing import Dict, Any, List, Tuple
2
 
3
  import numpy as np
4
 
5
- from config import WEIGHT_REGIME, WEIGHT_VOLUME, WEIGHT_STRUCTURE
 
 
 
 
 
 
 
 
6
 
7
 
8
  def compute_structure_score(regime_data: Dict[str, Any]) -> float:
@@ -10,29 +29,38 @@ def compute_structure_score(regime_data: Dict[str, Any]) -> float:
10
  structure = regime_data.get("structure", 0)
11
  vol_expanding = regime_data.get("vol_expanding", False)
12
  vol_contracting = regime_data.get("vol_contracting", False)
13
- atr_trend = regime_data.get("atr_trend", "falling")
 
14
 
15
  if trend == "bullish":
16
- base = 0.8
17
  elif trend == "ranging":
18
- base = 0.4
19
  else:
20
- base = 0.1
21
 
22
- if structure == 1:
 
23
  base = min(1.0, base + 0.15)
24
- elif structure == -1:
25
- base = max(0.0, base - 0.15)
26
-
27
- if vol_expanding:
28
- base = max(0.0, base - 0.1)
29
-
30
- if vol_contracting and trend == "bullish":
 
 
 
 
 
 
 
 
 
 
31
  base = max(0.0, base - 0.05)
32
 
33
- if atr_trend == "rising" and trend == "bullish":
34
- base = min(1.0, base + 0.05)
35
-
36
  return float(np.clip(base, 0.0, 1.0))
37
 
38
 
@@ -46,33 +74,50 @@ def score_token(
46
  "regime_score": 0.0,
47
  "volume_score": 0.0,
48
  "structure_score": 0.0,
 
49
  "total_score": 0.0,
50
  }
51
 
52
  regime_score = float(np.clip(regime_data.get("regime_score", 0.0), 0.0, 1.0))
53
- volume_score = float(np.clip(volume_data.get("volume_score", 0.0), 0.0, 1.0))
54
  structure_score = compute_structure_score(regime_data)
55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  total_score = (
57
  regime_score * WEIGHT_REGIME
58
  + volume_score * WEIGHT_VOLUME
59
  + structure_score * WEIGHT_STRUCTURE
 
60
  )
61
 
62
- climax_penalty = 0.15 if volume_data.get("climax", False) else 0.0
63
- total_score = float(np.clip(total_score - climax_penalty, 0.0, 1.0))
 
64
 
65
  return {
66
  "regime_score": round(regime_score, 4),
67
  "volume_score": round(volume_score, 4),
68
  "structure_score": round(structure_score, 4),
69
- "total_score": round(total_score, 4),
 
70
  }
71
 
72
 
73
- def rank_tokens(
74
- scored_map: Dict[str, Dict[str, Any]],
75
- ) -> List[Tuple[str, Dict[str, Any]]]:
76
  return sorted(
77
  scored_map.items(),
78
  key=lambda item: item[1].get("total_score", 0.0),
@@ -80,6 +125,20 @@ def rank_tokens(
80
  )
81
 
82
 
83
- def format_score_bar(score: float, width: int = 20) -> str:
 
 
 
 
 
 
 
 
 
 
 
 
84
  filled = int(round(score * width))
85
- return "[" + "█" * filled + "░" * (width - filled) + f"] {score:.2f}"
 
 
 
1
+ """
2
+ scorer.py — Multi-factor scoring with regime confidence as 4th dimension.
3
+
4
+ Key fixes vs prior version:
5
+ - WEIGHT_CONFIDENCE (0.15) added as explicit 4th score axis
6
+ - Absorption hard-zeroes volume_score regardless of other signals
7
+ - Failed breakout penalty applied at scoring level (defence in depth)
8
+ - Structure score uses ADX to weight trend quality, not just HH/HL
9
+ - format_score_bar returns richer display with quality tier label
10
+ """
11
+
12
  from typing import Dict, Any, List, Tuple
13
 
14
  import numpy as np
15
 
16
+ from config import (
17
+ WEIGHT_REGIME,
18
+ WEIGHT_VOLUME,
19
+ WEIGHT_STRUCTURE,
20
+ WEIGHT_CONFIDENCE,
21
+ ADX_TREND_THRESHOLD,
22
+ ADX_STRONG_THRESHOLD,
23
+ REGIME_CONFIDENCE_MIN,
24
+ )
25
 
26
 
27
  def compute_structure_score(regime_data: Dict[str, Any]) -> float:
 
29
  structure = regime_data.get("structure", 0)
30
  vol_expanding = regime_data.get("vol_expanding", False)
31
  vol_contracting = regime_data.get("vol_contracting", False)
32
+ adx = regime_data.get("adx", 0.0)
33
+ vol_expanding_from_base = regime_data.get("vol_expanding_from_base", False)
34
 
35
  if trend == "bullish":
36
+ base = 0.75
37
  elif trend == "ranging":
38
+ base = 0.35
39
  else:
40
+ base = 0.10
41
 
42
+ # ADX quality modifier
43
+ if adx >= ADX_STRONG_THRESHOLD:
44
  base = min(1.0, base + 0.15)
45
+ elif adx < ADX_TREND_THRESHOLD:
46
+ base = max(0.0, base - 0.20)
47
+
48
+ # Structure alignment
49
+ if structure == 1 and trend == "bullish":
50
+ base = min(1.0, base + 0.12)
51
+ elif structure == -1 and trend == "bullish":
52
+ base = max(0.0, base - 0.20)
53
+ elif structure == -1 and trend == "bearish":
54
+ base = min(1.0, base + 0.12)
55
+
56
+ # Volatility context
57
+ if vol_expanding_from_base:
58
+ base = min(1.0, base + 0.08)
59
+ if vol_expanding and not vol_expanding_from_base:
60
+ base = max(0.0, base - 0.10)
61
+ if vol_contracting:
62
  base = max(0.0, base - 0.05)
63
 
 
 
 
64
  return float(np.clip(base, 0.0, 1.0))
65
 
66
 
 
74
  "regime_score": 0.0,
75
  "volume_score": 0.0,
76
  "structure_score": 0.0,
77
+ "confidence_score": 0.0,
78
  "total_score": 0.0,
79
  }
80
 
81
  regime_score = float(np.clip(regime_data.get("regime_score", 0.0), 0.0, 1.0))
82
+ confidence_score = float(np.clip(regime_data.get("regime_confidence", 0.0), 0.0, 1.0))
83
  structure_score = compute_structure_score(regime_data)
84
 
85
+ raw_volume_score = float(np.clip(volume_data.get("volume_score", 0.0), 0.0, 1.0))
86
+
87
+ # Absorption hard-zeroes the volume signal regardless of other factors
88
+ if volume_data.get("absorption", False):
89
+ volume_score = 0.0
90
+ elif volume_data.get("failed_breakout", False):
91
+ # Failed breakout halves the volume score
92
+ volume_score = raw_volume_score * 0.5
93
+ else:
94
+ volume_score = raw_volume_score
95
+
96
+ # Climax penalty (not a veto here — defence in depth after veto layer)
97
+ if volume_data.get("climax", False):
98
+ volume_score = min(volume_score, 0.30)
99
+
100
  total_score = (
101
  regime_score * WEIGHT_REGIME
102
  + volume_score * WEIGHT_VOLUME
103
  + structure_score * WEIGHT_STRUCTURE
104
+ + confidence_score * WEIGHT_CONFIDENCE
105
  )
106
 
107
+ # Confidence multiplier: low confidence compresses total score
108
+ if confidence_score < REGIME_CONFIDENCE_MIN:
109
+ total_score *= confidence_score / REGIME_CONFIDENCE_MIN
110
 
111
  return {
112
  "regime_score": round(regime_score, 4),
113
  "volume_score": round(volume_score, 4),
114
  "structure_score": round(structure_score, 4),
115
+ "confidence_score": round(confidence_score, 4),
116
+ "total_score": round(float(np.clip(total_score, 0.0, 1.0)), 4),
117
  }
118
 
119
 
120
+ def rank_tokens(scored_map: Dict[str, Dict[str, Any]]) -> List[Tuple[str, Dict[str, Any]]]:
 
 
121
  return sorted(
122
  scored_map.items(),
123
  key=lambda item: item[1].get("total_score", 0.0),
 
125
  )
126
 
127
 
128
+ def quality_tier(score: float) -> str:
129
+ if score >= 0.80:
130
+ return "A+"
131
+ if score >= 0.65:
132
+ return "A"
133
+ if score >= 0.50:
134
+ return "B"
135
+ if score >= 0.35:
136
+ return "C"
137
+ return "D"
138
+
139
+
140
+ def format_score_bar(score: float, width: int = 18) -> str:
141
  filled = int(round(score * width))
142
+ bar = "█" * filled + "░" * (width - filled)
143
+ tier = quality_tier(score)
144
+ return f"[{bar}] {score:.3f} ({tier})"