aster_user_ws_test.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import 'dotenv/config'
  2. import { AsterAdapter } from '../src/exchanges/aster/asterAdapter'
  3. import { AsterWsClient } from '../src/exchanges/aster/wsClient'
  4. async function main() {
  5. const httpBase = process.env.ASTER_HTTP_BASE || 'https://fapi.asterdex.com'
  6. const wsBase = process.env.ASTER_WS_BASE || 'wss://fstream.asterdex.com'
  7. const user = process.env.ASTER_ORDER_USER || ''
  8. const signer = process.env.ASTER_API_KEY || ''
  9. const pk = process.env.ASTER_API_SECRET || ''
  10. console.log('Env check:', {
  11. ASTER_ORDER_USER: user ? 'SET' : 'NOT_SET',
  12. ASTER_API_KEY: signer ? 'SET' : 'NOT_SET',
  13. ASTER_API_SECRET: pk ? 'SET' : 'NOT_SET',
  14. })
  15. if (!user || !signer || !pk) {
  16. console.error('Missing envs: require ASTER_ORDER_USER / ASTER_API_KEY / ASTER_API_SECRET')
  17. process.exit(1)
  18. }
  19. const adapter = new AsterAdapter({
  20. rpcUrl: '',
  21. chainId: 0,
  22. routerAddress: '',
  23. httpBase,
  24. defaultUser: user,
  25. defaultSigner: signer,
  26. })
  27. // 1) 获取 listenKey
  28. const ensured = await adapter.ensureListenKey()
  29. const listenKey = ensured.listenKey
  30. console.log('listenKey:', listenKey)
  31. // 2) 连接账户 user stream
  32. const client = new AsterWsClient({ wsUrl: wsBase })
  33. client.setUserStream(listenKey, wsBase)
  34. client.on('open', () => console.log('[WS] connected'))
  35. client.on('close', (c, r) => console.log('[WS] closed', c, r))
  36. client.on('error', e => console.log('[WS] error', e))
  37. client.on('ws_error', e => console.log('[WS] ws_error', e))
  38. // 原始与标准化事件
  39. client.on('raw', m => console.log('[WS] raw', JSON.stringify(m)))
  40. client.on('balance', p => console.log('[WS] balance', JSON.stringify(p)))
  41. client.on('account_positions', p => console.log('[WS] positions', JSON.stringify(p)))
  42. client.on('account_info', p => console.log('[WS] account_info', JSON.stringify(p)))
  43. client.on('orders', p => console.log('[WS] orders', JSON.stringify(p)))
  44. client.connectUserStream()
  45. // 可选:自动触发一笔很小的下单来验证账户事件(设置 ASTER_WS_TRIGGER_ORDER=1 生效)
  46. if (process.env.ASTER_WS_TRIGGER_ORDER === '1') {
  47. try {
  48. const symbol = process.env.ASTER_TEST_SYMBOL || 'BTCUSDT'
  49. const url = `${httpBase.replace(/\/$/, '')}/fapi/v3/order`
  50. // 给 WS 一点时间完成握手
  51. await new Promise(r => setTimeout(r, 1500))
  52. async function postOrder(formFields: Record<string, string>) {
  53. return await fetch(url, {
  54. method: 'POST',
  55. headers: { 'content-type': 'application/x-www-form-urlencoded' } as any,
  56. body: new URLSearchParams(formFields as any).toString(),
  57. } as any)
  58. }
  59. async function tryOpenClose(positionSide: 'BOTH' | 'LONG' | 'SHORT') {
  60. const qty = process.env.ASTER_TEST_QTY || '0.001'
  61. const openSide = 'BUY'
  62. const closeSide = 'SELL'
  63. const openSig = await adapter.generateOrderSignature(
  64. {
  65. symbol,
  66. positionSide,
  67. type: 'MARKET',
  68. side: openSide,
  69. quantity: qty,
  70. timeInForce: undefined,
  71. },
  72. { user, signer, privateKey: pk },
  73. )
  74. const res1 = await postOrder(openSig.formFields)
  75. const txt1 = await res1.text()
  76. console.log(`[TRIGGER ${positionSide}] open`, res1.status, txt1)
  77. // 若开仓失败,直接返回 false 以便上层 fallback
  78. if (!res1.ok) return false
  79. // 给 WS 一点时间推送
  80. await new Promise(r => setTimeout(r, 1000))
  81. const closeSig = await adapter.generateOrderSignature(
  82. {
  83. symbol,
  84. positionSide,
  85. type: 'MARKET',
  86. side: closeSide,
  87. quantity: qty,
  88. timeInForce: undefined,
  89. // 对于 LONG/SHORT 情况,SELL 将会减仓
  90. },
  91. { user, signer, privateKey: pk },
  92. )
  93. const res2 = await postOrder(closeSig.formFields)
  94. const txt2 = await res2.text()
  95. console.log(`[TRIGGER ${positionSide}] close`, res2.status, txt2)
  96. return res2.ok
  97. }
  98. // 顺序尝试:BOTH -> LONG -> SHORT
  99. const okBoth = await tryOpenClose('BOTH')
  100. if (!okBoth) {
  101. const okLong = await tryOpenClose('LONG')
  102. if (!okLong) {
  103. await tryOpenClose('SHORT')
  104. }
  105. }
  106. } catch (e) {
  107. console.log('[TRIGGER] error', (e as any)?.message || e)
  108. }
  109. }
  110. // 保持 60 秒观察
  111. setTimeout(() => {
  112. console.log('Done.')
  113. process.exit(0)
  114. }, 60000)
  115. }
  116. main().catch(e => {
  117. console.error(e)
  118. process.exit(1)
  119. })