261 lines
9.6 KiB
Python
261 lines
9.6 KiB
Python
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
|