""" Position Management Assistant API Provides intelligent mitigation plans, exit strategies, and risk monitoring for active positions """ from fastapi import APIRouter, HTTPException, Query from pydantic import BaseModel, Field from typing import List, Optional, Dict, Literal from datetime import datetime, timezone, timedelta import numpy as np router = APIRouter(prefix="/api/position-assistant", tags=["Position Assistant"]) class ActivePosition(BaseModel): """Current active position details""" symbol: str = Field(default="XAU/USD") direction: Literal["LONG", "SHORT"] entry_price: float quantity: float stop_loss: float take_profit: Optional[float] = None entry_time: str notes: Optional[str] = None class MitigationStrategy(BaseModel): """Smart mitigation strategy for managing risk""" strategy_name: str priority: int # 1 = highest priority action: str trigger_price: float reasoning: str expected_benefit: str risk_level: Literal["LOW", "MEDIUM", "HIGH"] class PriceReversal(BaseModel): """Predicted price reversal levels and timing""" level: float probability: float # 0-1 timeframe: str # e.g., "2-4 hours", "End of day" reasoning: str confluences: List[str] class PositionHealth(BaseModel): """Real-time position health assessment""" status: Literal["HEALTHY", "AT_RISK", "CRITICAL", "WINNING"] current_pnl: float current_pnl_percent: float distance_to_stop_loss: float distance_to_stop_loss_percent: float time_in_trade: str recommendation: str urgency: Literal["LOW", "MEDIUM", "HIGH", "URGENT"] class PositionManagementPlan(BaseModel): """Complete position management plan""" position: ActivePosition current_price: float health: PositionHealth mitigation_strategies: List[MitigationStrategy] reversal_zones: List[PriceReversal] exit_plan: Dict alerts: List[str] next_actions: List[str] def _calculate_position_health( position: ActivePosition, current_price: float ) -> PositionHealth: """Calculate real-time position health""" # Calculate P&L if position.direction == "SHORT": pnl = (position.entry_price - current_price) * position.quantity pnl_percent = ((position.entry_price - current_price) / position.entry_price) * 100 distance_to_sl = position.stop_loss - current_price else: # LONG pnl = (current_price - position.entry_price) * position.quantity pnl_percent = ((current_price - position.entry_price) / position.entry_price) * 100 distance_to_sl = current_price - position.stop_loss distance_to_sl_percent = (distance_to_sl / position.entry_price) * 100 # Calculate time in trade entry_dt = datetime.fromisoformat(position.entry_time.replace('Z', '+00:00')) now_dt = datetime.now(timezone.utc) time_diff = now_dt - entry_dt hours = time_diff.total_seconds() / 3600 if hours < 1: time_in_trade = f"{int(time_diff.total_seconds() / 60)} minutes" elif hours < 24: time_in_trade = f"{hours:.1f} hours" else: time_in_trade = f"{hours/24:.1f} days" # Determine status and urgency if pnl > 0: if pnl_percent > 2: status = "WINNING" urgency = "LOW" recommendation = "Consider taking partial profits to secure gains" else: status = "HEALTHY" urgency = "LOW" recommendation = "Monitor for continuation or reversal signals" else: loss_percent_of_sl = abs(pnl_percent) / abs((position.stop_loss - position.entry_price) / position.entry_price * 100) if loss_percent_of_sl > 0.8: status = "CRITICAL" urgency = "URGENT" recommendation = "CLOSE POSITION NOW or implement emergency mitigation" elif loss_percent_of_sl > 0.5: status = "AT_RISK" urgency = "HIGH" recommendation = "Consider scaling out or tightening stop loss" else: status = "AT_RISK" urgency = "MEDIUM" recommendation = "Watch for reversal signals, keep stop loss in place" return PositionHealth( status=status, current_pnl=round(pnl, 2), current_pnl_percent=round(pnl_percent, 2), distance_to_stop_loss=round(distance_to_sl, 2), distance_to_stop_loss_percent=round(distance_to_sl_percent, 2), time_in_trade=time_in_trade, recommendation=recommendation, urgency=urgency ) def _generate_mitigation_strategies( position: ActivePosition, current_price: float, health: PositionHealth ) -> List[MitigationStrategy]: """Generate smart mitigation strategies""" strategies = [] if position.direction == "SHORT": # SHORT position mitigation strategies # Strategy 1: Partial close at break-even strategies.append(MitigationStrategy( strategy_name="Break-Even Exit (Partial)", priority=1, action=f"Close 50% of position at ${position.entry_price:.2f}", trigger_price=position.entry_price, reasoning="Lock in zero loss on half the position if price retraces to entry", expected_benefit="Reduces risk by 50% while keeping upside exposure", risk_level="LOW" )) # Strategy 2: Scale out in profit if current_price < position.entry_price: target_1 = position.entry_price - (position.entry_price - current_price) * 1.5 strategies.append(MitigationStrategy( strategy_name="Scale Out (First Target)", priority=2, action=f"Close 30% of position at ${target_1:.2f}", trigger_price=target_1, reasoning="Take partial profits at 1.5x current movement", expected_benefit="Secure profits while maintaining exposure", risk_level="LOW" )) # Strategy 3: Move stop to break-even if health.current_pnl > 0: strategies.append(MitigationStrategy( strategy_name="Move Stop to Break-Even", priority=3, action=f"Move stop loss from ${position.stop_loss:.2f} to ${position.entry_price:.2f}", trigger_price=current_price, reasoning="Eliminate downside risk once in profit", expected_benefit="Cannot lose money on this trade anymore", risk_level="LOW" )) # Strategy 4: Emergency hedge if health.status == "CRITICAL": hedge_price = position.entry_price + (position.stop_loss - position.entry_price) * 0.5 strategies.append(MitigationStrategy( strategy_name="Emergency Hedge (LONG)", priority=1, action=f"Open LONG position at ${current_price:.2f} (same size)", trigger_price=current_price, reasoning="Neutralize the position to stop bleeding while you reassess", expected_benefit="Stop further losses immediately", risk_level="HIGH" )) # Strategy 5: Widen stop temporarily if health.status == "AT_RISK" and health.urgency == "HIGH": new_sl = position.stop_loss + (position.stop_loss - position.entry_price) * 0.3 strategies.append(MitigationStrategy( strategy_name="Temporary Stop Widening", priority=4, action=f"Widen stop loss to ${new_sl:.2f} temporarily", trigger_price=current_price, reasoning="Give position room to breathe during volatility spike", expected_benefit="Avoid premature stop-out if reversal is coming", risk_level="MEDIUM" )) else: # LONG position # LONG position mitigation strategies (mirror of SHORT) strategies.append(MitigationStrategy( strategy_name="Break-Even Exit (Partial)", priority=1, action=f"Close 50% of position at ${position.entry_price:.2f}", trigger_price=position.entry_price, reasoning="Lock in zero loss on half the position if price retraces to entry", expected_benefit="Reduces risk by 50% while keeping upside exposure", risk_level="LOW" )) if current_price > position.entry_price: target_1 = position.entry_price + (current_price - position.entry_price) * 1.5 strategies.append(MitigationStrategy( strategy_name="Scale Out (First Target)", priority=2, action=f"Close 30% of position at ${target_1:.2f}", trigger_price=target_1, reasoning="Take partial profits at 1.5x current movement", expected_benefit="Secure profits while maintaining exposure", risk_level="LOW" )) if health.current_pnl > 0: strategies.append(MitigationStrategy( strategy_name="Move Stop to Break-Even", priority=3, action=f"Move stop loss from ${position.stop_loss:.2f} to ${position.entry_price:.2f}", trigger_price=current_price, reasoning="Eliminate downside risk once in profit", expected_benefit="Cannot lose money on this trade anymore", risk_level="LOW" )) # Sort by priority strategies.sort(key=lambda x: x.priority) return strategies def _predict_reversal_zones( position: ActivePosition, current_price: float ) -> List[PriceReversal]: """Predict potential reversal zones using technical analysis""" reversals = [] if position.direction == "SHORT": # For SHORT: Looking for price to drop (reversal down from current) # Support level 1: 0.5 Fibonacci from entry to current fib_50 = position.entry_price - (position.entry_price - current_price) * 0.5 if current_price > position.entry_price: # If against us fib_50 = current_price - (current_price - position.entry_price) * 0.382 reversals.append(PriceReversal( level=round(fib_50, 2), probability=0.65, timeframe="2-4 hours", reasoning="38.2% Fibonacci retracement - common reversal zone", confluences=["Fibonacci level", "Potential exhaustion zone"] )) # Support level 2: Round number below entry round_number = (int(position.entry_price / 100) * 100) - 100 if round_number < current_price: reversals.append(PriceReversal( level=round(round_number, 2), probability=0.55, timeframe="4-8 hours", reasoning="Major round number psychological support", confluences=["Round number", "Psychological level"] )) # Support level 3: Previous day low (simulated) prev_day_low = position.entry_price - (position.entry_price * 0.015) # 1.5% below entry reversals.append(PriceReversal( level=round(prev_day_low, 2), probability=0.70, timeframe="End of day", reasoning="Estimated previous day low - strong support", confluences=["Previous low", "Session support"] )) else: # LONG # For LONG: Looking for price to rise (reversal up from current) fib_50 = position.entry_price + (current_price - position.entry_price) * 0.5 if current_price < position.entry_price: # If against us fib_50 = current_price + (position.entry_price - current_price) * 0.382 reversals.append(PriceReversal( level=round(fib_50, 2), probability=0.65, timeframe="2-4 hours", reasoning="38.2% Fibonacci retracement - common reversal zone", confluences=["Fibonacci level", "Potential exhaustion zone"] )) round_number = (int(position.entry_price / 100) * 100) + 100 if round_number > current_price: reversals.append(PriceReversal( level=round(round_number, 2), probability=0.55, timeframe="4-8 hours", reasoning="Major round number psychological resistance", confluences=["Round number", "Psychological level"] )) prev_day_high = position.entry_price + (position.entry_price * 0.015) reversals.append(PriceReversal( level=round(prev_day_high, 2), probability=0.70, timeframe="End of day", reasoning="Estimated previous day high - strong resistance", confluences=["Previous high", "Session resistance"] )) # Sort by probability (highest first) reversals.sort(key=lambda x: x.probability, reverse=True) return reversals def _create_exit_plan( position: ActivePosition, current_price: float, health: PositionHealth, reversals: List[PriceReversal] ) -> Dict: """Create comprehensive exit plan""" plan = { "immediate_action": None, "optimal_exits": [], "emergency_exit": None, "time_based_exit": None } if health.status == "CRITICAL": plan["immediate_action"] = { "action": "CLOSE IMMEDIATELY", "reason": "Position is critically at risk", "price": current_price } plan["emergency_exit"] = { "action": "Market order close if stop loss hit", "trigger": position.stop_loss, "loss_amount": health.current_pnl if health.current_pnl < 0 else 0 } elif health.status == "WINNING": # Build scaling out plan if position.direction == "SHORT": target_1 = current_price - (position.entry_price - current_price) * 0.5 target_2 = current_price - (position.entry_price - current_price) * 1.0 else: target_1 = current_price + (current_price - position.entry_price) * 0.5 target_2 = current_price + (current_price - position.entry_price) * 1.0 plan["optimal_exits"] = [ { "level": 1, "price": round(target_1, 2), "quantity_percent": 33, "reason": "First profit target - secure initial gains" }, { "level": 2, "price": round(target_2, 2), "quantity_percent": 33, "reason": "Second profit target - let winners run" }, { "level": 3, "price": "Trailing stop", "quantity_percent": 34, "reason": "Trail remaining with break-even stop" } ] else: # AT_RISK or HEALTHY # Exit at reversal zones plan["optimal_exits"] = [ { "level": i + 1, "price": rev.level, "quantity_percent": 100 if i == 0 else 50, "reason": f"{rev.reasoning} ({int(rev.probability*100)}% probability)" } for i, rev in enumerate(reversals[:2]) ] # Time-based exit (end of day or session) hours_in_trade = (datetime.now(timezone.utc) - datetime.fromisoformat(position.entry_time.replace('Z', '+00:00'))).total_seconds() / 3600 if hours_in_trade > 4 and health.status != "WINNING": plan["time_based_exit"] = { "time": "End of trading session", "action": "Review and consider closing if no reversal", "reason": "Avoid holding losing position overnight" } return plan @router.post("/analyze", response_model=PositionManagementPlan) async def analyze_position( position: ActivePosition, current_price: float = Query(..., description="Current market price") ) -> PositionManagementPlan: """ Analyze active position and provide comprehensive management plan Example: ``` POST /api/position-assistant/analyze?current_price=4085 { "direction": "SHORT", "entry_price": 4070, "quantity": 1.0, "stop_loss": 4109, "entry_time": "2025-11-24T10:00:00Z" } ``` """ try: # Calculate position health health = _calculate_position_health(position, current_price) # Generate mitigation strategies strategies = _generate_mitigation_strategies(position, current_price, health) # Predict reversal zones reversals = _predict_reversal_zones(position, current_price) # Create exit plan exit_plan = _create_exit_plan(position, current_price, health, reversals) # Generate alerts alerts = [] if health.status == "CRITICAL": alerts.append("🚨 URGENT: Position at critical risk level") alerts.append(f"⚠️ Stop loss ${abs(health.distance_to_stop_loss):.2f} away") elif health.status == "AT_RISK" and health.urgency == "HIGH": alerts.append(f"⚠️ Position down {abs(health.current_pnl_percent):.1f}%") alerts.append("💡 Consider mitigation strategies") elif health.status == "WINNING": alerts.append(f"✅ Position up {health.current_pnl_percent:.1f}%") alerts.append("🎯 Consider taking partial profits") # Generate next actions next_actions = [] if strategies: top_strategy = strategies[0] next_actions.append(f"📋 Primary: {top_strategy.action}") if reversals: top_reversal = reversals[0] next_actions.append(f"🎯 Watch for reversal at ${top_reversal.level:.2f} ({top_reversal.timeframe})") if exit_plan.get("immediate_action"): next_actions.insert(0, f"🚨 {exit_plan['immediate_action']['action']}") return PositionManagementPlan( position=position, current_price=current_price, health=health, mitigation_strategies=strategies, reversal_zones=reversals, exit_plan=exit_plan, alerts=alerts, next_actions=next_actions ) except Exception as e: raise HTTPException( status_code=500, detail=f"Failed to analyze position: {str(e)}" ) @router.get("/quick-status") async def get_quick_status( direction: str = Query(..., description="LONG or SHORT"), entry_price: float = Query(...), current_price: float = Query(...), stop_loss: float = Query(...) ) -> Dict: """ Quick position status check without full analysis Example: ``` GET /api/position-assistant/quick-status?direction=SHORT&entry_price=4070¤t_price=4085&stop_loss=4109 ``` """ try: # Quick P&L calculation if direction.upper() == "SHORT": pnl = entry_price - current_price pnl_percent = ((entry_price - current_price) / entry_price) * 100 distance_to_sl = stop_loss - current_price else: pnl = current_price - entry_price pnl_percent = ((current_price - entry_price) / entry_price) * 100 distance_to_sl = current_price - stop_loss distance_to_sl_percent = (distance_to_sl / entry_price) * 100 # Quick status if pnl > 0: status = "✅ In Profit" color = "green" else: loss_ratio = abs(distance_to_sl_percent / ((stop_loss - entry_price) / entry_price * 100)) if loss_ratio > 0.8: status = "🚨 CRITICAL - Close to stop loss" color = "red" elif loss_ratio > 0.5: status = "⚠️ AT RISK" color = "orange" else: status = "📊 Monitoring" color = "yellow" return { "status": status, "color": color, "pnl": round(pnl, 2), "pnl_percent": round(pnl_percent, 2), "distance_to_stop_loss": round(abs(distance_to_sl), 2), "distance_to_stop_loss_percent": round(abs(distance_to_sl_percent), 2) } except Exception as e: raise HTTPException( status_code=500, detail=f"Failed to get quick status: {str(e)}" )