mirror of
https://github.com/freqtrade/frequi.git
synced 2024-11-23 03:25:15 +00:00
added pairlist configurator
This commit is contained in:
parent
d4c6815eca
commit
ac78a18414
68
src/components/ftbot/PairlistConfigItem.vue
Normal file
68
src/components/ftbot/PairlistConfigItem.vue
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
<template>
|
||||||
|
<b-card no-body class="mb-2">
|
||||||
|
<template #header>
|
||||||
|
<b-row align-v="center">
|
||||||
|
<b-col>
|
||||||
|
<b-row align-v="center">
|
||||||
|
<b-col cols="auto"><ReorderIcon class="handle me-2" /></b-col>
|
||||||
|
<b-col
|
||||||
|
><b-button variant="link" @click="visible = !visible">
|
||||||
|
<h6 class="mb-0">{{ pairlist.name }}</h6>
|
||||||
|
</b-button></b-col
|
||||||
|
>
|
||||||
|
</b-row>
|
||||||
|
</b-col>
|
||||||
|
<b-col cols="auto">
|
||||||
|
<b-button size="sm" @click="emit('remove', index)">x</b-button>
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
</template>
|
||||||
|
<b-card-body :class="{ hidden: !visible }">
|
||||||
|
<b-collapse v-model="visible">
|
||||||
|
<PairlistConfigParameter
|
||||||
|
v-for="(parameter, key) in pairlist.params"
|
||||||
|
:key="key"
|
||||||
|
v-model="pairlist.params[key].value"
|
||||||
|
:param="parameter"
|
||||||
|
/>
|
||||||
|
</b-collapse>
|
||||||
|
</b-card-body>
|
||||||
|
</b-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Pairlist } from '@/types';
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import PairlistConfigParameter from '../general/PairlistConfigParameter.vue';
|
||||||
|
import ReorderIcon from 'vue-material-design-icons/ReorderHorizontal.vue';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
modelValue: Pairlist;
|
||||||
|
index: number;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'remove']);
|
||||||
|
|
||||||
|
const visible = ref(false);
|
||||||
|
|
||||||
|
const pairlist = computed({
|
||||||
|
get() {
|
||||||
|
return props.modelValue;
|
||||||
|
},
|
||||||
|
set(value: Pairlist) {
|
||||||
|
emit('update:modelValue', value);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.hidden {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsing {
|
||||||
|
-webkit-transition: none;
|
||||||
|
transition: none;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
117
src/components/ftbot/PairlistConfigurator.vue
Normal file
117
src/components/ftbot/PairlistConfigurator.vue
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
<template>
|
||||||
|
<b-modal
|
||||||
|
v-model="visible"
|
||||||
|
:scrollable="true"
|
||||||
|
:no-close-on-backdrop="true"
|
||||||
|
ok-title="Save"
|
||||||
|
title="Pairlist configurator"
|
||||||
|
@ok="save"
|
||||||
|
@show="configureConfig"
|
||||||
|
>
|
||||||
|
<div class="mb-2">
|
||||||
|
<b-form-input v-model="config.name" placeholder="Configuration name..."></b-form-input>
|
||||||
|
</div>
|
||||||
|
<b-form-select
|
||||||
|
v-model="selectedPairlist"
|
||||||
|
:options="pairlistSelectOptions"
|
||||||
|
class="mb-2"
|
||||||
|
@input="addToConfig"
|
||||||
|
></b-form-select>
|
||||||
|
<div ref="pairlistConfigsEl">
|
||||||
|
<PairlistConfigItem
|
||||||
|
v-for="(pairlist, i) in pairlists"
|
||||||
|
:key="pairlist.id"
|
||||||
|
v-model="config.pairlists[i]"
|
||||||
|
:index="i"
|
||||||
|
@remove="removeFromConfig"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</b-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, ref, toRaw } from 'vue';
|
||||||
|
import { useBotStore } from '@/stores/ftbotwrapper';
|
||||||
|
import PairlistConfigItem from './PairlistConfigItem.vue';
|
||||||
|
import { Pairlist, PairlistConfig } from '@/types';
|
||||||
|
import { useSortable, moveArrayElement } from '@vueuse/integrations/useSortable';
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'saveConfig']);
|
||||||
|
const props = defineProps<{
|
||||||
|
modelValue: boolean;
|
||||||
|
config: PairlistConfig;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const botStore = useBotStore();
|
||||||
|
|
||||||
|
const availablePairlists = ref<Pairlist[]>([]);
|
||||||
|
const selectedPairlist = ref<Pairlist>({} as Pairlist);
|
||||||
|
const config = ref<PairlistConfig>(props.config);
|
||||||
|
const pairlistConfigsEl = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
const visible = computed({
|
||||||
|
get() {
|
||||||
|
return props.modelValue;
|
||||||
|
},
|
||||||
|
set(value: boolean) {
|
||||||
|
emit('update:modelValue', value);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// v-for updates with sorting, deleting and adding items seem to get wonky without unique keys for every item
|
||||||
|
const pairlists = computed(() =>
|
||||||
|
config.value.pairlists.map((p) => {
|
||||||
|
if (p.id) {
|
||||||
|
return p;
|
||||||
|
} else {
|
||||||
|
return { id: Date.now().toString(36) + Math.random().toString(36).substring(2), ...p };
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const pairlistSelectOptions = computed(() => {
|
||||||
|
const pairlists = availablePairlists.value.map((p) => {
|
||||||
|
return { value: p, text: p.name };
|
||||||
|
});
|
||||||
|
|
||||||
|
return [{ text: 'Select a pairlist....', value: {} }, ...pairlists];
|
||||||
|
});
|
||||||
|
|
||||||
|
useSortable(pairlistConfigsEl, config.value.pairlists, {
|
||||||
|
handle: '.handle',
|
||||||
|
onUpdate: async (e) => {
|
||||||
|
moveArrayElement(config.value.pairlists, e.oldIndex, e.newIndex);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
availablePairlists.value = (await botStore.activeBot.getPairlists()).pairlists;
|
||||||
|
});
|
||||||
|
|
||||||
|
const addToConfig = () => {
|
||||||
|
if (selectedPairlist.value) {
|
||||||
|
const pairlist = structuredClone(toRaw(selectedPairlist.value));
|
||||||
|
for (const param in pairlist.params) {
|
||||||
|
pairlist.params[param].value = pairlist.params[param].default
|
||||||
|
? pairlist.params[param].default.toString()
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
config.value.pairlists.push(pairlist);
|
||||||
|
selectedPairlist.value = {} as Pairlist;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeFromConfig = (index: number) => {
|
||||||
|
config.value.pairlists.splice(index, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const save = async (e: Event) => {
|
||||||
|
e.preventDefault();
|
||||||
|
emit('saveConfig', config.value);
|
||||||
|
visible.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const configureConfig = () => {
|
||||||
|
config.value = structuredClone(toRaw(props.config));
|
||||||
|
};
|
||||||
|
</script>
|
51
src/components/general/PairlistConfigParameter.vue
Normal file
51
src/components/general/PairlistConfigParameter.vue
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
<template>
|
||||||
|
<b-form-group
|
||||||
|
label-cols="4"
|
||||||
|
label-cols-lg="6"
|
||||||
|
label-size="md"
|
||||||
|
class="pb-1"
|
||||||
|
:description="param.help"
|
||||||
|
>
|
||||||
|
<b-form-input
|
||||||
|
v-if="param.type === PairlistParamType.string || param.type === PairlistParamType.number"
|
||||||
|
v-model="paramValue"
|
||||||
|
size="sm"
|
||||||
|
></b-form-input>
|
||||||
|
|
||||||
|
<b-form-checkbox
|
||||||
|
v-if="param.type === PairlistParamType.boolean"
|
||||||
|
v-model="paramValue"
|
||||||
|
></b-form-checkbox>
|
||||||
|
|
||||||
|
<b-form-select
|
||||||
|
v-if="param.type === PairlistParamType.option"
|
||||||
|
v-model="paramValue"
|
||||||
|
:options="param.options"
|
||||||
|
></b-form-select>
|
||||||
|
|
||||||
|
<template #label>
|
||||||
|
<label> {{ param.description }}</label>
|
||||||
|
</template>
|
||||||
|
</b-form-group>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { PairlistParameter, PairlistParamType } from '@/types';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
param: PairlistParameter;
|
||||||
|
modelValue: string | undefined;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
|
const paramValue = computed({
|
||||||
|
get() {
|
||||||
|
return props.modelValue;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
emit('update:modelValue', value);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -23,6 +23,9 @@
|
||||||
<router-link v-if="botStore.canRunBacktest" class="nav-link navbar-nav" to="/backtest"
|
<router-link v-if="botStore.canRunBacktest" class="nav-link navbar-nav" to="/backtest"
|
||||||
>Backtest</router-link
|
>Backtest</router-link
|
||||||
>
|
>
|
||||||
|
<router-link class="nav-link navbar-nav" to="/pairlist_config"
|
||||||
|
>Pairlist Config</router-link
|
||||||
|
>
|
||||||
<theme-select />
|
<theme-select />
|
||||||
</b-navbar-nav>
|
</b-navbar-nav>
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,11 @@ const routes: Array<RouteRecordRaw> = [
|
||||||
allowAnonymous: true,
|
allowAnonymous: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/pairlist_config',
|
||||||
|
name: 'Pairlist Configuration',
|
||||||
|
component: () => import('@/views/PairlistConfigView.vue'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/(.*)*',
|
path: '/(.*)*',
|
||||||
name: '404',
|
name: '404',
|
||||||
|
|
|
@ -42,7 +42,12 @@ import { showAlert } from './alerts';
|
||||||
import { useWebSocket } from '@vueuse/core';
|
import { useWebSocket } from '@vueuse/core';
|
||||||
import { FTWsMessage, FtWsMessageTypes } from '@/types/wsMessageTypes';
|
import { FTWsMessage, FtWsMessageTypes } from '@/types/wsMessageTypes';
|
||||||
import { showNotification } from '@/shared/notifications';
|
import { showNotification } from '@/shared/notifications';
|
||||||
import { FreqAIModelListResult } from '../types/types';
|
import {
|
||||||
|
FreqAIModelListResult,
|
||||||
|
PairlistEvalResponse,
|
||||||
|
PairlistsPayload,
|
||||||
|
PairlistsResponse,
|
||||||
|
} from '../types/types';
|
||||||
|
|
||||||
export function createBotSubStore(botId: string, botName: string) {
|
export function createBotSubStore(botId: string, botName: string) {
|
||||||
const userService = useUserService(botId);
|
const userService = useUserService(botId);
|
||||||
|
@ -535,6 +540,24 @@ export function createBotSubStore(botId: string, botName: string) {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async getPairlists() {
|
||||||
|
try {
|
||||||
|
const { data } = await api.get<PairlistsResponse>('/pairlists/available');
|
||||||
|
return Promise.resolve(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getPairlistEvalStatus() {
|
||||||
|
try {
|
||||||
|
const { data } = await api.get<PairlistEvalResponse>('/pairlists/evaluate');
|
||||||
|
return Promise.resolve(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
// // Post methods
|
// // Post methods
|
||||||
// // TODO: Migrate calls to API to a seperate module unrelated to pinia?
|
// // TODO: Migrate calls to API to a seperate module unrelated to pinia?
|
||||||
async startBot() {
|
async startBot() {
|
||||||
|
@ -938,6 +961,21 @@ export function createBotSubStore(botId: string, botName: string) {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
async evaluatePairlist(payload: PairlistsPayload) {
|
||||||
|
try {
|
||||||
|
const { data } = await api.post<WhitelistResponse, AxiosResponse<StatusResponse>>(
|
||||||
|
'/pairlists/evaluate',
|
||||||
|
payload,
|
||||||
|
);
|
||||||
|
return Promise.resolve(data);
|
||||||
|
} catch (error) {
|
||||||
|
if (axios.isAxiosError(error)) {
|
||||||
|
console.error(error.response);
|
||||||
|
}
|
||||||
|
showAlert('Error testing pairlist', 'danger');
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -271,3 +271,52 @@ export enum LoadingStatus {
|
||||||
success,
|
success,
|
||||||
error,
|
error,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PairlistsResponse {
|
||||||
|
pairlists: Pairlist[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PairlistEvalResponse {
|
||||||
|
detail?: string;
|
||||||
|
method?: string[];
|
||||||
|
whitelist?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Pairlist {
|
||||||
|
id?: string;
|
||||||
|
is_pairlist_generator: boolean;
|
||||||
|
name: string;
|
||||||
|
params: Record<string, PairlistParameter>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PairlistConfig {
|
||||||
|
name: string;
|
||||||
|
pairlists: Pairlist[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum PairlistParamType {
|
||||||
|
string = 'string',
|
||||||
|
number = 'number',
|
||||||
|
boolean = 'boolean',
|
||||||
|
option = 'option',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PairlistParameter {
|
||||||
|
description: string;
|
||||||
|
help: string;
|
||||||
|
type: PairlistParamType;
|
||||||
|
value?: string;
|
||||||
|
default: string;
|
||||||
|
options?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PairlistPayloadItem {
|
||||||
|
method: string;
|
||||||
|
[key: string]: string | number | boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PairlistsPayload {
|
||||||
|
pairlists: PairlistPayloadItem[];
|
||||||
|
blacklist: string[];
|
||||||
|
stake_currency: string;
|
||||||
|
}
|
||||||
|
|
108
src/views/PairlistConfigView.vue
Normal file
108
src/views/PairlistConfigView.vue
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<b-form-select v-model="selectedConfig" :options="configsSelectOptions"></b-form-select>
|
||||||
|
<b-button @click="showConfigurator = !showConfigurator">Configure</b-button>
|
||||||
|
<b-button :disabled="evaluating" @click="test">Test</b-button>
|
||||||
|
|
||||||
|
<PairlistConfigurator
|
||||||
|
v-model="showConfigurator"
|
||||||
|
:config="selectedConfig"
|
||||||
|
@save-config="addConfig"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p>{{ progressMessage }}</p>
|
||||||
|
<div class="mt-4">{{ whitelist }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useBotStore } from '@/stores/ftbotwrapper';
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import PairlistConfigurator from '@/components/ftbot/PairlistConfigurator.vue';
|
||||||
|
import { PairlistConfig, PairlistParamType, PairlistPayloadItem } from '@/types';
|
||||||
|
|
||||||
|
const botStore = useBotStore();
|
||||||
|
const showConfigurator = ref(false);
|
||||||
|
const configs = ref<PairlistConfig[]>([]);
|
||||||
|
const selectedConfig = ref<PairlistConfig>({ name: '', pairlists: [] });
|
||||||
|
const whitelist = ref<string[]>([]);
|
||||||
|
const evaluating = ref(false);
|
||||||
|
const progressMessage = ref('');
|
||||||
|
|
||||||
|
const addConfig = (config: PairlistConfig) => {
|
||||||
|
const i = configs.value.findIndex((c) => c.name === config.name);
|
||||||
|
|
||||||
|
if (i > -1) {
|
||||||
|
configs.value[i] = config;
|
||||||
|
} else {
|
||||||
|
configs.value.push(config);
|
||||||
|
}
|
||||||
|
selectedConfig.value = config;
|
||||||
|
};
|
||||||
|
|
||||||
|
const configsSelectOptions = computed(() => {
|
||||||
|
const options = configs.value.map((c) => {
|
||||||
|
return { value: c, text: c.name };
|
||||||
|
});
|
||||||
|
|
||||||
|
const val: PairlistConfig = {
|
||||||
|
name: '',
|
||||||
|
pairlists: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
return [{ text: 'New config...', value: val }, ...options];
|
||||||
|
});
|
||||||
|
|
||||||
|
const test = async () => {
|
||||||
|
if (!selectedConfig.value) return;
|
||||||
|
|
||||||
|
const pairlists: PairlistPayloadItem[] = [];
|
||||||
|
|
||||||
|
selectedConfig.value.pairlists.forEach((config) => {
|
||||||
|
const pairlist = {
|
||||||
|
method: config.name,
|
||||||
|
};
|
||||||
|
for (const key in config.params) {
|
||||||
|
const param = config.params[key];
|
||||||
|
if (param.value) {
|
||||||
|
pairlist[key] = convertToParamType(param.type, param.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pairlists.push(pairlist);
|
||||||
|
});
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
pairlists: pairlists,
|
||||||
|
stake_currency: botStore.activeBot.stakeCurrency,
|
||||||
|
blacklist: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
evaluating.value = true;
|
||||||
|
whitelist.value = [];
|
||||||
|
const res = await botStore.activeBot.evaluatePairlist(payload);
|
||||||
|
console.log(res);
|
||||||
|
const evalIntervalId = setInterval(async () => {
|
||||||
|
const res = await botStore.activeBot.getPairlistEvalStatus();
|
||||||
|
|
||||||
|
if (res.whitelist) {
|
||||||
|
whitelist.value = res.whitelist;
|
||||||
|
clearInterval(evalIntervalId);
|
||||||
|
evaluating.value = false;
|
||||||
|
progressMessage.value = '';
|
||||||
|
} else if (res.detail) {
|
||||||
|
progressMessage.value = res.detail;
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const convertToParamType = (type: PairlistParamType, value: string) => {
|
||||||
|
if (type === PairlistParamType.number) {
|
||||||
|
return Number(value);
|
||||||
|
} else if (type === PairlistParamType.boolean) {
|
||||||
|
return Boolean(value);
|
||||||
|
} else {
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
Loading…
Reference in New Issue
Block a user