2023-05-23 18:27:33 +00:00
|
|
|
<template>
|
2023-05-30 18:43:35 +00:00
|
|
|
<div class="d-flex px-3 gap-3 flex-column flex-lg-row">
|
|
|
|
<b-list-group ref="availablePairlistsEl" class="available-pairlists">
|
|
|
|
<b-list-group-item
|
|
|
|
v-for="pairlist in availablePairlists"
|
|
|
|
:key="pairlist.name"
|
|
|
|
:class="{
|
|
|
|
'no-drag': pairlistStore.config.pairlists.length == 0 && !pairlist.is_pairlist_generator,
|
|
|
|
}"
|
|
|
|
class="pairlist d-flex text-start align-items-center py-2 px-3"
|
|
|
|
>
|
|
|
|
<div class="d-flex flex-grow-1 align-items-start flex-column">
|
2023-06-01 19:06:32 +00:00
|
|
|
<span class="fw-bold">{{ pairlist.name }}</span>
|
|
|
|
<span class="text-small">{{ pairlist.description }}</span>
|
2023-05-24 18:19:46 +00:00
|
|
|
</div>
|
2023-05-30 18:43:35 +00:00
|
|
|
<b-button
|
2023-06-01 19:47:12 +00:00
|
|
|
class="p-0 add-pairlist"
|
2023-05-30 18:43:35 +00:00
|
|
|
style="border: none"
|
|
|
|
variant="outline-light"
|
|
|
|
:disabled="pairlistStore.config.pairlists.length == 0 && !pairlist.is_pairlist_generator"
|
|
|
|
@click="pairlistStore.addToConfig(pairlist, pairlistStore.config.pairlists.length)"
|
|
|
|
>
|
|
|
|
<i-mdi-arrow-right-bold-box-outline class="fs-4" />
|
|
|
|
</b-button>
|
|
|
|
</b-list-group-item>
|
|
|
|
</b-list-group>
|
2023-05-31 10:32:36 +00:00
|
|
|
<div class="d-flex flex-column flex-fill">
|
2023-05-30 18:43:35 +00:00
|
|
|
<PairlistConfigActions />
|
2023-06-09 15:24:07 +00:00
|
|
|
|
|
|
|
<div class="d-flex align-items-center gap-2 my-2">
|
|
|
|
<span class="col-auto">Stake currency: </span>
|
|
|
|
<b-form-input v-model="pairlistStore.stakeCurrency" size="sm" />
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="mb-2">
|
|
|
|
<b-form-checkbox v-model="pairlistStore.customExchange" class="mb-2"
|
|
|
|
>Custom Exchange</b-form-checkbox
|
|
|
|
>
|
|
|
|
<exchange-select
|
|
|
|
v-if="pairlistStore.customExchange"
|
|
|
|
v-model="pairlistStore.selectedExchange"
|
|
|
|
/>
|
|
|
|
</div>
|
2023-05-30 18:43:35 +00:00
|
|
|
<PairlistConfigBlacklist />
|
|
|
|
<b-alert
|
|
|
|
:model-value="
|
|
|
|
pairlistStore.config.pairlists.length > 0 && !pairlistStore.firstPairlistIsGenerator
|
|
|
|
"
|
|
|
|
variant="warning"
|
|
|
|
>
|
|
|
|
First entry in the pairlist must be a Generating pairlist, like StaticPairList or
|
|
|
|
VolumePairList.
|
|
|
|
</b-alert>
|
2023-06-01 10:49:27 +00:00
|
|
|
<div
|
|
|
|
ref="pairlistConfigsEl"
|
|
|
|
class="d-flex flex-column flex-grow-1 position-relative"
|
|
|
|
:class="{ empty: configEmpty }"
|
|
|
|
>
|
2023-05-30 18:43:35 +00:00
|
|
|
<PairlistConfigItem
|
|
|
|
v-for="(pairlist, i) in pairlistsComp"
|
|
|
|
:key="pairlist.id"
|
|
|
|
v-model="pairlistStore.config.pairlists[i]"
|
|
|
|
:index="i"
|
|
|
|
@remove="pairlistStore.removeFromConfig"
|
2023-05-30 11:41:37 +00:00
|
|
|
/>
|
2023-05-30 18:43:35 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="col-12 col-lg-3">
|
2023-06-09 12:05:27 +00:00
|
|
|
<b-form-radio-group v-model="selectedView" class="mb-2" size="sm" buttons>
|
|
|
|
<b-form-radio button value="Config"> Config</b-form-radio>
|
|
|
|
<b-form-radio button value="Results" :disabled="pairlistStore.whitelist.length === 0">
|
|
|
|
Results</b-form-radio
|
|
|
|
>
|
|
|
|
</b-form-radio-group>
|
2023-05-30 18:43:35 +00:00
|
|
|
<CopyableTextfield
|
2023-06-09 12:05:27 +00:00
|
|
|
v-if="selectedView === 'Config'"
|
2023-05-30 18:43:35 +00:00
|
|
|
:content="pairlistStore.configJSON"
|
|
|
|
:is-valid="pairlistStore.pairlistValid"
|
|
|
|
/>
|
2023-06-09 12:05:27 +00:00
|
|
|
<CopyableTextfield v-if="selectedView === 'Results'" :content="pairlistStore.whitelist" />
|
2023-05-30 18:43:35 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
2023-05-23 18:27:33 +00:00
|
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2023-06-09 12:05:27 +00:00
|
|
|
import { computed, onMounted, ref, watch } from 'vue';
|
2023-05-23 18:27:33 +00:00
|
|
|
import { useBotStore } from '@/stores/ftbotwrapper';
|
2023-05-29 21:36:23 +00:00
|
|
|
import { usePairlistConfigStore } from '@/stores/pairlistConfig';
|
2023-05-23 18:27:33 +00:00
|
|
|
import PairlistConfigItem from './PairlistConfigItem.vue';
|
2023-05-30 11:28:55 +00:00
|
|
|
import PairlistConfigBlacklist from './PairlistConfigBlacklist.vue';
|
2023-05-30 15:45:03 +00:00
|
|
|
import PairlistConfigActions from './PairlistConfigActions.vue';
|
2023-06-04 11:41:14 +00:00
|
|
|
import { Pairlist } from '@/types';
|
2023-05-23 18:27:33 +00:00
|
|
|
import { useSortable, moveArrayElement } from '@vueuse/integrations/useSortable';
|
2023-05-29 12:55:15 +00:00
|
|
|
import CopyableTextfield from '@/components/general/CopyableTextfield.vue';
|
2023-06-04 08:01:28 +00:00
|
|
|
import ExchangeSelect from './ExchangeSelect.vue';
|
2023-05-23 18:27:33 +00:00
|
|
|
|
|
|
|
const botStore = useBotStore();
|
2023-05-29 21:36:23 +00:00
|
|
|
const pairlistStore = usePairlistConfigStore();
|
2023-05-23 18:27:33 +00:00
|
|
|
|
|
|
|
const availablePairlists = ref<Pairlist[]>([]);
|
|
|
|
const pairlistConfigsEl = ref<HTMLElement | null>(null);
|
2023-05-24 18:19:46 +00:00
|
|
|
const availablePairlistsEl = ref<HTMLElement | null>(null);
|
2023-06-09 12:05:27 +00:00
|
|
|
const selectedView = ref<'Config' | 'Results'>('Config');
|
2023-05-29 06:40:24 +00:00
|
|
|
|
2023-05-23 18:27:33 +00:00
|
|
|
// v-for updates with sorting, deleting and adding items seem to get wonky without unique keys for every item
|
2023-05-26 11:12:40 +00:00
|
|
|
const pairlistsComp = computed(() =>
|
2023-05-29 21:36:23 +00:00
|
|
|
pairlistStore.config.pairlists.map((p) => {
|
2023-05-23 18:27:33 +00:00
|
|
|
if (p.id) {
|
|
|
|
return p;
|
|
|
|
} else {
|
|
|
|
return { id: Date.now().toString(36) + Math.random().toString(36).substring(2), ...p };
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
2023-06-01 10:49:27 +00:00
|
|
|
const configEmpty = computed(() => {
|
|
|
|
return pairlistStore.config.pairlists.length == 0;
|
|
|
|
});
|
|
|
|
|
2023-05-24 18:19:46 +00:00
|
|
|
useSortable(availablePairlistsEl, availablePairlists.value, {
|
|
|
|
group: {
|
|
|
|
name: 'configurator',
|
|
|
|
pull: 'clone',
|
|
|
|
put: false,
|
|
|
|
},
|
|
|
|
sort: false,
|
2023-05-29 09:57:37 +00:00
|
|
|
filter: '.no-drag',
|
2023-06-01 10:49:27 +00:00
|
|
|
dragClass: 'dragging',
|
2023-05-23 18:27:33 +00:00
|
|
|
});
|
|
|
|
|
2023-05-29 21:36:23 +00:00
|
|
|
useSortable(pairlistConfigsEl, pairlistStore.config.pairlists, {
|
2023-05-23 18:27:33 +00:00
|
|
|
handle: '.handle',
|
2023-05-24 18:19:46 +00:00
|
|
|
group: 'configurator',
|
2023-05-23 18:27:33 +00:00
|
|
|
onUpdate: async (e) => {
|
2023-05-29 21:36:23 +00:00
|
|
|
moveArrayElement(pairlistStore.config.pairlists, e.oldIndex, e.newIndex);
|
2023-05-23 18:27:33 +00:00
|
|
|
},
|
2023-05-24 18:19:46 +00:00
|
|
|
onAdd: (e) => {
|
|
|
|
const pairlist = availablePairlists.value[e.oldIndex];
|
2023-05-29 21:36:23 +00:00
|
|
|
pairlistStore.addToConfig(pairlist, e.newIndex);
|
2023-05-24 21:53:10 +00:00
|
|
|
// quick fix from: https://github.com/SortableJS/Sortable/issues/1515
|
|
|
|
e.clone.replaceWith(e.item);
|
|
|
|
e.clone.remove();
|
2023-05-24 18:19:46 +00:00
|
|
|
},
|
2023-05-23 18:27:33 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
onMounted(async () => {
|
2023-05-28 17:39:06 +00:00
|
|
|
availablePairlists.value = (await botStore.activeBot.getPairlists()).pairlists.sort((a, b) =>
|
|
|
|
// Sort by is_pairlist_generator (by name), then by name.
|
|
|
|
// TODO: this might need to be improved
|
|
|
|
a.is_pairlist_generator === b.is_pairlist_generator
|
|
|
|
? a.name.localeCompare(b.name)
|
|
|
|
: a.is_pairlist_generator
|
|
|
|
? -1
|
|
|
|
: 1,
|
|
|
|
);
|
2023-06-07 17:26:33 +00:00
|
|
|
pairlistStore.selectOrCreateConfig(
|
|
|
|
pairlistStore.savedConfigs.length > 0 ? pairlistStore.savedConfigs[0].name : 'default',
|
|
|
|
);
|
2023-05-23 18:27:33 +00:00
|
|
|
});
|
2023-06-09 12:05:27 +00:00
|
|
|
|
|
|
|
watch(
|
|
|
|
() => pairlistStore.whitelist,
|
|
|
|
() => {
|
|
|
|
selectedView.value = 'Results';
|
|
|
|
},
|
|
|
|
);
|
2023-05-23 18:27:33 +00:00
|
|
|
</script>
|
2023-05-24 18:19:46 +00:00
|
|
|
|
2023-05-29 09:57:37 +00:00
|
|
|
<style lang="scss" scoped>
|
2023-05-24 18:19:46 +00:00
|
|
|
.pairlist {
|
2023-06-01 10:49:27 +00:00
|
|
|
&:hover {
|
|
|
|
cursor: grab;
|
|
|
|
}
|
|
|
|
&.no-drag {
|
|
|
|
color: gray;
|
|
|
|
}
|
|
|
|
&.no-drag:hover {
|
|
|
|
cursor: default;
|
|
|
|
}
|
|
|
|
&.dragging {
|
|
|
|
border: 1px solid white;
|
|
|
|
border-radius: 0;
|
|
|
|
}
|
2023-05-29 09:57:37 +00:00
|
|
|
}
|
|
|
|
|
2023-06-05 10:25:23 +00:00
|
|
|
[data-bs-theme='light'] .add-pairlist {
|
2023-06-01 19:47:12 +00:00
|
|
|
color: black;
|
|
|
|
}
|
|
|
|
|
2023-06-01 10:49:27 +00:00
|
|
|
.empty {
|
|
|
|
border: 3px dashed rgba(255, 255, 255, 0.65);
|
2023-05-24 18:19:46 +00:00
|
|
|
|
2023-06-05 10:25:23 +00:00
|
|
|
[data-bs-theme='light'] & {
|
2023-06-01 19:47:12 +00:00
|
|
|
border: 3px dashed rgba(0, 0, 0, 0.5);
|
|
|
|
}
|
|
|
|
|
2023-06-01 10:49:27 +00:00
|
|
|
&:after {
|
|
|
|
content: 'Drag pairlist here';
|
|
|
|
position: absolute;
|
|
|
|
align-self: center;
|
|
|
|
font-size: 1.1rem;
|
|
|
|
text-transform: uppercase;
|
|
|
|
line-height: 0;
|
|
|
|
top: 50%;
|
|
|
|
}
|
2023-05-24 18:19:46 +00:00
|
|
|
}
|
|
|
|
</style>
|