first commit
This commit is contained in:
260
api/views.py
Normal file
260
api/views.py
Normal file
@@ -0,0 +1,260 @@
|
||||
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
|
||||
Reference in New Issue
Block a user