Add account data fetching and display balances in dashboard
This commit is contained in:
Binary file not shown.
Binary file not shown.
101
fetch_history.py
Normal file
101
fetch_history.py
Normal file
@ -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!")
|
||||
47
main_app.py
47
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)
|
||||
|
||||
|
||||
@ -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} |"
|
||||
|
||||
Reference in New Issue
Block a user