feat: trade mandate (budget + horizon) wired end-to-end

- database.py: add trade_budget_eur / preferred_horizon_min/max config
  defaults and include them in cycle config migrations
- auto_cycle.py: read trade params from config and inject into cycle_meta
- ai_analyzer.py: inject INVESTOR TRADE MANDATE block into scoring and
  suggestion prompts so GPT-4o penalises horizon mismatches and sizes
  within the capital cap
- Config.tsx: Trade Parameters card with budget + horizon sliders and live
  mandate summary
- TradeIdeas.tsx: horizon filter pills (< 1M / 1-3M / 3-6M / > 6M) and
  budget/horizon indicator pulled from saved config
- useApi.ts: extend useUpdateCycleConfig type with new config fields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-22 17:24:38 +02:00
parent 9b98594c07
commit a68a08d9af
6 changed files with 151 additions and 6 deletions

View File

@@ -619,10 +619,25 @@ Scoring instructions:
_portfolio_sc_section = f"\n{portfolio_context_block}\n" if portfolio_context_block else ""
_inst_sc_section = f"\n{institutional_block}\n" if institutional_block else ""
# Trade mandate block from cycle_meta
_budget = _cm.get("trade_budget_eur", 5000)
_h_min = _cm.get("preferred_horizon_min", 30)
_h_max = _cm.get("preferred_horizon_max", 180)
_max_pos = round(_budget * 0.25)
_trade_mandate_sc = (
f"\n## INVESTOR TRADE MANDATE\n"
f"- Available capital budget: €{_budget:,.0f}\n"
f"- Preferred horizon: {_h_min}{_h_max} days\n"
f"- Max capital per position: €{_max_pos:,.0f} (25% of budget)\n"
f"⚠️ Patterns whose horizon_days is outside [{_h_min}, {_h_max}] should receive a penalty"
f" in the R/R pillar (poor timing fit). Size trade suggestions within the budget cap.\n"
)
user = f"""GLOBAL CONTEXT:
- Geopolitical risk score: {geo_score.get('score', 50)}/100 ({geo_score.get('level', 'medium')})
- Top risks: {geo_score.get('top_risks', [])}
{temporal_section_sc}
{_trade_mandate_sc}
{macro_section}
{_fred_sc_section}
{_pd_sc_section}
@@ -1411,7 +1426,20 @@ Additional rules:
portfolio_section = f"\n{portfolio_context_block}\n" if portfolio_context_block else ""
convergence_section = f"\n{convergence_block}\n" if convergence_block else ""
_sg_budget = _cycle_meta.get("trade_budget_eur", 5000)
_sg_h_min = _cycle_meta.get("preferred_horizon_min", 30)
_sg_h_max = _cycle_meta.get("preferred_horizon_max", 180)
_sg_max_pos = round(_sg_budget * 0.25)
_suggest_mandate = (
f"\n## INVESTOR TRADE MANDATE\n"
f"- Available capital budget: €{_sg_budget:,.0f} | Max per position: €{_sg_max_pos:,.0f}\n"
f"- Target horizon: {_sg_h_min}{_sg_h_max} days\n"
f"⚠️ Only propose patterns whose horizon_days is within [{_sg_h_min}, {_sg_h_max}]."
f" Patterns outside this window will be rejected. Size each trade so capital ≤ €{_sg_max_pos:,.0f}.\n"
)
user = f"""You are a senior geopolitical and financial strategist, expert in options.
{_suggest_mandate}
{macro_block}{geo_block}{lessons_block}{reliability_block}{iv_block}
{temporal_news_block}
## Market prices (D-1 change)

View File

@@ -255,6 +255,10 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]:
_market_session = "after_hours"
_market_note = "After-hours US — prix indicatifs, liquidité réduite."
_budget_eur = float(get_config("trade_budget_eur") or "5000")
_horizon_min = int(get_config("preferred_horizon_min") or "30")
_horizon_max = int(get_config("preferred_horizon_max") or "180")
cycle_meta = {
"current_cycle_ts": _now.isoformat(),
"last_cycle_ts": _last_cycle_ts_str,
@@ -265,6 +269,9 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]:
"is_weekend": _is_weekend,
"market_session": _market_session,
"market_note": _market_note,
"trade_budget_eur": _budget_eur,
"preferred_horizon_min": _horizon_min,
"preferred_horizon_max": _horizon_max,
}
logger.info(
f"[Cycle {run_id[:16]}] Cycle meta: delta={_delta_minutes:.0f}min depuis dernier cycle"

View File

@@ -311,6 +311,9 @@ def init_db():
"auto_cycle_similarity_threshold": "0.30",
"min_ev_threshold": "0.0",
"min_score_threshold": "0",
"trade_budget_eur": "5000",
"preferred_horizon_min": "30",
"preferred_horizon_max": "180",
"exit_defaults": json.dumps({
"target_pct": 30.0,
"stop_loss_pct": -50.0,