Skip to content

Commit

Permalink
fix: make trailingTrade to be executed when the symbol is locked
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisleekr committed Aug 9, 2022
1 parent 499e667 commit c05c46c
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 162 deletions.
210 changes: 62 additions & 148 deletions app/cronjob/__tests__/trailingTrade.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ describe('trailingTrade', () => {
let mockConfigGet;

let mockGetAccountInfo;
let mockLockSymbol;
let mockIsSymbolLocked;
let mockUnlockSymbol;

let mockGetSymbolConfiguration;
let mockGetSymbolInfo;
Expand All @@ -33,9 +35,14 @@ describe('trailingTrade', () => {
let mockSaveDataToCache;
let mockErrorHandlerWrapper;

jest.useFakeTimers();

beforeEach(() => {
jest.clearAllMocks().resetModules();

mockLockSymbol = jest.fn().mockResolvedValue(true);
mockUnlockSymbol = jest.fn().mockResolvedValue(true);

mockLoggerInfo = jest.fn();
mockSlackSendMessage = jest.fn().mockResolvedValue(true);

Expand Down Expand Up @@ -262,7 +269,9 @@ describe('trailingTrade', () => {

jest.mock('../trailingTradeHelper/common', () => ({
getAccountInfo: mockGetAccountInfo,
isSymbolLocked: mockIsSymbolLocked
lockSymbol: mockLockSymbol,
isSymbolLocked: mockIsSymbolLocked,
unlockSymbol: mockUnlockSymbol
}));

jest.mock('../trailingTrade/steps', () => ({
Expand Down Expand Up @@ -429,27 +438,6 @@ describe('trailingTrade', () => {
);
});
});

describe('when there is existing running executeTrailingTrade for a symbol', () => {
beforeEach(async () => {
mockCacheHget = jest.fn().mockResolvedValue('true');

const { execute: trailingTradeExecute } = require('../trailingTrade');
await trailingTradeExecute(logger, 'LTCUSDT');
});

it(`does not trigger cache.hset`, () => {
expect(mockCacheHset).not.toHaveBeenCalled();
});

it(`does not trigger getAccountInfo`, () => {
expect(mockGetAccountInfo).not.toHaveBeenCalled();
});

it(`does not trigger isSymbolLocked`, () => {
expect(mockIsSymbolLocked).not.toHaveBeenCalled();
});
});
});

describe('when symbol is locked', () => {
Expand All @@ -467,7 +455,10 @@ describe('trailingTrade', () => {
get: mockConfigGet
}));

mockIsSymbolLocked = jest.fn().mockResolvedValue(true);
mockIsSymbolLocked = jest
.fn()
.mockResolvedValueOnce(true)
.mockResolvedValueOnce(false);

mockGetAccountInfo = jest.fn().mockResolvedValue({
account: 'info'
Expand Down Expand Up @@ -647,7 +638,9 @@ describe('trailingTrade', () => {

jest.mock('../trailingTradeHelper/common', () => ({
getAccountInfo: mockGetAccountInfo,
isSymbolLocked: mockIsSymbolLocked
lockSymbol: mockLockSymbol,
isSymbolLocked: mockIsSymbolLocked,
unlockSymbol: mockUnlockSymbol
}));

jest.mock('../trailingTrade/steps', () => ({
Expand Down Expand Up @@ -675,142 +668,63 @@ describe('trailingTrade', () => {
beforeEach(async () => {
const { execute: trailingTradeExecute } = require('../trailingTrade');
await trailingTradeExecute(logger, 'BTCUSDT');

jest.runOnlyPendingTimers();
});

it(`triggers isSymbolLocked - BTCUSDT`, () => {
expect(mockIsSymbolLocked).toHaveBeenCalledWith(logger, 'BTCUSDT');
});
it('returns expected result for BTCUSDT', async () => {
expect(mockLoggerInfo).toHaveBeenCalledWith(
{
symbol: 'BTCUSDT',
data: {
symbol: 'BTCUSDT',
isLocked: true,
featureToggle: { feature1Enabled: false },
accountInfo: { account: 'info' },
symbolConfiguration: { symbol: 'configuration data' },
symbolInfo: { symbol: 'info' },
overrideAction: { action: 'override-action' },
ensureManualOrder: { ensured: 'manual-buy-order' },
ensureGridTradeOrder: { ensured: 'grid-trade' },
baseAssetBalance: { baseAsset: 'balance' },
quoteAssetBalance: { quoteAsset: 'balance' },
openOrders: [{ orderId: 'order-id-BTCUSDT', symbol: 'BTCUSDT' }],
lastCandle: { got: 'lowest value' },
indicators: { some: 'value' },
buy: { should: 'buy?', actioned: 'yes' },
sell: { should: 'sell?', actioned: 'yes' },
handled: 'open-orders',
action: 'determined',
placeManualTrade: { placed: 'manual-trade' },
cancelOrder: { cancelled: 'existing-order' },
stopLoss: 'processed',
removed: 'last-buy-price',
order: {},
canDisable: true,
saveToCache: true,
saved: 'data-to-cache'
}
},
'TrailingTrade: Finish process...'
);
});
});

describe('execute trailing trade for ETHUSDT', () => {
beforeEach(async () => {
const { execute: trailingTradeExecute } = require('../trailingTrade');
await trailingTradeExecute(logger, 'ETHUSDT');
});

it(`triggers isSymbolLocked - ETHUSDT`, () => {
expect(mockIsSymbolLocked).toHaveBeenCalledWith(logger, 'ETHUSDT');
});

it('returns expected result for ETHUSDT', async () => {
it('returns expected result for BTCUSDT 1st execution', async () => {
expect(mockLoggerInfo).toHaveBeenCalledWith(
{
symbol: 'ETHUSDT',
data: {
symbol: 'ETHUSDT',
isLocked: true,
featureToggle: { feature1Enabled: false },
accountInfo: { account: 'info' },
symbolConfiguration: { symbol: 'configuration data' },
symbolInfo: { symbol: 'info' },
overrideAction: { action: 'override-action' },
ensureManualOrder: { ensured: 'manual-buy-order' },
ensureGridTradeOrder: { ensured: 'grid-trade' },
baseAssetBalance: { baseAsset: 'balance' },
quoteAssetBalance: { quoteAsset: 'balance' },
openOrders: [{ orderId: 'order-id-ETHUSDT', symbol: 'ETHUSDT' }],
lastCandle: { got: 'lowest value' },
indicators: { some: 'value' },
buy: { should: 'buy?', actioned: 'yes' },
sell: { should: 'sell?', actioned: 'yes' },
handled: 'open-orders',
action: 'determined',
placeManualTrade: { placed: 'manual-trade' },
cancelOrder: { cancelled: 'existing-order' },
stopLoss: 'processed',
removed: 'last-buy-price',
order: {},
canDisable: true,
saveToCache: true,
saved: 'data-to-cache'
}
debug: true,
symbol: 'BTCUSDT'
},
'TrailingTrade: Finish process...'
'TrailingTrade: Skip process as the symbol is currently locked. It will be re-execute 10 seconds later.'
);
});
});

describe('execute trailing trade for LTCUSDT', () => {
beforeEach(async () => {
const { execute: trailingTradeExecute } = require('../trailingTrade');
await trailingTradeExecute(logger, 'LTCUSDT');
});

it(`triggers isSymbolLocked - LTCUSDT`, () => {
expect(mockIsSymbolLocked).toHaveBeenCalledWith(logger, 'LTCUSDT');
});

it('returns expected result for ETHUSDT', () => {
expect(mockLoggerInfo).toHaveBeenCalledWith(
{
symbol: 'LTCUSDT',
data: {
symbol: 'LTCUSDT',
isLocked: true,
featureToggle: { feature1Enabled: false },
accountInfo: { account: 'info' },
symbolConfiguration: { symbol: 'configuration data' },
symbolInfo: { symbol: 'info' },
overrideAction: { action: 'override-action' },
ensureManualOrder: { ensured: 'manual-buy-order' },
ensureGridTradeOrder: { ensured: 'grid-trade' },
baseAssetBalance: { baseAsset: 'balance' },
quoteAssetBalance: { quoteAsset: 'balance' },
openOrders: [{ orderId: 'order-id-LTCUSDT', symbol: 'LTCUSDT' }],
lastCandle: { got: 'lowest value' },
indicators: { some: 'value' },
buy: { should: 'buy?', actioned: 'yes' },
sell: { should: 'sell?', actioned: 'yes' },
handled: 'open-orders',
action: 'determined',
placeManualTrade: { placed: 'manual-trade' },
cancelOrder: { cancelled: 'existing-order' },
stopLoss: 'processed',
removed: 'last-buy-price',
order: {},
canDisable: true,
saveToCache: true,
saved: 'data-to-cache'
}
},
'TrailingTrade: Finish process...'
);
it('returns expected result for BTCUSDT 2nd execution', async () => {
setTimeout(() => {
expect(mockLoggerInfo).toHaveBeenCalledWith(
{
symbol: 'BTCUSDT',
data: {
symbol: 'BTCUSDT',
isLocked: false,
featureToggle: { feature1Enabled: false },
lastCandle: { got: 'lowest value' },
accountInfo: { account: 'info' },
symbolConfiguration: { symbol: 'configuration data' },
indicators: { some: 'value' },
symbolInfo: { symbol: 'info' },
openOrders: [
{ orderId: 'order-id-BTCUSDT', symbol: 'BTCUSDT' }
],
action: 'determined',
baseAssetBalance: { baseAsset: 'balance' },
quoteAssetBalance: { quoteAsset: 'balance' },
buy: { should: 'buy?', actioned: 'yes' },
sell: { should: 'sell?', actioned: 'yes' },
order: {},
canDisable: true,
saveToCache: true,
ensureManualOrder: { ensured: 'manual-buy-order' },
ensureGridTradeOrder: { ensured: 'grid-trade' },
overrideAction: { action: 'override-action' },
handled: 'open-orders',
placeManualTrade: { placed: 'manual-trade' },
cancelOrder: { cancelled: 'existing-order' },
stopLoss: 'processed',
removed: 'last-buy-price',
saved: 'data-to-cache'
}
},
'TrailingTrade: Finish process...'
);
}, 10000);
});
});
});
Expand Down
41 changes: 27 additions & 14 deletions app/cronjob/trailingTrade.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
const { v4: uuidv4 } = require('uuid');
const config = require('config');

const {
getAccountInfo,
isSymbolLocked
isSymbolLocked,
lockSymbol,
unlockSymbol
} = require('./trailingTradeHelper/common');

const {
Expand All @@ -25,30 +28,36 @@ const {
saveDataToCache
} = require('./trailingTrade/steps');
const { errorHandlerWrapper } = require('../error-handler');
const { cache } = require('../helpers');

const execute = async (rawLogger, symbol) => {
const logger = rawLogger.child({ jobName: 'trailingTrade' });
const logger = rawLogger.child({
jobName: 'trailingTrade',
correlationId: uuidv4()
});

await errorHandlerWrapper(logger, 'Trailing Trade', async () => {
if ((await cache.hget(`execute-trailing-trade`, symbol)) === 'true') {
// do nothing, there is another execution task running
// Check if the symbol is locked, if it is locked, it means the symbol is still trading.
const isLocked = await isSymbolLocked(logger, symbol);

if (isLocked === true) {
logger.info(
{ debug: true, symbol },
'⏯ TrailingTrade: Skip process as the symbol is currently locked. It will be re-execute 10 seconds later.'
);
setTimeout(() => execute(logger, symbol), 10000);
return;
}

await cache.hset(`execute-trailing-trade`, symbol, true, 5);
logger.info({ debug: true, symbol }, '▶ TrailingTrade: Start process...');

await lockSymbol(logger, symbol);

// Retrieve account info from cache
const accountInfo = await getAccountInfo(logger);

// Retrieve feature toggles
const featureToggle = config.get('featureToggle');

logger.info({ debug: true, symbol }, '▶ TrailingTrade: Start process...');

// Check if the symbol is locked, if it is locked, it means the symbol is still processing.
const isLocked = await isSymbolLocked(logger, symbol);

// Define skeleton of data structure
let data = {
symbol,
Expand Down Expand Up @@ -161,11 +170,15 @@ const execute = async (rawLogger, symbol) => {
stepLogger.info({ data }, `Finish step - ${stepName}`);
}

logger.info({ symbol }, '⏹ TrailingTrade: Finish process (Debug)...');
// Unlock symbol for processing
await unlockSymbol(logger, symbol);

logger.info({ symbol, data }, 'TrailingTrade: Finish process...');
logger.info(
{ symbol, debug: true },
'⏹ TrailingTrade: Finish process (Debug)...'
);

await cache.hdel(`execute-trailing-trade`, symbol);
logger.info({ symbol, data }, 'TrailingTrade: Finish process...');
});
};

Expand Down

0 comments on commit c05c46c

Please sign in to comment.