Skip to content

Commit bb8e25a

Browse files
committed
feat: add gateio subclass
1 parent a54b360 commit bb8e25a

File tree

1 file changed

+192
-0
lines changed

1 file changed

+192
-0
lines changed

src/exchanges/gateio.js

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
const Exchange = require('../exchange')
2+
const WebSocket = require('websocket').w3cwebsocket
3+
const axios = require('axios')
4+
5+
class GateIO extends Exchange {
6+
constructor() {
7+
super()
8+
9+
this.id = 'GATEIO'
10+
this.endpoints = {
11+
PRODUCTS: [
12+
'https://api.gateio.ws/api/v4/spot/currency_pairs',
13+
'https://api.gateio.ws/api/v4/futures/usdt/contracts'
14+
]
15+
}
16+
this.types = {}
17+
this.multipliers = {}
18+
}
19+
20+
async getUrl(pair) {
21+
// https://www.gate.io/docs/developers/apiv4/ws/en/
22+
const type = this.types[pair]
23+
if (type === 'spot') return 'wss://ws.gate.io/v3/'
24+
if (type === 'futures') return 'wss://fx-ws.gateio.ws/v4/ws/usdt'
25+
}
26+
27+
formatProducts(response) {
28+
const products = []
29+
const multipliers = {}
30+
const types = {}
31+
32+
response.forEach((data, index) => {
33+
const type = ['spot', 'futures'][index]
34+
35+
data.forEach(product => {
36+
let pair
37+
switch (type) {
38+
case 'spot':
39+
pair = product.id + '_SPOT'
40+
multipliers[pair] = parseFloat(product.min_base_amount)
41+
break
42+
case 'futures':
43+
pair = product.name + '_FUTURES'
44+
multipliers[pair] = parseFloat(product.quanto_multiplier)
45+
break
46+
}
47+
48+
if (products.find(a => a.toLowerCase() === pair.toLowerCase())) {
49+
throw new Error(
50+
'Duplicate pair detected on gateio exchange (' + pair + ')'
51+
)
52+
}
53+
54+
types[pair] = type
55+
products.push(pair)
56+
})
57+
})
58+
return {
59+
products,
60+
multipliers,
61+
types
62+
}
63+
}
64+
65+
/**
66+
* Sub
67+
* @param {WebSocket} api
68+
* @param {string} pair
69+
*/
70+
async subscribe(api, pair) {
71+
// Public Trades Channel
72+
// https://www.gate.io/docs/developers/apiv4/ws/en/#public-trades-channel
73+
if (!(await super.subscribe(api, pair))) {
74+
return
75+
}
76+
77+
if (this.types[pair] === 'spot') {
78+
api.send(
79+
JSON.stringify({
80+
method: `trades.subscribe`,
81+
params: [pair.split('_').slice(0, 2).join('_')]
82+
})
83+
)
84+
}
85+
if (this.types[pair] === 'futures') {
86+
api.send(
87+
JSON.stringify({
88+
time: Date.now(),
89+
channel: `${this.types[pair]}.trades`,
90+
event: 'subscribe',
91+
payload: [pair.split('_').slice(0, 2).join('_')]
92+
})
93+
)
94+
}
95+
96+
return true
97+
}
98+
99+
/**
100+
* Sub
101+
* @param {WebSocket} api
102+
* @param {string} pair
103+
*/
104+
async unsubscribe(api, pair) {
105+
if (!(await super.unsubscribe(api, pair))) {
106+
return
107+
}
108+
109+
if (this.types[pair] === 'spot') {
110+
api.send(
111+
JSON.stringify({
112+
method: `trades.unsubscribe`,
113+
params: [pair.split('_').slice(0, 2).join('_')]
114+
})
115+
)
116+
} else if (this.types[pair] === 'futures') {
117+
api.send(
118+
JSON.stringify({
119+
time: Date.now(),
120+
channel: `${this.types[pair]}.trades`,
121+
event: 'unsubscribe',
122+
payload: [pair.split('_').slice(0, 2).join('_')]
123+
})
124+
)
125+
}
126+
127+
return true
128+
}
129+
130+
formatPerpTrade(trade, channel) {
131+
return {
132+
exchange: this.id,
133+
pair: trade.contract + '_' + channel,
134+
timestamp: +new Date(trade.create_time_ms),
135+
price: +trade.price,
136+
size: +(
137+
Math.abs(trade.size) * this.multipliers[trade.contract + '_' + channel]
138+
),
139+
side: trade.size > 0 ? 'buy' : 'sell'
140+
}
141+
}
142+
143+
formatSpotTrade()
144+
145+
onMessage(event, api) {
146+
const json = JSON.parse(event.data)
147+
148+
if (!json) {
149+
return
150+
}
151+
152+
let trades = []
153+
154+
const { channel, event, result, method, params } = json
155+
156+
isPerpChannel = () => (
157+
channel && channel?.endsWith('trades') &&
158+
event && event === 'update' &&
159+
result && result?.length
160+
)
161+
162+
163+
isSpotChannel = () =>
164+
method === 'trades.update' &&
165+
Array.isArray(params)
166+
167+
if (isPerpChannel()) {
168+
trades = result.map(trade => this.formatPerpTrade(
169+
trade,
170+
channel.split('.')[0].toUpperCase()
171+
)
172+
)
173+
} else if ( isSpotChannel() ) {
174+
const [contract, tradesData] = params
175+
trades = tradesData.map(trade => {
176+
return {
177+
exchange: this.id,
178+
pair: contract + '_SPOT',
179+
timestamp: +new Date(
180+
parseInt((trade.time * 1000).toString().split('.')[0])
181+
),
182+
price: +trade.price,
183+
size: +trade.amount,
184+
side: trade.type
185+
}
186+
})
187+
}
188+
return this.emitTrades(api.id, trades)
189+
}
190+
}
191+
192+
module.exports = GateIO

0 commit comments

Comments
 (0)