price_checker.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. import requests
  2. import decimal
  3. import time
  4. import threading
  5. import json
  6. from flask import Flask, render_template, jsonify
  7. from collections import deque # 用于高效地存储固定数量的历史数据
  8. import logging # Import the logging module
  9. # --- 配置部分 (与之前相同) ---
  10. GATEIO_SPOT_PAIR = 'CAT_USDT'
  11. IN_TOKEN_ADDRESS_BSC = '0x55d398326f99059ff775485246999027b3197955'
  12. OUT_TOKEN_ADDRESS_BSC = '0x6894CDe390a3f51155ea41Ed24a33A4827d3063D'
  13. AMOUNT_TO_QUERY_HUMAN = decimal.Decimal('1000')
  14. PROXY_HOST = '127.0.0.1'
  15. PROXY_PORT = '7890'
  16. proxies = {
  17. 'http': f'http://{PROXY_HOST}:{PROXY_PORT}',
  18. 'https': f'http://{PROXY_HOST}:{PROXY_PORT}',
  19. }
  20. # proxies = None # 如果你不需要代理
  21. decimal.getcontext().prec = 36
  22. # --- 价格获取函数 (与之前相同, 省略以保持简洁) ---
  23. def get_openocean_price_bsc(in_token_addr, out_token_addr, human_amount_in_decimal_for_request, gas_price=3):
  24. # ... (代码与之前相同)
  25. chain = 'bsc'
  26. url = f'https://open-api.openocean.finance/v4/{chain}/quote'
  27. params = {
  28. 'inTokenAddress': in_token_addr,
  29. 'outTokenAddress': out_token_addr,
  30. 'amount': str(human_amount_in_decimal_for_request),
  31. 'gasPrice': gas_price,
  32. }
  33. try:
  34. response = requests.get(url, params=params, proxies=proxies, timeout=10)
  35. response.raise_for_status()
  36. data = response.json()
  37. if data.get('code') == 200 and data.get('data'):
  38. api_data = data['data']
  39. if not (api_data.get('inToken') and api_data['inToken'].get('decimals') is not None and
  40. api_data.get('outToken') and api_data['outToken'].get('decimals') is not None and
  41. api_data.get('inAmount') is not None and api_data.get('outAmount') is not None):
  42. return {"error": "OO API响应中缺少必要的数据"}
  43. in_token_decimals = int(api_data['inToken']['decimals'])
  44. out_token_decimals = int(api_data['outToken']['decimals'])
  45. atomic_in_amount = decimal.Decimal(api_data['inAmount'])
  46. atomic_out_amount = decimal.Decimal(api_data['outAmount'])
  47. derived_human_in_amount = atomic_in_amount / (decimal.Decimal('10') ** in_token_decimals)
  48. derived_human_out_amount = atomic_out_amount / (decimal.Decimal('10') ** out_token_decimals)
  49. if derived_human_out_amount == 0:
  50. return {"error": "OO 计算的输出数量为零"}
  51. calculated_price = derived_human_in_amount / derived_human_out_amount
  52. return {"price_in_per_out": calculated_price}
  53. else:
  54. error_message = data.get('message', 'N/A') if data else '无响应数据'
  55. error_code = data.get('code', 'N/A') if data else 'N/A'
  56. return {"error": f"OO API 错误 - Code: {error_code}, Msg: {error_message}"}
  57. except requests.exceptions.RequestException as e:
  58. return {"error": f"OO请求失败: {e}"}
  59. except Exception as e:
  60. return {"error": f"OO意外错误: {e}"}
  61. def get_gateio_spot_price(pair_symbol):
  62. # ... (代码与之前相同)
  63. url = f'https://api.gateio.ws/api/v4/spot/tickers'
  64. params = {'currency_pair': pair_symbol}
  65. try:
  66. response = requests.get(url, params=params, proxies=proxies, timeout=10)
  67. response.raise_for_status()
  68. data = response.json()
  69. if isinstance(data, list) and len(data) > 0:
  70. ticker_data = data[0]
  71. if ticker_data.get('currency_pair') == pair_symbol:
  72. last_price_str = ticker_data.get('last')
  73. if last_price_str:
  74. return {"price_base_in_quote": decimal.Decimal(last_price_str)}
  75. else:
  76. return {"error": "Gate未找到last price"}
  77. else:
  78. return {"error": f"Gate交易对不匹配"}
  79. else:
  80. return {"error": "Gate API数据格式错误"}
  81. except requests.exceptions.RequestException as e:
  82. return {"error": f"Gate请求失败: {e}"}
  83. except Exception as e:
  84. return {"error": f"Gate意外错误: {e}"}
  85. app = Flask(__name__)
  86. # --- 全局数据和历史记录 ---
  87. MAX_HISTORY_POINTS = 60 # 图表上显示的最大数据点数量 (例如,过去5分钟,每5秒一个点)
  88. latest_data = {
  89. "oo_price": None,
  90. "gate_price": None,
  91. "difference_percentage": None,
  92. "oo_error": None,
  93. "gate_error": None,
  94. "last_updated": None
  95. }
  96. # 使用 deque 来存储历史数据,它在添加和删除元素时具有 O(1) 的复杂度
  97. historical_prices = deque(maxlen=MAX_HISTORY_POINTS) # 存储 {'timestamp': ts, 'oo': price, 'gate': price}
  98. historical_diff = deque(maxlen=MAX_HISTORY_POINTS) # 存储 {'timestamp': ts, 'diff': percentage}
  99. data_lock = threading.Lock()
  100. def update_prices_periodically():
  101. global latest_data, historical_prices, historical_diff
  102. while True:
  103. fetch_timestamp_str = time.strftime("%H:%M:%S") # 用于图表标签的简单时间戳
  104. oo_price_data = get_openocean_price_bsc(
  105. IN_TOKEN_ADDRESS_BSC,
  106. OUT_TOKEN_ADDRESS_BSC,
  107. AMOUNT_TO_QUERY_HUMAN
  108. )
  109. gate_price_data = get_gateio_spot_price(GATEIO_SPOT_PAIR)
  110. current_oo_price_decimal = None
  111. current_gate_price_decimal = None
  112. current_oo_error = None
  113. current_gate_error = None
  114. diff_percentage_val = None
  115. diff_percentage_str = "N/A"
  116. if "error" not in oo_price_data:
  117. current_oo_price_decimal = oo_price_data['price_in_per_out']
  118. else:
  119. current_oo_error = oo_price_data['error']
  120. if "error" not in gate_price_data:
  121. current_gate_price_decimal = gate_price_data['price_base_in_quote']
  122. else:
  123. current_gate_error = gate_price_data['error']
  124. # 只有当两个价格都有效时,才计算价差并记录历史
  125. if current_oo_price_decimal is not None and current_gate_price_decimal is not None:
  126. # 将Decimal转换为浮点数以便存储和图表绘制 (Chart.js 通常使用JS number)
  127. oo_price_float = float(current_oo_price_decimal)
  128. gate_price_float = float(current_gate_price_decimal)
  129. historical_prices.append({
  130. "timestamp": fetch_timestamp_str,
  131. "oo": oo_price_float,
  132. "gate": gate_price_float
  133. })
  134. if current_gate_price_decimal > 0:
  135. difference = current_oo_price_decimal - current_gate_price_decimal
  136. percentage_diff_decimal = (difference / current_gate_price_decimal) * 100
  137. diff_percentage_val = float(percentage_diff_decimal) # 用于图表
  138. diff_percentage_str = f"{percentage_diff_decimal:+.4f}%" # 用于显示
  139. historical_diff.append({
  140. "timestamp": fetch_timestamp_str,
  141. "diff": diff_percentage_val
  142. })
  143. else:
  144. diff_percentage_str = "Gate价格为0"
  145. # 对于价差图表,如果gate价格为0,可以记录一个特殊值或跳过
  146. historical_diff.append({
  147. "timestamp": fetch_timestamp_str,
  148. "diff": None # 或者一个非常大/小的值来表示无效
  149. })
  150. else:
  151. # 如果其中一个价格无效,我们仍然可以尝试记录有效的那个,或者都标记为None
  152. # 这里我们选择如果任一价格无效,价格历史点也记录None,价差历史点也记录None
  153. historical_prices.append({
  154. "timestamp": fetch_timestamp_str,
  155. "oo": float(current_oo_price_decimal) if current_oo_price_decimal else None,
  156. "gate": float(current_gate_price_decimal) if current_gate_price_decimal else None
  157. })
  158. historical_diff.append({
  159. "timestamp": fetch_timestamp_str,
  160. "diff": None
  161. })
  162. with data_lock:
  163. latest_data["oo_price"] = str(current_oo_price_decimal) if current_oo_price_decimal is not None else None
  164. latest_data["gate_price"] = str(
  165. current_gate_price_decimal) if current_gate_price_decimal is not None else None
  166. latest_data["difference_percentage"] = diff_percentage_str
  167. latest_data["oo_error"] = current_oo_error
  168. latest_data["gate_error"] = current_gate_error
  169. latest_data["last_updated"] = time.strftime("%Y-%m-%d %H:%M:%S")
  170. log_oo = f"{current_oo_price_decimal:.6f}" if current_oo_price_decimal else "N/A"
  171. log_gate = f"{current_gate_price_decimal:.6f}" if current_gate_price_decimal else "N/A"
  172. print(
  173. f"Data updated at {fetch_timestamp_str}: OO: {log_oo}, Gate: {log_gate}, Diff: {diff_percentage_str}, Errors: OO: {current_oo_error}, Gate: {current_gate_error}")
  174. time.sleep(5) # 更新频率
  175. @app.route('/')
  176. def index():
  177. return render_template('index.html')
  178. @app.route('/data')
  179. def get_data():
  180. with data_lock:
  181. # 准备图表需要的数据
  182. # 对于价格图表
  183. price_chart_labels = [item['timestamp'] for item in historical_prices]
  184. oo_price_values = [item['oo'] for item in historical_prices]
  185. gate_price_values = [item['gate'] for item in historical_prices]
  186. # 对于价差图表
  187. diff_chart_labels = [item['timestamp'] for item in historical_diff]
  188. diff_values = [item['diff'] for item in historical_diff]
  189. # 将最新的数据点加入 (如果历史记录已满,deque会自动移除旧的)
  190. # 这里的数据是 latest_data 和 historical_data 的组合
  191. # Chart.js需要完整的历史数据来初始化,然后可以增量更新
  192. # 或者,前端每次都获取完整的历史窗口
  193. # 为了简单起见,前端将定期获取完整的 /data,其中包含历史和当前
  194. response_data = {
  195. "current": latest_data,
  196. "history": {
  197. "prices": {
  198. "labels": list(price_chart_labels), # deque 不是直接 JSON 可序列化的
  199. "oo": list(oo_price_values),
  200. "gate": list(gate_price_values)
  201. },
  202. "difference": {
  203. "labels": list(diff_chart_labels),
  204. "values": list(diff_values)
  205. }
  206. }
  207. }
  208. return jsonify(response_data)
  209. if __name__ == "__main__":
  210. # Disable Werkzeug access logs
  211. werkzeug_logger = logging.getLogger('werkzeug')
  212. werkzeug_logger.setLevel(logging.ERROR) # or logging.CRITICAL
  213. price_updater_thread = threading.Thread(target=update_prices_periodically, daemon=True)
  214. price_updater_thread.start()
  215. app.run(debug=True, host='0.0.0.0', port=5000, use_reloader=False)