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,
|
2023-09-02 15:42:44 +00:00
|
|
|
TimeSummaryReturnValue,
|
2022-04-17 08:21:19 +00:00
|
|
|
LockResponse,
|
|
|
|
ProfitInterface,
|
|
|
|
BacktestResult,
|
|
|
|
BacktestSteps,
|
|
|
|
LogLine,
|
|
|
|
SysInfoResponse,
|
|
|
|
LoadingStatus,
|
|
|
|
BacktestHistoryEntry,
|
2022-04-17 08:28:48 +00:00
|
|
|
RunModes,
|
2023-09-02 15:42:44 +00:00
|
|
|
TimeSummaryPayload,
|
2022-04-17 17:48:51 +00:00
|
|
|
BlacklistResponse,
|
|
|
|
WhitelistResponse,
|
|
|
|
StrategyListResult,
|
|
|
|
AvailablePairPayload,
|
|
|
|
AvailablePairResult,
|
|
|
|
PairHistoryPayload,
|
|
|
|
PairCandlePayload,
|
|
|
|
StatusResponse,
|
|
|
|
ForceSellPayload,
|
|
|
|
DeleteTradeResponse,
|
|
|
|
BacktestStatus,
|
|
|
|
BacktestPayload,
|
|
|
|
BlacklistPayload,
|
|
|
|
ForceEnterPayload,
|
|
|
|
TradeResponse,
|
2022-04-28 17:21:30 +00:00
|
|
|
ClosedTrade,
|
2023-04-18 17:26:31 +00:00
|
|
|
BotDescriptor,
|
2023-05-31 18:22:36 +00:00
|
|
|
BgTaskStarted,
|
|
|
|
BackgroundTaskStatus,
|
2023-06-03 10:32:05 +00:00
|
|
|
Exchange,
|
|
|
|
ExchangeListResult,
|
|
|
|
FreqAIModelListResult,
|
2023-06-04 06:36:35 +00:00
|
|
|
PairlistEvalResponse,
|
|
|
|
PairlistsPayload,
|
|
|
|
PairlistsResponse,
|
2023-07-30 07:47:59 +00:00
|
|
|
BacktestResultInMemory,
|
2023-07-30 07:56:32 +00:00
|
|
|
BacktestMetadataWithStrategyName,
|
2023-07-31 19:18:54 +00:00
|
|
|
BacktestMetadataPatch,
|
|
|
|
BacktestResultUpdate,
|
2023-09-03 07:06:46 +00:00
|
|
|
TimeSummaryOptions,
|
2022-04-17 08:21:19 +00:00
|
|
|
} from '@/types';
|
2022-05-04 18:43:14 +00:00
|
|
|
import axios, { AxiosResponse } from 'axios';
|
2022-04-17 08:21:19 +00:00
|
|
|
import { defineStore } from 'pinia';
|
2023-09-23 15:06:24 +00:00
|
|
|
import { useAlertForBot } from '../shared/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';
|
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
|
|
|
|
2023-09-23 15:06:24 +00:00
|
|
|
const { showAlert } = useAlertForBot(botName);
|
|
|
|
|
2022-04-17 17:48:51 +00:00
|
|
|
const useBotStore = defineStore(botId, {
|
|
|
|
state: () => {
|
|
|
|
return {
|
2022-09-13 20:13:15 +00:00
|
|
|
websocketStarted: false,
|
2022-05-04 18:43:14 +00:00
|
|
|
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,
|
2023-09-02 15:42:44 +00:00
|
|
|
dailyStats: {} as TimeSummaryReturnValue,
|
2023-09-03 07:06:46 +00:00
|
|
|
weeklyStats: {} as TimeSummaryReturnValue,
|
|
|
|
monthlyStats: {} as TimeSummaryReturnValue,
|
2022-04-17 17:48:51 +00:00
|
|
|
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[],
|
2022-12-20 06:13:08 +00:00
|
|
|
freqaiModelList: [] as string[],
|
2023-06-03 10:32:05 +00:00
|
|
|
exchangeList: [] as Exchange[],
|
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: '',
|
2023-07-30 07:47:59 +00:00
|
|
|
backtestHistory: {} as Record<string, BacktestResultInMemory>,
|
2022-04-17 17:48:51 +00:00
|
|
|
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,
|
2023-07-30 07:47:59 +00:00
|
|
|
selectedBacktestResult: (state) =>
|
|
|
|
state.backtestHistory[state.selectedBacktestResultKey]?.strategy || {},
|
2022-04-17 17:48:51 +00:00
|
|
|
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,
|
|
|
|
);
|
|
|
|
},
|
2023-01-24 06:03:01 +00:00
|
|
|
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);
|
|
|
|
}
|
2023-01-24 06:03:01 +00:00
|
|
|
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-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();
|
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();
|
|
|
|
},
|
2022-12-10 11:51:28 +00:00
|
|
|
getLoginInfo() {
|
|
|
|
return userService.getLoginInfo();
|
|
|
|
},
|
2023-04-18 17:26:31 +00:00
|
|
|
updateBot(updatedBotInfo: Partial<BotDescriptor>) {
|
|
|
|
userService.updateBot(updatedBotInfo);
|
2022-04-17 17:48:51 +00:00
|
|
|
},
|
|
|
|
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>[] = [];
|
2023-02-28 17:05:44 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
},
|
2023-02-27 19:38:06 +00:00
|
|
|
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}`,
|
|
|
|
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
|
|
|
}
|
2023-02-27 19:38:06 +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) {
|
2023-03-07 17:19:44 +00:00
|
|
|
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,
|
|
|
|
},
|
|
|
|
};
|
2023-03-07 17:19:44 +00:00
|
|
|
this.candleDataStatus = LoadingStatus.success;
|
2022-04-17 17:48:51 +00:00
|
|
|
})
|
|
|
|
.catch((err) => {
|
|
|
|
console.error(err);
|
2023-03-07 17:19:44 +00:00
|
|
|
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);
|
|
|
|
});
|
|
|
|
},
|
2023-07-27 04:55:07 +00:00
|
|
|
async getPairHistory(payload: PairHistoryPayload) {
|
2023-04-10 16:20:09 +00:00
|
|
|
if (payload.pair && payload.timeframe) {
|
2022-04-17 17:48:51 +00:00
|
|
|
this.historyStatus = LoadingStatus.loading;
|
2023-07-27 04:55:07 +00:00
|
|
|
try {
|
|
|
|
const { data } = await api.get('/pair_history', {
|
2022-04-17 17:48:51 +00:00
|
|
|
params: { ...payload },
|
|
|
|
timeout: 50000,
|
|
|
|
});
|
2023-07-27 04:55:07 +00:00
|
|
|
this.history = {
|
|
|
|
[`${payload.pair}__${payload.timeframe}`]: {
|
|
|
|
pair: payload.pair,
|
|
|
|
timeframe: payload.timeframe,
|
|
|
|
timerange: payload.timerange,
|
|
|
|
data: data,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
this.historyStatus = LoadingStatus.success;
|
|
|
|
} catch (err) {
|
|
|
|
console.error(err);
|
|
|
|
this.historyStatus = LoadingStatus.error;
|
|
|
|
if (axios.isAxiosError(err)) {
|
|
|
|
console.error(err.response);
|
|
|
|
const errMsg = err.response?.data?.detail ?? 'Error fetching history';
|
|
|
|
showAlert(errMsg, 'danger');
|
|
|
|
}
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
reject(err);
|
|
|
|
});
|
|
|
|
}
|
2022-04-17 17:48:51 +00:00
|
|
|
}
|
|
|
|
// Error branchs
|
|
|
|
const error = 'pair or timeframe or timerange not specified';
|
|
|
|
console.error(error);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
reject(error);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
async getStrategyPlotConfig() {
|
|
|
|
try {
|
2023-01-18 05:43:02 +00:00
|
|
|
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 = {};
|
|
|
|
}
|
2023-01-18 05:43:02 +00:00
|
|
|
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);
|
2023-07-27 04:56:10 +00:00
|
|
|
if (axios.isAxiosError(error)) {
|
|
|
|
console.error(error.response);
|
|
|
|
const errMsg = error.response?.data?.detail ?? 'Error fetching history';
|
|
|
|
showAlert(errMsg, 'warning');
|
|
|
|
}
|
2022-12-20 06:13:08 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
},
|
2023-06-03 10:32:05 +00:00
|
|
|
async getExchangeList() {
|
|
|
|
try {
|
|
|
|
const { data } = await api.get<ExchangeListResult>('/exchanges');
|
|
|
|
this.exchangeList = data.exchanges;
|
|
|
|
return Promise.resolve(data.exchanges);
|
|
|
|
} catch (error) {
|
|
|
|
console.error(error);
|
|
|
|
return Promise.reject(error);
|
|
|
|
}
|
|
|
|
},
|
2022-04-17 17:48:51 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
},
|
2023-09-03 07:06:46 +00:00
|
|
|
async getTimeSummary(aggregation: TimeSummaryOptions, payload: TimeSummaryPayload = {}) {
|
2022-04-17 17:48:51 +00:00
|
|
|
const { timescale = 20 } = payload;
|
2022-04-22 18:02:50 +00:00
|
|
|
try {
|
2023-09-03 07:06:46 +00:00
|
|
|
const { data } = await api.get<TimeSummaryReturnValue>(`/${aggregation}`, {
|
2023-09-02 15:42:44 +00:00
|
|
|
params: { timescale },
|
|
|
|
});
|
2023-09-03 07:06:46 +00:00
|
|
|
if (aggregation === TimeSummaryOptions.daily) {
|
|
|
|
this.dailyStats = data;
|
|
|
|
} else if (aggregation === TimeSummaryOptions.weekly) {
|
|
|
|
this.weeklyStats = data;
|
|
|
|
} else if (aggregation === TimeSummaryOptions.monthly) {
|
|
|
|
this.monthlyStats = data;
|
|
|
|
}
|
|
|
|
|
2022-04-22 18:02:50 +00:00
|
|
|
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 {
|
2023-05-08 18:32:38 +00:00
|
|
|
const { data } = await api.get<BotState>('/show_config');
|
2022-09-10 12:56:21 +00:00
|
|
|
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
|
|
|
},
|
2023-05-23 18:27:33 +00:00
|
|
|
async getPairlists() {
|
|
|
|
try {
|
|
|
|
const { data } = await api.get<PairlistsResponse>('/pairlists/available');
|
|
|
|
return Promise.resolve(data);
|
|
|
|
} catch (error) {
|
|
|
|
console.error(error);
|
|
|
|
return Promise.reject(error);
|
|
|
|
}
|
|
|
|
},
|
2023-05-31 18:22:36 +00:00
|
|
|
async evaluatePairlist(payload: PairlistsPayload) {
|
2023-05-23 18:27:33 +00:00
|
|
|
try {
|
2023-05-31 18:22:36 +00:00
|
|
|
const { data } = await api.post<PairlistsPayload, AxiosResponse<BgTaskStarted>>(
|
|
|
|
'/pairlists/evaluate',
|
|
|
|
payload,
|
|
|
|
);
|
|
|
|
return Promise.resolve(data);
|
|
|
|
} catch (error) {
|
|
|
|
console.error(error);
|
|
|
|
return Promise.reject(error);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async getPairlistEvalResult(jobId: string) {
|
|
|
|
try {
|
|
|
|
const { data } = await api.get<PairlistEvalResponse>(`/pairlists/evaluate/${jobId}`);
|
|
|
|
return Promise.resolve(data);
|
|
|
|
} catch (error) {
|
|
|
|
console.error(error);
|
|
|
|
return Promise.reject(error);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async getBackgroundJobStatus(jobId: string) {
|
|
|
|
try {
|
|
|
|
const { data } = await api.get<BackgroundTaskStatus>(`/background/${jobId}`);
|
2023-05-23 18:27:33 +00:00
|
|
|
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 {
|
2023-04-13 04:44:21 +00:00
|
|
|
const { data } = await api.post<Record<string, never>, AxiosResponse<StatusResponse>>(
|
|
|
|
'/start',
|
|
|
|
{},
|
|
|
|
);
|
2022-09-10 12:56:21 +00:00
|
|
|
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 {
|
2023-04-13 04:44:21 +00:00
|
|
|
const res = await api.post<Record<string, never>, AxiosResponse<StatusResponse>>(
|
|
|
|
'/stop',
|
|
|
|
{},
|
|
|
|
);
|
2022-04-17 17:48:51 +00:00
|
|
|
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 {
|
2023-04-13 04:44:21 +00:00
|
|
|
const res = await api.post<Record<string, never>, AxiosResponse<StatusResponse>>(
|
|
|
|
'/stopbuy',
|
|
|
|
{},
|
|
|
|
);
|
2022-04-17 17:48:51 +00:00
|
|
|
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 {
|
2023-04-13 04:44:21 +00:00
|
|
|
const res = await api.post<Record<string, never>, AxiosResponse<StatusResponse>>(
|
|
|
|
'/reload_config',
|
|
|
|
{},
|
|
|
|
);
|
2022-04-17 17:48:51 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
},
|
2023-05-16 18:23:46 +00:00
|
|
|
async reloadTrade(tradeid: string) {
|
|
|
|
try {
|
|
|
|
const res = await api.post<never, AxiosResponse<Trade>>(`/trades/${tradeid}/reload`);
|
|
|
|
return Promise.resolve(res);
|
|
|
|
} catch (error) {
|
|
|
|
if (axios.isAxiosError(error)) {
|
|
|
|
console.error(error.response);
|
|
|
|
}
|
|
|
|
showAlert(`Failed to reload trade ${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);
|
2023-05-08 18:32:38 +00:00
|
|
|
showAlert(`Error occured entering: '${error.response?.data?.error}'`, 'danger');
|
2022-04-17 17:48:51 +00:00
|
|
|
}
|
|
|
|
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(
|
2023-05-08 18:32:38 +00:00
|
|
|
`Error occured while adding pairs to Blacklist: '${error.response?.data?.error}'`,
|
2022-04-17 17:48:51 +00:00
|
|
|
'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: {
|
|
|
|
pairs_to_delete: blacklistPairs,
|
|
|
|
},
|
2022-10-19 06:10:46 +00:00
|
|
|
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(
|
2023-05-08 18:32:38 +00:00
|
|
|
`Error occured while removing pairs from Blacklist: '${error.response?.data?.error}'`,
|
2022-04-17 17:48:51 +00:00
|
|
|
'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() {
|
2023-02-22 19:12:41 +00:00
|
|
|
const { data } = await api.get<BacktestStatus>('/backtest');
|
2022-04-17 17:48:51 +00:00
|
|
|
|
2023-02-22 19:12:41 +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() {
|
2023-07-25 18:51:17 +00:00
|
|
|
const { data } = await api.get<BacktestHistoryEntry[]>('/backtest/history');
|
|
|
|
this.backtestHistoryList = data;
|
2022-04-17 17:48:51 +00:00
|
|
|
},
|
|
|
|
updateBacktestResult(backtestResult: BacktestResult) {
|
|
|
|
this.backtestResult = backtestResult;
|
|
|
|
Object.entries(backtestResult.strategy).forEach(([key, strat]) => {
|
2023-07-30 07:56:32 +00:00
|
|
|
const metadata: BacktestMetadataWithStrategyName = {
|
2023-07-30 18:04:01 +00:00
|
|
|
...(backtestResult.metadata[key] ?? {}),
|
2023-07-30 07:56:32 +00:00
|
|
|
strategyName: key,
|
2023-07-31 17:23:23 +00:00
|
|
|
notes: backtestResult.metadata[key]?.notes ?? ``,
|
|
|
|
editing: false,
|
2023-07-30 07:56:32 +00:00
|
|
|
};
|
2023-08-02 04:33:05 +00:00
|
|
|
// console.log(key, strat, metadata);
|
2022-04-17 08:28:48 +00:00
|
|
|
|
2023-07-31 19:18:54 +00:00
|
|
|
// Never versions will always have run_id
|
|
|
|
const stratKey =
|
|
|
|
backtestResult.metadata[key].run_id ??
|
|
|
|
`${key}_${strat.total_trades}_${strat.profit_total.toFixed(3)}`;
|
2023-07-30 07:47:59 +00:00
|
|
|
const btResult: BacktestResultInMemory = {
|
|
|
|
metadata,
|
|
|
|
strategy: strat,
|
|
|
|
};
|
2022-04-17 17:48:51 +00:00
|
|
|
// this.backtestHistory[stratKey] = strat;
|
2023-07-30 07:47:59 +00:00
|
|
|
this.backtestHistory = { ...this.backtestHistory, ...{ [stratKey]: btResult } };
|
2022-04-17 17:48:51 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
},
|
2023-07-25 18:51:17 +00:00
|
|
|
async deleteBacktestHistoryResult(btHistoryEntry: BacktestHistoryEntry) {
|
|
|
|
try {
|
|
|
|
const { data } = await api.delete<BacktestHistoryEntry[]>(
|
|
|
|
`/backtest/history/${btHistoryEntry.filename}`,
|
|
|
|
);
|
|
|
|
this.backtestHistoryList = data;
|
|
|
|
} catch (err) {
|
|
|
|
console.error(err);
|
|
|
|
return Promise.reject(err);
|
|
|
|
}
|
|
|
|
},
|
2023-07-31 19:18:54 +00:00
|
|
|
async saveBacktestResultMetadata(payload: BacktestResultUpdate) {
|
|
|
|
try {
|
|
|
|
const { data } = await api.patch<
|
|
|
|
BacktestMetadataPatch,
|
|
|
|
AxiosResponse<BacktestHistoryEntry[]>
|
|
|
|
>(`/backtest/history/${payload.filename}`, payload);
|
|
|
|
console.log(data);
|
|
|
|
data.forEach((entry) => {
|
|
|
|
if (entry.run_id in this.backtestHistory) {
|
|
|
|
this.backtestHistory[entry.run_id].metadata.notes = entry.notes;
|
|
|
|
console.log('updating ...');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
// Update metadata in backtestHistoryList
|
|
|
|
} catch (err) {
|
|
|
|
console.error(err);
|
|
|
|
return Promise.reject(err);
|
|
|
|
}
|
|
|
|
},
|
2022-04-17 17:48:51 +00:00
|
|
|
setBacktestResultKey(key: string) {
|
|
|
|
this.selectedBacktestResultKey = key;
|
|
|
|
},
|
2023-07-26 04:42:34 +00:00
|
|
|
removeBacktestResultFromMemory(key: string) {
|
|
|
|
if (this.selectedBacktestResultKey === key) {
|
|
|
|
// Get first key from backtestHistory that is not the key to be deleted
|
|
|
|
const keys = Object.keys(this.backtestHistory);
|
|
|
|
const index = keys.findIndex((k) => k !== key);
|
|
|
|
if (index !== -1) {
|
|
|
|
this.selectedBacktestResultKey = keys[index];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
delete this.backtestHistory[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);
|
|
|
|
}
|
|
|
|
},
|
2023-05-08 18:32:38 +00:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
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) {
|
2023-06-24 18:25:43 +00:00
|
|
|
case FtWsMessageTypes.exception:
|
|
|
|
showAlert(`WSException: ${msg.data}`, 'danger');
|
|
|
|
break;
|
2022-12-05 05:55:41 +00:00
|
|
|
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;
|
2023-04-13 05:19:34 +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
|
2023-09-02 07:09:49 +00:00
|
|
|
this.getPairCandles({ pair, timeframe });
|
2022-12-05 17:29:02 +00:00
|
|
|
}
|
2022-12-05 06:06:57 +00:00
|
|
|
break;
|
2023-04-13 05:19:34 +00:00
|
|
|
}
|
2022-12-05 05:55:41 +00:00
|
|
|
default:
|
|
|
|
// Unhandled events ...
|
2023-05-08 18:32:38 +00:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
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 ||
|
2023-04-10 16:22:07 +00:00
|
|
|
this.botApiVersion < 2.2 ||
|
|
|
|
this.isWebserverMode === true
|
2022-09-13 20:13:15 +00:00
|
|
|
) {
|
|
|
|
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');
|
2023-06-24 18:22:59 +00:00
|
|
|
if (this.isWebserverMode !== true) {
|
|
|
|
//
|
|
|
|
this.websocketStarted = true;
|
|
|
|
const subscriptions = [
|
|
|
|
FtWsMessageTypes.whitelist,
|
|
|
|
FtWsMessageTypes.entryFill,
|
|
|
|
FtWsMessageTypes.exitFill,
|
|
|
|
FtWsMessageTypes.entryCancel,
|
|
|
|
FtWsMessageTypes.exitCancel,
|
|
|
|
/*'new_candle' /*'analyzed_df'*/
|
|
|
|
];
|
|
|
|
if (this.botApiVersion >= 2.21) {
|
|
|
|
subscriptions.push(FtWsMessageTypes.newCandle);
|
|
|
|
}
|
2022-12-05 18:58:25 +00:00
|
|
|
|
2023-06-24 18:22:59 +00:00
|
|
|
send(
|
|
|
|
JSON.stringify({
|
|
|
|
type: 'subscribe',
|
|
|
|
data: subscriptions,
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
send(
|
|
|
|
JSON.stringify({
|
|
|
|
type: FtWsMessageTypes.whitelist,
|
|
|
|
data: '',
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
}
|
2022-09-13 20:13:15 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
);
|
|
|
|
},
|
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();
|
|
|
|
}
|