Browse Source

套利流程50%。

skyfffire 5 months ago
parent
commit
33e0addbb9
4 changed files with 157 additions and 48 deletions
  1. 31 18
      arbitrage_system.py
  2. 21 18
      price_checker_ok.py
  3. 93 0
      submit_process_demo.py
  4. 12 12
      templates/index_plotly_dynamic_ok.html

+ 31 - 18
arbitrage_system.py

@@ -1,7 +1,10 @@
 import decimal
 import threading
 import uuid # 用于生成唯一的流程ID
-from datetime import datetime, timezone
+import datetime
+import logging
+# 配置日志
+logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
 
 from flask import Flask, request, jsonify
 from web3_py_client import EthClient # 你特定的客户端
@@ -28,36 +31,46 @@ global_nonce_USER_WALLET = 0 # 从 USER_WALLET 发送交易的全局 Nonce
 nonce_lock_USER_WALLET = threading.Lock() # USER_WALLET Nonce 的锁
 
 try:
-    if web3.provider: # 检查 web3 是否已某种程度初始化
+    if web3.w3.provider: # 检查 web3 是否已某种程度初始化
         # 这个全局 nonce 应该小心初始化。
         # 如果 price_checker 发送交易,它应该管理 USER_WALLET 的 tx 的 nonce。
         # 这个服务器的 global_nonce 是针对它自己可能创建的 tx。
         # 暂时假设传入的 TX 具有其 nonce 或 price_checker 处理了它。
-        global_nonce_USER_WALLET = web3.eth.get_transaction_count(USER_WALLET, 'latest')
-        print(f"如果服务器要创建交易,{USER_WALLET} 的初始 nonce 将在此处获取。")
+        global_nonce_USER_WALLET = web3.w3.eth.get_transaction_count(USER_WALLET, 'latest')
+        logging.info(f"如果服务器要创建交易,{USER_WALLET} 的初始 nonce 将在此处获取。")
     else:
-        print("Web3 提供者未连接, USER_WALLET 的全局 nonce 未初始化。")
+        logging.info("Web3 提供者未连接, USER_WALLET 的全局 nonce 未初始化。")
 except Exception as e:
-    print(f"初始化 {USER_WALLET} 的全局 nonce 时出错: {e}")
+    logging.info(f"初始化 {USER_WALLET} 的全局 nonce 时出错: {e}")
 
 # --- Flask 应用 ---
 app = Flask(__name__)
 
-def get_current_timestamp_iso():
-    """获取当前 UTC 时间的 ISO 格式字符串"""
-    return datetime.now(timezone.utc).isoformat()
+def get_formatted_timestamp():
+    """
+    获取指定格式的时间戳: YYYY-MM-DD HH:MM:SS,ms
+    例如: 2025-05-16 14:44:09,324
+    """
+    now = datetime.datetime.now()
+    # 格式化日期和时间部分
+    timestamp_str = now.strftime("%Y-%m-%d %H:%M:%S")
+    # 获取毫秒部分,并格式化为3位数字
+    milliseconds = now.microsecond // 1000
+    milliseconds_str = f"{milliseconds:03d}"
+    # 组合最终格式
+    return f"{timestamp_str},{milliseconds_str}"
 
 def add_state_flow_entry(process_item, state_name, msg, status_val="pending"):
     """辅助函数,用于向 stateFlow 列表添加条目。"""
     entry = {
         "stateName": state_name, # 状态名称
-        "timestamp": get_current_timestamp_iso(), # 时间戳
+        "timestamp": get_formatted_timestamp(), # 时间戳
         "msg": msg, # 消息
         "status": status_val # 状态值: "pending", "success", "fail", "skipped"
     }
     process_item["stateFlow"].append(entry)
     process_item["currentState"] = state_name # 更新整体状态
-    print(f"[流程 {process_item.get('id', 'N/A')}][{state_name}]: {msg} (状态: {status_val})")
+    logging.info(f"[流程 {process_item.get('id', 'N/A')}][{state_name}]: {msg} (状态: {status_val})")
 
 def arbitrage_process_flow(process_item):
     """
@@ -71,7 +84,7 @@ def arbitrage_process_flow(process_item):
     try:
         pass
     except Exception as e:
-        print(f"流程 {process_id} ({symbol}) 的套利过程中出错: {e}")
+        logging.info(f"流程 {process_id} ({symbol}) 的套利过程中出错: {e}")
         # stateFlow 中的最后一个状态应反映错误点
         process_item['finalStatus'] = "FAILED" # 最终状态:失败
         # 如果尚未由特定步骤的失败设置
@@ -89,14 +102,14 @@ def arbitrage_process_flow(process_item):
                     break
             if item_to_move:
                 history_process_list.append(item_to_move)
-                print(f"流程 {process_id} 已移至历史记录。")
+                logging.info(f"流程 {process_id} 已移至历史记录。")
             else:
-                print(f"警告: 流程 {process_id} 未在 processing_list 中找到,无法移至历史记录。")
+                logging.info(f"警告: 流程 {process_id} 未在 processing_list 中找到,无法移至历史记录。")
 
         # 更新此交易对的最后处理区块信息 (可选, 用于防止立即重新套利)
         # current_block = web3.eth.get_block('latest')['number']
         # last_process_info[symbol] = {"block": current_block, "timestamp": time.time()}
-        # print(f"已更新 {symbol} 的最后处理信息至区块 {current_block}")
+        # logging.info(f"已更新 {symbol} 的最后处理信息至区块 {current_block}")
 
 @app.route('/submit_process', methods=['POST'])
 def handle_submit_process():
@@ -130,7 +143,7 @@ def handle_submit_process():
         process_id = str(uuid.uuid4()) # 生成唯一流程ID
         process_item = {
             "id": process_id,
-            "creationTime": get_current_timestamp_iso(), # 创建时间
+            "creationTime": get_formatted_timestamp(), # 创建时间
             "tx": data['tx'], # 交易详情,应包含 rawTransaction
             "profit": str(profit), # 利润 (字符串存储)
             "profitLimit": str(profit_limit), # 利润阈值 (字符串存储)
@@ -150,7 +163,7 @@ def handle_submit_process():
             processing_list.append(process_item)
 
         last_process_info[symbol] = current_block
-        print(f"已更新 {symbol} 的最后处理信息至区块 {current_block}")
+        logging.info(f"已更新 {symbol} 的最后处理信息至区块 {current_block}")
 
         # 在新线程中开始套利过程
         arb_thread = threading.Thread(target=arbitrage_process_flow, args=(process_item,), daemon=True)
@@ -186,5 +199,5 @@ def get_status():
 
 if __name__ == "__main__":
     # 如果此服务器为其自身的交易管理 global_nonce_USER_WALLET,则在此处初始化
-    print("启动 Flask 套利执行服务器...")
+    logging.info("启动 Flask 套利执行服务器...")
     app.run(host='0.0.0.0', port=5002, debug=False) # 使用与 price_checker 不同的端口

+ 21 - 18
price_checker_ok.py

@@ -16,20 +16,22 @@ from plotly.utils import PlotlyJSONEncoder
 CHAIN_ID = 1
 IN_TOKEN_ADDRESS = '0xdAC17F958D2ee523a2206206994597C13D831ec7' # USDT on Ethereum
 IN_TOKEN_DECIMALS = 6
-AMOUNT_TO_QUERY_OPENOCEAN_IN = decimal.Decimal('1000') # 1000 USDT
-OUT_TOKEN_ADDRESS_TARGET_ETH = '0xf816507E690f5Aa4E29d164885EB5fa7a5627860' # RATO on Ethereum
-USER_WALLET_ADDRESS = '0xb1f33026db86a86372493a3b124d7123e9045bb4' # 示例地址
+IN_AMOUNT_TO_QUERY = decimal.Decimal('20')
+OUT_TOKEN_ADDRESS = '0xf816507E690f5Aa4E29d164885EB5fa7a5627860' # RATO on Ethereum
+USER_WALLET = '0xb1f33026db86a86372493a3b124d7123e9045bb4'
+USER_EXCHANGE_WALLET = '0xc71835a042F4d870B0F4296cc89cAeb921a9f3DA'
 SLIPPAGE = 1
-MEXC_TARGET_PAIR_USDT = 'RATO_USDT' # MEXC 现货交易对 TARGET/USDT
+MEXC_TARGET_PAIR_USDT = 'RATO_USDT' # MEXC 现货交易对
+
 proxies = None # {'http': 'http://proxy_url:port', 'https': 'http://proxy_url:port'}
 decimal.getcontext().prec = 36
 
-# --- 链上价格获取函数 (OpenOcean) ---
+# --- 链上价格获取函数 (Okx) ---
 # 返回: price_base_per_target (例如 USDT per RATO)
-def get_chain_price_vs_target_currency(chain_id, in_token_addr, out_token_addr, amount, in_token_decimals, slippage, user_wallet_addr):
+def get_chain_price_vs_target_currency(chain_id, in_token_addr, out_token_addr, amount, in_token_decimals, slippage, user_wallet_addr, user_exchange_wallet_addr):
     try:
         in_token_amount = amount * (10 ** in_token_decimals)
-        data = ok_chain_client.swap(chain_id, in_token_amount, in_token_addr, out_token_addr, slippage, user_wallet_addr)
+        data = ok_chain_client.swap(chain_id, in_token_amount, in_token_addr, out_token_addr, slippage, user_wallet_addr, user_exchange_wallet_addr)
 
         if data.get('code') == '0' and data.get('data'):
             d = data['data'][0]
@@ -44,9 +46,9 @@ def get_chain_price_vs_target_currency(chain_id, in_token_addr, out_token_addr,
         else:
             pprint.pprint(data)
             return {
-                "error": f"OpenOcean API错误({chain_id}) - Code:{data.get('code', 'N/A')}, Msg:{data.get('msg', data.get('message', 'N/A')) if isinstance(data, dict) else '格式错误'}"}
+                "error": f"Okx API错误({chain_id}) - Code:{data.get('code', 'N/A')}, Msg:{data.get('msg', data.get('message', 'N/A')) if isinstance(data, dict) else '格式错误'}"}
     except Exception as e:
-        return {"error": f"OpenOcean ({chain_id})请求错误: {e}"}
+        return {"error": f"Okx ({chain_id})请求错误: {e}"}
 
 # MEXC 现货 (获取 目标代币/USDT 的 bid 价格)
 # 返回: price_target_per_usdt_bid1 (例如 RATO per USDT)
@@ -100,15 +102,16 @@ def update_data_for_plotly_and_table():
         fetch_time_full = time.strftime("%Y-%m-%d %H:%M:%S")
         fetch_time_chart = time.strftime("%H:%M:%S")
 
-        # 1. OpenOcean: price_base_per_target (e.g., USDT / RATO)
+        # 1. Okx: price_base_per_target (e.g., USDT / RATO)
         oo_data = get_chain_price_vs_target_currency(
             CHAIN_ID,
             IN_TOKEN_ADDRESS,
-            OUT_TOKEN_ADDRESS_TARGET_ETH,
-            AMOUNT_TO_QUERY_OPENOCEAN_IN,
+            OUT_TOKEN_ADDRESS,
+            IN_AMOUNT_TO_QUERY,
             IN_TOKEN_DECIMALS,
             SLIPPAGE,
-            USER_WALLET_ADDRESS
+            USER_WALLET,
+            USER_EXCHANGE_WALLET
         )
         oo_price_usdt_per_target = oo_data.get("price_base_per_target") # USDT/TARGET
         oo_err = oo_data.get("error")
@@ -149,7 +152,7 @@ def update_data_for_plotly_and_table():
 
         ok_oo = 'OK' if oo_price_usdt_per_target else 'F'
         ok_mexc_bid = 'OK' if mexc_price_usdt_per_target_bid1 else 'F' # Check converted price
-        print(f"{fetch_time_chart} Fetch | OO:{ok_oo} | MEXC Bid1 (USDT/{TARGET_ASSET_SYMBOL}):{ok_mexc_bid}")
+        print(f"{fetch_time_chart} Fetch | OO:{ok_oo} | MEXC Bid1 ({TARGET_ASSET_SYMBOL}/USDT):{ok_mexc_bid}")
         time.sleep(REFRESH_INTERVAL_SECONDS)
 
 @app.route('/')
@@ -191,7 +194,7 @@ def get_plotly_chart_data():
         # Price Chart: OO (USDT/TARGET) vs MEXC_converted (USDT/TARGET)
         fig_prices = go.Figure()
         fig_prices.add_trace(go.Scatter(x=times, y=[p['oo_price_usdt_per_target'] for p in points], mode='lines',
-                                        name=f'OpenOcean ({display_base_asset}/{display_target_asset})',
+                                        name=f'Okx ({display_base_asset}/{display_target_asset})',
                                         line=dict(color='rgb(75, 192, 192)'),
                                         hovertemplate=f'<b>OO</b><br>价格: %{{y:.8f}} {display_base_asset}<extra></extra>'))
         fig_prices.add_trace(go.Scatter(x=times, y=[p['mexc_price_usdt_per_target_bid1'] for p in points], mode='lines',
@@ -225,9 +228,9 @@ def get_plotly_chart_data():
 if __name__ == "__main__":
     print("应用启动...")
     print(f"目标资产: {TARGET_ASSET_SYMBOL}")
-    print(f"计价货币 (OpenOcean输入 / MEXC交易对基础): {BASE_CURRENCY_SYMBOL}") # Should be USDT
-    print(f"OpenOcean 链上查询: {AMOUNT_TO_QUERY_OPENOCEAN_IN} {BASE_CURRENCY_SYMBOL} -> {TARGET_ASSET_SYMBOL} (ETH chain, using {IN_TOKEN_ADDRESS[-6:]} for {BASE_CURRENCY_SYMBOL} and {OUT_TOKEN_ADDRESS_TARGET_ETH[-6:]} for {TARGET_ASSET_SYMBOL})")
-    print(f"MEXC 现货交易对: {MEXC_TARGET_PAIR_USDT} (价格将被转换为 {BASE_CURRENCY_SYMBOL}/{TARGET_ASSET_SYMBOL} 进行比较)")
+    print(f"计价货币 (Okx输入 / MEXC交易对基础): {BASE_CURRENCY_SYMBOL}") # Should be USDT
+    print(f"链上查询: {IN_AMOUNT_TO_QUERY} {BASE_CURRENCY_SYMBOL} -> {TARGET_ASSET_SYMBOL} (ETH chain, using {IN_TOKEN_ADDRESS[-6:]} for {BASE_CURRENCY_SYMBOL} and {OUT_TOKEN_ADDRESS[-6:]} for {TARGET_ASSET_SYMBOL})")
+    print(f"MEXC 现货交易对: {MEXC_TARGET_PAIR_USDT} (价格将被转换为 {TARGET_ASSET_SYMBOL}/{BASE_CURRENCY_SYMBOL} 进行比较)")
     data_thread = threading.Thread(target=update_data_for_plotly_and_table, daemon=True)
     data_thread.start()
     print(f"Flask 服务将在 http://0.0.0.0:5001 上运行 (刷新间隔: {REFRESH_INTERVAL_SECONDS}s)")

+ 93 - 0
submit_process_demo.py

@@ -0,0 +1,93 @@
+import requests
+import json
+import time
+from decimal import Decimal # 使用 Decimal 来表示精确的金额和价差
+
+# --- 配置 arb_executor.py 的 HTTP 地址和端口 ---
+ARB_EXECUTOR_URL = "http://localhost:5002/submit_process"
+
+# --- 模拟的套利机会数据 ---
+# 这是一个示例,实际数据应来自你的 price_checker 逻辑
+def create_mock_arbitrage_data():
+    import ok_chain_client
+    
+    CHAIN_ID = 1
+    IN_TOKEN_ADDRESS = '0xdAC17F958D2ee523a2206206994597C13D831ec7' # USDT on Ethereum
+    IN_TOKEN_DECIMALS = 6
+    IN_AMOUNT_TO_QUERY = Decimal('20')
+    OUT_TOKEN_ADDRESS = '0xf816507E690f5Aa4E29d164885EB5fa7a5627860' # RATO on Ethereum
+    USER_WALLET = '0xb1f33026db86a86372493a3b124d7123e9045bb4'
+    USER_EXCHANGE_WALLET = '0xc71835a042F4d870B0F4296cc89cAeb921a9f3DA'
+    SLIPPAGE = 1
+    MEXC_TARGET_PAIR_USDT = 'RATO_USDT' # MEXC 现货交易对
+
+    # 询价,注意!!!这里直接把交易所地址当收款方,省去transfer的流程
+    data = ok_chain_client.swap(CHAIN_ID, 
+                                IN_AMOUNT_TO_QUERY * (10 ** IN_TOKEN_DECIMALS), 
+                                IN_TOKEN_ADDRESS, 
+                                OUT_TOKEN_ADDRESS, 
+                                SLIPPAGE, 
+                                USER_WALLET,
+                                USER_EXCHANGE_WALLET,  # 这里直接把交易所地址当收款方,省去transfer的流程!!!
+                                )
+
+    d = data['data'][0]
+    tx = d['tx']
+    # 构造提交给 arb_executor 的数据体
+    data = {
+        "tx": tx,
+        "profit": str(0.02),
+        "profitLimit": str(0.01),
+        "symbol": MEXC_TARGET_PAIR_USDT,
+        "fromToken": IN_TOKEN_ADDRESS,
+        "fromTokenAmountHuman": IN_AMOUNT_TO_QUERY,   
+        "fromTokenDecimal": IN_TOKEN_DECIMALS,                  
+        "toToken": OUT_TOKEN_ADDRESS
+    }
+    return data
+
+# --- 发送请求函数 ---
+def submit_arbitrage_process(arbitrage_data):
+    """
+    向 arb_executor 服务提交套利处理请求。
+    """
+    print(f"正在提交套利数据到 {ARB_EXECUTOR_URL}")
+
+    try:
+        # 发送 POST 请求
+        response = requests.post(ARB_EXECUTOR_URL, json=arbitrage_data)
+
+        # 检查响应状态码
+        if response.status_code == 201:
+            print("\n请求成功! 套利流程已启动。")
+        elif response.status_code == 200:
+             print("\n请求接收成功,但未达到利润阈值,未启动套利流程。")
+        elif response.status_code == 400:
+             print("\n请求失败! 无效的请求数据。")
+        else:
+            print(f"\n请求失败! 状态码: {response.status_code}")
+
+        # 打印响应体
+        try:
+            print("响应体:")
+            print(json.dumps(response.json(), indent=4))
+        except json.JSONDecodeError:
+            print("响应体不是有效的 JSON:")
+            print(response.text)
+
+    except requests.exceptions.ConnectionError as e:
+        print(f"\n连接错误: 无法连接到 {ARB_EXECUTOR_URL}。请确保 arb_executor.py 正在运行。")
+        print(f"错误详情: {e}")
+    except Exception as e:
+        print(f"\n发送请求时发生未知错误: {e}")
+
+# --- 主执行逻辑 ---
+if __name__ == "__main__":
+    print("--- 模拟 Price Checker 发现套利机会 ---")
+
+    # 模拟一个达到利润阈值的套利机会
+    arb_opportunity_met = create_mock_arbitrage_data()
+
+    submit_arbitrage_process(arb_opportunity_met)
+
+    print("\n模拟完成。请检查 arb_executor.py 的控制台输出和 /processing, /history 端点查看结果。")

+ 12 - 12
templates/index_plotly_dynamic_ok.html

@@ -3,7 +3,7 @@
 <head>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>{{ base_asset }}/{{ target_asset }} 价格监控 (Plotly)</title> <!-- 注意这里base_asset在前,符合 USDT/RATO -->
+    <title>{{ target_asset }} / {{ base_asset }}价格监控 (Plotly)</title> 
     <script src='https://cdn.plot.ly/plotly-latest.min.js'></script>
     <style>
         body { font-family: Arial, sans-serif; margin: 20px; background-color: #f4f4f4; color: #333; }
@@ -25,8 +25,8 @@
 </head>
 <body>
     <div class="container">
-        <!-- 主标题显示 USDT/RATO (base/target) -->
-        <h1 id="main-title">{{ base_asset }}/{{ target_asset }} 多平台价格监控</h1>
+        <!-- 主标题显示 RATO/USDT (base/target) -->
+        <h1 id="main-title">{{ target_asset }}/{{ base_asset }} 多平台价格监控</h1>
         <div class="controls-container"> <button id="pause-resume-button" class="control-button">暂停刷新</button> </div>
         <table>
             <thead>
@@ -34,18 +34,18 @@
                 <tr><th>平台</th><th>价格 ({{ base_asset }})</th><th class="status-cell">状态/错误</th></tr>
             </thead>
             <tbody>
-                <tr> <!-- OpenOcean显示 USDT/RATO -->
-                    <td class="platform-name">OpenOcean ({{ base_asset }}/{{ target_asset }})</td>
+                <tr> <!-- OpenOcean显示 RATO/USDT -->
+                    <td class="platform-name">OpenOcean ({{ target_asset }}/{{ base_asset }})</td>
                     <td id="oo-price-usdt-per-target">加载中...</td><td id="oo-status" class="status-cell"></td>
                 </tr>
-                <tr> <!-- MEXC显示 USDT/RATO (转换后) -->
-                    <td class="platform-name" id="mexc-label">MEXC 现货 Bid1 ({{ mexc_pair_usdt }} → {{ base_asset }}/{{ target_asset }})</td>
+                <tr> <!-- MEXC显示 RATO/USDT (转换后) -->
+                    <td class="platform-name" id="mexc-label">MEXC 现货 Bid1 ({{ mexc_pair_usdt }} → {{ target_asset }}/{{ base_asset }})</td>
                     <td id="mexc-price-usdt-per-target-bid1">加载中...</td>
                     <td id="mexc-status" class="status-cell"></td>
                 </tr>
             </tbody>
         </table>
-        <h2>价差百分比 (基于 {{ base_asset }}/{{ target_asset }} 价格)</h2>
+        <h2>价差百分比 (基于 {{ target_asset }}/{{ base_asset }} 价格)</h2>
         <table>
              <thead><tr><th>对比</th><th>价差 (%)</th></tr></thead>
             <tbody>
@@ -56,12 +56,12 @@
     </div>
 
     <div class="container">
-        <h2 id="price-chart-title">{{ base_asset }}/{{ target_asset }} 价格历史曲线</h2>
+        <h2 id="price-chart-title">{{ target_asset }}/{{ base_asset }} 价格历史曲线</h2>
         <div id='priceHistoryChartPlotly' class="chart-container"></div>
         <div id="price-chart-status" class="status-line">加载价格图表...</div>
     </div>
     <div class="container">
-        <h2 id="diff-chart-title">{{ base_asset }}/{{ target_asset }} 价差百分比历史曲线</h2>
+        <h2 id="diff-chart-title">{{ target_asset }}/{{ base_asset }} 价差百分比历史曲线</h2>
         <div id='diffPercentageChartPlotly' class="chart-container diff-chart-container"></div>
         <div id="diff-chart-status" class="status-line">加载价差图表...</div>
     </div>
@@ -176,7 +176,7 @@
                         pricePlotInitialized = true;
                         priceChartDiv.on('plotly_relayout', (eventData) => { syncPlotlyXAxes(priceChartDiv, diffChartDiv, eventData); });
                     }
-                    priceChartStatusDiv.textContent = `价格图表 (${BASE_ASSET_JS}/${TARGET_ASSET_JS}) 更新于: ${new Date().toLocaleTimeString()}`;
+                    priceChartStatusDiv.textContent = `价格图表 (${TARGET_ASSET_JS}/${BASE_ASSET_JS}) 更新于: ${new Date().toLocaleTimeString()}`;
                 } else {
                     priceChartStatusDiv.textContent = "错误: 价格图表数据无效。";
                 }
@@ -191,7 +191,7 @@
                         diffPlotInitialized = true;
                         diffChartDiv.on('plotly_relayout', (eventData) => { syncPlotlyXAxes(diffChartDiv, priceChartDiv, eventData); });
                     }
-                    diffChartStatusDiv.textContent = `价差图表 (${BASE_ASSET_JS}/${TARGET_ASSET_JS}) 更新于: ${new Date().toLocaleTimeString()}`;
+                    diffChartStatusDiv.textContent = `价差图表 (${TARGET_ASSET_JS}/${BASE_ASSET_JS}) 更新于: ${new Date().toLocaleTimeString()}`;
                 } else {
                      diffChartStatusDiv.textContent = "错误: 价差图表数据无效。";
                 }