chore: update block-order in components

script before template.
This commit is contained in:
Matthias 2024-07-30 06:21:02 +02:00
parent 8b40c2a862
commit dc3553bb3e
32 changed files with 996 additions and 996 deletions

View File

@ -1,21 +1,3 @@
<template>
<div class="d-flex flex-row">
<BFormGroup class="flex-grow-1" :label="label" label-for="indicatorSelector">
<VSelect
v-model="selAvailableIndicator"
:options="columns"
size="sm"
:clearable="false"
@option:selected="emitIndicator"
>
</VSelect>
</BFormGroup>
<BButton size="sm" title="Abort" class="ms-1 mt-auto" variant="secondary" @click="abort">
<i-mdi-close />
</BButton>
</div>
</template>
<script setup lang="ts">
import VSelect from 'vue-select';
@ -52,4 +34,22 @@ watch(
);
</script>
<template>
<div class="d-flex flex-row">
<BFormGroup class="flex-grow-1" :label="label" label-for="indicatorSelector">
<VSelect
v-model="selAvailableIndicator"
:options="columns"
size="sm"
:clearable="false"
@option:selected="emitIndicator"
>
</VSelect>
</BFormGroup>
<BButton size="sm" title="Abort" class="ms-1 mt-auto" variant="secondary" @click="abort">
<i-mdi-close />
</BButton>
</div>
</template>
<style scoped></style>

View File

@ -1,29 +1,3 @@
<template>
<div class="d-flex flex-column h-100 position-relative">
<div class="flex-grow-1">
<ECharts v-if="trades" :option="chartOptions" autoresize :theme="settingsStore.chartTheme" />
</div>
<BFormGroup
class="z-2"
:class="showTitle ? 'ms-5 ps-5' : 'position-absolute'"
label="Bins"
style="width: 33%; min-width: 12rem"
label-for="input-bins"
label-cols="6"
content-cols="6"
size="sm"
>
<BFormSelect
id="input-bins"
v-model="settingsStore.profitDistributionBins"
size="sm"
class="mt-1"
:options="binOptions"
></BFormSelect>
</BFormGroup>
</div>
</template>
<script setup lang="ts">
import ECharts from 'vue-echarts';
import { EChartsOption } from 'echarts';
@ -143,6 +117,32 @@ const chartOptions = computed((): EChartsOption => {
});
</script>
<template>
<div class="d-flex flex-column h-100 position-relative">
<div class="flex-grow-1">
<ECharts v-if="trades" :option="chartOptions" autoresize :theme="settingsStore.chartTheme" />
</div>
<BFormGroup
class="z-2"
:class="showTitle ? 'ms-5 ps-5' : 'position-absolute'"
label="Bins"
style="width: 33%; min-width: 12rem"
label-for="input-bins"
label-cols="6"
content-cols="6"
size="sm"
>
<BFormSelect
id="input-bins"
v-model="settingsStore.profitDistributionBins"
size="sm"
class="mt-1"
:options="binOptions"
></BFormSelect>
</BFormGroup>
</div>
</template>
<style scoped>
.echarts {
width: 100%;

View File

@ -1,14 +1,3 @@
<template>
<ECharts
v-if="dailyStats.data"
ref="dailyChart"
:option="dailyChartOptions"
:theme="settingsStore.chartTheme"
:style="{ height: width * 0.6 + 'px' }"
autoresize
/>
</template>
<script setup lang="ts">
import ECharts from 'vue-echarts';
// import { EChartsOption } from 'echarts';
@ -162,6 +151,17 @@ const dailyChartOptions: ComputedRef<EChartsOption> = computed(() => {
});
</script>
<template>
<ECharts
v-if="dailyStats.data"
ref="dailyChart"
:option="dailyChartOptions"
:theme="settingsStore.chartTheme"
:style="{ height: width * 0.6 + 'px' }"
autoresize
/>
</template>
<style lang="scss" scoped>
.echarts {
min-height: 240px;

View File

@ -1,12 +1,3 @@
<template>
<ECharts
v-if="trades.length > 0"
:option="chartOptions"
autoresize
:theme="settingsStore.chartTheme"
/>
</template>
<script setup lang="ts">
import ECharts from 'vue-echarts';
import { EChartsOption } from 'echarts';
@ -178,6 +169,15 @@ const chartOptions = computed((): EChartsOption => {
});
</script>
<template>
<ECharts
v-if="trades.length > 0"
:option="chartOptions"
autoresize
:theme="settingsStore.chartTheme"
/>
</template>
<style scoped>
.echarts {
width: 100%;

View File

@ -1,3 +1,30 @@
<script setup lang="ts">
import MessageBox, { MsgBoxObject } from '@/components/general/MessageBox.vue';
import { useBotStore } from '@/stores/ftbotwrapper';
import { BacktestHistoryEntry } from '@/types';
import InfoBox from '../general/InfoBox.vue';
const botStore = useBotStore();
const msgBox = ref<typeof MessageBox>();
const filterText = ref('');
const filterTextDebounced = refDebounced(filterText, 350, { maxWait: 1000 });
onMounted(() => {
botStore.activeBot.getBacktestHistory();
});
function deleteBacktestResult(result: BacktestHistoryEntry) {
const msg: MsgBoxObject = {
title: 'Delete result',
message: `Delete result ${result.filename} from disk?`,
accept: () => {
botStore.activeBot.deleteBacktestHistoryResult(result);
},
};
msgBox.value?.show(msg);
}
</script>
<template>
<div>
<button
@ -104,33 +131,6 @@
<MessageBox ref="msgBox" />
</template>
<script setup lang="ts">
import MessageBox, { MsgBoxObject } from '@/components/general/MessageBox.vue';
import { useBotStore } from '@/stores/ftbotwrapper';
import { BacktestHistoryEntry } from '@/types';
import InfoBox from '../general/InfoBox.vue';
const botStore = useBotStore();
const msgBox = ref<typeof MessageBox>();
const filterText = ref('');
const filterTextDebounced = refDebounced(filterText, 350, { maxWait: 1000 });
onMounted(() => {
botStore.activeBot.getBacktestHistory();
});
function deleteBacktestResult(result: BacktestHistoryEntry) {
const msg: MsgBoxObject = {
title: 'Delete result',
message: `Delete result ${result.filename} from disk?`,
accept: () => {
botStore.activeBot.deleteBacktestHistoryResult(result);
},
};
msgBox.value?.show(msg);
}
</script>
<style lang="scss" scoped>
.table-rounded-corners {
box-shadow: 0 0 0 1px var(--bs-border-color);

View File

@ -1,3 +1,34 @@
<script setup lang="ts">
import { StrategyBacktestResult } from '@/types';
import { TableField } from 'bootstrap-vue-next';
const props = defineProps({
backtestResult: { required: true, type: Object as () => StrategyBacktestResult },
});
const backtestResultStats = computed(() => {
const tmp = generateBacktestMetricRows(props.backtestResult);
return formatObjectForTable({ value: tmp }, 'metric');
});
const backtestResultSettings = computed(() => {
// Transpose Result into readable format
const tmp = generateBacktestSettingRows(props.backtestResult);
return formatObjectForTable({ value: tmp }, 'setting');
});
const backtestResultFields: TableField[] = [
{ key: 'metric', label: 'Metric' },
{ key: 'value', label: 'Value' },
];
const backtestsettingFields: TableField[] = [
{ key: 'setting', label: 'Setting' },
{ key: 'value', label: 'Value' },
];
</script>
<template>
<div class="px-0 mw-100">
<div class="d-flex justify-content-center">
@ -72,35 +103,4 @@
</div>
</template>
<script setup lang="ts">
import { StrategyBacktestResult } from '@/types';
import { TableField } from 'bootstrap-vue-next';
const props = defineProps({
backtestResult: { required: true, type: Object as () => StrategyBacktestResult },
});
const backtestResultStats = computed(() => {
const tmp = generateBacktestMetricRows(props.backtestResult);
return formatObjectForTable({ value: tmp }, 'metric');
});
const backtestResultSettings = computed(() => {
// Transpose Result into readable format
const tmp = generateBacktestSettingRows(props.backtestResult);
return formatObjectForTable({ value: tmp }, 'setting');
});
const backtestResultFields: TableField[] = [
{ key: 'metric', label: 'Metric' },
{ key: 'value', label: 'Value' },
];
const backtestsettingFields: TableField[] = [
{ key: 'setting', label: 'Setting' },
{ key: 'value', label: 'Value' },
];
</script>
<style lang="scss" scoped></style>

View File

@ -1,3 +1,28 @@
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
import { ChartSliderPosition, Trade } from '@/types';
defineProps({
timeframe: { required: true, type: String },
strategy: { required: true, type: String },
freqaiModel: { required: false, default: undefined, type: String },
timerange: { required: true, type: String },
pairlist: { required: true, type: Array as () => string[] },
trades: { required: true, type: Array as () => Trade[] },
});
const botStore = useBotStore();
const isBarVisible = ref({ right: true, left: true });
const sliderPosition = ref<ChartSliderPosition>();
const navigateChartToTrade = (trade: Trade) => {
sliderPosition.value = {
startValue: trade.open_timestamp,
endValue: trade.close_timestamp,
};
};
</script>
<template>
<div>
<div class="d-flex flex-row mb-1 align-items-center">
@ -70,31 +95,6 @@
</div>
</template>
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
import { ChartSliderPosition, Trade } from '@/types';
defineProps({
timeframe: { required: true, type: String },
strategy: { required: true, type: String },
freqaiModel: { required: false, default: undefined, type: String },
timerange: { required: true, type: String },
pairlist: { required: true, type: Array as () => string[] },
trades: { required: true, type: Array as () => Trade[] },
});
const botStore = useBotStore();
const isBarVisible = ref({ right: true, left: true });
const sliderPosition = ref<ChartSliderPosition>();
const navigateChartToTrade = (trade: Trade) => {
sliderPosition.value = {
startValue: trade.open_timestamp,
endValue: trade.close_timestamp,
};
};
</script>
<style lang="scss" scoped>
.candle-chart-container {
// TODO: Rough estimate - still to fix correctly

View File

@ -1,32 +1,3 @@
<template>
<div class="px-0 mw-100">
<div class="d-flex justify-content-center">
<h3>Backtest-result comparison</h3>
</div>
<!-- <div class="d-flex">
<div v-for="[key, result] in Object.entries(backtestResults)" :key="key" class="border m-1">
<BacktestResultSelectEntry :backtest-result="result" />
</div>
</div> -->
<div class="d-flex flex-column text-start ms-0 me-2 gap-2">
<div class="d-flex flex-column flex-xl-row">
<div class="px-0 px-xl-0 pt-2 pt-xl-0 ps-xl-1 flex-fill">
<BTable bordered :items="backtestResultStats" :fields="backtestResultFields">
<template
v-for="[key, result] in Object.entries(backtestResults)"
#[`head(${key})`]
:key="key"
>
<BacktestResultSelectEntry :backtest-result="result" />
</template>
</BTable>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { BacktestResultInMemory } from '@/types';
@ -56,4 +27,33 @@ const backtestResultFields = computed<TableField[]>(() => {
});
</script>
<template>
<div class="px-0 mw-100">
<div class="d-flex justify-content-center">
<h3>Backtest-result comparison</h3>
</div>
<!-- <div class="d-flex">
<div v-for="[key, result] in Object.entries(backtestResults)" :key="key" class="border m-1">
<BacktestResultSelectEntry :backtest-result="result" />
</div>
</div> -->
<div class="d-flex flex-column text-start ms-0 me-2 gap-2">
<div class="d-flex flex-column flex-xl-row">
<div class="px-0 px-xl-0 pt-2 pt-xl-0 ps-xl-1 flex-fill">
<BTable bordered :items="backtestResultStats" :fields="backtestResultFields">
<template
v-for="[key, result] in Object.entries(backtestResults)"
#[`head(${key})`]
:key="key"
>
<BacktestResultSelectEntry :backtest-result="result" />
</template>
</BTable>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped></style>

View File

@ -1,3 +1,37 @@
<script setup lang="ts">
import { BacktestResultInMemory, BacktestResultUpdate } from '@/types';
defineProps({
backtestHistory: {
required: true,
type: Object as () => Record<string, BacktestResultInMemory>,
},
selectedBacktestResultKey: { required: false, default: '', type: String },
canUseModify: { required: false, default: false, type: Boolean },
});
const emit = defineEmits<{
selectionChange: [value: string];
removeResult: [value: string];
updateResult: [value: BacktestResultUpdate];
}>();
const setBacktestResult = (key: string) => {
emit('selectionChange', key);
};
function confirmInput(run_id: string, result: BacktestResultInMemory) {
result.metadata.editing = !result.metadata.editing;
if (result.metadata.filename) {
emit('updateResult', {
run_id: run_id,
notes: result.metadata.notes ?? '',
filename: result.metadata.filename,
strategy: result.metadata.strategyName,
});
}
}
</script>
<template>
<div class="container d-flex flex-column align-items-stretch">
<h3>Available results:</h3>
@ -45,38 +79,4 @@
</div>
</template>
<script setup lang="ts">
import { BacktestResultInMemory, BacktestResultUpdate } from '@/types';
defineProps({
backtestHistory: {
required: true,
type: Object as () => Record<string, BacktestResultInMemory>,
},
selectedBacktestResultKey: { required: false, default: '', type: String },
canUseModify: { required: false, default: false, type: Boolean },
});
const emit = defineEmits<{
selectionChange: [value: string];
removeResult: [value: string];
updateResult: [value: BacktestResultUpdate];
}>();
const setBacktestResult = (key: string) => {
emit('selectionChange', key);
};
function confirmInput(run_id: string, result: BacktestResultInMemory) {
result.metadata.editing = !result.metadata.editing;
if (result.metadata.filename) {
emit('updateResult', {
run_id: run_id,
notes: result.metadata.notes ?? '',
filename: result.metadata.filename,
strategy: result.metadata.strategyName,
});
}
}
</script>
<style scoped></style>

View File

@ -1,3 +1,16 @@
<script setup lang="ts">
import { BacktestResultInMemory } from '@/types';
defineProps({
backtestResult: {
required: true,
type: Object as () => BacktestResultInMemory,
},
selectedBacktestResultKey: { required: false, default: '', type: String },
canUseModify: { required: false, default: false, type: Boolean },
});
</script>
<template>
<div class="d-flex flex-column me-2 text-start">
<div class="fw-bold">
@ -13,17 +26,4 @@
</div>
</template>
<script setup lang="ts">
import { BacktestResultInMemory } from '@/types';
defineProps({
backtestResult: {
required: true,
type: Object as () => BacktestResultInMemory,
},
selectedBacktestResultKey: { required: false, default: '', type: String },
canUseModify: { required: false, default: false, type: Boolean },
});
</script>
<style scoped></style>

View File

@ -1,3 +1,55 @@
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
import { BacktestPayload } from '@/types';
import { useBtStore } from '@/stores/btStore';
const botStore = useBotStore();
const btStore = useBtStore();
function clickBacktest() {
const btPayload: BacktestPayload = {
strategy: btStore.strategy,
timerange: btStore.timerange,
enable_protections: btStore.enableProtections,
};
const openTradesInt = parseInt(btStore.maxOpenTrades, 10);
if (openTradesInt) {
btPayload.max_open_trades = openTradesInt;
}
if (btStore.stakeAmountUnlimited) {
btPayload.stake_amount = 'unlimited';
} else {
const stakeAmountLoc = Number(btStore.stakeAmount);
if (stakeAmountLoc) {
btPayload.stake_amount = stakeAmountLoc.toString();
}
}
const startingCapitalLoc = Number(btStore.startingCapital);
if (startingCapitalLoc) {
btPayload.dry_run_wallet = startingCapitalLoc;
}
if (btStore.selectedTimeframe) {
btPayload.timeframe = btStore.selectedTimeframe;
}
if (btStore.selectedDetailTimeframe) {
btPayload.timeframe_detail = btStore.selectedDetailTimeframe;
}
if (!btStore.allowCache) {
btPayload.backtest_cache = 'none';
}
if (btStore.freqAI.enabled) {
btPayload.freqaimodel = btStore.freqAI.model;
if (btStore.freqAI.identifier !== '') {
btPayload.freqai = { identifier: btStore.freqAI.identifier };
}
}
botStore.activeBot.startBacktest(btPayload);
}
</script>
<template>
<div class="mb-2">
<span>Strategy</span>
@ -199,56 +251,4 @@
</div>
</template>
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
import { BacktestPayload } from '@/types';
import { useBtStore } from '@/stores/btStore';
const botStore = useBotStore();
const btStore = useBtStore();
function clickBacktest() {
const btPayload: BacktestPayload = {
strategy: btStore.strategy,
timerange: btStore.timerange,
enable_protections: btStore.enableProtections,
};
const openTradesInt = parseInt(btStore.maxOpenTrades, 10);
if (openTradesInt) {
btPayload.max_open_trades = openTradesInt;
}
if (btStore.stakeAmountUnlimited) {
btPayload.stake_amount = 'unlimited';
} else {
const stakeAmountLoc = Number(btStore.stakeAmount);
if (stakeAmountLoc) {
btPayload.stake_amount = stakeAmountLoc.toString();
}
}
const startingCapitalLoc = Number(btStore.startingCapital);
if (startingCapitalLoc) {
btPayload.dry_run_wallet = startingCapitalLoc;
}
if (btStore.selectedTimeframe) {
btPayload.timeframe = btStore.selectedTimeframe;
}
if (btStore.selectedDetailTimeframe) {
btPayload.timeframe_detail = btStore.selectedDetailTimeframe;
}
if (!btStore.allowCache) {
btPayload.backtest_cache = 'none';
}
if (btStore.freqAI.enabled) {
btPayload.freqaimodel = btStore.freqAI.model;
if (btStore.freqAI.identifier !== '') {
btPayload.freqai = { identifier: btStore.freqAI.identifier };
}
}
botStore.activeBot.startBacktest(btPayload);
}
</script>
<style scoped></style>

View File

@ -1,65 +1,3 @@
<template>
<div>
<div class="mb-2">
<label class="me-auto h3">Balance</label>
<div class="float-end d-flex flex-row">
<BButton
v-if="canUseBotBalance"
size="sm"
:title="!showBotOnly ? 'Showing Account balance' : 'Showing Bot balance'"
@click="showBotOnly = !showBotOnly"
>
<i-mdi-robot v-if="showBotOnly" />
<i-mdi-bank v-else />
</BButton>
<BButton
size="sm"
:title="!hideSmallBalances ? 'Hide small balances' : 'Show all balances'"
@click="hideSmallBalances = !hideSmallBalances"
>
<i-mdi-eye-off v-if="hideSmallBalances" />
<i-mdi-eye v-else />
</BButton>
<BButton class="float-end" size="sm" @click="refreshBalance">
<i-mdi-refresh />
</BButton>
</div>
</div>
<BalanceChart v-if="balanceCurrencies" :currencies="chartValues" />
<div>
<p v-if="botStore.activeBot.balance.note">
<strong>{{ botStore.activeBot.balance.note }}</strong>
</p>
<BTable class="table-sm" :items="balanceCurrencies" :fields="tableFields">
<template #custom-foot>
<td class="pt-1"><strong>Total</strong></td>
<td class="pt-1">
<span
class="font-italic"
:title="`Increase over initial capital of ${formatCurrency(
botStore.activeBot.balance.starting_capital,
)} ${botStore.activeBot.balance.stake}`"
>
{{ formatPercent(botStore.activeBot.balance.starting_capital_ratio) }}
</span>
</td>
<!-- this is a computed prop that adds up all the expenses in the visible rows -->
<td class="pt-1">
<strong>
{{
showBotOnly && canUseBotBalance
? formatCurrency(botStore.activeBot.balance.total_bot)
: formatCurrency(botStore.activeBot.balance.total)
}}
</strong>
</td>
</template>
</BTable>
</div>
</div>
</template>
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
import { BalanceValues } from '@/types';
@ -132,3 +70,65 @@ onMounted(() => {
refreshBalance();
});
</script>
<template>
<div>
<div class="mb-2">
<label class="me-auto h3">Balance</label>
<div class="float-end d-flex flex-row">
<BButton
v-if="canUseBotBalance"
size="sm"
:title="!showBotOnly ? 'Showing Account balance' : 'Showing Bot balance'"
@click="showBotOnly = !showBotOnly"
>
<i-mdi-robot v-if="showBotOnly" />
<i-mdi-bank v-else />
</BButton>
<BButton
size="sm"
:title="!hideSmallBalances ? 'Hide small balances' : 'Show all balances'"
@click="hideSmallBalances = !hideSmallBalances"
>
<i-mdi-eye-off v-if="hideSmallBalances" />
<i-mdi-eye v-else />
</BButton>
<BButton class="float-end" size="sm" @click="refreshBalance">
<i-mdi-refresh />
</BButton>
</div>
</div>
<BalanceChart v-if="balanceCurrencies" :currencies="chartValues" />
<div>
<p v-if="botStore.activeBot.balance.note">
<strong>{{ botStore.activeBot.balance.note }}</strong>
</p>
<BTable class="table-sm" :items="balanceCurrencies" :fields="tableFields">
<template #custom-foot>
<td class="pt-1"><strong>Total</strong></td>
<td class="pt-1">
<span
class="font-italic"
:title="`Increase over initial capital of ${formatCurrency(
botStore.activeBot.balance.starting_capital,
)} ${botStore.activeBot.balance.stake}`"
>
{{ formatPercent(botStore.activeBot.balance.starting_capital_ratio) }}
</span>
</td>
<!-- this is a computed prop that adds up all the expenses in the visible rows -->
<td class="pt-1">
<strong>
{{
showBotOnly && canUseBotBalance
? formatCurrency(botStore.activeBot.balance.total_bot)
: formatCurrency(botStore.activeBot.balance.total)
}}
</strong>
</td>
</template>
</BTable>
</div>
</div>
</template>

View File

@ -1,76 +1,3 @@
<template>
<BTable
ref="tradesTable"
small
hover
show-empty
primary-key="botId"
:items="tableItems"
:fields="tableFields"
>
<template #cell(botName)="{ item, value }">
<div class="d-flex flex-row">
<BFormCheckbox
v-if="item.botId && botStore.botCount > 1"
v-model="
botStore.botStores[(item as unknown as ComparisonTableItems).botId ?? ''].isSelected
"
title="Show this bot in Dashboard"
>{{ value }}</BFormCheckbox
>
<BFormCheckbox
v-if="!item.botId && botStore.botCount > 1"
v-model="allToggled"
title="Toggle all bots"
>{{ value }}</BFormCheckbox
>
<span v-if="botStore.botCount <= 1">{{ value }}</span>
</div>
</template>
<template #cell(profitOpen)="{ item }">
<ProfitPill
v-if="item.profitOpen && item.botId != 'Summary'"
:profit-ratio="(item as unknown as ComparisonTableItems).profitOpenRatio"
:profit-abs="(item as unknown as ComparisonTableItems).profitOpen"
:profit-desc="`Total Profit (Open and realized) ${formatPercent(
(item as unknown as ComparisonTableItems).profitOpenRatio ?? 0.0,
)}`"
:stake-currency="(item as unknown as ComparisonTableItems).stakeCurrency"
/>
</template>
<template #cell(profitClosed)="{ item }">
<ProfitPill
v-if="item.profitClosed && item.botId != 'Summary'"
:profit-ratio="(item as unknown as ComparisonTableItems).profitClosedRatio"
:profit-abs="(item as unknown as ComparisonTableItems).profitClosed"
:stake-currency="(item as unknown as ComparisonTableItems).stakeCurrency"
/>
</template>
<template #cell(balance)="{ item }">
<div v-if="item.balance">
<span :title="(item as unknown as ComparisonTableItems).stakeCurrency"
>{{
formatPrice(
(item as unknown as ComparisonTableItems).balance ?? 0,
(item as unknown as ComparisonTableItems).stakeCurrencyDecimals,
)
}}
</span>
<span class="text-small">{{
` ${item.stakeCurrency}${item.isDryRun ? ' (dry)' : ''}`
}}</span>
</div>
</template>
<template #cell(winVsLoss)="{ item }">
<div v-if="item.losses !== undefined">
<span class="text-profit">{{ item.wins }}</span> /
<span class="text-loss">{{ item.losses }}</span>
</div>
</template>
</BTable>
</template>
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
import { ProfitInterface, ComparisonTableItems } from '@/types';
@ -159,4 +86,77 @@ const tableItems = computed<TableItem[]>(() => {
});
</script>
<template>
<BTable
ref="tradesTable"
small
hover
show-empty
primary-key="botId"
:items="tableItems"
:fields="tableFields"
>
<template #cell(botName)="{ item, value }">
<div class="d-flex flex-row">
<BFormCheckbox
v-if="item.botId && botStore.botCount > 1"
v-model="
botStore.botStores[(item as unknown as ComparisonTableItems).botId ?? ''].isSelected
"
title="Show this bot in Dashboard"
>{{ value }}</BFormCheckbox
>
<BFormCheckbox
v-if="!item.botId && botStore.botCount > 1"
v-model="allToggled"
title="Toggle all bots"
>{{ value }}</BFormCheckbox
>
<span v-if="botStore.botCount <= 1">{{ value }}</span>
</div>
</template>
<template #cell(profitOpen)="{ item }">
<ProfitPill
v-if="item.profitOpen && item.botId != 'Summary'"
:profit-ratio="(item as unknown as ComparisonTableItems).profitOpenRatio"
:profit-abs="(item as unknown as ComparisonTableItems).profitOpen"
:profit-desc="`Total Profit (Open and realized) ${formatPercent(
(item as unknown as ComparisonTableItems).profitOpenRatio ?? 0.0,
)}`"
:stake-currency="(item as unknown as ComparisonTableItems).stakeCurrency"
/>
</template>
<template #cell(profitClosed)="{ item }">
<ProfitPill
v-if="item.profitClosed && item.botId != 'Summary'"
:profit-ratio="(item as unknown as ComparisonTableItems).profitClosedRatio"
:profit-abs="(item as unknown as ComparisonTableItems).profitClosed"
:stake-currency="(item as unknown as ComparisonTableItems).stakeCurrency"
/>
</template>
<template #cell(balance)="{ item }">
<div v-if="item.balance">
<span :title="(item as unknown as ComparisonTableItems).stakeCurrency"
>{{
formatPrice(
(item as unknown as ComparisonTableItems).balance ?? 0,
(item as unknown as ComparisonTableItems).stakeCurrencyDecimals,
)
}}
</span>
<span class="text-small">{{
` ${item.stakeCurrency}${item.isDryRun ? ' (dry)' : ''}`
}}</span>
</div>
</template>
<template #cell(winVsLoss)="{ item }">
<div v-if="item.losses !== undefined">
<span class="text-profit">{{ item.wins }}</span> /
<span class="text-loss">{{ item.losses }}</span>
</div>
</template>
</BTable>
</template>
<style scoped></style>

View File

@ -1,69 +1,4 @@
forceexit
<template>
<div>
<button
class="btn btn-secondary btn-sm ms-1"
:disabled="!botStore.activeBot.isTrading || isRunning"
title="Start Trading"
@click="botStore.activeBot.startBot()"
>
<i-mdi-play height="24" width="24" />
</button>
<button
class="btn btn-secondary btn-sm ms-1"
:disabled="!botStore.activeBot.isTrading || !isRunning"
title="Stop Trading - Also stops handling open trades."
@click="handleStopBot()"
>
<i-mdi-stop height="24" width="24" />
</button>
<button
class="btn btn-secondary btn-sm ms-1"
:disabled="!botStore.activeBot.isTrading || !isRunning"
title="StopBuy - Stops buying, but still handles open trades"
@click="handleStopBuy()"
>
<i-mdi-pause height="24" width="24" />
</button>
<button
class="btn btn-secondary btn-sm ms-1"
:disabled="!botStore.activeBot.isTrading"
title="Reload Config - reloads configuration including strategy, resetting all settings changed on the fly."
@click="handleReloadConfig()"
>
<i-mdi-reload height="24" width="24" />
</button>
<button
class="btn btn-secondary btn-sm ms-1"
:disabled="!botStore.activeBot.isTrading"
title="Force exit all"
@click="handleForceExit()"
>
<i-mdi-close-box-multiple height="24" width="24" />
</button>
<button
v-if="botStore.activeBot.botState && botStore.activeBot.botState.force_entry_enable"
class="btn btn-secondary btn-sm ms-1"
:disabled="!botStore.activeBot.isTrading || !isRunning"
title="Force enter - Immediately enter a trade at an optional price. Exits are then handled according to strategy rules."
@click="forceEnter = true"
>
<i-mdi-plus-box-multiple-outline style="font-size: 20px" />
</button>
<button
v-if="botStore.activeBot.isWebserverMode && false"
:disabled="botStore.activeBot.isTrading"
class="btn btn-secondary btn-sm ms-1"
title="Start Trading mode"
@click="botStore.activeBot.startTrade()"
>
<i-mdi-play class="fs-4" />
</button>
<ForceEntryForm v-model="forceEnter" :pair="botStore.activeBot.selectedPair" />
<MessageBox ref="msgBox" />
</div>
</template>
<script setup lang="ts">
import MessageBox, { MsgBoxObject } from '@/components/general/MessageBox.vue';
import { useBotStore } from '@/stores/ftbotwrapper';
@ -128,3 +63,68 @@ const handleForceExit = () => {
msgBox.value?.show(msg);
};
</script>
<template>
<div>
<button
class="btn btn-secondary btn-sm ms-1"
:disabled="!botStore.activeBot.isTrading || isRunning"
title="Start Trading"
@click="botStore.activeBot.startBot()"
>
<i-mdi-play height="24" width="24" />
</button>
<button
class="btn btn-secondary btn-sm ms-1"
:disabled="!botStore.activeBot.isTrading || !isRunning"
title="Stop Trading - Also stops handling open trades."
@click="handleStopBot()"
>
<i-mdi-stop height="24" width="24" />
</button>
<button
class="btn btn-secondary btn-sm ms-1"
:disabled="!botStore.activeBot.isTrading || !isRunning"
title="StopBuy - Stops buying, but still handles open trades"
@click="handleStopBuy()"
>
<i-mdi-pause height="24" width="24" />
</button>
<button
class="btn btn-secondary btn-sm ms-1"
:disabled="!botStore.activeBot.isTrading"
title="Reload Config - reloads configuration including strategy, resetting all settings changed on the fly."
@click="handleReloadConfig()"
>
<i-mdi-reload height="24" width="24" />
</button>
<button
class="btn btn-secondary btn-sm ms-1"
:disabled="!botStore.activeBot.isTrading"
title="Force exit all"
@click="handleForceExit()"
>
<i-mdi-close-box-multiple height="24" width="24" />
</button>
<button
v-if="botStore.activeBot.botState && botStore.activeBot.botState.force_entry_enable"
class="btn btn-secondary btn-sm ms-1"
:disabled="!botStore.activeBot.isTrading || !isRunning"
title="Force enter - Immediately enter a trade at an optional price. Exits are then handled according to strategy rules."
@click="forceEnter = true"
>
<i-mdi-plus-box-multiple-outline style="font-size: 20px" />
</button>
<button
v-if="botStore.activeBot.isWebserverMode && false"
:disabled="botStore.activeBot.isTrading"
class="btn btn-secondary btn-sm ms-1"
title="Start Trading mode"
@click="botStore.activeBot.startTrade()"
>
<i-mdi-play class="fs-4" />
</button>
<ForceEntryForm v-model="forceEnter" :pair="botStore.activeBot.selectedPair" />
<MessageBox ref="msgBox" />
</div>
</template>

View File

@ -1,12 +1,3 @@
<template>
<BTable class="text-start" small borderless :items="profitItems" :fields="profitFields">
<template #cell(value)="row">
<DateTimeTZ v-if="row.item.isTs && row.value" :date="row.value as number"></DateTimeTZ>
<template v-else>{{ row.value }}</template>
</template>
</BTable>
</template>
<script setup lang="ts">
import { ProfitInterface } from '@/types';
import { TableField, TableItem } from 'bootstrap-vue-next';
@ -125,3 +116,12 @@ const profitItems = computed<TableItem[]>(() => {
];
});
</script>
<template>
<BTable class="text-start" small borderless :items="profitItems" :fields="profitFields">
<template #cell(value)="row">
<DateTimeTZ v-if="row.item.isTs && row.value" :date="row.value as number"></DateTimeTZ>
<template v-else>{{ row.value }}</template>
</template>
</BTable>
</template>

View File

@ -1,3 +1,9 @@
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
const botStore = useBotStore();
</script>
<template>
<div v-if="botStore.activeBot.botState">
<p>
@ -84,9 +90,3 @@
/>
</div>
</template>
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
const botStore = useBotStore();
</script>

View File

@ -1,3 +1,34 @@
<script setup lang="ts">
import { Trade } from '@/types';
import { useBotStore } from '@/stores/ftbotwrapper';
const props = defineProps({
trades: { required: true, type: Array as () => Trade[] },
title: { default: 'Trades', type: String },
stakeCurrency: { required: false, default: '', type: String },
activeTrades: { default: false, type: Boolean },
showFilter: { default: false, type: Boolean },
multiBotView: { default: false, type: Boolean },
emptyText: { default: 'No Trades to show.', type: String },
stakeCurrencyDecimals: { default: 3, type: Number },
});
const botStore = useBotStore();
const currentPage = ref(1);
const filterText = ref('');
const perPage = props.activeTrades ? 200 : 25;
const rows = computed(() => props.trades.length);
const filteredTrades = computed(() => {
return props.trades.slice((currentPage.value - 1) * perPage, currentPage.value * perPage);
});
const tradeClick = (trade) => {
botStore.activeBot.setDetailTrade(trade);
};
</script>
<template>
<div class="h-100 overflow-auto p-1">
<BListGroup id="tradeList">
@ -33,37 +64,6 @@
</div>
</template>
<script setup lang="ts">
import { Trade } from '@/types';
import { useBotStore } from '@/stores/ftbotwrapper';
const props = defineProps({
trades: { required: true, type: Array as () => Trade[] },
title: { default: 'Trades', type: String },
stakeCurrency: { required: false, default: '', type: String },
activeTrades: { default: false, type: Boolean },
showFilter: { default: false, type: Boolean },
multiBotView: { default: false, type: Boolean },
emptyText: { default: 'No Trades to show.', type: String },
stakeCurrencyDecimals: { default: 3, type: Number },
});
const botStore = useBotStore();
const currentPage = ref(1);
const filterText = ref('');
const perPage = props.activeTrades ? 200 : 25;
const rows = computed(() => props.trades.length);
const filteredTrades = computed(() => {
return props.trades.slice((currentPage.value - 1) * perPage, currentPage.value * perPage);
});
const tradeClick = (trade) => {
botStore.activeBot.setDetailTrade(trade);
};
</script>
<style lang="scss" scoped>
.my-05 {
margin-top: 0.125rem;

View File

@ -1,20 +1,3 @@
<template>
<div class="d-flex">
<div
class="px-1 d-flex flex-row flex-fill text-start justify-content-between align-items-center"
>
<span>
<span class="me-1 fw-bold">{{ trade.pair }}</span>
<small class="text-secondary">(#{{ trade.trade_id }})</small>
</span>
<small>
<DateTimeTZ :date="trade.open_timestamp" :date-only="true" />
</small>
</div>
<TradeProfit class="col-5" :trade="trade" />
</div>
</template>
<script setup lang="ts">
import { Trade } from '@/types';
import TradeProfit from './TradeProfit.vue';
@ -35,6 +18,23 @@ defineProps({
});
</script>
<template>
<div class="d-flex">
<div
class="px-1 d-flex flex-row flex-fill text-start justify-content-between align-items-center"
>
<span>
<span class="me-1 fw-bold">{{ trade.pair }}</span>
<small class="text-secondary">(#{{ trade.trade_id }})</small>
</span>
<small>
<DateTimeTZ :date="trade.open_timestamp" :date-only="true" />
</small>
</div>
<TradeProfit class="col-5" :trade="trade" />
</div>
</template>
<style lang="scss" scoped>
.card-body {
padding: 0 0.2em;

View File

@ -1,26 +1,3 @@
<template>
<div class="w-100 d-flex">
<BFormSelect
id="exchange-select"
v-model="exchangeModel.exchange"
size="sm"
:options="exchangeList"
>
</BFormSelect>
<BFormSelect
id="tradeMode-select"
v-model="exchangeModel.trade_mode"
size="sm"
:options="tradeModes"
:disabled="tradeModes.length < 2"
>
</BFormSelect>
<BButton class="ms-2 no-min-w" size="sm" @click="botStore.activeBot.getExchangeList">
<i-mdi-refresh />
</BButton>
</div>
</template>
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
@ -78,3 +55,26 @@ onMounted(() => {
}
});
</script>
<template>
<div class="w-100 d-flex">
<BFormSelect
id="exchange-select"
v-model="exchangeModel.exchange"
size="sm"
:options="exchangeList"
>
</BFormSelect>
<BFormSelect
id="tradeMode-select"
v-model="exchangeModel.trade_mode"
size="sm"
:options="tradeModes"
:disabled="tradeModes.length < 2"
>
</BFormSelect>
<BButton class="ms-2 no-min-w" size="sm" @click="botStore.activeBot.getExchangeList">
<i-mdi-refresh />
</BButton>
</div>
</template>

View File

@ -1,3 +1,92 @@
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
import { ForceEnterPayload, OrderSides } from '@/types';
const props = defineProps({
pair: { type: String, default: '' },
positionIncrease: { type: Boolean, default: false },
});
const model = defineModel<boolean>();
const botStore = useBotStore();
const form = ref<HTMLFormElement>();
const selectedPair = ref('');
const price = ref<number | undefined>(undefined);
const stakeAmount = ref<number | undefined>(undefined);
const leverage = ref<number | undefined>(undefined);
const ordertype = ref('');
const orderSide = ref<OrderSides>(OrderSides.long);
const enterTag = ref('force_entry');
const orderTypeOptions = [
{ value: 'market', text: 'Market' },
{ value: 'limit', text: 'Limit' },
];
const orderSideOptions = [
{ value: 'long', text: 'Long' },
{ value: 'short', text: 'Short' },
];
const checkFormValidity = () => {
const valid = form.value?.checkValidity();
return valid;
};
const handleSubmit = async () => {
// Exit when the form isn't valid
if (!checkFormValidity()) {
return;
}
// call forceentry
const payload: ForceEnterPayload = { pair: selectedPair.value };
if (price.value) {
payload.price = Number(price.value);
}
if (ordertype.value) {
payload.ordertype = ordertype.value;
}
if (stakeAmount.value) {
payload.stakeamount = stakeAmount.value;
}
if (botStore.activeBot.botApiVersion >= 2.13 && botStore.activeBot.shortAllowed) {
payload.side = orderSide.value;
}
if (botStore.activeBot.botApiVersion >= 2.16 && enterTag.value) {
payload.entry_tag = enterTag.value;
}
if (leverage.value) {
payload.leverage = leverage.value;
}
botStore.activeBot.forceentry(payload);
await nextTick();
model.value = false;
};
const resetForm = () => {
console.log('resetForm');
selectedPair.value = props.pair;
price.value = undefined;
stakeAmount.value = undefined;
ordertype.value =
botStore.activeBot.botState?.order_types?.forcebuy ||
botStore.activeBot.botState?.order_types?.force_entry ||
botStore.activeBot.botState?.order_types?.buy ||
botStore.activeBot.botState?.order_types?.entry ||
'limit';
};
const handleEntry = () => {
// Trigger submit handler
handleSubmit();
};
const inputSelect = (bvModalEvt) => {
bvModalEvt.srcElement?.select();
};
</script>
<template>
<BModal
id="forceentry-modal"
@ -117,92 +206,3 @@
</form>
</BModal>
</template>
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
import { ForceEnterPayload, OrderSides } from '@/types';
const props = defineProps({
pair: { type: String, default: '' },
positionIncrease: { type: Boolean, default: false },
});
const model = defineModel<boolean>();
const botStore = useBotStore();
const form = ref<HTMLFormElement>();
const selectedPair = ref('');
const price = ref<number | undefined>(undefined);
const stakeAmount = ref<number | undefined>(undefined);
const leverage = ref<number | undefined>(undefined);
const ordertype = ref('');
const orderSide = ref<OrderSides>(OrderSides.long);
const enterTag = ref('force_entry');
const orderTypeOptions = [
{ value: 'market', text: 'Market' },
{ value: 'limit', text: 'Limit' },
];
const orderSideOptions = [
{ value: 'long', text: 'Long' },
{ value: 'short', text: 'Short' },
];
const checkFormValidity = () => {
const valid = form.value?.checkValidity();
return valid;
};
const handleSubmit = async () => {
// Exit when the form isn't valid
if (!checkFormValidity()) {
return;
}
// call forceentry
const payload: ForceEnterPayload = { pair: selectedPair.value };
if (price.value) {
payload.price = Number(price.value);
}
if (ordertype.value) {
payload.ordertype = ordertype.value;
}
if (stakeAmount.value) {
payload.stakeamount = stakeAmount.value;
}
if (botStore.activeBot.botApiVersion >= 2.13 && botStore.activeBot.shortAllowed) {
payload.side = orderSide.value;
}
if (botStore.activeBot.botApiVersion >= 2.16 && enterTag.value) {
payload.entry_tag = enterTag.value;
}
if (leverage.value) {
payload.leverage = leverage.value;
}
botStore.activeBot.forceentry(payload);
await nextTick();
model.value = false;
};
const resetForm = () => {
console.log('resetForm');
selectedPair.value = props.pair;
price.value = undefined;
stakeAmount.value = undefined;
ordertype.value =
botStore.activeBot.botState?.order_types?.forcebuy ||
botStore.activeBot.botState?.order_types?.force_entry ||
botStore.activeBot.botState?.order_types?.buy ||
botStore.activeBot.botState?.order_types?.entry ||
'limit';
};
const handleEntry = () => {
// Trigger submit handler
handleSubmit();
};
const inputSelect = (bvModalEvt) => {
bvModalEvt.srcElement?.select();
};
</script>

View File

@ -1,58 +1,3 @@
<template>
<div>
<BModal
id="forceexit-modal"
v-model="model"
title="Force exiting a trade"
@show="resetForm"
@hidden="resetForm"
@ok="handleEntry"
>
<form ref="form" @submit.stop.prevent="handleSubmit">
<p>
<span>Exiting Trade #{{ trade.trade_id }} {{ trade.pair }}.</span>
<br />
<span>Currently owning {{ trade.amount }} {{ trade.base_currency }}</span>
</p>
<BFormGroup
label-for="stake-input"
invalid-feedback="Amount must be empty or a positive number"
:state="amount !== undefined && amount > 0"
>
<template #label>
<span class="fst-italic">*Amount in {{ trade.base_currency }} [optional]</span>
<span class="ms-1 fst-italic">{{ amountInBase }}</span>
</template>
<BFormInput id="stake-input" v-model="amount" type="number" step="0.000001"></BFormInput>
<BFormInput
id="stake-input"
v-model="amount"
type="range"
step="0.000001"
min="0"
:max="trade.amount"
></BFormInput>
</BFormGroup>
<BFormGroup
label="*OrderType"
label-for="ordertype-input"
invalid-feedback="OrderType"
:state="ordertype !== undefined"
>
<BFormSelect
v-model="ordertype"
:options="['market', 'limit']"
style="min-width: 7em"
size="sm"
>
</BFormSelect>
</BFormGroup>
</form>
</BModal>
</div>
</template>
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
import { ForceSellPayload, Trade } from '@/types';
@ -121,3 +66,58 @@ const amountInBase = computed<string>(() => {
: '';
});
</script>
<template>
<div>
<BModal
id="forceexit-modal"
v-model="model"
title="Force exiting a trade"
@show="resetForm"
@hidden="resetForm"
@ok="handleEntry"
>
<form ref="form" @submit.stop.prevent="handleSubmit">
<p>
<span>Exiting Trade #{{ trade.trade_id }} {{ trade.pair }}.</span>
<br />
<span>Currently owning {{ trade.amount }} {{ trade.base_currency }}</span>
</p>
<BFormGroup
label-for="stake-input"
invalid-feedback="Amount must be empty or a positive number"
:state="amount !== undefined && amount > 0"
>
<template #label>
<span class="fst-italic">*Amount in {{ trade.base_currency }} [optional]</span>
<span class="ms-1 fst-italic">{{ amountInBase }}</span>
</template>
<BFormInput id="stake-input" v-model="amount" type="number" step="0.000001"></BFormInput>
<BFormInput
id="stake-input"
v-model="amount"
type="range"
step="0.000001"
min="0"
:max="trade.amount"
></BFormInput>
</BFormGroup>
<BFormGroup
label="*OrderType"
label-for="ordertype-input"
invalid-feedback="OrderType"
:state="ordertype !== undefined"
>
<BFormSelect
v-model="ordertype"
:options="['market', 'limit']"
style="min-width: 7em"
size="sm"
>
</BFormSelect>
</BFormGroup>
</form>
</BModal>
</div>
</template>

View File

@ -1,3 +1,16 @@
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
const locFreqaiModel = defineModel<string>();
const botStore = useBotStore();
onMounted(() => {
if (botStore.activeBot.freqaiModelList.length === 0) {
botStore.activeBot.getFreqAIModelList();
}
});
</script>
<template>
<div>
<div class="w-100 d-flex">
@ -15,16 +28,3 @@
</div>
</div>
</template>
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
const locFreqaiModel = defineModel<string>();
const botStore = useBotStore();
onMounted(() => {
if (botStore.activeBot.freqaiModelList.length === 0) {
botStore.activeBot.getFreqAIModelList();
}
});
</script>

View File

@ -1,25 +1,3 @@
<template>
<div class="d-flex h-100 p-0 align-items-start">
<div ref="scrollContainer" class="border p-1 text-start pb-5 w-100 h-100 overflow-auto">
<pre
v-for="(log, index) in botStore.activeBot.lastLogs"
:key="index"
class="m-0 overflow-visible"
style="line-height: unset"
><span class="text-muted">{{ log[0] }} <span :class="getLogColor(log[3])">{{ log[3].padEnd(7, ' ') }}</span> {{ log[2] }} - </span><span class="text-{{ log[1] }}">{{ log[4] }}</span
></pre>
</div>
<div class="d-flex flex-column gap-1 ms-1">
<BButton id="refresh-logs" size="sm" title="Reload Logs" @click="refreshLogs">
<i-mdi-refresh />
</BButton>
<BButton size="sm" title="Scroll to bottom" @click="scrollToBottom">
<i-mdi-arrow-down-thick />
</BButton>
</div>
</div>
</template>
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
@ -53,6 +31,28 @@ function scrollToBottom() {
}
</script>
<template>
<div class="d-flex h-100 p-0 align-items-start">
<div ref="scrollContainer" class="border p-1 text-start pb-5 w-100 h-100 overflow-auto">
<pre
v-for="(log, index) in botStore.activeBot.lastLogs"
:key="index"
class="m-0 overflow-visible"
style="line-height: unset"
><span class="text-muted">{{ log[0] }} <span :class="getLogColor(log[3])">{{ log[3].padEnd(7, ' ') }}</span> {{ log[2] }} - </span><span class="text-{{ log[1] }}">{{ log[4] }}</span
></pre>
</div>
<div class="d-flex flex-column gap-1 ms-1">
<BButton id="refresh-logs" size="sm" title="Reload Logs" @click="refreshLogs">
<i-mdi-refresh />
</BButton>
<BButton size="sm" title="Scroll to bottom" @click="scrollToBottom">
<i-mdi-arrow-down-thick />
</BButton>
</div>
</div>
</template>
<style lang="scss" scoped>
textarea {
width: 100%;

View File

@ -1,3 +1,56 @@
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
const newblacklistpair = ref('');
const blackListShow = ref(false);
const blacklistSelect = ref<number[]>([]);
const botStore = useBotStore();
const initBlacklist = () => {
if (botStore.activeBot.whitelist.length === 0) {
botStore.activeBot.getWhitelist();
}
if (botStore.activeBot.blacklist.length === 0) {
botStore.activeBot.getBlacklist();
}
};
const addBlacklistPair = () => {
if (newblacklistpair.value) {
blackListShow.value = false;
botStore.activeBot.addBlacklist({ blacklist: [newblacklistpair.value] });
newblacklistpair.value = '';
}
};
const blacklistSelectClick = (key) => {
const index = blacklistSelect.value.indexOf(key);
if (index > -1) {
blacklistSelect.value.splice(index, 1);
} else {
blacklistSelect.value.push(key);
}
};
const deletePairs = () => {
if (blacklistSelect.value.length === 0) {
console.log('nothing to delete');
return;
}
// const pairlist = blacklistSelect.value;
const pairlist = botStore.activeBot.blacklist.filter(
(value, index) => blacklistSelect.value.indexOf(index) > -1,
);
console.log('Deleting pairs: ', pairlist);
botStore.activeBot.deleteBlacklist(pairlist);
blacklistSelect.value = [];
};
onMounted(() => {
initBlacklist();
});
</script>
<template>
<div>
<div>
@ -94,59 +147,6 @@
</div>
</template>
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
const newblacklistpair = ref('');
const blackListShow = ref(false);
const blacklistSelect = ref<number[]>([]);
const botStore = useBotStore();
const initBlacklist = () => {
if (botStore.activeBot.whitelist.length === 0) {
botStore.activeBot.getWhitelist();
}
if (botStore.activeBot.blacklist.length === 0) {
botStore.activeBot.getBlacklist();
}
};
const addBlacklistPair = () => {
if (newblacklistpair.value) {
blackListShow.value = false;
botStore.activeBot.addBlacklist({ blacklist: [newblacklistpair.value] });
newblacklistpair.value = '';
}
};
const blacklistSelectClick = (key) => {
const index = blacklistSelect.value.indexOf(key);
if (index > -1) {
blacklistSelect.value.splice(index, 1);
} else {
blacklistSelect.value.push(key);
}
};
const deletePairs = () => {
if (blacklistSelect.value.length === 0) {
console.log('nothing to delete');
return;
}
// const pairlist = blacklistSelect.value;
const pairlist = botStore.activeBot.blacklist.filter(
(value, index) => blacklistSelect.value.indexOf(index) > -1,
);
console.log('Deleting pairs: ', pairlist);
botStore.activeBot.deleteBlacklist(pairlist);
blacklistSelect.value = [];
};
onMounted(() => {
initBlacklist();
});
</script>
<style scoped lang="scss">
.check {
// Hidden checkbox on blacklist selection

View File

@ -1,3 +1,27 @@
<script setup lang="ts">
import { Lock } from '@/types';
import { useBotStore } from '@/stores/ftbotwrapper';
import { TableField } from 'bootstrap-vue-next';
const botStore = useBotStore();
const tableFields: TableField[] = [
{ key: 'pair', label: 'Pair' },
{ key: 'lock_end_timestamp', label: 'Until', formatter: (value) => timestampms(value as number) },
{ key: 'reason', label: 'Reason' },
{ key: 'actions' },
];
const removePairLock = (item: Lock) => {
console.log(item);
if (item.id !== undefined) {
botStore.activeBot.deleteLock(item.id);
} else {
showAlert('This Freqtrade version does not support deleting locks.');
}
};
</script>
<template>
<div>
<div class="mb-2">
@ -23,28 +47,4 @@
</div>
</template>
<script setup lang="ts">
import { Lock } from '@/types';
import { useBotStore } from '@/stores/ftbotwrapper';
import { TableField } from 'bootstrap-vue-next';
const botStore = useBotStore();
const tableFields: TableField[] = [
{ key: 'pair', label: 'Pair' },
{ key: 'lock_end_timestamp', label: 'Until', formatter: (value) => timestampms(value as number) },
{ key: 'reason', label: 'Reason' },
{ key: 'actions' },
];
const removePairLock = (item: Lock) => {
console.log(item);
if (item.id !== undefined) {
botStore.activeBot.deleteLock(item.id);
} else {
showAlert('This Freqtrade version does not support deleting locks.');
}
};
</script>
<style scoped></style>

View File

@ -1,41 +1,3 @@
<template>
<div>
<BFormGroup
label-for="trade-filter"
class="mb-2 ms-2"
:class="{
'me-4': backtestMode,
'me-2': !backtestMode,
}"
>
<BFormInput id="trade-filter" v-model="filterText" type="text" placeholder="Filter" />
</BFormGroup>
<BListGroup>
<BListGroupItem
v-for="comb in combinedPairList"
:key="comb.pair"
button
class="d-flex justify-content-between align-items-center py-1"
:active="comb.pair === botStore.activeBot.selectedPair"
:title="`${comb.pair} - ${comb.tradeCount} trades`"
@click="botStore.activeBot.selectedPair = comb.pair"
>
<div>
{{ comb.pair }}
<span v-if="comb.locks" :title="comb.lockReason"> <i-mdi-lock /> </span>
</div>
<TradeProfit v-if="comb.trade && !backtestMode" :trade="comb.trade" />
<ProfitPill
v-if="backtestMode && comb.tradeCount > 0"
:profit-ratio="comb.profit"
:stake-currency="botStore.activeBot.stakeCurrency"
/>
</BListGroupItem>
</BListGroup>
</div>
</template>
<script setup lang="ts">
import { Lock, Trade } from '@/types';
@ -131,6 +93,44 @@ const combinedPairList = computed(() => {
});
</script>
<template>
<div>
<BFormGroup
label-for="trade-filter"
class="mb-2 ms-2"
:class="{
'me-4': backtestMode,
'me-2': !backtestMode,
}"
>
<BFormInput id="trade-filter" v-model="filterText" type="text" placeholder="Filter" />
</BFormGroup>
<BListGroup>
<BListGroupItem
v-for="comb in combinedPairList"
:key="comb.pair"
button
class="d-flex justify-content-between align-items-center py-1"
:active="comb.pair === botStore.activeBot.selectedPair"
:title="`${comb.pair} - ${comb.tradeCount} trades`"
@click="botStore.activeBot.selectedPair = comb.pair"
>
<div>
{{ comb.pair }}
<span v-if="comb.locks" :title="comb.lockReason"> <i-mdi-lock /> </span>
</div>
<TradeProfit v-if="comb.trade && !backtestMode" :trade="comb.trade" />
<ProfitPill
v-if="backtestMode && comb.tradeCount > 0"
:profit-ratio="comb.profit"
:stake-currency="botStore.activeBot.stakeCurrency"
/>
</BListGroupItem>
</BListGroup>
</div>
</template>
<style scoped>
.list-group {
text-align: left;

View File

@ -1,3 +1,8 @@
<script setup lang="ts">
import { usePairlistConfigStore } from '@/stores/pairlistConfig';
import EditValue from '../general/EditValue.vue';
const pairlistStore = usePairlistConfigStore();
</script>
<template>
<div class="d-flex flex-column flex-sm-row mb-2 gap-2">
<BButton
@ -40,8 +45,3 @@
</BButton>
</div>
</template>
<script setup lang="ts">
import { usePairlistConfigStore } from '@/stores/pairlistConfig';
import EditValue from '../general/EditValue.vue';
const pairlistStore = usePairlistConfigStore();
</script>

View File

@ -1,3 +1,13 @@
<script setup lang="ts">
import { usePairlistConfigStore } from '@/stores/pairlistConfig';
const pairlistStore = usePairlistConfigStore();
const copyFromConfig = ref('');
const visible = ref(false);
const configNames = computed(() =>
pairlistStore.savedConfigs.filter((c) => c.name !== pairlistStore.config.name).map((c) => c.name),
);
</script>
<template>
<BCard no-body class="mb-2">
<template #header>
@ -48,14 +58,4 @@
</BCollapse>
</BCard>
</template>
<script setup lang="ts">
import { usePairlistConfigStore } from '@/stores/pairlistConfig';
const pairlistStore = usePairlistConfigStore();
const copyFromConfig = ref('');
const visible = ref(false);
const configNames = computed(() =>
pairlistStore.savedConfigs.filter((c) => c.name !== pairlistStore.config.name).map((c) => c.name),
);
</script>
<style lang="scss" scoped></style>

View File

@ -1,3 +1,24 @@
<script setup lang="ts">
import { usePairlistConfigStore } from '@/stores/pairlistConfig';
import { Pairlist } from '@/types';
const pairlistStore = usePairlistConfigStore();
defineProps<{
index: number;
}>();
const pairlist = defineModel<Pairlist>({ required: true });
const hasParameters = computed(() => Object.keys(pairlist.value.params).length > 0);
function toggleVisible() {
if (hasParameters.value) {
pairlist.value.showParameters = !pairlist.value.showParameters;
}
}
</script>
<template>
<BCard no-body class="mb-2">
<template #header>
@ -54,25 +75,4 @@
</BCard>
</template>
<script setup lang="ts">
import { usePairlistConfigStore } from '@/stores/pairlistConfig';
import { Pairlist } from '@/types';
const pairlistStore = usePairlistConfigStore();
defineProps<{
index: number;
}>();
const pairlist = defineModel<Pairlist>({ required: true });
const hasParameters = computed(() => Object.keys(pairlist.value.params).length > 0);
function toggleVisible() {
if (hasParameters.value) {
pairlist.value.showParameters = !pairlist.value.showParameters;
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -1,3 +1,14 @@
<script setup lang="ts">
import { PairlistParameter, PairlistParamType } from '@/types';
defineProps<{
param: PairlistParameter;
}>();
// TODO: type should really be PairlistParamValue
const paramValue = defineModel<any>();
</script>
<template>
<BFormGroup label-cols="4" label-size="md" class="pb-1 text-start" :description="param.help">
<BFormInput
@ -22,14 +33,3 @@
</template>
</BFormGroup>
</template>
<script setup lang="ts">
import { PairlistParameter, PairlistParamType } from '@/types';
defineProps<{
param: PairlistParameter;
}>();
// TODO: type should really be PairlistParamValue
const paramValue = defineModel<any>();
</script>

View File

@ -1,3 +1,26 @@
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
import { usePairlistConfigStore } from '@/stores/pairlistConfig';
import ChartView from '@/views/ChartsView.vue';
const botStore = useBotStore();
const pairlistStore = usePairlistConfigStore();
const whitelist = ref<{ enabled: boolean; pair: string }[]>([]);
watch(
() => pairlistStore.whitelist,
() => {
whitelist.value = pairlistStore.whitelist.map((p) => {
return {
enabled: true,
pair: p,
};
});
},
);
</script>
<template>
<div>
<div v-if="whitelist.length > 0" class="d-flex flex-column flex-lg-row px-2">
@ -38,26 +61,3 @@
</div>
</div>
</template>
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
import { usePairlistConfigStore } from '@/stores/pairlistConfig';
import ChartView from '@/views/ChartsView.vue';
const botStore = useBotStore();
const pairlistStore = usePairlistConfigStore();
const whitelist = ref<{ enabled: boolean; pair: string }[]>([]);
watch(
() => pairlistStore.whitelist,
() => {
whitelist.value = pairlistStore.whitelist.map((p) => {
return {
enabled: true,
pair: p,
};
});
},
);
</script>

View File

@ -1,3 +1,74 @@
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
import { usePairlistConfigStore } from '@/stores/pairlistConfig';
import PairlistConfigItem from './PairlistConfigItem.vue';
import PairlistConfigBlacklist from './PairlistConfigBlacklist.vue';
import PairlistConfigActions from './PairlistConfigActions.vue';
import { Pairlist } from '@/types';
import { useSortable, moveArrayElement } from '@vueuse/integrations/useSortable';
import ExchangeSelect from './ExchangeSelect.vue';
const botStore = useBotStore();
const pairlistStore = usePairlistConfigStore();
const availablePairlists = ref<Pairlist[]>([]);
const pairlistConfigsEl = ref<HTMLElement | null>(null);
const availablePairlistsEl = ref<HTMLElement | null>(null);
const selectedView = ref<'Config' | 'Results'>('Config');
const configEmpty = computed(() => {
return pairlistStore.config.pairlists.length == 0;
});
useSortable(availablePairlistsEl, availablePairlists.value, {
group: {
name: 'configurator',
pull: 'clone',
put: false,
},
sort: false,
filter: '.no-drag',
dragClass: 'dragging',
});
useSortable(pairlistConfigsEl, pairlistStore.config.pairlists, {
handle: '.handle',
group: 'configurator',
onUpdate: async (e) => {
moveArrayElement(pairlistStore.config.pairlists, e.oldIndex, e.newIndex);
},
onAdd: (e) => {
const pairlist = availablePairlists.value[e.oldIndex];
pairlistStore.addToConfig(pairlist, e.newIndex);
// quick fix from: https://github.com/SortableJS/Sortable/issues/1515
e.clone.replaceWith(e.item);
e.clone.remove();
},
});
onMounted(async () => {
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,
);
pairlistStore.selectOrCreateConfig(
pairlistStore.isSavedConfig(pairlistStore.configName) ? pairlistStore.configName : 'default',
);
});
watch(
() => pairlistStore.whitelist,
() => {
selectedView.value = 'Results';
},
);
</script>
<template>
<div class="d-flex px-3 mb-3 gap-3 flex-column flex-lg-row">
<BListGroup ref="availablePairlistsEl" class="available-pairlists">
@ -90,77 +161,6 @@
</div>
</template>
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
import { usePairlistConfigStore } from '@/stores/pairlistConfig';
import PairlistConfigItem from './PairlistConfigItem.vue';
import PairlistConfigBlacklist from './PairlistConfigBlacklist.vue';
import PairlistConfigActions from './PairlistConfigActions.vue';
import { Pairlist } from '@/types';
import { useSortable, moveArrayElement } from '@vueuse/integrations/useSortable';
import ExchangeSelect from './ExchangeSelect.vue';
const botStore = useBotStore();
const pairlistStore = usePairlistConfigStore();
const availablePairlists = ref<Pairlist[]>([]);
const pairlistConfigsEl = ref<HTMLElement | null>(null);
const availablePairlistsEl = ref<HTMLElement | null>(null);
const selectedView = ref<'Config' | 'Results'>('Config');
const configEmpty = computed(() => {
return pairlistStore.config.pairlists.length == 0;
});
useSortable(availablePairlistsEl, availablePairlists.value, {
group: {
name: 'configurator',
pull: 'clone',
put: false,
},
sort: false,
filter: '.no-drag',
dragClass: 'dragging',
});
useSortable(pairlistConfigsEl, pairlistStore.config.pairlists, {
handle: '.handle',
group: 'configurator',
onUpdate: async (e) => {
moveArrayElement(pairlistStore.config.pairlists, e.oldIndex, e.newIndex);
},
onAdd: (e) => {
const pairlist = availablePairlists.value[e.oldIndex];
pairlistStore.addToConfig(pairlist, e.newIndex);
// quick fix from: https://github.com/SortableJS/Sortable/issues/1515
e.clone.replaceWith(e.item);
e.clone.remove();
},
});
onMounted(async () => {
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,
);
pairlistStore.selectOrCreateConfig(
pairlistStore.isSavedConfig(pairlistStore.configName) ? pairlistStore.configName : 'default',
);
});
watch(
() => pairlistStore.whitelist,
() => {
selectedView.value = 'Results';
},
);
</script>
<style lang="scss" scoped>
.pairlist {
&:hover {