Kaynağa Gözat

显示价差

skyffire 6 ay önce
ebeveyn
işleme
9fa2b989af
3 değiştirilmiş dosya ile 394 ekleme ve 135 silme
  1. 0 0
      css/style.css
  2. 170 135
      price_checker.py
  3. 224 0
      templates/index.html

+ 0 - 0
css/style.css


+ 170 - 135
price_checker.py

@@ -1,108 +1,75 @@
 import requests
-import decimal  # 导入 decimal 模块,用于更精确的货币运算
-import time  # 导入 time 模块,用于实现轮询间隔
-
-# --- 配置部分 (与之前代码相同,此处省略以保持简洁) ---
-# proxies = None # 如果不使用代理
+import decimal
+import time
+import threading
+import json
+from flask import Flask, render_template, jsonify
+from collections import deque  # 用于高效地存储固定数量的历史数据
+
+# --- 配置部分 (与之前相同) ---
 GATEIO_SPOT_PAIR = 'MUBARAK_USDT'
-# BSC (币安智能链) 代币地址
-IN_TOKEN_ADDRESS_BSC = '0x55d398326f99059ff775485246999027b3197955'  # BSC 上的 USDT 代币合约地址
+IN_TOKEN_ADDRESS_BSC = '0x55d398326f99059ff775485246999027b3197955'
 OUT_TOKEN_ADDRESS_BSC = '0x5C85D6C6825aB4032337F11Ee92a72DF936b46F6'
-AMOUNT_TO_QUERY_HUMAN = decimal.Decimal('1000')  # 查询数量设置为1个单位的输入代币
+AMOUNT_TO_QUERY_HUMAN = decimal.Decimal('1000')
 PROXY_HOST = '127.0.0.1'
 PROXY_PORT = '7890'
 proxies = {
     'http': f'http://{PROXY_HOST}:{PROXY_PORT}',
     'https': f'http://{PROXY_HOST}:{PROXY_PORT}',
 }
+# proxies = None # 如果你不需要代理
+
+decimal.getcontext().prec = 36
 
 
-# --- OpenOcean 的价格获取函数 ---
+# --- 价格获取函数 (与之前相同, 省略以保持简洁) ---
 def get_openocean_price_bsc(in_token_addr, out_token_addr, human_amount_in_decimal_for_request, gas_price=3):
-    """
-    从 OpenOcean API 获取报价,并根据 API 返回的原子单位数量和代币小数位数计算价格。
-
-    价格计算方式为: (通过 inAmount 和 inToken.decimals 计算得到的人类可读输入数量) / (通过 outAmount 和 outToken.decimals 计算得到的人类可读输出数量)
-    这个价格表示 “每单位输出代币需要多少单位输入代币”。
-
-    Args:
-        in_token_addr (str): 输入代币的合约地址。
-        out_token_addr (str): 输出代币的合约地址。
-        human_amount_in_decimal_for_request (decimal.Decimal): 用于 API 请求的人类可读的输入金额。
-                                                            API会根据此金额和实际代币信息处理请求。
-        gas_price (int, optional): Gas 价格。 默认为 3。
-
-    Returns:
-        dict: 包含价格计算结果或错误信息的字典。
-              成功时: {"price_in_per_out": calculated_price}
-              失败时: {"error": "错误信息"}
-    """
+    # ... (代码与之前相同)
     chain = 'bsc'
     url = f'https://open-api.openocean.finance/v4/{chain}/quote'
     params = {
         'inTokenAddress': in_token_addr,
         'outTokenAddress': out_token_addr,
-        'amount': str(human_amount_in_decimal_for_request),  # API 请求中的 amount 参数
+        'amount': str(human_amount_in_decimal_for_request),
         'gasPrice': gas_price,
     }
     try:
         response = requests.get(url, params=params, proxies=proxies, timeout=10)
-        response.raise_for_status()  # 如果HTTP状态码表示错误,则抛出异常
+        response.raise_for_status()
         data = response.json()
 
         if data.get('code') == 200 and data.get('data'):
             api_data = data['data']
-
-            # 确保所有需要的数据都存在
             if not (api_data.get('inToken') and api_data['inToken'].get('decimals') is not None and
                     api_data.get('outToken') and api_data['outToken'].get('decimals') is not None and
                     api_data.get('inAmount') is not None and api_data.get('outAmount') is not None):
-                return {"error": "API响应中缺少必要的代币信息、数量或小数位数"}
+                return {"error": "OO API响应中缺少必要的数据"}
 
-            # 从 API 响应中获取原子单位的数量和小数位数
             in_token_decimals = int(api_data['inToken']['decimals'])
             out_token_decimals = int(api_data['outToken']['decimals'])
-
             atomic_in_amount = decimal.Decimal(api_data['inAmount'])
             atomic_out_amount = decimal.Decimal(api_data['outAmount'])
 
-            # 计算人类可读的输入数量
-            # derived_human_in_amount = atomic_in_amount / (decimal.Decimal('10') ** in_token_decimals)
-            # 根据您的反馈,OpenOcean的 inToken.volume 已经是人类可读的输入量,而 inAmount 是API内部处理后的原子单位量
-            # 如果您的意图是严格使用API返回的 inAmount 来反推人类可读输入量,那么上面的计算是正确的。
-            # 我们将基于 inAmount 和 outAmount 来计算,正如您所要求的。
             derived_human_in_amount = atomic_in_amount / (decimal.Decimal('10') ** in_token_decimals)
-
-            # 计算人类可读的输出数量
             derived_human_out_amount = atomic_out_amount / (decimal.Decimal('10') ** out_token_decimals)
 
-            if derived_human_out_amount == 0:  # 避免除以零的错误
-                return {"error": "计算得出人类可读输出数量为零,无法计算价格"}
+            if derived_human_out_amount == 0:
+                return {"error": "OO 计算的输出数量为零"}
 
-            if derived_human_in_amount == 0 and human_amount_in_decimal_for_request > 0:
-                # 如果API返回的inAmount为0,但请求量大于0,可能是一个问题或特殊情况
-                # 但我们仍按您的要求基于derived_human_in_amount计算
-                pass
-
-            # 根据您的要求:价格 = 输入数量 / 输出数量
-            # 这个价格表示:1 单位的输出代币等价于多少单位的输入代币
             calculated_price = derived_human_in_amount / derived_human_out_amount
-
-            return {"price_in_per_out": calculated_price}  # 使用更能描述价格含义的键名
-
+            return {"price_in_per_out": calculated_price}
         else:
-            # API 返回的 code 不是 200 或没有 data 字段
-            error_message = data.get('message', 'N/A') if data else '无响应数据 (data is None)'
+            error_message = data.get('message', 'N/A') if data else '无响应数据'
             error_code = data.get('code', 'N/A') if data else 'N/A'
             return {"error": f"OO API 错误 - Code: {error_code}, Msg: {error_message}"}
-
     except requests.exceptions.RequestException as e:
         return {"error": f"OO请求失败: {e}"}
-    except Exception as e:  # 捕获包括 JSONDecodeError 在内的其他潜在错误
+    except Exception as e:
         return {"error": f"OO意外错误: {e}"}
 
 
 def get_gateio_spot_price(pair_symbol):
+    # ... (代码与之前相同)
     url = f'https://api.gateio.ws/api/v4/spot/tickers'
     params = {'currency_pair': pair_symbol}
     try:
@@ -127,86 +94,154 @@ def get_gateio_spot_price(pair_symbol):
         return {"error": f"Gate意外错误: {e}"}
 
 
-# --- 主逻辑 (已修改) ---
-def main():
-    print(f"开始轮询价格 (每秒一次), BSC vs Gate.io {GATEIO_SPOT_PAIR}")
-    print("按 Ctrl+C 停止。")
-    # 打印表头,使用格式化字符串使其对齐
-    header = f"{'OpenOcean ':<25} | {'Gate.io ':<25} | {'价差百分比':<15}"
-    print(header)
-    print("-" * (25 + 3 + 25 + 3 + 15))  # 打印与表头长度匹配的分隔线
+app = Flask(__name__)
 
-    try:
-        while True:
-            # --- 初始化本轮迭代的变量 ---
-            oo_rate_usdc_per_usdt = None  # OpenOcean 的汇率 (1 USDT = X USDC)
-            gate_rate_usdc_per_usdt_inverted = None  # Gate.io 转换后的汇率 (1 USDT = X USDC)
-
-            oo_display_str = "N/A"  # OpenOcean 在最终输出行中显示的字符串
-            gate_display_str = "N/A"  # Gate.io 在最终输出行中显示的字符串
-            diff_percentage_display_str = "N/A"  # 价差百分比在最终输出行中显示的字符串
-
-            oo_error_this_iteration = None  # 存储本轮OpenOcean的错误信息
-            gate_error_this_iteration = None  # 存储本轮Gate.io的错误信息
-
-            # --- 1. OpenOcean BSC 查询 ---
-            oo_price_data = get_openocean_price_bsc(
-                IN_TOKEN_ADDRESS_BSC,
-                OUT_TOKEN_ADDRESS_BSC,
-                AMOUNT_TO_QUERY_HUMAN
-            )
-
-            if "error" not in oo_price_data:
-                oo_rate_usdc_per_usdt = oo_price_data['price_in_per_out']
-                oo_display_str = f"{oo_rate_usdc_per_usdt:.6f}"  # 格式化价格
-            else:
-                oo_error_this_iteration = oo_price_data['error']  # 记录错误信息
-
-            # --- 2. Gate.io 现货查询 ---
-            gate_price_data = get_gateio_spot_price(GATEIO_SPOT_PAIR)
-
-            if "error" not in gate_price_data:
-                gate_rate_usdt_per_usdc = gate_price_data['price_base_in_quote']
-                if gate_rate_usdt_per_usdc is not None and gate_rate_usdt_per_usdc > 0:
-                    # 转换 Gate.io 的汇率方向为 1 USDT = Y USDC (Y = 1 / X)
-                    gate_rate_usdc_per_usdt_inverted = gate_rate_usdt_per_usdc
-                    gate_display_str = f"{gate_rate_usdc_per_usdt_inverted:.6f}"  # 格式化价格
-                elif gate_rate_usdt_per_usdc is not None:  # 价格为0或负数
-                    gate_error_this_iteration = f"Gate.io 无效汇率 ({gate_rate_usdt_per_usdc})"
-                    gate_display_str = "Invalid"  # 在行内显示为无效
-                # else: price_base_in_quote is None, error already handled by "error" key check
-            else:
-                gate_error_this_iteration = gate_price_data['error']  # 记录错误信息
-
-            # --- 3. 计算价差百分比 (仅当两边价格都有效时) ---
-            if oo_rate_usdc_per_usdt is not None and gate_rate_usdc_per_usdt_inverted is not None:
-                if gate_rate_usdc_per_usdt_inverted != 0:  # 避免除以零
-                    # 价差 = OpenOcean汇率 - Gate.io转换后汇率
-                    difference = oo_rate_usdc_per_usdt - gate_rate_usdc_per_usdt_inverted
-                    # 价差百分比 = (价差 / Gate.io转换后汇率) * 100
-                    # 您可以根据需要选择以哪个价格为基准计算百分比,这里以 Gate.io 为基准
-                    percentage_diff = (difference / gate_rate_usdc_per_usdt_inverted) * 100
-                    diff_percentage_display_str = f"{percentage_diff:+.4f}%"  # 显示正负号和小数点后4位
-                else:
-                    diff_percentage_display_str = "Gate.io汇率为0"  # Gate.io 汇率为0,无法计算百分比
-
-            # --- 4. 打印错误信息 (如果本轮有错误发生) ---
-            # 这些错误会打印在数据行的上方,以便用户了解具体问题
-            if oo_error_this_iteration:
-                print(f"[错误] OpenOcean: {oo_error_this_iteration}")
-            if gate_error_this_iteration:
-                print(f"[错误] Gate.io: {gate_error_this_iteration}")
-
-            # --- 5. 组合打印在一行 ---
-            # 使用格式化字符串确保列对齐
-            print(f"{oo_display_str:<25} | {gate_display_str:<25} | {diff_percentage_display_str:<15}")
-
-            time.sleep(1)  # 等待1秒
+# --- 全局数据和历史记录 ---
+MAX_HISTORY_POINTS = 60  # 图表上显示的最大数据点数量 (例如,过去5分钟,每5秒一个点)
+latest_data = {
+    "oo_price": None,
+    "gate_price": None,
+    "difference_percentage": None,
+    "oo_error": None,
+    "gate_error": None,
+    "last_updated": None
+}
+# 使用 deque 来存储历史数据,它在添加和删除元素时具有 O(1) 的复杂度
+historical_prices = deque(maxlen=MAX_HISTORY_POINTS)  # 存储 {'timestamp': ts, 'oo': price, 'gate': price}
+historical_diff = deque(maxlen=MAX_HISTORY_POINTS)  # 存储 {'timestamp': ts, 'diff': percentage}
+
+data_lock = threading.Lock()
+
+
+def update_prices_periodically():
+    global latest_data, historical_prices, historical_diff
+    while True:
+        fetch_timestamp_str = time.strftime("%H:%M:%S")  # 用于图表标签的简单时间戳
+
+        oo_price_data = get_openocean_price_bsc(
+            IN_TOKEN_ADDRESS_BSC,
+            OUT_TOKEN_ADDRESS_BSC,
+            AMOUNT_TO_QUERY_HUMAN
+        )
+        gate_price_data = get_gateio_spot_price(GATEIO_SPOT_PAIR)
+
+        current_oo_price_decimal = None
+        current_gate_price_decimal = None
+        current_oo_error = None
+        current_gate_error = None
+        diff_percentage_val = None
+        diff_percentage_str = "N/A"
+
+        if "error" not in oo_price_data:
+            current_oo_price_decimal = oo_price_data['price_in_per_out']
+        else:
+            current_oo_error = oo_price_data['error']
 
-    except KeyboardInterrupt:  # 允许用户通过 Ctrl+C 来停止脚本
-        print("\n轮询停止。")
+        if "error" not in gate_price_data:
+            current_gate_price_decimal = gate_price_data['price_base_in_quote']
+        else:
+            current_gate_error = gate_price_data['error']
+
+        # 只有当两个价格都有效时,才计算价差并记录历史
+        if current_oo_price_decimal is not None and current_gate_price_decimal is not None:
+            # 将Decimal转换为浮点数以便存储和图表绘制 (Chart.js 通常使用JS number)
+            oo_price_float = float(current_oo_price_decimal)
+            gate_price_float = float(current_gate_price_decimal)
+
+            historical_prices.append({
+                "timestamp": fetch_timestamp_str,
+                "oo": oo_price_float,
+                "gate": gate_price_float
+            })
+
+            if current_gate_price_decimal > 0:
+                difference = current_oo_price_decimal - current_gate_price_decimal
+                percentage_diff_decimal = (difference / current_gate_price_decimal) * 100
+                diff_percentage_val = float(percentage_diff_decimal)  # 用于图表
+                diff_percentage_str = f"{percentage_diff_decimal:+.4f}%"  # 用于显示
+                historical_diff.append({
+                    "timestamp": fetch_timestamp_str,
+                    "diff": diff_percentage_val
+                })
+            else:
+                diff_percentage_str = "Gate价格为0"
+                # 对于价差图表,如果gate价格为0,可以记录一个特殊值或跳过
+                historical_diff.append({
+                    "timestamp": fetch_timestamp_str,
+                    "diff": None  # 或者一个非常大/小的值来表示无效
+                })
+        else:
+            # 如果其中一个价格无效,我们仍然可以尝试记录有效的那个,或者都标记为None
+            # 这里我们选择如果任一价格无效,价格历史点也记录None,价差历史点也记录None
+            historical_prices.append({
+                "timestamp": fetch_timestamp_str,
+                "oo": float(current_oo_price_decimal) if current_oo_price_decimal else None,
+                "gate": float(current_gate_price_decimal) if current_gate_price_decimal else None
+            })
+            historical_diff.append({
+                "timestamp": fetch_timestamp_str,
+                "diff": None
+            })
+
+        with data_lock:
+            latest_data["oo_price"] = str(current_oo_price_decimal) if current_oo_price_decimal is not None else None
+            latest_data["gate_price"] = str(
+                current_gate_price_decimal) if current_gate_price_decimal is not None else None
+            latest_data["difference_percentage"] = diff_percentage_str
+            latest_data["oo_error"] = current_oo_error
+            latest_data["gate_error"] = current_gate_error
+            latest_data["last_updated"] = time.strftime("%Y-%m-%d %H:%M:%S")
+
+        log_oo = f"{current_oo_price_decimal:.6f}" if current_oo_price_decimal else "N/A"
+        log_gate = f"{current_gate_price_decimal:.6f}" if current_gate_price_decimal else "N/A"
+        print(
+            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}")
+
+        time.sleep(5)  # 更新频率
+
+
+@app.route('/')
+def index():
+    return render_template('index.html')
+
+
+@app.route('/data')
+def get_data():
+    with data_lock:
+        # 准备图表需要的数据
+        # 对于价格图表
+        price_chart_labels = [item['timestamp'] for item in historical_prices]
+        oo_price_values = [item['oo'] for item in historical_prices]
+        gate_price_values = [item['gate'] for item in historical_prices]
+
+        # 对于价差图表
+        diff_chart_labels = [item['timestamp'] for item in historical_diff]
+        diff_values = [item['diff'] for item in historical_diff]
+
+        # 将最新的数据点加入 (如果历史记录已满,deque会自动移除旧的)
+        # 这里的数据是 latest_data 和 historical_data 的组合
+        # Chart.js需要完整的历史数据来初始化,然后可以增量更新
+        # 或者,前端每次都获取完整的历史窗口
+
+        # 为了简单起见,前端将定期获取完整的 /data,其中包含历史和当前
+        response_data = {
+            "current": latest_data,
+            "history": {
+                "prices": {
+                    "labels": list(price_chart_labels),  # deque 不是直接 JSON 可序列化的
+                    "oo": list(oo_price_values),
+                    "gate": list(gate_price_values)
+                },
+                "difference": {
+                    "labels": list(diff_chart_labels),
+                    "values": list(diff_values)
+                }
+            }
+        }
+        return jsonify(response_data)
 
 
 if __name__ == "__main__":
-    decimal.getcontext().prec = 36  # 设置 decimal 的计算精度
-    main()
+    price_updater_thread = threading.Thread(target=update_prices_periodically, daemon=True)
+    price_updater_thread.start()
+    app.run(debug=True, host='0.0.0.0', port=5000, use_reloader=False)

+ 224 - 0
templates/index.html

@@ -0,0 +1,224 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>价格与价差监控 MUBARAK/USDT</title>
+    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <!-- 引入 Chart.js -->
+    <style>
+        body { font-family: Arial, sans-serif; margin: 20px; background-color: #f4f4f4; color: #333; }
+        .container { background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); margin-bottom: 20px; }
+        h1, h2 { text-align: center; color: #333; }
+        table { width: 100%; border-collapse: collapse; margin-top: 20px; margin-bottom: 20px; }
+        th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
+        th { background-color: #e9e9e9; }
+        .price-up { color: green; }
+        .price-down { color: red; }
+        .error-message { color: #c00; font-style: italic; font-size: 0.9em; } /* Renamed from .error */
+        .status-cell { /* For status messages, not just errors */ }
+        .timestamp { font-size: 0.9em; color: #666; text-align: right; margin-top: 15px; }
+        .chart-container {
+            position: relative;
+            height: 40vh; /* Responsive height */
+            width: 80vw;  /* Responsive width */
+            margin: auto; /* Center the chart */
+            margin-bottom: 30px;
+        }
+    </style>
+</head>
+<body>
+    <div class="container">
+        <h1>MUBARAK/USDT 价格监控</h1>
+
+        <table>
+            <thead>
+                <tr>
+                    <th>平台</th>
+                    <th>价格 (USDT/MUBARAK)</th>
+                    <th>状态/错误</th>
+                </tr>
+            </thead>
+            <tbody>
+                <tr>
+                    <td>OpenOcean (BSC)</td>
+                    <td id="oo-price">加载中...</td>
+                    <td id="oo-status" class="status-cell"></td>
+                </tr>
+                <tr>
+                    <td>Gate.io (Spot)</td>
+                    <td id="gate-price">加载中...</td>
+                    <td id="gate-status" class="status-cell"></td>
+                </tr>
+                <tr>
+                    <td><b>价差百分比</b></td>
+                    <td id="diff-percentage" colspan="2">计算中...</td>
+                </tr>
+            </tbody>
+        </table>
+        <div class="timestamp">最后更新: <span id="last-updated">N/A</span></div>
+    </div>
+
+    <div class="container">
+        <h2>价格历史曲线</h2>
+        <div class="chart-container">
+            <canvas id="priceHistoryChart"></canvas>
+        </div>
+    </div>
+
+    <div class="container">
+        <h2>价差百分比历史曲线</h2>
+        <div class="chart-container">
+            <canvas id="diffHistoryChart"></canvas>
+        </div>
+    </div>
+
+    <script>
+        let priceChartInstance = null;
+        let diffChartInstance = null;
+        const MAX_CHART_POINTS = 60; // 与后端 MAX_HISTORY_POINTS 对应或稍小
+
+        function formatPrice(priceStr) {
+            if (priceStr === null || priceStr === undefined || priceStr === "N/A") return "N/A";
+            const price = parseFloat(priceStr);
+            return isNaN(price) ? "N/A" : price.toFixed(6);
+        }
+
+        function initializeChart(ctx, chartType, datasetsConfig, initialLabels, titleText) {
+            return new Chart(ctx, {
+                type: 'line',
+                data: {
+                    labels: initialLabels, // 时间戳
+                    datasets: datasetsConfig // e.g., [{ label: 'OpenOcean', data: [], borderColor: 'rgb(75, 192, 192)', tension: 0.1, fill: false }, ...]
+                },
+                options: {
+                    responsive: true,
+                    maintainAspectRatio: false,
+                    animation: {
+                        duration: 200 // 平滑过渡
+                    },
+                    scales: {
+                        x: {
+                            title: { display: true, text: '时间' }
+                        },
+                        y: {
+                            title: { display: true, text: chartType === 'price' ? '价格 (USDT)' : '价差 (%)' },
+                            beginAtZero: chartType === 'diff' ? false : undefined // 价差可能为负
+                        }
+                    },
+                    plugins: {
+                        legend: { position: 'top' },
+                        title: { display: true, text: titleText }
+                    },
+                    elements: {
+                        point:{
+                            radius: 2 // Smaller points
+                        }
+                    }
+                }
+            });
+        }
+
+        function updateChartData(chartInstance, newLabels, newDatasetsData) {
+            chartInstance.data.labels = newLabels;
+            newDatasetsData.forEach((datasetData, index) => {
+                chartInstance.data.datasets[index].data = datasetData;
+            });
+            chartInstance.update('quiet'); // 'quiet' to prevent re-animation on every update
+        }
+
+        function updateDisplayAndCharts() {
+            fetch('/data')
+                .then(response => response.json())
+                .then(data => {
+                    // --- 更新表格数据 ---
+                    const current = data.current;
+                    document.getElementById('oo-price').textContent = formatPrice(current.oo_price);
+                    document.getElementById('gate-price').textContent = formatPrice(current.gate_price);
+
+                    const diffEl = document.getElementById('diff-percentage');
+                    if (current.difference_percentage && current.difference_percentage !== "N/A") {
+                        diffEl.textContent = current.difference_percentage;
+                        const diffValue = parseFloat(current.difference_percentage.replace('%', ''));
+                        if (!isNaN(diffValue)) { // Ensure it's a number before comparing
+                           if (diffValue > 0) diffEl.className = 'price-up';
+                           else if (diffValue < 0) diffEl.className = 'price-down';
+                           else diffEl.className = '';
+                        } else {
+                           diffEl.className = '';
+                        }
+                    } else {
+                        diffEl.textContent = current.difference_percentage || "N/A";
+                        diffEl.className = '';
+                    }
+
+                    const ooStatusEl = document.getElementById('oo-status');
+                    ooStatusEl.textContent = current.oo_error ? `错误: ${current.oo_error}` : '正常';
+                    ooStatusEl.className = current.oo_error ? 'status-cell error-message' : 'status-cell ';
+
+                    const gateStatusEl = document.getElementById('gate-status');
+                    gateStatusEl.textContent = current.gate_error ? `错误: ${current.gate_error}` : '正常';
+                    gateStatusEl.className = current.gate_error ? 'status-cell error-message' : 'status-cell ';
+
+                    document.getElementById('last-updated').textContent = current.last_updated || "N/A";
+
+                    // --- 初始化或更新图表 ---
+                    const history = data.history;
+
+                    // 价格历史图表
+                    const priceCtx = document.getElementById('priceHistoryChart').getContext('2d');
+                    const priceDatasets = [
+                        {
+                            label: 'OpenOcean',
+                            data: history.prices.oo,
+                            borderColor: 'rgb(75, 192, 192)',
+                            tension: 0.1,
+                            fill: false
+                        },
+                        {
+                            label: 'Gate.io',
+                            data: history.prices.gate,
+                            borderColor: 'rgb(255, 99, 132)',
+                            tension: 0.1,
+                            fill: false
+                        }
+                    ];
+                    if (!priceChartInstance) {
+                        priceChartInstance = initializeChart(priceCtx, 'price', priceDatasets, history.prices.labels, '价格历史 (MUBARAK/USDT)');
+                    } else {
+                        updateChartData(priceChartInstance, history.prices.labels, [history.prices.oo, history.prices.gate]);
+                    }
+
+                    // 价差历史图表
+                    const diffCtx = document.getElementById('diffHistoryChart').getContext('2d');
+                    const diffDatasets = [
+                        {
+                            label: '价差百分比 (OO vs Gate)',
+                            data: history.difference.values,
+                            borderColor: 'rgb(54, 162, 235)',
+                            tension: 0.1,
+                            fill: false
+                        }
+                    ];
+                     if (!diffChartInstance) {
+                        diffChartInstance = initializeChart(diffCtx, 'diff', diffDatasets, history.difference.labels, '价差百分比历史');
+                    } else {
+                         updateChartData(diffChartInstance, history.difference.labels, [history.difference.values]);
+                    }
+
+                })
+                .catch(error => {
+                    console.error('Error fetching data:', error);
+                    // Handle display errors if fetch fails
+                    document.getElementById('oo-price').textContent = '错误';
+                    document.getElementById('gate-price').textContent = '错误';
+                    document.getElementById('diff-percentage').textContent = '无法获取数据';
+                });
+        }
+
+        // 首次加载数据并初始化图表
+        updateDisplayAndCharts();
+        // 每5秒更新一次数据和图表 (与后端更新频率匹配)
+        setInterval(updateDisplayAndCharts, 5000);
+    </script>
+</body>
+</html>