New paste Repaste Download
self = <tests.test_morning_pullup_afternoon_fade_penalty.TestMorningPullupAfternoonFadePenalty object at 0x14a1e83b0>
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 = <MagicMock id='5606466496'>
    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 = <MagicMock id='5606466496'>
row        = {'atr': 12.090528571428573, 'atr_pct': 0.03190618190591801, 'c1_to_c3_slope': 0.036601, 'c1_to_c3_slope_override': True, ...}
self       = <tests.test_morning_pullup_afternoon_fade_penalty.TestMorningPullupAfternoonFadePenalty object at 0x14a1e83b0>
service    = <rtrader.services.ranking_service.RankingService object at 0x14e2be030>
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 = <function RankingService.calculate_ranking.<locals>._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 = <function RankingService.calculate_ranking.<locals>._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        = <MagicMock id='5606466496'>
        is_index_symbol = False
        ledger     = <rtrader.services.ranking_service.ScoreLedger object at 0x14e2bfbf0>
        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       = <rtrader.services.ranking_service.RankingService object at 0x14e2be030>
        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       = <rtrader.services.ranking_service.RankingService object at 0x14e2be030>
        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 = <function _log_ranking_debug at 0x1466e0540>
    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 = <function apply_network_reconciliation.<locals>._drop_boost at 0x14e253e20>
_drop_penalty = <function apply_network_reconciliation.<locals>._drop_penalty at 0x14e253240>
_log       = <function apply_network_reconciliation.<locals>._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     = <function _log_ranking_debug at 0x1466e0540>
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
Filename: None. Size: 34kb. View raw, , hex, or download this file.

This paste expires on 2026-01-11 07:01:43.673838+00:00. Pasted through deprecated-web.