Skip to content

Commit

Permalink
fix: stop loss with highest priority (chrisleekr#589)
Browse files Browse the repository at this point in the history
  • Loading branch information
uhliksk authored Jan 16, 2023
1 parent 357bc0f commit 450e07c
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 32 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file.
- Fixed the open orders to be cancelled when the current price is higher/lower than the order price by [@uhliksk](https://github.com/uhliksk) - [#569](https://github.com/chrisleekr/binance-trading-bot/pull/569)
- Improved queue processing by replacing Bull queue to customised queue system by [@uhliksk](https://github.com/uhliksk) - [#562](https://github.com/chrisleekr/binance-trading-bot/pull/562), [#581](https://github.com/chrisleekr/binance-trading-bot/pull/581), [#588](https://github.com/chrisleekr/binance-trading-bot/pull/588)
- Added conservative sell strategy, which can reduce the sell trigger price as the grid gets deeper by [@rando128](https://github.com/rando128) - [#585](https://github.com/chrisleekr/binance-trading-bot/pull/585)
- Fixed the stop-loss to be a higher priority than the new buy order by [@uhliksk](https://github.com/uhliksk) - [#589](https://github.com/chrisleekr/binance-trading-bot/pull/589)

Thanks [@uhliksk](https://github.com/uhliksk) and [@rando128](https://github.com/rando128) for your great contributions. 💯 :heart:

Expand Down
105 changes: 105 additions & 0 deletions app/cronjob/trailingTrade/step/__tests__/determine-action.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1145,6 +1145,111 @@ describe('determine-action.js', () => {
});
});
});

describe(
`isLowerThanStopLossTriggerPrice - sell stop loss is enabled ` +
`and when current price is less than stop loss trigger price`,
() => {
beforeEach(async () => {
// Set action is enabled
mockIsActionDisabled = jest.fn().mockResolvedValue({
isDisabled: false
});

// Set buy open orders and open trades
mockGetNumberOfBuyOpenOrders = jest.fn().mockResolvedValue(3);
mockGetNumberOfOpenTrades = jest.fn().mockResolvedValue(5);

jest.mock('../../../trailingTradeHelper/common', () => ({
isActionDisabled: mockIsActionDisabled,
getNumberOfBuyOpenOrders: mockGetNumberOfBuyOpenOrders,
getNumberOfOpenTrades: mockGetNumberOfOpenTrades,
getAPILimit: mockGetAPILimit,
isExceedingMaxOpenTrades: mockIsExceedingMaxOpenTrades
}));

// Set there is no grid trade order
mockGetGridTradeOrder = jest.fn().mockResolvedValue(null);

jest.mock('../../../trailingTradeHelper/order', () => ({
getGridTradeOrder: mockGetGridTradeOrder
}));

rawData = _.cloneDeep(orgRawData);

// Set balance less than minimum notional value
rawData.baseAssetBalance.total = 0.0003;

rawData.symbolConfiguration.botOptions.orderLimit = {
enabled: true,
maxBuyOpenOrders: 5,
maxOpenTrades: 10
};

rawData.symbolConfiguration.buy = {
athRestriction: {
enabled: true
},
// Set the current index to 2nd
currentGridTradeIndex: 1,
currentGridTrade: {
triggerPercentage: 1,
stopPercentage: 1.025,
limitPercentage: 1.026,
maxPurchaseAmount: 10,
executed: false,
executedOrder: null
}
};

// Enable sell stop loss
rawData.symbolConfiguration.sell.stopLoss = {
enabled: true,
maxLossPercentage: 0.95
};

// Set ATH restriction is higher than the current price
rawData.buy = {
currentPrice: 28000,
triggerPrice: 28000,
athRestrictionPrice: 28001
};

// When stop loss trigger price is less than the current price,
// then do not try to buy. Let it sell by stop loss.
rawData.sell = {
currentPrice: 28000,
triggerPrice: 30900,
lastBuyPrice: 30000,
stopLossTriggerPrice: 28500
};

step = require('../determine-action');

result = await step.execute(loggerMock, rawData);
});

it('should not determine the action because it should be sold by the stop loss', () => {
expect(result).toMatchObject({
action: 'not-determined',
baseAssetBalance: {
total: 0.0003
},
buy: {
currentPrice: 28000,
triggerPrice: 28000,
athRestrictionPrice: 28001
},
sell: {
currentPrice: 28000,
lastBuyPrice: 30000,
triggerPrice: 30900,
stopLossTriggerPrice: 28500
}
});
});
}
);
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,7 @@ describe('handle-open-orders.js', () => {
it('returns expected value', () => {
expect(result).toStrictEqual({
symbol: 'BTCUSDT',
action: 'buy',
action: 'buy-order-cancelled',
openOrders: [
{
symbol: 'BTCUSDT',
Expand Down Expand Up @@ -846,7 +846,7 @@ describe('handle-open-orders.js', () => {
it('returns expected value', () => {
expect(result).toStrictEqual({
symbol: 'BTCUSDT',
action: 'buy',
action: 'buy-order-cancelled',
openOrders: [
{
symbol: 'BTCUSDT',
Expand Down
54 changes: 29 additions & 25 deletions app/cronjob/trailingTrade/step/determine-action.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,30 @@ const {
} = require('../../trailingTradeHelper/common');
const { getGridTradeOrder } = require('../../trailingTradeHelper/order');

/**
* Check whether current price is lower or equal than stop loss trigger price
*
* @param {*} data
* @returns
*/
const isLowerThanStopLossTriggerPrice = data => {
const {
symbolConfiguration: {
sell: {
stopLoss: { enabled: sellStopLossEnabled }
}
},
sell: {
currentPrice: sellCurrentPrice,
stopLossTriggerPrice: sellStopLossTriggerPrice
}
} = data;

return (
sellStopLossEnabled === true && sellCurrentPrice <= sellStopLossTriggerPrice
);
};

/**
* Check whether can buy or not
*
Expand All @@ -27,7 +51,11 @@ const canBuy = data => {
buy: { currentPrice: buyCurrentPrice, triggerPrice: buyTriggerPrice }
} = data;

return buyCurrentPrice <= buyTriggerPrice && currentGridTrade !== null;
return (
buyCurrentPrice <= buyTriggerPrice &&
currentGridTrade !== null &&
!isLowerThanStopLossTriggerPrice(data)
);
};

/**
Expand Down Expand Up @@ -185,30 +213,6 @@ const isHigherThanSellTriggerPrice = data => {
return sellCurrentPrice >= sellTriggerPrice;
};

/**
* Check whether current price is lower or equal than stop loss trigger price
*
* @param {*} data
* @returns
*/
const isLowerThanStopLossTriggerPrice = data => {
const {
symbolConfiguration: {
sell: {
stopLoss: { enabled: sellStopLossEnabled }
}
},
sell: {
currentPrice: sellCurrentPrice,
stopLossTriggerPrice: sellStopLossTriggerPrice
}
} = data;

return (
sellStopLossEnabled === true && sellCurrentPrice <= sellStopLossTriggerPrice
);
};

/**
* Check whether should execute stop-loss if recommendation is neutral, sell or strong sell
*
Expand Down
8 changes: 5 additions & 3 deletions app/cronjob/trailingTrade/step/handle-open-orders.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ const execute = async (logger, rawData) => {
if (order.side.toLowerCase() === 'buy') {
if (await isExceedingMaxOpenTrades(logger, data)) {
// Cancel the initial buy order if max. open trades exceeded
data.action = 'buy-order-cancelled';
logger.info(
{ data, saveLog: true },
`The current number of open trades has reached the maximum number of open trades. ` +
Expand All @@ -69,6 +68,9 @@ const execute = async (logger, rawData) => {
} else {
data.buy.openOrders = [];

// Set action as buy order cancelled
data.action = 'buy-order-cancelled';

data.accountInfo = await getAccountInfoFromAPI(logger);
}
} else if (
Expand Down Expand Up @@ -112,8 +114,8 @@ const execute = async (logger, rawData) => {
// Reset buy open orders
data.buy.openOrders = [];

// Set action as buy
data.action = 'buy';
// Set action as buy order cancelled
data.action = 'buy-order-cancelled';

data.accountInfo = await getAccountInfoFromAPI(logger);
}
Expand Down
6 changes: 5 additions & 1 deletion public/js/QuoteAssetGridTradeArchiveIcon.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,11 @@ class QuoteAssetGridTradeArchiveIcon extends React.Component {
parseFloat(row.stopLossQuoteQty).toFixed(quoteAssetTickSize)
}
className={`text-center align-middle ${
row.totalSellQuoteQty === 0 ? 'text-muted' : ''
row.totalSellQuoteQty === 0
? 'text-muted'
: row.stopLossQuoteQty === 0
? ''
: 'text-danger'
}`}>
{parseFloat(row.totalSellQuoteQty).toFixed(quoteAssetTickSize)}{' '}
{quoteAsset}
Expand Down
6 changes: 5 additions & 1 deletion public/js/SymbolGridTradeArchiveIcon.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,11 @@ class SymbolGridTradeArchiveIcon extends React.Component {
parseFloat(row.stopLossQuoteQty).toFixed(quoteAssetTickSize)
}
className={`text-center align-middle ${
row.totalSellQuoteQty === 0 ? 'text-muted' : ''
row.totalSellQuoteQty === 0
? 'text-muted'
: row.stopLossQuoteQty === 0
? ''
: 'text-danger'
}`}>
{parseFloat(row.totalSellQuoteQty).toFixed(quoteAssetTickSize)}{' '}
{quoteAsset}
Expand Down

0 comments on commit 450e07c

Please sign in to comment.