import json import logging import os import sys import time import subprocess import multiprocessing import schedule import sqlite3 import pandas as pd from datetime import datetime, timezone from logging_utils import setup_logging # --- Configuration --- WATCHED_COINS = ["BTC", "ETH", "SOL", "BNB", "HYPE", "ASTER", "ZEC", "PUMP", "SUI"] COIN_LISTER_SCRIPT = "list_coins.py" MARKET_FEEDER_SCRIPT = "market.py" DATA_FETCHER_SCRIPT = "data_fetcher.py" RESAMPLER_SCRIPT = "resampler.py" # Restored resampler script PRICE_DATA_FILE = os.path.join("_data", "current_prices.json") DB_PATH = os.path.join("_data", "market_data.db") STATUS_FILE = os.path.join("_data", "fetcher_status.json") def run_market_feeder(): """Target function to run the market.py script in a separate process.""" setup_logging('off', 'MarketFeedProcess') logging.info("Market feeder process started.") try: # Pass the log level to the script subprocess.run([sys.executable, MARKET_FEEDER_SCRIPT, "--log-level", "off"], check=True) except subprocess.CalledProcessError as e: logging.error(f"Market feeder script failed with error: {e}") except KeyboardInterrupt: logging.info("Market feeder process stopping.") def run_data_fetcher_job(): """Defines the job to be run by the scheduler for the data fetcher.""" logging.info(f"Scheduler starting data_fetcher.py task for {', '.join(WATCHED_COINS)}...") try: command = [sys.executable, DATA_FETCHER_SCRIPT, "--coins"] + WATCHED_COINS + ["--days", "7", "--log-level", "off"] subprocess.run(command, check=True) logging.info("data_fetcher.py task finished successfully.") except Exception as e: logging.error(f"Failed to run data_fetcher.py job: {e}") def data_fetcher_scheduler(): """Schedules and runs the data_fetcher.py script periodically.""" setup_logging('off', 'DataFetcherScheduler') run_data_fetcher_job() schedule.every(1).minutes.do(run_data_fetcher_job) logging.info("Data fetcher scheduled to run every 1 minute.") while True: schedule.run_pending() time.sleep(1) # --- Restored Resampler Functions --- def run_resampler_job(): """Defines the job to be run by the scheduler for the resampler.""" logging.info(f"Scheduler starting resampler.py task for {', '.join(WATCHED_COINS)}...") try: # Uses default timeframes configured within resampler.py command = [sys.executable, RESAMPLER_SCRIPT, "--coins"] + WATCHED_COINS + ["--log-level", "off"] subprocess.run(command, check=True) logging.info("resampler.py task finished successfully.") except Exception as e: logging.error(f"Failed to run resampler.py job: {e}") def resampler_scheduler(): """Schedules and runs the resampler.py script periodically.""" setup_logging('off', 'ResamplerScheduler') run_resampler_job() schedule.every(4).minutes.do(run_resampler_job) logging.info("Resampler scheduled to run every 4 minutes.") while True: schedule.run_pending() time.sleep(1) # --- End of Restored Functions --- class MainApp: def __init__(self, coins_to_watch: list): self.watched_coins = coins_to_watch self.prices = {} self.last_db_update_info = "Initializing..." self._lines_printed = 0 # To track how many lines we printed last time def read_prices(self): """Reads the latest prices from the JSON file.""" if not os.path.exists(PRICE_DATA_FILE): return try: with open(PRICE_DATA_FILE, 'r', encoding='utf-8') as f: self.prices = json.load(f) except (json.JSONDecodeError, IOError): logging.debug("Could not read price file (might be locked).") def get_overall_db_status(self): """Reads the fetcher status from the status file.""" if not os.path.exists(STATUS_FILE): self.last_db_update_info = "Status file not found." return try: with open(STATUS_FILE, 'r', encoding='utf-8') as f: status = json.load(f) coin = status.get("last_updated_coin") timestamp_utc_str = status.get("last_run_timestamp_utc") num_candles = status.get("num_updated_candles", 0) if timestamp_utc_str: dt_naive = datetime.strptime(timestamp_utc_str, '%Y-%m-%d %H:%M:%S') dt_utc = dt_naive.replace(tzinfo=timezone.utc) dt_local = dt_utc.astimezone(None) timestamp_display = dt_local.strftime('%Y-%m-%d %H:%M:%S %Z') else: timestamp_display = "N/A" self.last_db_update_info = f"{coin} at {timestamp_display} ({num_candles} candles)" except (IOError, json.JSONDecodeError) as e: self.last_db_update_info = "Error reading status file." logging.error(f"Could not read status file: {e}") def display_dashboard(self): """Displays a formatted table for prices and DB status without blinking.""" # Move the cursor up to overwrite the previous output if self._lines_printed > 0: print(f"\x1b[{self._lines_printed}A", end="") # Build the output as a single string output_lines = [] output_lines.append("--- Market Dashboard ---") table_width = 26 output_lines.append("-" * table_width) output_lines.append(f"{'#':<2} | {'Coin':<6} | {'Live Price':>10} |") output_lines.append("-" * table_width) for i, coin in enumerate(self.watched_coins, 1): price = self.prices.get(coin, "Loading...") output_lines.append(f"{i:<2} | {coin:<6} | {price:>10} |") output_lines.append("-" * table_width) output_lines.append(f"DB Status: Last coin updated -> {self.last_db_update_info}") # Join lines and add a code to clear from cursor to end of screen # This prevents artifacts if the new output is shorter than the old one. final_output = "\n".join(output_lines) + "\n\x1b[J" print(final_output, end="") # Store the number of lines printed for the next iteration self._lines_printed = len(output_lines) sys.stdout.flush() def run(self): """Main loop to read and display data.""" while True: self.read_prices() self.get_overall_db_status() self.display_dashboard() time.sleep(2) if __name__ == "__main__": setup_logging('normal', 'MainApp') logging.info(f"Running coin lister: '{COIN_LISTER_SCRIPT}'...") try: subprocess.run([sys.executable, COIN_LISTER_SCRIPT], check=True, capture_output=True, text=True) except subprocess.CalledProcessError as e: logging.error(f"Failed to run '{COIN_LISTER_SCRIPT}'. Error: {e.stderr}") sys.exit(1) logging.info(f"Starting market feeder ('{MARKET_FEEDER_SCRIPT}')...") market_process = multiprocessing.Process(target=run_market_feeder, daemon=True) market_process.start() logging.info(f"Starting historical data fetcher ('{DATA_FETCHER_SCRIPT}')...") fetcher_process = multiprocessing.Process(target=data_fetcher_scheduler, daemon=True) fetcher_process.start() # --- Restored Resampler Process Start --- logging.info(f"Starting resampler ('{RESAMPLER_SCRIPT}')...") resampler_process = multiprocessing.Process(target=resampler_scheduler, daemon=True) resampler_process.start() # --- End Resampler Process Start --- time.sleep(3) app = MainApp(coins_to_watch=WATCHED_COINS) try: app.run() except KeyboardInterrupt: logging.info("Shutting down...") market_process.terminate() fetcher_process.terminate() # --- Restored Resampler Shutdown --- resampler_process.terminate() market_process.join() fetcher_process.join() resampler_process.join() # --- End Resampler Shutdown --- logging.info("Shutdown complete.") sys.exit(0)