exchangemarket/src/group.js
sairajzero d3b27348ee Bug fix
- Fixed various bugs in the last update
- Added 'use strict'; to multiple files to prevent undeclared variables
2021-12-03 20:09:40 +05:30

377 lines
16 KiB
JavaScript

'use strict';
var DB; //container for database
function addTag(floID, tag) {
return new Promise((resolve, reject) => {
DB.query("INSERT INTO Tags (floID, tag) VALUE (?,?)", [floID, tag])
.then(result => resolve(`Added ${floID} to ${tag}`))
.catch(error => {
if (error.code === "ER_DUP_ENTRY")
reject(INVALID(`${floID} already in ${tag}`));
else
reject(error);
});
});
}
function removeTag(floID, tag) {
return new Promise((resolve, reject) => {
DB.query("DELETE FROM Tags WHERE floID=? AND tag=?", [floID, tag])
.then(result => resolve(`Removed ${floID} from ${tag}`))
.catch(error => reject(error));
})
}
function getBestPairs(currentRate) {
return new Promise((resolve, reject) => {
DB.query("SELECT tag, sellPriority, buyPriority FROM TagList").then(result => {
//Sorted in Ascending (ie, stack; pop for highest)
let tags_buy = result.sort((a, b) => a.buyPriority > b.buyPriority ? 1 : -1).map(r => r.tag);
let tags_sell = result.sort((a, b) => a.sellPriority > b.sellPriority ? 1 : -1).map(r => r.tag);
resolve(new bestPair(currentRate, tags_buy, tags_sell));
}).catch(error => reject(error))
})
}
const bestPair = function(cur_rate, tags_buy, tags_sell) {
const currentRate = cur_rate;
Object.defineProperty(this, 'cur_rate', {
get: () => currentRate
});
this.get = () => new Promise((resolve, reject) => {
Promise.allSettled([getBuyOrder(), getSellOrder()]).then(results => {
if (results[0].status === "fulfilled" && results[1].status === "fulfilled")
resolve({
buyOrder: results[0].value,
sellOrder: results[1].value,
null_base: getSellOrder.cache.mode_null
})
else
reject({
buy: results[0].reason,
sell: results[1].reason
})
}).catch(error => reject(error))
});
this.next = (tx_quantity, incomplete_sell, flag_sell) => {
let buy = getBuyOrder.cache,
sell = getSellOrder.cache;
if (buy.cur_order && sell.cur_order) {
//buy order
if (tx_quantity < buy.cur_order.quantity)
buy.cur_order.quantity -= tx_quantity;
else if (tx_quantity == buy.cur_order.quantity)
buy.cur_order = null;
else
throw Error("Tx quantity cannot be more than order quantity");
//sell order
if (tx_quantity < sell.cur_order.quantity) {
sell.cur_order.quantity -= tx_quantity;
if (incomplete_sell) {
if (!sell.mode_null && flag_sell)
sell.null_queue.push(sell.cur_order);
sell.cur_order = null;
}
} else if (tx_quantity == sell.cur_order.quantity)
sell.cur_order = null;
else
throw Error("Tx quantity cannot be more than order quantity");
} else
throw Error("No current order found");
};
const getSellOrder = () => new Promise((resolve, reject) => {
let cache = getSellOrder.cache;
if (cache.cur_order) { //If cache already has a pending order
verifySellOrder(cache.cur_order, currentRate, cache.mode_null).then(result => {
cache.cur_order = result;
resolve(result);
}).catch(error => {
if (error !== false)
return reject(error);
//Order not valid (minimum gain)
cache.cur_order = null;
getSellOrder()
.then(result => resolve(result))
.catch(error => reject(error))
})
} else if (cache.orders && cache.orders.length) { //If cache already has orders in priority
getTopValidSellOrder(cache.orders, currentRate, cache.mode_null).then(result => {
cache.cur_order = result;
resolve(result);
}).catch(error => {
if (error !== false)
return reject(error);
//No valid order found in current tag
cache.orders = null;
getSellOrder()
.then(result => resolve(result))
.catch(error => reject(error))
})
} else if (cache.tags.length) { //If cache has remaining tags
cache.cur_tag = cache.tags.pop();
getSellOrdersInTag(cache.cur_tag, currentRate).then(orders => {
cache.orders = orders;
getSellOrder()
.then(result => resolve(result))
.catch(error => reject(error))
}).catch(error => reject(error));
} else if (!cache.end) { //Un-tagged floID's orders (do only once)
getUntaggedSellOrders(currentRate).then(orders => {
cache.orders = orders;
cache.cur_tag = null;
cache.end = true;
getSellOrder()
.then(result => resolve(result))
.catch(error => reject(error))
}).catch(error => reject(error));
} else if (!cache.mode_null) { //Lowest priority Coins (FLO Brought from other sources)
cache.orders = cache.null_queue.reverse(); //Reverse it so that we can pop the highest priority
cache.mode_null = true;
cache.null_queue = null;
getSellOrder()
.then(result => resolve(result))
.catch(error => reject(error))
} else
reject(false);
});
getSellOrder.cache = {
tags: tags_sell,
null_queue: [],
mode_null: false
};
const getBuyOrder = () => new Promise((resolve, reject) => {
let cache = getBuyOrder.cache;
if (cache.cur_order) { //If cache already has a pending order
verifyBuyOrder(cache.cur_order, currentRate).then(result => {
cache.cur_order = result;
resolve(result);
}).catch(error => {
if (error !== false)
return reject(error);
//Order not valid (cash not available)
cache.cur_order = null;
getBuyOrder()
.then(result => resolve(result))
.catch(error => reject(error))
})
} else if (cache.orders && cache.orders.length) { //If cache already has orders in priority
getTopValidBuyOrder(cache.orders, currentRate).then(result => {
cache.cur_order = result;
resolve(result);
}).catch(error => {
if (error !== false)
return reject(error);
//No valid order found in current tag
cache.orders = null;
getBuyOrder()
.then(result => resolve(result))
.catch(error => reject(error))
})
} else if (cache.tags.length) { //If cache has remaining tags
cache.cur_tag = cache.tags.pop();
getBuyOrdersInTag(cache.cur_tag, currentRate).then(orders => {
cache.orders = orders;
getBuyOrder()
.then(result => resolve(result))
.catch(error => reject(error))
}).catch(error => reject(error));
} else if (!cache.end) { //Un-tagged floID's orders (do only once)
getUntaggedBuyOrders(currentRate).then(orders => {
cache.orders = orders;
cache.cur_tag = null;
cache.end = true;
getBuyOrder()
.then(result => resolve(result))
.catch(error => reject(error))
}).catch(error => reject(error));
} else
reject(false);
});
getBuyOrder.cache = {
tags: tags_buy
};
}
function getUntaggedSellOrders(cur_price) {
return new Promise((resolve, reject) => {
DB.query("SELECT SellOrder.id, SellOrder.floID, SellOrder.quantity FROM SellOrder" +
" LEFT JOIN Tags ON Tags.floID = SellOrder.floID" +
" WHERE Tags.floID IS NULL AND SellOrder.minPrice <=? ORDER BY SellOrder.time_placed DESC", [cur_price])
.then(orders => resolve(orders))
.catch(error => reject(error))
})
}
function getUntaggedBuyOrders(cur_price) {
return new Promise((resolve, reject) => {
DB.query("SELECT BuyOrder.id, BuyOrder.floID, BuyOrder.quantity FROM BuyOrder" +
" LEFT JOIN Tags ON Tags.floID = BuyOrder.floID" +
" WHERE Tags.floID IS NULL AND BuyOrder.maxPrice >=? ORDER BY BuyOrder.time_placed DESC", [cur_price])
.then(orders => resolve(orders))
.catch(error => reject(error))
})
}
function getSellOrdersInTag(tag, cur_price) {
return new Promise((resolve, reject) => {
DB.query("SELECT SellOrder.id, SellOrder.floID, SellOrder.quantity FROM SellOrder" +
" INNER JOIN Tags ON Tags.floID = SellOrder.floID" +
" WHERE Tags.tag = ? AND SellOrder.minPrice <=? ORDER BY SellOrder.time_placed DESC", [tag, cur_price]).then(orders => {
if (orders.length <= 1) // No (or) Only-one order, hence priority sort not required.
resolve(orders);
else
getPointsFromAPI(tag, orders.map(o => o.floID)).then(points => {
let orders_sorted = orders.map(o => [o, points[o.floID]])
.sort((a, b) => a[1] > b[1] ? -1 : a[1] < b[1] ? 1 : 0) //sort by points (ascending)
.map(x => x[0]);
resolve(orders_sorted);
}).catch(error => reject(error))
}).catch(error => reject(error))
});
}
function getBuyOrdersInTag(tag, cur_price) {
return new Promise((resolve, reject) => {
DB.query("SELECT BuyOrder.id, BuyOrder.floID, BuyOrder.quantity FROM BuyOrder" +
" INNER JOIN Tags ON Tags.floID = BuyOrder.floID" +
" WHERE Tags.tag = ? AND BuyOrder.maxPrice >=? ORDER BY BuyOrder.time_placed DESC", [tag, cur_price]).then(orders => {
if (orders.length <= 1) // No (or) Only-one order, hence priority sort not required.
resolve(orders);
else
getPointsFromAPI(tag, orders.map(o => o.floID)).then(points => {
let orders_sorted = orders.map(o => [o, points[o.floID]])
.sort((a, b) => a[1] > b[1] ? -1 : a[1] < b[1] ? 1 : 0) //sort by points (ascending)
.map(x => x[0]);
resolve(orders_sorted);
}).catch(error => reject(error))
}).catch(error => reject(error))
});
}
function getPointsFromAPI(tag, floIDs) {
floIDs = Array.from(new Set(floIDs));
return new Promise((resolve, reject) => {
DB.query("SELECT api FROM TagList WHERE tag=?", [tag]).then(result => {
let api = result[0].api;
Promise.allSettled(floIDs.map(id => fetch_api(api, id))).then(result => {
let points = {};
for (let i in result)
if (result[i].status === "fulfilled")
points[floIDs[i]] = result[i].value;
resolve(points);
}).catch(error => reject(error))
}).catch(error => reject(error))
});
}
function fetch_api(api, id) {
return new Promise((resolve, reject) => {
//TODO: fetch data from API
let url = api.replace('<flo-id>', id);
global.fetch(url).then(response => {
if (response.ok)
response.text()
.then(result => resolve(result))
.catch(error => reject(error))
else
reject(response);
}).catch(error => reject(error))
})
}
function getTopValidSellOrder(orders, cur_price, mode_null) {
return new Promise((resolve, reject) => {
if (!orders.length)
return reject(false)
verifySellOrder(orders.pop(), cur_price, mode_null) //pop: as the orders are sorted in ascending (highest point should be checked 1st)
.then(result => resolve(result))
.catch(error => {
if (error !== false)
return reject(error);
getTopValidSellOrder(orders, cur_price, mode_null)
.then(result => resolve(result))
.catch(error => reject(error));
});
});
}
function verifySellOrder(sellOrder, cur_price, mode_null) {
return new Promise((resolve, reject) => {
if (!mode_null)
DB.query("SELECT quantity, base FROM Vault WHERE floID=? AND base IS NOT NULL ORDER BY base", [sellOrder.floID]).then(result => {
let rem = sellOrder.quantity,
sell_base = 0,
base_quantity = 0;
for (let i = 0; i < result.length && rem > 0; i++) {
if (rem < result[i].quantity) {
sell_base += (rem * result[i].base);
base_quantity += rem;
rem = 0;
} else {
sell_base += (result[i].quantity * result[i].base);
base_quantity += result[i].quantity;
rem -= result[i].quantity;
}
}
if (base_quantity)
sell_base = sell_base / base_quantity;
if (sell_base > cur_price)
reject(false);
else
resolve(sellOrder);
}).catch(error => reject(error));
else if (mode_null)
DB.query("SELECT SUM(quantity) as total FROM Vault WHERE floID=?", [sellOrder.floID]).then(result => {
if (result.total < sellOrder.quantity)
console.warn(`Sell Order ${sellOrder.id} was made without enough FLO. This should not happen`);
if (result.total > 0)
resolve(sellOrder);
else
reject(false);
}).catch(error => reject(error))
})
}
function getTopValidBuyOrder(orders, cur_price) {
return new Promise((resolve, reject) => {
if (!orders.length)
return reject(false)
verifyBuyOrder(orders.pop(), cur_price) //pop: as the orders are sorted in ascending (highest point should be checked 1st)
.then(result => resolve(result))
.catch(error => {
if (error !== false)
return reject(error);
getTopValidBuyOrder(orders, cur_price)
.then(result => resolve(result))
.catch(error => reject(error));
});
});
}
function verifyBuyOrder(buyOrder, cur_price) {
return new Promise((resolve, reject) => {
DB.query("SELECT rupeeBalance AS bal FROM Cash WHERE floID=?", [buyOrder.floID]).then(result => {
if (result[0].bal < cur_price * buyOrder.quantity) {
//This should not happen unless a buy order is placed when user doesnt have enough rupee balance
console.warn(`Buy order ${buyOrder.id} is active, but rupee# is insufficient`);
reject(false);
} else
resolve(buyOrder);
}).catch(error => reject(error));
})
}
module.exports = {
addTag,
removeTag,
getBestPairs,
set DB(db) {
DB = db;
}
};