244 lines
8.3 KiB
Python
Executable File
244 lines
8.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sys
|
|
import urllib.parse
|
|
import urllib.request
|
|
|
|
DEFAULT_TIMEOUT = 20
|
|
|
|
|
|
def fetch_json(url, headers=None, timeout=DEFAULT_TIMEOUT):
|
|
merged_headers = {
|
|
"Accept": "application/json",
|
|
"User-Agent": "Mozilla/5.0 (compatible; OpenClaw Coin Hunter/1.0)",
|
|
}
|
|
if headers:
|
|
merged_headers.update(headers)
|
|
req = urllib.request.Request(url, headers=merged_headers)
|
|
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
data = resp.read()
|
|
return json.loads(data.decode("utf-8"))
|
|
|
|
|
|
def print_json(data):
|
|
print(json.dumps(data, ensure_ascii=False, indent=2))
|
|
|
|
|
|
def bybit_ticker(symbol: str):
|
|
url = (
|
|
"https://api.bybit.com/v5/market/tickers?category=spot&symbol="
|
|
+ urllib.parse.quote(symbol.upper())
|
|
)
|
|
payload = fetch_json(url)
|
|
items = payload.get("result", {}).get("list", [])
|
|
if not items:
|
|
raise SystemExit(f"No Bybit spot ticker found for {symbol}")
|
|
item = items[0]
|
|
out = {
|
|
"provider": "bybit",
|
|
"symbol": symbol.upper(),
|
|
"lastPrice": item.get("lastPrice"),
|
|
"price24hPcnt": item.get("price24hPcnt"),
|
|
"highPrice24h": item.get("highPrice24h"),
|
|
"lowPrice24h": item.get("lowPrice24h"),
|
|
"turnover24h": item.get("turnover24h"),
|
|
"volume24h": item.get("volume24h"),
|
|
"bid1Price": item.get("bid1Price"),
|
|
"ask1Price": item.get("ask1Price"),
|
|
}
|
|
print_json(out)
|
|
|
|
|
|
def bybit_klines(symbol: str, interval: str, limit: int):
|
|
params = urllib.parse.urlencode({
|
|
"category": "spot",
|
|
"symbol": symbol.upper(),
|
|
"interval": interval,
|
|
"limit": str(limit),
|
|
})
|
|
url = f"https://api.bybit.com/v5/market/kline?{params}"
|
|
payload = fetch_json(url)
|
|
rows = payload.get("result", {}).get("list", [])
|
|
out = {
|
|
"provider": "bybit",
|
|
"symbol": symbol.upper(),
|
|
"interval": interval,
|
|
"candles": [
|
|
{
|
|
"startTime": r[0],
|
|
"open": r[1],
|
|
"high": r[2],
|
|
"low": r[3],
|
|
"close": r[4],
|
|
"volume": r[5],
|
|
"turnover": r[6],
|
|
}
|
|
for r in rows
|
|
],
|
|
}
|
|
print_json(out)
|
|
|
|
|
|
def dexscreener_search(query: str):
|
|
url = "https://api.dexscreener.com/latest/dex/search/?q=" + urllib.parse.quote(query)
|
|
payload = fetch_json(url)
|
|
pairs = payload.get("pairs") or []
|
|
out = []
|
|
for p in pairs[:10]:
|
|
out.append({
|
|
"chainId": p.get("chainId"),
|
|
"dexId": p.get("dexId"),
|
|
"pairAddress": p.get("pairAddress"),
|
|
"url": p.get("url"),
|
|
"baseToken": p.get("baseToken"),
|
|
"quoteToken": p.get("quoteToken"),
|
|
"priceUsd": p.get("priceUsd"),
|
|
"liquidityUsd": (p.get("liquidity") or {}).get("usd"),
|
|
"fdv": p.get("fdv"),
|
|
"marketCap": p.get("marketCap"),
|
|
"volume24h": (p.get("volume") or {}).get("h24"),
|
|
"buys24h": ((p.get("txns") or {}).get("h24") or {}).get("buys"),
|
|
"sells24h": ((p.get("txns") or {}).get("h24") or {}).get("sells"),
|
|
})
|
|
print_json({"provider": "dexscreener", "query": query, "pairs": out})
|
|
|
|
|
|
def dexscreener_token(chain: str, address: str):
|
|
url = f"https://api.dexscreener.com/tokens/v1/{urllib.parse.quote(chain)}/{urllib.parse.quote(address)}"
|
|
payload = fetch_json(url)
|
|
pairs = payload if isinstance(payload, list) else payload.get("pairs") or []
|
|
out = []
|
|
for p in pairs[:10]:
|
|
out.append({
|
|
"chainId": p.get("chainId"),
|
|
"dexId": p.get("dexId"),
|
|
"pairAddress": p.get("pairAddress"),
|
|
"baseToken": p.get("baseToken"),
|
|
"quoteToken": p.get("quoteToken"),
|
|
"priceUsd": p.get("priceUsd"),
|
|
"liquidityUsd": (p.get("liquidity") or {}).get("usd"),
|
|
"fdv": p.get("fdv"),
|
|
"marketCap": p.get("marketCap"),
|
|
"volume24h": (p.get("volume") or {}).get("h24"),
|
|
})
|
|
print_json({"provider": "dexscreener", "chain": chain, "address": address, "pairs": out})
|
|
|
|
|
|
def coingecko_search(query: str):
|
|
url = "https://api.coingecko.com/api/v3/search?query=" + urllib.parse.quote(query)
|
|
payload = fetch_json(url)
|
|
coins = payload.get("coins") or []
|
|
out = []
|
|
for c in coins[:10]:
|
|
out.append({
|
|
"id": c.get("id"),
|
|
"name": c.get("name"),
|
|
"symbol": c.get("symbol"),
|
|
"marketCapRank": c.get("market_cap_rank"),
|
|
"thumb": c.get("thumb"),
|
|
})
|
|
print_json({"provider": "coingecko", "query": query, "coins": out})
|
|
|
|
|
|
def coingecko_coin(coin_id: str):
|
|
params = urllib.parse.urlencode({
|
|
"localization": "false",
|
|
"tickers": "false",
|
|
"market_data": "true",
|
|
"community_data": "false",
|
|
"developer_data": "false",
|
|
"sparkline": "false",
|
|
})
|
|
url = f"https://api.coingecko.com/api/v3/coins/{urllib.parse.quote(coin_id)}?{params}"
|
|
payload = fetch_json(url)
|
|
md = payload.get("market_data") or {}
|
|
out = {
|
|
"provider": "coingecko",
|
|
"id": payload.get("id"),
|
|
"symbol": payload.get("symbol"),
|
|
"name": payload.get("name"),
|
|
"marketCapRank": payload.get("market_cap_rank"),
|
|
"currentPriceUsd": (md.get("current_price") or {}).get("usd"),
|
|
"marketCapUsd": (md.get("market_cap") or {}).get("usd"),
|
|
"fullyDilutedValuationUsd": (md.get("fully_diluted_valuation") or {}).get("usd"),
|
|
"totalVolumeUsd": (md.get("total_volume") or {}).get("usd"),
|
|
"priceChangePercentage24h": md.get("price_change_percentage_24h"),
|
|
"priceChangePercentage7d": md.get("price_change_percentage_7d"),
|
|
"priceChangePercentage30d": md.get("price_change_percentage_30d"),
|
|
"circulatingSupply": md.get("circulating_supply"),
|
|
"totalSupply": md.get("total_supply"),
|
|
"maxSupply": md.get("max_supply"),
|
|
"homepage": (payload.get("links") or {}).get("homepage", [None])[0],
|
|
}
|
|
print_json(out)
|
|
|
|
|
|
def birdeye_token(address: str):
|
|
api_key = os.getenv("BIRDEYE_API_KEY") or os.getenv("BIRDEYE_APIKEY")
|
|
if not api_key:
|
|
raise SystemExit("Birdeye requires BIRDEYE_API_KEY in the environment")
|
|
url = "https://public-api.birdeye.so/defi/token_overview?address=" + urllib.parse.quote(address)
|
|
payload = fetch_json(url, headers={
|
|
"x-api-key": api_key,
|
|
"x-chain": "solana",
|
|
})
|
|
print_json({"provider": "birdeye", "address": address, "data": payload.get("data")})
|
|
|
|
|
|
def build_parser():
|
|
parser = argparse.ArgumentParser(description="Coin Hunter market data probe")
|
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
|
|
p = sub.add_parser("bybit-ticker", help="Fetch Bybit spot ticker")
|
|
p.add_argument("symbol")
|
|
|
|
p = sub.add_parser("bybit-klines", help="Fetch Bybit spot klines")
|
|
p.add_argument("symbol")
|
|
p.add_argument("--interval", default="60", help="Bybit interval, e.g. 1, 5, 15, 60, 240, D")
|
|
p.add_argument("--limit", type=int, default=10)
|
|
|
|
p = sub.add_parser("dex-search", help="Search DexScreener by query")
|
|
p.add_argument("query")
|
|
|
|
p = sub.add_parser("dex-token", help="Fetch DexScreener token pairs by chain/address")
|
|
p.add_argument("chain")
|
|
p.add_argument("address")
|
|
|
|
p = sub.add_parser("gecko-search", help="Search CoinGecko")
|
|
p.add_argument("query")
|
|
|
|
p = sub.add_parser("gecko-coin", help="Fetch CoinGecko coin by id")
|
|
p.add_argument("coin_id")
|
|
|
|
p = sub.add_parser("birdeye-token", help="Fetch Birdeye token overview (Solana)")
|
|
p.add_argument("address")
|
|
|
|
return parser
|
|
|
|
|
|
def main():
|
|
parser = build_parser()
|
|
args = parser.parse_args()
|
|
if args.command == "bybit-ticker":
|
|
bybit_ticker(args.symbol)
|
|
elif args.command == "bybit-klines":
|
|
bybit_klines(args.symbol, args.interval, args.limit)
|
|
elif args.command == "dex-search":
|
|
dexscreener_search(args.query)
|
|
elif args.command == "dex-token":
|
|
dexscreener_token(args.chain, args.address)
|
|
elif args.command == "gecko-search":
|
|
coingecko_search(args.query)
|
|
elif args.command == "gecko-coin":
|
|
coingecko_coin(args.coin_id)
|
|
elif args.command == "birdeye-token":
|
|
birdeye_token(args.address)
|
|
else:
|
|
parser.error("Unknown command")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|