Skip to content

Commit

Permalink
fix: dbot performance issue -- unified websocket connection and moved… (
Browse files Browse the repository at this point in the history
binary-com#7103)

* fix: dbot performance issue -- unified websocket connection and moved apis to app initialization

* fix: improved the api_base class added fallback and account change logics

* fix: removed clientid logic from dbot app

* fix: added mobx reaction to handle accountid change

* fix: moved the initialization of the websocket after app initializes

* fix: fixed pip_sizes in api_base made the names consistent

* fix: code improvisation -- worked on the suggested changes

* chore: fixed toggle button conditionals and base class name

* fix: hoist self-exclusion checks on initialization

* fix: prevent from subscribing multiple time to ticks_history with same symbol

* fix: #83948 'This market is closed' repetedly appears even after the bot is stopped

* fix: removed redundunt api subscriptions open_proposal_contract, balance, and proposal

* fix: profit/loss log is getting printed 2 times - unsubscribed when we stop the bot

* fix: added time api request to keep ws alive
  • Loading branch information
sandeep-deriv committed Jan 6, 2023
1 parent dc3e990 commit 1abb7de
Show file tree
Hide file tree
Showing 17 changed files with 246 additions and 69 deletions.
2 changes: 2 additions & 0 deletions packages/bot-skeleton/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
"immutable": "^3.8.2",
"localforage": "^1.9.0",
"lz-string": "^1.4.4",
"mobx": "^6.6.1",
"mobx-react": "^7.5.1",
"redux": "^4.0.1",
"redux-thunk": "^2.2.0",
"scratch-blocks": "0.1.0-prerelease.20200917235131",
Expand Down
8 changes: 8 additions & 0 deletions packages/bot-skeleton/src/scratch/dbot-store.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { reaction } from 'mobx';
import { api_base } from '../services/api/api-base';

class DBotStoreInterface {
// TODO here we are suppose to define an interface and implement fields of DBotStore.
handleFileChange = () => {
Expand Down Expand Up @@ -27,6 +30,11 @@ class DBotStore extends DBotStoreInterface {
this.handleFileChange = store.handleFileChange;
this.startLoading = store.startLoading;
this.endLoading = store.endLoading;

reaction(
() => this.client.loginid,
() => api_base.createNewInstance(this.client.loginid)
);
}

static setInstance(store) {
Expand Down
6 changes: 4 additions & 2 deletions packages/bot-skeleton/src/scratch/dbot.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { observer as globalObserver } from '../utils/observer';
import ApiHelpers from '../services/api/api-helpers';
import Interpreter from '../services/tradeEngine/utils/interpreter';
import { setColors } from './hooks/colours';
import { api_base } from '../services/api/api-base';

class DBot {
constructor() {
Expand Down Expand Up @@ -97,7 +98,7 @@ class DBot {
window.dispatchEvent(new Event('resize'));
window.addEventListener('dragover', DBot.handleDragOver);
window.addEventListener('drop', e => DBot.handleDropOver(e, handleFileChange));

api_base.init();
// disable overflow
el_scratch_div.parentNode.style.overflow = 'hidden';
resolve();
Expand Down Expand Up @@ -134,7 +135,7 @@ class DBot {
}

this.interpreter = Interpreter();

api_base.setIsRunning(true);
this.interpreter.run(code).catch(error => {
globalObserver.emit('Error', error);
this.stopBot();
Expand Down Expand Up @@ -226,6 +227,7 @@ class DBot {
* that trade will be completed first to reflect correct contract status in UI.
*/
stopBot() {
api_base.setIsRunning(false);
if (this.interpreter) {
this.interpreter.stop();
}
Expand Down
117 changes: 117 additions & 0 deletions packages/bot-skeleton/src/services/api/api-base.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { observer as globalObserver } from '../../utils/observer';
import { generateDerivApiInstance, getLoginId, getToken } from './appId';

class APIBase {
api;
token;
account_id;
pip_sizes = {};
account_info = {};
is_running = false;
subscriptions = [];
time_interval = null;

init(force_update = false) {
if (getLoginId()) {
this.toggleRunButton(true);
if (force_update && this.api) this.api.disconnect();
this.api = generateDerivApiInstance();
this.initEventListeners();
this.authorizeAndSubscribe();
if (this.time_interval) clearInterval(this.time_interval);
this.time_interval = null;
this.getTime();
}
}

initEventListeners() {
if (window) {
window.addEventListener('online', this.reconnectIfNotConnected);
window.addEventListener('focus', this.reconnectIfNotConnected);
}
}

createNewInstance(account_id) {
if (this.account_id !== account_id) {
this.init(true);
}
}

reconnectIfNotConnected = () => {
// eslint-disable-next-line no-console
console.log('connection state: ', this.api.connection.readyState);
if (this.api.connection.readyState !== 1) {
// eslint-disable-next-line no-console
console.log('Info: Connection to the server was closed, trying to reconnect.');
this.init();
}
};

authorizeAndSubscribe() {
const { token, account_id } = getToken();
if (token) {
this.token = token;
this.account_id = account_id;
this.getActiveSymbols();
this.api
.authorize(this.token)
.then(({ authorize }) => {
this.subscribe();
this.account_info = authorize;
})
.catch(e => {
globalObserver.emit('Error', e);
});
}
}

subscribe() {
this.api.send({ balance: 1, subscribe: 1 }).catch(e => {
globalObserver.emit('Error', e);
});
this.api.send({ transaction: 1, subscribe: 1 }).catch(e => {
globalObserver.emit('Error', e);
});
}

getActiveSymbols = async () => {
const { active_symbols = [] } = await this.api.send({ active_symbols: 'brief' }).catch(e => {
globalObserver.emit('Error', e);
});
const pip_sizes = {};
active_symbols.forEach(({ symbol, pip }) => {
pip_sizes[symbol] = +(+pip).toExponential().substring(3);
});
this.pip_sizes = pip_sizes;
this.toggleRunButton(false);
};

toggleRunButton = toggle => {
const run_button = document.querySelector('#db-animation__run-button');
if (!run_button) return;
run_button.disabled = toggle;
};

setIsRunning(toggle = false) {
this.is_running = toggle;
}

pushSubscription(subscription) {
this.subscriptions.push(subscription);
}

clearSubscriptions() {
this.subscriptions.forEach(s => s.unsubscribe());
this.subscriptions = [];
}

getTime() {
if (!this.time_interval) {
this.time_interval = setInterval(() => {
this.api.send({ time: 1 });
}, 30000);
}
}
}

export const api_base = new APIBase();
16 changes: 16 additions & 0 deletions packages/bot-skeleton/src/services/api/appId.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,19 @@ export const generateDerivApiInstance = () => {
});
return deriv_api;
};

export const getLoginId = () => {
const login_id = localStorage.getItem('active_loginid');
if (login_id && login_id !== 'null') return login_id;
return null;
};

export const getToken = () => {
const active_loginid = getLoginId();
const client_accounts = JSON.parse(localStorage.getItem('client.accounts')) || undefined;
const active_account = (client_accounts && client_accounts[active_loginid]) || {};
return {
token: active_account?.token || undefined,
account_id: active_loginid || undefined,
};
};
48 changes: 30 additions & 18 deletions packages/bot-skeleton/src/services/api/ticks_service.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Map } from 'immutable';
import { historyToTicks, getLast } from 'binary-utils';
import { doUntilDone, getUUID } from '../tradeEngine/utils/helpers';
import { observer as globalObserver } from '../../utils/observer';
import { api_base } from './api-base';

const parseTick = tick => ({
epoch: +tick.epoch,
Expand Down Expand Up @@ -39,8 +40,7 @@ const updateCandles = (candles, ohlc) => {
const getType = isCandle => (isCandle ? 'candles' : 'ticks');

export default class TicksService {
constructor(api) {
this.api = api;
constructor() {
this.ticks = new Map();
this.candles = new Map();
this.tickListeners = new Map();
Expand All @@ -60,23 +60,13 @@ export default class TicksService {

if (!this.active_symbols_promise) {
this.active_symbols_promise = new Promise(resolve => {
this.getActiveSymbols().then(active_symbols => {
this.pipSizes = active_symbols
.reduce((s, i) => s.set(i.symbol, +(+i.pip).toExponential().substring(3)), new Map())
.toObject();
resolve(this.pipSizes);
});
this.pipSizes = api_base.pip_sizes;
resolve(this.pipSizes);
});
}
return this.active_symbols_promise;
}

getActiveSymbols = async () => {
await this.api.expectResponse('authorize');
const active_symbols = await this.api.send({ active_symbols: 'brief' });
return active_symbols.active_symbols;
};

request(options) {
const { symbol, granularity } = options;

Expand All @@ -89,7 +79,6 @@ export default class TicksService {
if (style === 'candles' && this.candles.hasIn([symbol, Number(granularity)])) {
return Promise.resolve(this.candles.getIn([symbol, Number(granularity)]));
}

return this.requestStream({ ...options, style });
}

Expand Down Expand Up @@ -163,7 +152,7 @@ export default class TicksService {
...(tickSubscription || []),
];

Promise.all(subscription.map(id => doUntilDone(() => this.api.forget(id))));
Promise.all(subscription.map(id => doUntilDone(() => api_base.api.forget(id))));

this.subscriptions = new Map();
}
Expand Down Expand Up @@ -195,7 +184,7 @@ export default class TicksService {
}

observe() {
this.api.onMessage().subscribe(({ data }) => {
api_base.api.onMessage().subscribe(({ data }) => {
if (data.msg_type === 'tick') {
const { tick } = data;
const { symbol, id } = tick;
Expand Down Expand Up @@ -260,7 +249,7 @@ export default class TicksService {
style,
};
return new Promise((resolve, reject) => {
doUntilDone(() => this.api.send(request_object))
doUntilDone(() => api_base.api.send(request_object), [], api_base)
.then(r => {
if (style === 'ticks') {
const ticks = historyToTicks(r.history);
Expand All @@ -278,4 +267,27 @@ export default class TicksService {
.catch(reject);
});
}

forget = subscription_id => {
if (subscription_id) {
api_base.api.forget(subscription_id);
}
};

unsubscribeFromTicksService() {
if (this.ticks_history_promise) {
const { stringified_options } = this.ticks_history_promise;
const { symbol = '' } = JSON.parse(stringified_options);
if (symbol) {
this.forget(this.subscriptions.getIn(['tick', symbol]));
}
}
if (this.candles_promise) {
const { stringified_options } = this.candles_promise;
const { symbol = '' } = JSON.parse(stringified_options);
if (symbol) {
this.forget(this.subscriptions.getIn(['candle', symbol]));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { getFormattedText } from '@deriv/shared';
import { info } from '../utils/broadcast';
import DBotStore from '../../../scratch/dbot-store';
import { api_base } from '../../api/api-base';

let balance_string = '';

export default Engine =>
class Balance extends Engine {
observeBalance() {
this.api.onMessage().subscribe(({ data }) => {
const subscription = api_base.api.onMessage().subscribe(({ data }) => {
if (data.msg_type === 'balance') {
const {
balance: { balance: b, currency },
Expand All @@ -18,6 +19,7 @@ export default Engine =>
info({ accountID: this.accountInfo.loginid, balance: balance_string });
}
});
api_base.pushSubscription(subscription);
}

// eslint-disable-next-line class-methods-use-this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { sell, openContractReceived } from './state/actions';
import { contractStatus, contract as broadcastContract } from '../utils/broadcast';
import { doUntilDone } from '../utils/helpers';
import DBotStore from '../../../scratch/dbot-store';
import { api_base } from '../../api/api-base';

export default Engine =>
class OpenContract extends Engine {
observeOpenContract() {
this.api.onMessage().subscribe(({ data }) => {
const subscription = api_base.api.onMessage().subscribe(({ data }) => {
if (data.msg_type === 'proposal_open_contract') {
const contract = data.proposal_open_contract;

Expand Down Expand Up @@ -41,6 +42,7 @@ export default Engine =>
}
}
});
api_base.pushSubscription(subscription);
}

waitForAfter() {
Expand All @@ -51,15 +53,21 @@ export default Engine =>

subscribeToOpenContract(contract_id = this.contractId) {
this.contractId = contract_id;
doUntilDone(() => this.api.send({ proposal_open_contract: 1, contract_id, subscribe: 1 }))
const request_object = {
proposal_open_contract: 1,
contract_id,
subscribe: 1,
};

doUntilDone(() => api_base.api.send(request_object))
.then(data => {
const { populateConfig } = DBotStore.instance;
populateConfig(data.proposal_open_contract);
this.openContractId = data.proposal_open_contract.id;
})
.catch(error => {
if (error.error.code !== 'AlreadySubscribed') {
doUntilDone(() => this.api.send({ proposal_open_contract: 1, contract_id, subscribe: 1 })).then(
doUntilDone(() => api_base.api.send(request_object)).then(
response => (this.openContractId = response.proposal_open_contract.id)
);
}
Expand Down
Loading

0 comments on commit 1abb7de

Please sign in to comment.