Jelajahi Sumber

一些调整,

skyfffire 5 bulan lalu
induk
melakukan
50a79de499
4 mengubah file dengan 114 tambahan dan 70 penghapusan
  1. 1 1
      arbitrage_process.py
  2. 6 5
      mexc_client.py
  3. 79 36
      monitor.html
  4. 28 28
      price_checker_ok.py

+ 1 - 1
arbitrage_process.py

@@ -477,7 +477,7 @@ class ArbitrageProcess:
                     last_deposit_state = deposit
 
                     logging.info(f"等待资产在交易所到账...({deposit['confirmTimes']}/{deposit['unlockConfirm']})")
-                    if deposit['confirmTimes'] >= deposit['unlockConfirm']:
+                    if Decimal(deposit['confirmTimes']) >= Decimal(deposit['unlockConfirm']):
                         is_arrived = True
 
                 if is_arrived:

+ 6 - 5
mexc_client.py

@@ -568,17 +568,18 @@ if __name__ == '__main__':
             #     'coin': 'USDT',
             #     'netWork': 'ETH',
             #     'address': '0xb1f33026db86a86372493a3b124d7123e9045bb4',
-            #     'amount': 301.5
+            #     'amount': 155
             # }
             # withdraw_rst = client.wallet.post_withdraw(withdraw_params)
             # print(f"  提笔响应:{withdraw_rst}")
 
 
 
-            print("\n获取提币历史信息...")
-            withdraw_list = client.wallet.get_withdraw_list()
-            print("历史信息:")
-            print(f"  {withdraw_list[0]}")
+            # print("\n获取提币历史信息...")
+            # withdraw_list = client.wallet.get_withdraw_list()
+            # print("历史信息:")
+            # for withdraw in withdraw_list:
+            #     print(f"  {withdraw}")
         except requests.exceptions.RequestException as e:
             print(f"  私有 API 请求期间出错: {e}")
             if e.response is not None:

+ 79 - 36
monitor.html

@@ -12,19 +12,16 @@
             background-color: #f4f7f6; /* Lighter background for the page */
         }
         .task-card {
-            margin-bottom: 15px; /* Slightly reduced margin */
-            /* border: 1px solid #ddd; */ /* Border is handled by Bootstrap card */
-            /* border-radius: 5px; */ /* Bootstrap card has rounded corners */
+            margin-bottom: 15px; 
         }
-        .task-card .card-header { /* Styling for the clickable header */
-            padding: 0; /* Remove padding to let button fill it */
+        .task-card .card-header { 
+            padding: 0; 
         }
         .task-card .card-header button {
-            /* text-decoration: none !important; */ /* Remove underline from btn-link if used */
-            color: #333; /* Darker text for better readability on light bg */
+            color: #333; 
         }
         .task-card .card-header button:hover {
-            background-color: #e9ecef; /* Slightly darker on hover */
+            background-color: #e9ecef; 
         }
         .state-flow-table th, .state-flow-table td {
             font-size: 0.9em;
@@ -38,10 +35,10 @@
         .log-msg {
             white-space: pre-wrap;
             word-break: break-all;
-            max-height: 150px; /* Increased max height for logs */
+            max-height: 150px; 
             overflow-y: auto;
             font-size: 0.85em;
-            background-color: #fdfdfd; /* Slightly off-white for log background */
+            background-color: #fdfdfd; 
             padding: 8px;
             border: 1px solid #eee;
             border-radius: 3px;
@@ -56,12 +53,18 @@
             margin-bottom: 0;
             margin-right: 0.5rem;
         }
+        .button-group {
+            margin-bottom: 1.5rem; /* Increased margin for button group */
+        }
     </style>
 </head>
 <body>
     <div class="container-fluid">
         <h1 class="mb-4">套利流程监控</h1>
-        <button id="refreshButton" class="btn btn-primary mb-4">刷新数据</button>
+        <div class="button-group">
+            <button id="refreshButton" class="btn btn-primary mr-2">手动刷新</button>
+            <button id="toggleAutoRefreshButton" class="btn btn-secondary">暂停自动刷新</button>
+        </div>
         
         <div class="row">
             <div class="col-md-6">
@@ -86,8 +89,15 @@
     </div>
 
     <script>
-        const API_BASE_URL = 'http://localhost:5002'; // 修改为你的 Flask API 地址
+        const API_BASE_URL = 'http://localhost:5002';
+        const REFRESH_INTERVAL_MS = 10000; // 自动刷新间隔:10秒
+        let autoRefreshIntervalId = null;
+        let isAutoRefreshPaused = false;
 
+        const refreshButton = document.getElementById('refreshButton');
+        const toggleAutoRefreshButton = document.getElementById('toggleAutoRefreshButton');
+
+        // ... (getStatusClass, status_includes_any, getStatusBadgeClass, formatStateFlow, createTaskCard 函数保持不变) ...
         function getStatusClass(status) { // For text color in stateFlow
             status = status ? status.toLowerCase() : 'info';
             if (status.includes('success') || status.includes('completed')) return 'status-success';
@@ -146,17 +156,15 @@
         }
 
         function createTaskCard(task, listIdForAccordion) {
-            // Generate a safer ID for HTML attributes, especially if task.id could have unusual chars
             const safeId = `task-${task.id.replace(/[^a-zA-Z0-9-_]/g, '')}`;
             const headingId = `heading-${safeId}`;
             const collapseId = `collapse-${safeId}`;
 
             const card = document.createElement('div');
-            card.className = 'card task-card shadow-sm'; // Added shadow-sm for a bit of depth
+            card.className = 'card task-card shadow-sm';
 
-            // Card Header (Clickable Toggle)
             const cardHeader = document.createElement('div');
-            cardHeader.className = 'card-header'; // Bootstrap handles padding now
+            cardHeader.className = 'card-header';
             cardHeader.id = headingId;
             cardHeader.innerHTML = `
                 <h2 class="mb-0">
@@ -174,12 +182,11 @@
                 </h2>
             `;
 
-            // Card Body (Collapsible Content)
             const collapseDiv = document.createElement('div');
             collapseDiv.id = collapseId;
-            collapseDiv.className = 'collapse'; // Default collapsed
+            collapseDiv.className = 'collapse';
             collapseDiv.setAttribute('aria-labelledby', headingId);
-            collapseDiv.setAttribute('data-parent', `#${listIdForAccordion}`); // For accordion behavior
+            collapseDiv.setAttribute('data-parent', `#${listIdForAccordion}`);
 
             collapseDiv.innerHTML = `
                 <div class="card-body">
@@ -204,7 +211,13 @@
                 console.error(`Element not found: ${targetElementId} or ${countElementId}`);
                 return;
             }
-            targetElement.innerHTML = '<p class="text-muted"><em>加载中...</em></p>';
+            
+            // 如果不是首次加载 (即 targetElement.innerHTML 为 '加载中...'),则不显示 "加载中..." 文本,避免闪烁
+            const isInitialLoad = targetElement.querySelector('p') && targetElement.querySelector('p').textContent.includes('加载中');
+            if (isInitialLoad) {
+                 targetElement.innerHTML = '<p class="text-muted"><em>加载中...</em></p>';
+            }
+
             try {
                 const response = await fetch(`${API_BASE_URL}${endpoint}`);
                 if (!response.ok) {
@@ -212,27 +225,19 @@
                 }
                 const data = await response.json();
 
-                // 对数据按 creationTime 倒序排序
                 if (data && data.length > 0) {
                     data.sort((a, b) => {
-                        // 假设 creationTime 是 'YYYY-MM-DD HH:MM:SS,ms' 格式
-                        // 如果不是有效字符串,则将其视为较早的时间
                         const timeA = a.creationTime || '0'; 
                         const timeB = b.creationTime || '0';
-                        if (timeA < timeB) {
-                            return 1; // b 在 a 之前,所以 b 应该排在前面(对于倒序)
-                        }
-                        if (timeA > timeB) {
-                            return -1; // a 在 b 之前,所以 a 应该排在前面(对于倒序)
-                        }
-                        return 0; // 时间相同
+                        if (timeA < timeB) { return 1; }
+                        if (timeA > timeB) { return -1; }
+                        return 0;
                     });
                 }
                 
                 targetElement.innerHTML = ''; 
                 if (data && data.length > 0) {
                     data.forEach(task => {
-                        // Pass targetElementId to createTaskCard for the data-parent attribute of accordion
                         targetElement.appendChild(createTaskCard(task, targetElementId));
                     });
                 } else {
@@ -242,25 +247,63 @@
 
             } catch (error) {
                 console.error(`获取 ${endpoint} 数据失败:`, error);
-                targetElement.innerHTML = `<p class="text-danger">加载数据失败: ${error.message}</p>`;
+                if (isInitialLoad) { // 只有在初始加载失败时才替换整个内容
+                    targetElement.innerHTML = `<p class="text-danger">加载数据失败: ${error.message}</p>`;
+                } else {
+                    console.warn("获取更新数据失败,保留旧数据。") // 或者可以加一个小的错误提示
+                }
                 countElement.textContent = 'ERR';
             }
         }
 
         function refreshAllData() {
+            console.log("Refreshing data..."); // 用于调试
             fetchData('/processing', 'processing-list', 'processing-count');
             fetchData('/history', 'history-list', 'history-count');
         }
 
-        document.getElementById('refreshButton').addEventListener('click', refreshAllData);
+        function startAutoRefresh() {
+            if (autoRefreshIntervalId) { // Clear existing interval if any
+                clearInterval(autoRefreshIntervalId);
+            }
+            autoRefreshIntervalId = setInterval(refreshAllData, REFRESH_INTERVAL_MS);
+            isAutoRefreshPaused = false;
+            toggleAutoRefreshButton.textContent = '暂停自动刷新';
+            toggleAutoRefreshButton.classList.remove('btn-success');
+            toggleAutoRefreshButton.classList.add('btn-secondary');
+            console.log("自动刷新已启动。 Interval ID:", autoRefreshIntervalId);
+        }
+
+        function stopAutoRefresh() {
+            if (autoRefreshIntervalId) {
+                clearInterval(autoRefreshIntervalId);
+                autoRefreshIntervalId = null; // Set to null to indicate it's stopped
+                console.log("自动刷新已暂停。");
+            }
+            isAutoRefreshPaused = true;
+            toggleAutoRefreshButton.textContent = '恢复自动刷新';
+            toggleAutoRefreshButton.classList.remove('btn-secondary');
+            toggleAutoRefreshButton.classList.add('btn-success');
+        }
+
+        refreshButton.addEventListener('click', refreshAllData);
+
+        toggleAutoRefreshButton.addEventListener('click', () => {
+            if (isAutoRefreshPaused) {
+                startAutoRefresh();
+                refreshAllData(); // Resume and immediately refresh
+            } else {
+                stopAutoRefresh();
+            }
+        });
 
         document.addEventListener('DOMContentLoaded', () => {
-            refreshAllData();
-            // setInterval(refreshAllData, 10000); // 每10秒刷新一次 (可选)
+            refreshAllData(); // Initial load
+            startAutoRefresh(); // Start auto-refresh by default
         });
     </script>
     <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
     <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/dist/umd/popper.min.js"></script>
     <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
 </body>
-</html>
+</html>

+ 28 - 28
price_checker_ok.py

@@ -21,7 +21,7 @@ ARB_EXECUTOR_URL = "http://localhost:5002/submit_process"
 # --- 配置部分 ---
 IN_AMOUNT_TO_QUERY = decimal.Decimal('350')
 EXCHANGE_OUT_AMOUNT = decimal.Decimal('5000000')
-PROFIT_LIMIT = 0.03                                                 # 触发交易的利润阈值
+PROFIT_LIMIT = 0.04                                                 # 触发交易的利润阈值
 IN_TOKEN_ADDRESS = '0xdAC17F958D2ee523a2206206994597C13D831ec7'     # USDT on Ethereum
 IN_TOKEN_DECIMALS = 6
 OUT_TOKEN_ADDRESS = '0xf816507E690f5Aa4E29d164885EB5fa7a5627860'    # RATO on Ethereum
@@ -133,7 +133,7 @@ def send_arb_msg(profit, data):
         "exchangeOutAmount": str(EXCHANGE_OUT_AMOUNT)
     }
 
-    print(f"正在提交套利数据到 {ARB_EXECUTOR_URL}")
+    logging.info(f"正在提交套利数据到 {ARB_EXECUTOR_URL}")
 
     try:
         # 发送 POST 请求
@@ -141,33 +141,33 @@ def send_arb_msg(profit, data):
 
         # 检查响应状态码
         if response.status_code == 201:
-            print("\n请求成功! 套利流程已启动。")
+            logging.info("\n请求成功! 套利流程已启动。")
         elif response.status_code == 200:
-             print("\n请求接收成功,但未达到利润阈值,未启动套利流程。")
+             logging.info("\n请求接收成功,但未达到利润阈值,未启动套利流程。")
         elif response.status_code == 400:
-             print("\n请求失败! 无效的请求数据。")
+             logging.info("\n请求失败! 无效的请求数据。")
         else:
-            print(f"\n请求失败! 状态码: {response.status_code}")
+            logging.info(f"\n请求失败! 状态码: {response.status_code}")
 
         # 打印响应体
         try:
-            print("响应体:")
-            print(json.dumps(response.json(), indent=4))
+            logging.info("响应体:")
+            logging.info(json.dumps(response.json(), indent=4))
         except json.JSONDecodeError:
-            print("响应体不是有效的 JSON:")
-            print(response.text)
+            logging.info("响应体不是有效的 JSON:")
+            logging.info(response.text)
 
     except requests.exceptions.ConnectionError as e:
-        print(f"\n连接错误: 无法连接到 {ARB_EXECUTOR_URL}。请确保 arb_executor.py 正在运行。")
-        print(f"错误详情: {e}")
+        logging.info(f"\n连接错误: 无法连接到 {ARB_EXECUTOR_URL}。请确保 arb_executor.py 正在运行。")
+        logging.info(f"错误详情: {e}")
     except Exception as e:
-        print(f"\n发送请求时发生未知错误: {e}")
+        logging.info(f"\n发送请求时发生未知错误: {e}")
 
     logging.info(f"套利消息已发送, {profit}, {human_in_base}, {human_out_target}")
 
 def update_data_for_plotly_and_table():
     global historical_data_points, latest_values_for_table
-    print(f"数据更新线程 ({TARGET_ASSET_SYMBOL}/{BASE_CURRENCY_SYMBOL})...")
+    logging.info(f"数据更新线程 ({TARGET_ASSET_SYMBOL}/{BASE_CURRENCY_SYMBOL})...")
     while True:
         fetch_time_full = time.strftime("%Y-%m-%d %H:%M:%S")
         fetch_time_chart = time.strftime("%H:%M:%S")
@@ -237,7 +237,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 ({TARGET_ASSET_SYMBOL}/USDT):{ok_mexc_bid}")
+        # logging.info(f"{fetch_time_chart} Fetch | OO:{ok_oo} | MEXC Bid1 ({TARGET_ASSET_SYMBOL}/USDT):{ok_mexc_bid}")
         time.sleep(REFRESH_INTERVAL_SECONDS)
 
 @app.route('/')
@@ -328,30 +328,30 @@ if __name__ == "__main__":
 
         # 4. 获取参数值
         mode = args.mode
-        print(f"脚本运行模式为: {mode}")
+        logging.info(f"脚本运行模式为: {mode}")
 
         # 这里可以根据 mode 的值执行不同的逻辑
         if mode == 'trade':
-            print("正在以交易模式运行...")
+            logging.info("正在以交易模式运行...")
             # ... 开发模式特有的代码 ...
         elif mode == 'view':
-            print("正在以观察模式运行...")
+            logging.info("正在以观察模式运行...")
             # ... 生产模式特有的代码 ...
         else:
-            print(f"警告: 未知的运行模式 '{mode}'.")
+            logging.info(f"警告: 未知的运行模式 '{mode}'.")
 
-        print("应用启动...")
-        print(f"目标资产: {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} 进行比较)")
+        logging.info("应用启动...")
+        logging.info(f"目标资产: {TARGET_ASSET_SYMBOL}")
+        logging.info(f"计价货币 (Okx输入 / MEXC交易对基础): {BASE_CURRENCY_SYMBOL}") # Should be USDT
+        logging.info(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})")
+        logging.info(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)")
+        logging.info(f"Flask 服务将在 http://0.0.0.0:5001 上运行 (刷新间隔: {REFRESH_INTERVAL_SECONDS}s)")
         app.run(debug=False, host='0.0.0.0', port=5001, use_reloader=False)
     except argparse.ArgumentError as e:
         # 这里捕获 argparse 解析参数时发生的错误,包括 required 参数未提供
-        print(f"参数错误: {e}")
+        logging.info(f"参数错误: {e}")
         # argparse 默认会在 required 参数未提供时打印错误消息并退出,
         # 但这里我们特意捕获并打印,可以根据需要进行更复杂的处理
         parser.print_help() # 打印帮助信息
@@ -359,8 +359,8 @@ if __name__ == "__main__":
     except SystemExit:
         # argparse 在 required 参数未提供时会触发 SystemExit 异常并退出程序
         # 如果你想在程序退出前做些什么,可以捕获这个异常
-        print("\n脚本由于参数错误而退出。")
+        logging.info("\n脚本由于参数错误而退出。")
 
     except Exception as e:
         # 捕获其他可能的异常
-        print(f"脚本运行时发生未知错误: {e}")
+        logging.info(f"脚本运行时发生未知错误: {e}")