2025-05-31 22:30:56 +08:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
"""
|
|
|
|
|
Clean Production OKX Data Collector
|
|
|
|
|
|
2025-06-10 12:55:27 +08:00
|
|
|
Simplified production script using the new DataCollectionService architecture.
|
|
|
|
|
Provides clean console output with minimal logging for production environments.
|
2025-05-31 22:30:56 +08:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import asyncio
|
|
|
|
|
import signal
|
|
|
|
|
import sys
|
|
|
|
|
import time
|
|
|
|
|
import json
|
2025-06-10 12:55:27 +08:00
|
|
|
from typing import Optional
|
2025-05-31 22:30:56 +08:00
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
# Add project root to path
|
|
|
|
|
project_root = Path(__file__).parent.parent
|
|
|
|
|
sys.path.insert(0, str(project_root))
|
|
|
|
|
|
2025-06-10 12:55:27 +08:00
|
|
|
# Set environment for clean production logging
|
2025-05-31 22:30:56 +08:00
|
|
|
import os
|
|
|
|
|
os.environ['DEBUG'] = 'false'
|
|
|
|
|
|
2025-06-10 12:55:27 +08:00
|
|
|
# Suppress verbose SQLAlchemy logging
|
2025-05-31 22:30:56 +08:00
|
|
|
import logging
|
|
|
|
|
logging.getLogger('sqlalchemy').setLevel(logging.CRITICAL)
|
|
|
|
|
logging.getLogger('sqlalchemy.engine').setLevel(logging.CRITICAL)
|
|
|
|
|
logging.getLogger('sqlalchemy.pool').setLevel(logging.CRITICAL)
|
|
|
|
|
logging.getLogger('sqlalchemy.dialects').setLevel(logging.CRITICAL)
|
|
|
|
|
logging.getLogger('sqlalchemy.orm').setLevel(logging.CRITICAL)
|
|
|
|
|
|
2025-06-10 12:55:27 +08:00
|
|
|
from data.collection_service import run_data_collection_service
|
2025-05-31 22:30:56 +08:00
|
|
|
|
|
|
|
|
|
2025-06-10 12:55:27 +08:00
|
|
|
async def get_config_timeframes(config_path: str) -> str:
|
|
|
|
|
"""Get timeframes from configuration for display."""
|
|
|
|
|
try:
|
|
|
|
|
with open(config_path, 'r') as f:
|
|
|
|
|
config = json.load(f)
|
|
|
|
|
# Get unique timeframes from all enabled trading pairs
|
|
|
|
|
all_timeframes = set()
|
|
|
|
|
for pair in config.get('trading_pairs', []):
|
|
|
|
|
if pair.get('enabled', True):
|
2025-06-02 13:27:01 +08:00
|
|
|
all_timeframes.update(pair.get('timeframes', ['1m', '5m']))
|
2025-06-10 12:55:27 +08:00
|
|
|
return ', '.join(sorted(all_timeframes))
|
|
|
|
|
except:
|
|
|
|
|
return "configured timeframes"
|
2025-05-31 22:30:56 +08:00
|
|
|
|
|
|
|
|
|
2025-06-10 12:55:27 +08:00
|
|
|
async def run_clean_production(duration_hours: Optional[float] = None) -> bool:
|
2025-05-31 22:30:56 +08:00
|
|
|
"""Run production collector with clean output."""
|
|
|
|
|
|
2025-06-10 12:55:27 +08:00
|
|
|
# Configuration path - use the new service config format
|
|
|
|
|
config_path = "config/data_collection.json"
|
2025-05-31 22:30:56 +08:00
|
|
|
|
|
|
|
|
try:
|
2025-06-10 12:55:27 +08:00
|
|
|
# Get timeframes for display
|
|
|
|
|
timeframes_str = await get_config_timeframes(config_path)
|
2025-06-02 13:27:01 +08:00
|
|
|
|
2025-05-31 22:30:56 +08:00
|
|
|
# Header
|
2025-06-10 12:55:27 +08:00
|
|
|
print("OKX PRODUCTION DATA COLLECTOR")
|
2025-05-31 22:30:56 +08:00
|
|
|
print("="*50)
|
2025-06-01 14:42:29 +08:00
|
|
|
if duration_hours:
|
2025-06-10 12:55:27 +08:00
|
|
|
print(f"Duration: {duration_hours} hours")
|
2025-06-01 14:42:29 +08:00
|
|
|
else:
|
2025-06-10 12:55:27 +08:00
|
|
|
print(f"Duration: Indefinite (until stopped)")
|
|
|
|
|
print(f"Timeframes: {timeframes_str}")
|
|
|
|
|
print(f"Database: Raw trades + aggregated candles")
|
|
|
|
|
print(f"Logs: logs/ directory")
|
2025-05-31 22:30:56 +08:00
|
|
|
print("="*50)
|
|
|
|
|
|
2025-06-10 12:55:27 +08:00
|
|
|
# Start data collection using the new service
|
|
|
|
|
print("Starting data collection service...")
|
|
|
|
|
success = await run_data_collection_service(config_path, duration_hours)
|
2025-05-31 22:30:56 +08:00
|
|
|
|
2025-06-10 12:55:27 +08:00
|
|
|
if success:
|
|
|
|
|
print("Data collection completed successfully")
|
|
|
|
|
return True
|
|
|
|
|
else:
|
|
|
|
|
print("Data collection failed")
|
2025-05-31 22:30:56 +08:00
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
2025-06-10 12:55:27 +08:00
|
|
|
print(f"Error: {e}")
|
2025-05-31 22:30:56 +08:00
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
"""Main entry point."""
|
2025-06-10 12:55:27 +08:00
|
|
|
import argparse
|
2025-05-31 22:30:56 +08:00
|
|
|
|
2025-06-10 12:55:27 +08:00
|
|
|
parser = argparse.ArgumentParser(description="Clean Production OKX Data Collector")
|
2025-05-31 22:30:56 +08:00
|
|
|
parser.add_argument(
|
2025-06-10 12:55:27 +08:00
|
|
|
"--hours",
|
2025-05-31 22:30:56 +08:00
|
|
|
type=float,
|
2025-06-10 12:55:27 +08:00
|
|
|
help="Collection duration in hours (default: indefinite until stopped manually)"
|
2025-05-31 22:30:56 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
2025-06-10 12:55:27 +08:00
|
|
|
# Validate arguments
|
2025-06-01 14:42:29 +08:00
|
|
|
if args.hours is not None and args.hours <= 0:
|
2025-06-10 12:55:27 +08:00
|
|
|
print("Duration must be positive")
|
2025-05-31 22:30:56 +08:00
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
success = asyncio.run(run_clean_production(args.hours))
|
|
|
|
|
sys.exit(0 if success else 1)
|
|
|
|
|
except KeyboardInterrupt:
|
2025-06-10 12:55:27 +08:00
|
|
|
print("\nInterrupted by user")
|
2025-05-31 22:30:56 +08:00
|
|
|
sys.exit(0)
|
|
|
|
|
except Exception as e:
|
2025-06-10 12:55:27 +08:00
|
|
|
print(f"Fatal error: {e}")
|
2025-05-31 22:30:56 +08:00
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
main()
|