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
|
import pandas as pd
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import importlib
|
import importlib
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
load_dotenv()
|
||||||
# --- REMOVED: import signal ---
|
# --- REMOVED: import signal ---
|
||||||
# --- REMOVED: from queue import Empty ---
|
# --- REMOVED: from queue import Empty ---
|
||||||
|
|
||||||
from logging_utils import setup_logging
|
from logging_utils import setup_logging
|
||||||
# --- Using the new high-performance WebSocket utility for live prices ---
|
|
||||||
from live_market_utils import start_live_feed
|
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
|
from strategies.base_strategy import BaseStrategy
|
||||||
# --- Rich dashboard renderer ---
|
|
||||||
from dashboard import DashboardRenderer
|
from dashboard import DashboardRenderer
|
||||||
from rich.live import Live
|
from rich.live import Live
|
||||||
|
from hyperliquid.info import Info
|
||||||
|
from hyperliquid.utils import constants
|
||||||
|
|
||||||
# --- Configuration ---
|
# --- Configuration ---
|
||||||
WATCHED_COINS = ["BTC", "ETH", "SOL", "BNB", "HYPE", "SUI", "xyz:BRENTOIL", "xyz:CL", "xyz:GOLD", "xyz:SILVER", "mkts:USTECH", "xyz:XYZ100"]
|
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_configs = strategy_configs
|
||||||
self.strategy_statuses = {}
|
self.strategy_statuses = {}
|
||||||
self.indicators_status = {}
|
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={
|
self.renderer = DashboardRenderer(table_visibility={
|
||||||
"market": True,
|
"market": True,
|
||||||
"strategies": False,
|
"strategies": False,
|
||||||
"indicators": True,
|
"indicators": True,
|
||||||
|
"balances": True,
|
||||||
})
|
})
|
||||||
|
|
||||||
def read_prices(self):
|
def read_prices(self):
|
||||||
@ -428,6 +436,35 @@ class MainApp:
|
|||||||
else:
|
else:
|
||||||
self.indicators_status = {}
|
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):
|
def check_process_status(self):
|
||||||
"""Checks if the background processes are still running."""
|
"""Checks if the background processes are still running."""
|
||||||
for name, process in self.background_processes.items():
|
for name, process in self.background_processes.items():
|
||||||
@ -445,7 +482,8 @@ class MainApp:
|
|||||||
COIN_DISPLAY_NAMES,
|
COIN_DISPLAY_NAMES,
|
||||||
self.strategy_statuses,
|
self.strategy_statuses,
|
||||||
self.strategy_configs,
|
self.strategy_configs,
|
||||||
self.indicators_status
|
self.indicators_status,
|
||||||
|
self.account_data
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
@ -455,6 +493,7 @@ class MainApp:
|
|||||||
self.read_prices()
|
self.read_prices()
|
||||||
self.read_strategy_statuses()
|
self.read_strategy_statuses()
|
||||||
self.read_indicators_status()
|
self.read_indicators_status()
|
||||||
|
self.read_account_data()
|
||||||
live.update(self.display_dashboard())
|
live.update(self.display_dashboard())
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
|||||||
@ -77,14 +77,13 @@ class PositionMonitor:
|
|||||||
output_lines.append("\n--- Perpetuals Account Summary ---")
|
output_lines.append("\n--- Perpetuals Account Summary ---")
|
||||||
output_lines.append(f" Account Value: ${account_value:,.2f} | Margin Used: ${margin_used:,.2f} | Utilization: {utilization:.2f}%")
|
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 ---")
|
output_lines.append("\n--- Spot Balances ---")
|
||||||
spot_balances = spot_state.get('balances', [])
|
spot_balances = spot_state.get('balances', [])
|
||||||
if not spot_balances:
|
if not spot_balances:
|
||||||
output_lines.append(" No spot balances found.")
|
output_lines.append(" No spot balances found.")
|
||||||
else:
|
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])
|
self.build_spot_balances_table(spot_balances, output_lines)
|
||||||
output_lines.append(f" {balances_str}")
|
|
||||||
|
|
||||||
# --- 3. Open Positions Table ---
|
# --- 3. Open Positions Table ---
|
||||||
output_lines.append("\n--- Open Perpetual Positions ---")
|
output_lines.append("\n--- Open Perpetual Positions ---")
|
||||||
@ -106,6 +105,23 @@ class PositionMonitor:
|
|||||||
self._lines_printed = len(output_lines)
|
self._lines_printed = len(output_lines)
|
||||||
sys.stdout.flush()
|
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):
|
def build_positions_table(self, positions: list, coin_to_strategy_map: dict, output_lines: list):
|
||||||
"""Builds the text for the positions summary table."""
|
"""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} |"
|
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