Skip to content

Commit

Permalink
fix: queue symbols to remove excess open trades
Browse files Browse the repository at this point in the history
  • Loading branch information
uhliksk committed Aug 11, 2022
1 parent 6dfc0d0 commit 635128d
Show file tree
Hide file tree
Showing 10 changed files with 402 additions and 10 deletions.
89 changes: 89 additions & 0 deletions app/__tests__/server-cronjob.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ describe('server-cronjob', () => {
let mockTaskRunning = false;

let mockExecuteAlive;
let mockExecuteTrailingTradeQueued;
let mockExecuteTrailingTradeIndicator;

describe('cronjob running fine', () => {
Expand All @@ -18,10 +19,12 @@ describe('server-cronjob', () => {
cache.hset = jest.fn().mockResolvedValue(true);

mockExecuteAlive = jest.fn().mockResolvedValue(true);
mockExecuteTrailingTradeQueued = jest.fn().mockResolvedValue(true);
mockExecuteTrailingTradeIndicator = jest.fn().mockResolvedValue(true);

jest.mock('../cronjob', () => ({
executeAlive: mockExecuteAlive,
executeTrailingTradeQueued: mockExecuteTrailingTradeQueued,
executeTrailingTradeIndicator: mockExecuteTrailingTradeIndicator
}));

Expand Down Expand Up @@ -101,6 +104,67 @@ describe('server-cronjob', () => {
});
});

describe('trailingTradeQueued', () => {
beforeEach(async () => {
config.get = jest.fn(key => {
switch (key) {
case 'jobs.trailingTradeQueued.enabled':
return true;
case 'jobs.trailingTradeQueued.cronTime':
return '* * * * * *';
case 'tz':
return 'Australia/Melbourne';
default:
return `value-${key}`;
}
});
});

describe('when task is already running', () => {
beforeEach(() => {
mockTaskRunning = true;
const { runCronjob } = require('../server-cronjob');
runCronjob(logger);
});

it('initialise CronJob', () => {
expect(mockCronJob).toHaveBeenCalledWith(
'* * * * * *',
expect.any(Function),
null,
false,
'Australia/Melbourne'
);
});

it('does not trigger executeTrailingTradeQueued', () => {
expect(mockExecuteTrailingTradeQueued).not.toHaveBeenCalled();
});
});

describe('when task is not running', () => {
beforeEach(() => {
mockTaskRunning = false;
const { runCronjob } = require('../server-cronjob');
runCronjob(logger);
});

it('initialise CronJob', () => {
expect(mockCronJob).toHaveBeenCalledWith(
'* * * * * *',
expect.any(Function),
null,
false,
'Australia/Melbourne'
);
});

it('triggers executeTrailingTradeQueued', () => {
expect(mockExecuteTrailingTradeQueued).toHaveBeenCalled();
});
});
});

describe('trailingTradeIndicator', () => {
beforeEach(async () => {
config.get = jest.fn(key => {
Expand Down Expand Up @@ -183,6 +247,10 @@ describe('server-cronjob', () => {
expect(mockExecuteAlive).not.toHaveBeenCalled();
});

it('does not trigger executeTrailingTradeQueued', () => {
expect(mockExecuteTrailingTradeQueued).not.toHaveBeenCalled();
});

it('does not trigger executeTrailingTradeIndicator', () => {
expect(mockExecuteTrailingTradeIndicator).not.toHaveBeenCalled();
});
Expand All @@ -198,12 +266,16 @@ describe('server-cronjob', () => {
cache.hset = jest.fn().mockResolvedValue(true);

mockExecuteAlive = jest.fn().mockResolvedValue(true);
mockExecuteTrailingTradeQueued = jest.fn().mockImplementation(() => {
setTimeout(() => Promise.resolve(true), 30000);
});
mockExecuteTrailingTradeIndicator = jest.fn().mockImplementation(() => {
setTimeout(() => Promise.resolve(true), 30000);
});

jest.mock('../cronjob', () => ({
executeAlive: mockExecuteAlive,
executeTrailingTradeQueued: mockExecuteTrailingTradeQueued,
executeTrailingTradeIndicator: mockExecuteTrailingTradeIndicator
}));

Expand All @@ -221,6 +293,19 @@ describe('server-cronjob', () => {
}));
config = require('config');

config.get = jest.fn(key => {
switch (key) {
case 'jobs.trailingTradeQueued.enabled':
return true;
case 'jobs.trailingTradeQueued.cronTime':
return '* * * * * *';
case 'tz':
return 'Australia/Melbourne';
default:
return `value-${key}`;
}
});

config.get = jest.fn(key => {
switch (key) {
case 'jobs.trailingTradeIndicator.enabled':
Expand Down Expand Up @@ -254,6 +339,10 @@ describe('server-cronjob', () => {
);
});

it('triggers executeTrailingTradeQueued', () => {
expect(mockExecuteTrailingTradeQueued).toHaveBeenCalled();
});

it('triggers executeTrailingTradeIndicator', () => {
expect(mockExecuteTrailingTradeIndicator).toHaveBeenCalled();
});
Expand Down
1 change: 1 addition & 0 deletions app/cronjob/__tests__/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ describe('index', () => {
expect(index).toStrictEqual({
executeAlive: expect.any(Function),
executeTrailingTrade: expect.any(Function),
executeTrailingTradeQueued: expect.any(Function),
executeTrailingTradeIndicator: expect.any(Function)
});
});
Expand Down
92 changes: 89 additions & 3 deletions app/cronjob/__tests__/trailingTrade.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ describe('trailingTrade', () => {
let mockIsSymbolLocked;
let mockUnlockSymbol;

let mockQueueSymbol;
let mockUnqueueSymbol;

let mockGetSymbolConfiguration;
let mockGetSymbolInfo;
let mockGetOverrideAction;
Expand Down Expand Up @@ -43,6 +46,9 @@ describe('trailingTrade', () => {
mockLockSymbol = jest.fn().mockResolvedValue(true);
mockUnlockSymbol = jest.fn().mockResolvedValue(true);

mockQueueSymbol = jest.fn().mockResolvedValue(true);
mockUnqueueSymbol = jest.fn().mockResolvedValue(true);

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

Expand Down Expand Up @@ -271,7 +277,8 @@ describe('trailingTrade', () => {
getAccountInfo: mockGetAccountInfo,
lockSymbol: mockLockSymbol,
isSymbolLocked: mockIsSymbolLocked,
unlockSymbol: mockUnlockSymbol
unlockSymbol: mockUnlockSymbol,
unqueueSymbol: mockUnqueueSymbol
}));

jest.mock('../trailingTrade/steps', () => ({
Expand Down Expand Up @@ -640,7 +647,8 @@ describe('trailingTrade', () => {
getAccountInfo: mockGetAccountInfo,
lockSymbol: mockLockSymbol,
isSymbolLocked: mockIsSymbolLocked,
unlockSymbol: mockUnlockSymbol
unlockSymbol: mockUnlockSymbol,
queueSymbol: mockQueueSymbol
}));

jest.mock('../trailingTrade/steps', () => ({
Expand Down Expand Up @@ -682,7 +690,7 @@ describe('trailingTrade', () => {
debug: true,
symbol: 'BTCUSDT'
},
'⏯ TrailingTrade: Skip process as the symbol is currently locked. It will be re-execute 10 seconds later.'
'⏯ TrailingTrade: Skip process as the symbol is currently locked. It will be queued.'
);
});

Expand Down Expand Up @@ -729,3 +737,81 @@ describe('trailingTrade', () => {
});
});
});

describe('trailingTradeQueued', () => {
let mockGetGlobalConfiguration;

let mockIsSymbolLocked;
let mockIsSymbolQueued;
let mockQueueSymbol;

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

mockGetGlobalConfiguration = jest.fn().mockResolvedValue({
symbols: ['BTCUSDT', 'ETHUSDT', 'LTCUSDT']
});
});

describe('when symbol is queued', () => {
beforeEach(async () => {
mockIsSymbolLocked = jest.fn().mockResolvedValue(true);
mockIsSymbolQueued = jest.fn().mockResolvedValue(true);
mockQueueSymbol = jest.fn().mockResolvedValue(true);

jest.mock('../trailingTradeHelper/common', () => ({
isSymbolLocked: mockIsSymbolLocked,
isSymbolQueued: mockIsSymbolQueued,
queueSymbol: mockQueueSymbol
}));

jest.mock('../trailingTradeHelper/configuration', () => ({
getGlobalConfiguration: mockGetGlobalConfiguration
}));

const {
executeQueued: trailingTradeQueued
} = require('../trailingTrade');

await trailingTradeQueued(logger);
});

it('triggers isSymbolQueued', () => {
expect(mockIsSymbolQueued).toHaveBeenCalled();
});

['BTCUSDT', 'ETHUSDT', 'LTCUSDT'].forEach(symbol => {
it(`triggers isSymbolLocked - ${symbol}`, () => {
expect(mockIsSymbolLocked).toHaveBeenCalledWith(logger, symbol);
});
});
});

describe('when symbol is not queued', () => {
beforeEach(async () => {
mockIsSymbolQueued = jest.fn().mockResolvedValue(false);

jest.mock('../trailingTradeHelper/common', () => ({
isSymbolQueued: mockIsSymbolQueued
}));

jest.mock('../trailingTradeHelper/configuration', () => ({
getGlobalConfiguration: mockGetGlobalConfiguration
}));

const {
executeQueued: trailingTradeQueued
} = require('../trailingTrade');

await trailingTradeQueued(logger);
});

it('triggers isSymbolQueued', () => {
expect(mockIsSymbolQueued).toHaveBeenCalled();
});

it('triggers isSymbolLocked', () => {
expect(mockIsSymbolLocked).not.toHaveBeenCalled();
});
});
});
4 changes: 4 additions & 0 deletions app/cronjob/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
const { execute: executeAlive } = require('./alive');
const { execute: executeTrailingTrade } = require('./trailingTrade');
const {
executeQueued: executeTrailingTradeQueued
} = require('./trailingTrade');
const {
execute: executeTrailingTradeIndicator
} = require('./trailingTradeIndicator');

module.exports = {
executeAlive,
executeTrailingTrade,
executeTrailingTradeQueued,
executeTrailingTradeIndicator
};
32 changes: 28 additions & 4 deletions app/cronjob/trailingTrade.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
const { v4: uuidv4 } = require('uuid');
const config = require('config');
const {
getGlobalConfiguration
} = require('./trailingTradeHelper/configuration');

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

const {
Expand Down Expand Up @@ -42,15 +48,16 @@ const execute = async (rawLogger, 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.'
'⏯ TrailingTrade: Skip process as the symbol is currently locked. It will be queued.'
);
setTimeout(() => execute(logger, symbol), 10000);
queueSymbol(logger, symbol);
return;
}

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

await lockSymbol(logger, symbol);
await unqueueSymbol(logger, symbol);

// Retrieve account info from cache
const accountInfo = await getAccountInfo(logger);
Expand Down Expand Up @@ -182,4 +189,21 @@ const execute = async (rawLogger, symbol) => {
});
};

module.exports = { execute };
const executeQueued = async rawLogger => {
const logger = rawLogger.child({
jobName: 'trailingTradeQueued',
correlationId: uuidv4()
});

// Retrieve global configuration
const globalConfiguration = await getGlobalConfiguration(logger);

// Execute queued trailing trade
globalConfiguration.symbols.map(async symbol => {
if ((await isSymbolQueued(logger, symbol)) === true) {
execute(logger, symbol);
}
});
};

module.exports = { execute, executeQueued };
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe('ensure-grid-trade-order-executed.js', () => {
let mockGetAPILimit;
let mockIsExceedAPILimit;
let mockDisableAction;
let mockQueueSymbol;
let mockSaveOrderStats;

let mockSaveSymbolGridTrade;
Expand Down Expand Up @@ -45,6 +46,7 @@ describe('ensure-grid-trade-order-executed.js', () => {
mockGetAPILimit = jest.fn().mockResolvedValue(10);
mockIsExceedAPILimit = jest.fn().mockReturnValue(false);
mockDisableAction = jest.fn().mockResolvedValue(true);
mockQueueSymbol = jest.fn().mockResolvedValue(true);
mockSaveOrderStats = jest.fn().mockResolvedValue(true);

mockSaveSymbolGridTrade = jest.fn().mockResolvedValue(true);
Expand Down Expand Up @@ -337,6 +339,7 @@ describe('ensure-grid-trade-order-executed.js', () => {
getAPILimit: mockGetAPILimit,
isExceedAPILimit: mockIsExceedAPILimit,
disableAction: mockDisableAction,
queueSymbol: mockQueueSymbol,
saveOrderStats: mockSaveOrderStats
}));

Expand Down Expand Up @@ -445,6 +448,10 @@ describe('ensure-grid-trade-order-executed.js', () => {
expect(mockDisableAction).not.toHaveBeenCalled();
});

it('does not trigger queueSymbol', () => {
expect(mockQueueSymbol).not.toHaveBeenCalled();
});

it('does not trigger saveOrderStats', () => {
expect(mockSaveOrderStats).not.toHaveBeenCalled();
});
Expand Down
Loading

0 comments on commit 635128d

Please sign in to comment.