working version, before optimalization
This commit is contained in:
171
tools/calculate_market_data.py
Normal file
171
tools/calculate_market_data.py
Normal file
@ -0,0 +1,171 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user