UNPKG

cossmmbot

Version:

Market Making bot for the coss.io exchange

2,046 lines (1,289 loc) 58.8 kB
const $tls = require("tls"); const $url = require("url"); const $https = require("https"); const $util = require("util"); const $crypto = require("crypto"); const $fs = require("fs"); const $events = require("events"); const $assert = require("assert").strict; class MMBot { /** * The main and unique class for market making on * the COSS plateform for a complete documentation * see : https://github.com/cyrus1996/CossMMBot */ constructor($spec,wallet){ /** * @var _spec <Object> * * * an Object that contains * every specification for every * pair that we want to trade on */ this._spec = {} /** * @var _ob_list <Object> * * created in => this.createOrderbook() * * an Object that stores all the * order books of the pairs we are * trading on */ this._ob_list = {} /** * @var _amount_min <Map> * * The map pair => min_amount that represents * the minimum amount to trade on the base pairs * (eg ETH: 0.02, COSS: 25) */ this._amount_min = new Map(); /** * @var _decimal <Map> * * the specification given by * the exchange for every pair * formed like this : * Pair => {price_decimal,amount_decimal} */ this._decimal = new Map() /** * @var _wallet <Object> * * created in => this.getWallet * * Stores your wallet specifications and amount */ this._wallet = {} /** * @var _start <Bool>, used to determine wheather * we have created all the stripes on each pair * before reaching the maximum api calls within * one minute */ this._start = false; /** * @var _callcounter <Int> * * the number of call done during the * intialization phasis */ this._cookie = false; this._callcounter = 15; this._kill = false; this._spec = $spec; for (var air in this._spec){ console.log("open->" + '{"' + air + '":' + JSON.stringify(this._spec[air]) + "}"); } process.on("uncaughtException", (err) => { console.log("ERROR: " + err.message); console.log(err); process.exit(); }) process.on("unhandledRejection", (err) => { console.log("ERROR: " + err.message); console.log(err); process.exit(); }) process.stdin.resume(); process.stdin.on("data", async (data) => { data = data.toString().replace("\n",""); data = data.split(","); var poll = []; var pair = data[3]; if (data[0] == "a") { data = {"a":[data[1],data[2]]} } else { data = {"b":[data[1],data[2]]} } if (this._spec[pair]["created"]) { await this.updateStripes([data],pair); } else { for(var x of [data]){ poll.push(x); } this._spec[pair]["events"].once("finish", async() => { await this.updateStripes(poll,pair); }) } }) /** * Populates our wallet * and make the rest of the * function asychronously */ this.getWallet(wallet).then(async(value) => { await this.getExchangeInfo(); for(var pair in this._spec){ if(await this.createSocket(pair)){ console.log("stripes created on " + pair); } else { console.log("a problem while creating stripes on " + pair); } } this._start = true; }) } async cancelOnce(ref = 0){ if (!this._cookie) { return false; } else { return new Promise((resolve,reject)=>{ var $response = ""; var $date = new Date().getTime() - 3000; var $data = '{"order_symbol": '+ pair +',"timestamp": '+ $date +',"recvWindow": 5000,"order_id": ' + id + '}'; var hmac = $crypto.createHmac("sha256",$private); hmac.update($data); var options = { "protocol": "https:", "hostname": "trade.coss.io", "method": "DELETE", "port": 443, "path": "/c/api/v1/order/cancel", "headers": { "Content-Type": "application/json", "Content-Length": $data.length, "Authorization": $public, "Signature": hmac.digest("hex") } } req = $https.request(options,async(res) => { res.on("data", (chunk) => {$response += chunk.toString()}); res.on("end", async() => { if ($response.indexOf("<html>") != -1 && $ref < 3) { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1000); this.cancelOnce(ref++).then(async (value) => { resolve(value); }) } else { resolve(true); } }) }); req.end($data); req.on("error", async(e) => { if (ref < 3) { this.cancelOnce(ref++).then(async (value) => { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1000); resolve(value); }) } else { resolve(true); } }) }) } } async getOpenOrders(pair,ref = 0){ return new Promise((resolve,reject) => { var $response = ""; var $date = new Date().getTime() - 3000; var $data = '{"order_symbol": '+ pair +'"timestamp": '+ $date +',"recvWindow": 5000}'; var hmac = $crypto.createHmac("sha256",$private); hmac.update($data); var options = { "protocol": "https:", "hostname": "trade.coss.io", "method": "POST", "port": 443, "path": "/c/api/v1/order/list/open", "headers": { "Content-Type": "application/json", "Content-Length": $data.length, "Authorization": $public, "Signature": hmac.digest("hex") } } req = $https.request(options,async(res) => { res.on("data", (chunk) => {$response += chunk.toString()}); res.on("end", async() => { try{ var json = JSON.parse($response); resolve(json); } catch(e){ this.getOpenOrders(pair,ref++).then(async (value) => { resolve(value); }) } }) }); req.end($data); req.on("error", async(e) => { if (ref < 3) { this.getOpenOrders(pair,ref++).then(async (value) => { resolve(value); }) } else { resolve(false); } }) }) } /** * * */ async cancelAllOrders(pair){ return new Promise(async (resolve,reject) =>{ var liste = await this.getOpenOrders(pair); if (!liste) { resolve(false)} if (this._callcounter + liste["list"].length < 700) { for(var x of liste["list"]){ await this.cancelOne(x["order_id"],pair); resolve(true); } } else { resolve(false); } }) } async timerCancelAll(pair){ return new Promise((resolve,reject) => { setTimeout(async() => { await this.cancelAllOrders(pair); resolve(true); },65000) }) } async cancelOne(id,pair,ref = 0){ return new Promise((resolve,reject) => { var $response = ""; var $date = new Date().getTime() - 3000; var $data = '{"order_symbol": '+ pair +',"timestamp": '+ $date +',"recvWindow": 5000,"order_id": ' + id + '}'; var hmac = $crypto.createHmac("sha256",$private); hmac.update($data); var options = { "protocol": "https:", "hostname": "trade.coss.io", "method": "DELETE", "port": 443, "path": "/c/api/v1/order/cancel", "headers": { "Content-Type": "application/json", "Content-Length": $data.length, "Authorization": $public, "Signature": hmac.digest("hex") } } req = $https.request(options,async(res) => { res.on("data", (chunk) => {$response += chunk.toString()}); res.on("end", async() => { if ($response.indexOf("<html>") != -1 && $ref < 3) { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1000); this.cancelOne(id,pair,ref++).then(async (value) => { resolve(value); }) } else { resolve(true); } }) }); req.end($data); req.on("error", async(e) => { if (ref < 3) { this.cancelOne(id,pair,ref++).then(async (value) => { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1000); resolve(value); }) } else { resolve(true); } }) }) } /** * @param pair <String> * * Controls the opening of new * sockets, by returning promises */ async openSocket(pair){ return new Promise(async (resolve,reject) => { setTimeout(async () => { if(!await this.createSocket(pair)){ await this.openSocket(pair) } else { resolve(true); } },70000) }) } /** * @params Ø * * @return Promise * * This function returns our wallet * and stores the results in the _wallet variable */ async getWallet(wallet){ return new Promise((resolve,reject) => { this._wallet = wallet; resolve(true); }) } /** * @params none * * This function is used to retrieve * infomations about the pairs on the * exchange * * @return Promise */ async getExchangeInfo(){ return new Promise((resolve,rej) => { $https.get("https://trade.coss.io/c/api/v1/exchange-info", (res) => { let data = ""; res.on("data", (chunk) => { data += chunk; }); res.on("end", () => { data = JSON.parse(data); data['base_currencies'].forEach((value) => { this._amount_min.set(value['currency_code'],value['minimum_total_order']); }); data['symbols'].forEach((value) => { this._decimal.set(value['symbol'],{amount_decimal: value['amount_limit_decimal'],price_decimal: value['price_limit_decimal']}); }); resolve(true); }); }); }); } /** * @param $pair <string> * * Creates a new connection on a specified * websocket on the exchange * in order to listen for data and update our * internal orderbook */ async createSocket($pair){ this._spec[$pair]["pong"] = 0; this._spec[$pair]["events"] = new $events; this._spec[$pair]["reminder_asks"] = false; this._spec[$pair]["reminder_bids"] = false; $assert.ok(this._decimal.has($pair),"The pair you gave is not listed"); console.log("opening socket on " + $pair); return new Promise((resolve,reject) => { this._spec[$pair]["socket"] = $tls.connect(443,"engine.coss.io", async ()=>{ this._spec[$pair]["socket"].write("GET /ws/v1/dp/" + $pair +" HTTP/1.1\r\n" + "Host: engine.coss.io\r\n" + "Accept: */*\r\nConnection: Upgrade\r\n" + "Upgrade: websocket\r\nSec-WebSocket-Version: 13\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n"); /** * If we receive a wrong response we reopen * a new socket on this pair until the connection * is ready. */ this._spec[$pair]["socket"].once("data",async (data) => { if (data.toString().indexOf("101") != -1) { console.log("---Ok on " + $pair); /** * Pong frames according to RFC 6455 */ this._spec[$pair]["pong"] = setInterval(() => { this._spec[$pair]["socket"].write(Buffer.from([0x8A,0x80,0x77,0x77,0x77,0x77])); },20000); /** * Once We've created our order book and our socket is * connected we add our * orders for the market making. If the orders have already been * created created would be set to true so it would be useless to create stripes * once again. */ /** * Before listening to the data * we have to create the order book in * order to prevent missing any data frame * from the websocket. */ var $ob = await this.createOrderbook($pair); /** * If we failed to open the order book * we do not do any further actions * because this would result in errors */ if (!$ob) { console.log("error on : " + $pair + " err message : ", err); clearInterval(this._spec[$pair]["pong"]); this._spec[$pair]["socket"].removeAllListeners("data").removeAllListeners("error"); resolve(false); } var response = this._spec[$pair]["created"] ? false : await this.createStripes($pair); this._spec[$pair]["created"] = true; this._spec[$pair]["events"].emit("finish"); resolve(response); } else { console.log("---new try on " + $pair); this.createSocket($pair).then((value) => { resolve(value); }); } }) /** * If an error occurs on our websocket * we just reopen it. But we need to clear * every thing before. */ this._spec[$pair]["socket"].on("error", async (err) => { console.log("error on : " + $pair + " err message : ", err); clearInterval(this._spec[$pair]["pong"]); this._spec[$pair]["socket"].removeAllListeners("data").removeAllListeners("error"); await this.createSocket($pair); }) this._spec[$pair]["socket"].on("data",async (data) => { return true; if (this._kill || !this._spec[$pair]["created"]) { return false} /** * We parse our data and update our orderbooks */ var $data = await this.parseData(data); $data ? console.log($data) : false; return true; $data ? await this.updateStripes($data,$pair):false; }); }); }); } /** * @param pair <String> * @param ref <Int> * @return Promise * * this function creates and store the order book for the * specified pair, the ref variable is used to count if * too much calls fail the opening on the pair is aborted */ async createOrderbook(pair,ref = 0){ this._start == false ? this._callcounter++ : false; if (this._kill) { return false} return new Promise((resolve,rej) => { $https.get("https://engine.coss.io/api/v1/dp?symbol="+pair, (res) => { var donnes = ""; res.on("data",async (chunk) => { donnes += chunk; }); res.on("end",async () => { try { donnes = JSON.parse(donnes); this._ob_list[pair] = donnes; resolve(donnes); } catch(e){ ref > 3 ? resolve(false): false; setTimeout(async() => { await this.createOrderbook(pair,ref++); resolve(true); },750); } }); }); }); } /** * @param data <String> * * Get the raw data as input and returns * a Promise containing the data in JSON */ async parseData(data){ if (data.length > 50 && data.indexOf("Server") == -1) { data = data.toString("ascii").match(/\{.+?\}/g); var $data = data.map(async (value) => { return JSON.parse(value.replace("[[","[").replace("]]","]")); }); return Promise.all($data); } else { return false; } } /** * @params data <array> A Json array containing the formatted * data to analyse * * @params pair <string> The pair corresponding to those data * * Check the data that are received if a quantity is set to 0 * we check if this quantity were part of our stripes * if it is the case we have to update our stripes */ async updateStripes(valuee,pair){ var $pairs = pair.split("_"); var final = []; var changes = false; /** * Once we receive data, we check * every frame from the websocket * to check if we received data * that includes orders that we have * opened. */ for(var data of valuee){ if (data['a']) { /** * if the data is about an ask order * that we receive, we check if this a 0 quantity * update which could mean some orders have been bought. */ if (data['a'][1] == 0 && this._spec[pair]["orderbook"]["asks"].includes(parseFloat(data['a'][0]))) { for (var val of this._spec[pair]["orderbook"]["asks"]){ /** * if we receive a 0 quantity data update, we have to check * weather we have an order at such price, if this is the case, * this means an order have been excecuted. */ if (data['a'][0] == val) { /** * if one of our asks order has been excuted we have * to rise our best bid order in order to still cover the * order book. so we calculate our new bid by multiply our highest * bid by the profit we want to take. */ changes = true; console.log("profit: ",this._spec[pair]["profit"]) var price = this._spec[pair]["orderbook"]["bids"][0] * (this._spec[pair]["profit"] / 100 + 1); var price_ceil = await this._ceil(price,this._decimal.get(pair)["price_decimal"]); price = val / price_ceil >= (this._spec[pair]["profit"] / 100 + 1) ? price_ceil : await this._floor(price,this._decimal.get(pair)["price_decimal"]); while (val / price < (this._spec[pair]["profit"] / 100 + 1)) { price = (price * 10 ** this._decimal.get(pair)["price_decimal"] - 1) / 10 ** this._decimal.get(pair)["price_decimal"]; } /** * we get the quantity that we should put for this new order. */ var amount = await this.quantity(this._spec[pair]["amount"],this._spec[pair]["ref"],price,pair); if (this._spec[pair]["allow_overflow"]) { /** * In the case we allowed overflowding, we keep * the same number of order opened at each time, * so if one order got excecuted we have to add one * above our highest ask in this case, see docs for more details. * spec []; */ /** * what ever happens if we have one order exceuted * we have to update our wallet. */ if (this._spec[pair]["orderbook"]["asks"].length <= 1) { /** * if we are over our range limit we just update our wallet * but nothing else. */ if (!this._spec[pair]["reminder_asks"]) { this._wallet[$pairs[1]] += await this.quantity(this._spec[pair]["amount"],this._spec[pair]["ref"],val,pair) * val; } this._spec[pair]["reminder_asks"] = val } else { /** * if we are in the range we specified, we get the quantity and * the price of the order we want to open and we open it. */ this._wallet[$pairs[1]] += await this.quantity(this._spec[pair]["amount"],this._spec[pair]["ref"],val,pair) * val; this._spec[pair]["orderbook"]["asks"].splice(this._spec[pair]["orderbook"]["asks"].indexOf(val),1); if(!this._spec[pair]["orderbook"]["bids"].includes(price) && this._spec[pair]["orderbook"]["asks"][0] / price >= (this._spec[pair]["profit"] / 100 * 2 + 1) && await this.openOrder(price,amount,"BUY",pair)) this._spec[pair]["orderbook"]["bids"].unshift(price); } /** * price_new, give us our new highest ask in order to still cover all the order book. */ var price_new = await this._ceil(this._spec[pair]["orderbook"]["asks"][this._spec[pair]["orderbook"]["asks"].length - 1] * (this._spec[pair]["profit"] / 100 + 1),this._decimal.get(pair)["price_decimal"]); // ceil if (this._spec[pair]["orderbook"]["asks"].length <= this._spec[pair]["orderbook"]["asks_length"]) { /** * we only want to have the same number of order as the moment * we set up our stripes, so if we have enough opened orders * it is not necessary to open more orders. */ var amount_new = await this.quantity(this._spec[pair]["amount"],this._spec[pair]["ref"],price_new,pair); if(await this.openOrder(price_new,amount_new,"SELL",pair)) this._spec[pair]["orderbook"]["asks"].push(price_new); } break; } else { /** * in the case we don't allow overfloating, we dont want to open * more orders, we just want to cover the range we gave to the * algorythm. */ if (this._spec[pair]["orderbook"]["asks"].length <= 1) { /** * if we are over our range limit we just update our wallet * but nothing else. */ if (!this._spec[pair]["reminder_asks"]) { this._wallet[$pairs[1]] += await this.quantity(this._spec[pair]["amount"],this._spec[pair]["ref"],val,pair) * val; } this._spec[pair]["reminder_asks"] = val; break; } else { /** * if we are in the range we specified, we get the quantity and * the price of the order we want to open and we open it. */ this._wallet[$pairs[1]] += await this.quantity(this._spec[pair]["amount"],this._spec[pair]["ref"],val,pair) * val; this._spec[pair]["orderbook"]["asks"].splice(this._spec[pair]["orderbook"]["asks"].indexOf(val),1); if(!this._spec[pair]["orderbook"]["bids"].includes(price) && this._spec[pair]["orderbook"]["asks"][0] / price >= (this._spec[pair]["profit"] / 100 * 2 + 1) && await this.openOrder(price,amount,"BUY",pair)) this._spec[pair]["orderbook"]["bids"].unshift(price); break; } } } } } /** * the below for loop, will most of the time, * remain usefull, however, in really rare occasion, * it will keep your orders on track, especially * when we receive multiples frames in a really short * period of time, or if we get the frames in the wrong order * but this should not happen, according to the TCP * protocol. */ for(var value of this._spec[pair]["orderbook"]["bids"]){ /** * if the frames specifies an ask that is * below our highest bid this mean we had * orders excecuted but we didn't got those frames. * so we have to do a similar work as above. */ if (data['a'][0] <= value) { /** * if the ask is below one of our * bids we have to open a new ask order. * so we get the quantity and the price. */ changes = true; var price = await this._floor(this._spec[pair]["orderbook"]["asks"][0] / (this._spec[pair]["profit"] / 100 + 1),this._decimal.get(pair)["price_decimal"]); // ceil var amount = await this.quantity(this._spec[pair]["amount"],this._spec[pair]["ref"],price,pair); if (this._spec[pair]["allow_overflow"]) { if (this._spec[pair]["orderbook"]["bids"].length <= 1) { if (!this._spec[pair]["reminder_bids"]) { this._wallet[$pairs[0]] += await this.quantity(this._spec[pair]["amount"],this._spec[pair]["ref"],value,pair); } this._spec[pair]["reminder_bids"] = value; final.push(value); } else { this._wallet[$pairs[0]] += await this.quantity(this._spec[pair]["amount"],this._spec[pair]["ref"],value,pair); if(await this.openOrder(price,amount,"SELL",pair))this._spec[pair]["orderbook"]["asks"].unshift(price); } var price_new = await this._floor(this._spec[pair]["orderbook"]["bids"][this._spec[pair]["orderbook"]["bids"].length - 1] / (this._spec[pair]["profit"] / 100 + 1),this._decimal.get(pair)["price_decimal"]); // floor if (this._spec[pair]["orderbook"]["bids"].length <= this._spec[pair]["orderbook"]["bids_length"]) { var amount_new = await this.quantity(this._spec[pair]["amount"],this._spec[pair]["ref"],price_new,pair); if(await this.openOrder(price_new,amount_new,"BUY",pair))this._spec[pair]["orderbook"]["bids"].push(price_new); } } else { if (this._spec[pair]["orderbook"]["bids"].length <= 1) { if (!this._spec[pair]["reminder_bids"]) { this._wallet[$pairs[0]] += await this.quantity(this._spec[pair]["amount"],this._spec[pair]["ref"],value,pair); } this._spec[pair]["reminder_bids"] = value; final.push(value); } else { this._wallet[$pairs[0]] += await this.quantity(this._spec[pair]["amount"],this._spec[pair]["ref"],value,pair); if(await this.openOrder(price,amount,"SELL",pair))this._spec[pair]["orderbook"]["asks"].unshift(price); } } } else { final.push(value); } } this._spec[pair]["orderbook"]["bids"] = final; if (changes && this._spec[pair]["force_liquidity"]) { while(this._spec[pair]["orderbook"]["asks"][0] / this._spec[pair]["orderbook"]["bids"][0] > (this._spec[pair]["profit"]*2/100 + 1)){ price = this._spec[pair]["orderbook"]["bids"][0] * (this._spec[pair]["profit"] / 100 + 1); price_ceil = await this._ceil(price,this._decimal.get(pair)["price_decimal"]); var bask = await this._floor(this._spec[pair]["orderbook"]["asks"][0] / (this._spec[pair]["profit"]/100 + 1),this._decimal.get(pair)["price_decimal"]); price = this._spec[pair]["orderbook"]["asks"][0] / price_ceil < (this._spec[pair]["profit"]*2/100 + 1) || bask / price_ceil < (this._spec[pair]["profit"]/100 + 1) ? await this._floor(price,this._decimal.get(pair)["price_decimal"]) : price_ceil; if (this._spec[pair]["orderbook"]["asks"][0] / price <= (this._spec[pair]["profit"]*2/100 + 1)|| this._spec[pair]["orderbook"]["bids"].includes(price) || bask / price < (this._spec[pair]["profit"]/100 + 1)) { break; } amount = await this.quantity(this._spec[pair]["amount"],this._spec[pair]["ref"],price,pair); if(await this.openOrder(price,amount,"BUY",pair)){ this._spec[pair]["orderbook"]["bids"].unshift(price); } else { break; } } } console.log("pair: ",pair," asks: ",this._spec[pair]["orderbook"]["asks"]) console.log("pair: ",pair," bids: ",this._spec[pair]["orderbook"]["bids"]) console.log('wallet :', this._wallet); final = []; } else if (data['b']) { if (data['b'][1] == 0 ) { for(var valeur of this._spec[pair]["orderbook"]["bids"]){ if (data['b'][0] == valeur) { changes = true; var price = this._spec[pair]["orderbook"]["asks"][0] / (this._spec[pair]["profit"] / 100 + 1); var price_floor = await this._floor(price,this._decimal.get(pair)["price_decimal"]); price = price_floor / valeur >= (this._spec[pair]["profit"] / 100 + 1) ? price_floor : await this._ceil(price,this._decimal.get(pair)["price_decimal"]); while (price / valeur < (this._spec[pair]["profit"] / 100 + 1)) { price = (price * 10 ** this._decimal.get(pair)["price_decimal"] + 1) / 10 ** this._decimal.get(pair)["price_decimal"]; } var amount = await this.quantity(this._spec[pair]["amount"],this._spec[pair]["ref"],price,pair); if (this._spec[pair]["allow_overflow"]) { if (this._spec[pair]["orderbook"]["bids"].length <= 1) { if (!this._spec[pair]["reminder_bids"]) { this._wallet[$pairs[0]] += await this.quantity(this._spec[pair]["amount"],this._spec[pair]["ref"],valeur,pair); } this._spec[pair]["reminder_bids"] = valeur } else { this._wallet[$pairs[0]] += await this.quantity(this._spec[pair]["amount"],this._spec[pair]["ref"],valeur,pair); this._spec[pair]["orderbook"]["bids"].splice(this._spec[pair]["orderbook"]["bids"].indexOf(valeur),1); if(!this._spec[pair]["orderbook"]["asks"].includes(price) && price / this._spec[pair]["orderbook"]["bids"][0] >= (this._spec[pair]["profit"] / 100 * 2 + 1) && await this.openOrder(price,amount,"SELL",pair))this._spec[pair]["orderbook"]["asks"].unshift(price); } var price_new = await this._round(this._spec[pair]["orderbook"]["bids"][this._spec[pair]["orderbook"]["bids"].length - 1] / (this._spec[pair]["profit"] / 100 + 1),this._decimal.get(pair)["price_decimal"]); // floor if (this._spec[pair]["orderbook"]["bids"].length <= this._spec[pair]["orderbook"]["bids_length"]) { var amount_new = await this.quantity(this._spec[pair]["amount"],this._spec[pair]["ref"],price_new,pair); if(await this.openOrder(price_new,amount_new,"BUY",pair))this._spec[pair]["orderbook"]["bids"].push(price_new); } break; } else { if (this._spec[pair]["orderbook"]["bids"].length <= 1) { if (!this._spec[pair]["reminder_bids"]) { this._wallet[$pairs[0]] += await this.quantity(this._spec[pair]["amount"],this._spec[pair]["ref"],valeur,pair); } this._spec[pair]["reminder_bids"] = valeur break; } else { this._wallet[$pairs[0]] += await this.quantity(this._spec[pair]["amount"],this._spec[pair]["ref"],valeur,pair); this._spec[pair]["orderbook"]["bids"].splice(this._spec[pair]["orderbook"]["bids"].indexOf(valeur),1); if(!this._spec[pair]["orderbook"]["asks"].includes(price) && price / this._spec[pair]["orderbook"]["bids"][0] >= (this._spec[pair]["profit"] / 100 * 2 + 1) && await this.openOrder(price,amount,"SELL",pair))this._spec[pair]["orderbook"]["asks"].unshift(price); break; } } } } } for(var value of this._spec[pair]["orderbook"]["asks"]){ if (data['b'][0] >= value) { changes = true; var price = await this._ceil(this._spec[pair]["orderbook"]["bids"][0] * (this._spec[pair]["profit"] / 100 + 1),this._decimal.get(pair)["price_decimal"]); var amount = await this.quantity(this._spec[pair]["amount"],this._spec[pair]["ref"],price,pair); if (this._spec[pair]["allow_overflow"]) { if (this._spec[pair]["orderbook"]["asks"].length <= 1) { if (!this._spec[pair]["reminder_asks"]) { this._wallet[$pairs[1]] += await this.quantity(this._spec[pair]["amount"],this._spec[pair]["ref"],value,pair) * value; } final.push(value); this._spec[pair]["reminder_asks"] = value } else { this._wallet[$pairs[1]] += await this.quantity(this._spec[pair]["amount"],this._spec[pair]["ref"],value,pair) * value; if(await this.openOrder(price,amount,"BUY",pair))this._spec[pair]["orderbook"]["bids"].unshift(price); } var price_new = await this._ceil(this._spec[pair]["orderbook"]["asks"][this._spec[pair]["orderbook"]["asks"].length - 1] * (this._spec[pair]["profit"] / 100 + 1),this._decimal.get(pair)["price_decimal"]); if (this._spec[pair]["orderbook"]["asks"].length <= this._spec[pair]["orderbook"]["asks_length"]) { var amount_new = await this.quantity(this._spec[pair]["amount"],this._spec[pair]["ref"],price_new,pair); if(await this.openOrder(price_new,amount_new,"SELL",pair))this._spec[pair]["orderbook"]["asks"].push(price_new); } } else { if (this._spec[pair]["orderbook"]["asks"].length <= 1) { if (!this._spec[pair]["reminder_asks"]) { this._wallet[$pairs[1]] += await this.quantity(this._spec[pair]["amount"],this._spec[pair]["ref"],value,pair) * value; } final.push(value); this._spec[pair]["reminder_asks"] = value } else { this._wallet[$pairs[1]] += await this.quantity(this._spec[pair]["amount"],this._spec[pair]["ref"],value,pair) * value; if(await this.openOrder(price,amount,"BUY",pair))this._spec[pair]["orderbook"]["bids"].unshift(price); } } } else { final.push(value); } } this._spec[pair]["orderbook"]["asks"] = final; if (changes && this._spec[pair]["force_liquidity"]) { while(this._spec[pair]["orderbook"]["asks"][0] / this._spec[pair]["orderbook"]["bids"][0] > (this._spec[pair]["profit"]*2/100 + 1)){ price = this._spec[pair]["orderbook"]["asks"][0] / (this._spec[pair]["profit"] / 100 + 1); price_floor = await this._floor(price,this._decimal.get(pair)["price_decimal"]); var bbid = await this._ceil(this._spec[pair]["orderbook"]["bids"][0] * (this._spec[pair]["profit"]/100 + 1),this._decimal.get(pair)["price_decimal"]); price = price_floor / this._spec[pair]["orderbook"]["bids"][0] < (this._spec[pair]["profit"]*2/100 + 1) || price_floor / bbid < (this._spec[pair]["profit"]/100 + 1)? await this._ceil(price,this._decimal.get(pair)["price_decimal"]) : price_floor; amount = await this.quantity(this._spec[pair]["amount"],this._spec[pair]["ref"],price,pair); if (price / this._spec[pair]["orderbook"]["bids"][0] <= (this._spec[pair]["profit"]*2/100 + 1) || this._spec[pair]["orderbook"]["asks"].includes(price) || price / bbid < (this._spec[pair]["profit"]/100 + 1)) { break; } if(await this.openOrder(price,amount,"SELL",pair)){ this._spec[pair]["orderbook"]["asks"].unshift(price); } else { break; } } } console.log("pair: ",pair," asks: ",this._spec[pair]["orderbook"]["asks"]) console.log("pair: ",pair," bids: ",this._spec[pair]["orderbook"]["bids"]) console.log('wallet :', this._wallet); } } return true; } /** * @param <pair> the pair on which * we want to make market making * * this function determines through several * ways the best on to create our lower ask * and our lowest bid */ async createStripes($pair){ var binance = {}; var now = {"asks": parseFloat(this._ob_list[$pair]['asks'][0][0]), "bids": parseFloat(this._ob_list[$pair]["bids"][0][0])}; var arb = {}; return new Promise((resolve,reject) => { var twice = $pair.split("_"); /** * this tries to get the best bid * and the best ask from binance to compare to * coss in the case the spread is too wide */ $https.get("https://www.binance.com/api/v1/depth?limit=5&symbol=" + twice.join(""), async res => { let data = ""; res.on("data", chunk => {data += chunk}); res.on("end", async () => { try{ data = JSON.parse(data); } catch(e){ binance = false; } if (data["code"]) { console.log("we don't have " + $pair + " on binance") binance = false; } else { /** * Like binance and coss price precision are not the * same we have to ajust to coss precision before doing * any work, not doing so would make us unable to open * any order due to wrong price precision. */ binance["asks"] = await this._ceil(data["asks"][0][0],this._decimal.get($pair)["price_decimal"]); binance["bids"] = await this._floor(data["bids"][0][0],this._decimal.get($pair)["price_decimal"]); } /** * Here we try to get some comparison point using * pseudo arbitrage. We compare to actual order book * prices. */ if (!(twice.includes("ETH") && twice.includes("BTC"))) { if (twice.includes("ETH")) { /** * We use the ordenate function because * spliting pair to get arbitrage prices * can lead to differents situations * (eg: COSS_ETH => COSS_BTC and ETH_BTC * COSS_BTC => COSS_ETH and ETH_BTC) */ arb = await this.ordenate("BTC",twice) } else { arb = await this.ordenate("ETH",twice); } } else { arb = false; } /** * We can now set up our price for stripes. */ console.log("now : ", now); console.log("arb : ", arb); console.log("binance : ", binance); var response = await this.setStripes(now,arb,binance,$pair) resolve(response); }); }) }) } async _floor($value,$decimals){ return Math.floor($value * 10 ** $decimals) / 10 ** $decimals; } async _ceil($value,$decimals){ return Math.ceil($value * 10 ** $decimals) / 10 ** $decimals; } async _round($value,$decimals){ return Math.round($value * 10 ** $decimals) / 10 ** $decimals; } /** * @params $pair1 <String> * * @params $pair1 <String> * * This function returns one pair * in the right order */ async ordenatePair($pair1,$pair2){ var order = [ "USD", "EUR", "GBP", "TUSD", "GUSD", "USDC", "USDT", "DAI", "BTC", "ETH", "COSS" ]; var one = order.indexOf($pair1); var two = order.indexOf($pair2); if (one == -1) return [$pair1,order[two]].join("_"); if (two == -1) return [$pair2,order[one]].join("_"); if (two < one) return [order[one],order[two]].join("_"); if (two > one) return [order[two],order[one]].join("_"); } /** * @params $name <String> * * @params $tab <Array> * * This function the price using the * $tab variable to calculate arbitrages */ async ordenate($name,$tab){ var $pair = $tab.join("_"); var $pair1 = await this.ordenatePair($tab[0],$name); var $pair2 = await this.ordenatePair($tab[1],$name); if(!await this.createOrderbook($pair1)) return false; if(!await this.createOrderbook($pair2)) return false; if (!this._ob_list[$pair1]["bids"][0].length || !this._ob_list[$pair1]["asks"][0].length) { return false; } else { if ($pair2.split("_")[1] == $name) { if ($pair1.split("_")[1] == $name) { var price_bids = await this._ceil(this._ob_list[$pair1]["bids"][0][0] / this._ob_list[$pair2]["asks"][0][0],this._decimal.get($pair)["price_decimal"]); var price_asks = await this._floor(this._ob_list[$pair1]["asks"][0][0] / this._ob_list[$pair2]["bids"][0][0],this._decimal.get($pair)["price_decimal"]); return {"asks": price_asks,"bids": price_bids} } else { return false; } } if ($pair2.split("_")[0] == $name) { if ($pair1.split("_")[0] == $name) { var price_bids = await this._ceil(this._ob_list[$pair2]["bids"][0][0] / this._ob_list[$pair1]["asks"][0][0],this._decimal.get($pair)["price_decimal"]); var price_asks = await this._floor(this._ob_list[$pair2]["asks"][0][0] / this._ob_list[$pair1]["bids"][0][0],this._decimal.get($pair)["price_decimal"]); return {"asks": price_asks,"bids": price_bids} } else { var price_bids = await this._floor(this._ob_list[$pair1]["bids"][0][0] * this._ob_list[$pair2]["bids"][0][0],this._decimal.get($pair)["price_decimal"]); var price_asks = await this._floor(this._ob_list[$pair1]["asks"][0][0] * this._ob_list[$pair2]["asks"][0][0],this._decimal.get($pair)["price_decimal"]); return {"asks": price_asks,"bids": price_bids} } } } } /** * This function is usefull in order to * split a bit our create stripe function * if not the create stripe function be to * big and not understandable. */ async setStripes(now,arb,binance,$pair){ /** * the dec variable is the price precision */ var dec = this._decimal.get($pair)["price_decimal"]; var spread_int = now["asks"] * 10 ** dec - now["bids"] * 10 ** dec; /** * There is a specific treatment if * the spread is below two price precision units * eg price precision 0.001 price_asks => 10.002 price_bids => 10.000 : spread in units = 2 */ if (spread_int <= 2) { /** * In the case the spread is exactly * one unit different between best bid and best ask */ if (spread_int == 1) { /** * Now we check if the spread is wide enough * to meet our profit criteria */ if (now["asks"]/now["bids"] >= this._spec[$pair]["profit"]*2/100 + 1) { /** * If we're here this means * that one unit of difference meets out * profit requirement, so we need to put our * first bid two units under the best asks because * we always need one empty slot between best ask * and best bid */ return await this.finalStripes(now["asks"], await this._add(now["asks"],dec,-2),$pair) } else { /** * If we're here this means we have a one unit spread * but doesn't meet our profit criteria so we open our orders * using the best ask has reference (this is arbitrary). */ return await this.finalStripes(now["asks"], await this._floor(now["asks"]/ (this._spec[$pair]["profit"] * 2/100 + 1), dec),$pair) } } else /** * Reaching this else statement means that spread is * exactly of two units, conditions are preatty similar to * above ones. */ { if (now["asks"]/now["bids"] >= this._spec[$pair]["profit"] * 2/100 + 1) { return await this.finalStripes(now["asks"], now["bids"],$pair) } else { return await this.finalStripes(now["asks"], await this._floor(now["asks"]/ (this._spec[$pair]["profit"] * 2/100 + 1), dec),$pair) } } } /** * If we set the force liquidity option to true */ if (this._spec[$pair]["force_liquidity"]) { /** * Reaching here we're going to determine * which is the best option to provide liquidity * on this pair, we go from the best option * to the worst one. */ if (binance) { /** * If we reach here we * got some data from binance * so we duplicate here the price from * binance and add our profit. */ return await this.finalStripes(binance["asks"], await this._floor(binance["asks"]/ (this._spec[$pair]["profit"] * 2/100 + 1), dec),$pair) } else { if (arb) { /** * Reaching here means that we were able to determine * our arbitrage price from ETH and BTC * now we choose the most narraow spread from the actual * price and the arbitrage price */ if(now["asks"]/now["bids"] > arb["asks"]/arb["bids"]){ return await this.finalStripes(arb["asks"], await this._floor(arb["asks"]/ (this._spec[$pair]["profit"] * 2/100 + 1), dec),$pair) } else { return await this.finalStripes(now["asks"], await this._floor(now["asks"]/ (this._spec[$pair]["profit"] * 2/100 + 1), dec),$pair) } } else { /** * Be carefull reaching here can lead * to some lost in particular cases * see doc ________________________ */ return await this.finalStripes(now["asks"], await this._floor(now["asks"]/ (this._spec[$pair]["profit"] * 2/100 + 1), dec),$pair) } } } else { /** * Reaching here means we don't want to * force liquidity on this pair but want to * put orders on the order book */ if (arb) { if(now["asks"]/now["bids"] > arb["asks"]/arb["bids"]){ if (arb["asks"]/arb["bids"] > this._spec[$pair]["profit"] * 2/100 + 1) { /** * You might be aware that in this case you will in fact * increase your profit, this one will be equal to : arb["asks"]/arb["bids"] */ return await this.finalStripes(arb["asks"], arb["bids"],$pair); } else { return await this.finalStripes(arb["asks"], await this._floor(arb["asks"]/ (this._spec[$pair]["profit"] * 2/100 + 1), dec),$pair) } } else { if (now["asks"]/now["bids"] > this._spec[$pair]["profit"] * 2/100 + 1) { /** * You might be aware that in this case you will in fact * increase your profit, this one will be equal to : now["asks"]/now["bids"] */ return await this.finalStripes(now["asks"], now["bids"],$pair); } else { return await this.finalStripes(now["asks"], await this._floor(now["asks"]/ (this._spec[$pair]["profit"] * 2/100 + 1), dec),$pair) } } } else { if (now["asks"]/now["bids"] > this._spec[$pair]["profit"] * 2/100 + 1) { /** * You might be aware that in this case you will in fact * increase your profit, this one will be equal to : now["asks"]/now["bids"] */ return await this.finalStripes(now["asks"], now["bids"],$pair); } else { return await this.finalStripes(now["asks"], await this._floor(now["asks"]/ (this._spec[$pair]["profit"] * 2/100 + 1), dec),$pair) } } } } async finalStripes($asks,$bids,$pair){ return new Promise(async(resolve,reject) => { /** * our prices decimal eg COSS_ETH => 6 */ var dec = this._decimal.get($pair)["price_decimal"]; /** * The amount we set in our beginning params */ var amount_one = this._spec[$pair]["total_amount_one"] ? this._spec[$pair]["total_amount_one"] : false; var amount_two = this._spec[$pair]["total_amount_two"] ? this._spec[$pair]["total_amount_two"] : false; /** * Our amount precision, means the maximum digits * allowed on the quantity of the pair */ var amount_dec = this._decimal.get($pair)["amount_decimal"] /** * each crypto name separately */ var [cryptoo1,cryptoo2] = $pair.split("_"); /** * this is the amount we have in our wallet * for each crypto of the pair. */ var crypto1 = this._wallet[cryptoo1] ? this._wallet[cryptoo1] : 0; var crypto2 = this._wallet[cryptoo2] ? this._wallet[cryptoo2] : 0; /** * if we don't have crypto in wallet * we should not go any further and return now */ if (crypto1 == 0 || crypto2 == 0) { resolve(false)} /** * this is the profit we want to take * between each pair of order. */ var profit = this._spec[$pair]["profit"]/100 + 1; this._spec[$pair]["orderbook"] = {}; /** * this is our range for initially covering the * orderbook. */ var [low_bid,high_ask] = this._spec[$pair]["range"]; $assert.ok(low_bid < high_ask," The leftmost value of the range has to be the lowest") $assert.ok(this._spec[$pair]["ref"] == 1 || this._spec[$pair]["ref"] == 0 || this._spec[$pair]["ref"] == 2,"ref has to be 0 or 1 ONLY see doc"); $assert.ok(typeof this._spec[$pair]['amount'] == "number" || this._spec[$pair]["amount"] == false,"amount has to be an interger or false"); /** * those variables are used to create our orders * properly. */ var ready = true; var ask_start = $asks; var bid_start = $bids; console.log("asks:", $asks, " bids: ",$bids); var orders_asks = []; var orders_bids = []; var quantity1 = 0; var quantity2 = 0; if (this._spec[$pair]["amount"]) { /** * if we gave to the program the exact * amount of crypto that we want to trade * for our market making we reach here. */ while (ready){ /** * typically this while loop * allows us to determine at which * price each order will be opened */ while(ask_start < high_ask){ /** * first regarding the orders on the asks * side, we take our starting ask price, determined * in our set stripe function, then multiply it by our * profit until reaching our high ask price, given in the range * of the pair _spec variable. */ var amount = await this.quantity(this._spec[$pair]["amount"],this._spec[$pair]["ref"],ask_start,$pair) quantity1 += amount; orders_asks.push([parseFloat(ask_start), amount]); /** * We ceil here in order to have at least, * the prodfit we gave to the program */ ask_start = await this._ceil(ask_start * profit, dec); } while(bid_start > low_bid){ /** * we do the same on the bid side * for both sides we keep a trace * of the total quantity that would be taken * from our wallet in order to open all the orders. */ var amount = await this.quantity(this._spec[$pair]["amount"],this._spec[$pair]["ref"],bid_start,$pair) quantity2 += amount * bid_start; orders_bids.push([parseFloat(bid_start), amount]); bid_start = await this._floor(bid_start / profit, dec); } console.log("quantity of " + cryptoo1 + " used : " + quantity1); console.log("quantity of " + cryptoo2 + " used : " + quantity2); console.log(cryptoo1 + " wallet : " + crypto1); console.log(cryptoo2 + "wallet : " + crypto2); if (quantity1 > crypto1 || quantity2 > crypto2) { /** * if one of the total amount of crypto * that we want to take are superior than * what we have in our wallet, we rise our profit * in order to open less orders and this should allow to * use less crypto, in order to meet our wallet criteria. */ profit *= this._spec[$pair]["profit"]/100 +1 ; /** * we need in order to keep a coherent gap between each side * adapt our starting bid price. */ ask_start = $asks; while($asks / $bids < (profit - 1) * 2 + 1 ){ $bids = await this._floor(($bids * 10 ** dec - 1) / 10 ** dec, dec); } bid_start = $bids; quantity1 = 0; quantity2 = 0; orders_asks = []; orders_bids = []; } else { ready = false; /** * if we changed our profit we have to change it * in our pair specification. */ if (this._spec[$pair]["profit"]/100 + 1 != profit) { console.log(" changing profit new profit is : " + (profit - 1) * 100); this._spec[$pair]["profit"] = (profit - 1) * 100 ; } } } this._spec[$pair]["orderbook"]["bids"] = []; this._spec[$pair]["orderbook"]["asks"] = []; /** * if the amount of orders we want to open would break the API * request limit call we have to return false in order, to give the * lead to our function that would wait for one minut */ if (orders_asks.length + orders_bids.length + this._callcounter > 700){ console.log("reached API limit request waiting to drain on " + $pair); setTimeout(async() => { for(var value of orders_asks){ if(await this.openOrder(value[0],value[1],"SELL",$pair))this._spec[$pair]["orderbook"]["asks"].push(value[0]); } for(var value of orders_bids){ if(await this.openOrder(value[0],value[1],"BUY",$pair))this._spec[$pair]["orderbook"]["bids"].push(value[0]); } console.log("pair: " + $pair + " order asks",this._spec[$pair]["orderbook"]["asks"]); console.log("pair: " + $pair + " order bids",this._spec[$pair]["orderbook"]["bids"]); console.log("wallet :"); console.log(this._wallet); this._spec[$pair]["orderbook"]["asks_length"] = this._spec[$pair]["orderbook"]["asks"].length; this._spec[$pair]["orderbook"]["bids_length"] = this._spec[$pair]["orderbook"]["bids"].length; this._spec[$pair]["created"] = true; resolve(true); },6500