├── .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()
--------------------------------------------------------------------------------