price_checker.py 10 KB

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