skyffire 6 months ago
parent
commit
e6b74a2b70
2 changed files with 86 additions and 20 deletions
  1. 9 8
      price_checker.py
  2. 77 12
      templates/index_plotly_sol.html

+ 9 - 8
price_checker.py

@@ -14,12 +14,12 @@ from plotly.utils import PlotlyJSONEncoder
 # --- 配置部分 ---
 # OpenOcean (假设 'SOL' 是输入, 'RFC' 是输出, 得到 RFC/SOL 价格)
 IN_TOKEN_ADDRESS_SOLANA = 'So11111111111111111111111111111111111111112'  # SOL Mint Address
-OUT_TOKEN_ADDRESS_SOLANA = 'C3DwDjT17gDvvCYC2nsdGHxDHVmQRdhKfpAdqQ29pump'  # RFC Mint Address (示例)
+OUT_TOKEN_ADDRESS_SOLANA = 'DitHyRMQiSDhn5cnKMJV2CDDt6sVct96YrECiM49pump'  # RFC Mint Address (示例)
 AMOUNT_TO_QUERY_OPENOCEAN_IN_SOL = decimal.Decimal('10')  # 例如用 10 SOL 去查询能买多少 RFC
 
 # Gate.io (RFC/USDT)
-GATEIO_SPOT_PAIR_RFC_USDT = 'RFC_USDT'
-GATEIO_FUTURES_CONTRACT_RFC_USDT = 'RFC_USDT'
+GATEIO_SPOT_PAIR_RFC_USDT = 'HOUSE_USDT'
+GATEIO_FUTURES_CONTRACT_RFC_USDT = 'HOUSE_USDT'
 
 # Binance (SOL/USDT - 用于转换)
 BINANCE_SOL_USDT_PAIR = 'SOLUSDT'
@@ -67,8 +67,8 @@ def get_gateio_spot_price_usdt(pair_symbol):  # 获取 RFC/USDT
         data = r.json()
         if isinstance(data, list) and data:
             td = data[0]
-            if td.get('currency_pair') == pair_symbol and td.get('last'):
-                return {"price_rfc_usdt": decimal.Decimal(td['last'])}
+            if td.get('currency_pair') == pair_symbol and td.get('lowest_ask'):
+                return {"price_rfc_usdt": decimal.Decimal(td['lowest_ask'])}
             else:
                 return {"error": f"Gate现货({pair_symbol})数据不匹配或无价格"}
         elif isinstance(data, dict) and data.get('label'):
@@ -88,8 +88,9 @@ def get_gateio_futures_price_usdt(contract_symbol, settle_currency='usdt'):  # 
         data = r.json()
         if isinstance(data, list) and data:
             td = data[0]
-            if td.get('contract') == contract_symbol and td.get('last'):
-                return {"price_rfc_usdt": decimal.Decimal(td['last'])}
+            if td.get('contract') == contract_symbol and td.get('highest_bid'):
+
+                return {"price_rfc_usdt": decimal.Decimal(td['highest_bid'])}
             else:
                 return {"error": f"Gate期货({contract_symbol})数据不匹配或无价格"}
         elif isinstance(data, dict) and data.get('label'):
@@ -123,7 +124,7 @@ app = Flask(__name__)
 log = logging.getLogger('werkzeug');
 log.setLevel(logging.ERROR)
 
-MAX_HISTORY_POINTS_PLOTLY = 300
+MAX_HISTORY_POINTS_PLOTLY = 86400
 REFRESH_INTERVAL_SECONDS = 1
 
 historical_data_points = deque(maxlen=MAX_HISTORY_POINTS_PLOTLY)

+ 77 - 12
templates/index_plotly_sol.html

@@ -6,6 +6,7 @@
     <title>{{ target_asset }}/SOL 多平台价格监控 (Plotly)</title>
     <script src='https://cdn.plot.ly/plotly-latest.min.js'></script>
     <style>
+        /* CSS样式与之前相同,此处省略以保持简洁 */
         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; margin-top:10px; margin-bottom:15px; }
@@ -29,6 +30,7 @@
     </style>
 </head>
 <body>
+    <!-- HTML 结构与之前相同,此处省略 -->
     <div class="container">
         <h1 id="main-title">{{ target_asset }}/SOL 多平台价格监控</h1>
         <div class="controls-container">
@@ -116,13 +118,16 @@
 
         let pricePlotInitialized = false;
         let diffPlotInitialized = false;
+        let isSyncingLayout = false; // Flag to prevent event loops
 
-        function formatPriceForTable(priceStr, precision = 8) { // Default precision 8 for SOL prices
+        // --- Helper functions (formatPriceForTable, formatPercentageForTable) ---
+        // (与之前版本相同)
+        function formatPriceForTable(priceStr, precision = 8) {
             if (priceStr === null || priceStr === undefined || String(priceStr).toLowerCase() === "n/a") return "N/A";
             const price = parseFloat(priceStr);
             if (isNaN(price)) return "N/A";
             if (price === 0) return (0).toFixed(precision);
-            if (Math.abs(price) < 1e-9 && price !== 0) return price.toExponential(3); // Very small numbers
+            if (Math.abs(price) < 1e-9 && price !== 0) return price.toExponential(3);
             return price.toFixed(precision);
         }
         function formatPercentageForTable(percStr) {
@@ -133,7 +138,45 @@
             return `${perc > 0 ? '+' : ''}${perc.toFixed(4)}%`;
         }
 
-        async function updateTableData() {
+        // --- Function to sync X-axis between two Plotly charts ---
+        function syncPlotlyXAxes(sourceDiv, targetDiv, eventData) {
+            if (isSyncingLayout) return; // Prevent infinite loop
+            isSyncingLayout = true;
+
+            const update = {};
+            if (eventData && eventData['xaxis.range[0]'] !== undefined) {
+                // Zoom/Pan event
+                update['xaxis.range[0]'] = eventData['xaxis.range[0]'];
+                update['xaxis.range[1]'] = eventData['xaxis.range[1]'];
+            } else if (eventData && eventData['xaxis.autorange']) {
+                // Double-click to reset zoom (autorange)
+                update['xaxis.autorange'] = true;
+            } else {
+                // Fallback: try to get range from source layout if eventData is not specific enough
+                // This might not always be reliable if eventData is empty or non-standard
+                if (sourceDiv.layout && sourceDiv.layout.xaxis && sourceDiv.layout.xaxis.range) {
+                     update['xaxis.range[0]'] = sourceDiv.layout.xaxis.range[0];
+                     update['xaxis.range[1]'] = sourceDiv.layout.xaxis.range[1];
+                } else {
+                    isSyncingLayout = false;
+                    return; // Not enough info to sync
+                }
+            }
+
+            // Only apply if a valid update is constructed
+            if (Object.keys(update).length > 0) {
+                Plotly.relayout(targetDiv, update).then(() => {
+                    isSyncingLayout = false;
+                }).catch(e => {
+                    console.error("Error syncing layout:", e);
+                    isSyncingLayout = false;
+                });
+            } else {
+                 isSyncingLayout = false;
+            }
+        }
+
+        async function updateTableData() { /* (与之前版本相同) */
             if (isPaused) return;
             try {
                 const response = await fetch('/table-data');
@@ -143,7 +186,7 @@
                 document.getElementById('oo-rfc-sol-price').textContent = formatPriceForTable(data.oo_rfc_sol_price);
                 document.getElementById('gate-spot-rfc-sol-price').textContent = formatPriceForTable(data.gate_spot_rfc_sol_price);
                 document.getElementById('gate-futures-rfc-sol-price').textContent = formatPriceForTable(data.gate_futures_rfc_sol_price);
-                document.getElementById('sol-usdt-price-binance').textContent = formatPriceForTable(data.sol_usdt_price_binance, 4); // SOL/USDT with 4 decimals
+                document.getElementById('sol-usdt-price-binance').textContent = formatPriceForTable(data.sol_usdt_price_binance, 4);
 
                 document.getElementById('oo-status').textContent = data.oo_error || '正常';
                 document.getElementById('gate-spot-status').textContent = data.gate_spot_error || '正常';
@@ -169,12 +212,8 @@
                     if (!isNaN(val)) { el.className = val > 0 ? 'price-up' : (val < 0 ? 'price-down' : ''); }
                      else { el.className = ''; }
                 });
-
                 document.getElementById('last-updated').textContent = data.last_updated || "N/A";
-
-            } catch (error) {
-                console.error('Error fetching table data:', error);
-            }
+            } catch (error) { console.error('Error fetching table data:', error); }
         }
 
         async function updatePlotlyCharts() {
@@ -184,15 +223,40 @@
                 if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
                 const chartData = await response.json();
 
+                const currentPriceXRange = priceChartDiv.layout ? priceChartDiv.layout.xaxis.range : undefined;
+                const currentDiffXRange = diffChartDiv.layout ? diffChartDiv.layout.xaxis.range : undefined;
+
                 if (chartData.price_chart && chartData.price_chart.data && chartData.price_chart.layout) {
+                    // Preserve X-axis range if user has zoomed/panned, unless paused and it's first draw
+                    if (pricePlotInitialized && !isPaused && currentPriceXRange) {
+                        // chartData.price_chart.layout.xaxis.range = currentPriceXRange; // This might reset if autorange is true
+                        // Let Plotly.react handle it, it preserves zoom by default if data length doesn't change drastically or x values change
+                    }
                     Plotly.react(priceChartDiv, chartData.price_chart.data, chartData.price_chart.layout, {responsive: true});
-                    if (!pricePlotInitialized) pricePlotInitialized = true;
+                    if (!pricePlotInitialized) {
+                        pricePlotInitialized = true;
+                        // Add event listener AFTER first plot
+                        priceChartDiv.on('plotly_relayout', (eventData) => {
+                             console.log('Price chart relayout:', eventData);
+                             syncPlotlyXAxes(priceChartDiv, diffChartDiv, eventData);
+                        });
+                    }
                     priceChartStatusDiv.textContent = `价格图表更新于: ${new Date().toLocaleTimeString()}`;
                 } else { priceChartStatusDiv.textContent = "错误: 价格图表数据无效。"; }
 
                 if (chartData.diff_chart && chartData.diff_chart.data && chartData.diff_chart.layout) {
+                    if (diffPlotInitialized && !isPaused && currentDiffXRange) {
+                        // chartData.diff_chart.layout.xaxis.range = currentDiffXRange;
+                    }
                     Plotly.react(diffChartDiv, chartData.diff_chart.data, chartData.diff_chart.layout, {responsive: true});
-                     if (!diffPlotInitialized) diffPlotInitialized = true;
+                     if (!diffPlotInitialized) {
+                        diffPlotInitialized = true;
+                        // Add event listener AFTER first plot
+                        diffChartDiv.on('plotly_relayout', (eventData) => {
+                            console.log('Diff chart relayout:', eventData);
+                            syncPlotlyXAxes(diffChartDiv, priceChartDiv, eventData);
+                        });
+                    }
                     diffChartStatusDiv.textContent = `价差图表更新于: ${new Date().toLocaleTimeString()}`;
                 } else { diffChartStatusDiv.textContent = "错误: 价差图表数据无效。"; }
 
@@ -203,7 +267,7 @@
             }
         }
 
-        function togglePauseResume() {
+        function togglePauseResume() { /* (与之前版本相同) */
             isPaused = !isPaused;
             if (isPaused) {
                 if (dataUpdateIntervalID) clearInterval(dataUpdateIntervalID); dataUpdateIntervalID = null;
@@ -216,6 +280,7 @@
         }
         pauseResumeButton.addEventListener('click', togglePauseResume);
 
+        // --- Initial Load ---
         updateTableData(); updatePlotlyCharts();
         if (!isPaused) {
             dataUpdateIntervalID = setInterval(() => { updateTableData(); updatePlotlyCharts(); }, refreshIntervalMs);