import json import time import requests import math import os from datetime import datetime from statistics import mean, stdev # --- Configuration --- COINS = ["ETH"] # Mapping of label to number of 1-minute periods PERIODS_CONFIG = { "37m": 37, "3h": 3 * 60, # 180 minutes "12h": 12 * 60, # 720 minutes "24h": 24 * 60 # 1440 minutes } MA_PERIODS = [33, 44, 88, 144] STD_DEV_MULTIPLIER = 1.6 # Standard deviation multiplier for bands OUTPUT_FILE = os.path.join("market_data", "indicators.json") API_URL = "https://api.hyperliquid.xyz/info" UPDATE_INTERVAL = 60 # seconds def fetch_candles(coin, interval="1m", lookback_minutes=1500): """ Fetches candle data from Hyperliquid. We need at least enough candles for the longest period (1440). Requesting slightly more to be safe. """ # Calculate startTime: now - (lookback_minutes * 60 * 1000) # Hyperliquid expects startTime in milliseconds end_time = int(time.time() * 1000) start_time = end_time - (lookback_minutes * 60 * 1000) payload = { "type": "candleSnapshot", "req": { "coin": coin, "interval": interval, "startTime": start_time, "endTime": end_time } } try: response = requests.post(API_URL, json=payload, timeout=10) response.raise_for_status() data = response.json() # Data format is typically a list of dicts: # {'t': 170..., 'T': 170..., 's': 'ETH', 'i': '1m', 'o': '...', 'c': '...', 'h': '...', 'l': '...', 'v': '...', 'n': ...} # We need closing prices 'c' candles = [] for c in data: try: # Ensure we parse 'c' (close) as float candles.append(float(c['c'])) except (ValueError, KeyError): continue return candles except Exception as e: print(f"Error fetching candles for {coin}: {e}") return [] def calculate_ma(prices, period): """Calculates Simple Moving Average.""" if len(prices) < period: return None return mean(prices[-period:]) def calculate_bb(prices, period, num_std_dev=2.0): """ Calculates Bollinger Bands for the LAST 'period' items in prices. Returns {mid, upper, lower} or None if insufficient data. """ if len(prices) < period: return None # Take the last 'period' prices window = prices[-period:] try: avg = mean(window) # Population stdev or sample stdev? Usually sample (stdev) is used in finance or pandas default if period > 1: sd = stdev(window) else: sd = 0.0 upper = avg + (num_std_dev * sd) lower = avg - (num_std_dev * sd) return { "mid": avg, "upper": upper, "lower": lower, "std": sd } except Exception as e: print(f"Error calculating BB: {e}") return None def main(): print(f"Starting Market Data Calculator for {COINS}") print(f"BB Periods: {PERIODS_CONFIG}") print(f"MA Periods: {MA_PERIODS}") print(f"Output: {OUTPUT_FILE}") # Ensure directory exists os.makedirs(os.path.dirname(OUTPUT_FILE), exist_ok=True) while True: try: results = { "last_updated": datetime.now().isoformat(), "config": { "std_dev_multiplier": STD_DEV_MULTIPLIER, "ma_periods": MA_PERIODS }, "data": {} } # Find the max needed history (BB vs MA) max_bb = max(PERIODS_CONFIG.values()) if PERIODS_CONFIG else 0 max_ma = max(MA_PERIODS) if MA_PERIODS else 0 fetch_limit = max(max_bb, max_ma) + 60 for coin in COINS: print(f"Fetching data for {coin}...", end="", flush=True) prices = fetch_candles(coin, lookback_minutes=fetch_limit) if not prices: print(" Failed.") continue print(f" Got {len(prices)} candles.", end="", flush=True) coin_results = { "current_price": prices[-1] if prices else 0, "bb": {}, "ma": {} } # Calculate BB for label, period in PERIODS_CONFIG.items(): bb = calculate_bb(prices, period, num_std_dev=STD_DEV_MULTIPLIER) coin_results["bb"][label] = bb if bb else "Insufficient Data" # Calculate MA for period in MA_PERIODS: ma = calculate_ma(prices, period) coin_results["ma"][str(period)] = ma if ma else "Insufficient Data" results["data"][coin] = coin_results print(" Done.") # Save to file with open(OUTPUT_FILE, 'w') as f: json.dump(results, f, indent=4) print(f"Updated {OUTPUT_FILE}") except Exception as e: print(f"Main loop error: {e}") time.sleep(UPDATE_INTERVAL) if __name__ == "__main__": main()