# Implementation Plan: Signal Math Upgrade

## Overview

Upgrade the Stonks Oracle signal processing pipeline from deterministic heuristic formulas to a probabilistic, regime-aware, and adaptive mathematical framework. Implementation proceeds in layers: foundations (config, schemas, new modules), then each pipeline stage (scoring → trend assembly → macro → competitive → projection → recommendation), then integration wiring, and finally testing. All changes are gated behind the `probabilistic_scoring_enabled` feature flag.

## Tasks

- [ ] 1. Foundation: Configuration and schema extensions
  - [x] 1.1 Extend `ScoringConfig` with probabilistic parameters in `services/aggregation/scoring.py`
    - Add `probabilistic: bool = False` toggle field
    - Add sigmoid gate parameters: `sigmoid_steepness`, `sigmoid_midpoint`
    - Add information gain parameters: `info_gain_lambda`, `info_gain_max`, `default_base_rate`
    - Add adaptive decay parameters: `adaptive_decay_impact_scale`, `adaptive_decay_surprise_scale`, `adaptive_decay_market_scale`
    - Add regime multiplier parameters: `regime_return_weight`, `regime_volume_weight`, `regime_multiplier_max`
    - All new fields must have defaults matching the design document values
    - _Requirements: 2.5, 3.1, 5.1, 6.3, 16.1_

  - [x] 1.2 Extend `SignalWeight` and `WeightedSignal` dataclasses in `services/aggregation/scoring.py`
    - Add optional fields to `SignalWeight`: `sigmoid_gate`, `info_gain_factor`, `source_accuracy_factor`, `regime_multiplier`
    - Add optional fields to `WeightedSignal`: `info_gain_factor`, `source_accuracy_factor`, `adaptive_half_life`
    - All new fields must have defaults (None or 1.0) for backward compatibility
    - _Requirements: 16.1, 2.5, 3.3, 4.2_

  - [x] 1.3 Extend `TrendSummary` Pydantic model in `services/shared/schemas.py`
    - Add optional fields: `p_bull`, `alpha`, `beta_param`, `bayesian_confidence`, `entropy`, `regime`, `pipeline_mode`
    - `pipeline_mode` defaults to `"heuristic"`; all others default to `None`
    - _Requirements: 16.1, 1.6, 9.6_

  - [x] 1.4 Extend `Recommendation` model in `services/shared/schemas.py` (or `services/recommendation/eligibility.py`)
    - Add optional fields: `expected_value`, `p_bull`, `pipeline_mode`
    - `pipeline_mode` defaults to `"heuristic"`; all others default to `None`
    - _Requirements: 16.1, 14.5_

  - [x] 1.5 Add `probabilistic_scoring_enabled` feature flag support in `services/shared/config.py`
    - Read `probabilistic_scoring_enabled` from `risk_configs.config` JSONB
    - Default to `False` when key is missing, value is invalid, or DB is unreachable
    - Propagate flag through `AggregationConfig` dataclass
    - Log which pipeline mode is active at cycle start
    - _Requirements: 16.3, 16.4, 16.5, 16.6, 16.7_

  - [x] 1.6 Create database migration `infra/migrations/034_source_accuracy.sql`
    - Create `source_accuracy` table with columns: `id UUID PRIMARY KEY DEFAULT gen_random_uuid()`, `source_id VARCHAR(200) NOT NULL`, `accuracy_ratio FLOAT NOT NULL DEFAULT 0.5`, `sample_count INTEGER NOT NULL DEFAULT 0`, `last_updated TIMESTAMPTZ`, `created_at TIMESTAMPTZ`
    - Add `UNIQUE(source_id)` constraint and `idx_source_accuracy_source` index
    - _Requirements: 4.5_

- [x] 2. Checkpoint — Verify foundation compiles and existing tests pass
  - Ensure all tests pass, ask the user if questions arise.

- [ ] 3. New module: Bayesian Accumulator (`services/aggregation/bayesian.py`)
  - [x] 3.1 Implement `BayesianPosterior` dataclass and `compute_bayesian_posterior` function
    - Create frozen dataclass with fields: `p_bull`, `alpha`, `beta`, `log_likelihood`, `bayesian_confidence`, `entropy`, `signal_count`
    - Define `PRIOR` class-level constant for uninformative prior (p_bull=0.5, α=1.0, β=1.0, C=0.0, H=1.0)
    - Implement log-likelihood accumulation: `L_t = Σ(w_i · s_i)` using `weight.combined * sentiment_value`
    - Compute `P_bull = σ(L_t)` via sigmoid function
    - Compute Beta posterior: `α = 1 + W_bull`, `β = 1 + W_bear` from positive/negative weight sums
    - Compute Bayesian confidence: `C = 1 - 4αβ/(α+β)²`
    - Compute Shannon entropy via `compute_entropy`
    - Return `PRIOR` for empty signal lists
    - Skip signals with NaN weight or sentiment
    - _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6_

  - [x] 3.2 Implement `compute_entropy` function
    - Shannon entropy: `H = -p·log₂(p) - (1-p)·log₂(1-p)`
    - Return 0.0 for p ≤ 0 or p ≥ 1 (edge cases)
    - Return value in [0, 1] with maximum 1.0 at p=0.5
    - _Requirements: 9.1, 9.7_

  - [x] 3.3 Write property test for sigmoid gate monotonicity
    - **Property 1: Sigmoid Gate Monotonicity**
    - **Validates: Requirements 2.6, 17.1**

  - [x] 3.4 Write property test for Beta posterior evidence accumulation
    - **Property 2: Beta Posterior Evidence Accumulation**
    - **Validates: Requirements 1.3, 17.2**

  - [x] 3.5 Write property test for Bayesian confidence symmetry and divergence
    - **Property 3: Bayesian Confidence Symmetry and Divergence**
    - **Validates: Requirements 1.4, 17.3**

  - [x] 3.6 Write property test for Bayesian posterior round-trip consistency
    - **Property 4: Bayesian Posterior Round-Trip Consistency**
    - **Validates: Requirements 1.7, 17.7**

  - [x] 3.7 Write property test for Shannon entropy range and maximum
    - **Property 8: Shannon Entropy Range and Maximum**
    - **Validates: Requirements 9.7**

  - [x] 3.8 Write property test for Bayesian confidence monotonic with agreeing signals
    - **Property 13: Bayesian Confidence Monotonic with Agreeing Signals**
    - **Validates: Requirements 8.6**

- [ ] 4. New module: Regime Detector (`services/aggregation/regime.py`)
  - [x] 4.1 Implement `MarketRegime` enum, `RegimeClassification` and `RegimeConfig` dataclasses
    - `MarketRegime`: `TREND_FOLLOWING`, `PANIC`, `MEAN_REVERSION`, `UNCERTAINTY`
    - `RegimeClassification`: `regime`, `trend_indicator`, `volatility_ratio`, `bullish_threshold`, `bearish_threshold`, `contradiction_penalty_multiplier`
    - `RegimeConfig`: all configurable parameters with defaults from design
    - _Requirements: 7.3_

  - [x] 4.2 Implement `compute_ema` and `classify_regime` functions
    - `compute_ema`: exponential moving average over last N values
    - `classify_regime`: compute trend indicator `R = sign(EMA_20 - EMA_100)` and volatility ratio `V_r = σ_20 / σ_100`
    - Classification rules: trend-following (R≠0 AND V_r<1.2), panic (V_r>1.5), mean-reversion (R=0 AND V_r<1.0), uncertainty (all other)
    - Adjust thresholds per regime: panic→±0.10, mean-reversion→±0.20, trend-following→±0.15, uncertainty→±0.15 with contradiction multiplier 0.6
    - Default to uncertainty when data is insufficient (<100 days) or σ values are zero
    - _Requirements: 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7, 7.9_

- [ ] 5. New module: Source Accuracy Tracker (`services/aggregation/source_accuracy.py`)
  - [x] 5.1 Implement `SourceAccuracy` dataclass and database functions
    - `SourceAccuracy` dataclass with `source_id`, `accuracy_ratio`, `sample_count`, `last_updated`
    - `accuracy_factor` property: return 1.0 when sample_count < 10, else `0.5 + accuracy_ratio`
    - `fetch_source_accuracy`: batch fetch from `source_accuracy` table via asyncpg
    - `update_source_accuracy`: update accuracy metrics from realized price outcomes
    - Handle DB unreachable: return neutral factor 1.0 for all sources
    - Clamp corrupted accuracy_ratio to [0.0, 1.0]
    - _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5_

- [x] 6. Checkpoint — Verify new modules compile and unit tests pass
  - Ensure all tests pass, ask the user if questions arise.

- [ ] 7. Signal Scorer upgrades (`services/aggregation/scoring.py`)
  - [x] 7.1 Implement sigmoid confidence gate
    - Add `sigmoid_gate(x, steepness, midpoint)` function: `σ(k·(x - midpoint))`
    - When `probabilistic=True`, replace binary gate with sigmoid gate in `compute_signal_weight`
    - When `probabilistic=False`, preserve existing binary gate behavior
    - _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5_

  - [x] 7.2 Implement information gain surprise weighting
    - Add `EVENT_TYPE_BASE_RATES` constant dict and `DEFAULT_BASE_RATE = 0.1`
    - Add `compute_info_gain(event_type, lambda_param, max_gain, default_base_rate)` function: `r = 1 + λ·(-log₂ P(event_type))`, clamped to max 3.0
    - Integrate as multiplicative factor in combined weight when `probabilistic=True`
    - _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5_

  - [x] 7.3 Implement adaptive recency decay
    - Add `compute_adaptive_half_life(base_half_life, impact_score, info_gain_factor, market_multiplier, config)` function
    - Compute `β_impact`, `β_surprise`, `β_market_reaction` scaling factors per design
    - `τ_i = τ_base · (1 + β_impact) · (1 + β_surprise) · (1 + β_market_reaction)`
    - When `probabilistic=True`, use adaptive half-life in `recency_weight`; otherwise use fixed
    - _Requirements: 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7_

  - [x] 7.4 Implement regime multiplier replacing market context multiplier
    - Add `compute_regime_multiplier(returns, volumes, config)` function
    - Compute z-scores for return and volume, then `M_regime = 1 + 0.15·|z_r| + 0.10·|z_v|`
    - Clamp to [1.0, 2.5]; default to 1.0 when data unavailable or σ=0
    - When `probabilistic=True`, use `M_regime` instead of `M_context` in combined weight
    - _Requirements: 6.1, 6.2, 6.3, 6.4, 6.5_

  - [x] 7.5 Integrate source accuracy factor into `compute_signal_weight`
    - Accept optional `source_accuracy_factor` parameter
    - When `probabilistic=True`, multiply into combined weight formula
    - When `probabilistic=False`, ignore (factor = 1.0)
    - _Requirements: 4.2, 4.3_

  - [x] 7.6 Update `compute_signal_weight` to branch on `probabilistic` flag
    - When `probabilistic=True`: use sigmoid gate × recency (adaptive) × credibility × (1 + novelty) × info_gain × source_accuracy × regime_multiplier
    - When `probabilistic=False`: preserve exact current formula (binary gate × recency × credibility × (1 + novelty) × market_context)
    - Populate all new optional fields on `SignalWeight` and `WeightedSignal`
    - _Requirements: 16.4, 16.5_

  - [x] 7.7 Write property test for information gain monotonicity
    - **Property 6: Information Gain Monotonicity**
    - **Validates: Requirements 3.5**

  - [x] 7.8 Write property test for adaptive decay lower bound
    - **Property 5: Adaptive Decay Lower Bound**
    - **Validates: Requirements 5.7, 17.4**

- [ ] 8. Contradiction upgrade (`services/aggregation/contradiction.py`)
  - [x] 8.1 Implement weighted disagreement entropy contradiction
    - Compute `f_pos = W_positive / (W_positive + W_negative)` and `f_neg = 1 - f_pos`
    - Compute `H_contradiction = -f_pos·log₂(f_pos) - f_neg·log₂(f_neg)`
    - Weight by evidence mass: `contradiction_score = H_contradiction · min(1.0, (W_pos + W_neg) / W_threshold)`
    - Return 0.0 when only one direction exists
    - Preserve existing `ContradictionResult` interface
    - When `probabilistic=False`, preserve existing minority/majority ratio behavior
    - _Requirements: 15.1, 15.2, 15.3, 15.4, 15.5, 15.6, 15.7_

  - [x] 8.2 Write property test for contradiction entropy monotonicity
    - **Property 9: Contradiction Entropy Monotonicity**
    - **Validates: Requirements 15.7**

- [ ] 9. Trend Assembly upgrades (`services/aggregation/worker.py`)
  - [x] 9.1 Integrate Bayesian posterior into trend assembly
    - When `probabilistic=True`, call `compute_bayesian_posterior` on merged signals
    - Use Bayesian confidence formula for trend confidence: `0.5 × C_bayesian + 0.25 × F_count + 0.25 × C_avg_credibility - P_contradiction`
    - Use entropy-based direction: H>0.9→mixed, P_bull>0.65→bullish, P_bull<0.35→bearish, else neutral
    - Apply regime-adjusted thresholds from `RegimeClassification`
    - Populate new `TrendSummary` fields: `p_bull`, `alpha`, `beta_param`, `bayesian_confidence`, `entropy`, `regime`, `pipeline_mode`
    - Store probabilistic outputs in `market_context` JSONB under `"probabilistic"` key
    - When `probabilistic=False`, preserve exact current heuristic behavior
    - _Requirements: 1.1, 1.2, 8.1, 8.2, 8.3, 8.4, 8.5, 9.1, 9.2, 9.3, 9.4, 9.5, 9.6, 7.8, 16.4, 16.5_

  - [x] 9.2 Wire regime detection into the aggregation cycle
    - Call `classify_regime` with closing prices and returns for each ticker
    - Pass `RegimeClassification` to trend assembly for threshold adjustment
    - Default to uncertainty regime when market data is unavailable
    - Persist regime classification in JSONB for auditability
    - _Requirements: 7.1, 7.2, 7.3, 7.8, 7.9_

- [ ] 10. Macro scoring upgrade (`services/aggregation/interpolation.py`)
  - [x] 10.1 Implement multiplicative macro exposure formula
    - When `probabilistic=True`, compute `S_macro = severity · (1 - Π_k(1 - w_k · O_k))` instead of linear weighted sum
    - Preserve overlap weights: w_geo=0.35, w_supply=0.25, w_commodity=0.25, w_sector=0.15
    - Preserve severity mapping and resilience modifier
    - When `probabilistic=False`, preserve exact current linear formula
    - _Requirements: 10.1, 10.2, 10.3, 10.4, 10.5, 10.6_

  - [x] 10.2 Implement conditional macro signal integration
    - When `probabilistic=True` and both company and macro signals exist, apply macro as multiplicative modifier: `S_adjusted = S_company · clamp(1 + M_macro · sign_alignment, 0.5, 1.5)`
    - When only macro signals exist, fall back to additive behavior with weight 0.3
    - When only company signals exist, use modifier = 1.0
    - Log macro modifier value per ticker
    - When `probabilistic=False`, preserve current additive merge behavior
    - _Requirements: 11.1, 11.2, 11.3, 11.4, 11.5_

  - [x] 10.3 Write property test for multiplicative macro exposure monotonicity
    - **Property 7: Multiplicative Macro Exposure Monotonicity**
    - **Validates: Requirements 10.7, 17.5**

- [ ] 11. Competitive signal upgrade (`services/aggregation/signal_propagation.py`)
  - [x] 11.1 Implement graph-distance attenuation for competitive signals
    - When `probabilistic=True`, compute `S_transfer = S_source · ρ_historical · e^(-d_network)` instead of flat transfer
    - Compute graph distance as shortest path in competitor relationship graph (cap at 3)
    - Use 90-day rolling Pearson correlation for `ρ_historical`; default to 0.3 (same-sector) or 0.1 (cross-sector) when insufficient data (<30 days)
    - Preserve existing relationship strength threshold (R ≥ 0.2) as pre-filter
    - When `probabilistic=False`, preserve exact current flat transfer behavior
    - _Requirements: 12.1, 12.2, 12.3, 12.4, 12.5, 12.6, 12.7_

  - [x] 11.2 Write property test for competitive signal distance attenuation
    - **Property 11: Competitive Signal Distance Attenuation**
    - **Validates: Requirements 12.7**

- [ ] 12. Projection upgrade (`services/aggregation/projection.py`)
  - [x] 12.1 Implement exponentially weighted momentum
    - When `probabilistic=True`, compute `M_t = Σ_{k=0}^{K-1} λ^k · ΔS_{t-k}` with λ=0.7, K up to 10
    - Normalize by geometric series sum to produce value in [-1, 1]
    - Fall back to current heuristic when fewer than 2 historical cycles available
    - Compute volatility-scaled momentum: `M_adj = M_t / max(σ_20, 0.01)`, clamped to [-2.0, 2.0]
    - When `probabilistic=False`, preserve exact current simple momentum behavior
    - _Requirements: 13.1, 13.2, 13.3, 13.4, 13.5, 13.6_

  - [x] 12.2 Write property test for exponentially weighted momentum direction
    - **Property 10: Exponentially Weighted Momentum Direction**
    - **Validates: Requirements 13.6, 17.6**

- [ ] 13. Recommendation upgrade (`services/recommendation/eligibility.py`)
  - [x] 13.1 Implement expected value recommendation gate
    - When `probabilistic=True`, compute `EV = P_bull · R_up - P_bear · R_down`
    - Estimate `R_up = strength · σ_20 · √(horizon_days)` and `R_down = (1 - strength) · σ_20 · √(horizon_days)`
    - When EV > threshold (default 0.005), allow recommendation through existing gates
    - When EV ≤ threshold, force recommendation to informational mode
    - Persist EV in `risk_checks` JSONB of `recommendation_evaluations`
    - Populate `expected_value`, `p_bull`, `pipeline_mode` on Recommendation model
    - Preserve all existing eligibility gates as additional requirements
    - When `probabilistic=False`, skip EV gate entirely
    - _Requirements: 14.1, 14.2, 14.3, 14.4, 14.5, 14.6_

  - [x] 13.2 Write property test for expected value directional consistency
    - **Property 12: Expected Value Directional Consistency**
    - **Validates: Requirements 17.8**

- [x] 14. Checkpoint — Verify all pipeline stages compile and existing tests still pass
  - Ensure all tests pass, ask the user if questions arise.

- [ ] 15. Integration wiring and feature flag plumbing
  - [x] 15.1 Wire feature flag through the aggregation worker entry point
    - Read `probabilistic_scoring_enabled` from `risk_configs` at cycle start in `services/aggregation/worker.py`
    - Pass flag to `ScoringConfig`, trend assembly, contradiction, macro, competitive, and projection stages
    - Log pipeline mode at cycle start
    - Ensure flag is read once per cycle (mid-cycle changes take effect next cycle)
    - _Requirements: 16.3, 16.6, 16.7_

  - [x] 15.2 Wire source accuracy fetch into the scoring pipeline
    - At cycle start, batch-fetch source accuracy for all source IDs in the current signal set
    - Pass `source_accuracy_factor` to `compute_signal_weight` for each signal
    - Handle DB errors gracefully (default to 1.0)
    - _Requirements: 4.1, 4.2, 4.3_

  - [x] 15.3 Wire regime detection into the aggregation cycle
    - Fetch closing prices and returns for each ticker from market data
    - Call `classify_regime` and pass result to trend assembly and scoring stages
    - Handle missing market data (default to uncertainty regime)
    - _Requirements: 7.1, 7.8, 7.9_

  - [x] 15.4 Store probabilistic outputs in existing JSONB columns
    - Store Bayesian fields in `trend_windows.market_context` JSONB under `"probabilistic"` key
    - Store EV fields in `recommendation_evaluations.risk_checks` JSONB
    - Store regime classification in trend window JSONB
    - _Requirements: 16.2_

- [ ] 16. Numerical stability and edge case hardening
  - [x] 16.1 Add input validation and edge case guards across all new functions
    - Guard `log₂(0)` in entropy and information gain computations
    - Floor `max(σ_20, 0.01)` for momentum volatility scaling
    - Default to uncertainty regime when σ values are zero
    - Return `M_regime = 1.0` when z-score σ = 0
    - Skip signals with NaN weight or sentiment
    - Clamp all outputs to documented ranges
    - _Requirements: 17.9, 6.4_

  - [x] 16.2 Write property test for numerical stability across all formulas
    - **Property 14: Numerical Stability Across All Formulas**
    - **Validates: Requirements 17.9, 6.4**

- [ ] 17. Unit tests for all new and modified modules
  - [x] 17.1 Write unit tests for Bayesian accumulator (`tests/test_bayesian.py`)
    - Test uninformative prior (empty signals → P_bull=0.5, α=1, β=1, C=0)
    - Test specific sigmoid gate values (x=0.5→0.5, x=0.2→<0.05, x=0.8→>0.95)
    - Test entropy direction mapping (H>0.9→mixed, P_bull>0.65→bullish, etc.)
    - _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5_

  - [x] 17.2 Write unit tests for regime detector (`tests/test_regime.py`)
    - Test specific (R, V_r) → expected regime classification
    - Test threshold adjustments per regime (panic→0.10, mean_reversion→0.20)
    - Test insufficient data fallback to uncertainty
    - _Requirements: 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7, 7.9_

  - [x] 17.3 Write unit tests for source accuracy tracker (`tests/test_source_accuracy.py`)
    - Test accuracy_factor property: sample_count < 10 → 1.0, else 0.5 + ratio
    - Test corrupted data clamping
    - _Requirements: 4.1, 4.2, 4.3_

  - [x] 17.4 Write unit tests for signal scoring upgrades (`tests/test_signal_math_unit.py`)
    - Test info gain clamp (very rare event → factor ≤ 3.0)
    - Test default base rate (unknown event type → 0.1)
    - Test adaptive decay edge cases (all zeros → τ_base, all max → 6×τ_base)
    - Test zero overlap → zero macro impact
    - Test max overlap → ≈severity×0.724
    - Test macro fallback behaviors (only macro → additive, only company → no modifier)
    - Test graph distance cutoff (d>3 → no propagation)
    - Test momentum fallback (<2 cycles → heuristic)
    - Test EV threshold behavior (EV>0.005→proceed, EV≤0.005→informational)
    - Test feature flag behaviors (flag=false→heuristic, flag=true→probabilistic)
    - _Requirements: 3.1, 3.4, 5.5, 5.6, 10.3, 10.4, 11.3, 13.3, 14.3, 14.4, 16.4, 16.5_

- [x] 18. Final checkpoint — Ensure all tests pass
  - Ensure all tests pass, ask the user if questions arise.

## Notes

- Tasks marked with `*` are optional and can be skipped for faster MVP
- Each task references specific requirements for traceability
- Checkpoints ensure incremental validation after each major phase
- Property tests validate the 14 universal correctness properties from the design document
- Unit tests validate specific examples, edge cases, and integration points
- The design uses Python throughout — no language selection needed
- Migration number is 034 (existing migrations go up to 033)
- All new dataclass fields use optional defaults for backward compatibility
- Feature flag `probabilistic_scoring_enabled` gates every behavioral change
