skyffire 6 mesiacov pred
rodič
commit
c3c56f8948
2 zmenil súbory, kde vykonal 397 pridanie a 367 odobranie
  1. 177 146
      price_checker.py
  2. 220 221
      templates/index.html

+ 177 - 146
price_checker.py

@@ -4,28 +4,36 @@ import time
 import threading
 import json
 from flask import Flask, render_template, jsonify
-from collections import deque  # 用于高效地存储固定数量的历史数据
-import logging # Import the logging module
-
-# --- 配置部分 (与之前相同) ---
-GATEIO_SPOT_PAIR = 'CAT_USDT'
-IN_TOKEN_ADDRESS_BSC = '0x55d398326f99059ff775485246999027b3197955'
-OUT_TOKEN_ADDRESS_BSC = '0x6894CDe390a3f51155ea41Ed24a33A4827d3063D'
-AMOUNT_TO_QUERY_HUMAN = decimal.Decimal('1000')
-PROXY_HOST = '127.0.0.1'
-PROXY_PORT = '7890'
+from collections import deque
+import logging
+
+# --- 配置部分 ---
+# OpenOcean
+IN_TOKEN_ADDRESS_BSC = '0x8F0528cE5eF7B51152A59745bEfDD91D97091d2F'  # USDT on BSC
+OUT_TOKEN_ADDRESS_BSC = '0x6894CDe390a3f51155ea41Ed24a33A4827d3063D'  # CAT on BSC (示例)
+AMOUNT_TO_QUERY_HUMAN = decimal.Decimal('1000')  # 查询的USDT数量
+
+# Gate.io 现货交易对
+GATEIO_SPOT_PAIR = 'ALPACA_USDT'  # 示例, 请确保存在且与OUT_TOKEN_ADDRESS_BSC对应
+
+# Gate.io USDT 结算永续合约名称
+GATEIO_FUTURES_CONTRACT = 'ALPACA_USDT'  # 示例, 请确保存在且与OUT_TOKEN_ADDRESS_BSC对应
+
+# 代理配置
+proxies = None
+# 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
 
 
-# --- 价格获取函数 (与之前相同, 省略以保持简洁) ---
+# --- 价格获取函数 ---
 def get_openocean_price_bsc(in_token_addr, out_token_addr, human_amount_in_decimal_for_request, gas_price=3):
-    # ... (代码与之前相同)
+    # (代码与之前相同, 返回 {"price_usdt_per_out_token": calculated_price} 或 {"error": ...})
     chain = 'bsc'
     url = f'https://open-api.openocean.finance/v4/{chain}/quote'
     params = {
@@ -38,27 +46,23 @@ def get_openocean_price_bsc(in_token_addr, out_token_addr, human_amount_in_decim
         response = requests.get(url, params=params, proxies=proxies, timeout=10)
         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):
+            required_keys = ['inToken', 'outToken', 'inAmount', 'outAmount']
+            if not all(key in api_data for key in required_keys) or \
+                    api_data['inToken'].get('decimals') is None or \
+                    api_data['outToken'].get('decimals') is None:
                 return {"error": "OO 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)
-            derived_human_out_amount = atomic_out_amount / (decimal.Decimal('10') ** out_token_decimals)
-
-            if derived_human_out_amount == 0:
-                return {"error": "OO 计算的输出数量为零"}
-
-            calculated_price = derived_human_in_amount / derived_human_out_amount
-            return {"price_in_per_out": calculated_price}
+            human_in_amount_used = atomic_in_amount / (decimal.Decimal('10') ** in_token_decimals)
+            human_out_amount_received = atomic_out_amount / (decimal.Decimal('10') ** out_token_decimals)
+            if human_out_amount_received == 0: return {"error": "OO 计算的输出数量为零"}
+            calculated_price = human_in_amount_used / human_out_amount_received
+            return {"price_usdt_per_out_token": calculated_price}
         else:
             error_message = data.get('message', 'N/A') if data else '无响应数据'
             error_code = data.get('code', 'N/A') if data else 'N/A'
@@ -70,7 +74,7 @@ def get_openocean_price_bsc(in_token_addr, out_token_addr, human_amount_in_decim
 
 
 def get_gateio_spot_price(pair_symbol):
-    # ... (代码与之前相同)
+    # (代码与之前相同, 返回 {"price_base_in_quote": decimal.Decimal(last_price_str)} 或 {"error": ...})
     url = f'https://api.gateio.ws/api/v4/spot/tickers'
     params = {'currency_pair': pair_symbol}
     try:
@@ -84,169 +88,196 @@ def get_gateio_spot_price(pair_symbol):
                 if last_price_str:
                     return {"price_base_in_quote": decimal.Decimal(last_price_str)}
                 else:
-                    return {"error": "Gate未找到last price"}
+                    return {"error": "Gate现货未找到last price"}
+            else:
+                return {"error": f"Gate现货交易对不匹配"}
+        else:
+            error_msg = "未知错误或交易对不存在"
+            if isinstance(data, dict) and data.get('label'):
+                error_msg = f"{data.get('label')}: {data.get('message', '')}"
+            return {"error": f"Gate现货 API 数据格式错误或未找到交易对 {pair_symbol}. Msg: {error_msg}"}
+    except requests.exceptions.RequestException as e:
+        return {"error": f"Gate现货请求失败: {e}"}
+    except Exception as e:
+        return {"error": f"Gate现货意外错误: {e}"}
+
+
+def get_gateio_futures_price(contract_symbol, settle_currency='usdt'):
+    # (代码与之前相同, 返回 {"price_settle_per_base_asset": decimal.Decimal(last_price_str)} 或 {"error": ...})
+    url = f'https://api.gateio.ws/api/v4/futures/{settle_currency}/tickers'
+    params = {'contract': contract_symbol}
+    try:
+        response = requests.get(url, params=params, proxies=proxies, timeout=10)
+        response.raise_for_status()
+        data = response.json()
+        if isinstance(data, list) and len(data) > 0:
+            ticker_data = data[0]
+            if ticker_data.get('contract') == contract_symbol:
+                last_price_str = ticker_data.get('last')
+                if last_price_str:
+                    return {"price_settle_per_base_asset": decimal.Decimal(last_price_str)}
+                else:
+                    return {"error": f"Gate期货 ({contract_symbol}) 未找到 'last' price"}
             else:
-                return {"error": f"Gate交易对不匹配"}
+                return {"error": f"Gate期货 API 返回合约与请求 ({contract_symbol}) 不匹配"}
         else:
-            return {"error": "Gate API数据格式错误"}
+            error_msg = "未知错误或合约不存在"
+            if isinstance(data, dict) and data.get('label'):
+                error_msg = f"{data.get('label')}: {data.get('message', '')}"
+            return {"error": f"Gate期货 API 数据格式错误或未找到合约 {contract_symbol}. Msg: {error_msg}"}
     except requests.exceptions.RequestException as e:
-        return {"error": f"Gate请求失败: {e}"}
+        return {"error": f"Gate期货请求失败: {e}"}
     except Exception as e:
-        return {"error": f"Gate意外错误: {e}"}
+        return {"error": f"Gate期货意外错误: {e}"}
 
 
 app = Flask(__name__)
 
-# --- 全局数据和历史记录 ---
-MAX_HISTORY_POINTS = 86400  # 图表上显示的最大数据点数量
+MAX_HISTORY_POINTS = 86400
 latest_data = {
     "oo_price": None,
-    "gate_price": None,
-    "difference_percentage": None,
+    "gate_spot_price": None,
+    "gate_futures_price": None,
+    "diff_oo_vs_spot_percentage": None,
+    "diff_oo_vs_futures_percentage": None,
+    "diff_spot_vs_futures_percentage": None,  # 基差
     "oo_error": None,
-    "gate_error": None,
-    "last_updated": None
+    "gate_spot_error": None,
+    "gate_futures_error": None,
+    "last_updated": None,
+    "gate_spot_pair_name": GATEIO_SPOT_PAIR,
+    "gate_futures_contract_name": GATEIO_FUTURES_CONTRACT
 }
-# 使用 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}
+# 历史价格: oo, spot, futures
+historical_prices = deque(maxlen=MAX_HISTORY_POINTS)
+# 历史价差: oo_vs_spot, oo_vs_futures, spot_vs_futures
+historical_diffs = deque(maxlen=MAX_HISTORY_POINTS)
 
 data_lock = threading.Lock()
 
 
+def calculate_percentage_diff(price_a, price_b):
+    """(price_a - price_b) / price_b * 100, B为基准"""
+    if price_a is not None and price_b is not None and price_b > 0:
+        diff = price_a - price_b
+        return (diff / price_b) * 100
+    return None
+
+
 def update_prices_periodically():
-    global latest_data, historical_prices, historical_diff
+    global latest_data, historical_prices, historical_diffs
     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']
-
-        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
-            })
+        fetch_time = time.strftime("%H:%M:%S")
+
+        oo_data = get_openocean_price_bsc(IN_TOKEN_ADDRESS_BSC, OUT_TOKEN_ADDRESS_BSC, AMOUNT_TO_QUERY_HUMAN)
+        spot_data = get_gateio_spot_price(GATEIO_SPOT_PAIR)
+        futures_data = get_gateio_futures_price(GATEIO_FUTURES_CONTRACT)
+
+        oo_price = oo_data.get("price_usdt_per_out_token")
+        spot_price = spot_data.get("price_base_in_quote")
+        futures_price = futures_data.get("price_settle_per_base_asset")
+
+        oo_err = oo_data.get("error")
+        spot_err = spot_data.get("error")
+        futures_err = futures_data.get("error")
+
+        # 价差计算
+        diff_oo_spot_pct = calculate_percentage_diff(oo_price, spot_price)
+        diff_oo_futures_pct = calculate_percentage_diff(oo_price, futures_price)
+        diff_spot_futures_pct = calculate_percentage_diff(spot_price, futures_price)
+
+        # 存储历史价格
+        historical_prices.append({
+            "timestamp": fetch_time,
+            "oo": float(oo_price) if oo_price else None,
+            "spot": float(spot_price) if spot_price else None,
+            "futures": float(futures_price) if futures_price else None,
+        })
+        # 存储历史价差
+        historical_diffs.append({
+            "timestamp": fetch_time,
+            "oo_vs_spot": float(diff_oo_spot_pct) if diff_oo_spot_pct is not None else None,
+            "oo_vs_futures": float(diff_oo_futures_pct) if diff_oo_futures_pct is not None else None,
+            "spot_vs_futures": float(diff_spot_futures_pct) if diff_spot_futures_pct is not None else 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["oo_price"] = str(oo_price) if oo_price else None
+            latest_data["gate_spot_price"] = str(spot_price) if spot_price else None
+            latest_data["gate_futures_price"] = str(futures_price) if futures_price else None
+            latest_data[
+                "diff_oo_vs_spot_percentage"] = f"{diff_oo_spot_pct:+.4f}%" if diff_oo_spot_pct is not None else "N/A"
+            latest_data[
+                "diff_oo_vs_futures_percentage"] = f"{diff_oo_futures_pct:+.4f}%" if diff_oo_futures_pct is not None else "N/A"
+            latest_data[
+                "diff_spot_vs_futures_percentage"] = f"{diff_spot_futures_pct:+.4f}%" if diff_spot_futures_pct is not None else "N/A"
+            latest_data["oo_error"] = oo_err
+            latest_data["gate_spot_error"] = spot_err
+            latest_data["gate_futures_error"] = futures_err
             latest_data["last_updated"] = time.strftime("%Y-%m-%d %H:%M:%S")
+            # gate_spot_pair_name & gate_futures_contract_name are set at init
 
-        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}")
+        print(f"{fetch_time} | OO: {latest_data['oo_price']} (Err:{oo_err}) | "
+              f"Spot({GATEIO_SPOT_PAIR}): {latest_data['gate_spot_price']} (Err:{spot_err}) | "
+              f"Futures({GATEIO_FUTURES_CONTRACT}): {latest_data['gate_futures_price']} (Err:{futures_err}) | "
+              f"D(OoS):{latest_data['diff_oo_vs_spot_percentage']} D(OoF):{latest_data['diff_oo_vs_futures_percentage']} D(SpF):{latest_data['diff_spot_vs_futures_percentage']}")
 
-        time.sleep(1)  # 更新频率
+        time.sleep(1)
 
 
 @app.route('/')
 def index():
-    return render_template('index.html')
+    app_config = {
+        "GATEIO_SPOT_PAIR": GATEIO_SPOT_PAIR,
+        "GATEIO_FUTURES_CONTRACT": GATEIO_FUTURES_CONTRACT,
+        # 也可以把 OUT_TOKEN_ADDRESS_BSC 对应的代币符号传过去,如果能确定的话
+        "TARGET_ASSET_SYMBOL": GATEIO_SPOT_PAIR.split('_')[0]  # 假设命名规则为 ASSET_USDT
+    }
+    return render_template('index.html', config=app_config)
 
 
 @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,
+            "current": {**latest_data},  # Create a copy
             "history": {
                 "prices": {
-                    "labels": list(price_chart_labels),  # deque 不是直接 JSON 可序列化的
-                    "oo": list(oo_price_values),
-                    "gate": list(gate_price_values)
+                    "labels": [item['timestamp'] for item in historical_prices],
+                    "oo": [item['oo'] for item in historical_prices],
+                    "spot": [item['spot'] for item in historical_prices],
+                    "futures": [item['futures'] for item in historical_prices],
                 },
-                "difference": {
-                    "labels": list(diff_chart_labels),
-                    "values": list(diff_values)
+                "diffs": {  # Changed from "difference" to "diffs" to hold multiple series
+                    "labels": [item['timestamp'] for item in historical_diffs],  # Should be same as price labels
+                    "oo_vs_spot": [item['oo_vs_spot'] for item in historical_diffs],
+                    "oo_vs_futures": [item['oo_vs_futures'] for item in historical_diffs],
+                    "spot_vs_futures": [item['spot_vs_futures'] for item in historical_diffs],
                 }
             }
         }
+        # Add config here too, so JS can access it if not from initial render_template
+        response_data["current"]["config_gate_spot_pair"] = GATEIO_SPOT_PAIR
+        response_data["current"]["config_gate_futures_contract"] = GATEIO_FUTURES_CONTRACT
+        response_data["current"]["config_target_asset_symbol"] = GATEIO_SPOT_PAIR.split('_')[0]
+
         return jsonify(response_data)
 
 
 if __name__ == "__main__":
-    # Disable Werkzeug access logs
     werkzeug_logger = logging.getLogger('werkzeug')
-    werkzeug_logger.setLevel(logging.ERROR) # or logging.CRITICAL
+    werkzeug_logger.setLevel(logging.ERROR)
+
+    print(f"监控 OpenOcean ({OUT_TOKEN_ADDRESS_BSC.split('0x')[-1][:6]}...) vs USDT")
+    print(f"Gate.io 现货: {GATEIO_SPOT_PAIR}")
+    print(f"Gate.io 期货: {GATEIO_FUTURES_CONTRACT}")
+    # Basic check for CAT example, adjust if your token is different
+    asset_symbol = GATEIO_SPOT_PAIR.split('_')[0].upper()
+    if asset_symbol == "CAT" and "0x6894CDe390a3f51155ea41Ed24a33A4827d3063D" not in OUT_TOKEN_ADDRESS_BSC:
+        print(f"[警告] 配置的资产符号 'CAT' 可能与 OpenOcean 输出代币地址不完全对应。")
+    elif asset_symbol not in GATEIO_FUTURES_CONTRACT.upper():
+        print(f"[警告] 现货交易对 '{GATEIO_SPOT_PAIR}' 的基础资产与期货合约 '{GATEIO_FUTURES_CONTRACT}' 可能不匹配。")
 
     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)
+    app.run(debug=True, host='0.0.0.0', port=5000, use_reloader=False)

+ 220 - 221
templates/index.html

@@ -3,7 +3,7 @@
 <head>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>价格与价差监控 Binance-OpenOcean</title>
+    <title>多平台价格与价差监控</title>
     <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
     <script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-zoom@2.0.1/dist/chartjs-plugin-zoom.min.js"></script>
     <style>
@@ -11,54 +11,24 @@
         .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, td { border: 1px solid #ddd; padding: 10px; text-align: left; font-size:0.9em; } /* Smaller padding/font for more data */
         th { background-color: #e9e9e9; }
         .price-up { color: green; }
         .price-down { color: red; }
         .error-message { color: #c00; font-style: italic; font-size: 0.9em; }
-        .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: 45vh;
-            width: 90vw;
-            margin: auto;
-            margin-bottom: 10px;
-        }
-        .controls-container {
-            text-align: center;
-            margin-bottom: 20px;
-            margin-top: 5px;
-        }
-        .control-button {
-            background-color: #007bff;
-            color: white;
-            border: none;
-            padding: 8px 15px;
-            text-align: center;
-            text-decoration: none;
-            display: inline-block;
-            font-size: 14px;
-            border-radius: 5px;
-            cursor: pointer;
-            margin: 0 5px;
-        }
-        .control-button:hover {
-            background-color: #0056b3;
-        }
-        .pause-button-active {
-            background-color: #ffc107;
-            color: #333;
-        }
-        .pause-button-active:hover {
-            background-color: #e0a800;
-        }
+        .chart-container { position: relative; height: 40vh; width: 90vw; margin: auto; margin-bottom: 10px; }
+        .controls-container { text-align: center; margin-bottom: 20px; margin-top: 5px; }
+        .control-button { background-color: #007bff; color: white; border: none; padding: 8px 15px; font-size: 14px; border-radius: 5px; cursor: pointer; margin:0 5px; }
+        .control-button:hover { background-color: #0056b3; }
+        .pause-button-active { background-color: #ffc107; color: #333; }
+        .pause-button-active:hover { background-color: #e0a800; }
+        .platform-name {font-weight: bold;}
     </style>
 </head>
 <body>
-    <!-- HTML 结构与之前相同,此处省略 -->
     <div class="container">
-        <h1>USDT 价格监控</h1>
+        <h1 id="main-title">价格监控</h1>
         <div class="controls-container">
             <button id="pause-resume-button" class="control-button">暂停刷新</button>
         </div>
@@ -66,24 +36,48 @@
             <thead>
                 <tr>
                     <th>平台</th>
-                    <th>价格</th>
+                    <th>价格 (USDT)</th>
                     <th>状态/错误</th>
                 </tr>
             </thead>
             <tbody>
                 <tr>
-                    <td>OpenOcean (BSC)</td>
+                    <td class="platform-name">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>
+                    <td class="platform-name" id="gate-spot-label">Gate.io 现货</td>
+                    <td id="gate-spot-price">加载中...</td>
+                    <td id="gate-spot-status" class="status-cell"></td>
+                </tr>
+                <tr>
+                    <td class="platform-name" id="gate-futures-label">Gate.io 期货</td>
+                    <td id="gate-futures-price">加载中...</td>
+                    <td id="gate-futures-status" class="status-cell"></td>
+                </tr>
+            </tbody>
+        </table>
+        <h2>价差百分比</h2>
+        <table>
+             <thead>
+                <tr>
+                    <th>对比</th>
+                    <th>价差 (%)</th>
+                </tr>
+            </thead>
+            <tbody>
+                <tr>
+                    <td id="diff-label-oo-spot">OO vs Gate.io 现货</td>
+                    <td id="diff-oo-vs-spot">计算中...</td>
                 </tr>
                 <tr>
-                    <td><b>价差百分比</b></td>
-                    <td id="diff-percentage" colspan="2">计算中...</td>
+                    <td id="diff-label-oo-futures">OO vs Gate.io 期货</td>
+                    <td id="diff-oo-vs-futures">计算中...</td>
+                </tr>
+                <tr>
+                    <td id="diff-label-spot-futures">Gate.io 现货 vs 期货</td>
+                    <td id="diff-spot-vs-futures">计算中...</td>
                 </tr>
             </tbody>
         </table>
@@ -91,7 +85,7 @@
     </div>
 
     <div class="container">
-        <h2>价格历史曲线</h2>
+        <h2 id="price-chart-title">价格历史曲线</h2>
         <div class="chart-container">
             <canvas id="priceHistoryChart"></canvas>
         </div>
@@ -101,9 +95,9 @@
     </div>
 
     <div class="container">
-        <h2>价差百分比历史曲线</h2>
+        <h2 id="diff-chart-title">价差百分比历史曲线</h2>
         <div class="chart-container">
-            <canvas id="diffHistoryChart"></canvas>
+            <canvas id="diffPercentageChart"></canvas> <!-- Changed ID -->
         </div>
         <div class="controls-container">
             <button id="reset-diff-zoom-button" class="control-button">重置缩放</button>
@@ -112,140 +106,142 @@
 
     <script>
         let priceChartInstance = null;
-        let diffChartInstance = null;
+        let diffChartInstance = null; // For percentage differences
 
         let dataUpdateIntervalID = null;
         let isPaused = false;
-        const REFRESH_INTERVAL_MS = 5000;
+        const REFRESH_INTERVAL_MS = 1000;
         const pauseResumeButton = document.getElementById('pause-resume-button');
-
-        let isSyncingZoomPan = false; // 标志位,防止同步死循环
+        let isSyncingZoomPan = false;
+
+        // Get initial config from Flask if available
+        const initialConfig = {
+            GATEIO_SPOT_PAIR: "{{ config.GATEIO_SPOT_PAIR if config else 'SPOT_USDT' }}",
+            GATEIO_FUTURES_CONTRACT: "{{ config.GATEIO_FUTURES_CONTRACT if config else 'FUTURES_USDT' }}",
+            TARGET_ASSET_SYMBOL: "{{ config.TARGET_ASSET_SYMBOL if config else 'ASSET' }}"
+        };
+        let currentGateSpotPair = initialConfig.GATEIO_SPOT_PAIR;
+        let currentGateFuturesContract = initialConfig.GATEIO_FUTURES_CONTRACT;
+        let currentAssetSymbol = initialConfig.TARGET_ASSET_SYMBOL;
 
         function formatPrice(priceStr) {
+            // (formatPrice function from previous example, can be reused)
             if (priceStr === null || priceStr === undefined || priceStr === "N/A") return "N/A";
             const price = parseFloat(priceStr);
-            return isNaN(price) ? "N/A" : price.toFixed(6);
+            if (isNaN(price)) return "N/A";
+            if (price === 0) return "0.000000";
+            if (price < 0.000001 && price > 0) return price.toExponential(4);
+            if (price < 0.01) return price.toFixed(8);
+            if (price < 1) return price.toFixed(6);
+            return price.toFixed(4);
+        }
+        function formatPercentage(percStr) {
+            if (percStr === null || percStr === undefined || percStr === "N/A") return "N/A";
+            // Assumes percStr is like "+0.1234%" or "-5.6789%"
+            if (String(percStr).includes('%')) return percStr;
+            const perc = parseFloat(percStr);
+            if (isNaN(perc)) return "N/A";
+            return `${perc > 0 ? '+' : ''}${perc.toFixed(4)}%`;
         }
 
-        // 函数:从源图表同步X轴到目标图表
-        function syncXAxes(sourceChart, targetChart) {
-            if (isSyncingZoomPan || !sourceChart || !targetChart) return; // 如果正在同步或图表未定义,则跳过
-
-            isSyncingZoomPan = true; // 设置标志位
-
+        function syncXAxes(sourceChart, targetChart) { /* (Same as before) */
+            if (isSyncingZoomPan || !sourceChart || !targetChart) return;
+            isSyncingZoomPan = true;
             const sourceXScale = sourceChart.scales.x;
             const targetXScale = targetChart.scales.x;
-
             if (sourceXScale && targetXScale) {
-                targetXScale.options.min = sourceXScale.min; // 同步X轴的最小值
-                targetXScale.options.max = sourceXScale.max; // 同步X轴的最大值
-                targetChart.update('none'); // 更新目标图表,'none' 表示不执行动画
+                targetXScale.options.min = sourceXScale.min;
+                targetXScale.options.max = sourceXScale.max;
+                targetChart.update('none');
             }
-
-            // 短暂延迟后重置标志位,允许下一次同步
-            // 如果不加延迟,快速连续操作可能会有问题
-            requestAnimationFrame(() => {
-                 isSyncingZoomPan = false;
-            });
+            requestAnimationFrame(() => { isSyncingZoomPan = false; });
         }
 
-        function initializeChart(ctx, chartIdentifier, chartType, datasetsConfig, initialLabels, titleText) {
-            const chartInstance = new Chart(ctx, {
+        function initializeChart(ctx, chartIdentifier, datasetsConfig, initialLabels, yAxisTitle, chartTitleText, isPriceChart = true) {
+            return new Chart(ctx, {
                 type: 'line',
-                data: {
-                    labels: initialLabels,
-                    datasets: datasetsConfig
-                },
+                data: { labels: initialLabels, datasets: datasetsConfig },
                 options: {
-                    responsive: true,
-                    maintainAspectRatio: false,
-                    animation: {
-                        duration: 150
-                    },
+                    responsive: true, maintainAspectRatio: false, animation: { duration: 150 },
                     scales: {
-                        x: {
-                            title: { display: true, text: '时间' },
-                            // 我们将通过回调函数同步 min/max,这里不设置
-                        },
+                        x: { title: { display: true, text: '时间' } },
                         y: {
-                            title: { display: true, text: chartType === 'price' ? '价格 (USDT)' : '价差 (%)' },
-                            beginAtZero: chartType === 'diff' ? false : undefined
+                            title: { display: true, text: yAxisTitle },
+                            beginAtZero: !isPriceChart ? false : undefined, // For diff chart, don't begin at zero
+                             ticks: {
+                                callback: function(value) {
+                                    return isPriceChart ? formatPrice(String(value)) : parseFloat(value).toFixed(2) + '%';
+                                }
+                            }
                         }
                     },
                     plugins: {
-                        legend: { position: 'top' },
-                        title: { display: true, text: titleText },
-                        zoom: {
-                            pan: {
-                                enabled: true,
-                                mode: 'x', // 只在X轴上平移 (Y轴通常不需要同步)
-                                threshold: 5,
-                                onPanComplete({chart}) { // 平移完成回调
-                                    if (chartIdentifier === 'price' && diffChartInstance) {
-                                        syncXAxes(chart, diffChartInstance);
-                                    } else if (chartIdentifier === 'diff' && priceChartInstance) {
-                                        syncXAxes(chart, priceChartInstance);
+                        legend: { position: 'top' }, title: { display: true, text: chartTitleText },
+                        tooltip: {
+                            callbacks: {
+                                label: function(context) {
+                                    let label = context.dataset.label || '';
+                                    if (label) label += ': ';
+                                    if (context.parsed.y !== null) {
+                                        label += isPriceChart ? formatPrice(String(context.parsed.y)) : parseFloat(context.parsed.y).toFixed(4) + '%';
                                     }
+                                    return label;
+                                }
+                            }
+                        },
+                        zoom: { /* (Zoom config same as before) */
+                            pan: {
+                                enabled: true, mode: 'x', threshold: 5,
+                                onPanComplete({chart}) {
+                                    if (chartIdentifier === 'price' && diffChartInstance) syncXAxes(chart, diffChartInstance);
+                                    else if (chartIdentifier === 'diff' && priceChartInstance) syncXAxes(chart, priceChartInstance);
                                 }
                             },
                             zoom: {
-                                wheel: { enabled: true, speed: 0.1 }, // 调整滚轮速度
-                                pinch: { enabled: true },
+                                wheel: { enabled: true, speed: 0.1 }, pinch: { enabled: true },
                                 drag: { enabled: true, backgroundColor: 'rgba(0,123,255,0.25)'},
-                                mode: 'x', // 只在X轴上缩放
-                                onZoomComplete({chart}) { // 缩放完成回调
-                                    if (chartIdentifier === 'price' && diffChartInstance) {
-                                        syncXAxes(chart, diffChartInstance);
-                                    } else if (chartIdentifier === 'diff' && priceChartInstance) {
-                                        syncXAxes(chart, priceChartInstance);
-                                    }
+                                mode: 'x',
+                                onZoomComplete({chart}) {
+                                    if (chartIdentifier === 'price' && diffChartInstance) syncXAxes(chart, diffChartInstance);
+                                    else if (chartIdentifier === 'diff' && priceChartInstance) syncXAxes(chart, priceChartInstance);
                                 }
                             }
                         }
                     },
-                    elements: {
-                        point:{ radius: 2 }
-                    }
+                    elements: { point:{ radius: 1.5 } } // Smaller points for more lines
                 }
             });
-            return chartInstance;
         }
 
-        function updateChartData(chartInstance, newLabels, newDatasetsData) {
-            if (!chartInstance) return; // 确保图表实例存在
-
+        function updateChartData(chartInstance, newLabels, newDatasetsDataArray) {
+            // (updateChartData function largely same, ensures it updates all datasets in newDatasetsDataArray)
+            if (!chartInstance) return;
             const currentXMin = chartInstance.scales.x.min;
             const currentXMax = chartInstance.scales.x.max;
-            const isZoomed = (currentXMin !== undefined && currentXMax !== undefined &&
-                             (currentXMin !== chartInstance.data.labels[0] || currentXMax !== chartInstance.data.labels[chartInstance.data.labels.length - 1]));
+            let isZoomed = (currentXMin !== undefined && currentXMax !== undefined);
+             if (isZoomed && chartInstance.data.labels.length > 0) {
+                 isZoomed = (currentXMin !== chartInstance.data.labels[0] || currentXMax !== chartInstance.data.labels[chartInstance.data.labels.length - 1]);
+            }
 
             chartInstance.data.labels = newLabels;
-            newDatasetsData.forEach((datasetData, index) => {
-                chartInstance.data.datasets[index].data = datasetData;
+            newDatasetsDataArray.forEach((datasetData, index) => {
+                if(chartInstance.data.datasets[index]) { // Check if dataset exists
+                    chartInstance.data.datasets[index].data = datasetData;
+                }
             });
 
-            // 如果图表之前是缩放状态,并且新的标签范围与旧的缩放范围不完全匹配,
-            // 尝试保持缩放,否则新的数据可能会导致缩放重置。
-            // 但这里简单处理:如果暂停,不更新 x 轴的 min/max,让用户控制。
-            // 如果正在刷新,则让图表根据新数据自动调整。
-            // 若要更精细控制 (如保持缩放比例滚动),会更复杂。
             if (!isPaused) {
-                 // 当数据刷新时,如果用户是手动缩放的,我们不应该重置它
-                 // Chart.js 会在数据更新时自动调整范围,除非 min/max 固定
-                 // 为了允许新数据扩展X轴,我们不在这里设置 min/max
-                 // 除非我们想实现固定窗口滚动
+                chartInstance.options.scales.x.min = undefined;
+                chartInstance.options.scales.x.max = undefined;
             } else {
-                // 如果暂停了,并且图表被用户缩放了,保持这个缩放
                 if(isZoomed) {
                     chartInstance.options.scales.x.min = currentXMin;
                     chartInstance.options.scales.x.max = currentXMax;
                 } else {
-                    // 如果没缩放,允许图表自动调整(尽管暂停时数据不会变)
                     chartInstance.options.scales.x.min = undefined;
                     chartInstance.options.scales.x.max = undefined;
                 }
             }
-
             chartInstance.update('quiet');
         }
 
@@ -254,137 +250,140 @@
                 .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)) {
-                           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 = '';
+                    // Update global config vars if they changed (though unlikely for these)
+                    currentGateSpotPair = current.config_gate_spot_pair || currentGateSpotPair;
+                    currentGateFuturesContract = current.config_gate_futures_contract || currentGateFuturesContract;
+                    currentAssetSymbol = current.config_target_asset_symbol || currentAssetSymbol;
+
+                    // --- Update Titles and Labels ---
+                    document.getElementById('main-title').textContent = `${currentAssetSymbol}/USDT 多平台价格监控`;
+                    document.getElementById('gate-spot-label').textContent = `Gate.io 现货 (${currentGateSpotPair})`;
+                    document.getElementById('gate-futures-label').textContent = `Gate.io 期货 (${currentGateFuturesContract})`;
+
+                    document.getElementById('diff-label-oo-spot').textContent = `OO vs Gate.io 现货 (${currentGateSpotPair})`;
+                    document.getElementById('diff-label-oo-futures').textContent = `OO vs Gate.io 期货 (${currentGateFuturesContract})`;
+                    document.getElementById('diff-label-spot-futures').textContent = `Gate.io 现货 (${currentGateSpotPair}) vs 期货 (${currentGateFuturesContract})`;
+
+                    const priceChartTitle = `${currentAssetSymbol}/USDT 价格历史`;
+                    const priceChartYLabel = `价格 (${currentAssetSymbol}/USDT)`;
+                    const diffChartTitleText = "价差百分比历史";
+
+                    if (priceChartInstance) {
+                        priceChartInstance.options.plugins.title.text = priceChartTitle;
+                        priceChartInstance.options.scales.y.title.text = priceChartYLabel;
+                    }
+                     if (diffChartInstance) {
+                        diffChartInstance.options.plugins.title.text = diffChartTitleText;
                     }
 
-                    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 ';
+                    // --- Update Table Data ---
+                    document.getElementById('oo-price').textContent = formatPrice(current.oo_price);
+                    document.getElementById('gate-spot-price').textContent = formatPrice(current.gate_spot_price);
+                    document.getElementById('gate-futures-price').textContent = formatPrice(current.gate_futures_price);
+
+                    document.getElementById('oo-status').textContent = current.oo_error ? `错误: ${current.oo_error}` : '正常';
+                    document.getElementById('gate-spot-status').textContent = current.gate_spot_error ? `错误: ${current.gate_spot_error}` : '正常';
+                    document.getElementById('gate-futures-status').textContent = current.gate_futures_error ? `错误: ${current.gate_futures_error}` : '正常';
+
+                    // Update error message classes
+                    document.getElementById('oo-status').className = current.oo_error ? 'status-cell error-message' : 'status-cell';
+                    document.getElementById('gate-spot-status').className = current.gate_spot_error ? 'status-cell error-message' : 'status-cell';
+                    document.getElementById('gate-futures-status').className = current.gate_futures_error ? 'status-cell error-message' : 'status-cell';
+
+                    // Update diff percentages
+                    const diffOOSpotEl = document.getElementById('diff-oo-vs-spot');
+                    const diffOOFuturesEl = document.getElementById('diff-oo-vs-futures');
+                    const diffSpotFuturesEl = document.getElementById('diff-spot-vs-futures');
+
+                    diffOOSpotEl.textContent = formatPercentage(current.diff_oo_vs_spot_percentage);
+                    diffOOFuturesEl.textContent = formatPercentage(current.diff_oo_vs_futures_percentage);
+                    diffSpotFuturesEl.textContent = formatPercentage(current.diff_spot_vs_futures_percentage);
+
+                    [diffOOSpotEl, diffOOFuturesEl, diffSpotFuturesEl].forEach(el => {
+                        const valStr = el.textContent.replace('%','').replace('+','');
+                        const val = parseFloat(valStr);
+                        if (!isNaN(val)) {
+                            el.className = val > 0 ? 'price-up' : (val < 0 ? 'price-down' : '');
+                        } else { el.className = ''; }
+                    });
 
                     document.getElementById('last-updated').textContent = current.last_updated || "N/A";
 
+                    // --- Update Charts ---
                     const history = data.history;
 
-                    // --- 价格历史图表 ---
+                    // Price History Chart
                     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 }
+                        { label: `Gate Spot (${currentGateSpotPair})`, data: history.prices.spot, borderColor: 'rgb(255, 99, 132)', tension: 0.1, fill: false },
+                        { label: `Gate Futures (${currentGateFuturesContract})`, data: history.prices.futures, borderColor: 'rgb(54, 162, 235)', tension: 0.1, fill: false }
                     ];
                     if (!priceChartInstance) {
-                        priceChartInstance = initializeChart(priceCtx, 'price', 'price', priceDatasets, history.prices.labels, '价格历史');
+                        priceChartInstance = initializeChart(priceCtx, 'price', priceDatasets, history.prices.labels, priceChartYLabel, priceChartTitle, true);
                     } else {
-                        updateChartData(priceChartInstance, history.prices.labels, [history.prices.oo, history.prices.gate]);
+                        priceChartInstance.data.datasets[1].label = `Gate Spot (${currentGateSpotPair})`;
+                        priceChartInstance.data.datasets[2].label = `Gate Futures (${currentGateFuturesContract})`;
+                        updateChartData(priceChartInstance, history.prices.labels, [history.prices.oo, history.prices.spot, history.prices.futures]);
                     }
 
-                    // --- 价差历史图表 ---
-                    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 }];
+                    // Diff Percentage Chart
+                    const diffCtx = document.getElementById('diffPercentageChart').getContext('2d');
+                    const diffDatasets = [
+                        { label: `OO vs Spot (${currentGateSpotPair})`, data: history.diffs.oo_vs_spot, borderColor: 'rgb(255, 159, 64)', tension: 0.1, fill: false },
+                        { label: `OO vs Futures (${currentGateFuturesContract})`, data: history.diffs.oo_vs_futures, borderColor: 'rgb(153, 102, 255)', tension: 0.1, fill: false },
+                        { label: `Spot (${currentGateSpotPair}) vs Futures (${currentGateFuturesContract})`, data: history.diffs.spot_vs_futures, borderColor: 'rgb(75, 192, 75)', tension: 0.1, fill: false }
+                    ];
                     if (!diffChartInstance) {
-                        diffChartInstance = initializeChart(diffCtx, 'diff', 'diff', diffDatasets, history.difference.labels, '价差百分比历史');
+                        diffChartInstance = initializeChart(diffCtx, 'diff', diffDatasets, history.diffs.labels, '价差 (%)', diffChartTitleText, false);
                     } else {
-                         updateChartData(diffChartInstance, history.difference.labels, [history.difference.values]);
-                    }
-
-                    // 初始加载或取消暂停时، 手动同步一次 (如果 X 轴不同步)
-                    // 避免初始加载时两个图表的 x 轴范围不一样
-                    if (priceChartInstance && diffChartInstance &&
-                        (priceChartInstance.scales.x.min !== diffChartInstance.scales.x.min ||
-                         priceChartInstance.scales.x.max !== diffChartInstance.scales.x.max) &&
-                        (!isPaused || !dataUpdateIntervalID ) // 只有在首次加载或从暂停恢复时才这样做
-                    ) {
-                        // console.log("Initial or resume sync needed");
-                        // syncXAxes(priceChartInstance, diffChartInstance); // 让价格图表作为主导
+                        diffChartInstance.data.datasets[0].label = `OO vs Spot (${currentGateSpotPair})`;
+                        diffChartInstance.data.datasets[1].label = `OO vs Futures (${currentGateFuturesContract})`;
+                        diffChartInstance.data.datasets[2].label = `Spot (${currentGateSpotPair}) vs Futures (${currentGateFuturesContract})`;
+                        updateChartData(diffChartInstance, history.diffs.labels, [history.diffs.oo_vs_spot, history.diffs.oo_vs_futures, history.diffs.spot_vs_futures]);
                     }
 
                 })
                 .catch(error => {
-                    console.error('Error fetching data:', error);
-                    // ... (错误处理与之前相同) ...
+                    console.error('Error fetching data for all platforms:', error);
+                    // Display a general error, or individual errors if available.
                 });
         }
 
-        function togglePauseResume() {
+        function togglePauseResume() { /* (Same as before) */
             isPaused = !isPaused;
             if (isPaused) {
-                clearInterval(dataUpdateIntervalID);
-                dataUpdateIntervalID = null;
-                pauseResumeButton.textContent = '继续刷新';
-                pauseResumeButton.classList.add('pause-button-active');
-                console.log("Data refresh PAUSED");
+                clearInterval(dataUpdateIntervalID); dataUpdateIntervalID = null;
+                pauseResumeButton.textContent = '继续刷新'; pauseResumeButton.classList.add('pause-button-active');
             } else {
-                pauseResumeButton.textContent = '暂停刷新';
-                pauseResumeButton.classList.remove('pause-button-active');
-                updateDisplayAndCharts(); // Refresh immediately
+                pauseResumeButton.textContent = '暂停刷新'; pauseResumeButton.classList.remove('pause-button-active');
+                updateDisplayAndCharts();
                 dataUpdateIntervalID = setInterval(updateDisplayAndCharts, REFRESH_INTERVAL_MS);
-                console.log("Data refresh RESUMED");
             }
         }
 
-        function resetAllZooms() {
+        function resetAllZooms() { /* (Same as before, ensure it updates both charts) */
             if (priceChartInstance) {
-                priceChartInstance.resetZoom();
-                // 重置后,手动清除 options 里的 min/max,确保下次数据更新能自动调整
-                priceChartInstance.options.scales.x.min = undefined;
-                priceChartInstance.options.scales.x.max = undefined;
-                priceChartInstance.update('none'); // 立即应用
+                priceChartInstance.resetZoom(); priceChartInstance.options.scales.x.min = undefined; priceChartInstance.options.scales.x.max = undefined;
             }
             if (diffChartInstance) {
-                diffChartInstance.resetZoom();
-                diffChartInstance.options.scales.x.min = undefined;
-                diffChartInstance.options.scales.x.max = undefined;
-                diffChartInstance.update('none'); // 立即应用
+                diffChartInstance.resetZoom(); diffChartInstance.options.scales.x.min = undefined; diffChartInstance.options.scales.x.max = undefined;
+            }
+            if(isPaused){ updateDisplayAndCharts(); }
+            else {
+                if (priceChartInstance) priceChartInstance.update('none');
+                if (diffChartInstance) diffChartInstance.update('none');
             }
-            // 重置后,如果两个图表X轴范围不同,需要再次同步
-            // 简单起见,下次 updateDisplayAndCharts 时会自动尝试同步(如果需要)
-            // 或者我们可以在 updateDisplayAndCharts 中,如果 isZoomed 为 false,则不设置 min/max
-             if (priceChartInstance && diffChartInstance) {
-                 // 在这里,我们希望两个图表都显示完整的数据范围
-                 // 所以清空min/max后,让下次数据更新(如果是暂停状态,手动触发一次)来重建
-                 if(isPaused){ // 如果是暂停状态,主动更新一下以确保X轴重置并数据重绘
-                    updateDisplayAndCharts();
-                 }
-             }
         }
 
-        // --- Event Listeners ---
         pauseResumeButton.addEventListener('click', togglePauseResume);
+        document.getElementById('reset-price-zoom-button').addEventListener('click', resetAllZooms);
+        document.getElementById('reset-diff-zoom-button').addEventListener('click', resetAllZooms);
 
-        document.getElementById('reset-price-zoom-button').addEventListener('click', () => {
-            resetAllZooms(); // 现在一个按钮重置所有,并尝试同步
-        });
-
-        document.getElementById('reset-diff-zoom-button').addEventListener('click', () => {
-             resetAllZooms(); // 现在一个按钮重置所有,并尝试同步
-        });
-
-        // Initial data load and start interval
-        updateDisplayAndCharts();
+        updateDisplayAndCharts(); // Initial load
         if (!isPaused) {
             dataUpdateIntervalID = setInterval(updateDisplayAndCharts, REFRESH_INTERVAL_MS);
         }
-
     </script>
 </body>
 </html>