diff --git a/__pycache__/logging_utils.cpython-313.pyc b/__pycache__/logging_utils.cpython-313.pyc deleted file mode 100644 index 6a8df43..0000000 Binary files a/__pycache__/logging_utils.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/trade_log.cpython-313.pyc b/__pycache__/trade_log.cpython-313.pyc deleted file mode 100644 index 0827178..0000000 Binary files a/__pycache__/trade_log.cpython-313.pyc and /dev/null differ diff --git a/fetch_history.py b/fetch_history.py new file mode 100644 index 0000000..7c14441 --- /dev/null +++ b/fetch_history.py @@ -0,0 +1,101 @@ +import requests +import json +import sqlite3 +import time +from datetime import datetime, timezone + +DB_PATH = "_data/market_data.db" +URL = "https://api.hyperliquid.xyz/info" + +def fetch_historical_candles(coin, start_ms, end_ms, interval="1m"): + """Fetch historical candles using the raw HTTP API.""" + candles = [] + current_start = start_ms + while current_start < end_ms: + payload = { + "type": "candleSnapshot", + "req": { + "coin": coin, + "interval": interval, + "startTime": current_start, + "endTime": end_ms + } + } + resp = requests.post(URL, json=payload) + batch = resp.json() + if not batch: + break + for candle in batch: + candle['coin'] = coin + candles.append(candle) + last_ts = batch[-1]['t'] + if last_ts < current_start: + break + current_start = last_ts + 1 + time.sleep(0.5) + return candles + +def write_candles_to_db(coin, candles, interval="1m"): + """Write candles to the database.""" + table_name = coin + "_" + interval + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + # Ensure table exists + cursor.execute(f''' + CREATE TABLE IF NOT EXISTS "{table_name}" ( + datetime_utc TEXT, + timestamp_ms INTEGER PRIMARY KEY, + open REAL, + high REAL, + low REAL, + close REAL, + volume REAL, + number_of_trades INTEGER + ) + ''') + for candle in candles: + record = ( + datetime.fromtimestamp(candle['t'] / 1000, tz=timezone.utc).strftime('%Y-%m-%d %H:%M:%S'), + candle['t'], + candle.get('o'), candle.get('h'), candle.get('l'), candle.get('c'), + candle.get('v'), candle.get('n') + ) + cursor.execute(f''' + INSERT OR REPLACE INTO "{table_name}" (datetime_utc, timestamp_ms, open, high, low, close, volume, number_of_trades) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ''', record) + conn.commit() + conn.close() + +def get_last_timestamp(coin): + """Get the most recent timestamp from the database.""" + table_name = coin + "_1m" + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + try: + cursor.execute(f'SELECT MAX(timestamp_ms) FROM "{table_name}"') + result = cursor.fetchone() + return int(result[0]) if result and result[0] is not None else None + except: + return None + finally: + conn.close() + +coins = ["mkts:USTECH", "xyz:XYZ100"] +now_ms = int(time.time() * 1000) +seven_days_ms = 7 * 24 * 60 * 60 * 1000 + +for coin in coins: + for tf in ["1m", "1d"]: + start_ts = now_ms - seven_days_ms + if start_ts >= now_ms: + print(f"{coin} ({tf}): Already up to date") + continue + + print(f"{coin} ({tf}): Fetching historical candles from {datetime.fromtimestamp(start_ts/1000, tz=timezone.utc)} to {datetime.fromtimestamp(now_ms/1000, tz=timezone.utc)}...") + candles = fetch_historical_candles(coin, start_ts, now_ms, interval=tf) + print(f"{coin} ({tf}): Fetched {len(candles)} candles") + write_candles_to_db(coin, candles, interval=tf) + print(f"{coin} ({tf}): Written to database") + +print("Done!") diff --git a/main_app.py b/main_app.py index 69e5243..aeb3ee8 100644 --- a/main_app.py +++ b/main_app.py @@ -10,17 +10,18 @@ import sqlite3 import pandas as pd from datetime import datetime import importlib +from dotenv import load_dotenv +load_dotenv() # --- REMOVED: import signal --- # --- REMOVED: from queue import Empty --- from logging_utils import setup_logging -# --- Using the new high-performance WebSocket utility for live prices --- from live_market_utils import start_live_feed -# --- Import the base class for type hinting (optional but good practice) --- from strategies.base_strategy import BaseStrategy -# --- Rich dashboard renderer --- from dashboard import DashboardRenderer from rich.live import Live +from hyperliquid.info import Info +from hyperliquid.utils import constants # --- Configuration --- WATCHED_COINS = ["BTC", "ETH", "SOL", "BNB", "HYPE", "SUI", "xyz:BRENTOIL", "xyz:CL", "xyz:GOLD", "xyz:SILVER", "mkts:USTECH", "xyz:XYZ100"] @@ -384,10 +385,17 @@ class MainApp: self.strategy_configs = strategy_configs self.strategy_statuses = {} self.indicators_status = {} + self.account_data = None + self.wallet_address = os.environ.get("MAIN_WALLET_ADDRESS") + if self.wallet_address: + self.info_client = Info(constants.MAINNET_API_URL, skip_ws=True) + else: + self.info_client = None self.renderer = DashboardRenderer(table_visibility={ "market": True, "strategies": False, "indicators": True, + "balances": True, }) def read_prices(self): @@ -428,6 +436,35 @@ class MainApp: else: self.indicators_status = {} + def read_account_data(self): + """Fetches account balances and positions from Hyperliquid API.""" + if not self.wallet_address or not self.info_client: + self.account_data = None + return + try: + perp_state = self.info_client.user_state(self.wallet_address) + spot_state = self.info_client.spot_user_state(self.wallet_address) + + margin_summary = perp_state.get('marginSummary', {}) + account_value = float(margin_summary.get('accountValue', 0)) + margin_used = float(margin_summary.get('totalMarginUsed', 0)) + utilization = (margin_used / account_value) * 100 if account_value > 0 else 0 + + spot_balances = spot_state.get('balances', []) + positions = perp_state.get('assetPositions', []) + + self.account_data = { + 'account_value': account_value, + 'margin_used': margin_used, + 'utilization': utilization, + 'spot_balances': spot_balances, + 'positions': positions, + } + + except Exception as e: + logging.error(f"Could not fetch account data: {e}") + self.account_data = None + def check_process_status(self): """Checks if the background processes are still running.""" for name, process in self.background_processes.items(): @@ -445,7 +482,8 @@ class MainApp: COIN_DISPLAY_NAMES, self.strategy_statuses, self.strategy_configs, - self.indicators_status + self.indicators_status, + self.account_data ) def run(self): @@ -455,6 +493,7 @@ class MainApp: self.read_prices() self.read_strategy_statuses() self.read_indicators_status() + self.read_account_data() live.update(self.display_dashboard()) time.sleep(0.5) diff --git a/position_monitor.py b/position_monitor.py index e20ec5d..b017dce 100644 --- a/position_monitor.py +++ b/position_monitor.py @@ -77,14 +77,13 @@ class PositionMonitor: output_lines.append("\n--- Perpetuals Account Summary ---") output_lines.append(f" Account Value: ${account_value:,.2f} | Margin Used: ${margin_used:,.2f} | Utilization: {utilization:.2f}%") - # --- 2. Spot Balances Summary --- + # --- 2. Spot Balances Table --- output_lines.append("\n--- Spot Balances ---") spot_balances = spot_state.get('balances', []) if not spot_balances: output_lines.append(" No spot balances found.") else: - balances_str = ", ".join([f"{b.get('coin')}: {float(b.get('total', 0)):,.4f}" for b in spot_balances if float(b.get('total', 0)) > 0]) - output_lines.append(f" {balances_str}") + self.build_spot_balances_table(spot_balances, output_lines) # --- 3. Open Positions Table --- output_lines.append("\n--- Open Perpetual Positions ---") @@ -106,6 +105,23 @@ class PositionMonitor: self._lines_printed = len(output_lines) sys.stdout.flush() + def build_spot_balances_table(self, spot_balances: list, output_lines: list): + """Builds the text for the spot balances table.""" + header = f"| {'Coin':<10} | {'Total':>18} |" + output_lines.append(header) + output_lines.append("-" * len(header)) + + for balance in spot_balances: + coin = balance.get('coin', 'Unknown') + total = float(balance.get('total', 0)) + + coin_str = f"{coin:<10}" + total_str = f"{total:>18,.4f}" + + output_lines.append(f"| {coin_str} | {total_str} |") + + output_lines.append("-" * len(header)) + def build_positions_table(self, positions: list, coin_to_strategy_map: dict, output_lines: list): """Builds the text for the positions summary table.""" header = f"| {'Strategy':<25} | {'Coin':<6} | {'Side':<5} | {'Size':>15} | {'Entry Price':>12} | {'Mark Price':>12} | {'PNL':>15} | {'Leverage':>10} |"