· 6 years ago · Dec 12, 2019, 10:02 AM
1const rp = require('request-promise');
2const FTXRest = require('./');
3const FTXWs = require('ftx-api-ws');
4const BitMEXClient = require('./indexBitmex');
5const Deribit = require('deribit-v2-ws');
6const qs = require('qs');
7const crypto = require('crypto');
8const fetch = require('node-fetch');
9const now = require('performance-now');
10var HttpsProxyAgent = require('https-proxy-agent');
11
12var BITMEX_KEY = "";
13var BITMEX_SECRET = "";
14var FTX_KEY = "";
15var FTX_SECRET = "";
16var FTX_SUBACCOUNT = "btcarb";
17var orderSize = 0.25;
18var threashold = 0.3;
19var orderstepsize = 0.5;
20var numberOfOrdersPerPair = 3;
21var minimumArb = 0.0003;
22var immediateArb = 0.0017;
23
24const key = '';
25const secret = '';
26
27const db = new Deribit({
28 key,
29 secret
30});
31
32var ratelimit = 0;
33var sleepforoneS = false;
34var sleepfor60s = false;
35var sendOrderBitmexProcessing = false;
36var sendOrderBitmexBulkProcessing = false;
37var activeOrders = [];
38var buySideOrder = 0;
39var sellSideOrder = 0;
40var openQty = 0;
41var liquidPrice = 0;
42var initMarginBalance = null;
43var nowMarginBalance = null;
44var mainfilterProtection = 1;
45var useProxy = false;
46const canceledOrders = [];
47var proxyTimer = 0;
48var cancelpause = false;
49
50let bmexBTCask = 0;
51let bmexBTCbid = 0;
52let FTXbidPrice = 0;
53let FTXaskPrice = 0;
54let bidsizeBmexBTC = 0;
55let asksizeBmexBTC = 0;
56var deribitBid = 0;
57var deribitAsk = 0;
58var marginLeverage;
59var priceSafety = false;
60var FTXmarginFraction = 0;
61var ftxPosition = 0;
62var FTXallowBuy = false;
63var FTXallowSell = false;
64
65const ftx = new FTXRest({
66 key: FTX_KEY,
67 secret: FTX_SECRET,
68 subaccount: FTX_SUBACCOUNT
69})
70var moment = require('moment');
71var buyInterestAdjust = 0;
72var sellInterestAdjust = 0;
73
74
75process.env.TZ = 'Asia/Hong_Kong';
76
77process.on('SIGINT', async () => {
78 await cancelallOrders('XBTUSD');
79 process.exit();
80});
81
82const sleep = ms => new Promise(r => setTimeout(r, ms));
83
84const ftxws = new FTXWs({
85 key: FTX_KEY,
86 secret: FTX_SECRET,
87 subaccount: FTX_SUBACCOUNT,
88});
89
90const bitmex = new BitMEXClient({
91 testnet: false,
92 apiKeyID: BITMEX_KEY,
93 apiKeySecret: BITMEX_SECRET,
94 maxTableLen: 10000,
95});
96
97
98
99
100async function sleepforone(calledFrom) {
101 sleepforoneS = true;
102 // console.log('sleepforone, sleep 1000, called from ' + calledFrom);
103
104 if (ratelimit < 20) {
105 await sleep(2000);
106 } else {
107 await sleep(0);
108 }
109
110 sleepforoneS = false;
111}
112
113async function sleepforOneMinute(calledFrom) {
114 sleepfor60s = true;
115 console.log('sleepforOneMinute, sleep 30000, called from ' + calledFrom);
116 await sleep(30000);
117 sleepfor60s = false;
118}
119
120function makeRequest(verb, endpoint, data = {}) {
121 const start = now();
122 const apiRoot = '/api/v1/';
123 const expires = Math.round(new Date().getTime() / 1000) + 60;
124 let query = '',
125 postBody = '';
126
127 if (verb === 'GET') {
128 query = '?' + qs.stringify(data);
129 } else {
130 postBody = JSON.stringify(data);
131 }
132
133 const signature = crypto.createHmac('sha256', BITMEX_SECRET)
134 .update(verb + apiRoot + endpoint + query + expires + postBody).digest('hex');
135
136 const headers = {
137 'content-type': 'application/json',
138 'accept': 'application/json',
139 'api-expires': expires,
140 'api-key': BITMEX_KEY,
141 'api-signature': signature,
142 };
143
144 const requestOptions = {
145 method: verb,
146 headers,
147 };
148
149 if (verb !== 'GET') {
150 requestOptions.body = postBody;
151 }
152
153 const url = 'https://www.bitmex.com' + apiRoot + endpoint + query;
154//agent: new HttpsProxyAgent('https://127.0.0.1:1080')
155
156 if (useProxy == true) {
157 return fetch(url, requestOptions, {
158 agent: new HttpsProxyAgent('https://172.31.30.169:3128')
159 }).then(response => {
160 ratelimit = response.headers.get('x-ratelimit-remaining');
161
162 return response.json();
163 }).then(
164 response => {
165 if ('error' in response) throw new Error(response.error.message);
166 verb !== 'GET' && console.log(endpoint, verb, 'took', (now() - start).toFixed(5), 'ms');
167
168 return response;
169 },
170 error => console.error('Network error', error),
171 );
172 } else {
173 return fetch(url, requestOptions, {
174 agent: new HttpsProxyAgent('https://172.31.25.247:3128')
175 }).then(response => {
176 ratelimit = response.headers.get('x-ratelimit-remaining');
177
178 return response.json();
179 }).then(
180 response => {
181 if ('error' in response) throw new Error(response.error.message);
182 verb !== 'GET' && console.log(endpoint, verb, 'took', (now() - start).toFixed(5), 'ms');
183
184 return response;
185 },
186 error => console.error('Network error', error),
187 );
188 }
189}
190
191async function getOrders(symbol) {
192 try {
193 let buyorderCount = 0;
194 let sellorderCount = 0;
195 let tempActiveOrders = [];
196 const data = {
197 symbol,
198 filter: {
199 open: true
200 },
201 count: 100,
202 };
203 const result = await makeRequest('GET', 'order', data);
204
205 for (let i = 0; i < result.length; i++) {
206 if (result[i].ordStatus == 'New' || result[i].ordStatus == 'Open') {
207 tempActiveOrders.push(result[i]);
208
209 if (result[i].side == 'Buy') {
210 buyorderCount = buyorderCount + 1;
211 }
212
213 if (result[i].side == 'Sell') {
214 sellorderCount = sellorderCount + 1;
215 }
216 }
217 }
218 activeOrders = tempActiveOrders;
219 buySideOrder = buyorderCount;
220 sellSideOrder = sellorderCount;
221 mainfilterProtection = mainfilterProtection + 1;
222 cancelFilterBitmex();
223 } catch (e) {
224 if (e.toString().includes('retry in', 0)) {
225 sleepforOneMinute('getOrders');
226 } else if (e.toString().includes('403 Forbidden', 0)) {
227 useProxy = true;
228 }
229 mainfilterProtection = 0;
230 console.error('getOrders ' + e);
231 }
232}
233
234async function getPosition(symbol) {
235 try {
236 const result = await makeRequest('GET', 'position', {
237 symbol
238 });
239 openQty = parseFloat(result[0].currentQty);
240 liquidPrice = parseFloat(result[0].liquidationPrice);
241 } catch (e) {
242 if (e.toString().includes('retry in', 0)) {
243 sleepforOneMinute('getPosition');
244 } else if (e.toString().includes('403 Forbidden', 0)) {
245 useProxy = true;
246 }
247
248 console.error('getPosition ' + e);
249 }
250}
251
252let started = false;
253async function getmarginBalance() {
254 try {
255 const result = await makeRequest('GET', 'user/margin', {
256 currency: 'XBt'
257 });
258
259 if (!started) {
260 initMarginBalance = result.marginBalance * 0.00000001;
261 started = true;
262 }
263
264 nowMarginBalance = result.marginBalance * 0.00000001;
265 } catch (e) {
266 if (e.toString().includes('retry in')) {
267 sleepforOneMinute('getmarginBalance');
268 } else if (e.toString().includes('403 Forbidden', 0)) {
269 useProxy = true;
270 }
271 console.error('getmarginBalance ' + e);
272 }
273}
274
275
276async function sendOrderBitmexClose(symbolp, sidep, pricep, ordTypep, execInstp, textp)
277{
278 const data = {
279 'symbol': symbolp,
280 'side': sidep,
281 'price': pricep,
282 'ordType': ordTypep,
283 'execInst': execInstp,
284 'text': textp,
285 };
286 try {
287 await makeRequest('POST', 'order', data);
288 } catch (e) {
289 if (e.toString().includes('retry in', 0)) {
290 sleepforOneMinute('sendOrderBitmexClose');
291 } else if (e.toString().includes('403 Forbidden', 0)) {
292 useProxy = true;
293 }
294
295 console.error('sendOrderBitmexClose ' + e);
296 console.error(data);
297 }
298 await sleep(1000);
299}
300
301
302async function sendOrderBitmex(symbolp, sidep, orderQtyp, pricep, ordTypep, execInstp, textp) {
303 sendOrderBitmexProcessing = true;
304
305 const data = {
306 'symbol': symbolp,
307 'side': sidep,
308 'orderQty': orderQtyp,
309 'price': pricep,
310 'ordType': ordTypep,
311 'execInst': execInstp,
312 'text': textp,
313 };
314
315 try {
316 await makeRequest('POST', 'order', data);
317 } catch (e) {
318 if (e.toString().includes('retry in', 0)) {
319 sleepforOneMinute('sendOrderBitmex');
320 } else if (e.toString().includes('403 Forbidden', 0)) {
321 useProxy = true;
322 }
323
324 console.error('sendOrderBitmex ' + e);
325 console.error(data);
326 }
327 await sleep(200);
328 sendOrderBitmexProcessing = false;
329 mainfilterProtection = 0;
330}
331
332async function sendOrderBitmexBulk(bulkDatap) {
333 sendOrderBitmexBulkProcessing = true;
334
335 try {
336 await makeRequest('POST', 'order/bulk', bulkDatap);
337 } catch (e) {
338 if (e.toString().includes('retry in', 0)) {
339 sleepforOneMinute('sendOrderBitmexBulk');
340 } else if (e.toString().includes('403 Forbidden', 0)) {
341 useProxy = true;
342 }
343
344 console.error('sendOrderBitmexBulk ' + e);
345 }
346 await sleep(500);
347 sendOrderBitmexBulkProcessing = false;
348 mainfilterProtection = 0;
349}
350
351async function cancelallOrders(symbol) {
352 try {
353 await makeRequest('DELETE', 'order/all', {
354 symbol
355 });
356 } catch (e) {
357 if (e.toString().includes('retry in', 0)) {
358 sleepforOneMinute('cancelallOrders');
359 } else if (e.toString().includes('403 Forbidden', 0)) {
360 useProxy = true;
361 }
362 console.error('cancelallOrders ' + e);
363 }
364}
365
366async function cancelOrders(orderID) {
367 try {
368 await makeRequest('DELETE', 'order', {
369 orderID
370 });
371 } catch (e) {
372 if (e.toString().includes('retry in', 0)) {
373 sleepforOneMinute('cancelallOrders');
374 } else if (e.toString().includes('403 Forbidden', 0)) {
375 useProxy = true;
376 }
377 else if (e.toString().includes('Not Found', 0)) {
378 cancelallOrders('XBTUSD');
379 }
380 console.error('cancelOrders ' + e);
381 }
382}
383
384//cancel orders that is out of arbitrage criteria
385async function cancelFilterBitmex(bmexBTCbid, bmexBTCask, FTXbidPrice, FTXaskPrice) {
386 try {
387 if (cancelpause != true && sleepfor60s!=true)
388 {
389 cancelpause = true;
390 const cancelOrderIDs = [];
391 var bidCancelAdjust = minimumArb;
392 var askCancelAdjust = minimumArb;
393 var btcThreashold = threashold*bmexBTCbid;
394
395 for (let i = 0; i < 40; i++) {
396 if (Math.abs(openQty) < btcThreashold * (i + 1)) {
397 if (openQty < 0) {
398 bidCancelAdjust = minimumArb - minimumArb * 0.025 * i - 0.0002 + buyInterestAdjust;
399 askCancelAdjust = minimumArb + minimumArb * 0.05 * i - 0.0002 + sellInterestAdjust;
400 }
401 else if (openQty > 0) {
402 bidCancelAdjust = minimumArb + minimumArb * 0.05 * i - 0.0002 + buyInterestAdjust;
403 askCancelAdjust = minimumArb - minimumArb * 0.025 * i - 0.0002 + sellInterestAdjust;
404 }
405 else
406 {
407 bidCancelAdjust = minimumArb - 0.0002;
408 askCancelAdjust = minimumArb - 0.0002;
409 }
410 break;
411 }
412 if (Math.abs(openQty) > btcThreashold * (40)) {
413 if (openQty < 0) {
414 bidCancelAdjust = minimumArb - minimumArb * 0.025 * 40 - 0.0002 + buyInterestAdjust;
415 askCancelAdjust = minimumArb + minimumArb * 0.05 * 40 - 0.0002 + sellInterestAdjust;
416 }
417 if (openQty > 0) {
418 bidCancelAdjust = minimumArb + minimumArb * 0.05 * 40 - 0.0002 + buyInterestAdjust;
419 askCancelAdjust = minimumArb - minimumArb * 0.025 * 40 - 0.0002 + sellInterestAdjust;
420 }
421 break;
422 }
423 }
424 //console.log("bidCancelAdjust",bidCancelAdjust,"askCancelAdjust",askCancelAdjust);
425 var cancelBidPriceFilter = FTXbidPrice / (1 + bidCancelAdjust);
426 var cancelAskPriceFilter = FTXaskPrice * (1 + askCancelAdjust);
427
428
429 if (activeOrders.length) {
430 for (let i = 0; i < activeOrders.length; i++) {
431 if (!canceledOrders.includes(activeOrders[i].orderID)) {
432 if (activeOrders[i].side == 'Buy' && activeOrders[i].price > cancelBidPriceFilter) {
433 if (!activeOrders[i].text.toString().includes('stoploss order', 0)) {
434 cancelOrderIDs.push(activeOrders[i].orderID);
435 canceledOrders.push(activeOrders[i].orderID);
436 }
437 } else if (activeOrders[i].side == 'Sell' && activeOrders[i].price < cancelAskPriceFilter) {
438 if (!activeOrders[i].text.toString().includes('stoploss order', 0)) {
439 cancelOrderIDs.push(activeOrders[i].orderID);
440 canceledOrders.push(activeOrders[i].orderID);
441 }
442 }
443 }
444 }
445 for (let i = 0; i < activeOrders.length; i++) {
446 let dupCount = 0;
447 for (let t = 0; t < activeOrders.length; t++) {
448 if (activeOrders[i].price == activeOrders[t].price) {
449 dupCount = dupCount + 1;
450 if (dupCount > 1) {
451 cancelOrderIDs.push(activeOrders[i].orderID);
452 canceledOrders.push(activeOrders[i].orderID);
453 activeOrders.splice(t,1);
454 }
455 }
456 }
457 }
458 }
459 if (cancelOrderIDs.length > 0) {
460 cancelOrders(cancelOrderIDs);
461 sleepforone('cancelOrders(cancelOrderIDs)');
462 }
463 while (canceledOrders.length > 200) {
464 canceledOrders.splice(0, 1);
465 }
466 cancelpause = false;
467 }
468 } catch (e) {
469 cancelpause = false;
470 console.error('cancelFilterBitmex ' + e);
471 }
472 cancelpause = false;
473}
474
475// Program entry point
476startProgram();
477
478async function startProgram() {
479 bitmex.on('error', console.error);
480 bitmex.on('open', () => console.log('Connection opened.'));
481 bitmex.on('close', () => console.log('Connection closed.'));
482 bitmex.on('initialize', () => console.log('Client initialized, data is flowing.'));
483
484 getPosition('XBTUSD');
485 getmarginBalance();
486
487 setInterval(() => {
488 try {
489 mainLoop();
490 cancelFilterBitmex(bmexBTCbid, bmexBTCask, FTXbidPrice, FTXaskPrice);
491 } catch (e) {}
492 }, 5000);
493
494 bitmex.addStream('XBTUSD', 'quote', data => {
495 try {
496 bmexBTCbid = data[data.length - 1].bidPrice;
497 bmexBTCask = data[data.length - 1].askPrice;
498 bidsizeBmexBTC = data[data.length - 1].bidSize; //will use this later for prehedge
499 asksizeBmexBTC = data[data.length - 1].askSize; //will use this later for prehedge
500 mainLoop();
501 } catch (e) {
502 console.error('quote ' + e);
503 bmexBTCbid = 0;
504 bmexBTCask = 0;
505 }
506 });
507
508 setInterval(() => {
509 try {
510 getOrders('XBTUSD');
511 getPosition('XBTUSD');
512 } catch (e) {}
513 }, 60000);
514
515 setInterval(async () => {
516 try {
517 cancelallOrders('XBTUSD');
518 await sleep(5000);
519 getOrders('XBTUSD');
520 } catch (e) {}
521 }, 600000);
522
523 setInterval(async () => {
524 try {
525 profit();
526 } catch (e) {}
527 }, 40000);
528
529 setInterval(async () => {
530 try {
531 if (activeOrders.length > numberOfOrdersPerPair * 8) {
532 cancelallOrders('XBTUSD');
533 await sleep(5000);
534 getOrders('XBTUSD');
535 }
536 } catch (e) {}
537 }, 20000);
538
539 setInterval(async () => {
540 try {
541 if (marginLeverage > 50)
542 {
543 if(openQty>=0)
544 {
545 var closePrice = (FTXbidPrice-10).toFixed(0);
546 sendOrderBitmexClose('XBTUSD', 'Sell', closePrice, 'Limit', 'Close', 'Closing Margin Level Too High');
547 }
548 else if (openQty<0)
549 {
550 var closePrice = (FTXaskPrice+10).toFixed(0);
551 sendOrderBitmexClose('XBTUSD', 'Buy', closePrice, 'Limit', 'Close', 'Closing Margin Level Too High');
552 }
553 }
554 } catch (e) {}
555 }, 10000);
556
557 setInterval(() => {
558 try {
559 if (useProxy == true && proxyTimer < 62) {
560 proxyTimer = proxyTimer + 1;
561 console.log("Using Proxy", "Proxy Timer", proxyTimer);
562 } else {
563 useProxy = false;
564 proxyTimer = 0;
565 }
566 } catch (e) {}
567 }, 60000);
568
569 setInterval(() => {
570 try {
571 if(sleepfor60s == true && activeOrders.length > 0)
572 {
573 cancelallOrders('XBTUSD');
574 }
575 } catch (e) {}
576 }, 15000);
577
578 await db.connect();
579
580 await db.subscribe(
581 'public',
582 'quote.BTC-PERPETUAL'
583 );
584
585 console.log(new Date, 'connected');
586 db.on('quote.BTC-PERPETUAL', function(e) {
587 try {
588 deribitBid = e.best_bid_price;
589 deribitAsk = e.best_ask_price;
590 } catch (e) {
591 console.log("quote.BTC-PERPETUAL " + e);
592 deribitBid = 0;
593 deribitAsk = 0;
594 }
595 });
596
597
598 bitmex.addStream('XBTUSD', 'position', data => {
599 try {
600 openQty = data[data.length - 1].currentQty;
601 openQty = parseFloat(openQty);
602 liquidPrice = parseFloat(data[data.length - 1].liquidationPrice);
603 } catch (e) {
604 openQty = 0;
605 console.error('position ' + e);
606 }
607 });
608
609 bitmex.addStream('XBTUSD', 'margin', data => {
610 try {
611 nowMarginBalance = data[data.length - 1].marginBalance * 0.00000001;
612 marginLeverage = data[data.length - 1].marginLeverage;
613 } catch (e) {
614 console.error('margin ' + e);
615 }
616 });
617
618 bitmex.addStream('XBTUSD', 'order', data => {
619 try {
620 let buyorderCount = 0;
621 let sellorderCount = 0;
622 let tempActiveOrders = [];
623 for (let i = 0; i < data.length; i++) {
624 if (data[i].ordStatus == 'New' || data[i].ordStatus == 'Open') {
625 tempActiveOrders.push(data[i]);
626 if (data[i].side == 'Buy') {
627 buyorderCount = buyorderCount + 1;
628 }
629 if (data[i].side == 'Sell') {
630 sellorderCount = sellorderCount + 1;
631 }
632 }
633 }
634 activeOrders = tempActiveOrders;
635 buySideOrder = buyorderCount;
636 sellSideOrder = sellorderCount;
637 mainfilterProtection = mainfilterProtection + 1;
638 if (activeOrders.length > numberOfOrdersPerPair * 8) {
639 cancelallOrders('XBTUSD');
640 } else {
641 cancelFilterBitmex(bmexBTCbid, bmexBTCask, FTXbidPrice, FTXaskPrice);
642 }
643 } catch (e) {
644 mainfilterProtection = 0;
645 console.error('order ' + e);
646 }
647 });
648
649 await ftxws.connect();
650 ftxws.subscribe('ticker', 'BTC-PERP');
651 ftxws.on('BTC-PERP::ticker', function(e) {
652 try {
653 if (e.bidSize < 3) {
654 FTXbidPrice = e.bid - 1;
655 } else {
656 FTXbidPrice = e.bid;
657 }
658 if (e.askSize < 3) {
659 FTXaskPrice = e.ask + 1;
660 } else {
661 FTXaskPrice = e.ask;
662 }
663 mainLoop();
664 cancelFilterBitmex(bmexBTCbid, bmexBTCask, FTXbidPrice, FTXaskPrice);
665 } catch (e) {
666 console.error('BTC-PERP::ticker ' + e);
667 FTXbidPrice = 0;
668 FTXaskPrice = 0;
669 }
670 });
671}
672
673function safetyCheck()
674{
675 var safe1 = parseFloat(bmexBTCbid) - parseFloat(FTXbidPrice);
676 var safe2 = parseFloat(bmexBTCask) - parseFloat(FTXaskPrice);
677 var safe3 = parseFloat(deribitBid) - parseFloat(FTXbidPrice);
678 var safe4 = parseFloat(deribitAsk) - parseFloat(FTXaskPrice);
679 var safe5 = parseFloat(deribitBid) - parseFloat(bmexBTCbid);
680 var safe6 = parseFloat(deribitAsk) - parseFloat(bmexBTCask);
681
682 if (safe1 < Math.abs(300) && safe2 < Math.abs(300) && safe3 < Math.abs(300) && safe4 < Math.abs(300) && safe5 < Math.abs(300) && safe6 < Math.abs(300) && ftxws.connectionStatus() == true && db.connectionStatus() == true)
683 {
684 priceSafety = true;
685 }
686 else
687 {
688 priceSafety = false;
689 }
690}
691
692let mainloopprocessing = false;
693async function mainLoop() {
694 safetyCheck();
695 try {
696 if (!sleepfor60s && !sleepforoneS && !sendOrderBitmexProcessing && !mainloopprocessing && mainfilterProtection > 0 && priceSafety == true) {
697 mainloopprocessing = true;
698 //Determine order prices and quantity
699 let buySize = parseFloat((orderSize*bmexBTCbid*nowMarginBalance).toFixed(0));
700 let sellSize = parseFloat((orderSize*bmexBTCask*nowMarginBalance).toFixed(0));
701 var buyArb = minimumArb;
702 var sellArb = minimumArb;
703 //console.log(buyInterestAdjust, sellInterestAdjust);
704 var btcThreashold = threashold*bmexBTCbid;
705
706 for (let i = 0; i < 40; i++) {
707 if (Math.abs(openQty) < btcThreashold * (i + 1)) {
708 if (openQty < 0) {
709 //buySize = buySize + buySize * 0.05 * i;
710 buyArb = minimumArb - minimumArb * 0.025 * i + buyInterestAdjust;
711 sellArb = minimumArb + minimumArb * 0.05 * i + sellInterestAdjust;
712 }
713 if (openQty > 0) {
714 //sellSize = sellSize + sellSize * 0.05 * i;
715 buyArb = minimumArb + minimumArb * 0.05 * i + buyInterestAdjust;
716 sellArb = minimumArb - minimumArb * 0.025 * i + sellInterestAdjust;
717 }
718 break;
719 }
720 if (Math.abs(openQty) > btcThreashold * (40)) {
721 if (openQty < 0) {
722 buySize = buySize + buySize * 0.05 * 20;
723 buyArb = minimumArb - minimumArb * 0.025 * 40 + buyInterestAdjust;
724 sellArb = minimumArb + minimumArb * 0.05 * 40 + sellInterestAdjust;
725 }
726 if (openQty > 0) {
727 sellSize = sellSize + sellSize * 0.05 * 20;
728 buyArb = minimumArb + minimumArb * 0.05 * 40 + buyInterestAdjust;
729 sellArb = minimumArb - minimumArb * 0.025 * 40 + sellInterestAdjust;
730 }
731 break;
732 }
733 }
734 buySize = parseFloat(buySize.toFixed(0));
735 sellSize = parseFloat(sellSize.toFixed(0));
736
737
738 if (FTXbidPrice != 0 && FTXaskPrice != 0 && bmexBTCbid != 0 && bmexBTCask != 0) {
739 //Large Arb Opportunity
740
741 if (openQty <= 0) {
742 if (bmexBTCbid < FTXbidPrice / (1 + immediateArb) && FTXallowSell == true) {
743 sleepforone('sendOrder bmexBTCbid < FTXbidPrice/(1+immediateArb)');
744 var buySize2 = buySize * 2;
745 sendOrderBitmex('XBTUSD', 'Buy', buySize2, bmexBTCbid + 2, 'Limit', '', '');
746 } else if (bmexBTCask > FTXaskPrice * (1 + immediateArb) && FTXallowBuy == true) {
747 if (Math.abs(openQty) < btcThreashold * 40) {
748 sleepforone('sendOrder bmexBTCask > FTXaskPrice*(1+immediateArb)');
749 var sellSize2 = sellSize * 2;
750 sendOrderBitmex('XBTUSD', 'Sell', sellSize2, bmexBTCask - 2, 'Limit', '', '');
751 }
752 }
753 }
754 if (openQty > 0) {
755 if (bmexBTCask > FTXaskPrice * (1 + immediateArb) && FTXallowBuy == true) {
756 sleepforone('sendOrder bmexBTCask > FTXaskPrice*(1+immediateArb)');
757 var sellSize2 = sellSize * 2;
758 sendOrderBitmex('XBTUSD', 'Sell', sellSize2, bmexBTCask - 2, 'Limit', '', '');
759 } else if (bmexBTCbid < FTXbidPrice / (1 + immediateArb) && FTXallowSell == true) {
760 if (Math.abs(openQty) < btcThreashold * 40) {
761 sleepforone('sendOrder bmexBTCbid < FTXbidPrice/(1+immediateArb)');
762 var buySize2 = buySize * 2;
763 sendOrderBitmex('XBTUSD', 'Buy', buySize2, bmexBTCbid + 2, 'Limit', '', '');
764 }
765 }
766 }
767
768 if (!sendOrderBitmexBulkProcessing) {
769 //Standard multiple Orders
770 const bulkData = {
771 'orders': []
772 };
773
774 if (Math.abs(openQty) < btcThreashold * 40) {
775 let buyPrice = (FTXbidPrice / (1 + buyArb)).toFixed(0);
776
777 if (parseFloat(buyPrice) > bmexBTCbid) {
778 buyPrice = bmexBTCbid;
779 }
780 if(FTXallowSell == true)
781 {
782 for (let i = 0; i < numberOfOrdersPerPair; i++) {
783 const buyPrice2 = parseFloat(buyPrice) - parseFloat(orderstepsize * i);
784 const data = {
785 'symbol': 'XBTUSD',
786 'side': 'Buy',
787 'orderQty': buySize,
788 'price': buyPrice2,
789 'ordType': 'Limit',
790 'execInst': 'ParticipateDoNotInitiate',
791 'text': ''
792 };
793 bulkData.orders.push(data);
794 }
795 }
796 let sellPrice = (FTXaskPrice * (1 + sellArb)).toFixed(0);
797
798 if (parseFloat(sellPrice) < bmexBTCask) {
799 sellPrice = bmexBTCask;
800 }
801 if(FTXallowBuy == true)
802 {
803 for (let i = 0; i < numberOfOrdersPerPair; i++) {
804 const sellPrice2 = parseFloat(sellPrice) + parseFloat(orderstepsize * i);
805 const data = {
806 'symbol': 'XBTUSD',
807 'side': 'Sell',
808 'orderQty': sellSize,
809 'price': sellPrice2,
810 'ordType': 'Limit',
811 'execInst': 'ParticipateDoNotInitiate',
812 'text': ''
813 };
814 bulkData.orders.push(data);
815 }
816 }
817 } else if (openQty > 0) {
818 let sellPrice = (FTXaskPrice * (1 + sellArb)).toFixed(0);
819 if (parseFloat(sellPrice) < bmexBTCask) {
820 sellPrice = bmexBTCask;
821 }
822 if(FTXallowBuy == true)
823 {
824 for (let i = 0; i < numberOfOrdersPerPair; i++) {
825 const sellPrice2 = parseFloat(sellPrice) + parseFloat(orderstepsize * i);
826 const data = {
827 'symbol': 'XBTUSD',
828 'side': 'Sell',
829 'orderQty': sellSize,
830 'price': sellPrice2,
831 'ordType': 'Limit',
832 'execInst': 'ParticipateDoNotInitiate',
833 'text': ''
834 };
835 bulkData.orders.push(data);
836 }
837 }
838 } else if (openQty < 0) {
839 let buyPrice = (FTXbidPrice / (1 + buyArb)).toFixed(0);
840 if (parseFloat(buyPrice) > bmexBTCbid) {
841 buyPrice = bmexBTCbid;
842 }
843 if(FTXallowSell == true)
844 {
845 for (let i = 0; i < numberOfOrdersPerPair; i++) {
846 const buyPrice2 = parseFloat(buyPrice) - parseFloat(orderstepsize * i);
847 const data = {
848 'symbol': 'XBTUSD',
849 'side': 'Buy',
850 'orderQty': buySize,
851 'price': buyPrice2,
852 'ordType': 'Limit',
853 'execInst': 'ParticipateDoNotInitiate',
854 'text': ''
855 };
856 bulkData.orders.push(data);
857 }
858 }
859 }
860
861 if (bulkData.orders.length > 0 && bulkData.orders != undefined && activeOrders.length<numberOfOrdersPerPair*4) {
862 for (let i = 0; i < activeOrders.length; i++) {
863 for (let t = 0; t < bulkData.orders.length; t++) {
864 if (bulkData.orders != undefined && bulkData.orders.length > 0) {
865 if (activeOrders[i].price == bulkData.orders[t].price) {
866 bulkData.orders.splice(t,1);
867 }
868 }
869 }
870 }
871 }
872
873 if (bulkData.orders.length > 0 && bulkData.orders != undefined) {
874 sendOrderBitmexBulk(bulkData);
875 sleepforone('sendOrderBitmexBulk standard');
876 }
877 }
878 }
879 mainloopprocessing = false;
880 }
881 } catch (e) {
882 console.error('mainLoop ' + e);
883 mainloopprocessing = false;
884 }
885}
886
887
888//cancel orders that are out of range, on a separate loop to save API ratelimit.
889async function cancelFilterBitmex2() {
890 try {
891 if (sleepfor60s!=true)
892 {
893 const cancelOrderIDs = [];
894
895 var buyOrders = [];
896 var sellOrders = [];
897
898 if (activeOrders.length)
899 {
900 for (let i = 0; i < activeOrders.length; i++) {
901 if (activeOrders[i].side == 'Buy') {
902 buyOrders.push(activeOrders[i]);
903 }
904 if (activeOrders[i].side == 'Sell') {
905 sellOrders.push(activeOrders[i]);
906 }
907 }
908 if (buyOrders.length >numberOfOrdersPerPair)
909 {
910 for (var i = 0; i< (buyOrders.length-numberOfOrdersPerPair); i++)
911 {
912 var smallest = buyOrders[0].price;
913 var smallestID = buyOrders[0].orderID;
914 var smallestTXT = buyOrders[0].text;
915 for (var t = 0; t<buyOrders.length; t++)
916 {
917 if (buyOrders[t].price < smallest) {
918 smallest = buyOrders[t].price;
919 smallestID = buyOrders[t].orderID;
920 smallestTXT = buyOrders[t].text;
921 }
922 }
923 if (!smallestTXT.toString().includes('Closing Margin Level Too High', 0))
924 {
925 cancelOrderIDs.push(smallestID);
926 canceledOrders.push(smallestID);
927 for (let t = 0; t < activeOrders.length; t++)
928 {
929 if (smallestID == activeOrders[t].orderID)
930 {
931 activeOrders.splice(t,1);
932 }
933 }
934 for (let f = 0; f < buyOrders.length; f++)
935 {
936 if(smallestID == buyOrders[f].orderID)
937 {
938 buyOrders.splice(f,1);
939 }
940 }
941 }
942 }
943 }
944
945 if (sellOrders.length >numberOfOrdersPerPair)
946 {
947 for (var i = 0; i< (sellOrders.length-numberOfOrdersPerPair); i++)
948 {
949 var largest = sellOrders[0].price;
950 var largestID = sellOrders[0].orderID;
951 var largestTXT = sellOrders[0].text;
952 for (var t = 0; t<sellOrders.length; t++)
953 {
954 if (sellOrders[t].price > largest) {
955 largest = sellOrders[t].price;
956 largestID = sellOrders[t].orderID;
957 largestTXT = sellOrders[t].text;
958 }
959 }
960 if (!largestTXT.toString().includes('Closing Margin Level Too High', 0))
961 {
962 cancelOrderIDs.push(largestID);
963 canceledOrders.push(largestID);
964 for (let t = 0; t < activeOrders.length; t++)
965 {
966 if (largestID == activeOrders[t].orderID)
967 {
968 activeOrders.splice(t,1);
969 }
970 }
971 for (let f = 0; f < sellOrders.length; f++)
972 {
973 if(largestID == sellOrders[f].orderID)
974 {
975 sellOrders.splice(f,1);
976 }
977 }
978 }
979 }
980 }
981
982 if (cancelOrderIDs.length > 0) {
983 cancelOrders(cancelOrderIDs);
984 }
985 while (canceledOrders.length > 200) {
986 canceledOrders.splice(0, 1);
987 }
988 }
989 }
990 } catch (e) {
991 console.error('cancelFilterBitmex2 ' + e);
992 }
993}
994
995
996
997async function getInterestRate() {
998 try {
999 var bitmexCurrentInterest = 0;
1000 var bitmexNextInterest = 0;
1001 var ftxFunding = 0;
1002 var timetoBitmexFunding = 0;
1003 var adjustedbitmexFunding = 0;
1004 const result = await makeRequest('GET', 'instrument/active');
1005 for (var i = 0; i< result.length; i++)
1006 {
1007 if(result[i].symbol == 'XBTUSD')
1008 {
1009 timetoBitmexFunding = moment(result[i].fundingTimestamp).unix() - moment(moment().valueOf()).unix();
1010 bitmexCurrentInterest = result[i].fundingRate/8;
1011 bitmexNextInterest = result[i].indicativeFundingRate;
1012 }
1013 }
1014 const data = await ftx.request({
1015 method: 'GET',
1016 path: '/futures/BTC-PERP' + '/stats'
1017 });
1018 ftxFunding = data.result.nextFundingRate;
1019 buyInterestAdjust = 0;
1020 sellInterestAdjust = 0;
1021 if (timetoBitmexFunding<1800)
1022 {
1023 adjustedbitmexFunding = bitmexCurrentInterest*8;
1024 }
1025 else if(timetoBitmexFunding<3600)
1026 {
1027 adjustedbitmexFunding = bitmexCurrentInterest*4;
1028 }
1029 else if(timetoBitmexFunding<7200)
1030 {
1031 adjustedbitmexFunding = bitmexCurrentInterest*2;
1032 }
1033 else
1034 {
1035 adjustedbitmexFunding = bitmexCurrentInterest;
1036 }
1037
1038 if(ftxFunding < 0)
1039 {
1040 buyInterestAdjust = buyInterestAdjust + ftxFunding*2.5;
1041 sellInterestAdjust = sellInterestAdjust - ftxFunding*2.5;
1042 }
1043 if(ftxFunding >= 0)
1044 {
1045 buyInterestAdjust = buyInterestAdjust + ftxFunding*2.5;
1046 sellInterestAdjust = sellInterestAdjust - ftxFunding*2.5;
1047 }
1048 if(adjustedbitmexFunding<0)
1049 {
1050 buyInterestAdjust = buyInterestAdjust + adjustedbitmexFunding;
1051 sellInterestAdjust = sellInterestAdjust - adjustedbitmexFunding;
1052 }
1053 if(adjustedbitmexFunding>=0)
1054 {
1055 buyInterestAdjust = buyInterestAdjust + adjustedbitmexFunding;
1056 sellInterestAdjust = sellInterestAdjust - adjustedbitmexFunding;
1057 }
1058 } catch (e) {
1059 if (e.toString().includes('retry in', 0)) {
1060 sleepforOneMinute('getPosition');
1061 } else if (e.toString().includes('403 Forbidden', 0)) {
1062 useProxy = true;
1063 }
1064 buyInterestAdjust = 0;
1065 sellInterestAdjust = 0;
1066 console.error('getInterestRate ' + e);
1067 }
1068}
1069getInterestRate();
1070getFTXAccount();
1071
1072async function getFTXAccount() {
1073 try{
1074 const data = await ftx.request({
1075 method: 'GET',
1076 path: '/account'
1077 });
1078 FTXmarginFraction = data.result.marginFraction;
1079 for (var i = 0; i < data.result.positions.length; i++) {
1080 if (data.result.positions[i].future == "BTC-PERP") {
1081 ftxPosition = parseFloat(data.result.positions[i].netSize * bmexBTCask);
1082 }
1083 }
1084 if(FTXmarginFraction < 0.12 && ftxPosition >0)
1085 {
1086 FTXallowBuy = false;
1087 FTXallowSell = true;
1088 }
1089 else if( FTXmarginFraction < 0.12 && ftxPosition <0)
1090 {
1091 FTXallowBuy = true;
1092 FTXallowSell = false;
1093 }
1094 else
1095 {
1096 FTXallowBuy = true;
1097 FTXallowSell = true;
1098 }
1099 } catch (err) {
1100 console.log(err);
1101 }
1102}
1103
1104setInterval(() => {
1105 try {
1106 getInterestRate();
1107 } catch (e) {}
1108}, 10*60*1000);
1109
1110
1111setInterval(() => {
1112 try {
1113 cancelFilterBitmex2();
1114 getFTXAccount();
1115 } catch (e) {}
1116}, 10000);
1117
1118setTimeout(function(){
1119 console.log("process restarted");
1120 process.exit(0);
1121}, 2*60*60*1000);