from rest_framework import viewsets, status from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.permissions import AllowAny from django.utils import timezone from django.db.models import Avg, Min, Max from datetime import timedelta from monitor.models import BitcoinPrice, MarketAnalysis from monitor.services.analyzer import MarketAnalyzer from api.serializers import ( BitcoinPriceSerializer, MarketAnalysisSerializer, StatusResponseSerializer, ChartDataSerializer, HealthCheckSerializer, ) class StatusView(APIView): """ API endpoint for current system status. Returns: JSON similar to your Zig version /api/status """ permission_classes = [AllowAny] def get(self, request): analyzer = MarketAnalyzer() # Get hourly analysis (as current status) hourly_analysis = analyzer.get_latest_analysis('hourly') # Get yearly analysis for yearly stats yearly_analysis = analyzer.get_latest_analysis('yearly') # If no analysis exists, run it if not hourly_analysis: hourly_analysis = analyzer.analyze_market('hourly') if not yearly_analysis: yearly_analysis = analyzer.analyze_market('yearly') # Prepare status data matching your Zig structure status_data = { 'current_price': float(hourly_analysis.current_price) if hourly_analysis else 0.0, 'current_status': hourly_analysis.status if hourly_analysis else 'neutral', 'yearly_average': float(yearly_analysis.average_price) if yearly_analysis else 0.0, 'yearly_min': float(yearly_analysis.min_price) if yearly_analysis else 0.0, 'yearly_max': float(yearly_analysis.max_price) if yearly_analysis else 0.0, 'threshold_percent': float(hourly_analysis.threshold_percent) if hourly_analysis else 15.0, 'lower_threshold': float(hourly_analysis.lower_threshold) if hourly_analysis else 0.0, 'upper_threshold': float(hourly_analysis.upper_threshold) if hourly_analysis else 0.0, 'last_yearly_update': yearly_analysis.timestamp if yearly_analysis else None, 'last_hourly_update': hourly_analysis.timestamp if hourly_analysis else None, 'stale_yearly': not yearly_analysis, 'stale_hourly': not hourly_analysis, 'fetch_error': None, # You can add error tracking later } serializer = StatusResponseSerializer(status_data) return Response(serializer.data) class EventsViewSet(viewsets.ReadOnlyModelViewSet): """ API endpoint for price events. Returns: JSON similar to your Zig version /api/events """ permission_classes = [AllowAny] serializer_class = MarketAnalysisSerializer def get_queryset(self): # Get analyses that are events queryset = MarketAnalysis.objects.filter(is_event=True).order_by('-timestamp') # Filter by event type if provided event_type = self.request.query_params.get('event_type', None) if event_type: queryset = queryset.filter(event_type=event_type) # Limit to last 365 events by default limit = int(self.request.query_params.get('limit', 365)) return queryset[:limit] @action(detail=False, methods=['get']) def recent(self, request): """Get recent events (last 24 hours).""" cutoff = timezone.now() - timedelta(hours=24) recent_events = MarketAnalysis.objects.filter( is_event=True, timestamp__gte=cutoff ).order_by('-timestamp') serializer = self.get_serializer(recent_events, many=True) return Response(serializer.data) class ChartDataView(APIView): """ API endpoint for chart data. Returns: JSON similar to your Zig version /api/chart-data """ permission_classes = [AllowAny] def get(self, request): # Get parameters days = int(request.query_params.get('days', 365)) cutoff = timezone.now() - timedelta(days=days) # Get price data prices = BitcoinPrice.objects.filter( timestamp__gte=cutoff ).order_by('timestamp') # Get events in same period events = MarketAnalysis.objects.filter( is_event=True, timestamp__gte=cutoff ).order_by('timestamp') # Prepare chart data chart_data = { 'timestamps': [p.timestamp for p in prices], 'prices': [float(p.price_usd) for p in prices], 'events': MarketAnalysisSerializer(events, many=True).data, 'threshold_percent': 15.0, # Default threshold } serializer = ChartDataSerializer(chart_data) return Response(serializer.data) class StatsView(APIView): """ API endpoint for statistics. Returns: JSON similar to your Zig version /api/stats """ permission_classes = [AllowAny] def get(self, request): analyzer = MarketAnalyzer() # Get analyses hourly = analyzer.get_latest_analysis('hourly') daily = analyzer.get_latest_analysis('daily') yearly = analyzer.get_latest_analysis('yearly') # Calculate additional stats total_events = MarketAnalysis.objects.filter(is_event=True).count() total_prices = BitcoinPrice.objects.count() # Get recent price for volatility calculation recent_prices = BitcoinPrice.objects.order_by('-timestamp')[:100] if len(recent_prices) > 1: # Simple volatility calculation prices = [float(p.price_usd) for p in recent_prices] avg_price = sum(prices) / len(prices) price_changes = [] for i in range(1, len(prices)): change = (prices[i] - prices[i-1]) / prices[i-1] * 100 price_changes.append(abs(change)) volatility = sum(price_changes) / len(price_changes) if price_changes else 0 else: volatility = 0 stats_data = { 'yearly_average': float(yearly.average_price) if yearly else 0.0, 'yearly_min': float(yearly.min_price) if yearly else 0.0, 'yearly_max': float(yearly.max_price) if yearly else 0.0, 'threshold_percent': float(hourly.threshold_percent) if hourly else 15.0, 'current_price': float(hourly.current_price) if hourly else 0.0, 'current_status': hourly.status if hourly else 'neutral', 'total_events': total_events, 'last_yearly_update': yearly.timestamp if yearly else None, 'last_hourly_update': hourly.timestamp if hourly else None, 'volatility_percent': round(volatility, 2), 'total_price_records': total_prices, } return Response(stats_data) class HealthCheckView(APIView): """ API endpoint for health check. Returns: JSON similar to your Zig version /health """ permission_classes = [AllowAny] def get(self, request): analyzer = MarketAnalyzer() # Check if we have recent data recent_price = BitcoinPrice.objects.order_by('-timestamp').first() recent_analysis = MarketAnalysis.objects.order_by('-timestamp').first() # Determine health status is_healthy = True checks = {} if recent_price: price_age = (timezone.now() - recent_price.timestamp).total_seconds() checks['price_age_seconds'] = price_age checks['price_fresh'] = price_age < 3600 # Less than 1 hour old if price_age >= 3600: is_healthy = False else: checks['price_fresh'] = False is_healthy = False if recent_analysis: analysis_age = (timezone.now() - recent_analysis.timestamp).total_seconds() checks['analysis_age_seconds'] = analysis_age checks['analysis_fresh'] = analysis_age < 7200 # Less than 2 hours old if analysis_age >= 7200: is_healthy = False else: checks['analysis_fresh'] = False is_healthy = False checks['database_connected'] = True checks['has_prices'] = BitcoinPrice.objects.exists() checks['has_analyses'] = MarketAnalysis.objects.exists() health_data = { 'status': 'healthy' if is_healthy else 'unhealthy', 'timestamp': timezone.now(), 'checks': checks, } serializer = HealthCheckSerializer(health_data) return Response(serializer.data) class BitcoinPriceViewSet(viewsets.ReadOnlyModelViewSet): """ API endpoint for Bitcoin price data. """ permission_classes = [AllowAny] queryset = BitcoinPrice.objects.all().order_by('-timestamp') serializer_class = BitcoinPriceSerializer def get_queryset(self): queryset = super().get_queryset() # Filter by date range start_date = self.request.query_params.get('start_date', None) end_date = self.request.query_params.get('end_date', None) if start_date: queryset = queryset.filter(timestamp__gte=start_date) if end_date: queryset = queryset.filter(timestamp__lte=end_date) # Limit results limit = self.request.query_params.get('limit', None) if limit: queryset = queryset[:int(limit)] return queryset