frequi_origin/src/stores/ftbot.ts

934 lines
32 KiB
TypeScript
Raw Normal View History

2022-04-17 17:48:51 +00:00
import { useApi } from '@/shared/apiService';
import { useUserService } from '@/shared/userService';
2022-04-17 08:21:19 +00:00
import {
BotState,
Trade,
PlotConfig,
StrategyResult,
BalanceInterface,
DailyReturnValue,
LockResponse,
ProfitInterface,
BacktestResult,
StrategyBacktestResult,
BacktestSteps,
LogLine,
SysInfoResponse,
LoadingStatus,
BacktestHistoryEntry,
2022-04-17 08:28:48 +00:00
RunModes,
2022-04-17 17:48:51 +00:00
DailyPayload,
BlacklistResponse,
WhitelistResponse,
StrategyListResult,
AvailablePairPayload,
AvailablePairResult,
PairHistoryPayload,
PairCandlePayload,
StatusResponse,
ForceSellPayload,
DeleteTradeResponse,
BacktestStatus,
BacktestPayload,
BlacklistPayload,
ForceEnterPayload,
TradeResponse,
2022-04-28 17:21:30 +00:00
ClosedTrade,
2022-04-17 08:21:19 +00:00
} from '@/types';
import axios, { AxiosResponse } from 'axios';
2022-04-17 08:21:19 +00:00
import { defineStore } from 'pinia';
2022-04-17 17:48:51 +00:00
import { showAlert } from './alerts';
2022-09-13 20:13:15 +00:00
import { useWebSocket } from '@vueuse/core';
2022-12-05 06:06:57 +00:00
import { FTWsMessage, FtWsMessageTypes } from '@/types/wsMessageTypes';
2022-12-06 05:59:24 +00:00
import { showNotification } from '@/shared/notifications';
import { FreqAIModelListResult } from '../types/types';
2022-04-17 17:48:51 +00:00
export function createBotSubStore(botId: string, botName: string) {
const userService = useUserService(botId);
const { api } = useApi(userService, botId);
2022-04-17 08:21:19 +00:00
2022-04-17 17:48:51 +00:00
const useBotStore = defineStore(botId, {
state: () => {
return {
2022-09-13 20:13:15 +00:00
websocketStarted: false,
isSelected: true,
2022-04-17 17:48:51 +00:00
ping: '',
botStatusAvailable: false,
isBotOnline: false,
2022-12-08 18:54:43 +00:00
isBotLoggedIn: true,
2022-04-17 17:48:51 +00:00
autoRefresh: false,
refreshing: false,
2022-04-18 08:53:07 +00:00
versionState: '',
2022-04-17 17:48:51 +00:00
lastLogs: [] as LogLine[],
refreshRequired: true,
2022-04-28 17:21:30 +00:00
trades: [] as ClosedTrade[],
2022-04-17 17:48:51 +00:00
openTrades: [] as Trade[],
tradeCount: 0,
performanceStats: [] as Performance[],
whitelist: [] as string[],
blacklist: [] as string[],
profit: {} as ProfitInterface,
botState: {} as BotState,
balance: {} as BalanceInterface,
dailyStats: {} as DailyReturnValue,
pairlistMethods: [] as string[],
2022-04-19 04:33:25 +00:00
detailTradeId: null as number | null,
2022-04-17 17:48:51 +00:00
selectedPair: '',
// TODO: type me
candleData: {},
candleDataStatus: LoadingStatus.loading,
// TODO: type me
history: {},
historyStatus: LoadingStatus.loading,
strategyPlotConfig: undefined as PlotConfig | undefined,
strategyList: [] as string[],
freqaiModelList: [] as string[],
2022-04-17 17:48:51 +00:00
strategy: {} as StrategyResult,
pairlist: [] as string[],
currentLocks: undefined as LockResponse | undefined,
// backtesting
backtestRunning: false,
backtestProgress: 0.0,
backtestStep: BacktestSteps.none,
backtestTradeCount: 0,
backtestResult: undefined as BacktestResult | undefined,
selectedBacktestResultKey: '',
backtestHistory: {} as Record<string, StrategyBacktestResult>,
backtestHistoryList: [] as BacktestHistoryEntry[],
2022-04-21 18:18:28 +00:00
sysInfo: {} as SysInfoResponse,
2022-04-17 17:48:51 +00:00
};
2022-04-17 08:28:48 +00:00
},
2022-04-17 17:48:51 +00:00
getters: {
2022-04-18 08:53:07 +00:00
version: (state) => state.botState?.version || state.versionState,
2022-04-17 17:48:51 +00:00
botApiVersion: (state) => state.botState?.api_version || 1.0,
stakeCurrency: (state) => state.botState?.stake_currency || '',
stakeCurrencyDecimals: (state) => state.botState?.stake_currency_decimals || 3,
canRunBacktest: (state) => state.botState?.runmode === RunModes.WEBSERVER,
isWebserverMode: (state) => state.botState?.runmode === RunModes.WEBSERVER,
selectedBacktestResult: (state) => state.backtestHistory[state.selectedBacktestResultKey],
shortAllowed: (state) => state.botState?.short_allowed || false,
2022-04-18 11:13:45 +00:00
openTradeCount: (state) => state.openTrades.length,
2022-04-17 17:48:51 +00:00
isTrading: (state) =>
state.botState?.runmode === RunModes.LIVE || state.botState?.runmode === RunModes.DRY_RUN,
timeframe: (state) => state.botState?.timeframe || '',
closedTrades: (state) => {
return state.trades
.filter((item) => !item.is_open)
.sort((a, b) =>
// Sort by close timestamp, then by tradeid
b.close_timestamp && a.close_timestamp
? b.close_timestamp - a.close_timestamp
: b.trade_id - a.trade_id,
);
},
tradeDetail: (state): Trade | undefined => {
2022-11-19 13:39:46 +00:00
// console.log('tradeDetail', state.openTrades.length, state.openTrades);
2022-04-17 17:48:51 +00:00
let dTrade = state.openTrades.find((item) => item.trade_id === state.detailTradeId);
if (!dTrade) {
dTrade = state.trades.find((item) => item.trade_id === state.detailTradeId);
}
return dTrade;
2022-04-17 17:48:51 +00:00
},
refreshNow: (state) => {
2022-04-18 17:46:53 +00:00
if (
state.autoRefresh &&
state.isBotOnline &&
state.botState?.runmode !== RunModes.WEBSERVER
) {
return true;
}
2022-04-17 17:48:51 +00:00
return false;
},
botName: (state) => state.botState?.bot_name || 'freqtrade',
2022-04-18 17:30:17 +00:00
allTrades: (state) => [...state.openTrades, ...state.trades] as Trade[],
2022-04-18 18:00:36 +00:00
activeLocks: (state) => state.currentLocks?.locks || [],
2022-09-27 16:11:58 +00:00
dailyStatsSorted: (state): DailyReturnValue => {
return {
...state.dailyStats,
data: state.dailyStats.data
? Object.values(state.dailyStats.data).sort((a, b) => (a.date > b.date ? 1 : -1))
: [],
2022-09-27 16:11:58 +00:00
};
},
2022-04-17 08:28:48 +00:00
},
2022-04-17 17:48:51 +00:00
actions: {
botAdded() {
this.autoRefresh = userService.getAutoRefresh();
},
2022-04-21 18:18:28 +00:00
async fetchPing() {
2022-04-17 17:48:51 +00:00
try {
const result = await api.get('/ping');
const now = Date.now();
// TODO: Name collision!
2022-04-21 18:18:28 +00:00
this.ping = `${result.data.status} ${now.toString()}`;
2023-04-02 13:26:05 +00:00
this.setIsBotOnline(true);
2022-04-17 17:48:51 +00:00
return Promise.resolve();
} catch (error) {
2022-04-21 18:18:28 +00:00
console.log('ping fail');
2023-04-02 13:26:05 +00:00
this.setIsBotOnline(false);
2022-04-17 17:48:51 +00:00
return Promise.reject();
}
},
logout() {
userService.logout();
},
getLoginInfo() {
return userService.getLoginInfo();
},
2022-04-17 17:48:51 +00:00
rename(name: string) {
userService.renameBot(name);
},
setAutoRefresh(newRefreshValue) {
this.autoRefresh = newRefreshValue;
// TODO: Investigate this -
// this ONLY works if ReloadControl is only visible once,otherwise it triggers twice
if (newRefreshValue) {
2022-04-28 04:46:22 +00:00
this.refreshFrequent();
this.refreshSlow(true);
2022-04-17 17:48:51 +00:00
}
userService.setAutoRefresh(newRefreshValue);
},
setIsBotOnline(isBotOnline: boolean) {
this.isBotOnline = isBotOnline;
},
2022-04-18 08:53:07 +00:00
async refreshSlow(forceUpdate = false) {
2022-04-18 17:46:53 +00:00
if (this.refreshing && !forceUpdate) {
return;
}
// Refresh data only when needed
if (forceUpdate || this.refreshRequired) {
2022-05-12 17:25:01 +00:00
try {
this.refreshing = true;
// TODO: Should be AxiosInstance
2022-12-27 13:39:35 +00:00
const updates: Promise<unknown>[] = [];
updates.push(this.getState());
2022-05-12 17:25:01 +00:00
updates.push(this.getPerformance());
updates.push(this.getProfit());
updates.push(this.getTrades());
updates.push(this.getBalance());
// /* white/blacklist might be refreshed more often as they are not expensive on the backend */
updates.push(this.getWhitelist());
updates.push(this.getBlacklist());
await Promise.all(updates);
this.refreshRequired = false;
} finally {
this.refreshing = false;
}
2022-04-18 17:46:53 +00:00
}
return Promise.resolve();
},
async refreshFrequent() {
2022-04-17 17:48:51 +00:00
// Refresh data that's needed in near realtime
2022-04-18 17:46:53 +00:00
await this.getOpenTrades();
await this.getLocks();
2022-04-17 17:48:51 +00:00
},
2022-04-19 04:33:25 +00:00
setDetailTrade(trade: Trade | null) {
this.detailTradeId = trade?.trade_id || null;
2022-04-17 17:48:51 +00:00
this.selectedPair = trade ? trade.pair : this.selectedPair;
},
async getTrades() {
try {
let totalTrades = 0;
const pageLength = 500;
const fetchTrades = async (limit: number, offset: number) => {
return api.get<TradeResponse>('/trades', {
params: { limit, offset },
});
};
const res = await fetchTrades(pageLength, 0);
const result: TradeResponse = res.data;
let { trades } = result;
2022-04-28 17:38:51 +00:00
if (Array.isArray(trades)) {
if (trades.length !== result.total_trades) {
// Pagination necessary
// Don't use Promise.all - this would fire all requests at once, which can
// cause problems for big sqlite databases
do {
// eslint-disable-next-line no-await-in-loop
const res = await fetchTrades(pageLength, trades.length);
2022-04-17 17:48:51 +00:00
2022-04-28 17:38:51 +00:00
const result: TradeResponse = res.data;
trades = trades.concat(result.trades);
totalTrades = res.data.total_trades;
} while (trades.length !== totalTrades);
}
const tradesCount = trades.length;
// Add botId to all trades
trades = trades.map((t) => ({
...t,
botId,
botName,
botTradeId: `${botId}__${t.trade_id}`,
}));
this.trades = trades;
this.tradeCount = tradesCount;
2022-04-17 17:48:51 +00:00
}
return Promise.resolve();
} catch (error) {
if (axios.isAxiosError(error)) {
console.error(error.response);
}
return Promise.reject(error);
}
},
async getOpenTrades() {
try {
const { data } = await api.get<never, AxiosResponse<Trade[]>>('/status');
// Check if trade-id's are different in this call, then trigger a full refresh
if (
Array.isArray(this.openTrades) &&
Array.isArray(data) &&
(this.openTrades.length !== data.length ||
!this.openTrades.every((val, index) => val.trade_id === data[index].trade_id))
) {
// Open trades changed, so we should refresh now.
this.refreshRequired = true;
this.refreshSlow(false);
}
if (Array.isArray(data)) {
const openTrades = data.map((t) => ({
...t,
botId,
botName,
botTradeId: `${botId}__${t.trade_id}`,
// eslint-disable-next-line @typescript-eslint/camelcase
profit_ratio: t.profit_ratio ?? -1,
}));
// TODO Don't force-patch profit_ratio but handle null values properly
this.openTrades = openTrades;
if (this.selectedPair === '') {
this.selectedPair = openTrades[0]?.pair || '';
2022-04-28 17:38:51 +00:00
}
}
} catch (data) {
return console.error(data);
}
2022-04-17 17:48:51 +00:00
},
getLocks() {
return api
.get('/locks')
.then((result) => (this.currentLocks = result.data))
.catch(console.error);
},
async deleteLock(lockid: string) {
try {
const res = await api.delete<LockResponse>(`/locks/${lockid}`);
showAlert(`Deleted Lock ${lockid}.`);
this.currentLocks = res.data;
return Promise.resolve(res);
} catch (error) {
if (axios.isAxiosError(error)) {
console.error(error.response);
}
showAlert(`Failed to delete lock ${lockid}`, 'danger');
return Promise.reject(error);
}
},
getPairCandles(payload: PairCandlePayload) {
if (payload.pair && payload.timeframe) {
this.candleDataStatus = LoadingStatus.loading;
2022-04-17 17:48:51 +00:00
return api
.get('/pair_candles', {
params: { ...payload },
})
.then((result) => {
this.candleData = {
...this.candleData,
[`${payload.pair}__${payload.timeframe}`]: {
pair: payload.pair,
timeframe: payload.timeframe,
data: result.data,
},
};
this.candleDataStatus = LoadingStatus.success;
2022-04-17 17:48:51 +00:00
})
.catch((err) => {
console.error(err);
this.candleDataStatus = LoadingStatus.error;
2022-04-17 17:48:51 +00:00
});
}
// Error branchs
const error = 'pair or timeframe not specified';
console.error(error);
return new Promise((resolve, reject) => {
reject(error);
});
},
getPairHistory(payload: PairHistoryPayload) {
if (payload.pair && payload.timeframe && payload.timerange) {
this.historyStatus = LoadingStatus.loading;
return api
.get('/pair_history', {
params: { ...payload },
timeout: 50000,
})
.then((result) => {
this.history = {
[`${payload.pair}__${payload.timeframe}`]: {
pair: payload.pair,
timeframe: payload.timeframe,
timerange: payload.timerange,
data: result.data,
},
};
this.historyStatus = LoadingStatus.success;
})
.catch((err) => {
console.error(err);
this.historyStatus = LoadingStatus.error;
});
}
// Error branchs
const error = 'pair or timeframe or timerange not specified';
console.error(error);
return new Promise((resolve, reject) => {
reject(error);
});
},
async getStrategyPlotConfig() {
try {
const payload = {};
if (this.isWebserverMode) {
if (!this.strategy.strategy) {
return Promise.reject({ data: 'No strategy selected' });
}
payload['strategy'] = this.strategy.strategy;
}
const { data: plotConfig } = await api.get<PlotConfig>('/plot_config', {
params: { ...payload },
});
2022-04-17 17:48:51 +00:00
if (plotConfig.subplots === null) {
// Subplots should not be null but an empty object
// TODO: Remove this fix when fix in freqtrade is populated further.
plotConfig.subplots = {};
}
this.strategyPlotConfig = plotConfig;
2022-04-17 17:48:51 +00:00
return Promise.resolve();
} catch (data) {
console.error(data);
return Promise.reject(data);
}
},
2022-09-10 12:56:21 +00:00
async getStrategyList() {
try {
const { data } = await api.get<StrategyListResult>('/strategies');
this.strategyList = data.strategies;
return Promise.resolve(data);
} catch (error) {
console.error(error);
return Promise.reject(error);
}
2022-04-17 17:48:51 +00:00
},
async getStrategy(strategy: string) {
try {
2022-09-10 12:56:21 +00:00
const { data } = await api.get<StrategyResult>(`/strategy/${strategy}`, {});
this.strategy = data;
return Promise.resolve(data);
2022-04-17 17:48:51 +00:00
} catch (error) {
console.error(error);
return Promise.reject(error);
}
},
async getFreqAIModelList() {
try {
const { data } = await api.get<FreqAIModelListResult>('/freqaimodels');
this.freqaiModelList = data.freqaimodels;
return Promise.resolve(data);
} catch (error) {
console.error(error);
2022-04-17 17:48:51 +00:00
return Promise.reject(error);
}
},
async getAvailablePairs(payload: AvailablePairPayload) {
try {
2022-09-10 12:56:21 +00:00
const { data } = await api.get<AvailablePairResult>('/available_pairs', {
2022-04-17 17:48:51 +00:00
params: { ...payload },
});
// result is of type AvailablePairResult
2022-09-10 12:56:21 +00:00
this.pairlist = data.pairs;
return Promise.resolve(data);
2022-04-17 17:48:51 +00:00
} catch (error) {
console.error(error);
return Promise.reject(error);
}
},
async getPerformance() {
try {
2022-09-10 12:56:21 +00:00
const { data } = await api.get<Performance[]>('/performance');
this.performanceStats = data;
return Promise.resolve(data);
2022-04-17 17:48:51 +00:00
} catch (error) {
console.error(error);
return Promise.reject(error);
}
},
2022-09-10 12:56:21 +00:00
async getWhitelist() {
try {
const { data } = await api.get<WhitelistResponse>('/whitelist');
this.whitelist = data.whitelist;
this.pairlistMethods = data.method;
return Promise.resolve(data);
} catch (error) {
return Promise.reject(error);
}
2022-04-17 17:48:51 +00:00
},
2022-09-10 12:56:21 +00:00
async getBlacklist() {
try {
const { data } = await api.get<BlacklistResponse>('/blacklist');
this.blacklist = data.blacklist;
return Promise.resolve(data);
} catch (error) {
return Promise.reject(error);
}
2022-04-17 17:48:51 +00:00
},
2022-09-10 12:56:21 +00:00
async getProfit() {
try {
const { data } = await api.get('/profit');
this.profit = data;
return Promise.resolve(data);
} catch (error) {
console.error(error);
return Promise.reject(error);
}
2022-04-17 17:48:51 +00:00
},
async getBalance() {
try {
2022-09-10 12:56:21 +00:00
const { data } = await api.get('/balance');
this.balance = data;
return Promise.resolve(data);
2022-04-17 17:48:51 +00:00
} catch (error) {
return Promise.reject(error);
}
},
2022-04-22 18:02:50 +00:00
async getDaily(payload: DailyPayload = {}) {
2022-04-17 17:48:51 +00:00
const { timescale = 20 } = payload;
2022-04-22 18:02:50 +00:00
try {
const { data } = await api.get<DailyReturnValue>('/daily', { params: { timescale } });
this.dailyStats = data;
return Promise.resolve(data);
} catch (error) {
2022-09-10 12:56:21 +00:00
console.error(error);
2022-04-22 18:02:50 +00:00
return Promise.reject(error);
}
2022-04-17 17:48:51 +00:00
},
2022-09-10 12:56:21 +00:00
async getState() {
try {
const { data } = await api.get('/show_config');
this.botState = data;
this.botStatusAvailable = true;
2022-09-13 20:13:15 +00:00
this.startWebSocket();
2022-09-10 12:56:21 +00:00
return Promise.resolve(data);
} catch (error) {
console.error(error);
return Promise.reject(error);
}
2022-04-17 17:48:51 +00:00
},
2022-09-10 12:56:21 +00:00
async getLogs() {
try {
const { data } = await api.get('/logs');
this.lastLogs = data.logs;
return Promise.resolve(data);
} catch (error) {
console.error(error);
return Promise.reject(error);
}
2022-04-17 17:48:51 +00:00
},
// // Post methods
2022-04-19 18:24:14 +00:00
// // TODO: Migrate calls to API to a seperate module unrelated to pinia?
2022-04-17 17:48:51 +00:00
async startBot() {
try {
2022-09-10 12:56:21 +00:00
const { data } = await api.post<{}, AxiosResponse<StatusResponse>>('/start', {});
console.log(data);
showAlert(data.status);
return Promise.resolve(data);
2022-04-17 17:48:51 +00:00
} catch (error) {
if (axios.isAxiosError(error)) {
console.error(error.response);
}
2022-12-05 05:58:36 +00:00
showAlert('Error starting bot.', 'danger');
2022-04-17 17:48:51 +00:00
return Promise.reject(error);
}
},
async stopBot() {
try {
const res = await api.post<{}, AxiosResponse<StatusResponse>>('/stop', {});
showAlert(res.data.status);
return Promise.resolve(res);
} catch (error) {
if (axios.isAxiosError(error)) {
console.error(error.response);
}
2022-12-05 05:58:36 +00:00
showAlert('Error stopping bot.', 'danger');
2022-04-17 17:48:51 +00:00
return Promise.reject(error);
}
},
async stopBuy() {
try {
const res = await api.post<{}, AxiosResponse<StatusResponse>>('/stopbuy', {});
showAlert(res.data.status);
return Promise.resolve(res);
} catch (error) {
if (axios.isAxiosError(error)) {
console.error(error.response);
}
2022-12-05 05:58:36 +00:00
showAlert('Error calling stopbuy.', 'danger');
2022-04-17 17:48:51 +00:00
return Promise.reject(error);
}
},
async reloadConfig() {
try {
const res = await api.post<{}, AxiosResponse<StatusResponse>>('/reload_config', {});
console.log(res.data);
showAlert(res.data.status);
return Promise.resolve(res);
} catch (error) {
if (axios.isAxiosError(error)) {
console.error(error.response);
}
2022-12-05 05:58:36 +00:00
showAlert('Error reloading.', 'danger');
2022-04-17 17:48:51 +00:00
return Promise.reject(error);
}
},
async deleteTrade(tradeid: string) {
try {
const res = await api.delete<DeleteTradeResponse>(`/trades/${tradeid}`);
showAlert(res.data.result_msg ? res.data.result_msg : `Deleted Trade ${tradeid}`);
return Promise.resolve(res);
} catch (error) {
if (axios.isAxiosError(error)) {
console.error(error.response);
}
showAlert(`Failed to delete trade ${tradeid}`, 'danger');
return Promise.reject(error);
}
},
2023-01-31 06:11:34 +00:00
async cancelOpenOrder(tradeid: string) {
try {
const res = await api.delete<DeleteTradeResponse>(`/trades/${tradeid}/open-order`);
showAlert(
res.data.result_msg ? res.data.result_msg : `Canceled open order for ${tradeid}`,
);
return Promise.resolve(res);
} catch (error) {
if (axios.isAxiosError(error)) {
console.error(error.response);
}
showAlert(`Failed to cancel open order ${tradeid}`, 'danger');
return Promise.reject(error);
}
},
2022-04-17 17:48:51 +00:00
async startTrade() {
try {
const res = await api.post('/start_trade', {});
return Promise.resolve(res);
} catch (error) {
return Promise.reject(error);
}
},
2022-04-21 17:36:12 +00:00
async forceexit(payload: ForceSellPayload) {
2022-04-17 17:48:51 +00:00
try {
const res = await api.post<ForceSellPayload, AxiosResponse<StatusResponse>>(
'/forcesell',
payload,
);
2022-12-05 05:58:36 +00:00
showAlert(`Exit order for ${payload.tradeid} created`, 'success');
2022-04-17 17:48:51 +00:00
return Promise.resolve(res);
} catch (error) {
if (axios.isAxiosError(error)) {
console.error(error.response);
}
2022-08-03 04:57:17 +00:00
showAlert(`Failed to create exit order for ${payload.tradeid}`, 'danger');
2022-04-17 17:48:51 +00:00
return Promise.reject(error);
}
},
2022-05-25 17:20:18 +00:00
async forceentry(payload: ForceEnterPayload) {
2022-04-17 17:48:51 +00:00
if (payload && payload.pair) {
try {
// TODO: Update forcebuy to forceenter ...
const res = await api.post<
ForceEnterPayload,
AxiosResponse<StatusResponse | TradeResponse>
>('/forcebuy', payload);
2022-12-05 05:58:36 +00:00
showAlert(`Order for ${payload.pair} created.`, 'success');
2022-04-17 17:48:51 +00:00
return Promise.resolve(res);
} catch (error) {
if (axios.isAxiosError(error)) {
console.error(error.response);
showAlert(
`Error occured entering: '${(error as any).response?.data?.error}'`,
'danger',
);
}
return Promise.reject(error);
}
}
// Error branchs
const error = 'Pair is empty';
console.error(error);
return Promise.reject(error);
},
async addBlacklist(payload: BlacklistPayload) {
console.log(`Adding ${payload} to blacklist`);
if (payload && payload.blacklist) {
try {
const result = await api.post<BlacklistPayload, AxiosResponse<BlacklistResponse>>(
'/blacklist',
payload,
);
this.blacklist = result.data.blacklist;
if (result.data.errors && Object.keys(result.data.errors).length !== 0) {
const { errors } = result.data;
Object.keys(errors).forEach((pair) => {
showAlert(
`Error while adding pair ${pair} to Blacklist: ${errors[pair].error_msg}`,
2022-12-05 05:58:36 +00:00
'danger',
2022-04-17 17:48:51 +00:00
);
});
} else {
showAlert(`Pair ${payload.blacklist} added.`);
}
return Promise.resolve(result.data);
} catch (error) {
if (axios.isAxiosError(error)) {
console.error(error.response);
showAlert(
`Error occured while adding pairs to Blacklist: '${
(error as any).response?.data?.error
}'`,
'danger',
);
}
return Promise.reject(error);
}
}
// Error branchs
const error = 'Pair is empty';
console.error(error);
return Promise.reject(error);
},
async deleteBlacklist(blacklistPairs: Array<string>) {
console.log(`Deleting ${blacklistPairs} from blacklist.`);
if (blacklistPairs) {
try {
const result = await api.delete<BlacklistPayload, AxiosResponse<BlacklistResponse>>(
'/blacklist',
{
params: {
// eslint-disable-next-line @typescript-eslint/camelcase
pairs_to_delete: blacklistPairs,
},
paramsSerializer: {
indexes: null,
},
2022-04-17 17:48:51 +00:00
},
);
this.blacklist = result.data.blacklist;
if (result.data.errors && Object.keys(result.data.errors).length !== 0) {
const { errors } = result.data;
Object.keys(errors).forEach((pair) => {
showAlert(
`Error while removing pair ${pair} from Blacklist: ${errors[pair].error_msg}`,
2022-12-05 05:58:36 +00:00
'danger',
2022-04-17 17:48:51 +00:00
);
});
} else {
showAlert(`Pair ${blacklistPairs} removed.`);
}
return Promise.resolve(result.data);
} catch (error) {
if (axios.isAxiosError(error)) {
console.error(error.response);
showAlert(
`Error occured while removing pairs from Blacklist: '${
(error as any).response?.data?.error
}'`,
'danger',
);
}
return Promise.reject(error);
}
}
// Error branchs
const error = 'Pair is empty';
console.error(error);
return Promise.reject(error);
},
async startBacktest(payload: BacktestPayload) {
try {
const result = await api.post<BacktestPayload, AxiosResponse<BacktestStatus>>(
'/backtest',
payload,
);
this.updateBacktestRunning(result.data);
} catch (err) {
console.log(err);
}
},
async pollBacktest() {
const { data } = await api.get<BacktestStatus>('/backtest');
2022-04-17 17:48:51 +00:00
this.updateBacktestRunning(data);
if (data.running === false && data.backtest_result) {
this.updateBacktestResult(data.backtest_result);
}
if (data.status === 'error') {
showAlert(`Backtest failed: ${data.status_msg}.`, 'danger');
2022-04-17 17:48:51 +00:00
}
},
async removeBacktest() {
this.backtestHistory = {};
try {
const { data } = await api.delete<BacktestStatus>('/backtest');
this.updateBacktestRunning(data);
return Promise.resolve(data);
} catch (err) {
return Promise.reject(err);
}
},
updateBacktestRunning(backtestStatus: BacktestStatus) {
this.backtestRunning = backtestStatus.running;
this.backtestProgress = backtestStatus.progress;
this.backtestStep = backtestStatus.step;
this.backtestTradeCount = backtestStatus.trade_count || 0;
},
async stopBacktest() {
try {
const { data } = await api.get<BacktestStatus>('/backtest/abort');
this.updateBacktestRunning(data);
return Promise.resolve(data);
} catch (err) {
return Promise.reject(err);
}
},
async getBacktestHistory() {
const result = await api.get<BacktestHistoryEntry[]>('/backtest/history');
this.backtestHistoryList = result.data;
},
updateBacktestResult(backtestResult: BacktestResult) {
this.backtestResult = backtestResult;
// TODO: Properly identify duplicates to avoid pushing the same multiple times
Object.entries(backtestResult.strategy).forEach(([key, strat]) => {
console.log(key, strat);
2022-04-17 08:28:48 +00:00
2022-04-17 17:48:51 +00:00
const stratKey = `${key}_${strat.total_trades}_${strat.profit_total.toFixed(3)}`;
// this.backtestHistory[stratKey] = strat;
this.backtestHistory = { ...this.backtestHistory, ...{ [stratKey]: strat } };
this.selectedBacktestResultKey = stratKey;
});
},
async getBacktestHistoryResult(payload: BacktestHistoryEntry) {
const result = await api.get<BacktestStatus>('/backtest/history/result', {
params: { filename: payload.filename, strategy: payload.strategy },
});
if (result.data.backtest_result) {
this.updateBacktestResult(result.data.backtest_result);
}
},
setBacktestResultKey(key: string) {
this.selectedBacktestResultKey = key;
},
2022-04-21 18:18:28 +00:00
async getSysInfo() {
2022-04-17 17:48:51 +00:00
try {
2022-04-21 18:18:28 +00:00
const { data } = await api.get<SysInfoResponse>('/sysinfo');
2022-04-17 17:48:51 +00:00
this.sysInfo = data;
return Promise.resolve(data);
} catch (err) {
return Promise.reject(err);
}
},
2022-10-30 14:05:13 +00:00
_handleWebsocketMessage(ws, event: MessageEvent<any>) {
2022-12-05 06:06:57 +00:00
const msg: FTWsMessage = JSON.parse(event.data);
2022-12-05 05:55:41 +00:00
switch (msg.type) {
case FtWsMessageTypes.whitelist:
this.whitelist = msg.data;
break;
case FtWsMessageTypes.entryFill:
case FtWsMessageTypes.exitFill:
2022-12-06 05:59:24 +00:00
case FtWsMessageTypes.exitCancel:
case FtWsMessageTypes.entryCancel:
2022-12-07 06:18:32 +00:00
showNotification(msg, botName);
2022-12-05 05:55:41 +00:00
break;
2022-12-05 06:06:57 +00:00
case FtWsMessageTypes.newCandle:
2022-12-05 18:58:25 +00:00
const [pair, timeframe] = msg.data;
2022-12-05 17:29:02 +00:00
// TODO: check for active bot ...
if (pair === this.selectedPair) {
// Reload pair candles
this.getPairCandles({ pair, timeframe, limit: 500 });
}
2022-12-05 06:06:57 +00:00
break;
2022-12-06 05:59:24 +00:00
2022-12-05 05:55:41 +00:00
default:
// Unhandled events ...
2022-12-05 06:06:57 +00:00
console.log(`Received event ${(msg as any).type}`);
2022-12-05 05:55:41 +00:00
break;
2022-10-30 14:05:13 +00:00
}
},
2022-09-13 20:13:15 +00:00
startWebSocket() {
if (
this.websocketStarted === true ||
this.botStatusAvailable === false ||
this.botApiVersion < 2.2
) {
return;
}
2022-12-27 13:39:35 +00:00
const { send, close } = useWebSocket(
2022-09-13 20:13:15 +00:00
// 'ws://localhost:8080/api/v1/message/ws?token=testtoken',
`${userService.getBaseWsUrl()}/message/ws?token=${userService.getAccessToken()}`,
{
autoReconnect: {
delay: 10000,
// retries: 10
},
// heartbeat: {
// message: JSON.stringify({ type: 'ping' }),
// interval: 10000,
// },
onError: (ws, event) => {
console.log('onError', event, ws);
this.websocketStarted = false;
close();
},
2022-10-30 14:05:13 +00:00
onMessage: this._handleWebsocketMessage,
2022-09-13 20:13:15 +00:00
onConnected: () => {
console.log('subscribing');
this.websocketStarted = true;
2022-12-05 18:58:25 +00:00
const subscriptions = [
FtWsMessageTypes.whitelist,
FtWsMessageTypes.entryFill,
FtWsMessageTypes.exitFill,
2022-12-06 05:33:56 +00:00
FtWsMessageTypes.entryCancel,
FtWsMessageTypes.exitCancel,
2022-12-05 18:58:25 +00:00
/*'new_candle' /*'analyzed_df'*/
];
if (this.botApiVersion >= 2.21) {
subscriptions.push(FtWsMessageTypes.newCandle);
}
2022-09-13 20:13:15 +00:00
send(
JSON.stringify({
type: 'subscribe',
2022-12-05 18:58:25 +00:00
data: subscriptions,
2022-09-13 20:13:15 +00:00
}),
);
send(
JSON.stringify({
2022-10-30 14:05:13 +00:00
type: FtWsMessageTypes.whitelist,
2022-09-13 20:13:15 +00:00
data: '',
}),
);
},
},
);
},
2022-04-17 08:28:48 +00:00
},
2022-04-17 17:48:51 +00:00
});
2022-09-13 20:13:15 +00:00
2022-04-17 17:48:51 +00:00
return useBotStore();
}