Spaces:
Sleeping
Sleeping
Add Jared read-only token OHLCV ingestion and manual contract lookup
Browse files- core/data.py +132 -0
core/data.py
CHANGED
|
@@ -81,6 +81,127 @@ def fetch_ompfinex_data(ticker, start_date, end_date, interval):
|
|
| 81 |
print_log(f"OMPFINEX fetch failed for {ticker}: {str(e)}", 'ERROR')
|
| 82 |
return pd.DataFrame()
|
| 83 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
def load_data(data_src='yahoo', ticker='AAPL', start='2020-01-01', end='2023-01-01', interval='1d', file_upload=None, alpha_api_key=None):
|
| 85 |
try:
|
| 86 |
print_log(f"Loading data: source={data_src}, ticker={ticker}, start={start}, end={end}, interval={interval}, file_upload={'set' if file_upload else 'unset'}, alpha_api_key={'set' if alpha_api_key else 'unset'}")
|
|
@@ -115,6 +236,17 @@ def load_data(data_src='yahoo', ticker='AAPL', start='2020-01-01', end='2023-01-
|
|
| 115 |
except Exception as e:
|
| 116 |
print_log(f"Failed to load CSV {file_path}: {str(e)}", 'ERROR')
|
| 117 |
raise ValueError(f"Failed to load CSV: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
elif ticker in ["USDT-IRT", "BTC-IRT", "ETH-IRT"]:
|
| 119 |
print_log(f"Fetching {ticker} from OMPFINEX")
|
| 120 |
df = fetch_ompfinex_data(ticker, start_date, end_date, interval)
|
|
|
|
| 81 |
print_log(f"OMPFINEX fetch failed for {ticker}: {str(e)}", 'ERROR')
|
| 82 |
return pd.DataFrame()
|
| 83 |
|
| 84 |
+
JARED_TRPC_BASE = os.getenv("JARED_TRPC_BASE", "https://jaredfromsubway.manus.space/api/trpc")
|
| 85 |
+
GECKO_BASE = os.getenv("GECKO_TERMINAL_BASE", "https://api.geckoterminal.com/api/v2")
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def _trpc_get(procedure, payload=None, timeout=20):
|
| 89 |
+
"""Read-only request to the public Jared evidence console tRPC API."""
|
| 90 |
+
query = {"json": payload}
|
| 91 |
+
response = requests.get(
|
| 92 |
+
f"{JARED_TRPC_BASE.rstrip('/')}/{procedure}",
|
| 93 |
+
params={"input": json.dumps(query, separators=(",", ":"))},
|
| 94 |
+
headers={"accept": "application/json"},
|
| 95 |
+
timeout=timeout,
|
| 96 |
+
)
|
| 97 |
+
response.raise_for_status()
|
| 98 |
+
body = response.json()
|
| 99 |
+
return body.get("result", {}).get("data", {}).get("json")
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def fetch_jared_observed_tokens(limit=100):
|
| 103 |
+
"""Return source-labeled, read-only wallet observations from Jared's public console."""
|
| 104 |
+
try:
|
| 105 |
+
result = _trpc_get("ml.getObservedTokenTable", {"limit": int(limit)})
|
| 106 |
+
return result if isinstance(result, list) else []
|
| 107 |
+
except Exception as exc:
|
| 108 |
+
print_log(f"Jared observed-token feed unavailable: {exc}", "WARNING")
|
| 109 |
+
return []
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def resolve_jared_token(token_query=None):
|
| 113 |
+
"""Resolve a symbol/name/address against the public observed-token table.
|
| 114 |
+
|
| 115 |
+
If no query is provided, choose the first observation with a verified OHLCV price.
|
| 116 |
+
The returned metadata is retained by callers for provenance display.
|
| 117 |
+
"""
|
| 118 |
+
observations = fetch_jared_observed_tokens()
|
| 119 |
+
query = (token_query or "").strip().lower()
|
| 120 |
+
candidates = observations
|
| 121 |
+
if query:
|
| 122 |
+
candidates = [
|
| 123 |
+
row for row in observations
|
| 124 |
+
if query in str(row.get("tokenAddress", "")).lower()
|
| 125 |
+
or query == str(row.get("symbol", "")).lower()
|
| 126 |
+
or query == str(row.get("name", "")).lower()
|
| 127 |
+
]
|
| 128 |
+
for row in candidates:
|
| 129 |
+
address = str(row.get("tokenAddress", "")).strip()
|
| 130 |
+
if address.startswith("0x") and len(address) == 42 and (
|
| 131 |
+
row.get("pricePathStatus") == "verified_ohlcv" or row.get("priceUsd") is not None
|
| 132 |
+
):
|
| 133 |
+
return row
|
| 134 |
+
return None
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
def _gecko_interval(interval):
|
| 138 |
+
if interval in {"1m", "2m", "5m", "15m", "30m", "60m", "90m", "1h"}:
|
| 139 |
+
return "hour", 1
|
| 140 |
+
if interval in {"1wk"}:
|
| 141 |
+
return "day", 7
|
| 142 |
+
return "day", 1
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def fetch_token_ohlcv(address, start_date, end_date, interval="1d", network="eth"):
|
| 146 |
+
"""Fetch public DEX OHLCV candles for an ERC-20 address via GeckoTerminal."""
|
| 147 |
+
if not (isinstance(address, str) and address.lower().startswith("0x") and len(address) == 42):
|
| 148 |
+
raise ValueError("A valid EVM token contract address is required")
|
| 149 |
+
headers = {"accept": "application/json"}
|
| 150 |
+
pools_url = f"{GECKO_BASE}/networks/{network}/tokens/{address}/pools"
|
| 151 |
+
pools_resp = requests.get(pools_url, params={"page": 1}, headers=headers, timeout=25)
|
| 152 |
+
pools_resp.raise_for_status()
|
| 153 |
+
pools = pools_resp.json().get("data", [])
|
| 154 |
+
if not pools:
|
| 155 |
+
raise ValueError(f"No public DEX pool found for {address}")
|
| 156 |
+
# Prefer the pool with the greatest 24h USD volume; otherwise use the first pool.
|
| 157 |
+
def volume(pool):
|
| 158 |
+
try:
|
| 159 |
+
return float((pool.get("attributes") or {}).get("volume_usd", {}).get("h24") or 0)
|
| 160 |
+
except Exception:
|
| 161 |
+
return 0.0
|
| 162 |
+
pool = max(pools, key=volume)
|
| 163 |
+
attrs = pool.get("attributes") or {}
|
| 164 |
+
pool_address = attrs.get("address") or str(pool.get("id", "")).split("_", 1)[-1]
|
| 165 |
+
timeframe, aggregate = _gecko_interval(interval)
|
| 166 |
+
ohlcv_url = f"{GECKO_BASE}/networks/{network}/pools/{pool_address}/ohlcv/{timeframe}"
|
| 167 |
+
ohlcv_resp = requests.get(
|
| 168 |
+
ohlcv_url,
|
| 169 |
+
params={"aggregate": aggregate, "limit": 1000},
|
| 170 |
+
headers=headers,
|
| 171 |
+
timeout=25,
|
| 172 |
+
)
|
| 173 |
+
ohlcv_resp.raise_for_status()
|
| 174 |
+
values = (((ohlcv_resp.json().get("data") or {}).get("attributes") or {}).get("ohlcv_list") or [])
|
| 175 |
+
if not values:
|
| 176 |
+
raise ValueError(f"No OHLCV history returned for {address}")
|
| 177 |
+
rows = []
|
| 178 |
+
for candle in values:
|
| 179 |
+
if len(candle) < 6:
|
| 180 |
+
continue
|
| 181 |
+
rows.append({"Date": pd.to_datetime(candle[0], unit="s"), "Open": candle[1], "High": candle[2], "Low": candle[3], "value": candle[4], "Volume": candle[5]})
|
| 182 |
+
df = pd.DataFrame(rows).dropna(subset=["Date", "value"])
|
| 183 |
+
if df.empty:
|
| 184 |
+
raise ValueError(f"OHLCV history contained no usable rows for {address}")
|
| 185 |
+
df = df.set_index("Date").sort_index()
|
| 186 |
+
start_ts, end_ts = pd.to_datetime(start_date), pd.to_datetime(end_date)
|
| 187 |
+
filtered = df[(df.index >= start_ts) & (df.index <= end_ts)]
|
| 188 |
+
# New meme tokens may have only a short history; never fabricate older candles.
|
| 189 |
+
return filtered if not filtered.empty else df
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def fetch_jared_token_data(token_query, start_date, end_date, interval="1d"):
|
| 193 |
+
"""Resolve a Jared-observed token and load its public DEX OHLCV history."""
|
| 194 |
+
row = resolve_jared_token(token_query)
|
| 195 |
+
if not row:
|
| 196 |
+
raise ValueError("No Jared observation with verified public OHLCV was found; enter a contract address or symbol.")
|
| 197 |
+
df = fetch_token_ohlcv(row["tokenAddress"], start_date, end_date, interval)
|
| 198 |
+
df.attrs["source"] = "Jared public observed-token table + GeckoTerminal public DEX OHLCV"
|
| 199 |
+
df.attrs["token_address"] = row.get("tokenAddress")
|
| 200 |
+
df.attrs["token_symbol"] = row.get("symbol") or row.get("name") or token_query
|
| 201 |
+
df.attrs["jared_observation"] = row
|
| 202 |
+
return df
|
| 203 |
+
|
| 204 |
+
|
| 205 |
def load_data(data_src='yahoo', ticker='AAPL', start='2020-01-01', end='2023-01-01', interval='1d', file_upload=None, alpha_api_key=None):
|
| 206 |
try:
|
| 207 |
print_log(f"Loading data: source={data_src}, ticker={ticker}, start={start}, end={end}, interval={interval}, file_upload={'set' if file_upload else 'unset'}, alpha_api_key={'set' if alpha_api_key else 'unset'}")
|
|
|
|
| 236 |
except Exception as e:
|
| 237 |
print_log(f"Failed to load CSV {file_path}: {str(e)}", 'ERROR')
|
| 238 |
raise ValueError(f"Failed to load CSV: {str(e)}")
|
| 239 |
+
elif data_src == 'jared' or ticker == 'JARED-LATEST' or (isinstance(ticker, str) and ticker.lower().startswith('0x')):
|
| 240 |
+
print_log(f"Fetching Jared-observed token OHLCV for {ticker}")
|
| 241 |
+
if isinstance(ticker, str) and ticker.lower().startswith('0x'):
|
| 242 |
+
df = fetch_token_ohlcv(ticker, start_date, end_date, interval)
|
| 243 |
+
df.attrs['source'] = 'Manual EVM contract address + GeckoTerminal public DEX OHLCV'
|
| 244 |
+
df.attrs['token_address'] = ticker
|
| 245 |
+
df.attrs['token_symbol'] = ticker
|
| 246 |
+
else:
|
| 247 |
+
df = fetch_jared_token_data(None if ticker == 'JARED-LATEST' else ticker, start_date, end_date, interval)
|
| 248 |
+
if df.empty:
|
| 249 |
+
raise ValueError(f"No public OHLCV history found for {ticker}")
|
| 250 |
elif ticker in ["USDT-IRT", "BTC-IRT", "ETH-IRT"]:
|
| 251 |
print_log(f"Fetching {ticker} from OMPFINEX")
|
| 252 |
df = fetch_ompfinex_data(ticker, start_date, end_date, interval)
|