Reorganize UI for external trading workflow with manual trade logging

- 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
This commit is contained in:
Krikorios
2025-11-16 07:50:00 +02:00
parent 73a26ea9b7
commit b5e2b02cb8
20 changed files with 4347 additions and 29 deletions
+78 -2
View File
@@ -1,7 +1,18 @@
from fastapi import APIRouter, HTTPException
from fastapi import APIRouter, HTTPException, Depends
from sqlalchemy.orm import Session
from typing import List, Optional
from app.services.openrouter import openrouter_service
from app.schemas.schemas import AIAnalysisRequest, AIAnalysisResponse
from app.schemas.schemas import (
AIAnalysisRequest,
AIAnalysisResponse,
AIPlanGenerationRequest,
AIPlanGenerationResponse,
AIPlanFeedback
)
from app.services.decisions import log_decision
from app.services.ai_plan_service import ai_plan_service
from app.db.database import get_db
router = APIRouter(prefix="/ai", tags=["AI Analysis"])
@@ -41,3 +52,68 @@ async def analyze_scenario(request: AIAnalysisRequest):
raise HTTPException(
status_code=500, detail=f"AI analysis failed: {str(e)}"
)
@router.post("/generate-plan", response_model=AIPlanGenerationResponse)
async def generate_trading_plan(
request: AIPlanGenerationRequest,
user_id: Optional[str] = None,
db: Session = Depends(get_db)
):
"""
Generate a comprehensive daily trading plan using AI
Uses user's indicator preferences and market data to create:
- Market bias (BULLISH/BEARISH/NEUTRAL)
- Entry zones and targets
- Support and resistance levels
- Risk management parameters
- Trading strategy notes
"""
try:
plan = await ai_plan_service.generate_plan(db, request, user_id)
return plan
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"AI plan generation failed: {str(e)}"
)
@router.get("/plans/history", response_model=List[AIPlanGenerationResponse])
async def get_plan_history(
user_id: Optional[str] = None,
limit: int = 10,
db: Session = Depends(get_db)
):
"""Get historical AI-generated trading plans"""
try:
plans = await ai_plan_service.get_plan_history(db, user_id, limit)
return plans
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"Failed to fetch plan history: {str(e)}"
)
@router.post("/plans/feedback")
async def submit_plan_feedback(
feedback: AIPlanFeedback,
db: Session = Depends(get_db)
):
"""Submit feedback on an AI-generated plan"""
try:
plan = await ai_plan_service.submit_feedback(
db,
feedback.plan_id,
feedback.accepted,
feedback.modified,
feedback.feedback
)
return {"success": True, "message": "Feedback submitted successfully"}
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"Failed to submit feedback: {str(e)}"
)
+150 -3
View File
@@ -1,9 +1,18 @@
from __future__ import annotations
from fastapi import APIRouter
from typing import Any, Dict
from fastapi import APIRouter, HTTPException, Depends, status
from typing import Any, Dict, List
from sqlalchemy.orm import Session
from app.services.settings import get_models, update_models, get_exchanges, update_exchanges
from app.db.database import get_db
from app.models.models import UserIndicatorPreferences
from app.schemas.schemas import (
IndicatorPreferenceCreate,
IndicatorPreferenceUpdate,
IndicatorPreferenceResponse,
IndicatorPreferencesListResponse
)
router = APIRouter(prefix="/settings", tags=["Settings"])
@@ -25,4 +34,142 @@ async def exchanges_get() -> Dict[str, Any]:
@router.put("/exchanges")
async def exchanges_put(patch: Dict[str, Any]) -> Dict[str, Any]:
return update_exchanges(patch)
return update_exchanges(patch)
# ============================================================================
# INDICATOR PREFERENCES ENDPOINTS
# ============================================================================
@router.get("/indicators/preferences", response_model=IndicatorPreferencesListResponse)
async def get_indicator_preferences(
user_id: str = None,
enabled_only: bool = False,
db: Session = Depends(get_db)
):
"""Get user's indicator preferences"""
query = db.query(UserIndicatorPreferences)
if user_id:
query = query.filter(UserIndicatorPreferences.user_id == user_id)
if enabled_only:
query = query.filter(UserIndicatorPreferences.enabled == True)
preferences = query.order_by(UserIndicatorPreferences.priority.desc()).all()
return {
"preferences": preferences,
"total": len(preferences)
}
@router.post("/indicators/preferences", response_model=IndicatorPreferenceResponse, status_code=status.HTTP_201_CREATED)
async def create_indicator_preference(
preference: IndicatorPreferenceCreate,
user_id: str = None,
db: Session = Depends(get_db)
):
"""Create a new indicator preference"""
# Check if indicator already exists for this user
existing = db.query(UserIndicatorPreferences).filter(
UserIndicatorPreferences.user_id == user_id,
UserIndicatorPreferences.indicator_name == preference.indicator_name
).first()
if existing:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Preference for indicator '{preference.indicator_name}' already exists"
)
db_preference = UserIndicatorPreferences(
user_id=user_id,
**preference.dict()
)
db.add(db_preference)
db.commit()
db.refresh(db_preference)
return db_preference
@router.put("/indicators/preferences/{preference_id}", response_model=IndicatorPreferenceResponse)
async def update_indicator_preference(
preference_id: int,
preference_update: IndicatorPreferenceUpdate,
db: Session = Depends(get_db)
):
"""Update an indicator preference"""
db_preference = db.query(UserIndicatorPreferences).filter(
UserIndicatorPreferences.id == preference_id
).first()
if not db_preference:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Indicator preference not found"
)
update_data = preference_update.dict(exclude_unset=True)
for key, value in update_data.items():
setattr(db_preference, key, value)
db.commit()
db.refresh(db_preference)
return db_preference
@router.delete("/indicators/preferences/{preference_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_indicator_preference(
preference_id: int,
db: Session = Depends(get_db)
):
"""Delete an indicator preference"""
db_preference = db.query(UserIndicatorPreferences).filter(
UserIndicatorPreferences.id == preference_id
).first()
if not db_preference:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Indicator preference not found"
)
db.delete(db_preference)
db.commit()
@router.post("/indicators/preferences/bulk", response_model=IndicatorPreferencesListResponse)
async def create_bulk_indicator_preferences(
preferences: List[IndicatorPreferenceCreate],
user_id: str = None,
db: Session = Depends(get_db)
):
"""Create multiple indicator preferences at once"""
created_preferences = []
for pref in preferences:
# Skip if already exists
existing = db.query(UserIndicatorPreferences).filter(
UserIndicatorPreferences.user_id == user_id,
UserIndicatorPreferences.indicator_name == pref.indicator_name
).first()
if not existing:
db_preference = UserIndicatorPreferences(
user_id=user_id,
**pref.dict()
)
db.add(db_preference)
created_preferences.append(db_preference)
db.commit()
# Refresh all created preferences
for pref in created_preferences:
db.refresh(pref)
return {
"preferences": created_preferences,
"total": len(created_preferences)
}