diff --git a/README.md b/README.md index dcfa1afb..85ec984a 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ you restart bot, it will reset frontend adjusted value from frontend. docker-compose -f docker-compose.server.yml up -d ``` -[![asciicast](https://asciinema.org/a/371137.png)](https://asciinema.org/a/371137) + [![asciicast](https://asciinema.org/a/371137.png)](https://asciinema.org/a/371137) 4. Open browser `http://0.0.0.0:8080` to see frontend statistics @@ -122,3 +122,5 @@ React.js based frontend communicating via Web Socket - [x] Develop simple frontend to see statistics - [x] Fix the issue with configuration - [x] Update frontend to remove cache +- [x] Fix the issue with rounding when places an order +- [x] Fix the issue with persistent redis diff --git a/app/jobs/simpleStopChaser/__tests__/fixtures/binance-account-info.json b/app/jobs/simpleStopChaser/__tests__/fixtures/binance-account-info.json index 0d636e32..0b3c4351 100644 --- a/app/jobs/simpleStopChaser/__tests__/fixtures/binance-account-info.json +++ b/app/jobs/simpleStopChaser/__tests__/fixtures/binance-account-info.json @@ -272,7 +272,22 @@ { "asset": "BAL", "free": "0.00000000", "locked": "0.00000000" }, { "asset": "YFI", "free": "0.00000000", "locked": "0.00000000" }, { "asset": "SRM", "free": "0.00000000", "locked": "0.00000000" }, - { "asset": "ANT", "free": "0.00000000", "locked": "0.00000000" } + { "asset": "ANT", "free": "0.00000000", "locked": "0.00000000" }, + { + "asset": "LINKUP", + "free": "2.41000000", + "locked": "0.00000000" + }, + { + "asset": "XRPDOWN", + "free": "0.00393000", + "locked": "0.00000000" + }, + { + "asset": "DOTUP", + "free": "1.59840000", + "locked": "0.00000000" + } ], "permissions": ["SPOT"] } diff --git a/app/jobs/simpleStopChaser/__tests__/fixtures/helper-get-symbol-info3.json b/app/jobs/simpleStopChaser/__tests__/fixtures/helper-get-symbol-info3.json new file mode 100644 index 00000000..59a582aa --- /dev/null +++ b/app/jobs/simpleStopChaser/__tests__/fixtures/helper-get-symbol-info3.json @@ -0,0 +1,83 @@ +{ + "symbol": "DOTUPUSDT", + "status": "TRADING", + "baseAsset": "DOTUP", + "baseAssetPrecision": 8, + "quoteAsset": "USDT", + "quotePrecision": 8, + "quoteAssetPrecision": 8, + "baseCommissionPrecision": 8, + "quoteCommissionPrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": true, + "ocoAllowed": true, + "quoteOrderQtyMarketAllowed": true, + "isSpotTradingAllowed": false, + "isMarginTradingAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "6.66600000", + "maxPrice": "126.64500000", + "tickSize": "0.00100000" + }, + { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "920000.00000000", + "stepSize": "0.01000000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + }, + { "filterType": "ICEBERG_PARTS", "limit": 10 }, + { + "filterType": "MARKET_LOT_SIZE", + "minQty": "0.00000000", + "maxQty": "4561.06981250", + "stepSize": "0.00000000" + }, + { "filterType": "MAX_NUM_ORDERS", "maxNumOrders": 200 }, + { "filterType": "MAX_NUM_ALGO_ORDERS", "maxNumAlgoOrders": 5 } + ], + "permissions": ["LEVERAGED"], + "filterLotSize": { + "filterType": "LOT_SIZE", + "minQty": "0.01000000", + "maxQty": "920000.00000000", + "stepSize": "0.01000000" + }, + "filterPrice": { + "filterType": "PRICE_FILTER", + "minPrice": "6.66600000", + "maxPrice": "126.64500000", + "tickSize": "0.00100000" + }, + "filterPercent": { + "filterType": "PERCENT_PRICE", + "multiplierUp": "5", + "multiplierDown": "0.2", + "avgPriceMins": 5 + }, + "filterMinNotional": { + "filterType": "MIN_NOTIONAL", + "minNotional": "10.00000000", + "applyToMarket": true, + "avgPriceMins": 5 + } +} diff --git a/app/jobs/simpleStopChaser/__tests__/helper.test.js b/app/jobs/simpleStopChaser/__tests__/helper.test.js index a622efba..b24ae2e2 100644 --- a/app/jobs/simpleStopChaser/__tests__/helper.test.js +++ b/app/jobs/simpleStopChaser/__tests__/helper.test.js @@ -415,6 +415,7 @@ describe('helper', () => { describe('getSellBalance', () => { const orgSymbolInfo = require('./fixtures/helper-get-symbol-info1.json'); + const orgSymbolInfoDOTUSDT = require('./fixtures/helper-get-symbol-info3.json'); const orgAccountInfo = require('./fixtures/binance-account-info.json'); const orgIndicators = require('./fixtures/helper-indicators.json'); @@ -716,6 +717,44 @@ describe('helper', () => { }); }); }); + + describe('when has enough quantity to sell -no round', () => { + beforeEach(async () => { + const accountInfo = _.cloneDeep(orgAccountInfo); + accountInfo.balances = _.map(accountInfo.balances, b => { + const balance = b; + if (balance.asset === 'DOTUP') { + balance.free = '1.5984'; + balance.locked = '0.0000'; + } + return balance; + }); + binance.client.accountInfo = jest.fn().mockResolvedValue(accountInfo); + + const symbolInfo = _.cloneDeep(orgSymbolInfoDOTUSDT); + const indicators = _.cloneDeep(orgIndicators); + + result = await simpleStopChaserHelper.getSellBalance( + logger, + symbolInfo, + indicators, + stopLossLimitConfig + ); + }); + + it('does not trigger cache.hdel', () => { + expect(cache.hdel).not.toHaveBeenCalled(); + }); + + it('returns expected value', () => { + expect(result).toStrictEqual({ + result: true, + message: 'Balance found', + freeBalance: 1.59, + lockedBalance: 0.0 + }); + }); + }); }); }); @@ -779,7 +818,7 @@ describe('helper', () => { result: true, message: 'Calculated order quantity to buy.', baseAssetPrice: 11756.29, - orderQuantity: 0.00085, + orderQuantity: 0.000849, freeBalance: 10 }); }); @@ -892,7 +931,7 @@ describe('helper', () => { result: false, message: 'Order quantity is less or equal than minimum quantity - 0.00000100. Do not place an order.', - quantity: 0.000001 + quantity: 0 }); }); }); diff --git a/app/jobs/simpleStopChaser/helper.js b/app/jobs/simpleStopChaser/helper.js index efd03f86..24c11598 100644 --- a/app/jobs/simpleStopChaser/helper.js +++ b/app/jobs/simpleStopChaser/helper.js @@ -168,7 +168,7 @@ const getBuyBalance = async (logger, indicators, options) => { // 4. Calculate free balance with precision const lotPrecision = symbolInfo.filterLotSize.stepSize.indexOf(1) - 1; - let freeBalance = +(+quoteAssetBalance.free).toFixed(lotPrecision); + let freeBalance = parseFloat(_.floor(+quoteAssetBalance.free, lotPrecision)); logger.info({ freeBalance }, 'Current free balance'); @@ -233,15 +233,17 @@ const getSellBalance = async ( // 3. Calculate free balance with precision const lotPrecision = symbolInfo.filterLotSize.stepSize.indexOf(1) - 1; - const freeBalance = +(+baseAssetBalance.free).toFixed(lotPrecision); - const lockedBalance = +(+baseAssetBalance.locked).toFixed(lotPrecision); + const freeBalance = parseFloat(_.floor(+baseAssetBalance.free, lotPrecision)); + const lockedBalance = parseFloat( + _.floor(+baseAssetBalance.locked, lotPrecision) + ); // 4. If total balance is not enough to sell, then the last buy price is meaningless. const totalBalance = freeBalance + lockedBalance; // Calculate quantity - commission - const quantity = +(totalBalance - totalBalance * (0.1 / 100)).toFixed( - lotPrecision + const quantity = parseFloat( + _.floor(totalBalance - totalBalance * (0.1 / 100), lotPrecision) ); if (quantity <= +symbolInfo.filterLotSize.minQty) { @@ -298,10 +300,13 @@ const getBuyOrderQuantity = (logger, symbolInfo, balanceInfo, indicators) => { const orderQuantityBeforeCommission = 1 / (+baseAssetPrice / freeBalance); - const orderQuantity = +( - orderQuantityBeforeCommission - - orderQuantityBeforeCommission * (0.1 / 100) - ).toFixed(lotPrecision); + const orderQuantity = parseFloat( + _.floor( + orderQuantityBeforeCommission - + orderQuantityBeforeCommission * (0.1 / 100), + lotPrecision + ) + ); if (orderQuantity <= 0) { return { @@ -331,8 +336,8 @@ const getBuyOrderQuantity = (logger, symbolInfo, balanceInfo, indicators) => { */ const getBuyOrderPrice = (logger, symbolInfo, orderQuantityInfo) => { const orderPrecision = symbolInfo.filterPrice.tickSize.indexOf(1) - 1; - const orderPrice = +(+orderQuantityInfo.baseAssetPrice).toFixed( - orderPrecision + const orderPrice = parseFloat( + _.floor(+orderQuantityInfo.baseAssetPrice, orderPrecision) ); logger.info({ orderPrecision, orderPrice }, 'Calculated order price'); @@ -409,7 +414,9 @@ const placeStopLossLimitOrder = async ( ); // Calculate quantity - commission - const quantity = +(balance - balance * (0.1 / 100)).toFixed(lotPrecision); + const quantity = parseFloat( + _.floor(balance - balance * (0.1 / 100), lotPrecision) + ); if (quantity <= +symbolInfo.filterLotSize.minQty) { return { diff --git a/docker-compose.server.yml b/docker-compose.server.yml index 66b5f749..3103357f 100644 --- a/docker-compose.server.yml +++ b/docker-compose.server.yml @@ -20,9 +20,9 @@ services: - BINANCE_JOBS_SIMPLE_STOP_CHASER_ENABLED=true - BINANCE_JOBS_SIMPLE_STOP_CHASER_CANDLES_INTERVAL=15m - BINANCE_JOBS_SIMPLE_STOP_CHASER_CANDLES_LIMIT=200 - - BINANCE_JOBS_SIMPLE_STOP_CHASER_SYMBOLS=["BTCUSDT", "ETHUSDT", - "LINKUSDT", "DOTUSDT", "ADAUSDT", "LTCUSDT", "CRVUSDT", "XRPUSDT", - "QTUMUSDT", "BNBUSDT", "TRXUSDT", "XLMUSDT"] + - BINANCE_JOBS_SIMPLE_STOP_CHASER_SYMBOLS=["BTCUPUSDT", "ETHUPUSDT", + "LINKUPUSDT", "DOTUPUSDT", "ADAUPUSDT", "LTCUPUSDT", "XRPUPUSDT", + "BNBUPUSDT", "TRXUPUSDT", "XLMUPUSDT"] - BINANCE_JOBS_SIMPLE_STOP_CHASER_MAX_PURCHASE_AMOUNT=100 - BINANCE_JOBS_SIMPLE_STOP_CHASER_STOP_LOSS_LIMIT_LAST_BUY_PERCENTAGE=1.06 - BINANCE_JOBS_SIMPLE_STOP_CHASER_STOP_LOSS_LIMIT_STOP_PERCENTAGE=0.97 @@ -39,6 +39,8 @@ services: restart: unless-stopped volumes: - redis_data:/data + - ./redis/redis.conf:/usr/local/etc/redis/redis.conf + command: redis-server /usr/local/etc/redis/redis.conf ports: - 6380:6379 diff --git a/docker-compose.yml b/docker-compose.yml index 7c534e0d..afbd2c9d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -41,6 +41,8 @@ services: - internal volumes: - redis_data:/data + - ./redis/redis.conf:/usr/local/etc/redis/redis.conf + command: redis-server /usr/local/etc/redis/redis.conf ports: - 6380:6379 diff --git a/redis/redis.conf b/redis/redis.conf new file mode 100644 index 00000000..20ee6454 --- /dev/null +++ b/redis/redis.conf @@ -0,0 +1,7 @@ +appendonly yes +appendfilename "appendonly.aof" +appendfsync everysec +no-appendfsync-on-rewrite no +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb +aof-load-truncated yes