├── .gitattributes ├── README.md ├── config.js ├── index.html └── price_monitor.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Backpack Exchange 价格监控工具 - 网页版 2 | 3 | 这是 Backpack Exchange 价格监控工具的网页版本,用于监控现货和永续合约之间的价格差异。本文档专门介绍网页版的使用方法,命令行版本请参考项目根目录的 `README.md`。 4 | 5 | ## 网页版特点 6 | 7 | - 友好的图形用户界面 8 | - 实时价格表格显示 9 | - 可视化配置面板 10 | - 浏览器桌面通知 11 | - 无需安装 Node.js 环境 12 | - 操作日志记录 13 | 14 | ## 使用方法 15 | 16 | 1. 在浏览器中打开 `index.html` 文件 17 | 2. 填入您的 API 密钥和密钥 18 | 3. 配置监控参数(币种、阈值等) 19 | 4. 点击"应用配置"然后"连接"按钮开始监控 20 | 21 | ## 界面说明 22 | 23 | ### 价格表格 24 | 显示实时更新的币种价格信息,包括: 25 | - 现货价格 26 | - 永续合约价格 27 | - 价差(绝对值和百分比) 28 | 29 | ### 配置面板 30 | 在界面下方可以配置以下参数: 31 | 32 | | 配置项 | 说明 | 33 | |-------|------| 34 | | API Key/Secret | 您的 Backpack Exchange API 密钥 | 35 | | 监控币种 | 每行输入一个币种代码,如 BTC、ETH 等 | 36 | | 价差阈值 | 当价差超过此值时触发提醒 | 37 | | 提醒间隔 | 同一币种两次提醒的最小间隔(分钟) | 38 | | 禁用防重复提醒 | 勾选后,只要价差超过阈值就会发出提醒 | 39 | | 显示价格表格 | 是否显示实时价格表格 | 40 | | 刷新间隔 | 界面刷新频率(毫秒) | 41 | 42 | ### 操作按钮 43 | - **应用配置**:保存当前配置并重置提醒状态 44 | - **连接**:连接到 Backpack Exchange 并开始监控 45 | - **断开连接**:停止监控并断开连接 46 | 47 | ### 日志区域 48 | 显示操作日志和提醒记录,方便查看历史提醒。 49 | 50 | ## 价差提醒 51 | 52 | 当价差超过设定阈值时: 53 | 1. 页面上会显示醒目的提醒框 54 | 2. 浏览器会发送桌面通知(需要授予通知权限) 55 | 3. 提醒信息会记录在日志区域 56 | 57 | ## 优化建议 58 | 59 | 如果您发现价差提醒不够频繁,可以: 60 | 1. 勾选"禁用防重复提醒"选项 61 | 2. 减少"提醒间隔"的时间 62 | 3. 降低"价差阈值"的数值 63 | 64 | ## 安全注意事项 65 | 66 | **重要**: 网页版本仅用于演示目的。在浏览器中使用 API 密钥存在安全风险,不建议用于生产环境。在实际应用中,应该使用后端服务进行 API 签名和 WebSocket 连接。 -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | // Backpack Exchange 价格监控配置文件 2 | 3 | export const config = { 4 | // API 配置 5 | apiKey: `写api`, 6 | apiSecret: `写ap`, 7 | 8 | // 要监控的币种列表(可自由添加) 9 | coins: [ 10 | 'BTC', 11 | 'ETH', 12 | 'SOL', 13 | 'LINK', 14 | 'JUP', 15 | // 可在下方添加更多币种 16 | // 'DOGE', 17 | // 'XRP', 18 | // 'ADA', 19 | // 'AVAX' 20 | ], 21 | 22 | // 价差提醒阈值配置 23 | priceAlerts: { 24 | // 价差百分比阈值,超过此值才会显示提醒(例如:0.1 表示 0.1%) 25 | // 设为0则显示所有价差 26 | thresholdPercentage: 0.01, 27 | // 是否禁用防重复机制 28 | disableDeduplication: false, 29 | // 两次提醒的最小间隔(分钟) 30 | alertInterval: 15 31 | }, 32 | 33 | // 显示设置 34 | display: { 35 | // 定时刷新间隔(毫秒) 36 | refreshInterval: 5000, 37 | // 是否在控制台显示当前价格表格 38 | showPriceTable: true 39 | } 40 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Backpack Exchange 价格监控 7 | 120 | 121 | 122 |
123 |

Backpack Exchange 价格监控

124 | 125 |
126 |

状态: 未连接

127 |

更新时间: --

128 |
129 | 130 |
131 |

价差提醒

132 |

133 |

134 |

135 |

136 |

137 |

138 |
139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 |
币种现货价格永续合约价格价差(绝对值)价差(百分比)
154 | 155 |
156 |

配置设置

157 |
158 | 159 | 160 |
161 |
162 | 163 | 164 |
165 |
166 | 167 | 172 |
173 |
174 | 175 | 176 |
177 |
178 | 179 | 180 |
181 |
182 | 185 |
186 |
187 | 190 |
191 |
192 | 193 | 194 |
195 | 196 | 197 | 198 |
199 | 200 |
201 |
准备就绪,请配置并连接...
202 |
203 |
204 | 205 | 644 | 645 | -------------------------------------------------------------------------------- /price_monitor.js: -------------------------------------------------------------------------------- 1 | import { BpxClient } from './bpx-client-main/lib.js' 2 | import { config } from './config.js' 3 | 4 | // 创建客户端实例 5 | const client = new BpxClient({ 6 | apiKey: config.apiKey, 7 | apiSecret: config.apiSecret, 8 | debug: false // 关闭调试信息,减少输出 9 | }) 10 | 11 | // 从配置文件获取币种列表 12 | const coins = config.coins 13 | const spotSymbols = coins.map(coin => `${coin}_USDC`) 14 | const perpSymbols = coins.map(coin => `${coin}_USDC_PERP`) 15 | 16 | // 价格存储 17 | const prices = { 18 | spot: {}, 19 | perp: {} 20 | } 21 | 22 | // 价差阈值 23 | const thresholdPercentage = config.priceAlerts.thresholdPercentage 24 | 25 | // 用于记录已经提醒过的币种价差,避免重复提醒 26 | const alertedPairs = new Set() 27 | 28 | // 显示格式化 29 | const formatPrice = (price) => parseFloat(price).toFixed(2) 30 | const formatPremium = (premium) => { 31 | const value = parseFloat(premium).toFixed(4) 32 | const sign = value > 0 ? '+' : '' 33 | return `${sign}${value}` 34 | } 35 | const formatPercentage = (percentage) => { 36 | const value = parseFloat(percentage).toFixed(4) 37 | const sign = value > 0 ? '+' : '' 38 | return `${sign}${value}%` 39 | } 40 | 41 | // 清除控制台并显示价格表格 42 | function displayPrices() { 43 | if (!config.display.showPriceTable) { 44 | return // 如果配置为不显示价格表,则直接返回 45 | } 46 | 47 | console.clear() // 清屏 48 | 49 | // 计算时间 50 | const now = new Date() 51 | const timeStr = now.toLocaleTimeString() 52 | 53 | // 创建表头 54 | console.log('=================== Backpack Exchange 价格监控 ===================') 55 | console.log(`更新时间: ${timeStr}`) 56 | console.log('------------------------------------------------------------------') 57 | console.log('币种 | 现货价格 | 永续合约价格 | 价差(绝对值) | 价差(百分比)') 58 | console.log('------------------------------------------------------------------') 59 | 60 | // 遍历币种显示价格 61 | for (const coin of coins) { 62 | const spotSymbol = `${coin}_USDC` 63 | const perpSymbol = `${coin}_USDC_PERP` 64 | 65 | const spotPrice = prices.spot[spotSymbol] 66 | const perpPrice = prices.perp[perpSymbol] 67 | 68 | if (spotPrice && perpPrice) { 69 | // 计算价差 70 | const premium = perpPrice - spotPrice 71 | const percentage = (premium / spotPrice) * 100 72 | 73 | // 格式化输出 74 | console.log( 75 | `${coin.padEnd(7)} | ` + 76 | `${formatPrice(spotPrice).padEnd(9)} | ` + 77 | `${formatPrice(perpPrice).padEnd(12)} | ` + 78 | `${formatPremium(premium).padEnd(12)} | ` + 79 | `${formatPercentage(percentage)}` 80 | ) 81 | } else { 82 | console.log(`${coin.padEnd(7)} | 等待数据...`) 83 | } 84 | } 85 | 86 | console.log('------------------------------------------------------------------') 87 | console.log(`* 价差阈值: ${thresholdPercentage}% | 超过阈值会显示提醒`) 88 | console.log('==================================================================') 89 | } 90 | 91 | // 检查价差并显示提醒 92 | function checkPriceDifference(coin, spotPrice, perpPrice) { 93 | // 计算价差 94 | const premium = perpPrice - spotPrice 95 | const percentage = (premium / spotPrice) * 100 96 | const absPercentage = Math.abs(percentage) 97 | 98 | // 阈值检查 99 | if (absPercentage >= thresholdPercentage) { 100 | const now = new Date().toLocaleTimeString() 101 | const alertKey = `${coin}_${Math.floor(percentage * 100)}` 102 | 103 | // 如果这个价差没有被提醒过,或者价差有显著变化,才显示提醒 104 | if (!alertedPairs.has(alertKey)) { 105 | // 添加到已提醒集合 106 | alertedPairs.add(alertKey) 107 | 108 | // 15分钟后自动从已提醒集合中移除,允许再次提醒 109 | setTimeout(() => { 110 | alertedPairs.delete(alertKey) 111 | }, 15 * 60 * 1000) 112 | 113 | // 显示提醒 114 | const premiumType = percentage > 0 ? '溢价' : '折价' 115 | console.log('\n========== 价差提醒 ==========') 116 | console.log(`时间: ${now}`) 117 | console.log(`币种: ${coin}`) 118 | console.log(`现货价格: ${formatPrice(spotPrice)}`) 119 | console.log(`合约价格: ${formatPrice(perpPrice)}`) 120 | console.log(`价差: ${formatPremium(premium)} (${formatPercentage(percentage)})`) 121 | console.log(`提醒: ${coin} 永续合约${premiumType}超过阈值 ${thresholdPercentage}%`) 122 | console.log('==============================\n') 123 | 124 | // 如果在不显示价格表的情况下,需要在控制台保留这条提醒 125 | if (!config.display.showPriceTable) { 126 | // 不需要清屏 127 | } else { 128 | // 5秒后恢复价格表显示 129 | setTimeout(() => { 130 | displayPrices() 131 | }, 5000) 132 | } 133 | 134 | return true 135 | } 136 | } else { 137 | // 如果价差低于阈值,从已提醒集合中移除 138 | const alertKey = `${coin}_${Math.floor(percentage * 100)}` 139 | alertedPairs.delete(alertKey) 140 | } 141 | 142 | return false 143 | } 144 | 145 | // 连接WebSocket并订阅价格 146 | async function main() { 147 | try { 148 | // 连接WebSocket 149 | console.log('正在连接到Backpack Exchange...') 150 | await client.wsConnect() 151 | 152 | // 订阅现货频道 153 | const spotChannels = spotSymbols.map(symbol => `ticker.${symbol}`) 154 | const spotSubscribeParams = { 155 | method: 'SUBSCRIBE', 156 | params: spotChannels, 157 | id: 1 158 | } 159 | 160 | // 订阅永续合约频道 161 | const perpChannels = perpSymbols.map(symbol => `ticker.${symbol}`) 162 | const perpSubscribeParams = { 163 | method: 'SUBSCRIBE', 164 | params: perpChannels, 165 | id: 2 166 | } 167 | 168 | // 发送订阅请求 169 | client.wsSend(spotSubscribeParams) 170 | client.wsSend(perpSubscribeParams) 171 | 172 | // 处理收到的消息 173 | client.ws.on('message', (data) => { 174 | try { 175 | const message = JSON.parse(data.toString()) 176 | 177 | // 处理ticker数据 178 | if (message.data && message.data.e === 'ticker') { 179 | const symbol = message.data.s 180 | const price = parseFloat(message.data.c) 181 | 182 | // 根据符号判断是现货还是合约 183 | if (symbol.endsWith('_PERP')) { 184 | prices.perp[symbol] = price 185 | 186 | // 提取币种名称 187 | const coin = symbol.split('_')[0] 188 | const spotSymbol = `${coin}_USDC` 189 | const spotPrice = prices.spot[spotSymbol] 190 | 191 | // 如果现货价格也有了,检查价差 192 | if (spotPrice) { 193 | checkPriceDifference(coin, spotPrice, price) 194 | } 195 | } else { 196 | prices.spot[symbol] = price 197 | 198 | // 提取币种名称 199 | const coin = symbol.split('_')[0] 200 | const perpSymbol = `${coin}_USDC_PERP` 201 | const perpPrice = prices.perp[perpSymbol] 202 | 203 | // 如果永续合约价格也有了,检查价差 204 | if (perpPrice) { 205 | checkPriceDifference(coin, price, perpPrice) 206 | } 207 | } 208 | 209 | // 更新显示 210 | if (config.display.showPriceTable) { 211 | displayPrices() 212 | } 213 | } 214 | 215 | // 处理错误消息 216 | if (message.error) { 217 | console.error(`订阅错误: ${JSON.stringify(message.error)}`) 218 | } 219 | 220 | } catch (error) { 221 | // 忽略解析错误,不输出到控制台 222 | } 223 | }) 224 | 225 | // 显示初始界面 226 | if (config.display.showPriceTable) { 227 | displayPrices() 228 | } else { 229 | console.log('价格监控已启动,将在价差超过阈值时显示提醒...') 230 | console.log(`当前监控的币种: ${coins.join(', ')}`) 231 | console.log(`价差阈值: ${thresholdPercentage}%`) 232 | } 233 | 234 | // 定期刷新显示(即使没有新数据) 235 | setInterval(() => { 236 | if (config.display.showPriceTable) { 237 | displayPrices() 238 | } 239 | }, config.display.refreshInterval) // 使用配置的刷新间隔 240 | 241 | } catch (error) { 242 | console.error('连接错误:', error.message) 243 | } 244 | } 245 | 246 | // 处理程序退出 247 | process.on('SIGINT', () => { 248 | console.log('\n正在关闭连接...') 249 | if (client.ws) { 250 | client.ws.close() 251 | } 252 | process.exit(0) 253 | }) 254 | 255 | // 启动监控 256 | console.log('启动Backpack Exchange价格监控...') 257 | console.log(`监控币种: ${coins.join(', ')}`) 258 | console.log(`价差阈值: ${thresholdPercentage}%`) 259 | main() --------------------------------------------------------------------------------