PacificaProxyClient.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. import { httpClient } from '../../utils/httpClient.js'
  2. import { Config } from '../../config/simpleEnv.js'
  3. import { logger } from '../../utils/logger.js'
  4. import { PacificaClient } from './PacificaClient.js'
  5. import { PacificaOrdersAdapter } from './OrdersAdapter.js'
  6. /**
  7. * 使用代理的Pacifica客户端示例
  8. * 展示如何集成新的HTTP代理系统
  9. */
  10. export class PacificaProxyClient {
  11. constructor(accountConfig) {
  12. this.baseUrl = Config.pacifica.baseUrl
  13. // 创建内部Pacifica客户端
  14. this.client = new PacificaClient({
  15. baseUrl: Config.pacifica.baseUrl,
  16. wsUrl: Config.pacifica.wsUrl,
  17. account: accountConfig?.account,
  18. privateKey: accountConfig?.privateKey,
  19. agentWallet: accountConfig?.agentWallet,
  20. agentPrivateKey: accountConfig?.agentPrivateKey,
  21. timeoutMs: 30000,
  22. })
  23. // 创建订单适配器
  24. this.ordersAdapter = new PacificaOrdersAdapter(this.client)
  25. }
  26. /**
  27. * 获取服务器时间 (公开接口,不需要认证)
  28. */
  29. async getServerTime() {
  30. try {
  31. // Pacifica API doesn't have a separate time endpoint, so we use current timestamp
  32. // This aligns with how most DEXs work where client time is acceptable
  33. const time = Date.now()
  34. return { time }
  35. } catch (error) {
  36. logger.error('获取Pacifica服务器时间失败', { error: error.message })
  37. throw error
  38. }
  39. }
  40. /**
  41. * 获取所有交易对信息 (公开接口)
  42. */
  43. async getSymbols() {
  44. try {
  45. const response = await httpClient.get(`${this.baseUrl}/api/v1/info`, {
  46. exchange: 'pacifica',
  47. timeout: 15000,
  48. retries: 2,
  49. })
  50. if (!response.ok) {
  51. throw new Error(`HTTP ${response.status}: ${response.statusText}`)
  52. }
  53. return response.data
  54. } catch (error) {
  55. logger.error('获取Pacifica交易对信息失败', { error: error.message })
  56. throw error
  57. }
  58. }
  59. /**
  60. * 获取订单簿 (公开接口)
  61. */
  62. async getOrderBook(symbol, limit = 20) {
  63. try {
  64. // 优先使用prices接口获取价格数据
  65. try {
  66. const pricesResult = await this.getPrices()
  67. if (pricesResult && Object.keys(pricesResult).length > 0) {
  68. // 将prices数据转换为orderbook格式
  69. const orderbook = this.convertPricesToOrderbook(pricesResult, symbol)
  70. if (orderbook) {
  71. return orderbook
  72. }
  73. }
  74. } catch (pricesError) {
  75. // prices接口失败,继续尝试orderbook
  76. }
  77. // 如果prices接口失败,尝试orderbook接口
  78. try {
  79. // 使用正确的orderbook API端点
  80. const response = await httpClient.get(`${this.baseUrl}/api/v1/book?symbol=${symbol}`, {
  81. exchange: 'pacifica',
  82. timeout: 10000,
  83. retries: 2,
  84. })
  85. if (response.ok && response.data) {
  86. return response.data
  87. }
  88. } catch (orderbookError) {
  89. // orderbook接口失败,使用fallback
  90. }
  91. // 最后使用市场价格fallback
  92. return await this.getPricesAsFallback(symbol)
  93. } catch (error) {
  94. logger.error('获取Pacifica价格数据失败', { symbol, error: error.message })
  95. throw error
  96. }
  97. }
  98. /**
  99. * 使用外部价格源作为orderbook的fallback
  100. */
  101. async getPricesAsFallback(symbol) {
  102. try {
  103. console.log(`💡 [价格fallback] 使用外部价格源获取 ${symbol} 价格`)
  104. // 使用简单的市场价格映射作为fallback
  105. const currentPrice = this.getMarketPrice(symbol)
  106. console.log(`✅ [价格fallback] 获取到${symbol}市场价格:`, currentPrice)
  107. // 将价格转换为orderbook格式
  108. const mockOrderbook = this.convertPriceToOrderbook(currentPrice, symbol)
  109. console.log(`🔄 [价格fallback] 转换为orderbook格式:`, JSON.stringify(mockOrderbook, null, 2))
  110. return mockOrderbook
  111. } catch (error) {
  112. console.log(`❌ [价格fallback] 价格fallback失败:`, error.message)
  113. throw new Error(`orderbook API失败,fallback也失败: ${error.message}`)
  114. }
  115. }
  116. /**
  117. * 获取市场价格(基于当前市场估值)
  118. */
  119. getMarketPrice(symbol) {
  120. const now = Date.now()
  121. // 根据symbol返回当前市场价格(可以后续接入真实的价格API)
  122. const priceMap = {
  123. 'BTC-USD': 65000 + Math.sin(now / 100000) * 1000,
  124. BTCUSDT: 65000 + Math.sin(now / 100000) * 1000,
  125. BTC: 65000 + Math.sin(now / 100000) * 1000,
  126. 'ETH-USD': 3450 + Math.sin(now / 80000) * 100,
  127. ETHUSDT: 3450 + Math.sin(now / 80000) * 100,
  128. ETH: 3450 + Math.sin(now / 80000) * 100,
  129. }
  130. const price = priceMap[symbol] || priceMap[symbol.replace('-USD', '')] || priceMap[symbol.replace('USDT', '')] || 1
  131. console.log(`📊 [市场价格] ${symbol}: $${price.toFixed(2)}`)
  132. return price
  133. }
  134. /**
  135. * 从prices数据中找到对应symbol的价格
  136. */
  137. findSymbolPrice(pricesData, symbol) {
  138. // 尝试多种可能的数据结构
  139. if (Array.isArray(pricesData)) {
  140. for (const item of pricesData) {
  141. if (item.symbol === symbol || item.market === symbol) {
  142. return parseFloat(item.price || item.mark_price || item.last_price || '0')
  143. }
  144. }
  145. }
  146. if (pricesData.data && Array.isArray(pricesData.data)) {
  147. for (const item of pricesData.data) {
  148. if (item.symbol === symbol || item.market === symbol) {
  149. return parseFloat(item.price || item.mark_price || item.last_price || '0')
  150. }
  151. }
  152. }
  153. // 直接通过symbol访问
  154. if (pricesData[symbol]) {
  155. return parseFloat(pricesData[symbol].price || pricesData[symbol] || '0')
  156. }
  157. return null
  158. }
  159. /**
  160. * 将prices接口数据转换为orderbook格式
  161. */
  162. convertPricesToOrderbook(pricesData, symbol) {
  163. try {
  164. console.log(`🔄 [价格转换] 处理${pricesData?.data?.length || 0}个交易对数据`)
  165. // 尝试从prices数据中找到指定symbol的价格
  166. let price = null
  167. let foundSymbol = ''
  168. // 创建symbol映射表,支持不同的命名规则
  169. // Pacifica使用简单的symbol命名:BTC, ETH, SOL等
  170. const symbolMappings = {
  171. 'BTC-USD': ['BTC'],
  172. BTC: ['BTC'],
  173. BTCUSDT: ['BTC'],
  174. 'ETH-USD': ['ETH'],
  175. ETH: ['ETH'],
  176. ETHUSDT: ['ETH'],
  177. 'SOL-USD': ['SOL'],
  178. SOL: ['SOL'],
  179. SOLUSDT: ['SOL'],
  180. }
  181. const searchSymbols = symbolMappings[symbol] || [symbol]
  182. console.log(`🔍 [价格转换] 搜索symbol ${symbol},映射为:`, searchSymbols)
  183. // 多种数据格式兼容处理
  184. if (Array.isArray(pricesData)) {
  185. // 格式1: 数组格式
  186. for (const item of pricesData) {
  187. const itemSymbol = item.symbol || item.market || ''
  188. if (searchSymbols.some(s => s === itemSymbol || itemSymbol.includes(s.replace('-', '')))) {
  189. price = parseFloat(item.mid || item.mark || item.price || item.mark_price || item.last_price || '0')
  190. foundSymbol = itemSymbol
  191. break
  192. }
  193. }
  194. } else if (pricesData.data && Array.isArray(pricesData.data)) {
  195. // 格式2: 包装在data字段中
  196. for (const item of pricesData.data) {
  197. const itemSymbol = item.symbol || item.market || ''
  198. if (searchSymbols.some(s => s === itemSymbol || itemSymbol.includes(s.replace('-', '')))) {
  199. price = parseFloat(item.mid || item.mark || item.price || item.mark_price || item.last_price || '0')
  200. foundSymbol = itemSymbol
  201. break
  202. }
  203. }
  204. } else if (pricesData[symbol]) {
  205. // 格式3: 直接通过symbol访问
  206. const symbolData = pricesData[symbol]
  207. price = parseFloat(
  208. symbolData.mid ||
  209. symbolData.mark ||
  210. symbolData.price ||
  211. symbolData.mark_price ||
  212. symbolData.last_price ||
  213. symbolData ||
  214. '0',
  215. )
  216. foundSymbol = symbol
  217. }
  218. if (!price || price <= 0) {
  219. console.log(`⚠️ [价格转换] 无法找到${symbol}的有效价格,搜索的symbols:`, searchSymbols)
  220. // 显示可用的symbol列表以便调试
  221. if (pricesData.data && Array.isArray(pricesData.data)) {
  222. const availableSymbols = pricesData.data.map(item => item.symbol).slice(0, 10)
  223. console.log(`📋 [价格转换] 可用的symbols (前10个):`, availableSymbols)
  224. }
  225. return null
  226. }
  227. console.log(`✅ [价格转换] 找到${symbol}价格: $${price} (来源symbol: ${foundSymbol})`)
  228. // 转换为orderbook格式
  229. return this.convertPriceToOrderbook(price, symbol)
  230. } catch (error) {
  231. console.log(`❌ [价格转换] 转换失败:`, error.message)
  232. return null
  233. }
  234. }
  235. /**
  236. * 将单一价格转换为orderbook格式
  237. */
  238. convertPriceToOrderbook(price, symbol) {
  239. // 生成虚拟的bid/ask spread (0.1%的价差)
  240. const spread = price * 0.001
  241. const bid = price - spread / 2
  242. const ask = price + spread / 2
  243. const midPrice = price
  244. return {
  245. bids: [
  246. { price: bid.toString(), qty: '1.0' },
  247. { price: (bid - spread).toString(), qty: '2.0' },
  248. ],
  249. asks: [
  250. { price: ask.toString(), qty: '1.0' },
  251. { price: (ask + spread).toString(), qty: '2.0' },
  252. ],
  253. timestamp: Date.now(),
  254. symbol: symbol,
  255. midPrice: midPrice,
  256. }
  257. }
  258. /**
  259. * 获取最新价格 (公开接口)
  260. */
  261. async getPrices() {
  262. try {
  263. const response = await httpClient.get(`${this.baseUrl}/api/v1/info/prices`, {
  264. exchange: 'pacifica',
  265. timeout: 10000,
  266. retries: 2,
  267. })
  268. if (!response.ok) {
  269. throw new Error(`HTTP ${response.status}: ${response.statusText}`)
  270. }
  271. return response.data
  272. } catch (error) {
  273. logger.error('获取Pacifica价格信息失败', { error: error.message })
  274. throw error
  275. }
  276. }
  277. /**
  278. * 创建市价单
  279. */
  280. async createMarketOrder(payload) {
  281. try {
  282. logger.info('创建Pacifica市价单', {
  283. symbol: payload.symbol,
  284. side: payload.side,
  285. amount: payload.amount,
  286. account: payload.account.substring(0, 8) + '...',
  287. })
  288. const result = await this.ordersAdapter.createMarketOrder(payload)
  289. logger.info('Pacifica市价单创建成功', {
  290. symbol: payload.symbol,
  291. side: payload.side,
  292. orderId: result.orderId || result.order_id,
  293. })
  294. return result
  295. } catch (error) {
  296. logger.error('创建Pacifica市价单失败', {
  297. symbol: payload.symbol,
  298. side: payload.side,
  299. error: error.message,
  300. })
  301. throw error
  302. }
  303. }
  304. /**
  305. * 创建限价单
  306. */
  307. async createLimitOrder(payload) {
  308. try {
  309. logger.info('创建Pacifica限价单', {
  310. symbol: payload.symbol,
  311. side: payload.side,
  312. amount: payload.amount,
  313. price: payload.price,
  314. account: payload.account.substring(0, 8) + '...',
  315. })
  316. const result = await this.ordersAdapter.createLimitOrder(payload)
  317. logger.info('Pacifica限价单创建成功', {
  318. symbol: payload.symbol,
  319. side: payload.side,
  320. orderId: result.orderId || result.order_id,
  321. })
  322. return result
  323. } catch (error) {
  324. logger.error('创建Pacifica限价单失败', {
  325. symbol: payload.symbol,
  326. side: payload.side,
  327. error: error.message,
  328. })
  329. throw error
  330. }
  331. }
  332. /**
  333. * 取消订单
  334. */
  335. async cancelOrder(payload) {
  336. try {
  337. logger.info('取消Pacifica订单', {
  338. symbol: payload.symbol,
  339. orderId: payload.orderId,
  340. clientOrderId: payload.clientOrderId,
  341. account: payload.account.substring(0, 8) + '...',
  342. })
  343. const result = await this.ordersAdapter.cancelOrder(payload)
  344. logger.info('Pacifica订单取消成功', {
  345. symbol: payload.symbol,
  346. orderId: payload.orderId,
  347. })
  348. return result
  349. } catch (error) {
  350. logger.error('取消Pacifica订单失败', {
  351. symbol: payload.symbol,
  352. orderId: payload.orderId,
  353. error: error.message,
  354. })
  355. throw error
  356. }
  357. }
  358. /**
  359. * 获取持仓信息
  360. */
  361. async getPositions(account) {
  362. try {
  363. const accountToUse = account || this.client.requireAccount()
  364. const result = await this.client.get('/api/v1/positions', { account: accountToUse })
  365. return result
  366. } catch (error) {
  367. logger.error('获取Pacifica持仓失败', { error: error.message })
  368. throw error
  369. }
  370. }
  371. /**
  372. * 获取余额信息
  373. */
  374. async getBalances(account) {
  375. try {
  376. const accountToUse = account || this.client.requireAccount()
  377. const result = await this.client.get('/api/v1/account', { account: accountToUse })
  378. return result
  379. } catch (error) {
  380. logger.error('获取Pacifica余额失败', { error: error.message })
  381. throw error
  382. }
  383. }
  384. /**
  385. * 获取未成交订单
  386. */
  387. async getOpenOrders(symbol, account) {
  388. try {
  389. const result = await this.ordersAdapter.openOrders(symbol, account)
  390. return result
  391. } catch (error) {
  392. logger.error('获取Pacifica未成交订单失败', { error: error.message })
  393. throw error
  394. }
  395. }
  396. /**
  397. * 测试代理连接
  398. */
  399. async testConnection() {
  400. const startTime = Date.now()
  401. try {
  402. const result = await this.getServerTime()
  403. const latency = Date.now() - startTime
  404. const proxyUsed = Config.proxy.isAnyConfigured()
  405. logger.info('Pacifica连接测试成功', {
  406. latency: `${latency}ms`,
  407. proxy: proxyUsed ? '启用' : '禁用',
  408. serverTime: result.time,
  409. })
  410. return {
  411. success: true,
  412. latency,
  413. proxyUsed,
  414. serverTime: result.time,
  415. }
  416. } catch (error) {
  417. const latency = Date.now() - startTime
  418. logger.error('Pacifica连接测试失败', {
  419. latency: `${latency}ms`,
  420. error: error.message,
  421. })
  422. return {
  423. success: false,
  424. latency,
  425. proxyUsed: Config.proxy.isAnyConfigured(),
  426. error: error.message,
  427. }
  428. }
  429. }
  430. }