first commit

This commit is contained in:
2026-01-16 22:20:18 +03:00
commit 5d437e5e28
56 changed files with 4463 additions and 0 deletions

260
api/views.py Normal file
View 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