Skip to content

Commit

Permalink
Merge pull request #26 from Adamant-im/dev
Browse files Browse the repository at this point in the history
v5.5.0
  • Loading branch information
adamant-al authored May 21, 2023
2 parents beff817 + c06ed29 commit 1d192eb
Show file tree
Hide file tree
Showing 18 changed files with 1,871 additions and 685 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ module.exports = {
'max-len': ['error',
{ 'code': 131,
'ignoreTrailingComments': true,
'ignoreComments': true,
'ignoreUrls': true,
'ignoreStrings': true,
'ignoreTemplateLiterals': true,
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Trading is a mode when a bot runs orders according to some strategy. It can be p

* [P2PB2B](https://p2pb2b.com)
* [Azbit](https://azbit.com?referralCode=9YVWYAF)
* [StakeCube](https://stakecube.net/?team=adm)

To add other exchange support, see [marketmaking.app/services](https://marketmaking.app/services/).

Expand Down
3 changes: 2 additions & 1 deletion config.default.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
/** List of supported exchanges **/
"exchanges": [
"P2PB2B",
"Azbit"
"Azbit",
"StakeCube"
],

/** Exchange to work with. Case insensitive. **/
Expand Down
2 changes: 1 addition & 1 deletion helpers/const.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ module.exports = {
PRINT_DECIMALS: 8, // For pretty print, 9.12345678 ETH
MAX_ADM_MESSAGE_LENGTH: 10000,
EXECUTE_IN_ORDER_BOOK_MAX_PRICE_CHANGE_PERCENT: 0.15, // In-orderbook trading: don't change price by mm-order more, than 0.15%
LIQUIDITY_SS_MAX_SPREAD_PERCENT: 0.3, // Liquidity spread support orders: Maintain spread percent
LIQUIDITY_SS_MAX_SPREAD_PERCENT: 0.2, // Liquidity spread support orders: Maintain spread percent
DEFAULT_ORDERBOOK_ORDERS_COUNT: 15,
LADDER_STATES: ['Not placed', 'Open', 'Filled', 'Partly filled', 'Cancelled', 'Missed', 'To be removed', 'Removed'],
LADDER_OPENED_STATES: ['Open', 'Partly filled'],
Expand Down
109 changes: 51 additions & 58 deletions helpers/dbModel.js
Original file line number Diff line number Diff line change
@@ -1,85 +1,78 @@
module.exports = (db) => {
return class {
constructor(data = {}, isSave) {
class Model {
constructor(data = {}, shouldSave) {
this.db = db;

Object.assign(this, data);
if (isSave) {

if (shouldSave) {
this.save();
}
}
static get db() {
return db;
}
static find(a) { // return Array
return new Promise((resolve, reject) => {
this.db.find(a).toArray((err, data) => {
resolve(data.map((d)=>new this(d)));
});
});
static async find(req) {
const data = await db.find(req).toArray();

return data.map((d) => new this(d));
}
static aggregate(a) { // return Array
return new Promise((resolve, reject) => {
this.db.aggregate(a).toArray((err, data) => {
resolve(data.map((d)=>new this(d)));
});
});
static async aggregate(req) {
const data = await db.aggregate(req).toArray();

return data.map((d) => new this(d));
}
static findOne(a) {
return new Promise((resolve, reject) => {
db.findOne(a).then((doc, b) => {
if (!doc) {
resolve(doc);
} else {
resolve(new this(doc));
}
});
});
static async findOne(req) {
const doc = await db.findOne(req);

return doc ? new this(doc) : doc;
}
static deleteOne(a) {
return new Promise((resolve, reject) => {
delete a.db;
db.deleteOne(a).then((res) => {
resolve(res.deletedCount);
});
});
static async deleteOne(req) {
delete req.db;

const { deletedCount } = await db.deleteOne(req);

return deletedCount;
}
static count(a) {
return new Promise((resolve, reject) => {
db.count(a).then((count) => {
resolve(count);
});
});
static async count(req) {
const count = await db.count(req);

return count;
}
_data() {
const data = {};
for (const field in this) {
if (!['db', '_id'].includes(field)) {
data[field] = this[field];

for (const fieldName in this) {
if (Object.prototype.hasOwnProperty.call(this, fieldName)) {
if (!['db', '_id'].includes(fieldName)) {
data[fieldName] = this[fieldName];
}
}
}

return data;
}
async update(obj, isSave) {
async update(obj, shouldSave) {
Object.assign(this, obj);
if (isSave) {

if (shouldSave) {
await this.save();
}
}
save() {
return new Promise((resolve, reject) => {
if (!this._id) {
db.insertOne(this._data(), (err, res) => {
this._id = res.insertedId;
resolve(this._id);
});
} else {
db.updateOne({ _id: this._id }, {
$set: this._data(),
}, { upsert: true }).then(() => {
resolve(this._id);
});
}
});
async save() {
if (!this._id) {
const res = await db.insertOne(this._data());
this._id = res.insertedId;
return this._id;
} else {
await db.updateOne({ _id: this._id }, {
$set: this._data(),
}, { upsert: true });

return this._id;
}
}
};

return Model;
};
100 changes: 69 additions & 31 deletions helpers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,35 @@ module.exports = {

/**
* Returns object with all of properties as a string for logging
* @param {*} object Data to convert to string
* @return {String}
*/
getFullObjectString(object) {
const util = require('util');
return util.inspect(object, { showHidden: false, depth: null, colors: true });
},

/**
* Converts to a string and truncates for logging
* @param {String} data Data to log
* @param {Number} length Max length of output. Optional.
* @param {Boolean} multiLineObjects If get full object output
* @return {String}
*/
getLogString(data, length, multiLineObjects = true) {
if (this.isObject(data) && multiLineObjects) {
data = this.getFullObjectString(data);
} else {
data = JSON.stringify(data);
}

if (data?.length > length) {
data = data.slice(0, length-1) + '…';
}

return data;
},

/**
* Returns current time in milliseconds since Unix Epoch
* The Unix epoch is 00:00:00 UTC on 1 January 1970 (an arbitrary date)
Expand All @@ -48,6 +70,12 @@ module.exports = {
return Math.floor((time - EPOCH) / 1000);
},

/**
* Pads the value with '0' until length of 2 digits
* 2 -> '02'
* @param {Number} num Value to pad
* @returns {string}
*/
padTo2Digits(num) {
return num.toString().padStart(2, '0');
},
Expand Down Expand Up @@ -775,8 +803,8 @@ module.exports = {
liquidity[key].totalCount = 0;
liquidity[key].amountTotal = 0;
liquidity[key].amountTotalQuote = 0;
liquidity[key].lowPrice = averagePrice * (1 - liquidity[key].spreadPercent/100/2);
liquidity[key].highPrice = averagePrice * (1 + liquidity[key].spreadPercent/100/2);
liquidity[key].lowPrice = averagePrice * (1 - liquidity[key].spreadPercent/100);
liquidity[key].highPrice = averagePrice * (1 + liquidity[key].spreadPercent/100);
liquidity[key].spread = averagePrice * liquidity[key].spreadPercent / 100;
// average price is the same for any spread
}
Expand Down Expand Up @@ -1119,66 +1147,76 @@ module.exports = {

/**
* Calculates clean (non-cheater) price for the order book
* It depends on:
* Distance^2 from smart price: bigger distance means higher probability of cheater order
* Amount of order (accumulated): smaller amount means higher probability of cheater order
* Koef threshold: bigger koef means higher probability of cheater order
* @param {Array of object} items Bids or asks, received using traderapi.getOrderBook()
* @param {String} type Items are 'asks' or 'bids'?
* @param {Array of object} liquidity Liquidity info, calculated in getOrderBookInfo()
* @param {String} type Items are 'asks' or 'bids'? Asks arranged from low to high, Bids from high to low (spread in the center).
* @param {Array of object} liquidity Liquidity info, calculated in getOrderBookInfo(). Using percent50 liquidity for total.
* @param {Number} smartPrice Smart price for the order book
* @param {Number} koef How to understand we achieve clean price. The more koef, the farther smart price from spread
* @return {Number} Clean price. It depends on distance from smart price
* @param {Number} koef How to understand we achieve clean price
* @return {Number} Clean price
*/
getCleanPrice(items, type, liquidity, smartPrice, koef = 0.02) {
getCleanPrice(items, type, liquidity, smartPrice, koef = 3) {

try {

let cleanPrice;

let a = 0; let t = 0; let c = 0; let c_t = 0;
let d = 0; let d2 = 0; let ct_d2 = 0;
const enough_ct_d2 = koef;
const table = [];

// Each iteration el.price moves towards to Smart price

for (let i = 0; i < items.length; i++) {

const el = items[i];

if (type === 'asks') {
if (items[i].price > smartPrice) break;
if (el.price > smartPrice) break;
a = el.amount;
t = liquidity['percent50'].amountAsks;
} else {
if (items[i].price < smartPrice) break;
if (el.price < smartPrice) break;
a = el.amount * el.price;
t = liquidity['percent50'].amountBidsQuote;
}

d = this.numbersDifferencePercent(el.price, smartPrice) / 100;
d2 = d * d;
d2 = d * d; // Decreases every iteration. For order with smartPrice (last iteration) it equals 0.
c += a;
c_t = c / t;
ct_d2 = c_t / d2;
c_t = c / t; // Grows each iteration
ct_d2 = c_t / d2; // Grows each iteration. For order with smartPrice (last iteration) it equals Infinity.

if (!cleanPrice && (ct_d2 > enough_ct_d2 || ct_d2 === Infinity)) {
cleanPrice = el.price;
if (ct_d2 < koef && items[i + 1]) { // While ct_d2 is less than Koef, consider an order as a cheater price
cleanPrice = items[i + 1].price;
}

// This table is only for logging
if (!cleanPrice) {
table.push({
items: items.length,
total: +t.toFixed(2),
price: el.price.toFixed(8),
d: +d.toFixed(2),
d2: +d2.toFixed(4),
a: a.toFixed(8),
c: +c.toFixed(8),
c_t: +c_t.toFixed(5),
ct_d2: +ct_d2.toFixed(5),
});
}
table.push({
items: items.length,
total: +t.toFixed(2),
price: el.price.toFixed(8),
d: +d.toFixed(2),
d2: +d2.toFixed(4),
a: a.toFixed(8),
c: +c.toFixed(8),
c_t: +c_t.toFixed(5),
ct_d2: +ct_d2.toFixed(5),
isCheater: ct_d2 < koef,
});

} // For i

if (!cleanPrice) { // Set Clean price as the best bid/ask
cleanPrice = items?.[0].price;
}

// See this table to understand the magic
// console.table(table);
// console.log(`cleanPrice for ${type} and ${smartPrice.toFixed(8)} smart price is ${cleanPrice.toFixed(8)}\n`);
// console.log(`Clean price is ${cleanPrice.toFixed(8)} for ${type} when Smart price = ${smartPrice.toFixed(8)} and Koef = ${koef}.\n`);

return cleanPrice;

Expand Down Expand Up @@ -1282,8 +1320,8 @@ module.exports = {

// Second, check mm_liquiditySpreadPercentMin: 'depth' orders should be not close to mid of spread
if (order.subPurpose !== 'ss' && tradeParams.mm_liquiditySpreadPercentMin) {
const innerLowPrice = orderBookInfo.averagePrice * (1 - tradeParams.mm_liquiditySpreadPercentMin/100/2) + roughness;
const innerHighPrice = orderBookInfo.averagePrice * (1 + tradeParams.mm_liquiditySpreadPercentMin/100/2) - roughness;
const innerLowPrice = orderBookInfo.averagePrice * (1 - tradeParams.mm_liquiditySpreadPercentMin/100) + roughness;
const innerHighPrice = orderBookInfo.averagePrice * (1 + tradeParams.mm_liquiditySpreadPercentMin/100) - roughness;
if (order.price > innerLowPrice && order.price < innerHighPrice) {
return true;
}
Expand Down
37 changes: 18 additions & 19 deletions modules/DB.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,28 @@ const config = require('./configReader');

const collections = {};

mongoClient.connect((error, client) => {
mongoClient.connect()
.then((client) => {
const db = client.db('tradebotdb');

if (error) {
log.error(`Unable to connect to MongoDB, ` + error);
process.exit(-1);
}
const db = client.db('tradebotdb');
collections.db = db;

collections.db = db;
const incomingTxsCollection = db.collection('incomingtxs');
incomingTxsCollection.createIndex([['date', 1], ['senderId', 1]]);

const incomingTxsCollection = db.collection('incomingtxs');
incomingTxsCollection.createIndex([['date', 1], ['senderId', 1]]);
const ordersCollection = db.collection('orders');
ordersCollection.createIndex([['isProcessed', 1], ['purpose', 1]]);
ordersCollection.createIndex([['pair', 1], ['exchange', 1]]);

const ordersCollection = db.collection('orders');
ordersCollection.createIndex([['isProcessed', 1], ['purpose', 1]]);
ordersCollection.createIndex([['pair', 1], ['exchange', 1]]);
collections.ordersDb = model(ordersCollection);
collections.incomingTxsDb = model(incomingTxsCollection);
collections.systemDb = model(db.collection('systems'));

collections.ordersDb = model(ordersCollection);
collections.incomingTxsDb = model(incomingTxsCollection);
collections.systemDb = model(db.collection('systems'));

log.log(`${config.notifyName} successfully connected to 'tradebotdb' MongoDB.`);

});
log.log(`${config.notifyName} successfully connected to 'tradebotdb' MongoDB.`);
})
.catch((error) => {
log.error(`Unable to connect to MongoDB, ` + error);
process.exit(-1);
});

module.exports = collections;
Loading

0 comments on commit 1d192eb

Please sign in to comment.