first commit
This commit is contained in:
229
config/tasks.py
Normal file
229
config/tasks.py
Normal file
@@ -0,0 +1,229 @@
|
||||
from celery import shared_task
|
||||
from celery.utils.log import get_task_logger
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
from django.utils import timezone
|
||||
from django.db import transaction
|
||||
|
||||
from monitor.models import BitcoinPrice, MarketAnalysis, SystemStatus
|
||||
from monitor.services.analyzer import MarketAnalyzer
|
||||
from monitor.services.data_fetcher import CoinGeckoFetcher
|
||||
|
||||
logger = get_task_logger(__name__)
|
||||
|
||||
|
||||
@shared_task(bind=True, max_retries=3)
|
||||
def fetch_bitcoin_price_task(self):
|
||||
"""
|
||||
Fetch current Bitcoin price and save to database.
|
||||
Runs every 5 minutes by default.
|
||||
"""
|
||||
logger.info("Starting Bitcoin price fetch task...")
|
||||
|
||||
try:
|
||||
fetcher = CoinGeckoFetcher()
|
||||
|
||||
# Fetch current price
|
||||
price_data = fetcher.fetch_current_price()
|
||||
|
||||
if not price_data:
|
||||
logger.error("Failed to fetch price data")
|
||||
raise Exception("No price data received")
|
||||
|
||||
# Save to database in transaction
|
||||
with transaction.atomic():
|
||||
bitcoin_price = BitcoinPrice.objects.create(
|
||||
timestamp=price_data['timestamp'],
|
||||
price_usd=price_data['price_usd'],
|
||||
volume=price_data.get('volume'),
|
||||
market_cap=price_data.get('market_cap'),
|
||||
)
|
||||
|
||||
# Update system status
|
||||
SystemStatus.objects.create(
|
||||
current_price=bitcoin_price.price_usd,
|
||||
last_hourly_update=timezone.now(),
|
||||
last_successful_fetch=timezone.now(),
|
||||
is_stale=False,
|
||||
is_healthy=True,
|
||||
)
|
||||
|
||||
logger.info(f"Successfully fetched and saved Bitcoin price: ${price_data['price_usd']}")
|
||||
|
||||
# Trigger analysis if it's been more than 55 minutes since last analysis
|
||||
# or if this is a significant price change
|
||||
last_analysis = MarketAnalysis.objects.filter(
|
||||
period='hourly'
|
||||
).order_by('-timestamp').first()
|
||||
|
||||
should_analyze = False
|
||||
if not last_analysis:
|
||||
should_analyze = True
|
||||
else:
|
||||
time_since_analysis = timezone.now() - last_analysis.timestamp
|
||||
if time_since_analysis.total_seconds() > 3300: # 55 minutes
|
||||
should_analyze = True
|
||||
|
||||
if should_analyze:
|
||||
# Run analysis in separate task
|
||||
run_hourly_analysis_task.delay()
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'price': float(price_data['price_usd']),
|
||||
'timestamp': price_data['timestamp'].isoformat(),
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in fetch_bitcoin_price_task: {e}")
|
||||
|
||||
# Update system status with error
|
||||
SystemStatus.objects.create(
|
||||
last_error=str(e),
|
||||
is_stale=True,
|
||||
is_healthy=False,
|
||||
)
|
||||
|
||||
# Retry the task
|
||||
self.retry(exc=e, countdown=60)
|
||||
|
||||
return {
|
||||
'success': False,
|
||||
'error': str(e),
|
||||
}
|
||||
|
||||
|
||||
@shared_task
|
||||
def run_hourly_analysis_task():
|
||||
"""
|
||||
Run hourly market analysis.
|
||||
Runs every hour by default.
|
||||
"""
|
||||
logger.info("Starting hourly analysis task...")
|
||||
|
||||
try:
|
||||
analyzer = MarketAnalyzer(threshold_percent=15.0)
|
||||
|
||||
# Run hourly analysis
|
||||
analysis = analyzer.analyze_market('hourly')
|
||||
|
||||
if analysis:
|
||||
logger.info(f"Hourly analysis completed: {analysis.status} at ${analysis.current_price}")
|
||||
|
||||
# Check if this is an event and log it
|
||||
if analysis.is_event:
|
||||
logger.warning(
|
||||
f"Market event detected: {analysis.event_type} "
|
||||
f"at ${analysis.current_price}"
|
||||
)
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'analysis_id': analysis.id,
|
||||
'status': analysis.status,
|
||||
'price': float(analysis.current_price),
|
||||
'is_event': analysis.is_event,
|
||||
}
|
||||
else:
|
||||
logger.warning("Hourly analysis returned no results")
|
||||
return {'success': False, 'error': 'No analysis results'}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in run_hourly_analysis_task: {e}")
|
||||
return {'success': False, 'error': str(e)}
|
||||
|
||||
|
||||
@shared_task
|
||||
def cleanup_old_data_task():
|
||||
"""
|
||||
Clean up old data to keep database size manageable.
|
||||
Runs once a day.
|
||||
"""
|
||||
logger.info("Starting data cleanup task...")
|
||||
|
||||
try:
|
||||
# Keep only last 30 days of price data for performance
|
||||
cutoff_date = timezone.now() - timedelta(days=30)
|
||||
deleted_count, _ = BitcoinPrice.objects.filter(
|
||||
timestamp__lt=cutoff_date
|
||||
).delete()
|
||||
|
||||
# Keep only last 1000 system status entries
|
||||
status_entries = SystemStatus.objects.all().order_by('-timestamp')
|
||||
if status_entries.count() > 1000:
|
||||
status_to_delete = status_entries[1000:]
|
||||
deleted_status_count, _ = status_to_delete.delete()
|
||||
|
||||
# Keep only last 365 analyses
|
||||
analyses = MarketAnalysis.objects.all().order_by('-timestamp')
|
||||
if analyses.count() > 365:
|
||||
analyses_to_delete = analyses[365:]
|
||||
deleted_analyses_count, _ = analyses_to_delete.delete()
|
||||
|
||||
logger.info(f"Cleanup completed. Deleted {deleted_count} old price records.")
|
||||
return {
|
||||
'success': True,
|
||||
'deleted_prices': deleted_count,
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in cleanup_old_data_task: {e}")
|
||||
return {'success': False, 'error': str(e)}
|
||||
|
||||
|
||||
@shared_task
|
||||
def check_system_health_task():
|
||||
"""
|
||||
Check system health and log status.
|
||||
Runs every 10 minutes.
|
||||
"""
|
||||
logger.info("Checking system health...")
|
||||
|
||||
try:
|
||||
# Check if we have recent data
|
||||
recent_price = BitcoinPrice.objects.order_by('-timestamp').first()
|
||||
recent_analysis = MarketAnalysis.objects.order_by('-timestamp').first()
|
||||
|
||||
is_healthy = True
|
||||
issues = []
|
||||
|
||||
if not recent_price:
|
||||
issues.append("No price data available")
|
||||
is_healthy = False
|
||||
else:
|
||||
price_age = (timezone.now() - recent_price.timestamp).total_seconds()
|
||||
if price_age > 3600: # More than 1 hour old
|
||||
issues.append(f"Price data is {price_age/60:.0f} minutes old")
|
||||
is_healthy = False
|
||||
|
||||
if not recent_analysis:
|
||||
issues.append("No analysis data available")
|
||||
is_healthy = False
|
||||
else:
|
||||
analysis_age = (timezone.now() - recent_analysis.timestamp).total_seconds()
|
||||
if analysis_age > 7200: # More than 2 hours old
|
||||
issues.append(f"Analysis data is {analysis_age/3600:.1f} hours old")
|
||||
is_healthy = False
|
||||
|
||||
# Log health status
|
||||
if is_healthy:
|
||||
logger.info("System is healthy")
|
||||
else:
|
||||
logger.warning(f"System has issues: {', '.join(issues)}")
|
||||
|
||||
# Update system status
|
||||
SystemStatus.objects.create(
|
||||
is_healthy=is_healthy,
|
||||
last_error=', '.join(issues) if issues else None,
|
||||
)
|
||||
|
||||
return {
|
||||
'healthy': is_healthy,
|
||||
'issues': issues,
|
||||
'last_price_time': recent_price.timestamp.isoformat() if recent_price else None,
|
||||
'last_analysis_time': recent_analysis.timestamp.isoformat() if recent_analysis else None,
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in check_system_health_task: {e}")
|
||||
return {'healthy': False, 'error': str(e)}
|
||||
Reference in New Issue
Block a user