From 6de39869b8b635e20519fa709cf6dc566bf9fe01 Mon Sep 17 00:00:00 2001 From: pedrohusky Date: Sat, 29 May 2021 18:26:11 -0300 Subject: [PATCH 01/14] Last buy now have been reworked to work like max purchase amount. --- .vs/slnx.sqlite | Bin 0 -> 118784 bytes .../step/remove-last-buy-price.js | 24 +++- .../trailingTradeHelper/configuration.js | 84 +++++++++++++- config/default.json | 2 + public/index.html | 4 + public/js/SettingIcon.js | 58 ++++++++-- public/js/SettingIconLastBuyThreshold.js | 109 ++++++++++++++++++ public/js/SymbolSettingIcon.js | 37 ++++++ 8 files changed, 305 insertions(+), 13 deletions(-) create mode 100644 .vs/slnx.sqlite create mode 100644 public/js/SettingIconLastBuyThreshold.js diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..2e0ae4c3cfc4d5ec9683e2795fda0d6c8a43903e GIT binary patch literal 118784 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 { const cachedLastBuyOrder = JSON.parse(await cache.get(`${symbol}-last-buy-order`)) || {}; @@ -169,15 +171,30 @@ const execute = async (logger, rawData) => { return data; } - if (baseAssetQuantity * currentPrice < parseFloat(minNotional)) { +/** + * Get symbol configuration + * + * @param {*} logger + * @param {*} rawData + */ + + //Get symbol config + const symbolConfiguration = await getConfiguration(logger, symbol); + + //Define variable + var lastBuyThreshold = symbolConfiguration.buy.lastBuyThreshold; + //Caculated coin value + var priceCalculated = baseAssetQuantity * currentPrice; + + if (priceCalculated < parseFloat(minNotional) && priceCalculated < lastBuyThreshold) { // Final check for open orders refreshedOpenOrders = await getAndCacheOpenOrdersForSymbol(logger, symbol); if (refreshedOpenOrders.length > 0) { logger.info('Do not remove last buy price. Found open orders.'); return data; } - - processMessage = + + processMessage = 'Balance is less than the notional value. Delete last buy price.'; logger.error({ baseAssetQuantity }, processMessage); @@ -192,6 +209,7 @@ const execute = async (logger, rawData) => { minNotional, openOrders }); + return data; } diff --git a/app/cronjob/trailingTradeHelper/configuration.js b/app/cronjob/trailingTradeHelper/configuration.js index 04c1d612..b97a3a40 100644 --- a/app/cronjob/trailingTradeHelper/configuration.js +++ b/app/cronjob/trailingTradeHelper/configuration.js @@ -202,6 +202,74 @@ const getMaxPurchaseAmount = async ( return newBuyMaxPurchaseAmount; }; + +const getLastBuyThreshold = async ( + logger, + symbol, + globalConfiguration, + symbolConfiguration +) => { + const symbolBuyLastBuyThreshold = _.get( + symbolConfiguration, + 'buy.lastBuyThreshold', + 10 + ); + + if (symbolBuyLastBuyThreshold !== 10) { + logger.info( + { symbolBuyLastBuyThreshold }, + 'Last buy threshold is found from symbol configuration.' + ); + return symbolBuyLastBuyThreshold; + } + + logger.info( + { symbolBuyLastBuyThreshold }, + 'Last Buy Threshold is set as 10. Need to calculate and override it' + ); + + let newBuyLastBuyThreshold = 10; + + // If old max purchase maount is -1, then should calculate maximum purchase amount 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; + + newBuyLastBuyThreshold = _.get( + globalConfiguration, + `buy.maxPurchaseAmounts.${quoteAsset}`, + 10 + ); + + logger.info( + { quoteAsset, newBuyLastBuyThreshold }, + 'Retreived max purchase amount from global configuration' + ); + + if (newBuyLastBuyThreshold === 10) { + newBuyLastBuyThreshold = parseFloat(minNotional); + + logger.info( + { newBuyLastBuyThreshold, minNotional }, + 'Could not get max purchase amount from global configuration. Use minimum notional from symbol info' + ); + } + } else { + logger.info( + { cachedSymbolInfo }, + 'Could not find symbol info, wait to be cached.' + ); + } + + return newBuyLastBuyThreshold; +}; /** * Get global/symbol configuration * @@ -230,8 +298,20 @@ const getConfiguration = async (logger, symbol = null) => { ) ); - // For symbol configuration, remove maxPurchaseAmounts - _.unset(mergedConfigValue, 'buy.maxPurchaseAmounts'); + _.set( + mergedConfigValue, + 'buy.lastBuyThreshold', + await getLastBuyThreshold( + logger, + symbol, + globalConfigValue, + symbolConfigValue + ) + ); + + // For symbol configuration, remove maxPurchaseAmounts + _.unset(mergedConfigValue, 'buy.maxPurchaseAmounts'); + _.unset(mergedConfigValue, 'buy.lastBuyThresholds'); } // Merge global and symbol configuration diff --git a/config/default.json b/config/default.json index acdb83ca..34224fb2 100644 --- a/config/default.json +++ b/config/default.json @@ -50,6 +50,8 @@ }, "buy": { "enabled": true, + "lastBuyThreshold": 10, + "lastBuyThresholds": {}, "maxPurchaseAmount": -1, "maxPurchaseAmounts": {}, "triggerPercentage": 1.0, diff --git a/public/index.html b/public/index.html index 62e56689..7ce28397 100644 --- a/public/index.html +++ b/public/index.html @@ -118,6 +118,10 @@ type="text/babel" src="./js/SettingIconMaxPurchaseAmount.js" > + diff --git a/public/js/SettingIcon.js b/public/js/SettingIcon.js index 2b2ebe4d..78f0250a 100644 --- a/public/js/SettingIcon.js +++ b/public/js/SettingIcon.js @@ -26,9 +26,12 @@ class SettingIcon extends React.Component { this.handleMaxPurchaeAmountChange = this.handleMaxPurchaeAmountChange.bind( this ); + this.handleLastBuyThresholdChange = this.handleLastBuyThresholdChange.bind( + this + ); } - getQuoteAssets(exchangeSymbols, selectedSymbols, maxPurchaseAmounts) { + getQuoteAssets(exchangeSymbols, selectedSymbols, maxPurchaseAmounts, lastBuyThresholds) { const quoteAssets = []; selectedSymbols.forEach(symbol => { @@ -43,9 +46,12 @@ class SettingIcon extends React.Component { if (maxPurchaseAmounts[quoteAsset] === undefined) { maxPurchaseAmounts[quoteAsset] = minNotional * 10; } + if (lastBuyThresholds[quoteAsset] == undefined) { + lastBuyThresholds[quoteAsset] = minNotional; + } }); - return { quoteAssets, maxPurchaseAmounts }; + return { quoteAssets, maxPurchaseAmounts, lastBuyThresholds }; } componentDidUpdate(nextProps) { @@ -70,15 +76,20 @@ class SettingIcon extends React.Component { if (configuration.buy.maxPurchaseAmounts === undefined) { configuration.buy.maxPurchaseAmounts = {}; } + if (configuration.buy.lastBuyThresholds === undefined) { + configuration.buy.lastBuyThresholds = {}; + } - // Set max purchase amount - const { quoteAssets, maxPurchaseAmounts } = this.getQuoteAssets( + // Set max purchase amount + const { quoteAssets, maxPurchaseAmounts, lastBuyThresholds } = this.getQuoteAssets( exchangeSymbols, selectedSymbols, - configuration.buy.maxPurchaseAmounts + configuration.buy.maxPurchaseAmounts, + configuration.buy.lastBuyThresholds ); configuration.buy.maxPurchaseAmounts = maxPurchaseAmounts; + configuration.buy.lastBuyThresholds = lastBuyThresholds; this.setState({ availableSymbols, @@ -145,6 +156,20 @@ class SettingIcon extends React.Component { }); } + handleLastBuyThresholdChange(newlastBuyThresholds) { + console.log('handleLastBuyThresholdChange => ', newlastBuyThresholds); + + const { configuration } = this.state; + + this.setState({ + configuration: _.set( + configuration, + 'buy.lastBuyThresholds', + newlastBuyThresholds + ) + }); + } + render() { const { configuration, availableSymbols, quoteAssets } = this.state; const { symbols: selectedSymbols } = configuration; @@ -203,14 +228,17 @@ class SettingIcon extends React.Component { const { quoteAssets, - maxPurchaseAmounts + maxPurchaseAmounts, + lastBuyThresholds } = this.getQuoteAssets( exchangeSymbols, selected, - configuration.buy.maxPurchaseAmounts + configuration.buy.maxPurchaseAmounts, + configuration.buy.lastBuyThresholds, ); - + configuration.buy.maxPurchaseAmounts = maxPurchaseAmounts; + configuration.buy.lastBuyThresholds = lastBuyThresholds; this.setState({ configuration, quoteAssets }); }} size='sm' @@ -381,6 +409,9 @@ class SettingIcon extends React.Component { + + + + + + diff --git a/public/js/SettingIconLastBuyThreshold.js b/public/js/SettingIconLastBuyThreshold.js new file mode 100644 index 00000000..6fe28501 --- /dev/null +++ b/public/js/SettingIconLastBuyThreshold.js @@ -0,0 +1,109 @@ +/* eslint-disable no-unused-vars */ +/* eslint-disable react/jsx-no-undef */ +/* eslint-disable no-undef */ +class SettingIconLastBuyThreshold extends React.Component { + constructor(props) { + super(props); + + this.state = { + lastBuyThresholds: {} + }; + + this.handleInputChange = this.handleInputChange.bind(this); + } + + componentDidUpdate(nextProps) { + // Only update configuration, when the modal is closed and different. + if ( + _.isEmpty(nextProps.lastBuyThresholds) === false && + _.isEqual(nextProps.lastBuyThresholds, this.state.lastBuyThresholds) === + false + ) { + const { lastBuyThresholds } = nextProps; + console.log('lastBuyThreshold has changed', { lastBuyThresholds }); + this.setState({ + lastBuyThresholds + }); + } + } + + 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 { lastBuyThresholds } = this.state; + + console.log( + '_.set(lastBuyThresholds, stateKey, value) => ', + _.set(lastBuyThresholds, stateKey, value) + ); + + const newlastBuyThreshold = _.set(lastBuyThresholds, stateKey, value); + this.setState({ + lastBuyThresholds: newlastBuyThreshold + }); + + this.props.handleLastBuyThresholdChange(lastBuyThresholds); + } + + render() { + const { quoteAssets } = this.props; + const { lastBuyThresholds } = this.state; + + if (_.isEmpty(lastBuyThresholds)) { + return ''; + } + + return quoteAssets.map((quoteAsset, index) => { + return ( +
+ + + Last Buy Threshold for {quoteAsset}{' '} + + + Set the last buy threshold for symbols with quote asset " + {quoteAsset}". The last buy threshold will be applied to + the symbols which ends with "{quoteAsset}" if not + configured the symbol configuration. + + + }> + + + + + +
+ ); + }); + } +} diff --git a/public/js/SymbolSettingIcon.js b/public/js/SymbolSettingIcon.js index 7152afaa..53c909ec 100644 --- a/public/js/SymbolSettingIcon.js +++ b/public/js/SymbolSettingIcon.js @@ -283,6 +283,43 @@ class SymbolSettingIcon extends React.Component {
+ + + + Remove last buy when under{' '} + + + If the coin value drops below the defined amount, the bot will remove the last buy price. Define 0 to never remove last buy price. + + + }> + + + + + + From 474ee2451a836e22fe7b5c9f681dbe91672bbd9d Mon Sep 17 00:00:00 2001 From: pedrohusky Date: Sat, 29 May 2021 19:07:58 -0300 Subject: [PATCH 02/14] didn't see the need to calculate minimum amount there. --- .vs/VSWorkspaceState.json | 13 +++++++++++++ .vs/slnx.sqlite | Bin 118784 -> 118784 bytes .../step/remove-last-buy-price.js | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 .vs/VSWorkspaceState.json diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json new file mode 100644 index 00000000..a52dd036 --- /dev/null +++ b/.vs/VSWorkspaceState.json @@ -0,0 +1,13 @@ +{ + "ExpandedNodes": [ + "", + "\\app", + "\\app\\cronjob", + "\\app\\cronjob\\trailingTrade", + "\\app\\cronjob\\trailingTrade\\step", + "\\public", + "\\public\\js" + ], + "SelectedNode": "\\app\\cronjob\\trailingTrade\\step\\remove-last-buy-price.js", + "PreviewInSolutionExplorer": false +} \ No newline at end of file diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite index 2e0ae4c3cfc4d5ec9683e2795fda0d6c8a43903e..726a2a1ab812150aba6265e82d30d0baa74d97a8 100644 GIT binary patch delta 323 zcmZozz}~QceS$P2$3z)tMvjdMOZWxYcs4Nbzu@1`znnLY*P3U;WSIX_@JzMTsSu`FX*qMP-@Esnxt(3JOY41x}@zIVmWrij}MsqGNTy zih@fLi%LohplaPRb5fCIolA>~Qu9iX73n9J0JZ0p=H&2l)he>GFgOdkq$ZW7ha?uI zrVr&dPCZjO^(C?v|p#lXP8#-G8!e~Z6^{|NsI{tW*2n*|j@ d_#Nb#of+}?g$*Q+#|GBs753X#*fT~<002!&YXblP delta 74 zcmV-Q0JZ;spa+1U2ap>929X>?0S2*Pqz?=P4PF2b=nuaSrwv}S5g<+tvtAvc5)lLn g0000452yeS+7HAJs1NV65x|%agQ!2Zs6PQ%kP>MYqyPW_ diff --git a/app/cronjob/trailingTrade/step/remove-last-buy-price.js b/app/cronjob/trailingTrade/step/remove-last-buy-price.js index eb65b9d3..31bc59c1 100644 --- a/app/cronjob/trailingTrade/step/remove-last-buy-price.js +++ b/app/cronjob/trailingTrade/step/remove-last-buy-price.js @@ -186,7 +186,7 @@ const execute = async (logger, rawData) => { //Caculated coin value var priceCalculated = baseAssetQuantity * currentPrice; - if (priceCalculated < parseFloat(minNotional) && priceCalculated < lastBuyThreshold) { + if (priceCalculated < lastBuyThreshold) { // Final check for open orders refreshedOpenOrders = await getAndCacheOpenOrdersForSymbol(logger, symbol); if (refreshedOpenOrders.length > 0) { From 7534773643cb7fb9f297148c7b0311736f1a66d1 Mon Sep 17 00:00:00 2001 From: pedrohusky Date: Sun, 30 May 2021 16:28:37 -0300 Subject: [PATCH 03/14] Done almost every comment. -Indenting -Renaming to lastBuyPriceRemoveThreshold -Fixing ${maxPurchaseAmount} where it shouldn't be -Setting -1 to lastBuyPriceRemoveThreshold default value. -Removing non-sense comments that I've made --- .gitignore | 2 + .vs/VSWorkspaceState.json | 13 ----- .vs/slnx.sqlite | Bin 118784 -> 0 bytes .../step/remove-last-buy-price.js | 25 +++------ .../trailingTradeHelper/configuration.js | 40 +++++++------- config/default.json | 4 +- public/index.html | 2 +- public/js/SettingIcon.js | 52 +++++++++--------- ...SettingIconLastBuyPriceRemoveThreshold.js} | 34 ++++++------ public/js/SymbolSettingIcon.js | 10 ++-- 10 files changed, 80 insertions(+), 102 deletions(-) delete mode 100644 .vs/VSWorkspaceState.json delete mode 100644 .vs/slnx.sqlite rename public/js/{SettingIconLastBuyThreshold.js => SettingIconLastBuyPriceRemoveThreshold.js} (68%) diff --git a/.gitignore b/.gitignore index 6f028a33..3ea2bb2d 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ data log .env +.vs +.vscode \ No newline at end of file diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json deleted file mode 100644 index a52dd036..00000000 --- a/.vs/VSWorkspaceState.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "ExpandedNodes": [ - "", - "\\app", - "\\app\\cronjob", - "\\app\\cronjob\\trailingTrade", - "\\app\\cronjob\\trailingTrade\\step", - "\\public", - "\\public\\js" - ], - "SelectedNode": "\\app\\cronjob\\trailingTrade\\step\\remove-last-buy-price.js", - "PreviewInSolutionExplorer": false -} \ No newline at end of file diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite deleted file mode 100644 index 726a2a1ab812150aba6265e82d30d0baa74d97a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118784 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 { return data; } -/** - * Get symbol configuration - * - * @param {*} logger - * @param {*} rawData - */ - - //Get symbol config - const symbolConfiguration = await getConfiguration(logger, symbol); - - //Define variable - var lastBuyThreshold = symbolConfiguration.buy.lastBuyThreshold; - //Caculated coin value - var priceCalculated = baseAssetQuantity * currentPrice; - - if (priceCalculated < lastBuyThreshold) { + //Get symbol config and define last buy remove price threshold, then get the calculated price. + const symbolConfiguration = rawData.symbolConfiguration; + const lastBuyPriceRemoveThreshold = symbolConfiguration.buy.lastBuyPriceRemoveThreshold; + const priceCalculated = baseAssetQuantity * currentPrice; + + if (priceCalculated < lastBuyPriceRemoveThreshold) { // Final check for open orders refreshedOpenOrders = await getAndCacheOpenOrdersForSymbol(logger, symbol); if (refreshedOpenOrders.length > 0) { logger.info('Do not remove last buy price. Found open orders.'); return data; } - + processMessage = 'Balance is less than the notional value. Delete last buy price.'; @@ -209,7 +199,6 @@ const execute = async (logger, rawData) => { minNotional, openOrders }); - return data; } diff --git a/app/cronjob/trailingTradeHelper/configuration.js b/app/cronjob/trailingTradeHelper/configuration.js index b97a3a40..70beb635 100644 --- a/app/cronjob/trailingTradeHelper/configuration.js +++ b/app/cronjob/trailingTradeHelper/configuration.js @@ -203,32 +203,32 @@ const getMaxPurchaseAmount = async ( return newBuyMaxPurchaseAmount; }; -const getLastBuyThreshold = async ( +const getLastBuyPriceRemoveThreshold = async ( logger, symbol, globalConfiguration, symbolConfiguration ) => { - const symbolBuyLastBuyThreshold = _.get( + const symbolBuyLastBuyPriceRemoveThreshold = _.get( symbolConfiguration, - 'buy.lastBuyThreshold', - 10 + 'buy.lastBuyPriceRemoveThreshold', + -1 ); - if (symbolBuyLastBuyThreshold !== 10) { + if (symbolBuyLastBuyPriceRemoveThreshold !== -1) { logger.info( - { symbolBuyLastBuyThreshold }, + { symbolBuyLastBuyPriceRemoveThreshold }, 'Last buy threshold is found from symbol configuration.' ); - return symbolBuyLastBuyThreshold; + return symbolBuyLastBuyPriceRemoveThreshold; } logger.info( - { symbolBuyLastBuyThreshold }, + { symbolBuyLastBuyPriceRemoveThreshold }, 'Last Buy Threshold is set as 10. Need to calculate and override it' ); - let newBuyLastBuyThreshold = 10; + let newBuyLastBuyPriceRemoveThreshold = -1; // If old max purchase maount is -1, then should calculate maximum purchase amount based on the notional amount. const cachedSymbolInfo = @@ -242,22 +242,22 @@ const getLastBuyThreshold = async ( filterMinNotional: { minNotional } } = cachedSymbolInfo; - newBuyLastBuyThreshold = _.get( + newBuyLastBuyPriceRemoveThreshold = _.get( globalConfiguration, - `buy.maxPurchaseAmounts.${quoteAsset}`, - 10 + `buy.lastBuyPriceRemoveThresholds.${quoteAsset}`, + -1 ); logger.info( - { quoteAsset, newBuyLastBuyThreshold }, + { quoteAsset, newBuyLastBuyPriceRemoveThreshold }, 'Retreived max purchase amount from global configuration' ); - if (newBuyLastBuyThreshold === 10) { - newBuyLastBuyThreshold = parseFloat(minNotional); + if (newBuyLastBuyPriceRemoveThreshold === -1) { + newBuyLastBuyPriceRemoveThreshold = parseFloat(minNotional); logger.info( - { newBuyLastBuyThreshold, minNotional }, + { newBuyLastBuyPriceRemoveThreshold, minNotional }, 'Could not get max purchase amount from global configuration. Use minimum notional from symbol info' ); } @@ -268,7 +268,7 @@ const getLastBuyThreshold = async ( ); } - return newBuyLastBuyThreshold; + return newBuyLastBuyPriceRemoveThreshold; }; /** * Get global/symbol configuration @@ -300,8 +300,8 @@ const getConfiguration = async (logger, symbol = null) => { _.set( mergedConfigValue, - 'buy.lastBuyThreshold', - await getLastBuyThreshold( + 'buy.lastBuyPriceRemoveThreshold', + await getLastBuyPriceRemoveThreshold( logger, symbol, globalConfigValue, @@ -311,7 +311,7 @@ const getConfiguration = async (logger, symbol = null) => { // For symbol configuration, remove maxPurchaseAmounts _.unset(mergedConfigValue, 'buy.maxPurchaseAmounts'); - _.unset(mergedConfigValue, 'buy.lastBuyThresholds'); + _.unset(mergedConfigValue, 'buy.lastBuyPriceRemoveThresholds'); } // Merge global and symbol configuration diff --git a/config/default.json b/config/default.json index 34224fb2..bfcec5da 100644 --- a/config/default.json +++ b/config/default.json @@ -50,8 +50,8 @@ }, "buy": { "enabled": true, - "lastBuyThreshold": 10, - "lastBuyThresholds": {}, + "lastBuyPriceRemoveThreshold": -1, + "lastBuyPriceRemoveThresholds": {}, "maxPurchaseAmount": -1, "maxPurchaseAmounts": {}, "triggerPercentage": 1.0, diff --git a/public/index.html b/public/index.html index 7ce28397..b28c2b7b 100644 --- a/public/index.html +++ b/public/index.html @@ -120,7 +120,7 @@ > diff --git a/public/js/SettingIcon.js b/public/js/SettingIcon.js index 78f0250a..fb8b1a19 100644 --- a/public/js/SettingIcon.js +++ b/public/js/SettingIcon.js @@ -26,12 +26,12 @@ class SettingIcon extends React.Component { this.handleMaxPurchaeAmountChange = this.handleMaxPurchaeAmountChange.bind( this ); - this.handleLastBuyThresholdChange = this.handleLastBuyThresholdChange.bind( + this.handleLastBuyPriceRemoveThresholdChange = this.handleLastBuyPriceRemoveThresholdChange.bind( this ); } - getQuoteAssets(exchangeSymbols, selectedSymbols, maxPurchaseAmounts, lastBuyThresholds) { + getQuoteAssets(exchangeSymbols, selectedSymbols, maxPurchaseAmounts, lastBuyPriceRemoveThresholds) { const quoteAssets = []; selectedSymbols.forEach(symbol => { @@ -46,12 +46,12 @@ class SettingIcon extends React.Component { if (maxPurchaseAmounts[quoteAsset] === undefined) { maxPurchaseAmounts[quoteAsset] = minNotional * 10; } - if (lastBuyThresholds[quoteAsset] == undefined) { - lastBuyThresholds[quoteAsset] = minNotional; + if (lastBuyPriceRemoveThresholds[quoteAsset] == undefined) { + lastBuyPriceRemoveThresholds[quoteAsset] = minNotional; } }); - return { quoteAssets, maxPurchaseAmounts, lastBuyThresholds }; + return { quoteAssets, maxPurchaseAmounts, lastBuyPriceRemoveThresholds }; } componentDidUpdate(nextProps) { @@ -76,20 +76,20 @@ class SettingIcon extends React.Component { if (configuration.buy.maxPurchaseAmounts === undefined) { configuration.buy.maxPurchaseAmounts = {}; } - if (configuration.buy.lastBuyThresholds === undefined) { - configuration.buy.lastBuyThresholds = {}; + if (configuration.buy.lastBuyPriceRemoveThresholds === undefined) { + configuration.buy.lastBuyPriceRemoveThresholds = {}; } // Set max purchase amount - const { quoteAssets, maxPurchaseAmounts, lastBuyThresholds } = this.getQuoteAssets( + const { quoteAssets, maxPurchaseAmounts, lastBuyPriceRemoveThresholds } = this.getQuoteAssets( exchangeSymbols, selectedSymbols, configuration.buy.maxPurchaseAmounts, - configuration.buy.lastBuyThresholds + configuration.buy.lastBuyPriceRemoveThresholds ); configuration.buy.maxPurchaseAmounts = maxPurchaseAmounts; - configuration.buy.lastBuyThresholds = lastBuyThresholds; + configuration.buy.lastBuyPriceRemoveThresholds = lastBuyPriceRemoveThresholds; this.setState({ availableSymbols, @@ -156,16 +156,16 @@ class SettingIcon extends React.Component { }); } - handleLastBuyThresholdChange(newlastBuyThresholds) { - console.log('handleLastBuyThresholdChange => ', newlastBuyThresholds); + handleLastBuyPriceRemoveThresholdChange(newLastBuyPriceRemoveThresholds) { + console.log('handleLastBuyPriceRemoveThresholdChange => ', newLastBuyPriceRemoveThresholds); const { configuration } = this.state; this.setState({ configuration: _.set( configuration, - 'buy.lastBuyThresholds', - newlastBuyThresholds + 'buy.lastBuyPriceRemoveThresholds', + newLastBuyPriceRemoveThresholds ) }); } @@ -229,16 +229,16 @@ class SettingIcon extends React.Component { const { quoteAssets, maxPurchaseAmounts, - lastBuyThresholds + lastBuyPriceRemoveThresholds } = this.getQuoteAssets( exchangeSymbols, selected, configuration.buy.maxPurchaseAmounts, - configuration.buy.lastBuyThresholds, + configuration.buy.lastBuyPriceRemoveThresholds, ); - + configuration.buy.maxPurchaseAmounts = maxPurchaseAmounts; - configuration.buy.lastBuyThresholds = lastBuyThresholds; + configuration.buy.lastBuyPriceRemoveThresholds = lastBuyPriceRemoveThresholds; this.setState({ configuration, quoteAssets }); }} size='sm' @@ -409,9 +409,9 @@ class SettingIcon extends React.Component { - - - + + + - diff --git a/public/js/SettingIconLastBuyThreshold.js b/public/js/SettingIconLastBuyPriceRemoveThreshold.js similarity index 68% rename from public/js/SettingIconLastBuyThreshold.js rename to public/js/SettingIconLastBuyPriceRemoveThreshold.js index 6fe28501..58073fe7 100644 --- a/public/js/SettingIconLastBuyThreshold.js +++ b/public/js/SettingIconLastBuyPriceRemoveThreshold.js @@ -1,12 +1,12 @@ /* eslint-disable no-unused-vars */ /* eslint-disable react/jsx-no-undef */ /* eslint-disable no-undef */ -class SettingIconLastBuyThreshold extends React.Component { +class SettingIconLastBuyPriceRemoveThreshold extends React.Component { constructor(props) { super(props); this.state = { - lastBuyThresholds: {} + lastBuyPriceRemoveThresholds: {} }; this.handleInputChange = this.handleInputChange.bind(this); @@ -15,14 +15,14 @@ class SettingIconLastBuyThreshold extends React.Component { componentDidUpdate(nextProps) { // Only update configuration, when the modal is closed and different. if ( - _.isEmpty(nextProps.lastBuyThresholds) === false && - _.isEqual(nextProps.lastBuyThresholds, this.state.lastBuyThresholds) === + _.isEmpty(nextProps.lastBuyPriceRemoveThresholds) === false && + _.isEqual(nextProps.lastBuyPriceRemoveThresholds, this.state.lastBuyPriceRemoveThresholds) === false ) { - const { lastBuyThresholds } = nextProps; - console.log('lastBuyThreshold has changed', { lastBuyThresholds }); + const { lastBuyPriceRemoveThresholds } = nextProps; + console.log('lastBuyPriceRemoveThreshold has changed', { lastBuyPriceRemoveThresholds }); this.setState({ - lastBuyThresholds + lastBuyPriceRemoveThresholds }); } } @@ -37,26 +37,26 @@ class SettingIconLastBuyThreshold extends React.Component { : target.value; const stateKey = target.getAttribute('data-state-key'); - const { lastBuyThresholds } = this.state; + const { lastBuyPriceRemoveThresholds } = this.state; console.log( - '_.set(lastBuyThresholds, stateKey, value) => ', - _.set(lastBuyThresholds, stateKey, value) + '_.set(lastBuyPriceRemoveThresholds, stateKey, value) => ', + _.set(lastBuyPriceRemoveThresholds, stateKey, value) ); - const newlastBuyThreshold = _.set(lastBuyThresholds, stateKey, value); + const newLastBuyPriceRemoveThreshold = _.set(lastBuyPriceRemoveThresholds, stateKey, value); this.setState({ - lastBuyThresholds: newlastBuyThreshold + lastBuyPriceRemoveThresholds: newLastBuyPriceRemoveThreshold }); - this.props.handleLastBuyThresholdChange(lastBuyThresholds); + this.props.handleLastBuyPriceRemoveThresholdChange(lastBuyPriceRemoveThresholds); } render() { const { quoteAssets } = this.props; - const { lastBuyThresholds } = this.state; + const { lastBuyPriceRemoveThresholds } = this.state; - if (_.isEmpty(lastBuyThresholds)) { + if (_.isEmpty(lastBuyPriceRemoveThresholds)) { return ''; } @@ -66,7 +66,7 @@ class SettingIconLastBuyThreshold extends React.Component { key={quoteAsset + '-' + index} className='coin-info-last-buy-threshold-wrapper'> Last Buy Threshold for {quoteAsset}{' '} @@ -98,7 +98,7 @@ class SettingIconLastBuyThreshold extends React.Component { min='1' step='0.0001' data-state-key={quoteAsset} - value={lastBuyThresholds[quoteAsset]} + value={lastBuyPriceRemoveThresholds[quoteAsset]} onChange={this.handleInputChange} /> diff --git a/public/js/SymbolSettingIcon.js b/public/js/SymbolSettingIcon.js index 53c909ec..d1d0006c 100644 --- a/public/js/SymbolSettingIcon.js +++ b/public/js/SymbolSettingIcon.js @@ -283,7 +283,7 @@ class SymbolSettingIcon extends React.Component { - + @@ -296,7 +296,7 @@ class SymbolSettingIcon extends React.Component { overlay={ - If the coin value drops below the defined amount, the bot will remove the last buy price. Define 0 to never remove last buy price. + If the coin value drops below the defined amount, the bot will remove the last buy price. Define 0 to never remove last buy price. }> @@ -314,12 +314,12 @@ class SymbolSettingIcon extends React.Component { required min='0.0001' step='0.0001' - data-state-key='buy.lastBuyThreshold' - value={symbolConfiguration.buy.lastBuyThreshold} + data-state-key='buy.lastBuyPriceRemoveThreshold' + value={symbolConfiguration.buy.lastBuyPriceRemoveThreshold} onChange={this.handleInputChange} /> - + From 4acd231b7d348a336e5ae92734750e739c4250fa Mon Sep 17 00:00:00 2001 From: pedrohusky Date: Tue, 1 Jun 2021 22:36:07 -0300 Subject: [PATCH 04/14] - Fixed comments, - description, - last buy remove threshold now appears in symbol settings (frontend arrow), -Using destructing assignment, -removed .vscode from gitignore, sorry --- .gitignore | 1 - .../trailingTrade/step/determine-action.js | 18 ++-- .../step/remove-last-buy-price.js | 26 +++--- .../trailingTradeHelper/configuration.js | 92 +++++++++---------- public/js/CoinWrapperSetting.js | 12 ++- public/js/SettingIcon.js | 12 +-- .../SettingIconLastBuyPriceRemoveThreshold.js | 41 +++++---- public/js/SymbolSettingIcon.js | 17 ++-- 8 files changed, 114 insertions(+), 105 deletions(-) diff --git a/.gitignore b/.gitignore index 3ea2bb2d..93a5990e 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,3 @@ log .env .vs -.vscode \ No newline at end of file diff --git a/app/cronjob/trailingTrade/step/determine-action.js b/app/cronjob/trailingTrade/step/determine-action.js index 16bfffc3..a27c107d 100644 --- a/app/cronjob/trailingTrade/step/determine-action.js +++ b/app/cronjob/trailingTrade/step/determine-action.js @@ -176,9 +176,9 @@ const execute = async (logger, rawData) => { data, 'wait', `The current price reached the trigger price. ` + - `But you have enough ${baseAsset} to sell. ` + - `Set the last buy price to start selling. ` + - `Do not process buy.` + `But you have enough ${baseAsset} to sell. ` + + `Set the last buy price to start selling. ` + + `Do not process buy.` ); } @@ -193,8 +193,8 @@ const execute = async (logger, rawData) => { data, 'buy-temporary-disabled', 'The current price reached the trigger price. ' + - `However, the action is temporarily disabled by ${checkDisable.disabledBy}. ` + - `Resume buy process after ${checkDisable.ttl}s.` + `However, the action is temporarily disabled by ${checkDisable.disabledBy}. ` + + `Resume buy process after ${checkDisable.ttl}s.` ); } @@ -223,8 +223,8 @@ const execute = async (logger, rawData) => { data, 'sell-temporary-disabled', 'The current price is reached the sell trigger price. ' + - `However, the action is temporarily disabled by ${checkDisable.disabledBy}. ` + - `Resume sell process after ${checkDisable.ttl}s.` + `However, the action is temporarily disabled by ${checkDisable.disabledBy}. ` + + `Resume sell process after ${checkDisable.ttl}s.` ); } // Then sell @@ -247,8 +247,8 @@ const execute = async (logger, rawData) => { data, 'sell-temporary-disabled', 'The current price is reached the stop-loss price. ' + - `However, the action is temporarily disabled by ${checkDisable.disabledBy}. ` + - `Resume sell process after ${checkDisable.ttl}s.` + `However, the action is temporarily disabled by ${checkDisable.disabledBy}. ` + + `Resume sell process after ${checkDisable.ttl}s.` ); } // Then sell market order diff --git a/app/cronjob/trailingTrade/step/remove-last-buy-price.js b/app/cronjob/trailingTrade/step/remove-last-buy-price.js index f01ad755..86eb9eaa 100644 --- a/app/cronjob/trailingTrade/step/remove-last-buy-price.js +++ b/app/cronjob/trailingTrade/step/remove-last-buy-price.js @@ -6,8 +6,6 @@ const { getAPILimit, isActionDisabled } = require('../../trailingTradeHelper/common'); -const { getConfiguration } = require('../../trailingTradeHelper/configuration'); - /** * Retrieve last buy order from cache * @@ -47,12 +45,12 @@ const removeLastBuyPrice = async ( `${symbol} Action (${moment().format( 'HH:mm:ss.SSS' )}): Removed last buy price\n` + - `- Message: ${processMessage}\n\`\`\`${JSON.stringify( - extraMessages, - undefined, - 2 - )}\`\`\`\n` + - `- Current API Usage: ${getAPILimit(logger)}` + `- Message: ${processMessage}\n\`\`\`${JSON.stringify( + extraMessages, + undefined, + 2 + )}\`\`\`\n` + + `- Current API Usage: ${getAPILimit(logger)}` ); }; @@ -69,6 +67,9 @@ const execute = async (logger, rawData) => { isLocked, action, symbol, + symbolConfiguration: { + buy: { lastBuyPriceRemoveThreshold } + }, symbolInfo: { filterLotSize: { stepSize, minQty }, filterMinNotional: { minNotional } @@ -171,12 +172,7 @@ const execute = async (logger, rawData) => { return data; } - //Get symbol config and define last buy remove price threshold, then get the calculated price. - const symbolConfiguration = rawData.symbolConfiguration; - const lastBuyPriceRemoveThreshold = symbolConfiguration.buy.lastBuyPriceRemoveThreshold; - const priceCalculated = baseAssetQuantity * currentPrice; - - if (priceCalculated < lastBuyPriceRemoveThreshold) { + if (baseAssetQuantity * currentPrice < lastBuyPriceRemoveThreshold) { // Final check for open orders refreshedOpenOrders = await getAndCacheOpenOrdersForSymbol(logger, symbol); if (refreshedOpenOrders.length > 0) { @@ -184,7 +180,7 @@ const execute = async (logger, rawData) => { return data; } - processMessage = + processMessage = 'Balance is less than the notional value. Delete last buy price.'; logger.error({ baseAssetQuantity }, processMessage); diff --git a/app/cronjob/trailingTradeHelper/configuration.js b/app/cronjob/trailingTradeHelper/configuration.js index 70beb635..3462c61b 100644 --- a/app/cronjob/trailingTradeHelper/configuration.js +++ b/app/cronjob/trailingTradeHelper/configuration.js @@ -182,7 +182,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) { @@ -215,58 +215,58 @@ const getLastBuyPriceRemoveThreshold = async ( -1 ); - if (symbolBuyLastBuyPriceRemoveThreshold !== -1) { + if (symbolBuyLastBuyPriceRemoveThreshold !== -1) { logger.info( - { symbolBuyLastBuyPriceRemoveThreshold }, + { symbolBuyLastBuyPriceRemoveThreshold }, 'Last buy threshold is found from symbol configuration.' ); - return symbolBuyLastBuyPriceRemoveThreshold; + return symbolBuyLastBuyPriceRemoveThreshold; } logger.info( - { symbolBuyLastBuyPriceRemoveThreshold }, - 'Last Buy Threshold is set as 10. Need to calculate and override it' + { symbolBuyLastBuyPriceRemoveThreshold }, + 'Last Buy Price Remove Threshold is set as -1. Need to calculate and override it' ); let newBuyLastBuyPriceRemoveThreshold = -1; - // If old max purchase maount is -1, then should calculate maximum purchase amount 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 }, - 'Retreived max purchase amount from global configuration' - ); - - if (newBuyLastBuyPriceRemoveThreshold === -1) { - newBuyLastBuyPriceRemoveThreshold = parseFloat(minNotional); - - logger.info( - { newBuyLastBuyPriceRemoveThreshold, minNotional }, - 'Could not get max purchase amount from global configuration. Use minimum notional from symbol info' - ); - } - } else { - logger.info( - { cachedSymbolInfo }, - 'Could not find symbol info, wait to be cached.' - ); + // If old last buy price remove threshold is -1, then should calculate maximum purchase amount 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; }; @@ -301,7 +301,7 @@ const getConfiguration = async (logger, symbol = null) => { _.set( mergedConfigValue, 'buy.lastBuyPriceRemoveThreshold', - await getLastBuyPriceRemoveThreshold( + await getLastBuyPriceRemoveThreshold( logger, symbol, globalConfigValue, @@ -309,9 +309,9 @@ const getConfiguration = async (logger, symbol = null) => { ) ); - // For symbol configuration, remove maxPurchaseAmounts - _.unset(mergedConfigValue, 'buy.maxPurchaseAmounts'); - _.unset(mergedConfigValue, 'buy.lastBuyPriceRemoveThresholds'); + // For symbol configuration, remove maxPurchaseAmounts + _.unset(mergedConfigValue, 'buy.maxPurchaseAmounts'); + _.unset(mergedConfigValue, 'buy.lastBuyPriceRemoveThresholds'); } // Merge global and symbol configuration diff --git a/public/js/CoinWrapperSetting.js b/public/js/CoinWrapperSetting.js index 47415d4a..6c9550c2 100644 --- a/public/js/CoinWrapperSetting.js +++ b/public/js/CoinWrapperSetting.js @@ -38,9 +38,8 @@ class CoinWrapperSetting extends React.Component { className='btn btn-sm btn-link p-0 ml-1' onClick={this.toggleCollapse}> + className={`fa ${collapsed ? 'fa-arrow-right' : 'fa-arrow-down' + }`}>
+
+ Last Buy Price Remove Threshold: + + {symbolConfiguration.buy.lastBuyPriceRemoveThreshold} {quoteAsset} + +
+
Max purchase amount: diff --git a/public/js/SettingIcon.js b/public/js/SettingIcon.js index fb8b1a19..f41c0478 100644 --- a/public/js/SettingIcon.js +++ b/public/js/SettingIcon.js @@ -46,7 +46,7 @@ class SettingIcon extends React.Component { if (maxPurchaseAmounts[quoteAsset] === undefined) { maxPurchaseAmounts[quoteAsset] = minNotional * 10; } - if (lastBuyPriceRemoveThresholds[quoteAsset] == undefined) { + if (lastBuyPriceRemoveThresholds[quoteAsset] === undefined) { lastBuyPriceRemoveThresholds[quoteAsset] = minNotional; } }); @@ -80,8 +80,8 @@ class SettingIcon extends React.Component { configuration.buy.lastBuyPriceRemoveThresholds = {}; } - // Set max purchase amount - const { quoteAssets, maxPurchaseAmounts, lastBuyPriceRemoveThresholds } = this.getQuoteAssets( + // Set max purchase amount + const { quoteAssets, maxPurchaseAmounts, lastBuyPriceRemoveThresholds } = this.getQuoteAssets( exchangeSymbols, selectedSymbols, configuration.buy.maxPurchaseAmounts, @@ -131,8 +131,8 @@ class SettingIcon extends React.Component { target.type === 'checkbox' ? target.checked : target.type === 'number' - ? +target.value - : target.value; + ? +target.value + : target.value; const stateKey = target.getAttribute('data-state-key'); const { configuration } = this.state; @@ -157,7 +157,7 @@ class SettingIcon extends React.Component { } handleLastBuyPriceRemoveThresholdChange(newLastBuyPriceRemoveThresholds) { - console.log('handleLastBuyPriceRemoveThresholdChange => ', newLastBuyPriceRemoveThresholds); + console.log('handleLastBuyPriceRemoveThresholdChange => ', newLastBuyPriceRemoveThresholds); const { configuration } = this.state; diff --git a/public/js/SettingIconLastBuyPriceRemoveThreshold.js b/public/js/SettingIconLastBuyPriceRemoveThreshold.js index 58073fe7..ee6bf6da 100644 --- a/public/js/SettingIconLastBuyPriceRemoveThreshold.js +++ b/public/js/SettingIconLastBuyPriceRemoveThreshold.js @@ -15,12 +15,12 @@ class SettingIconLastBuyPriceRemoveThreshold extends React.Component { componentDidUpdate(nextProps) { // Only update configuration, when the modal is closed and different. if ( - _.isEmpty(nextProps.lastBuyPriceRemoveThresholds) === false && - _.isEqual(nextProps.lastBuyPriceRemoveThresholds, this.state.lastBuyPriceRemoveThresholds) === - false + _.isEmpty(nextProps.lastBuyPriceRemoveThresholds) === false && + _.isEqual(nextProps.lastBuyPriceRemoveThresholds, this.state.lastBuyPriceRemoveThresholds) === + false ) { - const { lastBuyPriceRemoveThresholds } = nextProps; - console.log('lastBuyPriceRemoveThreshold has changed', { lastBuyPriceRemoveThresholds }); + const { lastBuyPriceRemoveThresholds } = nextProps; + console.log('lastBuyPriceRemoveThreshold has changed', { lastBuyPriceRemoveThresholds }); this.setState({ lastBuyPriceRemoveThresholds }); @@ -33,23 +33,23 @@ class SettingIconLastBuyPriceRemoveThreshold extends React.Component { target.type === 'checkbox' ? target.checked : target.type === 'number' - ? +target.value - : target.value; + ? +target.value + : target.value; const stateKey = target.getAttribute('data-state-key'); - const { lastBuyPriceRemoveThresholds } = this.state; + const { lastBuyPriceRemoveThresholds } = this.state; console.log( '_.set(lastBuyPriceRemoveThresholds, stateKey, value) => ', - _.set(lastBuyPriceRemoveThresholds, stateKey, value) + _.set(lastBuyPriceRemoveThresholds, stateKey, value) ); - const newLastBuyPriceRemoveThreshold = _.set(lastBuyPriceRemoveThresholds, stateKey, value); + const newLastBuyPriceRemoveThresholds = _.set(lastBuyPriceRemoveThresholds, stateKey, value); this.setState({ - lastBuyPriceRemoveThresholds: newLastBuyPriceRemoveThreshold + lastBuyPriceRemoveThresholds: newLastBuyPriceRemoveThresholds }); - this.props.handleLastBuyPriceRemoveThresholdChange(lastBuyPriceRemoveThresholds); + this.props.handleLastBuyPriceRemoveThresholdChange(lastBuyPriceRemoveThresholds); } render() { @@ -64,24 +64,29 @@ class SettingIconLastBuyPriceRemoveThreshold extends React.Component { return (
+ className='coin-info-last-buy-remove-threshold-wrapper'> - Last Buy Threshold for {quoteAsset}{' '} + Last Buy Price Remove Threshold for {quoteAsset}{' '} + id={'last-buy-remove-threshold-overlay-right' + quoteAsset}> Set the last buy threshold for symbols with quote asset " {quoteAsset}". The last buy threshold will be applied to the symbols which ends with "{quoteAsset}" if not configured the symbol configuration. + + When price of coin drops below the threshold the bot will + remove the last buy price. If the bot didn't sell the coin, + you can manually add the last buy price. But if the price is + still below the threshold, it will remove it again! }> @@ -95,7 +100,7 @@ class SettingIconLastBuyPriceRemoveThreshold extends React.Component { type='number' placeholder={'Enter last buy threshold for ' + quoteAsset} required - min='1' + min='0.0001' step='0.0001' data-state-key={quoteAsset} value={lastBuyPriceRemoveThresholds[quoteAsset]} diff --git a/public/js/SymbolSettingIcon.js b/public/js/SymbolSettingIcon.js index d1d0006c..fb821e95 100644 --- a/public/js/SymbolSettingIcon.js +++ b/public/js/SymbolSettingIcon.js @@ -77,8 +77,8 @@ class SymbolSettingIcon extends React.Component { target.type === 'checkbox' ? target.checked : target.type === 'number' - ? +target.value - : target.value; + ? +target.value + : target.value; const stateKey = target.getAttribute('data-state-key'); const { symbolConfiguration } = this.state; @@ -284,19 +284,22 @@ class SymbolSettingIcon extends React.Component { - Remove last buy when under{' '} + - If the coin value drops below the defined amount, the bot will remove the last buy price. Define 0 to never remove last buy price. + When price of coin drops below the threshold the bot will + remove the last buy price. If the bot didn't sell the coin, + you can manually add the last buy price. But if the price is + still below the threshold, it will remove it again! }> From 88fabcc1534561be9dd3cef0200ad8c08eae16ad Mon Sep 17 00:00:00 2001 From: pedrohusky Date: Tue, 8 Jun 2021 13:57:51 -0300 Subject: [PATCH 05/14] Can sell now calculates last buy remove threshold; Now last buy remove threshold appears in symbol 'configuration arrow' in frontend; Sorry for the wait. --- app/cronjob/trailingTrade/step/determine-action.js | 6 +++++- public/js/CoinWrapperSetting.js | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/cronjob/trailingTrade/step/determine-action.js b/app/cronjob/trailingTrade/step/determine-action.js index a27c107d..b40bf402 100644 --- a/app/cronjob/trailingTrade/step/determine-action.js +++ b/app/cronjob/trailingTrade/step/determine-action.js @@ -65,13 +65,17 @@ const canSell = data => { symbolInfo: { filterMinNotional: { minNotional } }, + symbolConfiguration: { + buy: { lastBuyPriceRemoveThreshold } + }, baseAssetBalance: { total: baseAssetTotalBalance }, sell: { currentPrice: sellCurrentPrice, lastBuyPrice } } = data; return ( lastBuyPrice > 0 && - baseAssetTotalBalance * sellCurrentPrice > parseFloat(minNotional) + baseAssetTotalBalance * sellCurrentPrice > parseFloat(minNotional) && + baseAssetTotalBalance * sellCurrentPrice > lastBuyPriceRemoveThreshold ); }; diff --git a/public/js/CoinWrapperSetting.js b/public/js/CoinWrapperSetting.js index 6c9550c2..c4a54ddb 100644 --- a/public/js/CoinWrapperSetting.js +++ b/public/js/CoinWrapperSetting.js @@ -74,7 +74,7 @@ class CoinWrapperSetting extends React.Component {
- Last Buy Price Remove Threshold: + Last Buy Price Threshold: {symbolConfiguration.buy.lastBuyPriceRemoveThreshold} {quoteAsset} From c5e657ea05fb6bae34fa3a53a96bff6f11f2839e Mon Sep 17 00:00:00 2001 From: pedrohusky Date: Tue, 8 Jun 2021 14:35:45 -0300 Subject: [PATCH 06/14] fix --- app/cronjob/trailingTradeHelper/configuration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/cronjob/trailingTradeHelper/configuration.js b/app/cronjob/trailingTradeHelper/configuration.js index 3462c61b..1545367b 100644 --- a/app/cronjob/trailingTradeHelper/configuration.js +++ b/app/cronjob/trailingTradeHelper/configuration.js @@ -230,7 +230,7 @@ const getLastBuyPriceRemoveThreshold = async ( let newBuyLastBuyPriceRemoveThreshold = -1; - // If old last buy price remove threshold is -1, then should calculate maximum purchase amount based on the notional amount. + // 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`) From bec5b638ae8dddcc173e3544a1ec098748407613 Mon Sep 17 00:00:00 2001 From: pedrohusky Date: Tue, 8 Jun 2021 14:42:09 -0300 Subject: [PATCH 07/14] fixed comment --- app/cronjob/trailingTradeHelper/configuration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/cronjob/trailingTradeHelper/configuration.js b/app/cronjob/trailingTradeHelper/configuration.js index 405c0c69..77eb7040 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`) From bcd6082ef70f10796db5274533b72b0aeeeff20e Mon Sep 17 00:00:00 2001 From: pedrohusky Date: Wed, 9 Jun 2021 14:08:11 -0300 Subject: [PATCH 08/14] FIX save global settings not applying to last buy threshold. --- app/frontend/websocket/handlers/setting-update.js | 1 + 1 file changed, 1 insertion(+) 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'); From 80805b2dc1aca65614608f45e28af71766e506b8 Mon Sep 17 00:00:00 2001 From: Chris Lee Date: Mon, 5 Jul 2021 21:43:15 +1000 Subject: [PATCH 09/14] Fixed lint --- .../trailingTrade/step/determine-action.js | 18 +++---- .../step/remove-last-buy-price.js | 12 ++--- package-lock.json | 12 ++--- public/js/CoinWrapperSetting.js | 8 +-- public/js/SettingIcon.js | 52 +++++++++++-------- .../SettingIconLastBuyPriceRemoveThreshold.js | 40 +++++++++----- public/js/SymbolSettingIcon.js | 19 ++++--- 7 files changed, 94 insertions(+), 67 deletions(-) diff --git a/app/cronjob/trailingTrade/step/determine-action.js b/app/cronjob/trailingTrade/step/determine-action.js index ee11ea97..cd6738f0 100644 --- a/app/cronjob/trailingTrade/step/determine-action.js +++ b/app/cronjob/trailingTrade/step/determine-action.js @@ -206,9 +206,9 @@ const execute = async (logger, rawData) => { data, 'wait', `The current price reached the trigger price. ` + - `But you have enough ${baseAsset} to sell. ` + - `Set the last buy price to start selling. ` + - `Do not process buy.` + `But you have enough ${baseAsset} to sell. ` + + `Set the last buy price to start selling. ` + + `Do not process buy.` ); } @@ -223,8 +223,8 @@ const execute = async (logger, rawData) => { data, 'buy-temporary-disabled', 'The current price reached the trigger price. ' + - `However, the action is temporarily disabled by ${checkDisable.disabledBy}. ` + - `Resume buy process after ${checkDisable.ttl}s.` + `However, the action is temporarily disabled by ${checkDisable.disabledBy}. ` + + `Resume buy process after ${checkDisable.ttl}s.` ); } @@ -262,8 +262,8 @@ const execute = async (logger, rawData) => { data, 'sell-temporary-disabled', 'The current price is reached the sell trigger price. ' + - `However, the action is temporarily disabled by ${checkDisable.disabledBy}. ` + - `Resume sell process after ${checkDisable.ttl}s.` + `However, the action is temporarily disabled by ${checkDisable.disabledBy}. ` + + `Resume sell process after ${checkDisable.ttl}s.` ); } // Then sell @@ -286,8 +286,8 @@ const execute = async (logger, rawData) => { data, 'sell-temporary-disabled', 'The current price is reached the stop-loss price. ' + - `However, the action is temporarily disabled by ${checkDisable.disabledBy}. ` + - `Resume sell process after ${checkDisable.ttl}s.` + `However, the action is temporarily disabled by ${checkDisable.disabledBy}. ` + + `Resume sell process after ${checkDisable.ttl}s.` ); } // Then sell market order diff --git a/app/cronjob/trailingTrade/step/remove-last-buy-price.js b/app/cronjob/trailingTrade/step/remove-last-buy-price.js index 86eb9eaa..c08acb56 100644 --- a/app/cronjob/trailingTrade/step/remove-last-buy-price.js +++ b/app/cronjob/trailingTrade/step/remove-last-buy-price.js @@ -45,12 +45,12 @@ const removeLastBuyPrice = async ( `${symbol} Action (${moment().format( 'HH:mm:ss.SSS' )}): Removed last buy price\n` + - `- Message: ${processMessage}\n\`\`\`${JSON.stringify( - extraMessages, - undefined, - 2 - )}\`\`\`\n` + - `- Current API Usage: ${getAPILimit(logger)}` + `- Message: ${processMessage}\n\`\`\`${JSON.stringify( + extraMessages, + undefined, + 2 + )}\`\`\`\n` + + `- Current API Usage: ${getAPILimit(logger)}` ); }; 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/js/CoinWrapperSetting.js b/public/js/CoinWrapperSetting.js index c4a54ddb..b52ee094 100644 --- a/public/js/CoinWrapperSetting.js +++ b/public/js/CoinWrapperSetting.js @@ -38,8 +38,9 @@ class CoinWrapperSetting extends React.Component { className='btn btn-sm btn-link p-0 ml-1' onClick={this.toggleCollapse}> + className={`fa ${ + collapsed ? 'fa-arrow-right' : 'fa-arrow-down' + }`}>
Last Buy Price Threshold: - {symbolConfiguration.buy.lastBuyPriceRemoveThreshold} {quoteAsset} + {symbolConfiguration.buy.lastBuyPriceRemoveThreshold}{' '} + {quoteAsset}
diff --git a/public/js/SettingIcon.js b/public/js/SettingIcon.js index 19474d0c..2104e49f 100644 --- a/public/js/SettingIcon.js +++ b/public/js/SettingIcon.js @@ -23,15 +23,18 @@ class SettingIcon extends React.Component { this.handleFormSubmit = this.handleFormSubmit.bind(this); this.handleInputChange = this.handleInputChange.bind(this); - this.handleMaxPurchaeAmountChange = this.handleMaxPurchaeAmountChange.bind( - this - ); - this.handleLastBuyPriceRemoveThresholdChange = this.handleLastBuyPriceRemoveThresholdChange.bind( - this - ); + this.handleMaxPurchaeAmountChange = + this.handleMaxPurchaeAmountChange.bind(this); + this.handleLastBuyPriceRemoveThresholdChange = + this.handleLastBuyPriceRemoveThresholdChange.bind(this); } - getQuoteAssets(exchangeSymbols, selectedSymbols, maxPurchaseAmounts, lastBuyPriceRemoveThresholds) { + getQuoteAssets( + exchangeSymbols, + selectedSymbols, + maxPurchaseAmounts, + lastBuyPriceRemoveThresholds + ) { const quoteAssets = []; selectedSymbols.forEach(symbol => { @@ -81,15 +84,17 @@ class SettingIcon extends React.Component { } // Set max purchase amount - const { quoteAssets, maxPurchaseAmounts, lastBuyPriceRemoveThresholds } = this.getQuoteAssets( - exchangeSymbols, - selectedSymbols, - configuration.buy.maxPurchaseAmounts, - configuration.buy.lastBuyPriceRemoveThresholds - ); + const { quoteAssets, maxPurchaseAmounts, lastBuyPriceRemoveThresholds } = + this.getQuoteAssets( + exchangeSymbols, + selectedSymbols, + configuration.buy.maxPurchaseAmounts, + configuration.buy.lastBuyPriceRemoveThresholds + ); configuration.buy.maxPurchaseAmounts = maxPurchaseAmounts; - configuration.buy.lastBuyPriceRemoveThresholds = lastBuyPriceRemoveThresholds; + configuration.buy.lastBuyPriceRemoveThresholds = + lastBuyPriceRemoveThresholds; this.setState({ availableSymbols, @@ -126,8 +131,8 @@ class SettingIcon extends React.Component { target.type === 'checkbox' ? target.checked : target.type === 'number' - ? +target.value - : target.value; + ? +target.value + : target.value; const stateKey = target.getAttribute('data-state-key'); const { configuration } = this.state; @@ -150,7 +155,10 @@ class SettingIcon extends React.Component { } handleLastBuyPriceRemoveThresholdChange(newLastBuyPriceRemoveThresholds) { - console.log('handleLastBuyPriceRemoveThresholdChange => ', newLastBuyPriceRemoveThresholds); + console.log( + 'handleLastBuyPriceRemoveThresholdChange => ', + newLastBuyPriceRemoveThresholds + ); const { configuration } = this.state; @@ -227,11 +235,13 @@ class SettingIcon extends React.Component { exchangeSymbols, selected, configuration.buy.maxPurchaseAmounts, - configuration.buy.lastBuyPriceRemoveThresholds, + configuration.buy.lastBuyPriceRemoveThresholds ); - configuration.buy.maxPurchaseAmounts = maxPurchaseAmounts; - configuration.buy.lastBuyPriceRemoveThresholds = lastBuyPriceRemoveThresholds; + configuration.buy.maxPurchaseAmounts = + maxPurchaseAmounts; + configuration.buy.lastBuyPriceRemoveThresholds = + lastBuyPriceRemoveThresholds; this.setState({ configuration, quoteAssets }); }} size='sm' @@ -403,8 +413,6 @@ class SettingIcon extends React.Component { - - Last Buy Price Remove Threshold for {quoteAsset}{' '} @@ -81,12 +94,11 @@ class SettingIconLastBuyPriceRemoveThreshold extends React.Component { Set the last buy threshold for symbols with quote asset " {quoteAsset}". The last buy threshold will be applied to the symbols which ends with "{quoteAsset}" if not - configured the symbol configuration. - - When price of coin drops below the threshold the bot will - remove the last buy price. If the bot didn't sell the coin, - you can manually add the last buy price. But if the price is - still below the threshold, it will remove it again! + configured the symbol configuration. When price of coin + drops below the threshold the bot will remove the last buy + price. If the bot didn't sell the coin, you can manually + add the last buy price. But if the price is still below + the threshold, it will remove it again! }> diff --git a/public/js/SymbolSettingIcon.js b/public/js/SymbolSettingIcon.js index 3c340b27..0f2c384d 100644 --- a/public/js/SymbolSettingIcon.js +++ b/public/js/SymbolSettingIcon.js @@ -72,8 +72,8 @@ class SymbolSettingIcon extends React.Component { target.type === 'checkbox' ? target.checked : target.type === 'number' - ? +target.value - : target.value; + ? +target.value + : target.value; const stateKey = target.getAttribute('data-state-key'); const { symbolConfiguration } = this.state; @@ -292,10 +292,12 @@ class SymbolSettingIcon extends React.Component { overlay={ - When price of coin drops below the threshold the bot will - remove the last buy price. If the bot didn't sell the coin, - you can manually add the last buy price. But if the price is - still below the threshold, it will remove it again! + When price of coin drops below the + threshold the bot will remove the last buy + price. If the bot didn't sell the coin, + you can manually add the last buy price. + But if the price is still below the + threshold, it will remove it again! }> @@ -314,7 +316,10 @@ class SymbolSettingIcon extends React.Component { min='0.0001' step='0.0001' data-state-key='buy.lastBuyPriceRemoveThreshold' - value={symbolConfiguration.buy.lastBuyPriceRemoveThreshold} + value={ + symbolConfiguration.buy + .lastBuyPriceRemoveThreshold + } onChange={this.handleInputChange} /> From 0fba006c1adbdc574c10b8966da4c213ae941278 Mon Sep 17 00:00:00 2001 From: Chris Lee Date: Mon, 5 Jul 2021 22:15:27 +1000 Subject: [PATCH 10/14] Updated Setting UI --- public/js/CoinWrapperSetting.js | 12 +- public/js/SettingIcon.js | 741 ++++++++++-------- .../SettingIconLastBuyPriceRemoveThreshold.js | 17 +- public/js/SymbolSettingIcon.js | 64 +- 4 files changed, 453 insertions(+), 381 deletions(-) diff --git a/public/js/CoinWrapperSetting.js b/public/js/CoinWrapperSetting.js index b52ee094..08659586 100644 --- a/public/js/CoinWrapperSetting.js +++ b/public/js/CoinWrapperSetting.js @@ -75,17 +75,19 @@ class CoinWrapperSetting extends React.Component {
- Last Buy Price Threshold: + Max purchase amount: - {symbolConfiguration.buy.lastBuyPriceRemoveThreshold}{' '} - {quoteAsset} + {symbolConfiguration.buy.maxPurchaseAmount} {quoteAsset}
- Max purchase amount: + + Remove last buy price under: + - {symbolConfiguration.buy.maxPurchaseAmount} {quoteAsset} + {symbolConfiguration.buy.lastBuyPriceRemoveThreshold}{' '} + {quoteAsset}
diff --git a/public/js/SettingIcon.js b/public/js/SettingIcon.js index 2104e49f..d3231447 100644 --- a/public/js/SettingIcon.js +++ b/public/js/SettingIcon.js @@ -367,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

+
@@ -412,7 +411,8 @@ class SettingIcon extends React.Component { - +
+
- +
+
- +
+
+
+
+
@@ -476,6 +481,8 @@ class SettingIcon extends React.Component { onChange={this.handleInputChange} /> +
+
@@ -515,7 +522,8 @@ class SettingIcon extends React.Component { onChange={this.handleInputChange} /> - +
+
@@ -556,8 +564,236 @@ 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 + + + + +
+
@@ -595,6 +831,8 @@ class SettingIcon extends React.Component { +
+
@@ -635,6 +873,8 @@ class SettingIcon extends React.Component { onChange={this.handleInputChange} /> +
+
@@ -674,6 +914,8 @@ class SettingIcon extends React.Component { onChange={this.handleInputChange} /> +
+
@@ -713,334 +955,163 @@ 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. - - - }> - - - - -
+ + + + + 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 index 3c348f58..c2a1c823 100644 --- a/public/js/SettingIconLastBuyPriceRemoveThreshold.js +++ b/public/js/SettingIconLastBuyPriceRemoveThreshold.js @@ -82,7 +82,8 @@ class SettingIconLastBuyPriceRemoveThreshold extends React.Component { } className='mb-2'> - Last Buy Price Remove Threshold for {quoteAsset}{' '} + Remove last buy price for {quoteAsset} when the estimated value is + lower than{' '} - Set the last buy threshold for symbols with quote asset " - {quoteAsset}". The last buy threshold will be applied to - the symbols which ends with "{quoteAsset}" if not - configured the symbol configuration. When price of coin - drops below the threshold the bot will remove the last buy - price. If the bot didn't sell the coin, you can manually - add the last buy price. But if the price is still below - the threshold, it will remove it again! + 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 0f2c384d..bb0c2560 100644 --- a/public/js/SymbolSettingIcon.js +++ b/public/js/SymbolSettingIcon.js @@ -281,23 +281,24 @@ class SymbolSettingIcon extends React.Component { - Remove last buy when under{' '} + Maximum purchase amount{' '} + - When price of coin drops below the - threshold the bot will remove the last buy - price. If the bot didn't sell the coin, - you can manually add the last buy price. - But if the price is still below the - threshold, it will remove it again! + Set maximum purchase amount. i.e. if + account has 200 USDT and set as{' '} + 100, then when reach buy + price, it will only buy 100{' '} + worth of the coin. Note that the bot will + remove the last buy price if the coin is + less worth than $10. }> @@ -311,38 +312,33 @@ class SymbolSettingIcon extends React.Component { - Maximum purchase amount{' '} + Remove last buy price when the estimated value is + lower than{' '} + - Set maximum purchase amount. i.e. if - account has 200 USDT and set as{' '} - 100, then when reach buy - price, it will only buy 100{' '} - worth of the coin. Note that the bot will - remove the last buy price if the coin is - less worth than $10. + Set the last buy price removal threshold. + When the estimated value drops below the + threshold, the bot will remove the last + buy price. }> @@ -356,15 +352,19 @@ class SymbolSettingIcon extends React.Component { + From cb98a86c2e5bd5af76bf25e5ed92c309ec46b9b0 Mon Sep 17 00:00:00 2001 From: Chris Lee Date: Mon, 5 Jul 2021 22:37:27 +1000 Subject: [PATCH 11/14] Updated Setting UI --- public/js/SettingIcon.js | 330 ++++++++------- public/js/SymbolSettingIcon.js | 747 ++++++++++++++++++--------------- 2 files changed, 578 insertions(+), 499 deletions(-) diff --git a/public/js/SettingIcon.js b/public/js/SettingIcon.js index d3231447..debd2bfb 100644 --- a/public/js/SettingIcon.js +++ b/public/js/SettingIcon.js @@ -629,9 +629,7 @@ class SettingIcon extends React.Component {
-
-
-
+
@@ -679,7 +677,7 @@ class SettingIcon extends React.Component {
-
+
@@ -721,9 +719,7 @@ class SettingIcon extends React.Component { />
-
-
-
+
@@ -956,162 +952,172 @@ class SettingIcon extends React.Component { />
+
+ + + + + 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. + + + }> + + + + + +
+
+
+
+
+
+
- - - - - 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/SymbolSettingIcon.js b/public/js/SymbolSettingIcon.js index bb0c2560..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

- +
+
@@ -321,7 +322,8 @@ class SymbolSettingIcon extends React.Component { onChange={this.handleInputChange} /> - +
+
@@ -364,7 +366,11 @@ class SymbolSettingIcon extends React.Component { onChange={this.handleInputChange} /> - +
+
+
+
+
@@ -408,6 +414,8 @@ class SymbolSettingIcon extends React.Component { onChange={this.handleInputChange} /> +
+
@@ -447,7 +455,8 @@ class SymbolSettingIcon extends React.Component { onChange={this.handleInputChange} /> - +
+
@@ -488,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 + + + + +
+
@@ -527,6 +761,8 @@ class SymbolSettingIcon extends React.Component { +
+
@@ -567,6 +803,8 @@ class SymbolSettingIcon extends React.Component { onChange={this.handleInputChange} /> +
+
@@ -606,6 +844,8 @@ class SymbolSettingIcon extends React.Component { onChange={this.handleInputChange} /> +
+
@@ -645,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. + + + }> + + + + + +
+
+
+
+ +
From 6316a870111cc3434dd6803b69955bf1a343ca26 Mon Sep 17 00:00:00 2001 From: Chris Lee Date: Mon, 5 Jul 2021 22:38:12 +1000 Subject: [PATCH 12/14] Fixed lint --- app/cronjob/trailingTradeHelper/configuration.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/cronjob/trailingTradeHelper/configuration.js b/app/cronjob/trailingTradeHelper/configuration.js index 77eb7040..4fddb8a7 100644 --- a/app/cronjob/trailingTradeHelper/configuration.js +++ b/app/cronjob/trailingTradeHelper/configuration.js @@ -222,7 +222,8 @@ const getLastBuyPriceRemoveThreshold = async ( 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. + // 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`) From 7619a48a3bf1cd2e4f6e9c31307daa68569c12c2 Mon Sep 17 00:00:00 2001 From: Chris Lee Date: Tue, 6 Jul 2021 21:25:01 +1000 Subject: [PATCH 13/14] Added tests --- .../__tests__/remove-last-buy-price.test.js | 179 +++++++++++---- .../trailingTrade/step/determine-action.js | 6 +- .../step/remove-last-buy-price.js | 2 +- .../__tests__/configuration.test.js | 204 ++++++++++++++++-- .../__tests__/fixtures/latest-stats.json | 10 + .../handlers/__tests__/setting-update.test.js | 24 ++- config/default.json | 4 +- 7 files changed, 352 insertions(+), 77 deletions(-) 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/determine-action.js b/app/cronjob/trailingTrade/step/determine-action.js index cd6738f0..8647d1d0 100644 --- a/app/cronjob/trailingTrade/step/determine-action.js +++ b/app/cronjob/trailingTrade/step/determine-action.js @@ -90,17 +90,13 @@ const canSell = data => { symbolInfo: { filterMinNotional: { minNotional } }, - symbolConfiguration: { - buy: { lastBuyPriceRemoveThreshold } - }, baseAssetBalance: { total: baseAssetTotalBalance }, sell: { currentPrice: sellCurrentPrice, lastBuyPrice } } = data; return ( lastBuyPrice > 0 && - baseAssetTotalBalance * sellCurrentPrice > parseFloat(minNotional) && - baseAssetTotalBalance * sellCurrentPrice > lastBuyPriceRemoveThreshold + baseAssetTotalBalance * sellCurrentPrice > parseFloat(minNotional) ); }; diff --git a/app/cronjob/trailingTrade/step/remove-last-buy-price.js b/app/cronjob/trailingTrade/step/remove-last-buy-price.js index c08acb56..297cb922 100644 --- a/app/cronjob/trailingTrade/step/remove-last-buy-price.js +++ b/app/cronjob/trailingTrade/step/remove-last-buy-price.js @@ -181,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/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/config/default.json b/config/default.json index c644d8f0..41b86567 100644 --- a/config/default.json +++ b/config/default.json @@ -50,10 +50,10 @@ }, "buy": { "enabled": true, - "lastBuyPriceRemoveThreshold": -1, - "lastBuyPriceRemoveThresholds": {}, "maxPurchaseAmount": -1, "maxPurchaseAmounts": {}, + "lastBuyPriceRemoveThreshold": -1, + "lastBuyPriceRemoveThresholds": {}, "triggerPercentage": 1.0, "stopPercentage": 1.02, "limitPercentage": 1.021, From 36619c159b2a4e366c6fd531c33c23254757ab7a Mon Sep 17 00:00:00 2001 From: Chris Lee Date: Tue, 6 Jul 2021 21:28:18 +1000 Subject: [PATCH 14/14] Updated CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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