2020-08-08 13:37:18 +00:00
|
|
|
<template>
|
2021-06-22 19:04:33 +00:00
|
|
|
<div class="d-flex h-100">
|
|
|
|
<div class="flex-fill container-fluid flex-column align-items-stretch d-flex h-100">
|
|
|
|
<b-modal
|
2021-06-23 18:33:56 +00:00
|
|
|
v-if="plotConfigModal"
|
2021-06-22 19:04:33 +00:00
|
|
|
id="plotConfiguratorModal"
|
|
|
|
title="Plot Configurator"
|
|
|
|
ok-only
|
|
|
|
hide-backdrop
|
|
|
|
button-size="sm"
|
|
|
|
>
|
|
|
|
<PlotConfigurator v-model="plotConfig" :columns="datasetColumns" />
|
|
|
|
</b-modal>
|
|
|
|
|
|
|
|
<div class="row mr-0">
|
2021-11-02 06:11:02 +00:00
|
|
|
<div class="ml-2 d-flex flex-wrap flex-md-nowrap align-items-center">
|
2021-10-10 13:16:27 +00:00
|
|
|
<span class="ml-2 text-nowrap">{{ strategyName }} | {{ timeframe || '' }}</span>
|
2021-12-15 18:41:32 +00:00
|
|
|
<v-select
|
2021-10-10 13:16:27 +00:00
|
|
|
v-model="pair"
|
|
|
|
class="ml-2"
|
|
|
|
:options="availablePairs"
|
2021-11-02 06:11:02 +00:00
|
|
|
style="min-width: 7em"
|
2021-10-10 13:16:27 +00:00
|
|
|
size="sm"
|
2021-12-15 18:41:32 +00:00
|
|
|
:clearable="false"
|
2021-10-10 13:16:27 +00:00
|
|
|
@change="refresh"
|
|
|
|
>
|
2021-12-15 18:41:32 +00:00
|
|
|
</v-select>
|
2021-10-10 13:16:27 +00:00
|
|
|
|
|
|
|
<b-button class="ml-2" :disabled="!!!pair" size="sm" @click="refresh">↻</b-button>
|
2022-04-02 17:23:32 +00:00
|
|
|
<small v-if="dataset" class="ml-2 text-nowrap" title="Long entry signals"
|
|
|
|
>Long signals: {{ dataset.enter_long_signals || dataset.buy_signals }}</small
|
2021-10-11 18:39:13 +00:00
|
|
|
>
|
2022-04-02 17:23:32 +00:00
|
|
|
<small v-if="dataset" class="ml-2 text-nowrap" title="Long exit signals"
|
|
|
|
>Long exit: {{ dataset.exit_long_signals || dataset.sell_signals }}</small
|
2021-10-11 18:39:13 +00:00
|
|
|
>
|
2021-11-20 19:11:54 +00:00
|
|
|
<small v-if="dataset && dataset.enter_short_signals" class="ml-2 text-nowrap"
|
|
|
|
>Short entries: {{ dataset.enter_short_signals }}</small
|
|
|
|
>
|
|
|
|
<small v-if="dataset && dataset.exit_short_signals" class="ml-2 text-nowrap"
|
|
|
|
>Short exits: {{ dataset.exit_short_signals }}</small
|
|
|
|
>
|
2021-06-22 19:04:33 +00:00
|
|
|
</div>
|
2022-03-11 06:48:39 +00:00
|
|
|
<div class="ml-auto d-flex align-items-center">
|
|
|
|
<b-form-checkbox v-model="heikinAshi">Heikin Ashi</b-form-checkbox>
|
|
|
|
|
|
|
|
<div class="ml-2">
|
|
|
|
<b-select
|
|
|
|
v-model="plotConfigName"
|
2022-04-18 11:43:55 +00:00
|
|
|
:options="botStore.activeBot.availablePlotConfigNames"
|
2022-03-11 06:48:39 +00:00
|
|
|
size="sm"
|
|
|
|
@change="plotConfigChanged"
|
|
|
|
>
|
|
|
|
</b-select>
|
|
|
|
</div>
|
2021-06-22 19:04:33 +00:00
|
|
|
|
2022-03-11 06:48:39 +00:00
|
|
|
<div class="ml-2 mr-0 mr-md-1">
|
|
|
|
<b-button size="sm" title="Plot configurator" @click="showConfigurator">
|
|
|
|
⚙
|
|
|
|
</b-button>
|
|
|
|
</div>
|
2021-06-22 19:04:33 +00:00
|
|
|
</div>
|
2020-09-27 07:24:12 +00:00
|
|
|
</div>
|
2021-06-22 19:04:33 +00:00
|
|
|
<div class="row mr-1 ml-1 h-100">
|
|
|
|
<CandleChart
|
|
|
|
v-if="hasDataset"
|
|
|
|
:dataset="dataset"
|
|
|
|
:trades="trades"
|
|
|
|
:plot-config="plotConfig"
|
2022-03-11 06:48:39 +00:00
|
|
|
:heikin-ashi="heikinAshi"
|
2022-04-17 07:28:00 +00:00
|
|
|
:use-u-t-c="settingsStore.timezone === 'UTC'"
|
2021-06-22 19:04:33 +00:00
|
|
|
:theme="getChartTheme"
|
2020-08-08 13:57:36 +00:00
|
|
|
>
|
2021-06-22 19:04:33 +00:00
|
|
|
</CandleChart>
|
2022-03-03 18:37:38 +00:00
|
|
|
<div v-else class="m-auto">
|
2022-03-03 18:46:42 +00:00
|
|
|
<b-spinner v-if="isLoadingDataset" label="Spinning" />
|
2022-03-03 18:37:38 +00:00
|
|
|
|
2022-03-03 18:46:42 +00:00
|
|
|
<div v-else style="font-size: 1.5rem">
|
2022-03-03 18:37:38 +00:00
|
|
|
{{ noDatasetText }}
|
|
|
|
</div>
|
|
|
|
</div>
|
2020-08-08 13:37:18 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
2021-06-22 19:04:33 +00:00
|
|
|
<transition name="fade" mode="in-out">
|
2021-06-23 18:33:56 +00:00
|
|
|
<div v-if="!plotConfigModal" v-show="showPlotConfig" class="w-25 config-sidebar">
|
2021-06-23 04:45:18 +00:00
|
|
|
<PlotConfigurator v-model="plotConfig" :columns="datasetColumns" :as-modal="false" />
|
2021-06-22 19:04:33 +00:00
|
|
|
</div>
|
|
|
|
</transition>
|
2020-08-08 13:37:18 +00:00
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="ts">
|
2022-04-18 11:43:55 +00:00
|
|
|
import { Trade, PairHistory, EMPTY_PLOTCONFIG, PlotConfig, LoadingStatus } from '@/types';
|
2020-08-08 13:38:47 +00:00
|
|
|
import CandleChart from '@/components/charts/CandleChart.vue';
|
|
|
|
import PlotConfigurator from '@/components/charts/PlotConfigurator.vue';
|
2020-08-08 13:57:36 +00:00
|
|
|
import { getCustomPlotConfig, getPlotConfigName } from '@/shared/storage';
|
2021-12-15 18:41:32 +00:00
|
|
|
import vSelect from 'vue-select';
|
2022-04-13 19:28:04 +00:00
|
|
|
import { useSettingsStore } from '@/stores/settings';
|
2020-08-08 13:57:36 +00:00
|
|
|
|
2022-04-18 17:46:53 +00:00
|
|
|
import { defineComponent, ref, computed, onMounted, watch } from '@vue/composition-api';
|
|
|
|
import { useGetters } from 'vuex-composition-helpers';
|
2022-04-18 11:43:55 +00:00
|
|
|
import { useBotStore } from '@/stores/ftbotwrapper';
|
2022-04-17 07:28:00 +00:00
|
|
|
|
|
|
|
export default defineComponent({
|
|
|
|
name: 'CandleChartContainer',
|
|
|
|
components: { CandleChart, PlotConfigurator, vSelect },
|
|
|
|
props: {
|
|
|
|
trades: { required: false, default: [], type: Array as () => Trade[] },
|
|
|
|
availablePairs: { required: true, type: Array as () => string[] },
|
|
|
|
timeframe: { required: true, type: String },
|
|
|
|
historicView: { required: false, default: false, type: Boolean },
|
|
|
|
plotConfigModal: { required: false, default: true, type: Boolean },
|
|
|
|
/** Only required if historicView is true */
|
|
|
|
timerange: { required: false, default: '', type: String },
|
|
|
|
/** Only required if historicView is true */
|
|
|
|
strategy: { required: false, default: '', type: String },
|
|
|
|
},
|
|
|
|
setup(props, { root }) {
|
|
|
|
const settingsStore = useSettingsStore();
|
2022-04-18 11:43:55 +00:00
|
|
|
const botStore = useBotStore();
|
2022-04-17 07:28:00 +00:00
|
|
|
const { getChartTheme } = useGetters(['getChartTheme']);
|
2022-04-18 17:46:53 +00:00
|
|
|
|
2022-04-17 07:28:00 +00:00
|
|
|
const pair = ref('');
|
|
|
|
const plotConfig = ref<PlotConfig>({ ...EMPTY_PLOTCONFIG });
|
|
|
|
const plotConfigName = ref('');
|
|
|
|
const heikinAshi = ref(false);
|
|
|
|
const showPlotConfig = ref(props.plotConfigModal);
|
|
|
|
|
|
|
|
const dataset = computed((): PairHistory => {
|
|
|
|
if (props.historicView) {
|
2022-04-18 11:43:55 +00:00
|
|
|
return botStore.activeBot.history[`${pair.value}__${props.timeframe}`]?.data;
|
2022-04-17 07:28:00 +00:00
|
|
|
}
|
2022-04-18 11:43:55 +00:00
|
|
|
return botStore.activeBot.candleData[`${pair.value}__${props.timeframe}`]?.data;
|
2022-04-17 07:28:00 +00:00
|
|
|
});
|
|
|
|
const strategyName = computed(() => props.strategy || dataset.value?.strategy || '');
|
|
|
|
const datasetColumns = computed(() => (dataset.value ? dataset.value.columns : []));
|
|
|
|
const hasDataset = computed(() => !!dataset.value);
|
|
|
|
const isLoadingDataset = computed((): boolean => {
|
|
|
|
if (props.historicView) {
|
2022-04-18 11:43:55 +00:00
|
|
|
return botStore.activeBot.historyStatus === LoadingStatus.loading;
|
2022-04-17 07:28:00 +00:00
|
|
|
}
|
2022-03-03 18:37:38 +00:00
|
|
|
|
2022-04-18 11:43:55 +00:00
|
|
|
return botStore.activeBot.candleDataStatus === LoadingStatus.loading;
|
2022-04-17 07:28:00 +00:00
|
|
|
});
|
|
|
|
const noDatasetText = computed((): string => {
|
2022-04-18 11:43:55 +00:00
|
|
|
const status = props.historicView
|
|
|
|
? botStore.activeBot.historyStatus
|
|
|
|
: botStore.activeBot.candleDataStatus;
|
2022-03-03 18:37:38 +00:00
|
|
|
|
2022-04-17 07:28:00 +00:00
|
|
|
switch (status) {
|
2022-04-18 11:43:55 +00:00
|
|
|
case LoadingStatus.loading:
|
2022-04-17 07:28:00 +00:00
|
|
|
return 'Loading...';
|
2022-03-03 18:37:38 +00:00
|
|
|
|
2022-04-18 11:43:55 +00:00
|
|
|
case LoadingStatus.success:
|
2022-04-17 07:28:00 +00:00
|
|
|
return 'No data available';
|
2020-09-27 07:37:07 +00:00
|
|
|
|
2022-04-18 11:43:55 +00:00
|
|
|
case LoadingStatus.error:
|
2022-04-17 07:28:00 +00:00
|
|
|
return 'Failed to load data';
|
2022-04-13 19:28:04 +00:00
|
|
|
|
2022-04-17 07:28:00 +00:00
|
|
|
default:
|
|
|
|
return 'Unknown';
|
|
|
|
}
|
2022-04-13 19:28:04 +00:00
|
|
|
});
|
|
|
|
|
2022-04-17 07:28:00 +00:00
|
|
|
const plotConfigChanged = () => {
|
|
|
|
console.log('plotConfigChanged');
|
|
|
|
plotConfig.value = getCustomPlotConfig(plotConfigName.value);
|
2022-04-18 11:43:55 +00:00
|
|
|
botStore.activeBot.setPlotConfigName(plotConfigName.value);
|
2022-04-17 07:28:00 +00:00
|
|
|
};
|
2022-03-03 19:06:44 +00:00
|
|
|
|
2022-04-17 07:28:00 +00:00
|
|
|
const showConfigurator = () => {
|
|
|
|
if (props.plotConfigModal) {
|
|
|
|
root.$bvModal.show('plotConfiguratorModal');
|
|
|
|
} else {
|
|
|
|
showPlotConfig.value = !showPlotConfig.value;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
const refresh = () => {
|
|
|
|
if (pair.value && props.timeframe) {
|
|
|
|
if (props.historicView) {
|
2022-04-18 11:43:55 +00:00
|
|
|
botStore.activeBot.getPairHistory({
|
2022-04-17 07:28:00 +00:00
|
|
|
pair: pair.value,
|
|
|
|
timeframe: props.timeframe,
|
|
|
|
timerange: props.timerange,
|
|
|
|
strategy: props.strategy,
|
|
|
|
});
|
|
|
|
} else {
|
2022-04-18 11:43:55 +00:00
|
|
|
botStore.activeBot.getPairCandles({
|
|
|
|
pair: pair.value,
|
|
|
|
timeframe: props.timeframe,
|
|
|
|
limit: 500,
|
|
|
|
});
|
2022-04-17 07:28:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2020-08-08 13:57:36 +00:00
|
|
|
|
2022-04-18 17:46:53 +00:00
|
|
|
watch(
|
|
|
|
() => props.availablePairs,
|
|
|
|
() => {
|
|
|
|
if (!props.availablePairs.find((p) => p === pair.value)) {
|
|
|
|
[pair.value] = props.availablePairs;
|
|
|
|
refresh();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
2020-08-08 13:37:18 +00:00
|
|
|
|
2022-04-18 17:46:53 +00:00
|
|
|
watch(
|
|
|
|
() => botStore.activeBot.selectedPair,
|
|
|
|
() => {
|
|
|
|
pair.value = botStore.activeBot.selectedPair;
|
|
|
|
refresh();
|
|
|
|
},
|
|
|
|
);
|
2020-09-14 17:56:43 +00:00
|
|
|
|
2022-04-17 07:28:00 +00:00
|
|
|
onMounted(() => {
|
2022-04-18 11:43:55 +00:00
|
|
|
if (botStore.activeBot.selectedPair) {
|
|
|
|
pair.value = botStore.activeBot.selectedPair;
|
2022-04-17 07:28:00 +00:00
|
|
|
} else if (props.availablePairs.length > 0) {
|
|
|
|
[pair.value] = props.availablePairs;
|
2020-09-14 17:56:43 +00:00
|
|
|
}
|
2022-04-17 07:28:00 +00:00
|
|
|
plotConfigName.value = getPlotConfigName();
|
|
|
|
plotConfig.value = getCustomPlotConfig(plotConfigName.value);
|
2020-09-14 17:56:43 +00:00
|
|
|
|
2022-04-17 07:28:00 +00:00
|
|
|
if (!hasDataset) {
|
|
|
|
refresh();
|
|
|
|
}
|
|
|
|
});
|
2020-10-31 07:47:07 +00:00
|
|
|
|
2022-04-17 07:28:00 +00:00
|
|
|
return {
|
2022-04-18 11:43:55 +00:00
|
|
|
botStore,
|
2022-04-17 07:28:00 +00:00
|
|
|
getChartTheme,
|
|
|
|
history,
|
|
|
|
dataset,
|
|
|
|
strategyName,
|
|
|
|
datasetColumns,
|
|
|
|
isLoadingDataset,
|
|
|
|
noDatasetText,
|
|
|
|
hasDataset,
|
|
|
|
settingsStore,
|
|
|
|
heikinAshi,
|
|
|
|
plotConfigChanged,
|
|
|
|
showPlotConfig,
|
|
|
|
showConfigurator,
|
|
|
|
refresh,
|
|
|
|
plotConfigName,
|
|
|
|
pair,
|
|
|
|
plotConfig,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
});
|
2020-08-08 13:37:18 +00:00
|
|
|
</script>
|
|
|
|
|
2021-06-22 19:04:33 +00:00
|
|
|
<style scoped lang="scss">
|
|
|
|
.fade-enter-active,
|
|
|
|
.fade-leave-active {
|
|
|
|
transition: all 0.2s;
|
|
|
|
}
|
|
|
|
|
|
|
|
.fade-enter,
|
|
|
|
.fade-leave-to {
|
|
|
|
opacity: 0;
|
|
|
|
transform: translateX(30px);
|
|
|
|
}
|
|
|
|
</style>
|