mirror of
https://github.com/freqtrade/frequi.git
synced 2024-11-10 18:23:50 +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"
|
||||
>Backtest</router-link
|
||||
>
|
||||
<router-link class="nav-link navbar-nav" to="/pairlist_config"
|
||||
>Pairlist Config</router-link
|
||||
>
|
||||
<theme-select />
|
||||
</b-navbar-nav>
|
||||
|
||||
|
|
|
@ -67,6 +67,11 @@ const routes: Array<RouteRecordRaw> = [
|
|||
allowAnonymous: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/pairlist_config',
|
||||
name: 'Pairlist Configuration',
|
||||
component: () => import('@/views/PairlistConfigView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/(.*)*',
|
||||
name: '404',
|
||||
|
|
|
@ -42,7 +42,12 @@ import { showAlert } from './alerts';
|
|||
import { useWebSocket } from '@vueuse/core';
|
||||
import { FTWsMessage, FtWsMessageTypes } from '@/types/wsMessageTypes';
|
||||
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) {
|
||||
const userService = useUserService(botId);
|
||||
|
@ -535,6 +540,24 @@ export function createBotSubStore(botId: string, botName: string) {
|
|||
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
|
||||
// // TODO: Migrate calls to API to a seperate module unrelated to pinia?
|
||||
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,
|
||||
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