fix: deduplicate recommendations and widen position sizing range

- Add dedup check in recommendation worker: skip generation when latest
  rec for same ticker+window has identical action/mode/confidence
- Widen position sizing range (1-10% portfolio, 0.3-2% max loss) and
  factor in trend strength + evidence count for differentiated sizing
- API returns only latest recommendation per ticker by default (DISTINCT ON)
  to eliminate duplicate rows in the frontend list view
This commit is contained in:
Celes Renata
2026-04-17 00:15:32 +00:00
parent 29f46d387c
commit f11aa0a1ee
3 changed files with 134 additions and 35 deletions
+30 -20
View File
@@ -66,17 +66,17 @@ class EligibilityConfig:
# --- Position sizing rules (Requirement 7.3) ---
# Base portfolio allocation percentage
base_portfolio_pct: float = 0.02
base_portfolio_pct: float = 0.01
# Maximum portfolio allocation percentage
max_portfolio_pct: float = 0.05
max_portfolio_pct: float = 0.10
# Base max loss percentage
base_max_loss_pct: float = 0.005
base_max_loss_pct: float = 0.003
# Maximum max loss percentage
max_max_loss_pct: float = 0.01
max_max_loss_pct: float = 0.02
# Confidence scaling: higher confidence → larger position (linear)
confidence_sizing_weight: float = 0.5
confidence_sizing_weight: float = 0.8
# Contradiction penalty: higher contradiction → smaller position
contradiction_sizing_penalty: float = 0.3
contradiction_sizing_penalty: float = 0.5
DEFAULT_ELIGIBILITY_CONFIG = EligibilityConfig()
@@ -216,30 +216,40 @@ def _compute_position_sizing(
) -> PositionSizing:
"""Compute position sizing guidance from portfolio rules and signal quality.
Higher confidence → larger allocation (up to max).
Higher confidence and trend strength → larger allocation (up to max).
Higher contradiction → smaller allocation (penalty).
Low evidence count further reduces allocation.
"""
# Start from base allocation
confidence_scale = config.base_portfolio_pct + (
config.confidence_sizing_weight
* summary.confidence
* (config.max_portfolio_pct - config.base_portfolio_pct)
)
# Confidence-based scaling over the full range
confidence_factor = config.confidence_sizing_weight * summary.confidence
# Trend strength multiplier — stronger trends justify larger positions
strength_factor = 0.5 + 0.5 * summary.trend_strength # range [0.5, 1.0]
portfolio_range = config.max_portfolio_pct - config.base_portfolio_pct
raw_portfolio = config.base_portfolio_pct + confidence_factor * strength_factor * portfolio_range
# Apply contradiction penalty
contradiction_penalty = config.contradiction_sizing_penalty * summary.contradiction_score
portfolio_pct = confidence_scale * (1.0 - contradiction_penalty)
portfolio_pct = raw_portfolio * (1.0 - contradiction_penalty)
# Evidence count penalty — fewer sources = less confidence in sizing
evidence_count = len(summary.top_supporting_evidence) + len(summary.top_opposing_evidence)
if evidence_count < 3:
portfolio_pct *= 0.5
elif evidence_count < 5:
portfolio_pct *= 0.75
# Clamp to bounds
portfolio_pct = max(config.base_portfolio_pct * 0.5, min(portfolio_pct, config.max_portfolio_pct))
# Max loss scales similarly
loss_scale = config.base_max_loss_pct + (
config.confidence_sizing_weight
* summary.confidence
* (config.max_max_loss_pct - config.base_max_loss_pct)
)
max_loss_pct = loss_scale * (1.0 - contradiction_penalty)
loss_range = config.max_max_loss_pct - config.base_max_loss_pct
raw_loss = config.base_max_loss_pct + confidence_factor * strength_factor * loss_range
max_loss_pct = raw_loss * (1.0 - contradiction_penalty)
if evidence_count < 3:
max_loss_pct *= 0.5
elif evidence_count < 5:
max_loss_pct *= 0.75
max_loss_pct = max(config.base_max_loss_pct * 0.5, min(max_loss_pct, config.max_max_loss_pct))
return PositionSizing(