├── config ├── .gitignore └── default.json ├── .babelrc ├── test.sh ├── .gitignore ├── .eslintignore ├── .eslintrc.json ├── package.json ├── cmd.js ├── utils.js ├── README.md ├── commands ├── price.js ├── lend.js ├── wall.js ├── lendBot.js └── bot.js └── services └── price.js /config/.gitignore: -------------------------------------------------------------------------------- 1 | local.json 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | 3 | WORK_DIR=`dirname $0` 4 | cd $WORK_DIR && npm start 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .eslintcache 3 | testnet.json 4 | node_modules/ 5 | *.sw* 6 | *.log 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | tmp/** 2 | build/** 3 | node_modules/** 4 | contracts/** 5 | migrations/1_initial_migration.js 6 | migrations/2_deploy_contracts.js 7 | test/metacoin.js 8 | coverage 9 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 2017, 4 | "sourceType": "module" 5 | }, 6 | "extends": [ ], 7 | "plugins": [ ], 8 | "rules": { 9 | "key-spacing" : 0, 10 | "jsx-quotes" : [2, "prefer-single"], 11 | "max-len" : [2, 150, 2], 12 | "object-curly-spacing" : [2, "always"], 13 | "indent": ["warn", 4] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tomox-market-maker", 3 | "version": "0.2.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node cmd.js bot BTC-TOMO", 7 | "lend": "node cmd.js lend USD-60", 8 | "lint": "eslint --cache ./ --ext .js" 9 | }, 10 | "dependencies": { 11 | "axios": "^0.18.1", 12 | "bignumber.js": "^9.0.0", 13 | "commander": "^3.0.2", 14 | "config": "^3.2.3", 15 | "tomojs": "^1.1.3", 16 | "tomoxjs": "^0.5.1" 17 | }, 18 | "devDependencies": { 19 | "eslint": "^6.5.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /cmd.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const commander = require('commander') 4 | const bot = require('./commands/bot') 5 | const lend = require('./commands/lend') 6 | const lendBot = require('./commands/lendBot') 7 | const price = require('./commands/price') 8 | const wall = require('./commands/wall') 9 | 10 | commander 11 | .version('1.0.0') 12 | .description('TomoChain Market Marker') 13 | 14 | commander 15 | .command('bot ') 16 | .action(async (pair) => { 17 | await bot.run(pair) 18 | }) 19 | 20 | commander 21 | .command('lend ') 22 | .action(async (pair) => { 23 | await lend.run(pair) 24 | }) 25 | 26 | commander 27 | .command('lendBot ') 28 | .action(async (pair) => { 29 | await lendBot.run(pair) 30 | }) 31 | 32 | commander 33 | .command('price') 34 | .action(async () => { 35 | await price.run() 36 | }) 37 | 38 | commander 39 | .command('wall ') 40 | .action(async (pair) => { 41 | await wall.run(pair) 42 | }) 43 | 44 | commander.parse(process.argv) 45 | -------------------------------------------------------------------------------- /config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "TOMO-BTC": { 3 | "pkey": "0x", 4 | "baseToken": "0x0000000000000000000000000000000000000001", 5 | "quoteToken": "0xBD8b2Fb871F97b2d5F0A1af3bF73619b09174B2A", 6 | "orderbookLength": 10, 7 | "speed": 5000 8 | }, 9 | "USD-60": { 10 | "pkey": "0x", 11 | "term": 60, 12 | "lendingToken": "0x260eD86C808f0de23721Abf47039C8dA53C0be2f", 13 | "collateralToken": "0x9839B789b09292c175Cc7173337520fE8fd9d8FE", 14 | "speed": 5000 15 | }, 16 | "priceFeeder": { 17 | "pkey": "0xxx", 18 | "lendingToken": "0x260eD86C808f0de23721Abf47039C8dA53C0be2f", 19 | "collateralToken": "0x716EE8E4e1A64b5E52fC3a2889c52cbA8F58523F", 20 | "rpc": "https://rpc.devnet.tomochain.com", 21 | "speed": 1800000 22 | }, 23 | "wallPKey": "0xxx", 24 | "relayerUrl": "http://localhost:3001", 25 | "orderbookLength": 5, 26 | "speed": 50000, 27 | "volume": 10, 28 | "lendingVolume": 500, 29 | "step": 0.01, 30 | "lendStep": 0.1 31 | } 32 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | 2 | calcPrecision = (price) => { 3 | const totalPrecision = 8 4 | let pricePrecision = 4 5 | let amountPrecision = totalPrecision - pricePrecision 6 | if (!price) return { pricePrecision: totalPrecision, amountPrecision: totalPrecision } 7 | switch (true) { 8 | case (price >= 50): 9 | pricePrecision = 2 10 | amountPrecision = totalPrecision - pricePrecision 11 | break 12 | case (price >= 1): 13 | pricePrecision = 4 14 | amountPrecision = totalPrecision - pricePrecision 15 | break 16 | case (price >= 0.1): 17 | pricePrecision = 5 18 | amountPrecision = totalPrecision - pricePrecision 19 | break 20 | case (price >= 0.001): 21 | pricePrecision = 6 22 | amountPrecision = totalPrecision - pricePrecision 23 | break 24 | default: 25 | pricePrecision = 8 26 | amountPrecision = totalPrecision - pricePrecision 27 | break 28 | } 29 | return { pricePrecision, amountPrecision } 30 | } 31 | 32 | module.exports = { calcPrecision } 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tomox-market-maker 2 | _Simple market maker to create liquidity/volume for TomoX_ 3 | 4 | **NOTE: we only use this script in the development environment, use it with your own risk** 5 | 6 | 7 | ## Install 8 | ``` 9 | npm install 10 | ``` 11 | Create `config/local.json` file 12 | ``` 13 | cp config/default.json config/local.json 14 | ``` 15 | Update `local.json` file with your parameters 16 | 17 | There are some parameters you need to update: 18 | - `ETH-TOMO`: Pair name 19 | - `pkey`: Private key of wallet that has enough balance for trades 20 | - `baseToken` and `quoteToken`: Addresses of pair 21 | - `relayerUrl`: The url of DEX, e.g `https://dex.testnet.tomochain.com` 22 | - `orderbookLength`: Number of BUY/SELL orders in Orderbook 23 | - `speed`: `[miliseconds]` Speed of the creating orders of the bot. Recommend > 10 seconds 24 | - `step`: The price step of the orders in orderbook ([L217](https://github.com/tomochain/tomox-market-maker/blob/6b8da681874b97fb24c86135851809b2928a25fc/commands/bot.js#L217)) 25 | - `volume`: Side of an orders (in USD) 26 | 27 | You can get pairs information via DEX API, e.g: DEX testnet `https://dex.testnet.tomochain.com/api/pairs/data` 28 | 29 | ## Usage 30 | 31 | The bot supports any pairs with proper config 32 | 33 | ``` 34 | node cmd.js bot BTC-TOMO 35 | ``` 36 | 37 | -------------------------------------------------------------------------------- /commands/price.js: -------------------------------------------------------------------------------- 1 | const TomoJS = require('tomojs') 2 | const BigNumber = require('bignumber.js') 3 | const config = require('config') 4 | 5 | let sleep = (time) => new Promise((resolve) => setTimeout(resolve, time)) 6 | 7 | const run = async (p) => { 8 | let feeder = config.priceFeeder 9 | let tomo = await TomoJS.setProvider(feeder.rpc, feeder.pkey) 10 | let speed = feeder.speed || 50000 11 | 12 | while(true) { 13 | let price 14 | let p = await tomo.tomox.getCurrentEpochPrice(feeder.collateralToken, feeder.lendingToken) 15 | let lendingToken = (await tomo.tomoz.getTokenInformation(feeder.lendingToken)) 16 | let lendingDecimals = lendingToken.decimals 17 | let collateralToken = (await tomo.tomoz.getTokenInformation(feeder.collateralToken)) 18 | let collateralDecimals = collateralToken.decimals 19 | 20 | if (!p) { 21 | p = await tomo.tomox.getCurrentEpochPrice(feeder.lendingToken, feeder.collateralToken) 22 | price = new BigNumber(10 ** lendingDecimals).multipliedBy(10** collateralDecimals).dividedBy(new BigNumber(p)) 23 | } else { 24 | price = new BigNumber(p) 25 | } 26 | 27 | let b = Math.random() >= 0.5 28 | 29 | price = b ? price.multipliedBy(2) : price.dividedBy(2) 30 | 31 | price = price.toFixed(0).toString(10) 32 | await tomo.tomox.setCollateralPrice({ 33 | token: feeder.collateralToken, 34 | lendingToken: feeder.lendingToken, 35 | price: price 36 | }) 37 | 38 | 39 | let blockNumber = await tomo.tomo.getBlockNumber() 40 | let epoch = Math.floor(blockNumber / 900) + 1 41 | console.log(`${collateralToken.symbol}/${lendingToken.symbol} price ${price} block ${blockNumber} epoch ${epoch}`) 42 | 43 | await sleep(speed) 44 | } 45 | } 46 | 47 | module.exports = { run } 48 | -------------------------------------------------------------------------------- /commands/lend.js: -------------------------------------------------------------------------------- 1 | const TomoX = require('tomoxjs') 2 | const BigNumber = require('bignumber.js') 3 | const config = require('config') 4 | const { getUSDPrice } = require('../services/price') 5 | 6 | let lendingToken = '' 7 | let term = '' 8 | let pair = 'USD-60' 9 | let defaultAmount = 1 10 | let FIXA = 2 // amount decimals 11 | let tomox = new TomoX() 12 | 13 | let sleep = (time) => new Promise((resolve) => setTimeout(resolve, time)) 14 | 15 | const createOrder = async (side, interest, quantity) => { 16 | let o = await tomox.createLending({ 17 | collateralToken: config[pair].collateralToken, 18 | lendingToken: config[pair].lendingToken, 19 | quantity: String(quantity), 20 | interest: String(interest), 21 | term: String(config[pair].term), 22 | side: side 23 | }) 24 | 25 | console.log(`${side} pair=${pair} rate=${interest} amount=${quantity} hash=${o.hash} nonce=${o.nonce}`) 26 | return o 27 | } 28 | 29 | const runMarketMaker = async () => { 30 | try { 31 | const orderBookData = await tomox.getLendingOrderBook({ 32 | term: config[pair].term, lendingToken: config[pair].lendingToken 33 | }) 34 | let tomoPrice = parseFloat(await getUSDPrice('TOMO-USD')) 35 | let side = (Math.floor(Math.random() * 10) % 2 === 0) ? 'BORROW' : 'INVEST' 36 | let interest = ((10 * tomoPrice) + 6.00).toFixed(2) 37 | let quantity = (defaultAmount * (1 + Math.random())).toFixed(FIXA) 38 | 39 | let o = await createOrder(side, interest, quantity) 40 | 41 | } catch (err) { 42 | console.log(err) 43 | } 44 | } 45 | 46 | const run = async (p) => { 47 | let usdPrice = parseFloat(await getUSDPrice(p)) 48 | defaultAmount = parseFloat(new BigNumber(config.lendingVolume).dividedBy(usdPrice).toFixed(FIXA)) 49 | tomox = new TomoX(config.get('relayerUrl'), '', config[p].pkey) 50 | pair = p || 'USD-60' 51 | 52 | let speed = config[pair].speed || config.speed || 50000 53 | while(true) { 54 | await runMarketMaker() 55 | await sleep(speed) 56 | } 57 | } 58 | 59 | module.exports = { run } 60 | -------------------------------------------------------------------------------- /services/price.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | const config = require('config') 3 | 4 | const gPrice = {} 5 | const gUSDPrice = {} 6 | 7 | const httpClient = axios.create() 8 | httpClient.defaults.timeout = 2500 9 | 10 | const getLatestPrice = async (p = false) => { 11 | try { 12 | if (p && (config[p] || {}).price) { 13 | return config[p].price 14 | } 15 | let arr = p.split('-') 16 | let baseSymbol = arr[0].toLowerCase() 17 | let quoteSymbol = arr[1].toLowerCase() 18 | if (quoteSymbol === 'usdt') { 19 | quoteSymbol = 'usd' 20 | } 21 | 22 | if ((quoteSymbol === 'tomo') || (quoteSymbol === 'eth')) { 23 | let response = await httpClient.get(`https://www.binance.com/api/v3/ticker/price?symbol=${quoteSymbol.toUpperCase()}BTC`) 24 | let tomoPrice = response.data.price 25 | 26 | if (baseSymbol === 'btc') { 27 | gPrice[p] = 1/tomoPrice 28 | } else { 29 | response = await httpClient.get(`https://www.binance.com/api/v3/ticker/price?symbol=${baseSymbol.toUpperCase()}BTC`) 30 | let tokenPrice = response.data.price 31 | 32 | gPrice[p] = (1/tomoPrice) * tokenPrice 33 | } 34 | return gPrice[p] 35 | } 36 | 37 | if ( quoteSymbol === 'usd' ) { 38 | const response = await httpClient.get(`https://www.binance.com/api/v3/ticker/price?symbol=${baseSymbol.toUpperCase()}USDT`) 39 | gPrice[p] = response.data.price 40 | 41 | } else { 42 | const response = await httpClient.get( 43 | `https://www.binance.com/api/v3/ticker/price?symbol=${baseSymbol.toUpperCase()}${quoteSymbol.toUpperCase()}` 44 | ) 45 | gPrice[p] = response.data.price 46 | } 47 | } catch (err) { 48 | console.log(err) 49 | } 50 | return gPrice[p] 51 | } 52 | 53 | const getUSDPrice = async (p = false) => { 54 | let baseSymbol = 'TOMO' 55 | try { 56 | if (p && (config[p] || {}).price) { 57 | return config[p].price 58 | } 59 | 60 | let arr = p.split('-') 61 | baseSymbol = arr[0].toUpperCase() 62 | 63 | if (baseSymbol != 'USDT' && baseSymbol != 'USD') { 64 | response = await httpClient.get(`https://www.binance.com/api/v3/ticker/price?symbol=${baseSymbol}USDT`) 65 | let tokenPrice = response.data.price 66 | 67 | gUSDPrice[baseSymbol] = tokenPrice 68 | } else { 69 | gUSDPrice[baseSymbol] = 1 70 | } 71 | } catch (err) { 72 | console.log(err) 73 | } 74 | return gUSDPrice[baseSymbol] 75 | } 76 | 77 | module.exports = { getLatestPrice, getUSDPrice } 78 | -------------------------------------------------------------------------------- /commands/wall.js: -------------------------------------------------------------------------------- 1 | const { getLatestPrice, getUSDPrice } = require('../services/price') 2 | const TomoX = require('tomoxjs') 3 | const BigNumber = require('bignumber.js') 4 | const config = require('config') 5 | const { calcPrecision } = require('../utils') 6 | 7 | let FIXA = 5 // amount decimals 8 | let FIXP = 7 // price decimals 9 | let tomox = new TomoX() 10 | let pair = 'TOMO-BTC' 11 | let baseToken = config.get(`${pair}.baseToken`) 12 | let quoteToken = config.get(`${pair}.quoteToken`) 13 | let EX_DECIMALS = 1e8 14 | 15 | let sleep = (time) => new Promise((resolve) => setTimeout(resolve, time)) 16 | 17 | const createOrder = async (price, amount, side) => { 18 | let prec = calcPrecision(price) 19 | price = new BigNumber(price).toFixed(prec.pricePrecision) 20 | amount = new BigNumber(amount).toFixed(prec.amountPrecision) 21 | FIXP = prec.pricePrecision 22 | FIXA = prec.amountPrecision 23 | let o = await tomox.createOrder({ 24 | baseToken: baseToken, 25 | quoteToken: quoteToken, 26 | price: price, 27 | amount: amount, 28 | side: side 29 | }) 30 | console.log(`${side} pair=${pair} price=${price} amount=${amount} hash=${o.hash} nonce=${o.nonce}`) 31 | return o 32 | } 33 | 34 | 35 | const run = async (p) => { 36 | tomox = new TomoX(config.get('relayerUrl'), '', config.wallPKey) 37 | pair = p || 'BTC-TOMO' 38 | baseToken = config[p].baseToken 39 | quoteToken = config[p].quoteToken 40 | let speed = config[pair].speed || config.speed || 50000 41 | let ORDERBOOK_LENGTH = config[p].orderbookLength || config.get('orderbookLength') || 5 42 | 43 | let hash 44 | let first = true 45 | while(true) { 46 | 47 | if (hash) { 48 | console.log('CANCEL', `orderHash=${hash}`) 49 | await tomox.cancelOrder(hash) 50 | hash = null 51 | await sleep(5000) 52 | } 53 | 54 | let remotePrice = parseFloat(await getLatestPrice(pair)) 55 | let price = new BigNumber(remotePrice).multipliedBy(EX_DECIMALS) 56 | let usdPrice = parseFloat(await getUSDPrice(pair)) 57 | 58 | let prec = calcPrecision(remotePrice) 59 | FIXP = prec.pricePrecision 60 | FIXA = prec.amountPrecision 61 | 62 | let amount = parseFloat(new BigNumber(config.volume) 63 | .dividedBy(usdPrice).multipliedBy(ORDERBOOK_LENGTH * 60 * 60 * 1000 / speed).toFixed(FIXA)) 64 | let ran = Math.floor(Math.random() * 3) 65 | 66 | if (first === true || ran !== 0) { 67 | first = false 68 | let b = (Math.random() >= 0.5) 69 | let side = b ? 'BUY' : 'SELL' 70 | 71 | price = b ? price.multipliedBy(2) : price.dividedBy(2) 72 | price = price.dividedBy(EX_DECIMALS).toString(10) 73 | 74 | let o = await createOrder(price, amount, side) 75 | hash = o.hash 76 | } else { 77 | console.log('SLEEP 1 hour') 78 | } 79 | await sleep(60 * 60 * 1000) 80 | } 81 | } 82 | 83 | module.exports = { run } 84 | -------------------------------------------------------------------------------- /commands/lendBot.js: -------------------------------------------------------------------------------- 1 | const TomoX = require('tomoxjs') 2 | const BigNumber = require('bignumber.js') 3 | const config = require('config') 4 | const { getUSDPrice } = require('../services/price') 5 | 6 | let lendingToken = '' 7 | let term = '' 8 | let pair = 'USD-60' 9 | let defaultAmount = 1 10 | let FIXA = 2 // amount decimals 11 | let FIXI = 2 // interest decimals 12 | let tomox = new TomoX() 13 | let lendOrderBookLength = 5 14 | let defaultInterest = 7 15 | let defaultStep = 0.01 16 | 17 | let sleep = (time) => new Promise((resolve) => setTimeout(resolve, time)) 18 | 19 | const createOrder = async (side, interest, quantity) => { 20 | let o = await tomox.createLending({ 21 | collateralToken: config[pair].collateralToken, 22 | lendingToken: config[pair].lendingToken, 23 | quantity: String(quantity), 24 | interest: String(interest), 25 | term: String(config[pair].term), 26 | side: side 27 | }) 28 | 29 | console.log(`${side} pair=${pair} rate=${interest} amount=${quantity} hash=${o.hash} nonce=${o.nonce}`) 30 | return o 31 | } 32 | 33 | const runMarketMaker = async () => { 34 | try { 35 | const orderBookData = await tomox.getLendingOrderBook({ 36 | term: config[pair].term, lendingToken: config[pair].lendingToken 37 | }) 38 | if (orderBookData.borrow.length < lendOrderBookLength) { 39 | let length = orderBookData.borrow.length 40 | let side = 'BORROW' 41 | let interest = (defaultInterest - (defaultStep * (length + 1) * (1 + Math.random()))).toFixed(FIXI) 42 | let quantity = (defaultAmount * (1 + Math.random())).toFixed(FIXA) 43 | 44 | let o = await createOrder(side, interest, quantity) 45 | return o 46 | } 47 | 48 | if (orderBookData.lend.length < lendOrderBookLength) { 49 | let length = orderBookData.lend.length 50 | let side = 'INVEST' 51 | let interest = (defaultInterest + (defaultStep * (length + 1) * (1 + Math.random()))).toFixed(FIXI) 52 | let quantity = (defaultAmount * (1 + Math.random())).toFixed(FIXA) 53 | 54 | let o = await createOrder(side, interest, quantity) 55 | return o 56 | } 57 | 58 | 59 | let side = (Math.floor(Math.random() * 10) % 2 === 0) ? 'BORROW' : 'INVEST' 60 | if (orderBookData.borrow[0].interest === (new BigNumber(defaultInterest)).multipliedBy(1e8).toString(10)) { 61 | side = 'INVEST' 62 | } 63 | 64 | if (orderBookData.lend[0].interest === (new BigNumber(defaultInterest)).multipliedBy(1e8).toString(10)) { 65 | side = 'BORROW' 66 | } 67 | 68 | let interest = defaultInterest 69 | let quantity = (defaultAmount * (1 + Math.random())).toFixed(FIXA) 70 | 71 | let o = await createOrder(side, interest, quantity) 72 | return o 73 | 74 | } catch (err) { 75 | console.log(err) 76 | } 77 | } 78 | 79 | const run = async (p) => { 80 | let usdPrice = parseFloat(await getUSDPrice(p)) 81 | let lendingVolume = config[pair].lendingVolume || config.lendingVolume 82 | defaultAmount = parseFloat(new BigNumber(lendingVolume).dividedBy(usdPrice).toFixed(FIXA)) 83 | tomox = new TomoX(config.get('relayerUrl'), '', config[p].pkey) 84 | pair = p || 'USD-60' 85 | 86 | let speed = config[pair].speed || config.speed || 50000 87 | lendOrderBookLength = config[pair].lendOrderBookLength || config.lendOrderBookLength || lendOrderBookLength 88 | defaultInterest = config[pair].interest || config.interest || defaultInterest 89 | defaultStep = config[pair].lendStep || config.lendStep || defaultInterest 90 | 91 | while(true) { 92 | await runMarketMaker() 93 | await sleep(speed) 94 | } 95 | } 96 | 97 | module.exports = { run } 98 | -------------------------------------------------------------------------------- /commands/bot.js: -------------------------------------------------------------------------------- 1 | const { getLatestPrice, getUSDPrice } = require('../services/price') 2 | const TomoX = require('tomoxjs') 3 | const BigNumber = require('bignumber.js') 4 | const config = require('config') 5 | const { calcPrecision } = require('../utils') 6 | 7 | let defaultAmount = 1 // TOMO 8 | let minimumPriceStepChange = 1 // TOMO 9 | let FIXA = 5 // amount decimals 10 | let FIXP = 7 // price decimals 11 | let ORDERBOOK_LENGTH = config.get('orderbookLength') // number of order in orderbook 12 | let tomox = new TomoX() 13 | let pair = 'TOMO-BTC' 14 | let baseToken = config.get(`${pair}.baseToken`) 15 | let quoteToken = config.get(`${pair}.quoteToken`) 16 | let TOKEN_DECIMALS = 1e18 17 | let BASE_TOKEN_DECIMALS = 1e18 18 | let EX_DECIMALS = 1e8 19 | 20 | let sleep = (time) => new Promise((resolve) => setTimeout(resolve, time)) 21 | let sellPrices = [] 22 | let buyPrices = [] 23 | let isFirstOrder = true 24 | 25 | const createOrder = async (price, amount, side) => { 26 | let prec = calcPrecision(price) 27 | price = new BigNumber(price).toFixed(prec.pricePrecision) 28 | amount = new BigNumber(amount).toFixed(prec.amountPrecision) 29 | FIXP = prec.pricePrecision 30 | FIXA = prec.amountPrecision 31 | let o = await tomox.createOrder({ 32 | baseToken: baseToken, 33 | quoteToken: quoteToken, 34 | price: price, 35 | amount: amount, 36 | side: side 37 | }) 38 | console.log(`${side} pair=${pair} price=${price} amount=${amount} hash=${o.hash} nonce=${o.nonce}`) 39 | return o 40 | } 41 | 42 | const runMarketMaker = async () => { 43 | try { 44 | const orderBookData = await tomox.getOrderBook({ baseToken, quoteToken }) 45 | if (!orderBookData) { 46 | return 47 | } 48 | 49 | if (orderBookData.asks.length >= ORDERBOOK_LENGTH 50 | && orderBookData.bids.length >= ORDERBOOK_LENGTH) { 51 | console.log('MATCHED ORDER !!!') 52 | await match(orderBookData) 53 | } 54 | 55 | sellPrices = [] 56 | buyPrices = [] 57 | orderBookData.asks.forEach(a => sellPrices.push(new BigNumber(a.pricepoint).dividedBy(TOKEN_DECIMALS).toFixed(FIXP))) 58 | orderBookData.bids.forEach(b => buyPrices.push(new BigNumber(b.pricepoint).dividedBy(TOKEN_DECIMALS).toFixed(FIXP))) 59 | 60 | let buy = await fillOrderbook(ORDERBOOK_LENGTH - orderBookData.bids.length, 'BUY', 0) 61 | let sell = await fillOrderbook(ORDERBOOK_LENGTH - orderBookData.asks.length, 'SELL', (buy || {}).nonce) 62 | 63 | await cancelOrders((sell || {}).nonce) 64 | 65 | } catch (err) { 66 | console.log(err) 67 | } 68 | } 69 | 70 | const findGoodPrice = (side, latestPrice) => { 71 | let i = 1 72 | while (true) { 73 | let step = minimumPriceStepChange.multipliedBy(i) 74 | let price = (side === 'BUY') ? latestPrice.minus(step) 75 | : latestPrice.plus(step) 76 | let pricepoint = price.dividedBy(EX_DECIMALS).toFixed(FIXP) 77 | 78 | if (side === 'BUY' && buyPrices.indexOf(pricepoint) < 0) { 79 | buyPrices.push(pricepoint) 80 | return price 81 | } else if (side !== 'BUY' && sellPrices.indexOf(pricepoint) < 0) { 82 | sellPrices.push(pricepoint) 83 | return price 84 | } else { 85 | i = i + 1 86 | } 87 | } 88 | } 89 | 90 | const cancelOrders = async (nonce) => { 91 | let orders = (await tomox.getOrders({ baseToken, quoteToken, status: 'OPEN' })).orders 92 | let latestPrice = new BigNumber(await getLatestPrice(pair)).multipliedBy(TOKEN_DECIMALS) 93 | let mmp = minimumPriceStepChange.dividedBy(EX_DECIMALS).multipliedBy(TOKEN_DECIMALS) 94 | let cancelHashes = orders.filter(order => { 95 | if (order.status !== 'OPEN') return false 96 | let price = new BigNumber(order.pricepoint) 97 | if (order.side === 'SELL' && price.isGreaterThan(latestPrice.plus(mmp.multipliedBy(ORDERBOOK_LENGTH)))) { 98 | return true 99 | } 100 | if (order.side === 'BUY' && price.isLessThan(latestPrice.minus(mmp.multipliedBy(ORDERBOOK_LENGTH)))) { 101 | return true 102 | } 103 | return false 104 | }) 105 | let hashes = cancelHashes.map(c => c.hash) 106 | let ret = await tomox.cancelManyOrders(hashes, nonce || 0) 107 | ret.forEach(o => { 108 | console.log('CANCEL', `orderHash=${o.orderHash} orderId=${o.orderID} hash=${o.hash} nonce=${o.nonce}`) 109 | }) 110 | } 111 | 112 | const fillOrderbook = async (len, side, nonce = 0) => { 113 | let hash = 0 114 | if (len <= 0) return { nonce, hash } 115 | 116 | try { 117 | latestPrice = new BigNumber(await getLatestPrice(pair)).multipliedBy(EX_DECIMALS) 118 | let amount = defaultAmount 119 | let orders = [] 120 | for (let i = 0; i < len; i++) { 121 | let price = findGoodPrice(side, latestPrice) 122 | let ranNum = Math.floor(Math.random() * 20) / 100 + 1 123 | 124 | let o = { 125 | baseToken: baseToken, 126 | quoteToken: quoteToken, 127 | price: price.dividedBy(EX_DECIMALS).toFixed(FIXP), 128 | amount: (amount * ranNum).toFixed(FIXA), 129 | side: side, 130 | } 131 | if (nonce != 0) { 132 | o.nonce = parseInt(nonce) + i 133 | } 134 | orders.push(o) 135 | } 136 | 137 | let ret = await tomox.createManyOrders(orders) 138 | orders.forEach((or, k) => { 139 | hash = ret[k].hash 140 | nonce = ret[k].nonce 141 | console.log(`${side} pair=${pair} price=${or.price} amount=${or.amount} hash=${ret[k].hash} nonce=${ret[k].nonce}`) 142 | }) 143 | return { nonce: parseInt(nonce) + 1, hash: hash } 144 | } catch (err) { 145 | console.log(err) 146 | } 147 | } 148 | 149 | const cancel = async (hash, nonce) => { 150 | const oc = await tomox.cancelOrder(hash, nonce) 151 | console.log('CANCEL', pair, hash, nonce) 152 | } 153 | 154 | const match = async (orderBookData) => { 155 | try { 156 | let remotePrice = parseFloat(await getLatestPrice(pair)) 157 | let bestPrice = new BigNumber(new BigNumber(remotePrice).toFixed(FIXP)).multipliedBy(TOKEN_DECIMALS) 158 | let price = new BigNumber(0) 159 | let amount = new BigNumber(0) 160 | let side = 'BUY' 161 | 162 | orderBookData.asks.forEach(ask => { 163 | let p = new BigNumber(ask.pricepoint) 164 | let a = new BigNumber(ask.amount) 165 | if (p.isLessThanOrEqualTo(bestPrice) && 166 | a.dividedBy(BASE_TOKEN_DECIMALS).multipliedBy(10 ** FIXA).isGreaterThan(new BigNumber(1)) 167 | ) { 168 | side = 'BUY' 169 | price = p 170 | amount = amount.plus(a) 171 | } 172 | }) 173 | 174 | orderBookData.bids.forEach(bid => { 175 | let p = new BigNumber(bid.pricepoint) 176 | let a = new BigNumber(bid.amount) 177 | if (p.isGreaterThanOrEqualTo(bestPrice) && 178 | a.dividedBy(BASE_TOKEN_DECIMALS).multipliedBy(10 ** FIXA).isGreaterThan(new BigNumber(1)) 179 | ) { 180 | side = 'SELL' 181 | price = p 182 | amount = amount.plus(a) 183 | } 184 | }) 185 | 186 | let ROUNDING_MODE = (side === 'SELL') ? 1 : 0 187 | 188 | if (amount.isEqualTo(0)) { 189 | price = bestPrice.dividedBy(TOKEN_DECIMALS).toFixed(FIXP) 190 | amount = defaultAmount.toFixed(FIXA) 191 | await createOrder(price, amount, side) 192 | } else { 193 | price = price.dividedBy(TOKEN_DECIMALS).toFixed(FIXP, ROUNDING_MODE) 194 | amount = amount.dividedBy(BASE_TOKEN_DECIMALS).toFixed(FIXA) 195 | 196 | await createOrder(price, amount, side) 197 | } 198 | 199 | 200 | } catch (err) { 201 | console.log(err) 202 | } 203 | } 204 | 205 | const run = async (p) => { 206 | tomox = new TomoX(config.get('relayerUrl'), '', config[p].pkey) 207 | pair = p || 'BTC-TOMO' 208 | ORDERBOOK_LENGTH = config[p].orderbookLength || config.get('orderbookLength') || 5 209 | baseToken = config[p].baseToken 210 | quoteToken = config[p].quoteToken 211 | defaultVolume = config[p].volume || config.volume 212 | 213 | let remotePrice = parseFloat(await getLatestPrice(pair)) 214 | let price = new BigNumber(remotePrice).multipliedBy(EX_DECIMALS) 215 | let usdPrice = parseFloat(await getUSDPrice(pair)) 216 | let step = config[p].step || config.step || 0.01 217 | minimumPriceStepChange = price.multipliedBy(step) 218 | 219 | let d = (await tomox.getTokenInfo(quoteToken)).decimals 220 | TOKEN_DECIMALS = 10 ** parseInt(d) 221 | d = (await tomox.getTokenInfo(baseToken)).decimals 222 | BASE_TOKEN_DECIMALS = 10 ** parseInt(d) 223 | 224 | let prec = calcPrecision(remotePrice) 225 | FIXP = prec.pricePrecision 226 | FIXA = prec.amountPrecision 227 | 228 | defaultAmount = parseFloat(new BigNumber(defaultVolume).dividedBy(usdPrice).toFixed(FIXA)) 229 | 230 | let speed = config[pair].speed || config.speed || 50000 231 | while(true) { 232 | await runMarketMaker() 233 | await sleep(speed) 234 | } 235 | } 236 | 237 | module.exports = { run } 238 | --------------------------------------------------------------------------------