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:
+36
-12
@@ -450,8 +450,14 @@ async def list_recommendations(
|
||||
since: Optional[str] = None,
|
||||
limit: int = Query(default=50, le=200),
|
||||
offset: int = 0,
|
||||
latest: bool = Query(default=True, description="Return only the latest recommendation per ticker"),
|
||||
):
|
||||
"""List recommendations with optional filters."""
|
||||
"""List recommendations with optional filters.
|
||||
|
||||
By default (latest=true), returns only the most recent recommendation
|
||||
per ticker to avoid showing duplicate/stale entries. Set latest=false
|
||||
to see the full history.
|
||||
"""
|
||||
conditions: list[str] = []
|
||||
params: list[Any] = []
|
||||
idx = 1
|
||||
@@ -475,17 +481,35 @@ async def list_recommendations(
|
||||
|
||||
where = ("WHERE " + " AND ".join(conditions)) if conditions else ""
|
||||
|
||||
rows = await pool.fetch(
|
||||
f"""SELECT r.id, r.ticker, r.action, r.mode, r.confidence,
|
||||
r.time_horizon, r.thesis, r.invalidation_conditions,
|
||||
r.portfolio_pct, r.max_loss_pct, r.model_version,
|
||||
r.risk_classification, r.generated_at
|
||||
FROM recommendations r
|
||||
{where}
|
||||
ORDER BY r.generated_at DESC
|
||||
LIMIT ${idx} OFFSET ${idx + 1}""",
|
||||
*params, limit, offset,
|
||||
)
|
||||
if latest:
|
||||
# Use DISTINCT ON to get only the latest recommendation per ticker
|
||||
rows = await pool.fetch(
|
||||
f"""SELECT DISTINCT ON (r.ticker)
|
||||
r.id, r.ticker, r.action, r.mode, r.confidence,
|
||||
r.time_horizon, r.thesis, r.invalidation_conditions,
|
||||
r.portfolio_pct, r.max_loss_pct, r.model_version,
|
||||
r.risk_classification, r.generated_at
|
||||
FROM recommendations r
|
||||
{where}
|
||||
ORDER BY r.ticker, r.generated_at DESC""",
|
||||
*params,
|
||||
)
|
||||
# Apply limit/offset manually after DISTINCT ON
|
||||
# Sort by generated_at DESC for the final result
|
||||
rows = sorted(rows, key=lambda r: r["generated_at"], reverse=True)
|
||||
rows = rows[offset:offset + limit]
|
||||
else:
|
||||
rows = await pool.fetch(
|
||||
f"""SELECT r.id, r.ticker, r.action, r.mode, r.confidence,
|
||||
r.time_horizon, r.thesis, r.invalidation_conditions,
|
||||
r.portfolio_pct, r.max_loss_pct, r.model_version,
|
||||
r.risk_classification, r.generated_at
|
||||
FROM recommendations r
|
||||
{where}
|
||||
ORDER BY r.generated_at DESC
|
||||
LIMIT ${idx} OFFSET ${idx + 1}""",
|
||||
*params, limit, offset,
|
||||
)
|
||||
results = []
|
||||
for r in rows:
|
||||
d = _row_to_dict(r)
|
||||
|
||||
Reference in New Issue
Block a user