chore: update block-order in components

script before template.
This commit is contained in:
Matthias 2024-07-30 06:23:04 +02:00
parent dc3553bb3e
commit 89181fdc71
31 changed files with 831 additions and 837 deletions

View File

@ -1,37 +1,3 @@
<template>
<div>
<div class="mb-2">
<h3 class="me-auto d-inline">{{ hasWeekly ? 'Period' : 'Daily' }} Breakdown</h3>
<BButton class="float-end" size="sm" @click="refreshSummary">
<i-mdi-refresh />
</BButton>
</div>
<BFormRadioGroup
v-if="hasWeekly"
id="order-direction"
v-model="periodicBreakdownPeriod"
:options="periodicBreakdownSelections"
name="radios-btn-default"
size="sm"
buttons
style="min-width: 10em"
button-variant="outline-primary"
@change="refreshSummary"
></BFormRadioGroup>
<div class="ps-1">
<TimePeriodChart
v-if="selectedStats"
:daily-stats="selectedStatsSorted"
:show-title="false"
/>
</div>
<div>
<BTable class="table-sm" :items="selectedStats.data" :fields="dailyFields"> </BTable>
</div>
</div>
</template>
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
import { TableField } from 'bootstrap-vue-next';
@ -105,3 +71,37 @@ onMounted(() => {
refreshSummary();
});
</script>
<template>
<div>
<div class="mb-2">
<h3 class="me-auto d-inline">{{ hasWeekly ? 'Period' : 'Daily' }} Breakdown</h3>
<BButton class="float-end" size="sm" @click="refreshSummary">
<i-mdi-refresh />
</BButton>
</div>
<BFormRadioGroup
v-if="hasWeekly"
id="order-direction"
v-model="periodicBreakdownPeriod"
:options="periodicBreakdownSelections"
name="radios-btn-default"
size="sm"
buttons
style="min-width: 10em"
button-variant="outline-primary"
@change="refreshSummary"
></BFormRadioGroup>
<div class="ps-1">
<TimePeriodChart
v-if="selectedStats"
:daily-stats="selectedStatsSorted"
:show-title="false"
/>
</div>
<div>
<BTable class="table-sm" :items="selectedStats.data" :fields="dailyFields"> </BTable>
</div>
</div>
</template>

View File

@ -1,3 +1,17 @@
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
const botStore = useBotStore();
const autoRefreshLoc = computed({
get() {
return botStore.globalAutoRefresh;
},
set(newValue: boolean) {
botStore.setGlobalAutoRefresh(newValue);
},
});
</script>
<template>
<div class="d-flex align-items-center ms-2">
<BFormCheckbox
@ -17,18 +31,4 @@
</div>
</template>
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
const botStore = useBotStore();
const autoRefreshLoc = computed({
get() {
return botStore.globalAutoRefresh;
},
set(newValue: boolean) {
botStore.setGlobalAutoRefresh(newValue);
},
});
</script>
<style scoped></style>

View File

@ -1,27 +1,3 @@
<template>
<div>
<div class="w-100 d-flex">
<BFormSelect
id="strategy-select"
v-model="locStrategy"
:options="botStore.activeBot.strategyList"
>
</BFormSelect>
<div class="ms-1">
<BButton @click="botStore.activeBot.getStrategyList">
<i-mdi-refresh />
</BButton>
</div>
</div>
<textarea
v-if="showDetails && botStore.activeBot.strategy"
v-model="strategyCode"
class="w-100 h-100"
></textarea>
</div>
</template>
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
@ -50,3 +26,27 @@ onMounted(() => {
}
});
</script>
<template>
<div>
<div class="w-100 d-flex">
<BFormSelect
id="strategy-select"
v-model="locStrategy"
:options="botStore.activeBot.strategyList"
>
</BFormSelect>
<div class="ms-1">
<BButton @click="botStore.activeBot.getStrategyList">
<i-mdi-refresh />
</BButton>
</div>
</div>
<textarea
v-if="showDetails && botStore.activeBot.strategy"
v-model="strategyCode"
class="w-100 h-100"
></textarea>
</div>
</template>

View File

@ -1,43 +1,3 @@
<template>
<div>
<div class="d-flex justify-content-center">
<div>
<label for="dateFrom">Start Date</label>
<Datepicker
id="dateFrom"
v-model="dateFrom"
:dark="settingsStore.isDarkTheme"
:max-date="now"
model-type="yyyy-MM-dd"
format="yyyy-MM-dd"
class="mt-1"
text-input
auto-apply
:enable-time-picker="false"
></Datepicker>
</div>
<div class="ms-2">
<label for="dateTo">End Date</label>
<Datepicker
v-model="dateTo"
:dark="settingsStore.isDarkTheme"
class="mt-1"
:max-date="tomorrow"
model-type="yyyy-MM-dd"
format="yyyy-MM-dd"
text-input
auto-apply
:enable-time-picker="false"
></Datepicker>
</div>
</div>
<label class="mt-1 text-start">
Timerange: <b>{{ timeRange }}</b>
</label>
</div>
</template>
<script setup lang="ts">
import Datepicker from '@vuepic/vue-datepicker';
import '@vuepic/vue-datepicker/dist/main.css';
@ -103,4 +63,44 @@ onMounted(() => {
});
</script>
<template>
<div>
<div class="d-flex justify-content-center">
<div>
<label for="dateFrom">Start Date</label>
<Datepicker
id="dateFrom"
v-model="dateFrom"
:dark="settingsStore.isDarkTheme"
:max-date="now"
model-type="yyyy-MM-dd"
format="yyyy-MM-dd"
class="mt-1"
text-input
auto-apply
:enable-time-picker="false"
></Datepicker>
</div>
<div class="ms-2">
<label for="dateTo">End Date</label>
<Datepicker
v-model="dateTo"
:dark="settingsStore.isDarkTheme"
class="mt-1"
:max-date="tomorrow"
model-type="yyyy-MM-dd"
format="yyyy-MM-dd"
text-input
auto-apply
:enable-time-picker="false"
></Datepicker>
</div>
</div>
<label class="mt-1 text-start">
Timerange: <b>{{ timeRange }}</b>
</label>
</div>
</template>
<style scoped></style>

View File

@ -1,12 +1,3 @@
<template>
<BFormSelect
v-model="selectedTimeframe"
placeholder="Use strategy default"
:options="availableTimeframes"
@change="emitSelectedTimeframe"
></BFormSelect>
</template>
<script setup lang="ts">
const props = defineProps({
value: { default: '', type: String },
@ -52,4 +43,13 @@ const emitSelectedTimeframe = () => {
};
</script>
<template>
<BFormSelect
v-model="selectedTimeframe"
placeholder="Use strategy default"
:options="availableTimeframes"
@change="emitSelectedTimeframe"
></BFormSelect>
</template>
<style scoped></style>

View File

@ -1,3 +1,30 @@
<script setup lang="ts">
import { Trade } from '@/types';
defineProps({
botApiVersion: {
type: Number,
default: 1.0,
},
trade: {
type: Object as () => Trade,
required: true,
},
enableForceEntry: {
type: Boolean,
default: false,
},
});
defineEmits<{
forceExit: [trade: Trade, type?: 'limit' | 'market'];
forceExitPartial: [trade: Trade];
cancelOpenOrder: [trade: Trade];
reloadTrade: [trade: Trade];
deleteTrade: [trade: Trade];
forceEntry: [trade: Trade];
}>();
</script>
<template>
<div class="d-flex flex-column">
<BButton
@ -75,31 +102,4 @@
</div>
</template>
<script setup lang="ts">
import { Trade } from '@/types';
defineProps({
botApiVersion: {
type: Number,
default: 1.0,
},
trade: {
type: Object as () => Trade,
required: true,
},
enableForceEntry: {
type: Boolean,
default: false,
},
});
defineEmits<{
forceExit: [trade: Trade, type?: 'limit' | 'market'];
forceExitPartial: [trade: Trade];
cancelOpenOrder: [trade: Trade];
reloadTrade: [trade: Trade];
deleteTrade: [trade: Trade];
forceEntry: [trade: Trade];
}>();
</script>
<style scoped lang="scss"></style>

View File

@ -1,3 +1,14 @@
<script setup lang="ts">
import { Trade } from '@/types';
const colorStore = useColorStore();
defineProps({
trade: { required: true, type: Object as () => Trade },
stakeCurrency: { required: true, type: String },
});
</script>
<template>
<div class="container text-start">
<div class="row">
@ -139,17 +150,6 @@
</div>
</div>
</template>
<script setup lang="ts">
import { Trade } from '@/types';
const colorStore = useColorStore();
defineProps({
trade: { required: true, type: Object as () => Trade },
stakeCurrency: { required: true, type: String },
});
</script>
<style scoped>
.detail-header {
border-bottom: 1px solid;

View File

@ -1,102 +1,3 @@
<template>
<div class="h-100 overflow-auto w-100">
<BTable
ref="tradesTable"
small
hover
stacked="md"
:items="
trades.filter(
(t) =>
t.pair.toLowerCase().includes(filterText.toLowerCase()) ||
t.exit_reason?.toLowerCase().includes(filterText.toLowerCase()) ||
t.enter_tag?.toLowerCase().includes(filterText.toLowerCase()),
) as unknown as TableItem[]
"
:fields="tableFields"
show-empty
:empty-text="emptyText"
:per-page="perPage"
:current-page="currentPage"
primary-key="botTradeId"
selectable
:select-head="false"
select-mode="single"
@row-contextmenu="handleContextMenuEvent"
@row-clicked="onRowClicked"
@row-selected="onRowSelected"
>
<template #cell(actions)="{ index, item }">
<TradeActionsPopover
:id="index"
:enable-force-entry="botStore.activeBot.botState.force_entry_enable"
:trade="item as unknown as Trade"
:bot-api-version="botStore.activeBot.botApiVersion"
@delete-trade="removeTradeHandler(item as unknown as Trade)"
@force-exit="forceExitHandler"
@force-exit-partial="forceExitPartialHandler"
@cancel-open-order="cancelOpenOrderHandler"
@reload-trade="reloadTradeHandler"
@force-entry="handleForceEntry"
/>
</template>
<template #cell(pair)="row">
<span>
{{ `${row.item.pair}${row.item.open_order_id || row.item.has_open_orders ? '*' : ''}` }}
</span>
</template>
<template #cell(trade_id)="row">
{{ row.item.trade_id }}
{{
botStore.activeBot.botApiVersion > 2.0 && row.item.trading_mode !== 'spot'
? '| ' + (row.item.is_short ? 'Short' : 'Long')
: ''
}}
</template>
<template #cell(stake_amount)="row">
{{ formatPriceWithDecimals(row.item.stake_amount) }}
{{ row.item.trading_mode !== 'spot' ? `(${row.item.leverage}x)` : '' }}
</template>
<template #cell(profit)="row">
<TradeProfit :trade="row.item as unknown as Trade" />
</template>
<template #cell(open_timestamp)="row">
<DateTimeTZ :date="(row.item as unknown as Trade).open_timestamp" />
</template>
<template #cell(close_timestamp)="row">
<DateTimeTZ :date="(row.item as unknown as Trade).close_timestamp ?? 0" />
</template>
</BTable>
<div class="w-100 d-flex justify-content-between">
<BPagination
v-if="!activeTrades"
v-model="currentPage"
:total-rows="rows"
:per-page="perPage"
aria-controls="my-table"
></BPagination>
<BFormGroup v-if="showFilter" label-for="trade-filter">
<BFormInput id="trade-filter" v-model="filterText" type="text" placeholder="Filter" />
</BFormGroup>
</div>
<ForceExitForm
v-if="activeTrades"
v-model="forceExitVisible"
:trade="feTrade"
:quote-currency-decimals="botStore.activeBot.botState.stake_currency_decimals"
/>
<ForceEntryForm
v-model="increasePosition.visible"
:pair="increasePosition.trade?.pair"
position-increase
/>
<BModal v-model="removeTradeVisible" title="Exit trade" @ok="forceExitExecuter">
{{ confirmExitText }}
</BModal>
</div>
</template>
<script setup lang="ts">
import { MultiDeletePayload, MultiForcesellPayload, Trade } from '@/types';
@ -315,6 +216,105 @@ watch(
);
</script>
<template>
<div class="h-100 overflow-auto w-100">
<BTable
ref="tradesTable"
small
hover
stacked="md"
:items="
trades.filter(
(t) =>
t.pair.toLowerCase().includes(filterText.toLowerCase()) ||
t.exit_reason?.toLowerCase().includes(filterText.toLowerCase()) ||
t.enter_tag?.toLowerCase().includes(filterText.toLowerCase()),
) as unknown as TableItem[]
"
:fields="tableFields"
show-empty
:empty-text="emptyText"
:per-page="perPage"
:current-page="currentPage"
primary-key="botTradeId"
selectable
:select-head="false"
select-mode="single"
@row-contextmenu="handleContextMenuEvent"
@row-clicked="onRowClicked"
@row-selected="onRowSelected"
>
<template #cell(actions)="{ index, item }">
<TradeActionsPopover
:id="index"
:enable-force-entry="botStore.activeBot.botState.force_entry_enable"
:trade="item as unknown as Trade"
:bot-api-version="botStore.activeBot.botApiVersion"
@delete-trade="removeTradeHandler(item as unknown as Trade)"
@force-exit="forceExitHandler"
@force-exit-partial="forceExitPartialHandler"
@cancel-open-order="cancelOpenOrderHandler"
@reload-trade="reloadTradeHandler"
@force-entry="handleForceEntry"
/>
</template>
<template #cell(pair)="row">
<span>
{{ `${row.item.pair}${row.item.open_order_id || row.item.has_open_orders ? '*' : ''}` }}
</span>
</template>
<template #cell(trade_id)="row">
{{ row.item.trade_id }}
{{
botStore.activeBot.botApiVersion > 2.0 && row.item.trading_mode !== 'spot'
? '| ' + (row.item.is_short ? 'Short' : 'Long')
: ''
}}
</template>
<template #cell(stake_amount)="row">
{{ formatPriceWithDecimals(row.item.stake_amount) }}
{{ row.item.trading_mode !== 'spot' ? `(${row.item.leverage}x)` : '' }}
</template>
<template #cell(profit)="row">
<TradeProfit :trade="row.item as unknown as Trade" />
</template>
<template #cell(open_timestamp)="row">
<DateTimeTZ :date="(row.item as unknown as Trade).open_timestamp" />
</template>
<template #cell(close_timestamp)="row">
<DateTimeTZ :date="(row.item as unknown as Trade).close_timestamp ?? 0" />
</template>
</BTable>
<div class="w-100 d-flex justify-content-between">
<BPagination
v-if="!activeTrades"
v-model="currentPage"
:total-rows="rows"
:per-page="perPage"
aria-controls="my-table"
></BPagination>
<BFormGroup v-if="showFilter" label-for="trade-filter">
<BFormInput id="trade-filter" v-model="filterText" type="text" placeholder="Filter" />
</BFormGroup>
</div>
<ForceExitForm
v-if="activeTrades"
v-model="forceExitVisible"
:trade="feTrade"
:quote-currency-decimals="botStore.activeBot.botState.stake_currency_decimals"
/>
<ForceEntryForm
v-model="increasePosition.visible"
:pair="increasePosition.trade?.pair"
position-increase
/>
<BModal v-model="removeTradeVisible" title="Exit trade" @ok="forceExitExecuter">
{{ confirmExitText }}
</BModal>
</div>
</template>
<style lang="scss" scoped>
.card-body {
padding: 0 0.2em;

View File

@ -1,3 +1,43 @@
<script setup lang="ts">
import { Trade } from '@/types';
import { useBotStore } from '@/stores/ftbotwrapper';
const props = defineProps({
trades: { required: true, type: Array as () => Trade[] },
backtestMode: { required: false, default: false, type: Boolean },
});
const emit = defineEmits<{ 'trade-select': [trade: Trade] }>();
const botStore = useBotStore();
const selectedTrade = ref({} as Trade);
const sortNewestFirst = ref(true);
const onTradeSelect = (trade: Trade) => {
selectedTrade.value = trade;
emit('trade-select', trade);
};
const sortedTrades = computed(() => {
return props.trades
.slice()
.sort((a, b) =>
sortNewestFirst.value
? b.open_timestamp - a.open_timestamp
: a.open_timestamp - b.open_timestamp,
);
});
const ordersVisible = ref(sortedTrades.value.map(() => false));
watch(
() => botStore.activeBot.selectedPair,
() => {
ordersVisible.value = sortedTrades.value.map(() => false);
},
);
</script>
<template>
<div>
<BListGroup>
@ -57,46 +97,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[] },
backtestMode: { required: false, default: false, type: Boolean },
});
const emit = defineEmits<{ 'trade-select': [trade: Trade] }>();
const botStore = useBotStore();
const selectedTrade = ref({} as Trade);
const sortNewestFirst = ref(true);
const onTradeSelect = (trade: Trade) => {
selectedTrade.value = trade;
emit('trade-select', trade);
};
const sortedTrades = computed(() => {
return props.trades
.slice()
.sort((a, b) =>
sortNewestFirst.value
? b.open_timestamp - a.open_timestamp
: a.open_timestamp - b.open_timestamp,
);
});
const ordersVisible = ref(sortedTrades.value.map(() => false));
watch(
() => botStore.activeBot.selectedPair,
() => {
ordersVisible.value = sortedTrades.value.map(() => false);
},
);
</script>
<style scoped>
.list-group {
text-align: left;

View File

@ -1,12 +1,3 @@
<template>
<ProfitPill
:profit-ratio="profitRatio"
:profit-abs="profitAbs"
:profit-desc="profitDesc"
:stake-currency="trade.quote_currency || 'USDT'"
/>
</template>
<script setup lang="ts">
import { Trade } from '@/types';
@ -60,4 +51,13 @@ const profitDesc = computed((): string => {
});
</script>
<template>
<ProfitPill
:profit-ratio="profitRatio"
:profit-abs="profitAbs"
:profit-desc="profitDesc"
:stake-currency="trade.quote_currency || 'USDT'"
/>
</template>
<style scoped></style>

View File

@ -1,3 +1,14 @@
<script setup lang="ts">
import { useClipboard } from '@vueuse/core';
defineProps({
content: { type: [String, Array<string>], required: true },
isValid: { type: Boolean, default: true },
});
const { copy, isSupported } = useClipboard();
</script>
<template>
<div class="copy-container position-relative">
<i-mdi-content-copy
@ -10,17 +21,6 @@
</div>
</template>
<script setup lang="ts">
import { useClipboard } from '@vueuse/core';
defineProps({
content: { type: [String, Array<string>], required: true },
isValid: { type: Boolean, default: true },
});
const { copy, isSupported } = useClipboard();
</script>
<style lang="scss" scoped>
.copy-container {
.copy-button {

View File

@ -1,7 +1,3 @@
<template>
<span :title="timezoneTooltip">{{ formattedDate }}</span>
</template>
<script setup lang="ts">
const props = defineProps({
date: { required: true, type: Number },
@ -29,4 +25,8 @@ const timezoneTooltip = computed((): string => {
});
</script>
<template>
<span :title="timezoneTooltip">{{ formattedDate }}</span>
</template>
<style scoped></style>

View File

@ -1,65 +1,3 @@
<template>
<form class="d-flex flex-row" @submit.prevent="saveNewName">
<div class="flex-grow-1">
<slot v-if="mode === EditState.None"> </slot>
<BFormInput v-else v-model="localName" size="sm"> </BFormInput>
</div>
<div
class="flex-grow-2 mt-auto d-flex gap-1 ms-1"
:class="alignVertical ? 'flex-column' : 'flex-row'"
>
<template v-if="allowEdit && mode === EditState.None">
<BButton
size="sm"
variant="secondary"
:title="`Edit this ${editableName}.`"
@click="mode = EditState.Editing"
>
<i-mdi-pencil />
</BButton>
<BButton
v-if="allowDuplicate"
size="sm"
variant="secondary"
:title="`Duplicate ${editableName}.`"
@click="duplicate"
>
<i-mdi-content-copy />
</BButton>
<BButton
size="sm"
variant="secondary"
:title="`Delete this ${editableName}.`"
@click="$emit('delete', modelValue)"
>
<i-mdi-delete />
</BButton>
</template>
<BButton
v-if="allowAdd && mode === EditState.None"
size="sm"
:title="`Add new ${editableName}.`"
variant="primary"
@click="addNewClick"
><i-mdi-plus-box-outline />
</BButton>
<template v-if="mode !== EditState.None">
<BButton
size="sm"
:title="`Add new ${editableName}`"
variant="primary"
@click="saveNewName"
>
<i-mdi-check />
</BButton>
<BButton size="sm" title="Abort" variant="secondary" @click="abort">
<i-mdi-close />
</BButton>
</template>
</div>
</form>
</template>
<script setup lang="ts">
const props = defineProps({
modelValue: {
@ -142,3 +80,65 @@ function saveNewName() {
mode.value = EditState.None;
}
</script>
<template>
<form class="d-flex flex-row" @submit.prevent="saveNewName">
<div class="flex-grow-1">
<slot v-if="mode === EditState.None"> </slot>
<BFormInput v-else v-model="localName" size="sm"> </BFormInput>
</div>
<div
class="flex-grow-2 mt-auto d-flex gap-1 ms-1"
:class="alignVertical ? 'flex-column' : 'flex-row'"
>
<template v-if="allowEdit && mode === EditState.None">
<BButton
size="sm"
variant="secondary"
:title="`Edit this ${editableName}.`"
@click="mode = EditState.Editing"
>
<i-mdi-pencil />
</BButton>
<BButton
v-if="allowDuplicate"
size="sm"
variant="secondary"
:title="`Duplicate ${editableName}.`"
@click="duplicate"
>
<i-mdi-content-copy />
</BButton>
<BButton
size="sm"
variant="secondary"
:title="`Delete this ${editableName}.`"
@click="$emit('delete', modelValue)"
>
<i-mdi-delete />
</BButton>
</template>
<BButton
v-if="allowAdd && mode === EditState.None"
size="sm"
:title="`Add new ${editableName}.`"
variant="primary"
@click="addNewClick"
><i-mdi-plus-box-outline />
</BButton>
<template v-if="mode !== EditState.None">
<BButton
size="sm"
:title="`Add new ${editableName}`"
variant="primary"
@click="saveNewName"
>
<i-mdi-check />
</BButton>
<BButton size="sm" title="Abort" variant="secondary" @click="abort">
<i-mdi-close />
</BButton>
</template>
</div>
</form>
</template>

View File

@ -1,13 +1,13 @@
<template>
<div :title="hint">
<i-mdi-information-outline />
</div>
</template>
<script setup lang="ts">
defineProps({
hint: { type: String, required: true },
});
</script>
<template>
<div :title="hint">
<i-mdi-information-outline />
</div>
</template>
<style lang="scss" scoped></style>

View File

@ -1,18 +1,3 @@
<template>
<BModal
id="MsgBoxModal"
ref="removeTradeModal"
v-model="showRef"
:title="title"
@ok="msgBoxOK"
@cancel="showRef = false"
@keyup.esc="showRef = false"
@keyup.enter="msgBoxOK"
>
{{ message }}
</BModal>
</template>
<script setup lang="ts">
export interface MsgBoxObject {
title: string;
@ -41,4 +26,19 @@ const show = (msg: MsgBoxObject) => {
defineExpose({ show });
</script>
<template>
<BModal
id="MsgBoxModal"
ref="removeTradeModal"
v-model="showRef"
:title="title"
@ok="msgBoxOK"
@cancel="showRef = false"
@keyup.esc="showRef = false"
@keyup.enter="msgBoxOK"
>
{{ message }}
</BModal>
</template>
<style scoped></style>

View File

@ -1,24 +1,3 @@
<template>
<div
class="d-flex justify-content-between align-items-center profit-pill ps-2 pe-1"
:class="isProfitable ? 'profit-pill-profit' : ''"
:title="profitDesc"
>
<ProfitSymbol :profit="(profitRatio || profitAbs) ?? 0" />
<div class="d-flex justify-content-center align-items-center flex-grow-1">
{{ profitRatio !== undefined ? formatPercent(profitRatio, 2) : '' }}
<span
v-if="profitString"
class="ms-1"
:class="profitRatio ? 'small' : ''"
:title="stakeCurrency"
>{{ profitString }}</span
>
</div>
</div>
</template>
<script setup lang="ts">
const props = defineProps({
profitRatio: { required: false, default: undefined, type: Number },
@ -47,6 +26,27 @@ const profitString = computed((): string => {
});
</script>
<template>
<div
class="d-flex justify-content-between align-items-center profit-pill ps-2 pe-1"
:class="isProfitable ? 'profit-pill-profit' : ''"
:title="profitDesc"
>
<ProfitSymbol :profit="(profitRatio || profitAbs) ?? 0" />
<div class="d-flex justify-content-center align-items-center flex-grow-1">
{{ profitRatio !== undefined ? formatPercent(profitRatio, 2) : '' }}
<span
v-if="profitString"
class="ms-1"
:class="profitRatio ? 'small' : ''"
:title="stakeCurrency"
>{{ profitString }}</span
>
</div>
</div>
</template>
<style scoped lang="scss">
.profit-pill {
border: 2px solid $color-loss;

View File

@ -1,9 +1,3 @@
<template>
<div class="d-inline-block">
<div :class="isProfitable ? 'triangle-up' : 'triangle-down'"></div>
</div>
</template>
<script setup lang="ts">
const props = defineProps({
profit: { type: Number, required: true },
@ -13,6 +7,12 @@ const isProfitable = computed(() => {
});
</script>
<template>
<div class="d-inline-block">
<div :class="isProfitable ? 'triangle-up' : 'triangle-down'"></div>
</div>
</template>
<style scoped lang="scss">
.triangle-up {
width: 0;

View File

@ -1,3 +1,12 @@
<script setup lang="ts">
defineProps({
description: { type: String, required: true },
help: { type: String, default: '', required: false },
classLabel: { type: String, default: 'col-4 fw-bold mb-0' },
classValue: { type: String, default: 'col-8' },
});
</script>
<template>
<div class="d-flex">
<div class="d-flex justify-content-between me-2" :class="classLabel">
@ -9,14 +18,3 @@
</div>
</div>
</template>
<script setup lang="ts">
defineProps({
description: { type: String, required: true },
help: { type: String, default: '', required: false },
classLabel: { type: String, default: 'col-4 fw-bold mb-0' },
classValue: { type: String, default: 'col-8' },
});
</script>
<style scoped></style>

View File

@ -1,3 +1,9 @@
<script setup lang="ts">
defineProps({
header: { required: false, type: String, default: '' },
});
</script>
<template>
<div class="card h-100 w-100">
<div class="drag-header card-header">
@ -11,12 +17,6 @@
</div>
</template>
<script setup lang="ts">
defineProps({
header: { required: false, type: String, default: '' },
});
</script>
<style scoped>
.card-header {
padding: 0.25rem 0.5rem;

View File

@ -1,3 +1,101 @@
<script setup lang="ts">
import Favico from 'favico.js';
import { OpenTradeVizOptions, useSettingsStore } from '@/stores/settings';
import { useLayoutStore } from '@/stores/layout';
import { useBotStore } from '@/stores/ftbotwrapper';
import { useRoute } from 'vue-router';
const botStore = useBotStore();
const settingsStore = useSettingsStore();
const layoutStore = useLayoutStore();
const route = useRoute();
const router = useRouter();
const favicon = ref<Favico | undefined>(undefined);
const pingInterval = ref<number>();
async function clickLogout() {
botStore.removeBot(botStore.selectedBot);
// TODO: This should be per bot
await router.push('/');
}
const setOpenTradesAsPill = (tradeCount: number) => {
if (!favicon.value) {
favicon.value = new Favico({
animation: 'none',
// position: 'up',
// fontStyle: 'normal',
// bgColor: '#',
// textColor: '#FFFFFF',
});
}
if (tradeCount !== 0 && settingsStore.openTradesInTitle === 'showPill') {
favicon.value.badge(tradeCount);
} else {
favicon.value.reset();
console.log('reset');
}
};
const resetDynamicLayout = (): void => {
console.log(`resetLayout called for ${route?.fullPath}`);
switch (route?.fullPath) {
case '/trade':
layoutStore.resetTradingLayout();
break;
case '/dashboard':
layoutStore.resetDashboardLayout();
break;
default:
}
};
const setTitle = () => {
let title = 'freqUI';
if (settingsStore.openTradesInTitle === OpenTradeVizOptions.asTitle) {
title = `(${botStore.activeBotorUndefined?.openTradeCount}) ${title}`;
}
if (botStore.activeBotorUndefined?.botName) {
title = `${title} - ${botStore.activeBotorUndefined?.botName}`;
}
document.title = title;
};
onBeforeUnmount(() => {
if (pingInterval.value) {
clearInterval(pingInterval.value);
}
});
onMounted(async () => {
await settingsStore.loadUIVersion();
pingInterval.value = window.setInterval(botStore.pingAll, 60000);
});
settingsStore.$subscribe((_, state) => {
const needsUpdate = settingsStore.openTradesInTitle !== state.openTradesInTitle;
if (needsUpdate) {
setTitle();
setOpenTradesAsPill(botStore.activeBotorUndefined?.openTradeCount || 0);
}
});
watch(
() => botStore.activeBotorUndefined?.botName,
() => setTitle(),
);
watch(
() => botStore.activeBotorUndefined?.openTradeCount,
() => {
if (settingsStore.openTradesInTitle === OpenTradeVizOptions.showPill) {
setOpenTradesAsPill(botStore.activeBotorUndefined?.openTradeCount ?? 0);
} else if (settingsStore.openTradesInTitle === OpenTradeVizOptions.asTitle) {
setTitle();
}
},
);
</script>
<template>
<header>
<BNavbar toggleable="sm" dark variant="primary">
@ -129,104 +227,6 @@
</header>
</template>
<script setup lang="ts">
import Favico from 'favico.js';
import { OpenTradeVizOptions, useSettingsStore } from '@/stores/settings';
import { useLayoutStore } from '@/stores/layout';
import { useBotStore } from '@/stores/ftbotwrapper';
import { useRoute } from 'vue-router';
const botStore = useBotStore();
const settingsStore = useSettingsStore();
const layoutStore = useLayoutStore();
const route = useRoute();
const router = useRouter();
const favicon = ref<Favico | undefined>(undefined);
const pingInterval = ref<number>();
async function clickLogout() {
botStore.removeBot(botStore.selectedBot);
// TODO: This should be per bot
await router.push('/');
}
const setOpenTradesAsPill = (tradeCount: number) => {
if (!favicon.value) {
favicon.value = new Favico({
animation: 'none',
// position: 'up',
// fontStyle: 'normal',
// bgColor: '#',
// textColor: '#FFFFFF',
});
}
if (tradeCount !== 0 && settingsStore.openTradesInTitle === 'showPill') {
favicon.value.badge(tradeCount);
} else {
favicon.value.reset();
console.log('reset');
}
};
const resetDynamicLayout = (): void => {
console.log(`resetLayout called for ${route?.fullPath}`);
switch (route?.fullPath) {
case '/trade':
layoutStore.resetTradingLayout();
break;
case '/dashboard':
layoutStore.resetDashboardLayout();
break;
default:
}
};
const setTitle = () => {
let title = 'freqUI';
if (settingsStore.openTradesInTitle === OpenTradeVizOptions.asTitle) {
title = `(${botStore.activeBotorUndefined?.openTradeCount}) ${title}`;
}
if (botStore.activeBotorUndefined?.botName) {
title = `${title} - ${botStore.activeBotorUndefined?.botName}`;
}
document.title = title;
};
onBeforeUnmount(() => {
if (pingInterval.value) {
clearInterval(pingInterval.value);
}
});
onMounted(async () => {
await settingsStore.loadUIVersion();
pingInterval.value = window.setInterval(botStore.pingAll, 60000);
});
settingsStore.$subscribe((_, state) => {
const needsUpdate = settingsStore.openTradesInTitle !== state.openTradesInTitle;
if (needsUpdate) {
setTitle();
setOpenTradesAsPill(botStore.activeBotorUndefined?.openTradeCount || 0);
}
});
watch(
() => botStore.activeBotorUndefined?.botName,
() => setTitle(),
);
watch(
() => botStore.activeBotorUndefined?.openTradeCount,
() => {
if (settingsStore.openTradesInTitle === OpenTradeVizOptions.showPill) {
setOpenTradesAsPill(botStore.activeBotorUndefined?.openTradeCount ?? 0);
} else if (settingsStore.openTradesInTitle === OpenTradeVizOptions.asTitle) {
setTitle();
}
},
);
</script>
<style lang="scss" scoped>
.logo {
vertical-align: middle;

View File

@ -1,3 +1,9 @@
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
const botStore = useBotStore();
</script>
<template>
<footer class="d-md-none">
<!-- Only visible on xs (phone) viewport! -->
@ -47,12 +53,6 @@
</footer>
</template>
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
const botStore = useBotStore();
</script>
<style lang="scss" scoped>
[data-theme='dark'] {
.router-link-active,

View File

@ -1,3 +1,75 @@
<script setup lang="ts">
import { useBtStore } from '@/stores/btStore';
import { useBotStore } from '@/stores/ftbotwrapper';
enum BtRunModes {
run = 'run',
results = 'results',
visualize = 'visualize',
visualizesummary = 'visualize-summary',
compareresults = 'compare-results',
historicresults = 'historicResults',
}
const botStore = useBotStore();
const btStore = useBtStore();
const hasBacktestResult = computed(() =>
botStore.activeBot.backtestHistory
? Object.keys(botStore.activeBot.backtestHistory).length !== 0
: false,
);
const hasMultiBacktestResult = computed(() =>
botStore.activeBot.backtestHistory
? Object.keys(botStore.activeBot.backtestHistory).length > 1
: false,
);
const timeframe = computed((): string => {
try {
return botStore.activeBot.selectedBacktestResult.timeframe;
} catch (err) {
return '';
}
});
const showLeftBar = ref(false);
const btFormMode = ref<BtRunModes>(BtRunModes.run);
const pollInterval = ref<number | null>(null);
const selectBacktestResult = () => {
// Set parameters for this result
btStore.strategy = botStore.activeBot.selectedBacktestResult.strategy_name;
botStore.activeBot.getStrategy(btStore.strategy);
btStore.selectedTimeframe = botStore.activeBot.selectedBacktestResult.timeframe;
btStore.selectedDetailTimeframe =
botStore.activeBot.selectedBacktestResult.timeframe_detail || '';
// TODO: maybe this should not use timerange, but the actual backtest start/end results instead?
btStore.timerange = botStore.activeBot.selectedBacktestResult.timerange;
};
watch(
() => botStore.activeBot.selectedBacktestResultKey,
() => {
selectBacktestResult();
},
);
onMounted(() => botStore.activeBot.getState());
watch(
() => botStore.activeBot.backtestRunning,
() => {
if (botStore.activeBot.backtestRunning === true) {
pollInterval.value = window.setInterval(botStore.activeBot.pollBacktest, 1000);
} else if (pollInterval.value) {
clearInterval(pollInterval.value);
pollInterval.value = null;
}
},
);
</script>
<template>
<div class="d-flex flex-column pt-1 me-1" style="height: calc(100vh - 60px)">
<div>
@ -151,78 +223,6 @@
</div>
</template>
<script setup lang="ts">
import { useBtStore } from '@/stores/btStore';
import { useBotStore } from '@/stores/ftbotwrapper';
enum BtRunModes {
run = 'run',
results = 'results',
visualize = 'visualize',
visualizesummary = 'visualize-summary',
compareresults = 'compare-results',
historicresults = 'historicResults',
}
const botStore = useBotStore();
const btStore = useBtStore();
const hasBacktestResult = computed(() =>
botStore.activeBot.backtestHistory
? Object.keys(botStore.activeBot.backtestHistory).length !== 0
: false,
);
const hasMultiBacktestResult = computed(() =>
botStore.activeBot.backtestHistory
? Object.keys(botStore.activeBot.backtestHistory).length > 1
: false,
);
const timeframe = computed((): string => {
try {
return botStore.activeBot.selectedBacktestResult.timeframe;
} catch (err) {
return '';
}
});
const showLeftBar = ref(false);
const btFormMode = ref<BtRunModes>(BtRunModes.run);
const pollInterval = ref<number | null>(null);
const selectBacktestResult = () => {
// Set parameters for this result
btStore.strategy = botStore.activeBot.selectedBacktestResult.strategy_name;
botStore.activeBot.getStrategy(btStore.strategy);
btStore.selectedTimeframe = botStore.activeBot.selectedBacktestResult.timeframe;
btStore.selectedDetailTimeframe =
botStore.activeBot.selectedBacktestResult.timeframe_detail || '';
// TODO: maybe this should not use timerange, but the actual backtest start/end results instead?
btStore.timerange = botStore.activeBot.selectedBacktestResult.timerange;
};
watch(
() => botStore.activeBot.selectedBacktestResultKey,
() => {
selectBacktestResult();
},
);
onMounted(() => botStore.activeBot.getState());
watch(
() => botStore.activeBot.backtestRunning,
() => {
if (botStore.activeBot.backtestRunning === true) {
pollInterval.value = window.setInterval(botStore.activeBot.pollBacktest, 1000);
} else if (pollInterval.value) {
clearInterval(pollInterval.value);
pollInterval.value = null;
}
},
);
</script>
<style lang="scss" scoped>
.bt-running-label {
position: absolute;

View File

@ -1,38 +1,3 @@
<template>
<div class="d-flex flex-column h-100">
<!-- <div v-if="isWebserverMode" class="me-auto ms-3"> -->
<!-- Currently only available in Webserver mode -->
<!-- <b-form-checkbox v-model="historicView">HistoricData</b-form-checkbox> -->
<!-- </div> -->
<div v-if="botStore.activeBot.isWebserverMode" class="mx-md-3 mt-2">
<div class="d-flex flex-wrap mx-1 gap-1 gap-md-2">
<div class="col-12 col-md-3 text-start me-md-1">
<span>Strategy</span>
<StrategySelect v-model="strategy" class="mt-1"></StrategySelect>
</div>
<div class="col-12 col-md-3 text-start">
<span>Timeframe</span>
<TimeframeSelect v-model="selectedTimeframe" class="mt-1" />
</div>
<TimeRangeSelect v-model="timerange"></TimeRangeSelect>
</div>
</div>
<div class="mx-md-2 mt-2 pb-1 h-100">
<CandleChartContainer
:available-pairs="availablePairs"
:historic-view="botStore.activeBot.isWebserverMode"
:timeframe="finalTimeframe"
:trades="botStore.activeBot.trades"
:timerange="botStore.activeBot.isWebserverMode ? timerange : ''"
:strategy="botStore.activeBot.isWebserverMode ? strategy : ''"
:plot-config-modal="false"
>
</CandleChartContainer>
</div>
</div>
</template>
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
@ -76,4 +41,37 @@ onMounted(() => {
});
</script>
<style scoped></style>
<template>
<div class="d-flex flex-column h-100">
<!-- <div v-if="isWebserverMode" class="me-auto ms-3"> -->
<!-- Currently only available in Webserver mode -->
<!-- <b-form-checkbox v-model="historicView">HistoricData</b-form-checkbox> -->
<!-- </div> -->
<div v-if="botStore.activeBot.isWebserverMode" class="mx-md-3 mt-2">
<div class="d-flex flex-wrap mx-1 gap-1 gap-md-2">
<div class="col-12 col-md-3 text-start me-md-1">
<span>Strategy</span>
<StrategySelect v-model="strategy" class="mt-1"></StrategySelect>
</div>
<div class="col-12 col-md-3 text-start">
<span>Timeframe</span>
<TimeframeSelect v-model="selectedTimeframe" class="mt-1" />
</div>
<TimeRangeSelect v-model="timerange"></TimeRangeSelect>
</div>
</div>
<div class="mx-md-2 mt-2 pb-1 h-100">
<CandleChartContainer
:available-pairs="availablePairs"
:historic-view="botStore.activeBot.isWebserverMode"
:timeframe="finalTimeframe"
:trades="botStore.activeBot.trades"
:timerange="botStore.activeBot.isWebserverMode ? timerange : ''"
:strategy="botStore.activeBot.isWebserverMode ? strategy : ''"
:plot-config-modal="false"
>
</CandleChartContainer>
</div>
</div>
</template>

View File

@ -1,3 +1,78 @@
<script setup lang="ts">
import { DashboardLayout, findGridLayout, useLayoutStore } from '@/stores/layout';
import { useBotStore } from '@/stores/ftbotwrapper';
import { GridItemData } from '@/types';
const botStore = useBotStore();
const layoutStore = useLayoutStore();
const currentBreakpoint = ref('');
function breakpointChanged(newBreakpoint: string) {
// console.log('breakpoint:', newBreakpoint);
currentBreakpoint.value = newBreakpoint;
}
const isResizableLayout = computed(() =>
['', 'sm', 'md', 'lg', 'xl'].includes(currentBreakpoint.value),
);
const isLayoutLocked = computed(() => {
return layoutStore.layoutLocked || !isResizableLayout.value;
});
const gridLayoutData = computed((): GridItemData[] => {
if (isResizableLayout.value) {
return layoutStore.dashboardLayout;
}
return [...layoutStore.getDashboardLayoutSm];
});
function layoutUpdatedEvent(newLayout) {
if (isResizableLayout.value) {
console.log('newlayout', newLayout);
console.log('saving dashboard');
layoutStore.dashboardLayout = newLayout;
}
}
const gridLayoutDaily = computed((): GridItemData => {
return findGridLayout(gridLayoutData.value, DashboardLayout.dailyChart);
});
const gridLayoutBotComparison = computed((): GridItemData => {
return findGridLayout(gridLayoutData.value, DashboardLayout.botComparison);
});
const gridLayoutAllOpenTrades = computed((): GridItemData => {
return findGridLayout(gridLayoutData.value, DashboardLayout.allOpenTrades);
});
const gridLayoutAllClosedTrades = computed((): GridItemData => {
return findGridLayout(gridLayoutData.value, DashboardLayout.allClosedTrades);
});
const gridLayoutCumChart = computed((): GridItemData => {
return findGridLayout(gridLayoutData.value, DashboardLayout.cumChartChart);
});
const gridLayoutProfitDistribution = computed((): GridItemData => {
return findGridLayout(gridLayoutData.value, DashboardLayout.profitDistributionChart);
});
const gridLayoutTradesLogChart = computed((): GridItemData => {
return findGridLayout(gridLayoutData.value, DashboardLayout.tradesLogChart);
});
const responsiveGridLayouts = computed(() => {
return {
sm: layoutStore.getDashboardLayoutSm,
};
});
onMounted(async () => {
botStore.allGetDaily({ timescale: 30 });
// botStore.activeBot.getTrades();
botStore.activeBot.getOpenTrades();
botStore.activeBot.getProfit();
});
</script>
<template>
<GridLayout
class="h-100 w-100"
@ -155,80 +230,3 @@
</template>
</GridLayout>
</template>
<script setup lang="ts">
import { DashboardLayout, findGridLayout, useLayoutStore } from '@/stores/layout';
import { useBotStore } from '@/stores/ftbotwrapper';
import { GridItemData } from '@/types';
const botStore = useBotStore();
const layoutStore = useLayoutStore();
const currentBreakpoint = ref('');
function breakpointChanged(newBreakpoint: string) {
// console.log('breakpoint:', newBreakpoint);
currentBreakpoint.value = newBreakpoint;
}
const isResizableLayout = computed(() =>
['', 'sm', 'md', 'lg', 'xl'].includes(currentBreakpoint.value),
);
const isLayoutLocked = computed(() => {
return layoutStore.layoutLocked || !isResizableLayout.value;
});
const gridLayoutData = computed((): GridItemData[] => {
if (isResizableLayout.value) {
return layoutStore.dashboardLayout;
}
return [...layoutStore.getDashboardLayoutSm];
});
function layoutUpdatedEvent(newLayout) {
if (isResizableLayout.value) {
console.log('newlayout', newLayout);
console.log('saving dashboard');
layoutStore.dashboardLayout = newLayout;
}
}
const gridLayoutDaily = computed((): GridItemData => {
return findGridLayout(gridLayoutData.value, DashboardLayout.dailyChart);
});
const gridLayoutBotComparison = computed((): GridItemData => {
return findGridLayout(gridLayoutData.value, DashboardLayout.botComparison);
});
const gridLayoutAllOpenTrades = computed((): GridItemData => {
return findGridLayout(gridLayoutData.value, DashboardLayout.allOpenTrades);
});
const gridLayoutAllClosedTrades = computed((): GridItemData => {
return findGridLayout(gridLayoutData.value, DashboardLayout.allClosedTrades);
});
const gridLayoutCumChart = computed((): GridItemData => {
return findGridLayout(gridLayoutData.value, DashboardLayout.cumChartChart);
});
const gridLayoutProfitDistribution = computed((): GridItemData => {
return findGridLayout(gridLayoutData.value, DashboardLayout.profitDistributionChart);
});
const gridLayoutTradesLogChart = computed((): GridItemData => {
return findGridLayout(gridLayoutData.value, DashboardLayout.tradesLogChart);
});
const responsiveGridLayouts = computed(() => {
return {
sm: layoutStore.getDashboardLayoutSm,
};
});
onMounted(async () => {
botStore.allGetDaily({ timescale: 30 });
// botStore.activeBot.getTrades();
botStore.activeBot.getOpenTrades();
botStore.activeBot.getProfit();
});
</script>
<style scoped></style>

View File

@ -1,3 +1,5 @@
<script setup lang="ts"></script>
<template>
<div class="home">
<div class="d-flex justify-content-center">
@ -20,8 +22,6 @@
</div>
</template>
<script setup lang="ts"></script>
<style lang="scss" scoped>
.home {
margin-top: 1.5em;

View File

@ -1,9 +1,9 @@
<script setup lang="ts"></script>
<template>
<div class="p-1 p-md-4 pe-md-2 h-100">
<LogViewer />
</div>
</template>
<script setup lang="ts"></script>
<style scoped></style>

View File

@ -1,3 +1,5 @@
<script setup lang="ts"></script>
<template>
<div class="container">
<BCard header="Freqtrade bot Login">
@ -6,8 +8,6 @@
</div>
</template>
<script setup lang="ts"></script>
<style scoped>
.container {
max-width: 520px;

View File

@ -1,3 +1,12 @@
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
defineProps({
history: { default: false, type: Boolean },
});
const botStore = useBotStore();
</script>
<template>
<div>
<!-- <TradeList
@ -40,13 +49,4 @@
</div>
</template>
<script setup lang="ts">
import { useBotStore } from '@/stores/ftbotwrapper';
defineProps({
history: { default: false, type: Boolean },
});
const botStore = useBotStore();
</script>
<style scoped></style>

View File

@ -1,5 +1,5 @@
<script setup lang="ts"></script>
<template>
<PairlistConfigurator class="pt-4" />
</template>
<script setup lang="ts"></script>

View File

@ -1,3 +1,31 @@
<script setup lang="ts">
import { OpenTradeVizOptions, useSettingsStore } from '@/stores/settings';
import { useLayoutStore } from '@/stores/layout';
import { FtWsMessageTypes } from '@/types/wsMessageTypes';
import { ColorPreferences, useColorStore } from '@/stores/colors';
const settingsStore = useSettingsStore();
const colorStore = useColorStore();
const layoutStore = useLayoutStore();
const timezoneOptions = ['UTC', Intl.DateTimeFormat().resolvedOptions().timeZone];
const openTradesOptions = [
{ value: OpenTradeVizOptions.showPill, text: 'Show pill in icon' },
{ value: OpenTradeVizOptions.asTitle, text: 'Show in title' },
{ value: OpenTradeVizOptions.noOpenTrades, text: "Don't show open trades in header" },
];
const colorPreferenceOptions = [
{ value: ColorPreferences.GREEN_UP, text: 'Green Up/Red Down' },
{ value: ColorPreferences.RED_UP, text: 'Green Down/Red Up' },
];
const resetDynamicLayout = () => {
layoutStore.resetTradingLayout();
layoutStore.resetDashboardLayout();
showAlert('Layouts have been reset.');
};
</script>
<template>
<div class="container mt-3">
<BCard header="FreqUI Settings">
@ -122,34 +150,6 @@
</div>
</template>
<script setup lang="ts">
import { OpenTradeVizOptions, useSettingsStore } from '@/stores/settings';
import { useLayoutStore } from '@/stores/layout';
import { FtWsMessageTypes } from '@/types/wsMessageTypes';
import { ColorPreferences, useColorStore } from '@/stores/colors';
const settingsStore = useSettingsStore();
const colorStore = useColorStore();
const layoutStore = useLayoutStore();
const timezoneOptions = ['UTC', Intl.DateTimeFormat().resolvedOptions().timeZone];
const openTradesOptions = [
{ value: OpenTradeVizOptions.showPill, text: 'Show pill in icon' },
{ value: OpenTradeVizOptions.asTitle, text: 'Show in title' },
{ value: OpenTradeVizOptions.noOpenTrades, text: "Don't show open trades in header" },
];
const colorPreferenceOptions = [
{ value: ColorPreferences.GREEN_UP, text: 'Green Up/Red Down' },
{ value: ColorPreferences.RED_UP, text: 'Green Down/Red Up' },
];
const resetDynamicLayout = () => {
layoutStore.resetTradingLayout();
layoutStore.resetDashboardLayout();
showAlert('Layouts have been reset.');
};
</script>
<style lang="scss" scoped>
.color-candle-arrows {
margin-left: -0.5rem;

View File

@ -1,3 +1,57 @@
<script setup lang="ts">
import { GridItemData } from '@/types';
import { useLayoutStore, findGridLayout, TradeLayout } from '@/stores/layout';
import { useBotStore } from '@/stores/ftbotwrapper';
const botStore = useBotStore();
const layoutStore = useLayoutStore();
const currentBreakpoint = ref('');
const breakpointChanged = (newBreakpoint: string) => {
// console.log('breakpoint:', newBreakpoint);
currentBreakpoint.value = newBreakpoint;
};
const isResizableLayout = computed(() =>
['', 'sm', 'md', 'lg', 'xl'].includes(currentBreakpoint.value),
);
const isLayoutLocked = computed(() => {
return layoutStore.layoutLocked || !isResizableLayout.value;
});
const gridLayoutData = computed((): GridItemData[] => {
if (isResizableLayout.value) {
return layoutStore.tradingLayout;
}
return [...layoutStore.getTradingLayoutSm];
});
const gridLayoutMultiPane = computed(() => {
return findGridLayout(gridLayoutData.value, TradeLayout.multiPane);
});
const gridLayoutOpenTrades = computed(() => {
return findGridLayout(gridLayoutData.value, TradeLayout.openTrades);
});
const gridLayoutTradeHistory = computed(() => {
return findGridLayout(gridLayoutData.value, TradeLayout.tradeHistory);
});
const gridLayoutTradeDetail = computed(() => {
return findGridLayout(gridLayoutData.value, TradeLayout.tradeDetail);
});
const gridLayoutChartView = computed(() => {
return findGridLayout(gridLayoutData.value, TradeLayout.chartView);
});
const responsiveGridLayouts = computed(() => {
return {
sm: layoutStore.getTradingLayoutSm,
};
});
</script>
<template>
<GridLayout
class="h-100 w-100"
@ -145,58 +199,4 @@
</GridLayout>
</template>
<script setup lang="ts">
import { GridItemData } from '@/types';
import { useLayoutStore, findGridLayout, TradeLayout } from '@/stores/layout';
import { useBotStore } from '@/stores/ftbotwrapper';
const botStore = useBotStore();
const layoutStore = useLayoutStore();
const currentBreakpoint = ref('');
const breakpointChanged = (newBreakpoint: string) => {
// console.log('breakpoint:', newBreakpoint);
currentBreakpoint.value = newBreakpoint;
};
const isResizableLayout = computed(() =>
['', 'sm', 'md', 'lg', 'xl'].includes(currentBreakpoint.value),
);
const isLayoutLocked = computed(() => {
return layoutStore.layoutLocked || !isResizableLayout.value;
});
const gridLayoutData = computed((): GridItemData[] => {
if (isResizableLayout.value) {
return layoutStore.tradingLayout;
}
return [...layoutStore.getTradingLayoutSm];
});
const gridLayoutMultiPane = computed(() => {
return findGridLayout(gridLayoutData.value, TradeLayout.multiPane);
});
const gridLayoutOpenTrades = computed(() => {
return findGridLayout(gridLayoutData.value, TradeLayout.openTrades);
});
const gridLayoutTradeHistory = computed(() => {
return findGridLayout(gridLayoutData.value, TradeLayout.tradeHistory);
});
const gridLayoutTradeDetail = computed(() => {
return findGridLayout(gridLayoutData.value, TradeLayout.tradeDetail);
});
const gridLayoutChartView = computed(() => {
return findGridLayout(gridLayoutData.value, TradeLayout.chartView);
});
const responsiveGridLayouts = computed(() => {
return {
sm: layoutStore.getTradingLayoutSm,
};
});
</script>
<style scoped></style>