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 { +
+ + Remove last buy price under: + + + {symbolConfiguration.buy.lastBuyPriceRemoveThreshold}{' '} + {quoteAsset} + +
+
Trigger percentage: diff --git a/public/js/SettingIcon.js b/public/js/SettingIcon.js index 209423e5..debd2bfb 100644 --- a/public/js/SettingIcon.js +++ b/public/js/SettingIcon.js @@ -25,9 +25,16 @@ class SettingIcon extends React.Component { this.handleInputChange = this.handleInputChange.bind(this); this.handleMaxPurchaeAmountChange = this.handleMaxPurchaeAmountChange.bind(this); + this.handleLastBuyPriceRemoveThresholdChange = + this.handleLastBuyPriceRemoveThresholdChange.bind(this); } - getQuoteAssets(exchangeSymbols, selectedSymbols, maxPurchaseAmounts) { + getQuoteAssets( + exchangeSymbols, + selectedSymbols, + maxPurchaseAmounts, + lastBuyPriceRemoveThresholds + ) { const quoteAssets = []; selectedSymbols.forEach(symbol => { @@ -42,9 +49,12 @@ class SettingIcon extends React.Component { if (maxPurchaseAmounts[quoteAsset] === undefined) { maxPurchaseAmounts[quoteAsset] = minNotional * 10; } + if (lastBuyPriceRemoveThresholds[quoteAsset] === undefined) { + lastBuyPriceRemoveThresholds[quoteAsset] = minNotional; + } }); - return { quoteAssets, maxPurchaseAmounts }; + return { quoteAssets, maxPurchaseAmounts, lastBuyPriceRemoveThresholds }; } componentDidUpdate(nextProps) { @@ -69,15 +79,22 @@ class SettingIcon extends React.Component { if (configuration.buy.maxPurchaseAmounts === undefined) { configuration.buy.maxPurchaseAmounts = {}; } + if (configuration.buy.lastBuyPriceRemoveThresholds === undefined) { + configuration.buy.lastBuyPriceRemoveThresholds = {}; + } // Set max purchase amount - const { quoteAssets, maxPurchaseAmounts } = this.getQuoteAssets( - exchangeSymbols, - selectedSymbols, - configuration.buy.maxPurchaseAmounts - ); + const { quoteAssets, maxPurchaseAmounts, lastBuyPriceRemoveThresholds } = + this.getQuoteAssets( + exchangeSymbols, + selectedSymbols, + configuration.buy.maxPurchaseAmounts, + configuration.buy.lastBuyPriceRemoveThresholds + ); configuration.buy.maxPurchaseAmounts = maxPurchaseAmounts; + configuration.buy.lastBuyPriceRemoveThresholds = + lastBuyPriceRemoveThresholds; this.setState({ availableSymbols, @@ -137,6 +154,23 @@ class SettingIcon extends React.Component { }); } + handleLastBuyPriceRemoveThresholdChange(newLastBuyPriceRemoveThresholds) { + console.log( + 'handleLastBuyPriceRemoveThresholdChange => ', + newLastBuyPriceRemoveThresholds + ); + + const { configuration } = this.state; + + this.setState({ + configuration: _.set( + configuration, + 'buy.lastBuyPriceRemoveThresholds', + newLastBuyPriceRemoveThresholds + ) + }); + } + render() { const { configuration, availableSymbols, quoteAssets } = this.state; const { symbols: selectedSymbols } = configuration; @@ -193,15 +227,21 @@ class SettingIcon extends React.Component { configuration.symbols = selected; - const { quoteAssets, maxPurchaseAmounts } = - this.getQuoteAssets( - exchangeSymbols, - selected, - configuration.buy.maxPurchaseAmounts - ); + const { + quoteAssets, + maxPurchaseAmounts, + lastBuyPriceRemoveThresholds + } = this.getQuoteAssets( + exchangeSymbols, + selected, + configuration.buy.maxPurchaseAmounts, + configuration.buy.lastBuyPriceRemoveThresholds + ); configuration.buy.maxPurchaseAmounts = maxPurchaseAmounts; + configuration.buy.lastBuyPriceRemoveThresholds = + lastBuyPriceRemoveThresholds; this.setState({ configuration, quoteAssets }); }} size='sm' @@ -327,14 +367,13 @@ class SettingIcon extends React.Component { variant='link' eventKey='0' className='p-0 fs-7 text-uppercase'> - Buy & Sell Configurations + Buy Configurations
-
-

Buy

+
@@ -372,6 +411,8 @@ class SettingIcon extends React.Component { +
+
+
+
+ +
+
+
+
+
@@ -424,6 +481,8 @@ class SettingIcon extends React.Component { onChange={this.handleInputChange} /> +
+
@@ -463,7 +522,8 @@ class SettingIcon extends React.Component { onChange={this.handleInputChange} /> - +
+
@@ -504,8 +564,232 @@ class SettingIcon extends React.Component { />
-
-

Sell

+
+ + + + + + Buy Restriction with ATH (All Time High) + + + + +
+
+ + + + + ATH Buy Restriction Enabled{' '} + + + If enabled, the bot will + retrieve ATH (All Time High) + price of the coin based on the + interval/candle configuration. + If the buy trigger price is + higher than ATH buy restriction + price, which is calculated by + ATH Restriction price + percentage, the bot will not + place a buy order. The bot will + place an order when the trigger + price is lower than ATH buy + restriction price. + + + }> + + + + + +
+
+ + + Interval + + + Set candle interval for + calculating the ATH (All The High) + price. + + + }> + + + + + + + + + + + + + + + +
+
+ + + Limit + + + Set the number of candles to + retrieve for calculating the ATH + (All The High) price. + + + }> + + + + + +
+
+ + + Restriction price percentage{' '} + + + Set the percentage to calculate + restriction price. i.e. if set{' '} + 0.9 and the ATH(All + Time High) price $110 + , restriction price will be{' '} + $99 for stop limit + order. + + + }> + + + + + +
+
+
+
+
+
+ + + + + + + + + + Sell Configurations + + + + +
+
@@ -543,6 +827,8 @@ class SettingIcon extends React.Component { +
+
@@ -583,6 +869,8 @@ class SettingIcon extends React.Component { onChange={this.handleInputChange} /> +
+
@@ -622,6 +910,8 @@ class SettingIcon extends React.Component { onChange={this.handleInputChange} /> +
+
@@ -661,332 +951,171 @@ class SettingIcon extends React.Component { onChange={this.handleInputChange} /> -

Sell - Stop-Loss

- - - - - Stop-Loss Enabled{' '} - - - If enabled, the bot will sell the coin - when it reaches the configured amount of - the loss from the last buy price. You - can enable this feature to prevent the - loss more than expected. - - - }> - - - - - - - - Max loss percentage{' '} - - - Set maximum loss percentage for stop-loss. - i.e. if set 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. - - - }> - - - - - - - - Temporary disable for buying (minutes){' '} - - - Set for how long to disable buying in - minutes after placing a stop-loss order. - i.e. if set 360, the bot will - temporarily disable buying for 6 hours. - - - }> - - - - - -
-
-
-
-
-
- - - - - - ATH (All Time High) Buy Restriction - - - - -
-
- - - - - ATH Buy Restriction Enabled{' '} - - - If enabled, the bot will retrieve ATH - (All Time High) price of the coin based - on the interval/candle configuration. If - the buy trigger price is higher than ATH - buy restriction price, which is - calculated by ATH Restriction price - percentage, the bot will not place a buy - order. The bot will place an order when - the trigger price is lower than ATH buy - restriction price. - - - }> - - - - - -
-
-
-
- - - Interval - - - Set candle interval for calculating the - ATH (All The High) price. - - - }> - - - - - - - - - - - - - - - -
-
- - - Limit - - - Set the number of candles to retrieve for - calculating the ATH (All The High) price. - - - }> - - - - -
-
-
- - - Restriction price percentage{' '} - - - Set the percentage to calculate - restriction price. i.e. if set{' '} - 0.9 and the ATH(All Time - High) price $110, restriction - price will be $99 for stop - limit order. - - - }> - - - - - + eventKey='0' + className='p-0 fs-7 text-uppercase'> + Sell Stop-Loss + + + + +
+
+ + + + + Stop-Loss Enabled{' '} + + + If enabled, the bot will + sell the coin when it + reaches the configured + amount of the loss from the + last buy price. You can + enable this feature to + prevent the loss more than + expected. + + + }> + + + + + +
+
+ + + Max loss percentage{' '} + + + Set maximum loss percentage + for stop-loss. i.e. if set{' '} + 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. + + + }> + + + + + +
+
+ + + Temporary disable for buying (minutes){' '} + + + Set for how long to disable + buying in minutes after + placing a stop-loss order. + i.e. if set 360, + the bot will temporarily + disable buying for 6 hours. + + + }> + + + + + +
+
+
+
+ +
diff --git a/public/js/SettingIconLastBuyPriceRemoveThreshold.js b/public/js/SettingIconLastBuyPriceRemoveThreshold.js new file mode 100644 index 00000000..c2a1c823 --- /dev/null +++ b/public/js/SettingIconLastBuyPriceRemoveThreshold.js @@ -0,0 +1,125 @@ +/* eslint-disable no-unused-vars */ +/* eslint-disable react/jsx-no-undef */ +/* eslint-disable no-undef */ +class SettingIconLastBuyPriceRemoveThreshold extends React.Component { + constructor(props) { + super(props); + + this.state = { + lastBuyPriceRemoveThresholds: {} + }; + + this.handleInputChange = this.handleInputChange.bind(this); + } + + componentDidUpdate(nextProps) { + // Only update configuration, when the modal is closed and different. + if ( + _.isEmpty(nextProps.lastBuyPriceRemoveThresholds) === false && + _.isEqual( + nextProps.lastBuyPriceRemoveThresholds, + this.state.lastBuyPriceRemoveThresholds + ) === false + ) { + const { lastBuyPriceRemoveThresholds } = nextProps; + console.log('lastBuyPriceRemoveThreshold has changed', { + lastBuyPriceRemoveThresholds + }); + this.setState({ + lastBuyPriceRemoveThresholds + }); + } + } + + handleInputChange(event) { + const target = event.target; + const value = + target.type === 'checkbox' + ? target.checked + : target.type === 'number' + ? +target.value + : target.value; + const stateKey = target.getAttribute('data-state-key'); + + const { lastBuyPriceRemoveThresholds } = this.state; + + console.log( + '_.set(lastBuyPriceRemoveThresholds, stateKey, value) => ', + _.set(lastBuyPriceRemoveThresholds, stateKey, value) + ); + + const newLastBuyPriceRemoveThresholds = _.set( + lastBuyPriceRemoveThresholds, + stateKey, + value + ); + this.setState({ + lastBuyPriceRemoveThresholds: newLastBuyPriceRemoveThresholds + }); + + this.props.handleLastBuyPriceRemoveThresholdChange( + lastBuyPriceRemoveThresholds + ); + } + + render() { + const { quoteAssets } = this.props; + const { lastBuyPriceRemoveThresholds } = this.state; + + if (_.isEmpty(lastBuyPriceRemoveThresholds)) { + return ''; + } + + return quoteAssets.map((quoteAsset, index) => { + return ( +
+ + + Remove last buy price for {quoteAsset} when the estimated value is + lower than{' '} + + + Set the last buy price removal threshold for symbols with + quote asset "{quoteAsset}". When the estimated value drops + below the threshold, the bot will remove the last buy + price. The threshold will be applied to the symbols which + end with "{quoteAsset}" if not configured in the symbol + configuration. + + + }> + + + + + +
+ ); + }); + } +} diff --git a/public/js/SymbolSettingIcon.js b/public/js/SymbolSettingIcon.js index 60da987e..966d2645 100644 --- a/public/js/SymbolSettingIcon.js +++ b/public/js/SymbolSettingIcon.js @@ -234,13 +234,13 @@ class SymbolSettingIcon extends React.Component { variant='link' eventKey='0' className='p-0 fs-7 text-uppercase'> - Buy & Sell Configurations + Buy Configurations
-
+

Buy

+
+
@@ -320,6 +322,55 @@ class SymbolSettingIcon extends React.Component { onChange={this.handleInputChange} /> +
+
+ + + Remove last buy price when the estimated value is + lower than{' '} + + + Set the last buy price removal threshold. + When the estimated value drops below the + threshold, the bot will remove the last + buy price. + + + }> + + + + + +
+
+
+
+
@@ -363,6 +414,8 @@ class SymbolSettingIcon extends React.Component { onChange={this.handleInputChange} /> +
+
@@ -402,7 +455,8 @@ class SymbolSettingIcon extends React.Component { onChange={this.handleInputChange} /> - +
+
@@ -443,8 +497,233 @@ class SymbolSettingIcon extends React.Component { />
-
-

Sell

+
+ + + + + + Buy Restriction with ATH (All Time High) + + + + +
+
+ + + + + ATH Buy Restriction Enabled{' '} + + + If enabled, the bot will + retrieve ATH (All Time High) + price of the coin based on the + interval/candle configuration. + If the buy trigger price is + higher than ATH buy restriction + price, which is calculated by + ATH Restriction price + percentage, the bot will not + place a buy order. The bot will + place an order when the trigger + price is lower than ATH buy + restriction price. + + + }> + + + + + +
+ +
+ + + Interval + + + Set candle interval for + calculating the ATH (All The High) + price. + + + }> + + + + + + + + + + + + + + + +
+
+ + + Limit + + + Set the number of candles to + retrieve for calculating the ATH + (All The High) price. + + + }> + + + + + +
+
+ + + Restriction price percentage{' '} + + + Set the percentage to calculate + restriction price. i.e. if set{' '} + 0.9 and the ATH(All + Time High) price $110 + , restriction price will be{' '} + $99 for stop limit + order. + + + }> + + + + + +
+
+
+
+
+
+ + + + + + + + + + Sell Configurations + + + + +
+
@@ -482,6 +761,8 @@ class SymbolSettingIcon extends React.Component { +
+
@@ -522,6 +803,8 @@ class SymbolSettingIcon extends React.Component { onChange={this.handleInputChange} /> +
+
@@ -561,6 +844,8 @@ class SymbolSettingIcon extends React.Component { onChange={this.handleInputChange} /> +
+
@@ -600,338 +885,171 @@ class SymbolSettingIcon extends React.Component { onChange={this.handleInputChange} /> -

Sell - Stop-Loss

- - - - - Stop-Loss Enabled{' '} - - - If enabled, the bot will sell the coin - when it reaches the configured amount of - the loss from the last buy price. You - can enable this feature to prevent the - loss more than expected. - - - }> - - - - - - - - Max loss percentage{' '} - - - Set maximum loss percentage for stop-loss. - i.e. if set 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. - - - }> - - - - - - - - Temporary disable for buying (minutes){' '} - - - Set for how long to disable buying in - minutes after placing a stop-loss order. - i.e. if set 360, the bot will - temporarily disable buying for 6 hours. - - - }> - - - - - -
-
-
-
-
-
- - - - - - ATH (All Time High) Buy Restriction - - - - -
-
- - - - - ATH Buy Restriction Enabled{' '} - - - If enabled, the bot will retrieve ATH - (All Time High) price of the coin based - on the interval/candle configuration. If - the buy trigger price is higher than ATH - buy restriction price, which is - calculated by ATH Restriction price - percentage, the bot will not place a buy - order. The bot will place an order when - the trigger price is lower than ATH buy - restriction price. - - - }> - - - - - -
-
- -
-
- - - Interval - - - Set candle interval for calculating the - ATH (All The High) price. - - - }> - - - - - - - - - - - - - - - -
-
- - - Limit - - - Set the number of candles to retrieve for - calculating the ATH (All The High) price. - - - }> - - - - -
-
-
- - - Restriction price percentage{' '} - - - Set the percentage to calculate - restriction price. i.e. if set{' '} - 0.9 and the ATH(All Time - High) price $110, restriction - price will be $99 for stop - limit order. - - - }> - - - - - + eventKey='0' + className='p-0 fs-7 text-uppercase'> + Sell Stop-Loss + + + + +
+
+ + + + + Stop-Loss Enabled{' '} + + + If enabled, the bot will + sell the coin when it + reaches the configured + amount of the loss from the + last buy price. You can + enable this feature to + prevent the loss more than + expected. + + + }> + + + + + +
+
+ + + Max loss percentage{' '} + + + Set maximum loss percentage + for stop-loss. i.e. if set{' '} + 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. + + + }> + + + + + +
+
+ + + Temporary disable for buying (minutes){' '} + + + Set for how long to disable + buying in minutes after + placing a stop-loss order. + i.e. if set 360, + the bot will temporarily + disable buying for 6 hours. + + + }> + + + + + +
+
+
+
+ +