| 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
|