- Restructure tabs to analysis-focused workflow: * Analysis Hub: AI analysis, risk management, manual trade logger * Daily Prep: Market summary, alerts, checklist, news, trading plan * Journal & Review: Trading journal, habit tracker, advanced analytics * Live Charts: Technical analysis with streaming charts - Add ManualTradeLogger component for logging trades from MT5/TradingView/cTrader - Remove execution-focused components (TradeControls, PortfolioTracker) - Update XAU/USD price to realistic ,084.99 - Add indicator preferences and AI plan service - Add comprehensive documentation on decision coverage and implementation
271 lines
10 KiB
Python
271 lines
10 KiB
Python
"""
|
|
AI Plan Generation Service
|
|
Generates daily trading plans using AI based on user's indicator preferences
|
|
"""
|
|
|
|
from typing import List, Optional, Dict
|
|
from datetime import date
|
|
from sqlalchemy.orm import Session
|
|
import json
|
|
|
|
from app.models.models import UserIndicatorPreferences, AIPlanGeneration
|
|
from app.schemas.schemas import (
|
|
AIPlanGenerationRequest,
|
|
AIPlanGenerationResponse,
|
|
MarketBias,
|
|
PriceData
|
|
)
|
|
from app.services.openrouter import openrouter_service
|
|
|
|
|
|
class AIPlanService:
|
|
"""Service for AI-powered trading plan generation"""
|
|
|
|
def _get_user_indicator_preferences(self, db: Session, user_id: Optional[str] = None) -> List[UserIndicatorPreferences]:
|
|
"""Fetch user's enabled indicator preferences"""
|
|
query = db.query(UserIndicatorPreferences).filter(
|
|
UserIndicatorPreferences.enabled == True
|
|
)
|
|
|
|
if user_id:
|
|
query = query.filter(UserIndicatorPreferences.user_id == user_id)
|
|
|
|
return query.order_by(UserIndicatorPreferences.priority.desc()).all()
|
|
|
|
def _build_ai_prompt(
|
|
self,
|
|
request: AIPlanGenerationRequest,
|
|
indicator_preferences: List[UserIndicatorPreferences]
|
|
) -> str:
|
|
"""Build comprehensive prompt for AI plan generation"""
|
|
|
|
indicator_names = [pref.indicator_name for pref in indicator_preferences] if indicator_preferences else []
|
|
|
|
prompt = f"""You are an expert gold (XAU/USD) trading analyst. Generate a detailed daily trading plan based on the following information:
|
|
|
|
CURRENT MARKET DATA:
|
|
- Current Price: ${request.current_price:.2f}
|
|
- User's Risk Tolerance: {request.risk_tolerance}
|
|
- Available Capital: ${request.user_capital if request.user_capital else 'Not specified'}
|
|
|
|
USER'S PREFERRED TECHNICAL INDICATORS:
|
|
{', '.join(indicator_names) if indicator_names else 'No specific preferences - use standard analysis'}
|
|
|
|
INDICATOR DETAILS:
|
|
"""
|
|
|
|
for pref in indicator_preferences:
|
|
prompt += f"- {pref.indicator_name} (Priority: {pref.priority})"
|
|
if pref.parameters:
|
|
prompt += f" - Parameters: {json.dumps(pref.parameters)}"
|
|
if pref.notes:
|
|
prompt += f" - Notes: {pref.notes}"
|
|
prompt += "\n"
|
|
|
|
if request.price_data and len(request.price_data) > 0:
|
|
recent_prices = request.price_data[-10:] # Last 10 data points
|
|
prompt += f"\nRECENT PRICE ACTION (last {len(recent_prices)} periods):\n"
|
|
for i, pd in enumerate(recent_prices, 1):
|
|
prompt += f" {i}. Open: ${pd.open:.2f}, High: ${pd.high:.2f}, Low: ${pd.low:.2f}, Close: ${pd.close:.2f}\n"
|
|
|
|
if request.indicators_data:
|
|
prompt += f"\nCURRENT INDICATOR VALUES:\n"
|
|
for indicator, value in request.indicators_data.items():
|
|
prompt += f"- {indicator}: {value}\n"
|
|
|
|
prompt += """
|
|
|
|
Please generate a comprehensive daily trading plan with the following structure:
|
|
|
|
1. MARKET BIAS: Determine if the market is BULLISH, BEARISH, or NEUTRAL
|
|
2. CONFIDENCE: Your confidence level in this analysis (0-100)
|
|
3. DAILY TARGET: Suggested profit target in dollars (be realistic based on user's capital and risk tolerance)
|
|
4. MAX LOSS: Maximum acceptable loss for the day (align with risk tolerance)
|
|
5. ENTRY ZONE: Recommended price range for entering positions (min and max)
|
|
6. TARGET PRICE: Primary profit-taking level
|
|
7. STOP LOSS: Stop-loss level to protect capital
|
|
8. SUPPORT LEVELS: 3-5 key support levels below current price
|
|
9. RESISTANCE LEVELS: 3-5 key resistance levels above current price
|
|
10. MAX TRADES: Recommended maximum number of trades for the day
|
|
11. TRADING NOTES: Detailed strategy notes including:
|
|
- Why this bias?
|
|
- What indicators support this view?
|
|
- What to watch for during the day?
|
|
- Risk management considerations
|
|
- Market conditions and factors
|
|
12. REASONING: Detailed explanation of your analysis and why you recommend this plan
|
|
|
|
Format your response as a valid JSON object with these exact keys:
|
|
{
|
|
"market_bias": "BULLISH" | "BEARISH" | "NEUTRAL",
|
|
"confidence": 75.0,
|
|
"daily_target": 500.0,
|
|
"max_loss": 250.0,
|
|
"entry_zone_min": 2010.0,
|
|
"entry_zone_max": 2015.0,
|
|
"target_price": 2040.0,
|
|
"stop_loss": 2005.0,
|
|
"support_levels": [2000.0, 1990.0, 1980.0],
|
|
"resistance_levels": [2020.0, 2030.0, 2040.0],
|
|
"max_trades": 3,
|
|
"trading_notes": "Detailed strategy notes here...",
|
|
"reasoning": "Full analysis and reasoning here..."
|
|
}
|
|
|
|
Be specific, actionable, and realistic. Consider the user's risk tolerance and preferred indicators heavily in your analysis.
|
|
"""
|
|
|
|
return prompt
|
|
|
|
async def generate_plan(
|
|
self,
|
|
db: Session,
|
|
request: AIPlanGenerationRequest,
|
|
user_id: Optional[str] = None
|
|
) -> AIPlanGenerationResponse:
|
|
"""Generate an AI-powered trading plan"""
|
|
|
|
# Get user's indicator preferences if requested
|
|
indicator_preferences = []
|
|
if request.use_indicator_preferences:
|
|
indicator_preferences = self._get_user_indicator_preferences(db, user_id)
|
|
|
|
# Build AI prompt
|
|
prompt = self._build_ai_prompt(request, indicator_preferences)
|
|
|
|
# Call AI service
|
|
try:
|
|
# Use OpenRouter service to get AI response
|
|
ai_response = await openrouter_service.generate_trading_plan(prompt)
|
|
|
|
# Parse AI response (assuming it returns JSON)
|
|
if isinstance(ai_response, str):
|
|
plan_data = json.loads(ai_response)
|
|
else:
|
|
plan_data = ai_response
|
|
|
|
# Create database record
|
|
db_plan = AIPlanGeneration(
|
|
user_id=user_id,
|
|
plan_date=date.today(),
|
|
market_bias=plan_data.get("market_bias", "NEUTRAL"),
|
|
confidence=plan_data.get("confidence", 50.0),
|
|
daily_target=plan_data.get("daily_target"),
|
|
max_loss=plan_data.get("max_loss"),
|
|
entry_zone_min=plan_data.get("entry_zone_min"),
|
|
entry_zone_max=plan_data.get("entry_zone_max"),
|
|
target_price=plan_data.get("target_price"),
|
|
stop_loss=plan_data.get("stop_loss"),
|
|
support_levels=plan_data.get("support_levels", []),
|
|
resistance_levels=plan_data.get("resistance_levels", []),
|
|
max_trades=plan_data.get("max_trades", 3),
|
|
trading_notes=plan_data.get("trading_notes"),
|
|
reasoning=plan_data.get("reasoning"),
|
|
indicators_used=[pref.indicator_name for pref in indicator_preferences],
|
|
market_conditions={
|
|
"current_price": request.current_price,
|
|
"risk_tolerance": request.risk_tolerance,
|
|
},
|
|
ai_model=openrouter_service.model,
|
|
accepted=False,
|
|
modified=False
|
|
)
|
|
|
|
db.add(db_plan)
|
|
db.commit()
|
|
db.refresh(db_plan)
|
|
|
|
# Return response
|
|
return AIPlanGenerationResponse(
|
|
id=db_plan.id,
|
|
plan_date=str(db_plan.plan_date),
|
|
market_bias=MarketBias(db_plan.market_bias),
|
|
confidence=db_plan.confidence,
|
|
daily_target=db_plan.daily_target,
|
|
max_loss=db_plan.max_loss,
|
|
entry_zone_min=db_plan.entry_zone_min,
|
|
entry_zone_max=db_plan.entry_zone_max,
|
|
target_price=db_plan.target_price,
|
|
stop_loss=db_plan.stop_loss,
|
|
support_levels=db_plan.support_levels,
|
|
resistance_levels=db_plan.resistance_levels,
|
|
max_trades=db_plan.max_trades,
|
|
trading_notes=db_plan.trading_notes,
|
|
indicators_used=db_plan.indicators_used,
|
|
reasoning=db_plan.reasoning,
|
|
market_conditions=db_plan.market_conditions,
|
|
ai_model=db_plan.ai_model,
|
|
created_at=db_plan.created_at
|
|
)
|
|
|
|
except json.JSONDecodeError as e:
|
|
raise Exception(f"Failed to parse AI response: {str(e)}")
|
|
except Exception as e:
|
|
raise Exception(f"AI plan generation failed: {str(e)}")
|
|
|
|
async def get_plan_history(
|
|
self,
|
|
db: Session,
|
|
user_id: Optional[str] = None,
|
|
limit: int = 10
|
|
) -> List[AIPlanGenerationResponse]:
|
|
"""Get historical AI-generated plans"""
|
|
query = db.query(AIPlanGeneration)
|
|
|
|
if user_id:
|
|
query = query.filter(AIPlanGeneration.user_id == user_id)
|
|
|
|
plans = query.order_by(AIPlanGeneration.created_at.desc()).limit(limit).all()
|
|
|
|
return [
|
|
AIPlanGenerationResponse(
|
|
id=plan.id,
|
|
plan_date=str(plan.plan_date),
|
|
market_bias=MarketBias(plan.market_bias),
|
|
confidence=plan.confidence,
|
|
daily_target=plan.daily_target,
|
|
max_loss=plan.max_loss,
|
|
entry_zone_min=plan.entry_zone_min,
|
|
entry_zone_max=plan.entry_zone_max,
|
|
target_price=plan.target_price,
|
|
stop_loss=plan.stop_loss,
|
|
support_levels=plan.support_levels,
|
|
resistance_levels=plan.resistance_levels,
|
|
max_trades=plan.max_trades,
|
|
trading_notes=plan.trading_notes,
|
|
indicators_used=plan.indicators_used,
|
|
reasoning=plan.reasoning,
|
|
market_conditions=plan.market_conditions,
|
|
ai_model=plan.ai_model,
|
|
created_at=plan.created_at
|
|
)
|
|
for plan in plans
|
|
]
|
|
|
|
async def submit_feedback(
|
|
self,
|
|
db: Session,
|
|
plan_id: int,
|
|
accepted: bool,
|
|
modified: bool = False,
|
|
feedback: Optional[str] = None
|
|
):
|
|
"""Submit user feedback on an AI-generated plan"""
|
|
plan = db.query(AIPlanGeneration).filter(AIPlanGeneration.id == plan_id).first()
|
|
|
|
if not plan:
|
|
raise Exception("Plan not found")
|
|
|
|
plan.accepted = accepted
|
|
plan.modified = modified
|
|
plan.feedback = feedback
|
|
|
|
db.commit()
|
|
db.refresh(plan)
|
|
|
|
return plan
|
|
|
|
|
|
# Global instance
|
|
ai_plan_service = AIPlanService()
|