diff --git a/.gitignore b/.gitignore index 6f028a33..93a5990e 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ data log .env +.vs diff --git a/CHANGELOG.md b/CHANGELOG.md index 91e432af..c6773aef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. ## Unreleased - Support ATH buy restriction by [@habibalkhabbaz](https://github.com/habibalkhabbaz) - [#82](https://github.com/chrisleekr/binance-trading-bot/issues/82) +- Support last buy price removing threshold by [@pedrohusky](https://github.com/pedrohusky) - [#190](https://github.com/chrisleekr/binance-trading-bot/issues/190) ## [0.0.71] - 2021-06-14 diff --git a/app/cronjob/trailingTrade/step/__tests__/remove-last-buy-price.test.js b/app/cronjob/trailingTrade/step/__tests__/remove-last-buy-price.test.js index 11443013..7547431d 100644 --- a/app/cronjob/trailingTrade/step/__tests__/remove-last-buy-price.test.js +++ b/app/cronjob/trailingTrade/step/__tests__/remove-last-buy-price.test.js @@ -52,6 +52,9 @@ describe('remove-last-buy-price.js', () => { action: 'not-determined', isLocked: true, symbol: 'BTCUPUSDT', + symbolConfiguration: { + buy: { lastBuyPriceRemoveThreshold: 10 } + }, symbolInfo: { filterLotSize: { stepSize: '0.01000000', @@ -98,6 +101,9 @@ describe('remove-last-buy-price.js', () => { action: 'buy', isLocked: false, symbol: 'BTCUPUSDT', + symbolConfiguration: { + buy: { lastBuyPriceRemoveThreshold: 10 } + }, symbolInfo: { filterLotSize: { stepSize: '0.01000000', @@ -150,6 +156,9 @@ describe('remove-last-buy-price.js', () => { action: 'not-determined', isLocked: false, symbol: 'BTCUPUSDT', + symbolConfiguration: { + buy: { lastBuyPriceRemoveThreshold: 10 } + }, symbolInfo: { filterLotSize: { stepSize: '0.01000000', @@ -196,6 +205,9 @@ describe('remove-last-buy-price.js', () => { action: 'not-determined', isLocked: false, symbol: 'BTCUPUSDT', + symbolConfiguration: { + buy: { lastBuyPriceRemoveThreshold: 10 } + }, symbolInfo: { filterLotSize: { stepSize: '0.01000000', @@ -242,6 +254,9 @@ describe('remove-last-buy-price.js', () => { action: 'not-determined', isLocked: false, symbol: 'BTCUPUSDT', + symbolConfiguration: { + buy: { lastBuyPriceRemoveThreshold: 10 } + }, symbolInfo: { filterLotSize: { stepSize: '0.01000000', @@ -304,6 +319,9 @@ describe('remove-last-buy-price.js', () => { action: 'not-determined', isLocked: false, symbol: 'BTCUPUSDT', + symbolConfiguration: { + buy: { lastBuyPriceRemoveThreshold: 10 } + }, symbolInfo: { filterLotSize: { stepSize: '0.01000000', @@ -357,6 +375,9 @@ describe('remove-last-buy-price.js', () => { action: 'not-determined', isLocked: false, symbol: 'BTCUPUSDT', + symbolConfiguration: { + buy: { lastBuyPriceRemoveThreshold: 10 } + }, symbolInfo: { filterLotSize: { stepSize: '0.01000000', @@ -405,6 +426,9 @@ describe('remove-last-buy-price.js', () => { action: 'not-determined', isLocked: false, symbol: 'ALPHABTC', + symbolConfiguration: { + buy: { lastBuyPriceRemoveThreshold: 0.0001 } + }, symbolInfo: { filterLotSize: { stepSize: '1.00000000', @@ -467,6 +491,9 @@ describe('remove-last-buy-price.js', () => { action: 'not-determined', isLocked: false, symbol: 'BTCUPUSDT', + symbolConfiguration: { + buy: { lastBuyPriceRemoveThreshold: 10 } + }, symbolInfo: { filterLotSize: { stepSize: '0.01000000', @@ -537,6 +564,9 @@ describe('remove-last-buy-price.js', () => { action: 'not-determined', isLocked: false, symbol: 'BTCUPUSDT', + symbolConfiguration: { + buy: { lastBuyPriceRemoveThreshold: 10 } + }, symbolInfo: { filterLotSize: { stepSize: '0.01000000', @@ -570,62 +600,118 @@ describe('remove-last-buy-price.js', () => { }); describe('when cannot find open orders', () => { - beforeEach(async () => { - jest.mock('../../../trailingTradeHelper/common', () => ({ - getAndCacheOpenOrdersForSymbol: mockGetAndCacheOpenOrdersForSymbol, - isActionDisabled: mockIsActionDisabled, - getAPILimit: mockGetAPILimit - })); + describe('last buy price remove threshold is same as minimum notional', () => { + beforeEach(async () => { + jest.mock('../../../trailingTradeHelper/common', () => ({ + getAndCacheOpenOrdersForSymbol: + mockGetAndCacheOpenOrdersForSymbol, + isActionDisabled: mockIsActionDisabled, + getAPILimit: mockGetAPILimit + })); - const step = require('../remove-last-buy-price'); + const step = require('../remove-last-buy-price'); - rawData = { - action: 'not-determined', - isLocked: false, - symbol: 'BTCUPUSDT', - symbolInfo: { - filterLotSize: { - stepSize: '0.01000000', - minQty: '0.01000000' + rawData = { + action: 'not-determined', + isLocked: false, + symbol: 'BTCUPUSDT', + symbolConfiguration: { + buy: { lastBuyPriceRemoveThreshold: 10 } }, - filterMinNotional: { - minNotional: '10.00000000' + symbolInfo: { + filterLotSize: { + stepSize: '0.01000000', + minQty: '0.01000000' + }, + filterMinNotional: { + minNotional: '10.00000000' + } + }, + openOrders: [], + baseAssetBalance: { + free: 0, + locked: 0.04 + }, + sell: { + currentPrice: 200, + lastBuyPrice: 160 } - }, - openOrders: [], - baseAssetBalance: { - free: 0, - locked: 0.04 - }, - sell: { - currentPrice: 200, - lastBuyPrice: 160 - } - }; + }; - result = await step.execute(loggerMock, rawData); - }); + result = await step.execute(loggerMock, rawData); + }); - it('triggers mongo.deleteOne', () => { - expect(mongoMock.deleteOne).toHaveBeenCalledWith( - loggerMock, - 'trailing-trade-symbols', - { key: 'BTCUPUSDT-last-buy-price' } - ); + it('triggers mongo.deleteOne', () => { + expect(mongoMock.deleteOne).toHaveBeenCalledWith( + loggerMock, + 'trailing-trade-symbols', + { key: 'BTCUPUSDT-last-buy-price' } + ); + }); + + it('returns expected data', () => { + expect(result).toStrictEqual({ + ...rawData, + ...{ + sell: { + currentPrice: 200, + lastBuyPrice: 160, + processMessage: + 'Balance is less than the last buy price remove threshold. Delete last buy price.', + updatedAt: expect.any(Object) + } + } + }); + }); }); - it('returns expected data', () => { - expect(result).toStrictEqual({ - ...rawData, - ...{ + describe('last buy price remove threshold is less than minimum notional', () => { + beforeEach(async () => { + jest.mock('../../../trailingTradeHelper/common', () => ({ + getAndCacheOpenOrdersForSymbol: + mockGetAndCacheOpenOrdersForSymbol, + isActionDisabled: mockIsActionDisabled, + getAPILimit: mockGetAPILimit + })); + + const step = require('../remove-last-buy-price'); + + rawData = { + action: 'not-determined', + isLocked: false, + symbol: 'BTCUPUSDT', + symbolConfiguration: { + buy: { lastBuyPriceRemoveThreshold: 5 } + }, + symbolInfo: { + filterLotSize: { + stepSize: '0.01000000', + minQty: '0.01000000' + }, + filterMinNotional: { + minNotional: '10.00000000' + } + }, + openOrders: [], + baseAssetBalance: { + free: 0, + locked: 0.04 + }, sell: { currentPrice: 200, - lastBuyPrice: 160, - processMessage: - 'Balance is less than the notional value. Delete last buy price.', - updatedAt: expect.any(Object) + lastBuyPrice: 160 } - } + }; + + result = await step.execute(loggerMock, rawData); + }); + + it('does not trigger mongo.deleteOne', () => { + expect(mongoMock.deleteOne).not.toHaveBeenCalled(); + }); + + it('returns expected data', () => { + expect(result).toStrictEqual(rawData); }); }); }); @@ -645,6 +731,9 @@ describe('remove-last-buy-price.js', () => { action: 'not-determined', isLocked: false, symbol: 'BTCUPUSDT', + symbolConfiguration: { + buy: { lastBuyPriceRemoveThreshold: 10 } + }, symbolInfo: { filterLotSize: { stepSize: '0.01000000', diff --git a/app/cronjob/trailingTrade/step/remove-last-buy-price.js b/app/cronjob/trailingTrade/step/remove-last-buy-price.js index b5457279..297cb922 100644 --- a/app/cronjob/trailingTrade/step/remove-last-buy-price.js +++ b/app/cronjob/trailingTrade/step/remove-last-buy-price.js @@ -6,7 +6,6 @@ const { getAPILimit, isActionDisabled } = require('../../trailingTradeHelper/common'); - /** * Retrieve last buy order from cache * @@ -14,6 +13,7 @@ const { * @param {*} symbol * @returns */ + const getLastBuyOrder = async (logger, symbol) => { const cachedLastBuyOrder = JSON.parse(await cache.get(`${symbol}-last-buy-order`)) || {}; @@ -67,6 +67,9 @@ const execute = async (logger, rawData) => { isLocked, action, symbol, + symbolConfiguration: { + buy: { lastBuyPriceRemoveThreshold } + }, symbolInfo: { filterLotSize: { stepSize, minQty }, filterMinNotional: { minNotional } @@ -169,7 +172,7 @@ const execute = async (logger, rawData) => { return data; } - if (baseAssetQuantity * currentPrice < parseFloat(minNotional)) { + if (baseAssetQuantity * currentPrice < lastBuyPriceRemoveThreshold) { // Final check for open orders refreshedOpenOrders = await getAndCacheOpenOrdersForSymbol(logger, symbol); if (refreshedOpenOrders.length > 0) { @@ -178,7 +181,7 @@ const execute = async (logger, rawData) => { } processMessage = - 'Balance is less than the notional value. Delete last buy price.'; + 'Balance is less than the last buy price remove threshold. Delete last buy price.'; logger.error({ baseAssetQuantity }, processMessage); diff --git a/app/cronjob/trailingTradeHelper/__tests__/configuration.test.js b/app/cronjob/trailingTradeHelper/__tests__/configuration.test.js index ec97b1f9..4d19e854 100644 --- a/app/cronjob/trailingTradeHelper/__tests__/configuration.test.js +++ b/app/cronjob/trailingTradeHelper/__tests__/configuration.test.js @@ -346,6 +346,7 @@ describe('configuration.js', () => { cache.hget = jest.fn().mockResolvedValue( JSON.stringify({ + quoteAsset: 'USDT', filterMinNotional: { minNotional: '10.00000000' } @@ -368,9 +369,21 @@ describe('configuration.js', () => { maxPurchaseAmounts: { USDT: 100 }, + lastBuyPriceRemoveThreshold: -1, + lastBuyPriceRemoveThresholds: { + USDT: 10 + }, triggerPercentage: 1.0, stopPercentage: 1.02, - limitPercentage: 1.021 + limitPercentage: 1.021, + athRestriction: { + enabled: true, + candles: { + interval: '1d', + limit: 30 + }, + restrictionPercentage: 0.9 + } }, sell: { enabled: true, @@ -387,6 +400,7 @@ describe('configuration.js', () => { system: { temporaryDisableActionAfterConfirmingOrder: 20, checkManualBuyOrderPeriod: 5, + placeManualOrderInterval: 5, refreshAccountInfoPeriod: 1 } }; @@ -418,9 +432,23 @@ describe('configuration.js', () => { BTC: 0.001, BUSD: 100 }, + lastBuyPriceRemoveThreshold: -1, + lastBuyPriceRemoveThresholds: { + USDT: 5, + BTC: 0.00005, + BUSD: 5 + }, triggerPercentage: 1.05, stopPercentage: 1.05, - limitPercentage: 1.051 + limitPercentage: 1.051, + athRestriction: { + enabled: true, + candles: { + interval: '1d', + limit: 30 + }, + restrictionPercentage: 0.9 + } }, sell: { enabled: false, @@ -437,6 +465,7 @@ describe('configuration.js', () => { system: { temporaryDisableActionAfterConfirmingOrder: 10, checkManualBuyOrderPeriod: 10, + placeManualOrderInterval: 5, refreshAccountInfoPeriod: 3 } }; @@ -454,6 +483,7 @@ describe('configuration.js', () => { buy: { enabled: true, maxPurchaseAmount: 150, + lastBuyPriceRemoveThreshold: 4, triggerPercentage: 1.04, stopPercentage: 1.04, limitPercentage: 1.041 @@ -496,9 +526,23 @@ describe('configuration.js', () => { enabled: false, maxPurchaseAmount: -1, maxPurchaseAmounts: { USDT: 100, BTC: 0.001, BUSD: 100 }, + lastBuyPriceRemoveThreshold: -1, + lastBuyPriceRemoveThresholds: { + USDT: 5, + BTC: 0.00005, + BUSD: 5 + }, triggerPercentage: 1.05, stopPercentage: 1.05, - limitPercentage: 1.051 + limitPercentage: 1.051, + athRestriction: { + enabled: true, + candles: { + interval: '1d', + limit: 30 + }, + restrictionPercentage: 0.9 + } }, sell: { enabled: false, @@ -515,6 +559,7 @@ describe('configuration.js', () => { system: { temporaryDisableActionAfterConfirmingOrder: 10, checkManualBuyOrderPeriod: 10, + placeManualOrderInterval: 5, refreshAccountInfoPeriod: 3 } }); @@ -566,9 +611,21 @@ describe('configuration.js', () => { maxPurchaseAmounts: { USDT: 100 }, + lastBuyPriceRemoveThreshold: -1, + lastBuyPriceRemoveThresholds: { + USDT: 10 + }, triggerPercentage: 1, stopPercentage: 1.02, - limitPercentage: 1.021 + limitPercentage: 1.021, + athRestriction: { + enabled: true, + candles: { + interval: '1d', + limit: 30 + }, + restrictionPercentage: 0.9 + } }, sell: { enabled: true, @@ -585,6 +642,7 @@ describe('configuration.js', () => { system: { temporaryDisableActionAfterConfirmingOrder: 20, checkManualBuyOrderPeriod: 5, + placeManualOrderInterval: 5, refreshAccountInfoPeriod: 1 } } @@ -600,9 +658,18 @@ describe('configuration.js', () => { buy: { enabled: true, maxPurchaseAmount: 100, + lastBuyPriceRemoveThreshold: 10, triggerPercentage: 1, stopPercentage: 1.02, - limitPercentage: 1.021 + limitPercentage: 1.021, + athRestriction: { + enabled: true, + candles: { + interval: '1d', + limit: 30 + }, + restrictionPercentage: 0.9 + } }, sell: { enabled: true, @@ -619,6 +686,7 @@ describe('configuration.js', () => { system: { temporaryDisableActionAfterConfirmingOrder: 20, checkManualBuyOrderPeriod: 5, + placeManualOrderInterval: 5, refreshAccountInfoPeriod: 1 } }); @@ -648,9 +716,23 @@ describe('configuration.js', () => { BTC: 0.001, BUSD: 100 }, + lastBuyPriceRemoveThreshold: -1, + lastBuyPriceRemoveThresholds: { + USDT: 5, + BTC: 0.0004, + BUSD: 3 + }, triggerPercentage: 1.05, stopPercentage: 1.05, - limitPercentage: 1.051 + limitPercentage: 1.051, + athRestriction: { + enabled: true, + candles: { + interval: '1d', + limit: 30 + }, + restrictionPercentage: 0.9 + } }, sell: { enabled: false, @@ -667,6 +749,7 @@ describe('configuration.js', () => { system: { temporaryDisableActionAfterConfirmingOrder: 10, checkManualBuyOrderPeriod: 10, + placeManualOrderInterval: 5, refreshAccountInfoPeriod: 3 } }; @@ -694,9 +777,18 @@ describe('configuration.js', () => { buy: { enabled: false, maxPurchaseAmount: 100, + lastBuyPriceRemoveThreshold: 5, triggerPercentage: 1.05, stopPercentage: 1.05, - limitPercentage: 1.051 + limitPercentage: 1.051, + athRestriction: { + enabled: true, + candles: { + interval: '1d', + limit: 30 + }, + restrictionPercentage: 0.9 + } }, sell: { enabled: false, @@ -713,6 +805,7 @@ describe('configuration.js', () => { system: { temporaryDisableActionAfterConfirmingOrder: 10, checkManualBuyOrderPeriod: 10, + placeManualOrderInterval: 5, refreshAccountInfoPeriod: 3 } }); @@ -720,7 +813,7 @@ describe('configuration.js', () => { }); describe('when found global/symbol configuration', () => { - describe('when symbol configuration buy max purchase amount is not -1', () => { + describe('when symbol configuration buy max purchase amount/last buy price remove threshold are not -1', () => { beforeEach(async () => { mongo.findOne = jest.fn((_logger, collection, filter) => { if ( @@ -743,9 +836,23 @@ describe('configuration.js', () => { BTC: 0.001, BUSD: 100 }, + lastBuyPriceRemoveThreshold: -1, + lastBuyPriceRemoveThresholds: { + USDT: 4, + BTC: 0.0008, + BUSD: 5 + }, triggerPercentage: 1.05, stopPercentage: 1.05, - limitPercentage: 1.051 + limitPercentage: 1.051, + athRestriction: { + enabled: true, + candles: { + interval: '1d', + limit: 30 + }, + restrictionPercentage: 0.9 + } }, sell: { enabled: false, @@ -762,6 +869,7 @@ describe('configuration.js', () => { system: { temporaryDisableActionAfterConfirmingOrder: 10, checkManualBuyOrderPeriod: 10, + placeManualOrderInterval: 5, refreshAccountInfoPeriod: 3 } }; @@ -780,9 +888,18 @@ describe('configuration.js', () => { buy: { enabled: true, maxPurchaseAmount: 150, + lastBuyPriceRemoveThreshold: 8, triggerPercentage: 1.04, stopPercentage: 1.04, - limitPercentage: 1.041 + limitPercentage: 1.041, + athRestriction: { + enabled: true, + candles: { + interval: '1d', + limit: 30 + }, + restrictionPercentage: 0.9 + } }, sell: { enabled: true, @@ -822,9 +939,18 @@ describe('configuration.js', () => { buy: { enabled: true, maxPurchaseAmount: 150, + lastBuyPriceRemoveThreshold: 8, triggerPercentage: 1.04, stopPercentage: 1.04, - limitPercentage: 1.041 + limitPercentage: 1.041, + athRestriction: { + enabled: true, + candles: { + interval: '1d', + limit: 30 + }, + restrictionPercentage: 0.9 + } }, sell: { enabled: true, @@ -841,13 +967,14 @@ describe('configuration.js', () => { system: { temporaryDisableActionAfterConfirmingOrder: 10, checkManualBuyOrderPeriod: 10, + placeManualOrderInterval: 5, refreshAccountInfoPeriod: 3 } }); }); }); - describe('when global configuration buy max purchase amount is not -1', () => { + describe('when global configuration buy max purchase amount/last buy price moreve threshold are not -1', () => { beforeEach(async () => { cache.hget = jest.fn().mockResolvedValue( JSON.stringify({ @@ -878,9 +1005,23 @@ describe('configuration.js', () => { BTC: 0.001, BUSD: 100 }, + lastBuyPriceRemoveThreshold: 7, + lastBuyPriceRemoveThresholds: { + USDT: 5, + BTC: 0.0004, + BUSD: 4 + }, triggerPercentage: 1.05, stopPercentage: 1.05, - limitPercentage: 1.051 + limitPercentage: 1.051, + athRestriction: { + enabled: true, + candles: { + interval: '1d', + limit: 30 + }, + restrictionPercentage: 0.9 + } }, sell: { enabled: false, @@ -897,6 +1038,7 @@ describe('configuration.js', () => { system: { temporaryDisableActionAfterConfirmingOrder: 10, checkManualBuyOrderPeriod: 10, + placeManualOrderInterval: 5, refreshAccountInfoPeriod: 3 } }; @@ -948,9 +1090,18 @@ describe('configuration.js', () => { buy: { enabled: true, maxPurchaseAmount: 50, + lastBuyPriceRemoveThreshold: 7, triggerPercentage: 1.04, stopPercentage: 1.04, - limitPercentage: 1.041 + limitPercentage: 1.041, + athRestriction: { + enabled: true, + candles: { + interval: '1d', + limit: 30 + }, + restrictionPercentage: 0.9 + } }, sell: { enabled: true, @@ -967,6 +1118,7 @@ describe('configuration.js', () => { system: { temporaryDisableActionAfterConfirmingOrder: 10, checkManualBuyOrderPeriod: 10, + placeManualOrderInterval: 5, refreshAccountInfoPeriod: 3 } }); @@ -1023,9 +1175,18 @@ describe('configuration.js', () => { buy: { enabled: false, maxPurchaseAmount: 80, + lastBuyPriceRemoveThreshold: 10, triggerPercentage: 1, stopPercentage: 1.02, - limitPercentage: 1.021 + limitPercentage: 1.021, + athRestriction: { + enabled: true, + candles: { + interval: '1d', + limit: 30 + }, + restrictionPercentage: 0.9 + } }, sell: { enabled: false, @@ -1045,6 +1206,7 @@ describe('configuration.js', () => { system: { temporaryDisableActionAfterConfirmingOrder: 20, checkManualBuyOrderPeriod: 5, + placeManualOrderInterval: 5, refreshAccountInfoPeriod: 1 } }); @@ -1094,9 +1256,18 @@ describe('configuration.js', () => { buy: { enabled: false, maxPurchaseAmount: -1, + lastBuyPriceRemoveThreshold: -1, triggerPercentage: 1, stopPercentage: 1.02, - limitPercentage: 1.021 + limitPercentage: 1.021, + athRestriction: { + enabled: true, + candles: { + interval: '1d', + limit: 30 + }, + restrictionPercentage: 0.9 + } }, sell: { enabled: false, @@ -1113,6 +1284,7 @@ describe('configuration.js', () => { system: { temporaryDisableActionAfterConfirmingOrder: 20, checkManualBuyOrderPeriod: 5, + placeManualOrderInterval: 5, refreshAccountInfoPeriod: 1 } }); diff --git a/app/cronjob/trailingTradeHelper/configuration.js b/app/cronjob/trailingTradeHelper/configuration.js index d669b507..4fddb8a7 100644 --- a/app/cronjob/trailingTradeHelper/configuration.js +++ b/app/cronjob/trailingTradeHelper/configuration.js @@ -154,7 +154,7 @@ const getMaxPurchaseAmount = async ( let newBuyMaxPurchaseAmount = -1; - // If old max purchase maount is -1, then should calculate maximum purchase amount based on the notional amount. + // If old max purchase amount is -1, then should calculate last buy remove threshold based on the notional amount. const cachedSymbolInfo = JSON.parse( await cache.hget('trailing-trade-symbols', `${symbol}-symbol-info`) @@ -174,7 +174,7 @@ const getMaxPurchaseAmount = async ( logger.info( { quoteAsset, newBuyMaxPurchaseAmount }, - 'Retreived max purchase amount from global configuration' + 'Retrieved max purchase amount from global configuration' ); if (newBuyMaxPurchaseAmount === -1) { @@ -194,6 +194,75 @@ const getMaxPurchaseAmount = async ( return newBuyMaxPurchaseAmount; }; + +const getLastBuyPriceRemoveThreshold = async ( + logger, + symbol, + globalConfiguration, + symbolConfiguration +) => { + const symbolBuyLastBuyPriceRemoveThreshold = _.get( + symbolConfiguration, + 'buy.lastBuyPriceRemoveThreshold', + -1 + ); + + if (symbolBuyLastBuyPriceRemoveThreshold !== -1) { + logger.info( + { symbolBuyLastBuyPriceRemoveThreshold }, + 'Last buy threshold is found from symbol configuration.' + ); + return symbolBuyLastBuyPriceRemoveThreshold; + } + + logger.info( + { symbolBuyLastBuyPriceRemoveThreshold }, + 'Last Buy Price Remove Threshold is set as -1. Need to calculate and override it' + ); + + let newBuyLastBuyPriceRemoveThreshold = -1; + + // If old last buy price remove threshold is -1, + // then should calculate last buy price remove threshold based on the notional amount. + const cachedSymbolInfo = + JSON.parse( + await cache.hget('trailing-trade-symbols', `${symbol}-symbol-info`) + ) || {}; + + if (_.isEmpty(cachedSymbolInfo) === false) { + const { + quoteAsset, + filterMinNotional: { minNotional } + } = cachedSymbolInfo; + + newBuyLastBuyPriceRemoveThreshold = _.get( + globalConfiguration, + `buy.lastBuyPriceRemoveThresholds.${quoteAsset}`, + -1 + ); + + logger.info( + { quoteAsset, newBuyLastBuyPriceRemoveThreshold }, + 'Retrieved last buy price remove threshold from global configuration' + ); + + if (newBuyLastBuyPriceRemoveThreshold === -1) { + newBuyLastBuyPriceRemoveThreshold = parseFloat(minNotional); + + logger.info( + { newBuyLastBuyPriceRemoveThreshold, minNotional }, + 'Could not get last buy price remove threshold from global configuration. Use minimum notional from symbol info' + ); + } + } else { + logger.info( + { cachedSymbolInfo }, + 'Could not find symbol info, wait to be cached.' + ); + } + + return newBuyLastBuyPriceRemoveThreshold; +}; /** * Get global/symbol configuration * @@ -222,8 +291,20 @@ const getConfiguration = async (logger, symbol = null) => { ) ); + _.set( + mergedConfigValue, + 'buy.lastBuyPriceRemoveThreshold', + await getLastBuyPriceRemoveThreshold( + logger, + symbol, + globalConfigValue, + symbolConfigValue + ) + ); + // For symbol configuration, remove maxPurchaseAmounts _.unset(mergedConfigValue, 'buy.maxPurchaseAmounts'); + _.unset(mergedConfigValue, 'buy.lastBuyPriceRemoveThresholds'); } // Merge global and symbol configuration diff --git a/app/frontend/websocket/handlers/__tests__/fixtures/latest-stats.json b/app/frontend/websocket/handlers/__tests__/fixtures/latest-stats.json index 255d741d..a10489c3 100644 --- a/app/frontend/websocket/handlers/__tests__/fixtures/latest-stats.json +++ b/app/frontend/websocket/handlers/__tests__/fixtures/latest-stats.json @@ -23,6 +23,8 @@ "enabled": true, "maxPurchaseAmount": -1, "maxPurchaseAmounts": {}, + "lastBuyPriceRemoveThreshold": -1, + "lastBuyPriceRemoveThresholds": {}, "triggerPercentage": 1, "stopPercentage": 1.02, "limitPercentage": 1.021, @@ -64,6 +66,8 @@ "enabled": true, "maxPurchaseAmount": -1, "maxPurchaseAmounts": {}, + "lastBuyPriceRemoveThreshold": -1, + "lastBuyPriceRemoveThresholds": {}, "triggerPercentage": 1, "stopPercentage": 1.02, "limitPercentage": 1.021, @@ -137,6 +141,8 @@ "enabled": true, "maxPurchaseAmount": -1, "maxPurchaseAmounts": {}, + "lastBuyPriceRemoveThreshold": -1, + "lastBuyPriceRemoveThresholds": {}, "triggerPercentage": 1, "stopPercentage": 1.02, "limitPercentage": 1.021, @@ -176,6 +182,7 @@ "buy": { "enabled": true, "maxPurchaseAmount": 100, + "lastBuyPriceRemoveThreshold": 10, "triggerPercentage": 1, "stopPercentage": 1.02, "limitPercentage": 1.021, @@ -347,6 +354,8 @@ "enabled": true, "maxPurchaseAmount": -1, "maxPurchaseAmounts": {}, + "lastBuyPriceRemoveThreshold": -1, + "lastBuyPriceRemoveThresholds": {}, "triggerPercentage": 1, "stopPercentage": 1.02, "limitPercentage": 1.021, @@ -386,6 +395,7 @@ "buy": { "enabled": true, "maxPurchaseAmount": 100, + "lastBuyPriceRemoveThreshold": 10, "triggerPercentage": 1, "stopPercentage": 1.02, "limitPercentage": 1.021, diff --git a/app/frontend/websocket/handlers/__tests__/setting-update.test.js b/app/frontend/websocket/handlers/__tests__/setting-update.test.js index dfd59a7b..f7eb298d 100644 --- a/app/frontend/websocket/handlers/__tests__/setting-update.test.js +++ b/app/frontend/websocket/handlers/__tests__/setting-update.test.js @@ -80,7 +80,8 @@ describe('setting-update.test.js', () => { }, buy: { enabled: true, - maxPurchaseAmount: 100 + maxPurchaseAmount: 100, + lastBuyPriceRemoveThreshold: 10 }, sell: { enabled: true, @@ -105,7 +106,8 @@ describe('setting-update.test.js', () => { }, buy: { enabled: true, - maxPurchaseAmount: 150 + maxPurchaseAmount: 150, + lastBuyPriceRemoveThreshold: 10 }, sell: { enabled: true, @@ -131,7 +133,8 @@ describe('setting-update.test.js', () => { }, buy: { enabled: true, - maxPurchaseAmount: -1 + maxPurchaseAmount: -1, + lastBuyPriceRemoveThreshold: -1 }, sell: { enabled: true, @@ -166,7 +169,8 @@ describe('setting-update.test.js', () => { candles: { interval: '1h', limit: '100' }, buy: { enabled: true, - maxPurchaseAmount: -1 + maxPurchaseAmount: -1, + lastBuyPriceRemoveThreshold: -1 }, sell: { enabled: true, @@ -197,7 +201,8 @@ describe('setting-update.test.js', () => { }, buy: { enabled: true, - maxPurchaseAmount: 100 + maxPurchaseAmount: 100, + lastBuyPriceRemoveThreshold: 10 }, sell: { enabled: true, @@ -222,7 +227,8 @@ describe('setting-update.test.js', () => { }, buy: { enabled: true, - maxPurchaseAmount: 150 + maxPurchaseAmount: 150, + lastBuyPriceRemoveThreshold: 10 }, sell: { enabled: true, @@ -248,7 +254,8 @@ describe('setting-update.test.js', () => { }, buy: { enabled: true, - maxPurchaseAmount: -1 + maxPurchaseAmount: -1, + lastBuyPriceRemoveThreshold: -1 }, sell: { enabled: true, @@ -290,7 +297,8 @@ describe('setting-update.test.js', () => { candles: { interval: '1h', limit: '100' }, buy: { enabled: true, - maxPurchaseAmount: -1 + maxPurchaseAmount: -1, + lastBuyPriceRemoveThreshold: -1 }, sell: { enabled: true, diff --git a/app/frontend/websocket/handlers/setting-update.js b/app/frontend/websocket/handlers/setting-update.js index 1bf7727a..151455b4 100644 --- a/app/frontend/websocket/handlers/setting-update.js +++ b/app/frontend/websocket/handlers/setting-update.js @@ -29,6 +29,7 @@ const handleSettingUpdate = async (logger, ws, payload) => { // Set max purchase amount to be -1, which mean max purchase amount // will be automatically calculate based on the notional amount. mergedConfiguration.buy.maxPurchaseAmount = -1; + mergedConfiguration.buy.lastBuyPriceRemoveThreshold = -1; logger.info({ mergedConfiguration }, 'New merged configuration'); diff --git a/config/default.json b/config/default.json index ecb34b6e..41b86567 100644 --- a/config/default.json +++ b/config/default.json @@ -52,6 +52,8 @@ "enabled": true, "maxPurchaseAmount": -1, "maxPurchaseAmounts": {}, + "lastBuyPriceRemoveThreshold": -1, + "lastBuyPriceRemoveThresholds": {}, "triggerPercentage": 1.0, "stopPercentage": 1.02, "limitPercentage": 1.021, diff --git a/package-lock.json b/package-lock.json index fce55cf9..49ff575b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6570,9 +6570,9 @@ } }, "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { "is-glob": "^4.0.1" @@ -13866,9 +13866,9 @@ "dev": true }, "normalize-url": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", - "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", "dev": true }, "npm-run-path": { diff --git a/public/index.html b/public/index.html index efd89828..10438e97 100644 --- a/public/index.html +++ b/public/index.html @@ -148,6 +148,10 @@ type="text/babel" src="./js/SettingIconMaxPurchaseAmount.js" > + diff --git a/public/js/CoinWrapperSetting.js b/public/js/CoinWrapperSetting.js index 47415d4a..08659586 100644 --- a/public/js/CoinWrapperSetting.js +++ b/public/js/CoinWrapperSetting.js @@ -81,6 +81,16 @@ class CoinWrapperSetting extends React.Component { +
Buy
+Sell
+0.9
and the ATH(All
+ Time High) price $110
+ , restriction price will be{' '}
+ $99
for stop limit
+ order.
+ Sell - Stop-Loss
-0.80
, it means
- you won't lose than -20%
of
- the last buy price. When you purchased the
- coin at $100
, the last price
- will be set as $100
. And then
- when the current price reaches{' '}
- $80
, the bot will place the{' '}
- market order to sell all
- available balance.
- 360
, the bot will
- temporarily disable buying for 6 hours.
- 0.9
and the ATH(All Time
- High) price $110
, restriction
- price will be $99
for stop
- limit order.
- 0.80
, it means
+ you won't lose than{' '}
+ -20%
of the last
+ buy price. When you purchased
+ the coin at $100
,
+ the last price will be set as{' '}
+ $100
. And then
+ when the current price reaches{' '}
+ $80
, the bot will
+ place the{' '}
+ market order{' '}
+ to sell all available balance.
+ 360
,
+ the bot will temporarily
+ disable buying for 6 hours.
+ Buy
Sell
+0.9
and the ATH(All
+ Time High) price $110
+ , restriction price will be{' '}
+ $99
for stop limit
+ order.
+ Sell - Stop-Loss
-0.80
, it means
- you won't lose than -20%
of
- the last buy price. When you purchased the
- coin at $100
, the last price
- will be set as $100
. And then
- when the current price reaches{' '}
- $80
, the bot will place the{' '}
- market order to sell all
- available balance.
- 360
, the bot will
- temporarily disable buying for 6 hours.
- 0.9
and the ATH(All Time
- High) price $110
, restriction
- price will be $99
for stop
- limit order.
- 0.80
, it means
+ you won't lose than{' '}
+ -20%
of the last
+ buy price. When you purchased
+ the coin at $100
,
+ the last price will be set as{' '}
+ $100
. And then
+ when the current price reaches{' '}
+ $80
, the bot will
+ place the{' '}
+ market order{' '}
+ to sell all available balance.
+ 360
,
+ the bot will temporarily
+ disable buying for 6 hours.
+