price_checker.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import requests
  2. import decimal # 导入 decimal 模块,用于更精确的货币运算
  3. import time # 导入 time 模块,用于实现轮询间隔
  4. # --- 配置部分 (与之前代码相同,此处省略以保持简洁) ---
  5. # proxies = None # 如果不使用代理
  6. GATEIO_SPOT_PAIR = 'MUBARAK_USDT'
  7. # BSC (币安智能链) 代币地址
  8. IN_TOKEN_ADDRESS_BSC = '0x55d398326f99059ff775485246999027b3197955' # BSC 上的 USDT 代币合约地址
  9. OUT_TOKEN_ADDRESS_BSC = '0x5C85D6C6825aB4032337F11Ee92a72DF936b46F6'
  10. AMOUNT_TO_QUERY_HUMAN = decimal.Decimal('1000') # 查询数量设置为1个单位的输入代币
  11. PROXY_HOST = '127.0.0.1'
  12. PROXY_PORT = '7890'
  13. proxies = {
  14. 'http': f'http://{PROXY_HOST}:{PROXY_PORT}',
  15. 'https': f'http://{PROXY_HOST}:{PROXY_PORT}',
  16. }
  17. # --- OpenOcean 的价格获取函数 ---
  18. def get_openocean_price_bsc(in_token_addr, out_token_addr, human_amount_in_decimal_for_request, gas_price=3):
  19. """
  20. 从 OpenOcean API 获取报价,并根据 API 返回的原子单位数量和代币小数位数计算价格。
  21. 价格计算方式为: (通过 inAmount 和 inToken.decimals 计算得到的人类可读输入数量) / (通过 outAmount 和 outToken.decimals 计算得到的人类可读输出数量)
  22. 这个价格表示 “每单位输出代币需要多少单位输入代币”。
  23. Args:
  24. in_token_addr (str): 输入代币的合约地址。
  25. out_token_addr (str): 输出代币的合约地址。
  26. human_amount_in_decimal_for_request (decimal.Decimal): 用于 API 请求的人类可读的输入金额。
  27. API会根据此金额和实际代币信息处理请求。
  28. gas_price (int, optional): Gas 价格。 默认为 3。
  29. Returns:
  30. dict: 包含价格计算结果或错误信息的字典。
  31. 成功时: {"price_in_per_out": calculated_price}
  32. 失败时: {"error": "错误信息"}
  33. """
  34. chain = 'bsc'
  35. url = f'https://open-api.openocean.finance/v4/{chain}/quote'
  36. params = {
  37. 'inTokenAddress': in_token_addr,
  38. 'outTokenAddress': out_token_addr,
  39. 'amount': str(human_amount_in_decimal_for_request), # API 请求中的 amount 参数
  40. 'gasPrice': gas_price,
  41. }
  42. try:
  43. response = requests.get(url, params=params, proxies=proxies, timeout=10)
  44. response.raise_for_status() # 如果HTTP状态码表示错误,则抛出异常
  45. data = response.json()
  46. if data.get('code') == 200 and data.get('data'):
  47. api_data = data['data']
  48. # 确保所有需要的数据都存在
  49. if not (api_data.get('inToken') and api_data['inToken'].get('decimals') is not None and
  50. api_data.get('outToken') and api_data['outToken'].get('decimals') is not None and
  51. api_data.get('inAmount') is not None and api_data.get('outAmount') is not None):
  52. return {"error": "API响应中缺少必要的代币信息、数量或小数位数"}
  53. # 从 API 响应中获取原子单位的数量和小数位数
  54. in_token_decimals = int(api_data['inToken']['decimals'])
  55. out_token_decimals = int(api_data['outToken']['decimals'])
  56. atomic_in_amount = decimal.Decimal(api_data['inAmount'])
  57. atomic_out_amount = decimal.Decimal(api_data['outAmount'])
  58. # 计算人类可读的输入数量
  59. # derived_human_in_amount = atomic_in_amount / (decimal.Decimal('10') ** in_token_decimals)
  60. # 根据您的反馈,OpenOcean的 inToken.volume 已经是人类可读的输入量,而 inAmount 是API内部处理后的原子单位量
  61. # 如果您的意图是严格使用API返回的 inAmount 来反推人类可读输入量,那么上面的计算是正确的。
  62. # 我们将基于 inAmount 和 outAmount 来计算,正如您所要求的。
  63. derived_human_in_amount = atomic_in_amount / (decimal.Decimal('10') ** in_token_decimals)
  64. # 计算人类可读的输出数量
  65. derived_human_out_amount = atomic_out_amount / (decimal.Decimal('10') ** out_token_decimals)
  66. if derived_human_out_amount == 0: # 避免除以零的错误
  67. return {"error": "计算得出的人类可读输出数量为零,无法计算价格"}
  68. if derived_human_in_amount == 0 and human_amount_in_decimal_for_request > 0:
  69. # 如果API返回的inAmount为0,但请求量大于0,可能是一个问题或特殊情况
  70. # 但我们仍按您的要求基于derived_human_in_amount计算
  71. pass
  72. # 根据您的要求:价格 = 输入数量 / 输出数量
  73. # 这个价格表示:1 单位的输出代币等价于多少单位的输入代币
  74. calculated_price = derived_human_in_amount / derived_human_out_amount
  75. return {"price_in_per_out": calculated_price} # 使用更能描述价格含义的键名
  76. else:
  77. # API 返回的 code 不是 200 或没有 data 字段
  78. error_message = data.get('message', 'N/A') if data else '无响应数据 (data is None)'
  79. error_code = data.get('code', 'N/A') if data else 'N/A'
  80. return {"error": f"OO API 错误 - Code: {error_code}, Msg: {error_message}"}
  81. except requests.exceptions.RequestException as e:
  82. return {"error": f"OO请求失败: {e}"}
  83. except Exception as e: # 捕获包括 JSONDecodeError 在内的其他潜在错误
  84. return {"error": f"OO意外错误: {e}"}
  85. def get_gateio_spot_price(pair_symbol):
  86. url = f'https://api.gateio.ws/api/v4/spot/tickers'
  87. params = {'currency_pair': pair_symbol}
  88. try:
  89. response = requests.get(url, params=params, proxies=proxies, timeout=10)
  90. response.raise_for_status()
  91. data = response.json()
  92. if isinstance(data, list) and len(data) > 0:
  93. ticker_data = data[0]
  94. if ticker_data.get('currency_pair') == pair_symbol:
  95. last_price_str = ticker_data.get('last')
  96. if last_price_str:
  97. return {"price_base_in_quote": decimal.Decimal(last_price_str)}
  98. else:
  99. return {"error": "Gate未找到last price"}
  100. else:
  101. return {"error": f"Gate交易对不匹配"}
  102. else:
  103. return {"error": "Gate API数据格式错误"}
  104. except requests.exceptions.RequestException as e:
  105. return {"error": f"Gate请求失败: {e}"}
  106. except Exception as e:
  107. return {"error": f"Gate意外错误: {e}"}
  108. # --- 主逻辑 (已修改) ---
  109. def main():
  110. print(f"开始轮询价格 (每秒一次), BSC vs Gate.io {GATEIO_SPOT_PAIR}")
  111. print("按 Ctrl+C 停止。")
  112. # 打印表头,使用格式化字符串使其对齐
  113. header = f"{'OpenOcean ':<25} | {'Gate.io ':<25} | {'价差百分比':<15}"
  114. print(header)
  115. print("-" * (25 + 3 + 25 + 3 + 15)) # 打印与表头长度匹配的分隔线
  116. try:
  117. while True:
  118. # --- 初始化本轮迭代的变量 ---
  119. oo_rate_usdc_per_usdt = None # OpenOcean 的汇率 (1 USDT = X USDC)
  120. gate_rate_usdc_per_usdt_inverted = None # Gate.io 转换后的汇率 (1 USDT = X USDC)
  121. oo_display_str = "N/A" # OpenOcean 在最终输出行中显示的字符串
  122. gate_display_str = "N/A" # Gate.io 在最终输出行中显示的字符串
  123. diff_percentage_display_str = "N/A" # 价差百分比在最终输出行中显示的字符串
  124. oo_error_this_iteration = None # 存储本轮OpenOcean的错误信息
  125. gate_error_this_iteration = None # 存储本轮Gate.io的错误信息
  126. # --- 1. OpenOcean BSC 查询 ---
  127. oo_price_data = get_openocean_price_bsc(
  128. IN_TOKEN_ADDRESS_BSC,
  129. OUT_TOKEN_ADDRESS_BSC,
  130. AMOUNT_TO_QUERY_HUMAN
  131. )
  132. if "error" not in oo_price_data:
  133. oo_rate_usdc_per_usdt = oo_price_data['price_in_per_out']
  134. oo_display_str = f"{oo_rate_usdc_per_usdt:.6f}" # 格式化价格
  135. else:
  136. oo_error_this_iteration = oo_price_data['error'] # 记录错误信息
  137. # --- 2. Gate.io 现货查询 ---
  138. gate_price_data = get_gateio_spot_price(GATEIO_SPOT_PAIR)
  139. if "error" not in gate_price_data:
  140. gate_rate_usdt_per_usdc = gate_price_data['price_base_in_quote']
  141. if gate_rate_usdt_per_usdc is not None and gate_rate_usdt_per_usdc > 0:
  142. # 转换 Gate.io 的汇率方向为 1 USDT = Y USDC (Y = 1 / X)
  143. gate_rate_usdc_per_usdt_inverted = gate_rate_usdt_per_usdc
  144. gate_display_str = f"{gate_rate_usdc_per_usdt_inverted:.6f}" # 格式化价格
  145. elif gate_rate_usdt_per_usdc is not None: # 价格为0或负数
  146. gate_error_this_iteration = f"Gate.io 无效汇率 ({gate_rate_usdt_per_usdc})"
  147. gate_display_str = "Invalid" # 在行内显示为无效
  148. # else: price_base_in_quote is None, error already handled by "error" key check
  149. else:
  150. gate_error_this_iteration = gate_price_data['error'] # 记录错误信息
  151. # --- 3. 计算价差百分比 (仅当两边价格都有效时) ---
  152. if oo_rate_usdc_per_usdt is not None and gate_rate_usdc_per_usdt_inverted is not None:
  153. if gate_rate_usdc_per_usdt_inverted != 0: # 避免除以零
  154. # 价差 = OpenOcean汇率 - Gate.io转换后汇率
  155. difference = oo_rate_usdc_per_usdt - gate_rate_usdc_per_usdt_inverted
  156. # 价差百分比 = (价差 / Gate.io转换后汇率) * 100
  157. # 您可以根据需要选择以哪个价格为基准计算百分比,这里以 Gate.io 为基准
  158. percentage_diff = (difference / gate_rate_usdc_per_usdt_inverted) * 100
  159. diff_percentage_display_str = f"{percentage_diff:+.4f}%" # 显示正负号和小数点后4位
  160. else:
  161. diff_percentage_display_str = "Gate.io汇率为0" # Gate.io 汇率为0,无法计算百分比
  162. # --- 4. 打印错误信息 (如果本轮有错误发生) ---
  163. # 这些错误会打印在数据行的上方,以便用户了解具体问题
  164. if oo_error_this_iteration:
  165. print(f"[错误] OpenOcean: {oo_error_this_iteration}")
  166. if gate_error_this_iteration:
  167. print(f"[错误] Gate.io: {gate_error_this_iteration}")
  168. # --- 5. 组合打印在一行 ---
  169. # 使用格式化字符串确保列对齐
  170. print(f"{oo_display_str:<25} | {gate_display_str:<25} | {diff_percentage_display_str:<15}")
  171. time.sleep(1) # 等待1秒
  172. except KeyboardInterrupt: # 允许用户通过 Ctrl+C 来停止脚本
  173. print("\n轮询停止。")
  174. if __name__ == "__main__":
  175. decimal.getcontext().prec = 36 # 设置 decimal 的计算精度
  176. main()