@@ -132,7 +132,7 @@ def _load_rule_rows(path: Path, *, configs: Sequence[str], max_rules: int) -> li
132132 selected = [row for name in wanted for row in rows if row .get ("config" ) == name ]
133133 else :
134134 def key (row : dict [str , str ]) -> float :
135- dd = float (row .get ("p90_dd_pct" ) or 100.0 )
135+ dd = float (row .get ("worst_dd_pct" ) or row . get ( " p90_dd_pct" ) or 100.0 )
136136 median = float (row .get ("median_monthly_pct" ) or - 999.0 )
137137 p10 = float (row .get ("p10_monthly_pct" ) or - 999.0 )
138138 neg = int (float (row .get ("neg_windows" ) or 99 ))
@@ -285,6 +285,21 @@ def _build_bank(
285285 return ScoreBank (names = names , scores = scores )
286286
287287
288+ def _apply_excluded_symbols (bank : ScoreBank , data : MktdData , excluded_symbols : Sequence [str ]) -> ScoreBank :
289+ excluded = {str (symbol ).strip ().upper () for symbol in excluded_symbols if str (symbol ).strip ()}
290+ if not excluded :
291+ return bank
292+ symbols = [str (symbol ).upper () for symbol in data .symbols ]
293+ missing = sorted (excluded - set (symbols ))
294+ if missing :
295+ raise ValueError (f"excluded symbols not present in data: { ', ' .join (missing )} " )
296+ scores = np .asarray (bank .scores , dtype = np .float64 ).copy ()
297+ for idx , symbol in enumerate (symbols ):
298+ if symbol in excluded :
299+ scores [:, :, idx ] = np .nan
300+ return ScoreBank (names = list (bank .names ), scores = scores )
301+
302+
288303def _combine_scores (bank : ScoreBank , candidate : Candidate ) -> np .ndarray :
289304 weights = _softmax (candidate .logits )
290305 return np .tensordot (weights , bank .scores , axes = (0 , 0 )).astype (np .float64 , copy = False )
@@ -760,6 +775,7 @@ def _summarise_results(
760775 "neg_windows" : int (np .sum (returns < 0.0 )),
761776 "windows" : int (returns .size ),
762777 "p90_dd_pct" : float (100.0 * np .percentile (maxdds , 90 )),
778+ "worst_dd_pct" : float (100.0 * np .max (maxdds )) if maxdds .size else 0.0 ,
763779 "median_smooth" : float (np .percentile (np .asarray (smooths , dtype = np .float64 ), 50 )),
764780 "median_ulcer" : float (np .percentile (np .asarray (ulcers , dtype = np .float64 ), 50 )),
765781 "median_sortino" : float (np .percentile (sortinos , 50 )),
@@ -1479,6 +1495,7 @@ def _fieldnames() -> list[str]:
14791495 "neg_windows" ,
14801496 "windows" ,
14811497 "p90_dd_pct" ,
1498+ "worst_dd_pct" ,
14821499 "median_smooth" ,
14831500 "median_ulcer" ,
14841501 "median_sortino" ,
@@ -1509,6 +1526,11 @@ def main() -> int:
15091526 parser .add_argument ("--xgb-rounds" , type = int , default = 80 )
15101527 parser .add_argument ("--xgb-device" , default = "cuda" )
15111528 parser .add_argument ("--xgb-model-dir" , type = Path , default = None )
1529+ parser .add_argument (
1530+ "--exclude-symbols" ,
1531+ default = "" ,
1532+ help = "Comma-separated symbols to remove from all score channels before search/evaluation." ,
1533+ )
15121534 parser .add_argument ("--no-handcrafted" , action = "store_true" )
15131535 parser .add_argument ("--out" , type = Path , default = Path ("analysis/binance33_meta_anneal.csv" ))
15141536 parser .add_argument ("--eval-days" , type = int , default = 120 )
@@ -1580,6 +1602,10 @@ def main() -> int:
15801602 )
15811603 if train_bank .names != val_bank .names :
15821604 raise RuntimeError ("train/validation score banks have different channels" )
1605+ excluded_symbols = _parse_str_list (args .exclude_symbols )
1606+ if excluded_symbols :
1607+ train_bank = _apply_excluded_symbols (train_bank , train_data , excluded_symbols )
1608+ val_bank = _apply_excluded_symbols (val_bank , val_data , excluded_symbols )
15831609
15841610 gross_grid = _parse_float_list (args .gross_grid )
15851611 max_weight_grid = _parse_float_list (args .max_weight_grid )
0 commit comments