172 lines
5.3 KiB
Python
172 lines
5.3 KiB
Python
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()
|