Team Ai
Apppublic

diegobeyl/backtesting

sourceHugging Faceupdated 9mo agoView on Hugging Face
2likes
main.py218 linesDownload Raw Back to api
1"""
2FastAPI Main Application V2 - Enhanced with rate limiting, structured logging and caching
3"""
4import sys
5import logging
6from pathlib import Path
7from fastapi import FastAPI, Request
8from fastapi.staticfiles import StaticFiles
9from fastapi.responses import FileResponse
10from fastapi.middleware.cors import CORSMiddleware
11from slowapi import Limiter, _rate_limit_exceeded_handler
12from slowapi.util import get_remote_address
13from slowapi.errors import RateLimitExceeded
14
15# Desactivar TODOS los loggers de terceros ANTES de cualquier import
16for logger_name in ['uvicorn', 'uvicorn.access', 'uvicorn.error', 'asyncio', 'click']:
17    logger_obj = logging.getLogger(logger_name)
18    logger_obj.handlers = []
19    logger_obj.propagate = False
20    logger_obj.setLevel(logging.CRITICAL)
21
22# Limpiar el logger raíz
23root_logger = logging.getLogger()
24for handler in root_logger.handlers[:]:
25    handler.close()
26    root_logger.removeHandler(handler)
27root_logger.propagate = False
28
29# Add parent directory for imports
30sys.path.insert(0, str(Path(__file__).parent.parent))
31
32from api.routes import router as api_router
33from api.config_manager import get_config_manager
34from config import config
35from utils.logger import setup_logger
36
37import pandas as pd
38import functools
39
40# Setup logger
41logger = setup_logger(__name__)
42
43# --- MONKEYPATCH FOR PANDAS FREQUENCY COMPATIBILITY ---
44def patch_pandas_resample():
45    original_series_resample = pd.Series.resample
46    original_df_resample = pd.DataFrame.resample
47    
48    @functools.wraps(original_series_resample)
49    def patched_series_resample(self, rule, *args, **kwargs):
50        if rule == 'ME':
51            rule = 'M'
52        return original_series_resample(self, rule, *args, **kwargs)
53        
54    @functools.wraps(original_df_resample)
55    def patched_df_resample(self, rule, *args, **kwargs):
56        if rule == 'ME':
57            rule = 'M'
58        return original_df_resample(self, rule, *args, **kwargs)
59        
60    pd.Series.resample = patched_series_resample
61    pd.DataFrame.resample = patched_df_resample
62    logger.info("Pandas resample patched for 'ME' frequency compatibility")
63
64# Apply patch immediately
65patch_pandas_resample()
66# ------------------------------------------------------
67
68# Initialize rate limiter
69limiter = Limiter(
70    key_func=get_remote_address,
71    default_limits=[f"{config.RATE_LIMIT_PER_MINUTE}/minute", f"{config.RATE_LIMIT_PER_HOUR}/hour"]
72)
73
74# Create FastAPI app
75app = FastAPI(
76    title="Donchian Backtesting API V2",
77    description="Enhanced backtesting system with rate limiting, caching, and structured logging",
78    version="2.0.0",
79    debug=config.DEBUG
80)
81
82# Add rate limiter state
83app.state.limiter = limiter
84app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
85
86# Flags para evitar eventos duplicados
87_startup_done = False
88_shutdown_done = False
89
90# Configure CORS
91app.add_middleware(
92    CORSMiddleware,
93    allow_origins=config.CORS_ORIGINS,
94    allow_credentials=True,
95    allow_methods=["*"],
96    allow_headers=["*"],
97)
98
99# Request logging middleware
100@app.middleware("http")
101async def log_requests(request: Request, call_next):
102    """Log all incoming requests."""
103    logger.info(
104        f"Request: {request.method} {request.url.path}",
105        extra={
106            "method": request.method,
107            "path": request.url.path,
108            "client": request.client.host if request.client else "unknown"
109        }
110    )
111    response = await call_next(request)
112    logger.info(
113        f"Response: {request.method} {request.url.path} - Status {response.status_code}",
114        extra={
115            "method": request.method,
116            "path": request.url.path,
117            "status_code": response.status_code
118        }
119    )
120    return response
121
122# Startup event
123@app.on_event("startup")
124async def startup_event():
125    """Initialize application on startup."""
126    global _startup_done
127    if _startup_done:
128        return
129    _startup_done = True
130    logger.info(f"Starting Backtesting API V2 in {config.ENVIRONMENT} mode")
131    logger.info(f"Rate limiting: {config.RATE_LIMIT_PER_MINUTE}/min, {config.RATE_LIMIT_PER_HOUR}/hour")
132    logger.info(f"Cache enabled: {config.ENABLE_CACHE}")
133    logger.info(f"CORS origins: {config.CORS_ORIGINS}")
134
135# Shutdown event
136@app.on_event("shutdown")
137async def shutdown_event():
138    """Cleanup on shutdown."""
139    global _shutdown_done
140    if _shutdown_done:
141        return
142    _shutdown_done = True
143    logger.info("Shutting down Backtesting API V2")
144
145# Include API routes
146app.include_router(api_router, prefix="/api")
147
148# Mount static files
149static_path = Path(__file__).parent.parent / "static"
150if static_path.exists():
151    app.mount("/static", StaticFiles(directory=str(static_path)), name="static")
152
153# Root endpoint
154@app.get("/")
155@limiter.limit(f"{config.RATE_LIMIT_PER_MINUTE * 2}/minute")  # More permissive for root
156async def read_root(request: Request):
157    """API root endpoint with documentation links."""
158    return {
159        "message": "Donchian Backtesting API V2",
160        "version": "2.0.0",
161        "description": "Enhanced backtesting system with rate limiting, caching, and structured logging",
162        "documentation": "/docs",
163        "openapi": "/openapi.json",
164        "health_check": "/health",
165        "api_endpoints": "/docs#/default",
166        "streamlit_ui": "http://localhost:8501"
167    }
168
169# Health check endpoint
170@app.get("/health")
171async def health_check():
172    """Health check endpoint for monitoring."""
173    from utils.cache import get_cache_stats
174    
175    return {
176        "status": "healthy",
177        "version": "2.0.0",
178        "environment": config.ENVIRONMENT,
179        "cache": get_cache_stats() if config.ENABLE_CACHE else {"enabled": False}
180    }
181
182# Cache management endpoint (admin only in production)
183@app.post("/api/cache/clear")
184@limiter.limit("5/minute")
185async def clear_cache_endpoint(request: Request):
186    """Clear application cache (use with caution)."""
187    from utils.cache import clear_cache
188    
189    if config.ENVIRONMENT == "production":
190        return {"error": "Cache clearing disabled in production"}
191    
192    clear_cache()
193    logger.warning("Cache cleared via API endpoint")
194    return {"message": "Cache cleared successfully"}
195
196if __name__ == "__main__":
197    import uvicorn
198    
199    # Desactivar COMPLETAMENTE los loggers de uvicorn antes de ejecutar
200    for logger_name in ['uvicorn', 'uvicorn.access', 'uvicorn.error', 'asyncio']:
201        logger_obj = logging.getLogger(logger_name)
202        logger_obj.handlers = []
203        logger_obj.propagate = False
204        logger_obj.setLevel(logging.CRITICAL)  # Silenciar completamente
205    
206    # Configuración de uvicorn sin logging
207    uvicorn_config = {
208        "app": "api.main:app",
209        "host": config.API_HOST,
210        "port": config.API_PORT,
211        "reload": False,
212        "log_level": "critical",  # Solo mostrar críticos
213        "access_log": False,
214        "use_colors": False
215    }
216    
217    uvicorn.run(**uvicorn_config)
218