From ac8ac31d01cdd1b0e69504c0201d8f5b835e5957 Mon Sep 17 00:00:00 2001 From: DiTus Date: Tue, 14 Oct 2025 10:48:26 +0200 Subject: [PATCH] first stategies, import script for BTC histry --- __pycache__/logging_utils.cpython-313.pyc | Bin 1838 -> 3013 bytes _data/candles/hyperliquid-historical.py | 231 ++++++++++++++++++++++ _data/candles/requirements.txt | 8 + _data/market_data.db-shm | Bin 0 -> 196608 bytes _data/strategies.json | 48 +++++ import_csv.py | 147 ++++++++++++++ logging_utils.py | 31 ++- main_app.py | 217 +++++++++++--------- strategy_template.py | 132 +++++++++++++ 9 files changed, 717 insertions(+), 97 deletions(-) create mode 100644 _data/candles/hyperliquid-historical.py create mode 100644 _data/candles/requirements.txt create mode 100644 _data/market_data.db-shm create mode 100644 _data/strategies.json create mode 100644 import_csv.py create mode 100644 strategy_template.py diff --git a/__pycache__/logging_utils.cpython-313.pyc b/__pycache__/logging_utils.cpython-313.pyc index 9515649e7d27fc2f205842a9042157adbac2f5c6..6a8df43fe1c42c95f44b1c05937e45ef178f2d8a 100644 GIT binary patch delta 1465 zcmZ8hO>7%Q6rS0CZ@NyCCZw^Gc$44|=NHKd5>bhowyDx47|9zWK_e|&?>bqqcf;%& zM1yh02|pwG&3p6aeedmi z-}vkB>NCI;N)@j;R{zbY}bN?8Zhyb#pn- z@)zV(w)c`G=iBF!9r)-Oze3`}#R8xbZg(xz0xj5zJE88#g{yLzT* zaG9Xc<#6Oi&uKu!;Wfq)GW_iY0B$LrD*$Bpbnoev4UL`e0pKiN%!nGd@;v|s>7&NK z1_^+q2^lF2GGZ8LVv-|(9MPnI0mAfoT5CxQ^@5BL21)LDkdRXceepNu1D z7A4TaKW-ZBJtX1|_)Qr+RwjQ^CTrIpr5>c3O7d7)JyKSi%H=ZC62Rcl{msM8hMH^+ zER{p#pnu^0$l*w3tSp=i4wdDTp1z}=(VwGZ)zW*%vE)%K*^DiJ#~sBkHKVVTw@xBc zUye5;vxmugfBD);RILrvgxZHSr@q!0eyM!3C4mbgpGK=|pJ!`RkLDiCHOCg~Y-4n( z{$_ov(Yw}=*4m^_@D)hF6nQa{t~&;4mX2jzFSuqgPxW)U{%)~kQZmI(b;gzFqV!kND`TMdMVAQ9<8HAgJO|3V#CrU>IKd zOX%aTz|KwT!=b#ptLLr5w_bqNZ_3XxQJN)KguXDKE_+Jz%8$%DQ2O>|b`e(Ya(DS4 zLuHhY3JDT{W-6=vdMqdaD}++TeVu^s+RiRoC;k!g^I+u%{_)V{iF~m-QVlnzt~I3f zHp}!0cW$(V$``_PDX1+WqD2aMiHd;Oq2N!3QX$BSSwgM=_J5y_rAwX8uA&VROdq(n yOhk)?5IzCnCqVuMj6UNA;T2dNIv$=m8lHIusMT7SfRojJtp&(lXaA&IP|V-Q34H+o delta 383 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 L12F^tfr0-6bYNsj literal 0 HcmV?d00001 diff --git a/_data/strategies.json b/_data/strategies.json new file mode 100644 index 0000000..d541d78 --- /dev/null +++ b/_data/strategies.json @@ -0,0 +1,48 @@ +{ + "rsi_strategy_eth": { + "enabled": true, + "script": "strategy_template.py", + "parameters": { + "coin": "ETH", + "timeframe": "5m", + "rsi_period": 14 + } + }, + "ma_cross_btc": { + "enabled": true, + "script": "strategy_template.py", + "parameters": { + "coin": "BTC", + "timeframe": "1h", + "short_ma": 10, + "long_ma": 50 + } + }, + "sma_125d_btc": { + "enabled": true, + "script": "strategy_template.py", + "parameters": { + "coin": "BTC", + "timeframe": "1D", + "sma_period": 125 + } + }, + "sma_44d_btc": { + "enabled": true, + "script": "strategy_template.py", + "parameters": { + "coin": "BTC", + "timeframe": "1D", + "sma_period": 44 + } + }, + "disabled_strategy": { + "enabled": false, + "script": "strategy_template.py", + "parameters": { + "coin": "SOL", + "timeframe": "15m" + } + } +} + diff --git a/import_csv.py b/import_csv.py new file mode 100644 index 0000000..3757ef2 --- /dev/null +++ b/import_csv.py @@ -0,0 +1,147 @@ +import argparse +import logging +import os +import sys +import sqlite3 +import pandas as pd +from datetime import datetime + +# Assuming logging_utils.py is in the same directory +from logging_utils import setup_logging + +class CsvImporter: + """ + Imports historical candle data from a large CSV file into the SQLite database, + intelligently adding only the missing data. + """ + + def __init__(self, log_level: str, csv_path: str, coin: str): + setup_logging(log_level, 'CsvImporter') + if not os.path.exists(csv_path): + logging.error(f"CSV file not found at '{csv_path}'. Please check the path.") + sys.exit(1) + + self.csv_path = csv_path + self.coin = coin + self.table_name = f"{self.coin}_1m" + self.db_path = os.path.join("_data", "market_data.db") + self.column_mapping = { + 'Open time': 'datetime_utc', + 'Open': 'open', + 'High': 'high', + 'Low': 'low', + 'Close': 'close', + 'Volume': 'volume', + 'Number of trades': 'number_of_trades' + } + + def run(self): + """Orchestrates the entire import and verification process.""" + logging.info(f"Starting import process for '{self.coin}' from '{self.csv_path}'...") + + with sqlite3.connect(self.db_path) as conn: + conn.execute("PRAGMA journal_mode=WAL;") + + # 1. Get the current state of the database + db_oldest, db_newest, initial_row_count = self._get_db_state(conn) + + # 2. Read, clean, and filter the CSV data + new_data_df = self._process_and_filter_csv(db_oldest, db_newest) + + if new_data_df.empty: + logging.info("No new data to import. Database is already up-to-date with the CSV file.") + return + + # 3. Append the new data to the database + self._append_to_db(new_data_df, conn) + + # 4. Summarize and verify the import + self._summarize_import(initial_row_count, len(new_data_df), conn) + + def _get_db_state(self, conn) -> (datetime, datetime, int): + """Gets the oldest and newest timestamps and total row count from the DB table.""" + try: + oldest = pd.read_sql(f'SELECT MIN(datetime_utc) FROM "{self.table_name}"', conn).iloc[0, 0] + newest = pd.read_sql(f'SELECT MAX(datetime_utc) FROM "{self.table_name}"', conn).iloc[0, 0] + count = pd.read_sql(f'SELECT COUNT(*) FROM "{self.table_name}"', conn).iloc[0, 0] + + oldest_dt = pd.to_datetime(oldest) if oldest else None + newest_dt = pd.to_datetime(newest) if newest else None + + if oldest_dt: + logging.info(f"Database contains data from {oldest_dt} to {newest_dt}.") + else: + logging.info("Database table is empty. A full import will be performed.") + + return oldest_dt, newest_dt, count + except pd.io.sql.DatabaseError: + logging.info(f"Table '{self.table_name}' not found. It will be created.") + return None, None, 0 + + def _process_and_filter_csv(self, db_oldest: datetime, db_newest: datetime) -> pd.DataFrame: + """Reads the CSV and returns a DataFrame of only the missing data.""" + logging.info("Reading and processing CSV file. This may take a moment for large files...") + df = pd.read_csv(self.csv_path, usecols=self.column_mapping.keys()) + + # Clean and format the data + df.rename(columns=self.column_mapping, inplace=True) + df['datetime_utc'] = pd.to_datetime(df['datetime_utc']) + + # Filter the data to find only rows that are outside the range of what's already in the DB + if db_oldest and db_newest: + # Get data from before the oldest record and after the newest record + df_filtered = df[(df['datetime_utc'] < db_oldest) | (df['datetime_utc'] > db_newest)] + else: + # If the DB is empty, all data is new + df_filtered = df + + logging.info(f"Found {len(df_filtered):,} new rows to import.") + return df_filtered + + def _append_to_db(self, df: pd.DataFrame, conn): + """Appends the DataFrame to the SQLite table.""" + logging.info(f"Appending {len(df):,} new rows to the database...") + df.to_sql(self.table_name, conn, if_exists='append', index=False) + logging.info("Append operation complete.") + + def _summarize_import(self, initial_count: int, added_count: int, conn): + """Prints a final summary and verification of the import.""" + logging.info("--- Import Summary & Verification ---") + + try: + final_count = pd.read_sql(f'SELECT COUNT(*) FROM "{self.table_name}"', conn).iloc[0, 0] + new_oldest = pd.read_sql(f'SELECT MIN(datetime_utc) FROM "{self.table_name}"', conn).iloc[0, 0] + new_newest = pd.read_sql(f'SELECT MAX(datetime_utc) FROM "{self.table_name}"', conn).iloc[0, 0] + + print(f"\n{'Status':<20}: SUCCESS") + print("-" * 40) + print(f"{'Initial Row Count':<20}: {initial_count:,}") + print(f"{'Rows Added':<20}: {added_count:,}") + print(f"{'Final Row Count':<20}: {final_count:,}") + print("-" * 40) + print(f"{'New Oldest Record':<20}: {new_oldest}") + print(f"{'New Newest Record':<20}: {new_newest}") + + # Verification check + if final_count == initial_count + added_count: + logging.info("Verification successful: Final row count matches expected count.") + else: + logging.warning("Verification warning: Final row count does not match expected count.") + except Exception as e: + logging.error(f"Could not generate summary. Error: {e}") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Import historical CSV data into the SQLite database.") + parser.add_argument("--file", required=True, help="Path to the large CSV file to import.") + parser.add_argument("--coin", default="BTC", help="The coin symbol for this data (e.g., BTC).") + parser.add_argument( + "--log-level", + default="normal", + choices=['off', 'normal', 'debug'], + help="Set the logging level for the script." + ) + args = parser.parse_args() + + importer = CsvImporter(log_level=args.log_level, csv_path=args.file, coin=args.coin) + importer.run() diff --git a/logging_utils.py b/logging_utils.py index 9ad66af..490363e 100644 --- a/logging_utils.py +++ b/logging_utils.py @@ -1,5 +1,29 @@ import logging import sys +from datetime import datetime + +class LocalTimeFormatter(logging.Formatter): + """ + Custom formatter to display time with milliseconds and a (UTC+HH) offset. + """ + def formatTime(self, record, datefmt=None): + # Convert log record's creation time to a local, timezone-aware datetime object + dt = datetime.fromtimestamp(record.created).astimezone() + + # Format the main time part + time_part = dt.strftime('%Y-%m-%d %H:%M:%S') + + # Get the UTC offset and format it as (UTC+HH) + offset = dt.utcoffset() + offset_str = "" + if offset is not None: + offset_hours = int(offset.total_seconds() / 3600) + sign = '+' if offset_hours >= 0 else '' + offset_str = f" (UTC{sign}{offset_hours})" + + # --- FIX: Cast record.msecs from float to int before formatting --- + # Combine time, milliseconds, and the offset string + return f"{time_part},{int(record.msecs):03d}{offset_str}" def setup_logging(log_level: str, process_name: str): """ @@ -29,10 +53,9 @@ def setup_logging(log_level: str, process_name: str): handler = logging.StreamHandler(sys.stdout) - # --- FIX: Added a date format that includes the timezone name (%Z) --- - formatter = logging.Formatter( - f'%(asctime)s - {process_name} - %(levelname)s - %(message)s', - datefmt='%Y-%m-%d %H:%M:%S %Z' + # This will produce timestamps like: 2025-10-13 14:30:00,123 (UTC+2) + formatter = LocalTimeFormatter( + f'%(asctime)s - {process_name} - %(levelname)s - %(message)s' ) handler.setFormatter(formatter) logger.addHandler(handler) diff --git a/main_app.py b/main_app.py index b3f446a..6cd9306 100644 --- a/main_app.py +++ b/main_app.py @@ -17,121 +17,141 @@ WATCHED_COINS = ["BTC", "ETH", "SOL", "BNB", "HYPE", "ASTER", "ZEC", "PUMP", "SU COIN_LISTER_SCRIPT = "list_coins.py" MARKET_FEEDER_SCRIPT = "market.py" DATA_FETCHER_SCRIPT = "data_fetcher.py" -RESAMPLER_SCRIPT = "resampler.py" # Restored resampler script +RESAMPLER_SCRIPT = "resampler.py" +STRATEGY_CONFIG_FILE = os.path.join("_data", "strategies.json") 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") +LOGS_DIR = "_logs" # Directory to store logs from background processes 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.") + """Target function to run market.py and redirect its output to a log file.""" + log_file = os.path.join(LOGS_DIR, "market_feeder.log") + while True: + try: + with open(log_file, 'a') as f: + subprocess.run( + [sys.executable, MARKET_FEEDER_SCRIPT, "--log-level", "normal"], + check=True, stdout=f, stderr=subprocess.STDOUT + ) + except (subprocess.CalledProcessError, Exception) as e: + logging.error(f"Market feeder script failed: {e}. Restarting...") + time.sleep(5) 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)}...") + """Defines the job for the data fetcher, redirecting output to a log file.""" + log_file = os.path.join(LOGS_DIR, "data_fetcher.log") + logging.info(f"Scheduler starting data_fetcher.py task...") 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.") + command = [sys.executable, DATA_FETCHER_SCRIPT, "--coins"] + WATCHED_COINS + ["--days", "7", "--log-level", "normal"] + with open(log_file, 'a') as f: + subprocess.run(command, check=True, stdout=f, stderr=subprocess.STDOUT) 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.""" + """Schedules the data_fetcher.py script.""" 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)}...") + """Defines the job for the resampler, redirecting output to a log file.""" + log_file = os.path.join(LOGS_DIR, "resampler.log") + logging.info(f"Scheduler starting resampler.py task...") 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.") + command = [sys.executable, RESAMPLER_SCRIPT, "--coins"] + WATCHED_COINS + ["--log-level", "normal"] + with open(log_file, 'a') as f: + subprocess.run(command, check=True, stdout=f, stderr=subprocess.STDOUT) 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.""" + """Schedules the resampler.py script.""" 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 --- + + +def run_strategy(strategy_name: str, config: dict): + """Target function to run a strategy, redirecting its output to a log file.""" + log_file = os.path.join(LOGS_DIR, f"strategy_{strategy_name}.log") + script_name = config['script'] + params_str = json.dumps(config['parameters']) + command = [sys.executable, script_name, "--name", strategy_name, "--params", params_str, "--log-level", "normal"] + while True: + try: + with open(log_file, 'a') as f: + f.write(f"\n--- Starting strategy '{strategy_name}' at {datetime.now()} ---\n") + subprocess.run(command, check=True, stdout=f, stderr=subprocess.STDOUT) + except (subprocess.CalledProcessError, Exception) as e: + logging.error(f"Strategy '{strategy_name}' failed: {e}. Restarting...") + time.sleep(10) + class MainApp: - def __init__(self, coins_to_watch: list): + def __init__(self, coins_to_watch: list, processes: dict): 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 + self._lines_printed = 0 + self.background_processes = processes + self.process_status = {} 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).") + if os.path.exists(PRICE_DATA_FILE): + 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.") 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 os.path.exists(STATUS_FILE): + 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_utc = datetime.fromisoformat(timestamp_utc_str.replace('Z', '+00:00')).replace(tzinfo=timezone.utc) + dt_local = dt_utc.astimezone(None) + + # --- FIX: Manually format the UTC offset --- + offset = dt_local.utcoffset() + offset_hours = int(offset.total_seconds() / 3600) + sign = '+' if offset_hours >= 0 else '' + offset_str = f"(UTC{sign}{offset_hours})" + timestamp_display = f"{dt_local.strftime('%Y-%m-%d %H:%M:%S')} {offset_str}" + else: + timestamp_display = "N/A" + self.last_db_update_info = f"{coin} at {timestamp_display} ({num_candles} candles)" + except (IOError, json.JSONDecodeError): + self.last_db_update_info = "Error reading status file." - 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 check_process_status(self): + """Checks if the background processes are still running.""" + for name, process in self.background_processes.items(): + self.process_status[name] = "Running" if process.is_alive() else "STOPPED" 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 ---") + """Displays a formatted table without blinking.""" + if self._lines_printed > 0: print(f"\x1b[{self._lines_printed}A", end="") + output_lines = ["--- Market Dashboard ---"] table_width = 26 output_lines.append("-" * table_width) output_lines.append(f"{'#':<2} | {'Coin':<6} | {'Live Price':>10} |") @@ -140,23 +160,25 @@ class MainApp: 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. + output_lines.append("DB Status:") + output_lines.append(f" Last update -> {self.last_db_update_info}") + + output_lines.append("--- Background Processes ---") + for name, status in self.process_status.items(): + output_lines.append(f"{name:<25}: {status}") + 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.""" + """Main loop to read data, display dashboard, and check processes.""" while True: self.read_prices() self.get_overall_db_status() + self.check_process_status() self.display_dashboard() time.sleep(2) @@ -164,6 +186,10 @@ class MainApp: if __name__ == "__main__": setup_logging('normal', 'MainApp') + # Create logs directory if it doesn't exist + if not os.path.exists(LOGS_DIR): + os.makedirs(LOGS_DIR) + logging.info(f"Running coin lister: '{COIN_LISTER_SCRIPT}'...") try: subprocess.run([sys.executable, COIN_LISTER_SCRIPT], check=True, capture_output=True, text=True) @@ -171,35 +197,40 @@ if __name__ == "__main__": 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() + processes = {} - logging.info(f"Starting historical data fetcher ('{DATA_FETCHER_SCRIPT}')...") - fetcher_process = multiprocessing.Process(target=data_fetcher_scheduler, daemon=True) - fetcher_process.start() + # Start Data Pipeline Processes + processes["Market Feeder"] = multiprocessing.Process(target=run_market_feeder, daemon=True) + processes["Data Fetcher"] = multiprocessing.Process(target=data_fetcher_scheduler, daemon=True) + processes["Resampler"] = multiprocessing.Process(target=resampler_scheduler, daemon=True) - # --- 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 --- + # Start Strategy Processes based on config + try: + with open(STRATEGY_CONFIG_FILE, 'r') as f: + strategy_configs = json.load(f) + for name, config in strategy_configs.items(): + if config.get("enabled", False): + proc = multiprocessing.Process(target=run_strategy, args=(name, config), daemon=True) + processes[f"Strategy: {name}"] = proc + except (FileNotFoundError, json.JSONDecodeError) as e: + logging.error(f"Could not load strategies from '{STRATEGY_CONFIG_FILE}': {e}") + + # Launch all processes + for name, proc in processes.items(): + logging.info(f"Starting process '{name}'...") + proc.start() time.sleep(3) - app = MainApp(coins_to_watch=WATCHED_COINS) + app = MainApp(coins_to_watch=WATCHED_COINS, processes=processes) 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 --- + for proc in processes.values(): + if proc.is_alive(): proc.terminate() + for proc in processes.values(): + if proc.is_alive(): proc.join() logging.info("Shutdown complete.") sys.exit(0) diff --git a/strategy_template.py b/strategy_template.py new file mode 100644 index 0000000..911fb7c --- /dev/null +++ b/strategy_template.py @@ -0,0 +1,132 @@ +import argparse +import logging +import sys +import time +import pandas as pd +import sqlite3 +import json +import os + +from logging_utils import setup_logging + +class TradingStrategy: + """ + A template for a trading strategy that reads data from the SQLite database + and executes its logic in a loop. + """ + + def __init__(self, strategy_name: str, params: dict, log_level: str): + self.strategy_name = strategy_name + self.params = params + self.coin = params.get("coin", "N/A") + self.timeframe = params.get("timeframe", "N/A") + self.db_path = os.path.join("_data", "market_data.db") + + # Load strategy-specific parameters + self.rsi_period = params.get("rsi_period") + self.short_ma = params.get("short_ma") + self.long_ma = params.get("long_ma") + self.sma_period = params.get("sma_period") + + setup_logging(log_level, f"Strategy-{self.strategy_name}") + logging.info(f"Initializing strategy with parameters: {self.params}") + + def load_data(self) -> pd.DataFrame: + """Loads historical data for the configured coin and timeframe from the database.""" + table_name = f"{self.coin}_{self.timeframe}" + # Ensure we load enough data for the longest indicator period + limit = 500 + if self.sma_period and self.sma_period > limit: + limit = self.sma_period + 50 # Add a buffer + elif self.long_ma and self.long_ma > limit: + limit = self.long_ma + 50 + + try: + with sqlite3.connect(f"file:{self.db_path}?mode=ro", uri=True) as conn: + query = f'SELECT * FROM "{table_name}" ORDER BY datetime_utc DESC LIMIT {limit}' + df = pd.read_sql(query, conn) + df['datetime_utc'] = pd.to_datetime(df['datetime_utc']) + df.set_index('datetime_utc', inplace=True) + df.sort_index(inplace=True) # Ensure data is chronological + return df + except Exception as e: + logging.error(f"Failed to load data from table '{table_name}': {e}") + return pd.DataFrame() + + def run_logic(self): + """ + The main loop where the strategy's logic is executed. + This should be implemented with your specific trading rules. + """ + logging.info(f"Starting main logic loop for {self.coin} on {self.timeframe} timeframe.") + while True: + data = self.load_data() + + if data.empty: + logging.warning("No data loaded. Waiting before retrying...") + time.sleep(60) + continue + + last_close = data['close'].iloc[-1] + logging.info(f"Latest data loaded. Last close price for {self.coin}: {last_close}") + + # --- SMA Strategy Logic --- + if self.sma_period: + if len(data) < self.sma_period: + logging.warning(f"Not enough data to calculate {self.sma_period}-period SMA. " + f"Need {self.sma_period}, have {len(data)}.") + else: + # Calculate the Simple Moving Average + sma = data['close'].rolling(window=self.sma_period).mean().iloc[-1] + logging.info(f"Current Price: {last_close}, {self.sma_period}-period SMA: {sma:.4f}") + + if last_close > sma: + logging.warning("--- BUY SIGNAL --- (Price is above SMA)") + elif last_close < sma: + logging.warning("--- SELL SIGNAL --- (Price is below SMA)") + else: + logging.info("--- HOLD SIGNAL --- (Price is at SMA)") + + # --- RSI Strategy Logic (Placeholder) --- + if self.rsi_period: + logging.info(f"RSI Period is set to: {self.rsi_period}. (RSI calculation not implemented).") + + # --- MA Cross Strategy Logic (Placeholder) --- + if self.short_ma and self.long_ma: + logging.info(f"Short MA: {self.short_ma}, Long MA: {self.long_ma}. (MA Cross logic not implemented).") + + logging.info("Logic execution finished. Waiting for next cycle.") + time.sleep(60) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Run a trading strategy.") + parser.add_argument("--name", required=True, help="The name of the strategy instance from the config.") + parser.add_argument("--params", required=True, help="A JSON string of the strategy's parameters.") + parser.add_argument( + "--log-level", + default="normal", + choices=['off', 'normal', 'debug'], + help="Set the logging level for the script." + ) + + args = parser.parse_args() + + try: + strategy_params = json.loads(args.params) + strategy = TradingStrategy( + strategy_name=args.name, + params=strategy_params, + log_level=args.log_level + ) + strategy.run_logic() + except json.JSONDecodeError: + logging.error("Failed to decode JSON from --params argument.") + sys.exit(1) + except KeyboardInterrupt: + logging.info("Strategy process stopped.") + sys.exit(0) + except Exception as e: + logging.error(f"A critical error occurred: {e}") + sys.exit(1) +