self = lpla_2025_11_03_json = {'audit_trail': [], 'charts': {'LPLA': {'2025-11-03': 'reports/backtest/LPLA-2025-11-03.png'}}, 'created_at': '2026-01-02T04:38:02.051713+00:00', 'generated_at': '2026-01-02T04:38:02.051713+00:00', ...} mock_context = def test_lpla_2025_11_03_penalty(self, lpla_2025_11_03_json, mock_context): """LPLA 2025-11-03 should trigger morning pull-up + afternoon fade penalty.""" service = RankingService() row = lpla_2025_11_03_json["rows"][0] symbol = row["symbol"] > result = service.calculate_ranking( symbol=symbol, row=row, state={}, ctx=mock_context, ) lpla_2025_11_03_json = {'audit_trail': [], 'charts': {'LPLA': {'2025-11-03': 'reports/backtest/LPLA-2025-11-03.png'}}, 'created_at': '2026-01-02T04:38:02.051713+00:00', 'generated_at': '2026-01-02T04:38:02.051713+00:00', ...} mock_context = row = {'atr': 12.090528571428573, 'atr_pct': 0.03190618190591801, 'c1_to_c3_slope': 0.036601, 'c1_to_c3_slope_override': True, ...} self = service = symbol = 'LPLA' tests/test_morning_pullup_afternoon_fade_penalty.py:76: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ rtrader/services/ranking_service.py:1616: in calculate_ranking self._reconcile_adjustments(symbol, row, row_details, boosts, penalties) _capture_delta = ._capture_delta at 0x14e252980> _primary_flow_meta = {'bias': 0.9306066160174535, 'direction': 'converging_up', 'max_cmf': 0.53686489213909, 'max_time': '14:41', ...} _primary_flow_trend = 'positive' _track_rule = ._track_rule at 0x14e252660> atr_data = {'close_atr_ratio': None, 'high_atr_ratio': None, 'low_atr_ratio': None} base_score = 0.0 boosts = {'atr_priority_reward': 6.381236381183602, 'cmf_convergence_signal_inflow_reward': 89.21691661427266, 'cmf_trunk_avg_reward': 39.21691661427266, 'post_c3_boost': 8.0, ...} calculate_atr_ratios_func = None ctx = is_index_symbol = False ledger = penalties = {'afternoon_cmf_volatility_penalty': 6.007595272883605, 'afternoon_peak_fade_penalty': 25.0, 'cmf_wave_expansion_penalty': 23.464516734523837, 'morning_pullup_afternoon_fade_penalty': 78.28840970350397, ...} row = {'atr': 12.090528571428573, 'atr_pct': 0.03190618190591801, 'c1_to_c3_slope': 0.036601, 'c1_to_c3_slope_override': True, ...} row_details = {'_atr_data': {'close_atr_ratio': None, 'high_atr_ratio': None, 'low_atr_ratio': None}, '_direction_trace_debug': ['in...tion': 'converging_up', 'max_cmf': 0.53686489213909, 'max_time': '14:41', ...}, '_primary_flow_trend': 'positive', ...} self = state = {} symbol = 'LPLA' symbol_upper = 'LPLA' rtrader/services/ranking_service.py:1818: in _reconcile_adjustments apply_network_reconciliation( boosts = {'atr_priority_reward': 6.381236381183602, 'cmf_convergence_signal_inflow_reward': 89.21691661427266, 'cmf_trunk_avg_reward': 39.21691661427266, 'post_c3_boost': 8.0, ...} penalties = {'afternoon_cmf_volatility_penalty': 6.007595272883605, 'afternoon_peak_fade_penalty': 25.0, 'cmf_wave_expansion_penalty': 23.464516734523837, 'morning_pullup_afternoon_fade_penalty': 78.28840970350397, ...} row = {'atr': 12.090528571428573, 'atr_pct': 0.03190618190591801, 'c1_to_c3_slope': 0.036601, 'c1_to_c3_slope_override': True, ...} row_details = {'_atr_data': {'close_atr_ratio': None, 'high_atr_ratio': None, 'low_atr_ratio': None}, '_direction_trace_debug': ['in...tion': 'converging_up', 'max_cmf': 0.53686489213909, 'max_time': '14:41', ...}, '_primary_flow_trend': 'positive', ...} self = symbol = 'LPLA' _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ symbol = 'LPLA' boosts = {'atr_priority_reward': 6.381236381183602, 'cmf_convergence_signal_inflow_reward': 89.21691661427266, 'cmf_trunk_avg_reward': 39.21691661427266, 'post_c3_boost': 8.0, ...} penalties = {'afternoon_cmf_volatility_penalty': 6.007595272883605, 'afternoon_peak_fade_penalty': 25.0, 'cmf_wave_expansion_penalty': 23.464516734523837, 'morning_pullup_afternoon_fade_penalty': 78.28840970350397, ...} log_fn = def apply_network_reconciliation( symbol: str, boosts: Dict[str, float], penalties: Dict[str, float], log_fn: Any = None, ) -> None: """ Apply the declarative network configuration to reconcile boosts and penalties. This is the core engine that processes NETWORK_CONFIG to: 1. Apply inhibit connections (penalty blocks boost) 2. Apply protection signals (skip connections) 3. Apply gate signal reductions 4. Apply boost-drops-penalty rules 5. Apply scaling rules Args: symbol: Stock symbol (for logging) boosts: Dict of boost names to values (modified in-place) penalties: Dict of penalty names to values (modified in-place) log_fn: Optional logging function (for debug output) """ def _log(msg: str, *args): if log_fn: log_fn(msg, *args) def _drop_boost(boost_key: str, reason: str) -> None: removed = boosts.pop(boost_key, None) if removed: _log( "[Network] {} | {} NEGATED by {} | removed={:.1f}", symbol, boost_key, reason, removed, ) def _drop_penalty(penalty_key: str, reason: str) -> None: removed = penalties.pop(penalty_key, None) if removed: _log( "[Network] {} | {} REMOVED by {} | removed={:.1f}", symbol, penalty_key, reason, removed, ) # 1. APPLY GATE SIGNALS # Gate signals can reduce or drop penalties for gate in NETWORK_CONFIG.get("gate_signals", []): if boosts.get(gate.name, 0) > 0: # Drop penalties specified by gate for penalty_to_drop in gate.drops: _drop_penalty(penalty_to_drop, f"gate:{gate.name}") # Reduce penalties specified by gate for penalty_key, factor in gate.reduces.items(): if penalty_key in penalties: original = penalties[penalty_key] reduced = original * factor penalties[penalty_key] = reduced _log( "[Network] {} | {} reduced by gate {} | factor={:.2f} orig={:.1f} new={:.1f}", symbol, penalty_key, gate.name, factor, original, reduced, ) # 2. BUILD PROTECTION LOOKUP # Which boosts are protected from which penalties? # Uses evolved thresholds when available protection_lookup: Dict[str, Dict[str, bool]] = {} # {boost: {penalty: True}} for protection in NETWORK_CONFIG.get("protection_signals", []): # Get evolved threshold (may be adjusted by evolution) effective_threshold = get_effective_protection_threshold(protection.source) if boosts.get(protection.source, 0) >= effective_threshold: for protected_boost in protection.protects: if protected_boost not in protection_lookup: protection_lookup[protected_boost] = {} for penalty_source in protection.from_penalties: protection_lookup[protected_boost][penalty_source] = True _log( "[Network] {} | {} protected from {} by {} (val={:.1f} >= {:.1f})", symbol, protected_boost, penalty_source, protection.source, boosts.get(protection.source, 0), effective_threshold, ) # 3. APPLY INHIBIT CONNECTIONS # Process penalty → boost negations, respecting protections, exceptions, and evolution for conn in NETWORK_CONFIG.get("inhibit_connections", []): # Check if this connection has been BROKEN by evolution if is_inhibit_disabled(conn.source): _log( "[Network] {} | inhibit connection {} DISABLED by evolution", symbol, conn.source, ) continue # Get evolved weight (may be scaled) evolved_weight = get_effective_inhibit_weight(conn.source) if evolved_weight == 0.0: continue # Connection broken if penalties.get(conn.source, 0) > 0: for target in conn.targets: # Check if target is a gate exception if target in conn.exceptions: _log( "[Network] {} | {} NOT negated by {} (gate exception)", symbol, target, conn.source, ) continue # Check if target is protected if target in protection_lookup and conn.source in protection_lookup[target]: _log( "[Network] {} | {} NOT negated by {} (protected)", symbol, target, conn.source, ) continue # If evolved weight is partial (between -1 and 0), reduce instead of drop if evolved_weight > -1.0 and target in boosts: reduction_factor = abs(evolved_weight) original = boosts[target] reduced = original * (1 - reduction_factor) boosts[target] = reduced _log( "[Network] {} | {} REDUCED by {} (evolved weight={:.2f}) | {:.1f}→{:.1f}", symbol, target, conn.source, evolved_weight, original, reduced, ) else: _drop_boost(target, conn.source) # 4. APPLY BOOST-DROPS-PENALTY RULES for boost_key, penalties_to_drop in NETWORK_CONFIG.get("boost_drops_penalty", {}).items(): if boosts.get(boost_key, 0) > 0: for penalty_key in penalties_to_drop: _drop_penalty(penalty_key, boost_key) # 5. APPLY BOOST-INHIBITS-BOOST RULES # When one boost is present, it can negate another boost for source_boost, targets in NETWORK_CONFIG.get("boost_inhibits_boost", {}).items(): if boosts.get(source_boost, 0) > 0: for target_boost in targets: if target_boost in boosts: removed = boosts.pop(target_boost) _log( "[Network] {} | {} DROPPED by {} | removed={:.1f}", symbol, target_boost, source_boost, removed, ) # 6. APPLY PENALTY-INHIBITS-PENALTY RULES # When one penalty is present, it can negate another penalty (to avoid double-penalizing) for source_penalty, targets in NETWORK_CONFIG.get("penalty_inhibits_penalty", {}).items(): if penalties.get(source_penalty, 0) > 0: for target_penalty in targets: if target_penalty in penalties: removed = penalties.pop(target_penalty) _log( "[Network] {} | {} DROPPED by {} | removed={:.1f}", symbol, target_penalty, source_penalty, removed, ) # 7. APPLY BOOST-REDUCES-PENALTY RULES (weighted adjustments) # Instead of binary drop/keep, use continuous weights like real neural networks # reduction_factor: 0.0 = drop entirely, 0.5 = reduce 50%, 1.0 = no change # GA can tune these via: # - CURRENT_GENOME.reduction_factors["{source}:{target}"] = direct factor override # - CURRENT_GENOME.inhibit_weights[source] = exponential weight modifier for source_boost, penalty_weights in NETWORK_CONFIG.get("boost_reduces_penalty", {}).items(): if boosts.get(source_boost, 0) > 0: for penalty_key, default_factor in penalty_weights.items(): if penalty_key in penalties: original = penalties[penalty_key] # Priority 1: Direct factor override from genome genome_key = f"{source_boost}:{penalty_key}" if genome_key in CURRENT_GENOME.reduction_factors: effective_factor = CURRENT_GENOME.reduction_factors[genome_key] # Priority 2: Exponential weight modifier elif source_boost in CURRENT_GENOME.inhibit_weights: genome_weight = CURRENT_GENOME.inhibit_weights[source_boost] effective_factor = default_factor ** genome_weight effective_factor = max(0.0, min(1.0, effective_factor)) # Default: use config value else: effective_factor = default_factor reduced = original * effective_factor penalties[penalty_key] = reduced reduction_pct = int((1 - effective_factor) * 100) _log( "[Network] {} | {} REDUCED {}% by {} | {:.1f}→{:.1f}", symbol, penalty_key, reduction_pct, source_boost, original, reduced, ) # 8. APPLY SCALING RULES (legacy format) for rule_key, rule_config in NETWORK_CONFIG.get("scaling_rules", {}).items(): # Check if condition is met if "_strong" in rule_key: # Threshold-based rule base_key = rule_key.replace("_strong", "") threshold = rule_config.get("threshold", 0) if boosts.get(base_key, 0) >= threshold: for target, factor in rule_config.get("reduce", {}).items(): if target in penalties: original = penalties[target] reduced = original * factor penalties[target] = reduced _log( "[Network] {} | {} scaled by {} | val≥{} factor={:.2f} {:.1f}→{:.1f}", symbol, target, rule_key, threshold, factor, original, reduced, ) if target in boosts: original = boosts[target] reduced = original * factor boosts[target] = reduced _log( "[Network] {} | {} scaled by {} | val≥{} factor={:.2f} {:.1f}→{:.1f}", symbol, target, rule_key, threshold, factor, original, reduced, ) else: # Penalty-based rule if penalties.get(rule_key, 0) > 0: for target, factor in rule_config.get("reduce", {}).items(): if target in boosts: original = boosts[target] reduced = original * factor boosts[target] = reduced _log( "[Network] {} | {} reduced by {} | factor={:.2f} {:.1f}→{:.1f}", symbol, target, rule_key, factor, original, reduced, ) # 9. APPLY PENALTY-REDUCES-BOOST RULES (simple, no protection) # Penalty presence reduces (not drops) a boost # GA can tune these factors via CURRENT_GENOME.reduction_factors for penalty_key, boost_factors in NETWORK_CONFIG.get("penalty_reduces_boost", {}).items(): if penalties.get(penalty_key, 0) > 0: for boost_key, default_factor in boost_factors.items(): if boost_key in boosts: # Use GA-evolved factor if available genome_key = f"{penalty_key}:{boost_key}" factor = CURRENT_GENOME.reduction_factors.get(genome_key, default_factor) original = boosts[boost_key] reduced = original * factor boosts[boost_key] = reduced reduction_pct = int((1 - factor) * 100) _log( "[Network] {} | {} REDUCED {}% by {} | {:.1f}→{:.1f}", symbol, boost_key, reduction_pct, penalty_key, original, reduced, ) # 9b. APPLY PROTECTED REDUCTIONS (penalty reduces boost, but some are protected) for penalty_key, config in NETWORK_CONFIG.get("protected_reductions", {}).items(): if penalties.get(penalty_key, 0) > 0: targets = config.get("targets", {}) protections = config.get("protections", {}) # Build protection lookup protected_boosts = set() for protector_boost, protected_list in protections.items(): if boosts.get(protector_boost, 0) > 0: protected_boosts.update(protected_list) _log( "[Network] {} | {} protects {} from {} reduction", symbol, protector_boost, protected_list, penalty_key, ) for boost_key, factor in targets.items(): if boost_key in boosts: if boost_key in protected_boosts: _log( "[Network] {} | {} NOT reduced by {} (protected) | current={:.1f}", symbol, boost_key, penalty_key, boosts[boost_key], ) continue original = boosts[boost_key] reduced = original * factor boosts[boost_key] = reduced reduction_pct = int((1 - factor) * 100) _log( "[Network] {} | {} REDUCED {}% by {} | {:.1f}→{:.1f}", symbol, boost_key, reduction_pct, penalty_key, original, reduced, ) # 10. APPLY BOOST-AMPLIFIES-BOOST RULES # When boost A is present, amplify boost B for source_boost, boost_factors in NETWORK_CONFIG.get("boost_amplifies_boost", {}).items(): if boosts.get(source_boost, 0) > 0: for target_boost, multiplier in boost_factors.items(): if target_boost in boosts: original = boosts[target_boost] amplified = original * multiplier boosts[target_boost] = amplified amplify_pct = int((multiplier - 1) * 100) _log( "[Network] {} | {} AMPLIFIED +{}% by {} | {:.1f}→{:.1f}", symbol, target_boost, amplify_pct, source_boost, original, amplified, ) # 11. APPLY BOOST-REDUCES-BOOST RULES # Avoid double-boosting for same signal # GA can tune these factors via CURRENT_GENOME.reduction_factors for source_boost, boost_factors in NETWORK_CONFIG.get("boost_reduces_boost", {}).items(): if boosts.get(source_boost, 0) > 0: for target_boost, default_factor in boost_factors.items(): if target_boost in boosts: # Use GA-evolved factor if available genome_key = f"{source_boost}:{target_boost}" factor = CURRENT_GENOME.reduction_factors.get(genome_key, default_factor) original = boosts[target_boost] reduced = original * factor boosts[target_boost] = reduced reduction_pct = int((1 - factor) * 100) _log( "[Network] {} | {} REDUCED {}% by {} (avoid double-boost) | {:.1f}→{:.1f}", symbol, target_boost, reduction_pct, source_boost, original, reduced, ) # 12. APPLY DYNAMIC SCALING # Scale based on actual value (not just presence) for source_boost, targets in NETWORK_CONFIG.get("dynamic_scaling", {}).items(): source_value = boosts.get(source_boost, 0) if source_value > 0: for target_penalty, config in targets.items(): if target_penalty in penalties: scale_by = config.get("scale_by", 10.0) max_reduction = config.get("max_reduction", 1.0) # Reduction factor: 1.0 (no reduction at 0) to (1-max_reduction) at scale_by # Example: score=5, scale_by=10 → factor=0.5 (50% reduction) reduction_pct = min(source_value / scale_by, 1.0) * max_reduction factor = 1.0 - reduction_pct factor = max(0.0, min(1.0, factor)) original = penalties[target_penalty] scaled = original * factor penalties[target_penalty] = scaled _log( "[Network] {} | {} dynamically scaled by {} | value={:.1f} factor={:.2f} {:.1f}→{:.1f}", symbol, target_penalty, source_boost, source_value, factor, original, scaled, ) # 13. APPLY COMPOUND INHIBITS (multi-input neurons) # These require multiple signals to be present before firing for compound in NETWORK_CONFIG.get("compound_inhibits", []): # Check if compound signal should fire # Collect all boost sources and penalty sources boost_sources = compound.boost_sources or [] penalty_sources = compound.penalty_sources or [] if compound.require_all: # AND logic: all boost sources must be present AND all penalty sources must be present boosts_present = all(boosts.get(src, 0) > 0 for src in boost_sources) penalties_present = all(penalties.get(src, 0) > 0 for src in penalty_sources) if not (boosts_present and penalties_present): continue else: # Weighted sum logic: sum of (weight × present) must exceed threshold all_sources = boost_sources + penalty_sources weighted_sum = sum( compound.source_weights.get(src, 1.0) for src in all_sources if boosts.get(src, 0) > 0 or penalties.get(src, 0) > 0 ) if compound.threshold and weighted_sum < compound.threshold: continue # Compound signal activated! Apply inhibitions source_summary = "+".join((boost_sources + penalty_sources)[:2]) if len(boost_sources) + len(penalty_sources) > 2: source_summary += "..." for penalty_key in compound.drops_penalties: if penalty_key in penalties: removed = penalties.pop(penalty_key) _log( "[Network] {} | {} DROPPED by compound {} | sources={} | removed={:.1f}", symbol, penalty_key, compound.name, source_summary, removed, ) for boost_key in compound.drops_boosts: if boost_key in boosts: removed = boosts.pop(boost_key) _log( "[Network] {} | {} DROPPED by compound {} | sources={} | removed={:.1f}", symbol, boost_key, compound.name, source_summary, removed, ) # Apply reductions (not just drops) for penalty_key, factor in compound.reduces_penalties.items(): if penalty_key in penalties: original = penalties[penalty_key] reduced = original * factor penalties[penalty_key] = reduced reduction_pct = int((1 - factor) * 100) _log( "[Network] {} | {} REDUCED {}% by compound {} | {:.1f}→{:.1f}", symbol, penalty_key, reduction_pct, compound.name, original, reduced, ) for boost_key, factor in compound.reduces_boosts.items(): if boost_key in boosts: original = boosts[boost_key] reduced = original * factor boosts[boost_key] = reduced reduction_pct = int((1 - factor) * 100) _log( "[Network] {} | {} REDUCED {}% by compound {} | {:.1f}→{:.1f}", symbol, boost_key, reduction_pct, compound.name, original, reduced, ) # 14. APPLY DYNAMIC AMPLIFICATION # Amplify boost based on another boost's actual value (not just presence) for source_boost, targets in NETWORK_CONFIG.get("dynamic_amplification", {}).items(): source_value = boosts.get(source_boost, 0) if source_value > 0: for target_boost, config in targets.items(): if target_boost in boosts: base = config.get("base", 1.0) scale_by = config.get("scale_by", 50.0) max_multiplier = config.get("max_multiplier", 2.0) max_value = config.get("max_value", None) # Calculate multiplier: base + (source_value / scale_by) # Example: source=25, scale_by=50, base=1.0 → multiplier=1.5 multiplier = base + (source_value / scale_by) multiplier = min(multiplier, max_multiplier) original = boosts[target_boost] amplified = original * multiplier # Apply max value cap if specified if max_value is not None: amplified = min(amplified, max_value) boosts[target_boost] = amplified amplify_pct = int((multiplier - 1) * 100) _log( "[Network] {} | {} dynamically AMPLIFIED +{}% by {} (val={:.1f}) | {:.1f}→{:.1f}", symbol, target_boost, amplify_pct, source_boost, source_value, original, amplified, ) # 15. APPLY PROTECTED DYNAMIC REDUCTIONS # Penalty reduces multiple boosts, but some boosts can be protected for penalty_key, config in NETWORK_CONFIG.get("protected_dynamic_reductions", {}).items(): penalty_value = penalties.get(penalty_key, 0) if penalty_value > 0: targets = config.get("targets", []) max_reduction = config.get("max_reduction", 0.1) scale_by = config.get("scale_by", 10.0) protections = config.get("protections", {}) # Calculate reduction factor based on penalty value # Example: penalty=10, scale_by=10, max_reduction=0.1 → factor=0.9 (10% reduction) reduction_pct = min(penalty_value / scale_by, 1.0) * max_reduction factor = 1.0 - reduction_pct factor = max(1.0 - max_reduction, min(1.0, factor)) # Build protection lookup: which boosts are protected by which boosts? protected_targets = set() for protector_boost, protected_list in protections.items(): if boosts.get(protector_boost, 0) > 0: protected_targets.update(protected_list) _log( "[Network] {} | {} protects {} from {} reduction", symbol, protector_boost, protected_list, penalty_key, ) for target_boost in targets: if target_boost in boosts: if target_boost in protected_targets: _log( "[Network] {} | {} NOT reduced by {} (protected) | current={:.1f}", symbol, target_boost, penalty_key, boosts[target_boost], ) continue original = boosts[target_boost] reduced = original * factor boosts[target_boost] = reduced actual_reduction_pct = int((1 - factor) * 100) _log( "[Network] {} | {} REDUCED {}% by {} (val={:.1f}) | {:.1f}→{:.1f}", > symbol, target_boost, actual_reduction_pct, penalty_key, penalty_value, original, reduced, ^^^^^^^^^^^^^^^^^^^^ ) E UnboundLocalError: cannot access local variable 'actual_reduction_pct' where it is not associated with a value _drop_boost = ._drop_boost at 0x14e253e20> _drop_penalty = ._drop_penalty at 0x14e253240> _log = ._log at 0x14e2534c0> amplified = 89.21691661427266 amplify_pct = 78 base = 1.0 base_key = 'wave_direction_momentum_boost' boost_factors = {'volume_cluster_stair_up_reward': 0.5} boost_key = 'cmf_convergence_signal_inflow_reward' boost_sources = ['primary_cmf_trend_reward', 'price_cmf_bullish_convergence_boost'] boosts = {'atr_priority_reward': 6.381236381183602, 'cmf_convergence_signal_inflow_reward': 89.21691661427266, 'cmf_trunk_avg_reward': 39.21691661427266, 'post_c3_boost': 8.0, ...} boosts_present = True compound = CompoundInhibit(name='trend_convergence_bullish_synergy', boost_sources=['primary_cmf_trend_reward', 'price_cmf_bullis...ty', 'afternoon_cmf_close_below_mean_penalty'], drops_boosts=[], reduces_penalties={}, reduces_boosts={}, sources=None) config = {'max_reduction': 0.1, 'protections': {'v_reverse_money_flow_recovery_boost': ['cmf_convergence_signal_inflow_reward']...['afternoon_cmf_close_above_mean_boost', 'cmf_convergence_signal_inflow_reward', 'cmf_wave_volatility_decrease_boost']} conn = InhibitConnection(source='cmf_trunk_avg_penalty', targets=['volume_cluster_consecutive_up_stair_reward', 'volume_cluster_consecutive_reward'], weight=-1.0, exceptions=[]) default_factor = 0.5 effective_factor = 0.5 effective_threshold = 0.01 evolved_weight = -1.0 factor = 0.9 gate = GateSignal(name='primary_cmf_trend_reward', reduces={'afternoon_peak_fade_penalty': 0.5}, drops=['morning_dump_penalty...ility_increase_penalty', 'cmf_volatility_penalty', 'session_drawdown_guard', 'cmf_late_diverging_uncertainty_penalty']) genome_key = 'price_cmf_bullish_convergence_boost:afternoon_cmf_close_below_mean_penalty' log_fn = max_multiplier = 2.0 max_reduction = 0.1 max_value = None multiplier = 1.7843383322854534 original = 50.0 penalties = {'afternoon_cmf_volatility_penalty': 6.007595272883605, 'afternoon_peak_fade_penalty': 25.0, 'cmf_wave_expansion_penalty': 23.464516734523837, 'morning_pullup_afternoon_fade_penalty': 78.28840970350397, ...} penalties_present = True penalties_to_drop = ['price_cmf_ratio_penalty'] penalty_key = 'volume_cluster_gap_to_market_close_penalty' penalty_sources = [] penalty_to_drop = 'cmf_late_diverging_uncertainty_penalty' penalty_value = 10.0 penalty_weights = {'afternoon_cmf_close_below_mean_penalty': 0.5, 'morning_pullup_afternoon_fade_penalty': 0.5} protected_boosts = set() protected_list = ['cmf_convergence_signal_inflow_reward'] protected_targets = set() protection = ProtectionSignal(source='v_reverse_money_flow_recovery_boost', threshold=0.01, protects=['cmf_convergence_signal_inflow_reward'], from_penalties=['afternoon_cmf_close_below_mean_penalty']) protection_lookup = {} protections = {'v_reverse_money_flow_recovery_boost': ['cmf_convergence_signal_inflow_reward']} protector_boost = 'v_reverse_money_flow_recovery_boost' reduced = 50.0 reduction_pct = 0.1 removed = 50.0 rule_config = {'reduce': {'post_c3_penalty': 0.5}, 'threshold': 25} rule_key = 'wave_direction_momentum_boost_strong' scale_by = 10.0 source_boost = 'late_day_volume_cluster_two_bars_score' source_penalty = 'primary_cmf_trend_penalty' source_summary = 'primary_cmf_trend_reward+price_cmf_bullish_convergence_boost' source_value = 0 symbol = 'LPLA' target = 'stable_cmf_accumulation_boost' target_boost = 'afternoon_cmf_close_above_mean_boost' targets = ['afternoon_cmf_close_above_mean_boost', 'cmf_convergence_signal_inflow_reward', 'cmf_wave_volatility_decrease_boost'] threshold = 25 rtrader/services/ranking_network.py:2976: UnboundLocalError