discover_swissquote_api.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import requests
  2. import time
  3. import json
  4. import random
  5. from datetime import datetime, UTC
  6. from concurrent.futures import ThreadPoolExecutor, as_completed
  7. from threading import Lock
  8. BASE_URL = "https://forex-data-feed.swissquote.com/public-quotes/bboquotes/instrument/{}"
  9. HEADERS = {
  10. "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0 Safari/537.36",
  11. "Accept": "application/json, text/plain, */*",
  12. "Accept-Language": "en-US,en;q=0.9",
  13. "Connection": "keep-alive"
  14. }
  15. BASE_ASSETS = [
  16. "XAU", "XAG", "XPT", "XPD",
  17. "EUR", "USD", "GBP", "JPY", "CHF", "AUD", "CAD", "NZD"
  18. ]
  19. QUOTE_ASSETS = [
  20. "USD", "EUR", "JPY", "CHF", "GBP"
  21. ]
  22. OUTPUT_FILE = "swissquote_pairs.json"
  23. # Rate limiting
  24. REQUESTS_PER_SECOND = 5
  25. MIN_INTERVAL = 1.0 / REQUESTS_PER_SECOND
  26. last_request_time = 0
  27. rate_lock = Lock()
  28. def rate_limited_request():
  29. global last_request_time
  30. with rate_lock:
  31. now = time.time()
  32. elapsed = now - last_request_time
  33. if elapsed < MIN_INTERVAL:
  34. time.sleep(MIN_INTERVAL - elapsed)
  35. last_request_time = time.time()
  36. def fetch(symbol, retries=2):
  37. url = BASE_URL.format(symbol)
  38. for attempt in range(retries + 1):
  39. try:
  40. rate_limited_request()
  41. time.sleep(random.uniform(0.02, 0.08)) # jitter
  42. r = requests.get(url, headers=HEADERS, timeout=3)
  43. if r.status_code != 200:
  44. raise Exception(f"HTTP {r.status_code}")
  45. data = r.json()
  46. # Normalize response (dict or list)
  47. if isinstance(data, list):
  48. if not data:
  49. return None
  50. data = data[0]
  51. if not isinstance(data, dict):
  52. return None
  53. prices = data.get("spreadProfilePrices")
  54. if not prices:
  55. return None
  56. p = prices[0]
  57. bid = float(p.get("bid", 0))
  58. ask = float(p.get("ask", 0))
  59. ts = p.get("timestamp")
  60. if bid > 0 and ask > 0:
  61. return {
  62. "symbol": symbol,
  63. "bid": bid,
  64. "ask": ask,
  65. "timestamp": ts
  66. }
  67. return None
  68. except Exception as e:
  69. if attempt < retries:
  70. time.sleep(0.3 * (2 ** attempt)) # backoff
  71. else:
  72. print(f"[FAIL] {symbol} → {e}")
  73. return None
  74. def main():
  75. symbols = [
  76. f"{b}/{q}"
  77. for b in BASE_ASSETS
  78. for q in QUOTE_ASSETS
  79. if b != q
  80. ]
  81. print(f"Checking {len(symbols)} symbols...\n")
  82. results = []
  83. with ThreadPoolExecutor(max_workers=4) as executor:
  84. futures = {executor.submit(fetch, s): s for s in symbols}
  85. for future in as_completed(futures):
  86. symbol = futures[future]
  87. result = future.result()
  88. if result:
  89. print(f"✔ {symbol}")
  90. results.append(result)
  91. else:
  92. print(f"✘ {symbol}")
  93. output = {
  94. "generated_at": datetime.now(UTC).isoformat(),
  95. "total_checked": len(symbols),
  96. "total_valid": len(results),
  97. "pairs": sorted(results, key=lambda x: x["symbol"])
  98. }
  99. with open(OUTPUT_FILE, "w") as f:
  100. json.dump(output, f, indent=2)
  101. print("\n=== DONE ===")
  102. print(f"Valid pairs: {len(results)}")
  103. print(f"Saved to: {OUTPUT_FILE}")
  104. if __name__ == "__main__":
  105. main()