mirror of
https://github.com/freqtrade/frequi.git
synced 2024-09-20 09:31:10 +00:00
commit
54f2187c23
46
cypress/e2e/dashboard.cy.ts
Normal file
46
cypress/e2e/dashboard.cy.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { setLoginInfo, defaultMocks } from './helpers';
|
||||
|
||||
function tradeMocks() {
|
||||
cy.intercept('GET', '**/api/v1/status', { fixture: 'status_empty.json' }).as('Status');
|
||||
cy.intercept('GET', '**/api/v1/profit', { fixture: 'profit.json' }).as('Profit');
|
||||
cy.intercept('GET', '**/api/v1/trades*', { fixture: 'trades.json' }).as('Trades');
|
||||
cy.intercept('GET', '**/api/v1/balance', { fixture: 'balance.json' }).as('Balance');
|
||||
cy.intercept('GET', '**/api/v1/whitelist', { fixture: 'whitelist.json' }).as('Whitelist');
|
||||
cy.intercept('GET', '**/api/v1/blacklist', { fixture: 'blacklist.json' }).as('Blacklist');
|
||||
cy.intercept('GET', '**/api/v1/locks', { fixture: 'locks_empty.json' }).as('Locks');
|
||||
cy.intercept('GET', '**/api/v1/performance', { fixture: 'performance.json' }).as('Performance');
|
||||
// TODO: Daily mock is missing.
|
||||
// cy.intercept('GET', '**/api/v1/daily', { fixture: 'performance.json' }).as('Performance');
|
||||
}
|
||||
|
||||
describe('Dashboard', () => {
|
||||
it('Dashboard view', () => {
|
||||
defaultMocks();
|
||||
tradeMocks();
|
||||
setLoginInfo();
|
||||
|
||||
cy.visit('/dashboard');
|
||||
cy.wait('@Ping');
|
||||
cy.wait('@Status');
|
||||
cy.wait('@Profit');
|
||||
cy.wait('@Balance');
|
||||
cy.wait('@Trades');
|
||||
cy.wait('@Whitelist');
|
||||
cy.wait('@Blacklist');
|
||||
cy.wait('@Locks');
|
||||
cy.wait('@Performance');
|
||||
cy.get('.drag-header').contains('Bot comparison').should('be.visible');
|
||||
cy.get('.drag-header').contains('Daily Profit').should('be.visible');
|
||||
cy.get('.drag-header').contains('Open Trades').should('be.visible');
|
||||
cy.get('.drag-header').contains('Cumulative Profit').should('be.visible');
|
||||
|
||||
// Assert Botcomparison content
|
||||
cy.get('span').contains('TestBot').should('be.visible');
|
||||
cy.get('span').contains('Summary').should('be.visible');
|
||||
// Scroll lower
|
||||
cy.get('.drag-header').contains('Closed Trades').scrollIntoView();
|
||||
cy.get('.drag-header').contains('Closed Trades').should('be.visible');
|
||||
cy.get('.drag-header').contains('Profit Distribution').should('be.visible');
|
||||
cy.get('.drag-header').contains('Trades Log').should('be.visible');
|
||||
});
|
||||
});
|
|
@ -31,12 +31,13 @@ describe('Login', () => {
|
|||
cy.visit('/trade');
|
||||
cy.location().should((loc) => {
|
||||
expect(loc.pathname).to.eq('/login');
|
||||
expect(loc.search).to.eq('?redirect=%2Ftrade');
|
||||
expect(loc.search).to.eq('?redirect=/trade');
|
||||
});
|
||||
});
|
||||
|
||||
it('Test Login', () => {
|
||||
cy.visit('/login');
|
||||
cy.get('.card-header').contains('Freqtrade bot Login');
|
||||
cy.get('input[id=name-input]').type('TestBot');
|
||||
cy.get('input[id=username-input]').type('Freqtrader');
|
||||
cy.get('input[id=password-input]').type('SuperDuperBot');
|
||||
|
@ -93,6 +94,7 @@ describe('Login', () => {
|
|||
|
||||
it('Test Login failed - wrong api url', () => {
|
||||
cy.visit('/login');
|
||||
cy.get('.card-header').contains('Freqtrade bot Login');
|
||||
cy.get('input[id=name-input]').type('TestBot');
|
||||
cy.get('input[id=username-input]').type('Freqtrader');
|
||||
cy.get('input[id=password-input]').type('SuperDuperBot');
|
||||
|
@ -123,6 +125,7 @@ describe('Login', () => {
|
|||
|
||||
it('Test Login failed - wrong password url', () => {
|
||||
cy.visit('/login');
|
||||
cy.get('.card-header').contains('Freqtrade bot Login');
|
||||
cy.get('input[id=name-input]').type('TestBot');
|
||||
cy.get('input[id=username-input]').type('Freqtrader');
|
||||
cy.get('input[id=password-input]').type('SuperDuperBot');
|
||||
|
|
|
@ -12,10 +12,11 @@ function tradeMocks() {
|
|||
}
|
||||
|
||||
describe('Trade', () => {
|
||||
it('Trade view', () => {
|
||||
it('Trade view', { scrollBehavior: false }, () => {
|
||||
defaultMocks();
|
||||
tradeMocks();
|
||||
setLoginInfo();
|
||||
cy.viewport('macbook-11');
|
||||
|
||||
cy.visit('/trade');
|
||||
cy.wait('@Ping');
|
||||
|
@ -27,11 +28,37 @@ describe('Trade', () => {
|
|||
cy.wait('@Blacklist');
|
||||
cy.wait('@Locks');
|
||||
cy.wait('@Performance');
|
||||
cy.get('.drag-header').contains('Multi Pane').should('be.visible');
|
||||
cy.get('.drag-header').contains('Chart').should('be.visible');
|
||||
cy.get('button').should('contain', 'BTC/USDT');
|
||||
cy.get('button').should('contain', 'ETH/USDT').should('be.visible');
|
||||
cy.get('button').contains('ETH/USDT').should('be.visible');
|
||||
|
||||
cy.get('a[role="tab"]').contains('General').click();
|
||||
// Test messageBox behavior
|
||||
// No modal visible
|
||||
cy.get('.modal-dialog > .modal-content > .modal-footer > .btn-secondary')
|
||||
.filter(':visible')
|
||||
.should('have.length', 0);
|
||||
|
||||
cy.get('button[title*="Stop Trading"]').click();
|
||||
// Modal open
|
||||
cy.get('.modal-dialog > .modal-content > .modal-footer > .btn-secondary')
|
||||
.filter(':visible')
|
||||
.contains('Cancel')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
// Modal closed
|
||||
cy.get('.modal-dialog > .modal-content > .modal-footer > .btn-secondary')
|
||||
.filter(':visible')
|
||||
.should('have.length', 0);
|
||||
|
||||
// General
|
||||
cy.get('button[role="tab"]').contains('General').click();
|
||||
cy.get('button').contains('ETH/USDT').should('not.be.visible');
|
||||
// 2nd segment
|
||||
cy.get('.drag-header').contains('Open Trades').scrollIntoView().should('be.visible');
|
||||
cy.get('.drag-header').contains('Closed Trades').scrollIntoView().should('be.visible');
|
||||
cy.get('span').contains('TRX/USDT').should('be.visible');
|
||||
cy.get('td').contains('8070.5').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -19,23 +19,23 @@ import './commands';
|
|||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
|
||||
import { mount } from 'cypress/vue2';
|
||||
// Vue2.7 Workaround to have proper types
|
||||
import { mount as mount3 } from 'cypress/vue';
|
||||
import { mount } from 'cypress/vue';
|
||||
|
||||
declare global {
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
mount: typeof mount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Augment the Cypress namespace to include type definitions for
|
||||
// your custom command.
|
||||
// Alternatively, can be defined in cypress/support/component.d.ts
|
||||
// with a <reference path="./component" /> at the top of your spec.
|
||||
declare global {
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
mount: typeof mount3;
|
||||
}
|
||||
}
|
||||
}
|
||||
// import '../../src/assets/main.css';
|
||||
|
||||
Cypress.Commands.add('mount', mount);
|
||||
|
||||
// Example use:
|
||||
// cy.mount(MyComponent)
|
||||
// cy.mount(MyComponent);s
|
||||
|
|
25
package.json
25
package.json
|
@ -16,9 +16,10 @@
|
|||
"cy:run-ct": "cypress run --component"
|
||||
},
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"axios": "^1.2.0",
|
||||
"bootstrap": "^4.6.0",
|
||||
"bootstrap-vue": "^2.23.1",
|
||||
"bootstrap": "^5.2.2",
|
||||
"bootstrap-vue-3": "^0.4.11",
|
||||
"bootswatch": "^5.2.2",
|
||||
"core-js": "^3.26.1",
|
||||
"date-fns": "^2.29.3",
|
||||
|
@ -28,35 +29,34 @@
|
|||
"humanize-duration": "^3.27.3",
|
||||
"pinia": "^2.0.27",
|
||||
"pinia-plugin-persistedstate": "^3.0.1",
|
||||
"vue": "^2.7.14",
|
||||
"vue": "3.2.42",
|
||||
"vue-class-component": "^7.2.5",
|
||||
"vue-demi": "0.13.11",
|
||||
"vue-echarts": "^6.2.3",
|
||||
"vue-grid-layout": "^2.4.0",
|
||||
"vue-material-design-icons": "^5.1.2",
|
||||
"vue-router": "^3.6.5",
|
||||
"vue-select": "^3.20.0"
|
||||
"vue-router": "^4.1.6",
|
||||
"vue-select": "^4.0.0-beta.5",
|
||||
"vue3-drr-grid-layout": "^1.9.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cypress/vite-dev-server": "^4.0.1",
|
||||
"@cypress/vue": "^2.2.3",
|
||||
"@cypress/vue": "^5.0.2",
|
||||
"@types/echarts": "^4.9.16",
|
||||
"@types/jest": "^27.5.0",
|
||||
"@typescript-eslint/eslint-plugin": "^2.33.0",
|
||||
"@typescript-eslint/parser": "^5.45.0",
|
||||
"@vitejs/plugin-vue2": "^1.1.2",
|
||||
"@vue/composition-api": "^1.7.1",
|
||||
"@vitejs/plugin-vue": "^1.2.2",
|
||||
"@vue/compiler-sfc": "3.2.42",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"@vue/eslint-config-typescript": "^5.1.0",
|
||||
"@vue/runtime-dom": "^3.2.45",
|
||||
"@vue/test-utils": "^1.3.3",
|
||||
"cypress": "^10.3.0",
|
||||
"@vue/test-utils": "^2.2.4",
|
||||
"cypress": "^11.2.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-prettier": "^3.4.1",
|
||||
"eslint-plugin-vue": "^7.20.0",
|
||||
"jest": "^27.5.1",
|
||||
"mutationobserver-shim": "^0.3.7",
|
||||
"popper.js": "^1.16.1",
|
||||
"portal-vue": "^2.1.7",
|
||||
"prettier": "^2.8.0",
|
||||
"sass": "^1.56.1",
|
||||
|
@ -65,7 +65,6 @@
|
|||
"typescript": "~4.9.3",
|
||||
"vite": "^2.9.14",
|
||||
"vite-jest": "^0.1.4",
|
||||
"vue-template-compiler": "^2.7.14",
|
||||
"vue-tsc": "^1.0.10"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,30 +1,39 @@
|
|||
<template>
|
||||
<div class="d-flex align-items-center justify-content-between w-100">
|
||||
<span class="mr-2">{{ bot.botName || bot.botId }}</span>
|
||||
<div v-if="bot" class="d-flex align-items-center justify-content-between w-100">
|
||||
<span class="me-2">{{ bot.botName || bot.botId }}</span>
|
||||
|
||||
<div class="align-items-center d-flex">
|
||||
<span class="ml-2 mr-1 align-middle">{{
|
||||
botStore.botStores[bot.botId].isBotOnline ? '🟢' : '🔴'
|
||||
}}</span>
|
||||
<b-form-checkbox
|
||||
v-model="autoRefreshLoc"
|
||||
class="ml-auto float-right mr-2 my-auto"
|
||||
class="ms-auto float-end me-2 my-auto mt-1"
|
||||
title="AutoRefresh"
|
||||
variant="secondary"
|
||||
switch
|
||||
@change="changeEvent"
|
||||
>
|
||||
R
|
||||
<span class="ms-2 me-1 align-middle">{{
|
||||
botStore.botStores[bot.botId].isBotOnline ? '🟢' : '🔴'
|
||||
}}</span>
|
||||
</b-form-checkbox>
|
||||
<div v-if="!noButtons" class="d-flex flex-align-cent">
|
||||
<b-button class="ml-1" size="sm" title="Delete bot" @click="$emit('edit')">
|
||||
<div v-if="!noButtons" class="float-end d-flex flex-align-center">
|
||||
<b-button class="ms-1" size="sm" title="Delete bot" @click="$emit('edit')">
|
||||
<EditIcon :size="16" />
|
||||
</b-button>
|
||||
|
||||
<b-button class="ml-1" size="sm" title="Delete bot" @click.prevent="clickRemoveBot(bot)">
|
||||
<b-button class="ms-1" size="sm" title="Delete bot" @click="botRemoveModalVisible = true">
|
||||
<DeleteIcon :size="16" title="Delete Bot" />
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
<b-modal
|
||||
v-if="!noButtons"
|
||||
id="removeBotModal"
|
||||
v-model="botRemoveModalVisible"
|
||||
title="Logout confirmation"
|
||||
@ok="confirmRemoveBot"
|
||||
>
|
||||
Really remove (logout) from {{ bot.botName }} ({{ bot.botId }})?
|
||||
</b-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -32,7 +41,7 @@
|
|||
import EditIcon from 'vue-material-design-icons/Pencil.vue';
|
||||
import DeleteIcon from 'vue-material-design-icons/Delete.vue';
|
||||
import { BotDescriptor } from '@/types';
|
||||
import { defineComponent, computed, getCurrentInstance } from 'vue';
|
||||
import { defineComponent, computed, ref } from 'vue';
|
||||
import { useBotStore } from '@/stores/ftbotwrapper';
|
||||
|
||||
export default defineComponent({
|
||||
|
@ -47,22 +56,17 @@ export default defineComponent({
|
|||
},
|
||||
emits: ['edit'],
|
||||
setup(props) {
|
||||
const root = getCurrentInstance();
|
||||
const botStore = useBotStore();
|
||||
|
||||
const changeEvent = (v) => {
|
||||
botStore.botStores[props.bot.botId].setAutoRefresh(v);
|
||||
};
|
||||
const botRemoveModalVisible = ref(false);
|
||||
|
||||
const clickRemoveBot = (bot: BotDescriptor) => {
|
||||
//
|
||||
root?.proxy.$bvModal
|
||||
.msgBoxConfirm(`Really remove (logout) from '${bot.botName}' (${bot.botId})?`)
|
||||
.then((value: boolean) => {
|
||||
if (value) {
|
||||
botStore.removeBot(bot.botId);
|
||||
}
|
||||
});
|
||||
const confirmRemoveBot = () => {
|
||||
botRemoveModalVisible.value = false;
|
||||
botStore.removeBot(props.bot.botId);
|
||||
console.log('removing bot.');
|
||||
};
|
||||
const autoRefreshLoc = computed({
|
||||
get() {
|
||||
|
@ -76,9 +80,18 @@ export default defineComponent({
|
|||
return {
|
||||
botStore,
|
||||
changeEvent,
|
||||
clickRemoveBot,
|
||||
autoRefreshLoc,
|
||||
confirmRemoveBot,
|
||||
botRemoveModalVisible,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.form-switch {
|
||||
padding-left: 0;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -9,12 +9,12 @@
|
|||
autofocus
|
||||
/>
|
||||
|
||||
<div class="d-flex ml-2">
|
||||
<div class="d-flex ms-2">
|
||||
<b-button type="submit" size="sm" title="Save">
|
||||
<CheckIcon :size="16" />
|
||||
</b-button>
|
||||
|
||||
<b-button class="ml-1" size="sm" title="Cancel" @click="$emit('cancelled')">
|
||||
<b-button class="ms-1" size="sm" title="Cancel" @click="$emit('cancelled')">
|
||||
<CloseIcon :size="16" />
|
||||
</b-button>
|
||||
</div>
|
||||
|
|
|
@ -18,8 +18,9 @@
|
|||
<b-form-input
|
||||
id="url-input"
|
||||
v-model="auth.url"
|
||||
:state="urlState"
|
||||
required
|
||||
trim
|
||||
:state="urlState === '' ? null : urlState"
|
||||
@keydown.enter.native="handleOk"
|
||||
></b-form-input>
|
||||
</b-form-group>
|
||||
|
@ -34,6 +35,7 @@
|
|||
v-model="auth.username"
|
||||
required
|
||||
placeholder="Freqtrader"
|
||||
:state="nameState === '' ? null : nameState"
|
||||
@keydown.enter.native="handleOk"
|
||||
></b-form-input>
|
||||
</b-form-group>
|
||||
|
@ -48,6 +50,7 @@
|
|||
v-model="auth.password"
|
||||
required
|
||||
type="password"
|
||||
:state="pwdState === '' ? null : pwdState"
|
||||
@keydown.enter.native="handleOk"
|
||||
></b-form-input>
|
||||
</b-form-group>
|
||||
|
@ -63,8 +66,8 @@
|
|||
>
|
||||
</b-alert>
|
||||
</div>
|
||||
<div v-if="inModal === false" class="float-right">
|
||||
<b-button class="mr-2" type="reset" variant="danger">Reset</b-button>
|
||||
<div v-if="inModal === false" class="float-end">
|
||||
<b-button class="me-2" type="reset" variant="danger">Reset</b-button>
|
||||
<b-button type="submit" variant="primary">Submit</b-button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -77,8 +80,8 @@ import { useUserService } from '@/shared/userService';
|
|||
import { AuthPayload } from '@/types';
|
||||
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { useRouter, useRoute } from '@/composables/router-helper';
|
||||
import { useBotStore } from '@/stores/ftbotwrapper';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
const defaultURL = window.location.origin || 'http://localhost:3000';
|
||||
|
||||
|
@ -93,9 +96,9 @@ export default defineComponent({
|
|||
const route = useRoute();
|
||||
const botStore = useBotStore();
|
||||
|
||||
const nameState = ref<boolean | null>();
|
||||
const pwdState = ref<boolean | null>();
|
||||
const urlState = ref<boolean | null>();
|
||||
const nameState = ref<boolean | ''>('');
|
||||
const pwdState = ref<boolean | ''>('');
|
||||
const urlState = ref<boolean | ''>('');
|
||||
const errorMessage = ref<string>('');
|
||||
const errorMessageCORS = ref<boolean>(false);
|
||||
const formRef = ref<HTMLFormElement>();
|
||||
|
@ -121,8 +124,9 @@ export default defineComponent({
|
|||
auth.value.url = defaultURL;
|
||||
auth.value.username = '';
|
||||
auth.value.password = '';
|
||||
nameState.value = null;
|
||||
pwdState.value = null;
|
||||
nameState.value = '';
|
||||
pwdState.value = '';
|
||||
urlState.value = '';
|
||||
errorMessage.value = '';
|
||||
};
|
||||
|
||||
|
@ -158,10 +162,10 @@ export default defineComponent({
|
|||
if (props.inModal === false) {
|
||||
if (typeof route?.query.redirect === 'string') {
|
||||
const resolved = router.resolve({ path: route.query.redirect });
|
||||
if (resolved.route.name !== '404') {
|
||||
router.push(resolved.route.path);
|
||||
} else {
|
||||
if (resolved.name === '404') {
|
||||
router.push('/');
|
||||
} else {
|
||||
router.push(resolved.path);
|
||||
}
|
||||
} else {
|
||||
router.push('/');
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="row flex-grow-1 chart-wrapper">
|
||||
<div class="d-flex flex-grow-1 chart-wrapper">
|
||||
<v-chart v-if="hasData" ref="candleChart" :theme="theme" autoresize manual-update />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,23 +1,12 @@
|
|||
<template>
|
||||
<div class="d-flex h-100">
|
||||
<div class="flex-fill container-fluid flex-column align-items-stretch d-flex h-100">
|
||||
<b-modal
|
||||
v-if="plotConfigModal"
|
||||
id="plotConfiguratorModal"
|
||||
title="Plot Configurator"
|
||||
ok-only
|
||||
hide-backdrop
|
||||
button-size="sm"
|
||||
>
|
||||
<PlotConfigurator :columns="datasetColumns" />
|
||||
</b-modal>
|
||||
|
||||
<div class="row mr-0">
|
||||
<div class="ml-2 d-flex flex-wrap flex-md-nowrap align-items-center">
|
||||
<span class="ml-2 text-nowrap">{{ strategyName }} | {{ timeframe || '' }}</span>
|
||||
<div class="flex-fill w-100 flex-column align-items-stretch d-flex h-100">
|
||||
<div class="d-flex me-0">
|
||||
<div class="ms-2 d-flex flex-wrap flex-md-nowrap align-items-center w-auto">
|
||||
<span class="ms-2 text-nowrap">{{ strategyName }} | {{ timeframe || '' }}</span>
|
||||
<v-select
|
||||
v-model="pair"
|
||||
class="ml-2"
|
||||
class="ms-2"
|
||||
:options="availablePairs"
|
||||
style="min-width: 7em"
|
||||
size="sm"
|
||||
|
@ -26,26 +15,26 @@
|
|||
>
|
||||
</v-select>
|
||||
|
||||
<b-button class="ml-2" :disabled="!!!pair" size="sm" @click="refresh">↻</b-button>
|
||||
<small v-if="dataset" class="ml-2 text-nowrap" title="Long entry signals"
|
||||
<b-button class="ms-2" :disabled="!!!pair" size="sm" @click="refresh">↻</b-button>
|
||||
<small v-if="dataset" class="ms-2 text-nowrap" title="Long entry signals"
|
||||
>Long signals: {{ dataset.enter_long_signals || dataset.buy_signals }}</small
|
||||
>
|
||||
<small v-if="dataset" class="ml-2 text-nowrap" title="Long exit signals"
|
||||
<small v-if="dataset" class="ms-2 text-nowrap" title="Long exit signals"
|
||||
>Long exit: {{ dataset.exit_long_signals || dataset.sell_signals }}</small
|
||||
>
|
||||
<small v-if="dataset && dataset.enter_short_signals" class="ml-2 text-nowrap"
|
||||
<small v-if="dataset && dataset.enter_short_signals" class="ms-2 text-nowrap"
|
||||
>Short entries: {{ dataset.enter_short_signals }}</small
|
||||
>
|
||||
<small v-if="dataset && dataset.exit_short_signals" class="ml-2 text-nowrap"
|
||||
<small v-if="dataset && dataset.exit_short_signals" class="ms-2 text-nowrap"
|
||||
>Short exits: {{ dataset.exit_short_signals }}</small
|
||||
>
|
||||
</div>
|
||||
<div class="ml-auto d-flex align-items-center">
|
||||
<div class="ms-auto d-flex align-items-center w-auto">
|
||||
<b-form-checkbox v-model="settingsStore.useHeikinAshiCandles"
|
||||
>Heikin Ashi</b-form-checkbox
|
||||
>
|
||||
|
||||
<div class="ml-2">
|
||||
<div class="ms-2">
|
||||
<b-form-select
|
||||
v-model="plotStore.plotConfigName"
|
||||
:options="plotStore.availablePlotConfigNames"
|
||||
|
@ -55,14 +44,14 @@
|
|||
</b-form-select>
|
||||
</div>
|
||||
|
||||
<div class="ml-2 mr-0 mr-md-1">
|
||||
<div class="ms-2 me-0 me-md-1">
|
||||
<b-button size="sm" title="Plot configurator" @click="showConfigurator">
|
||||
⚙
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mr-1 ml-1 h-100">
|
||||
<div class="me-1 ms-1 h-100">
|
||||
<CandleChart
|
||||
v-if="hasDataset"
|
||||
:dataset="dataset"
|
||||
|
@ -88,6 +77,16 @@
|
|||
<PlotConfigurator :columns="datasetColumns" :as-modal="false" />
|
||||
</div>
|
||||
</transition>
|
||||
<b-modal
|
||||
v-if="plotConfigModal"
|
||||
id="plotConfiguratorModal"
|
||||
v-model="showPlotConfigModal"
|
||||
title="Plot Configurator"
|
||||
ok-only
|
||||
hide-backdrop
|
||||
>
|
||||
<PlotConfigurator :columns="datasetColumns" />
|
||||
</b-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -99,7 +98,7 @@ import vSelect from 'vue-select';
|
|||
import { useSettingsStore } from '@/stores/settings';
|
||||
import { usePlotConfigStore } from '@/stores/plotConfig';
|
||||
|
||||
import { defineComponent, ref, computed, onMounted, watch, getCurrentInstance } from 'vue';
|
||||
import { defineComponent, ref, computed, onMounted, watch } from 'vue';
|
||||
import { useBotStore } from '@/stores/ftbotwrapper';
|
||||
|
||||
export default defineComponent({
|
||||
|
@ -122,7 +121,6 @@ export default defineComponent({
|
|||
},
|
||||
},
|
||||
setup(props) {
|
||||
const root = getCurrentInstance();
|
||||
const settingsStore = useSettingsStore();
|
||||
const botStore = useBotStore();
|
||||
const plotStore = usePlotConfigStore();
|
||||
|
@ -165,10 +163,10 @@ export default defineComponent({
|
|||
return 'Unknown';
|
||||
}
|
||||
});
|
||||
|
||||
const showPlotConfigModal = ref(false);
|
||||
const showConfigurator = () => {
|
||||
if (props.plotConfigModal) {
|
||||
root?.proxy.$bvModal.show('plotConfiguratorModal');
|
||||
showPlotConfigModal.value = !showPlotConfigModal.value;
|
||||
} else {
|
||||
showPlotConfig.value = !showPlotConfig.value;
|
||||
}
|
||||
|
@ -239,6 +237,7 @@ export default defineComponent({
|
|||
showConfigurator,
|
||||
refresh,
|
||||
pair,
|
||||
showPlotConfigModal,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
title="Remove indicator to plot"
|
||||
size="sm"
|
||||
:disabled="!selIndicatorName"
|
||||
class="ml-1"
|
||||
class="ms-1"
|
||||
@click="removeIndicator"
|
||||
>
|
||||
Remove indicator
|
||||
|
@ -64,13 +64,13 @@
|
|||
<hr />
|
||||
|
||||
<div>
|
||||
<b-button class="ml-1" variant="secondary" size="sm" @click="loadPlotConfig">Load</b-button>
|
||||
<b-button class="ml-1" variant="secondary" size="sm" @click="loadPlotConfigFromStrategy">
|
||||
<b-button class="ms-1" variant="secondary" size="sm" @click="loadPlotConfig">Load</b-button>
|
||||
<b-button class="ms-1" variant="secondary" size="sm" @click="loadPlotConfigFromStrategy">
|
||||
From strategy
|
||||
</b-button>
|
||||
|
||||
<b-button
|
||||
class="ml-1"
|
||||
class="ms-1"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
title="Load configuration from text box below"
|
||||
|
@ -79,7 +79,7 @@
|
|||
>
|
||||
<b-button
|
||||
id="showButton"
|
||||
class="ml-1"
|
||||
class="ms-1"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
title="Show configuration for easy transfer to a strategy"
|
||||
|
@ -89,7 +89,7 @@
|
|||
|
||||
<b-button
|
||||
v-if="showConfig"
|
||||
class="ml-1"
|
||||
class="ms-1"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
title="Load configuration from text box below"
|
||||
|
@ -97,7 +97,7 @@
|
|||
>Load from String</b-button
|
||||
>
|
||||
<b-button
|
||||
class="ml-1"
|
||||
class="ms-1"
|
||||
variant="primary"
|
||||
size="sm"
|
||||
data-toggle="tooltip"
|
||||
|
@ -106,7 +106,7 @@
|
|||
>Save</b-button
|
||||
>
|
||||
</div>
|
||||
<div v-if="showConfig" class="col-mb-5 ml-1 mt-2">
|
||||
<div v-if="showConfig" class="col-mb-5 ms-1 mt-2">
|
||||
<b-form-textarea
|
||||
id="TextArea"
|
||||
v-model="plotConfigJson"
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<b-form-input v-model="indicatorFilter" placeholder="Filter indicators"></b-form-input>
|
||||
<b-input-group-append>
|
||||
<Reset
|
||||
class="pointer align-self-center ml-1"
|
||||
class="pointer align-self-center ms-1"
|
||||
:size="18"
|
||||
@click="indicatorFilter = ''"
|
||||
></Reset>
|
||||
|
@ -36,7 +36,7 @@
|
|||
<b-form-group label="Color" label-for="colsel" size="sm">
|
||||
<b-input-group>
|
||||
<b-input-group-prepend>
|
||||
<div :style="{ 'background-color': selColor }" class="colorbox mr-2"></div>
|
||||
<div :style="{ 'background-color': selColor }" class="colorbox me-2"></div>
|
||||
<!-- <b-form-input
|
||||
id="colsel"
|
||||
v-model="selColor"
|
||||
|
@ -67,7 +67,7 @@
|
|||
</b-button>
|
||||
<b-button
|
||||
v-if="addNew"
|
||||
class="ml-1 flex-grow-1"
|
||||
class="ms-1 flex-grow-1"
|
||||
variant="secondary"
|
||||
title="Add "
|
||||
size="sm"
|
||||
|
@ -92,11 +92,11 @@ export default defineComponent({
|
|||
Reset,
|
||||
},
|
||||
props: {
|
||||
value: { required: true, type: Object as () => Record<string, IndicatorConfig> },
|
||||
modelValue: { required: true, type: Object as () => Record<string, IndicatorConfig> },
|
||||
columns: { required: true, type: Array as () => string[] },
|
||||
addNew: { required: true, type: Boolean },
|
||||
},
|
||||
emits: ['input'],
|
||||
emits: ['update:modelValue'],
|
||||
setup(props, { emit }) {
|
||||
const selColor = ref(randomColor());
|
||||
const graphType = ref<ChartType>(ChartType.line);
|
||||
|
@ -127,7 +127,7 @@ export default defineComponent({
|
|||
};
|
||||
});
|
||||
const emitIndicator = () => {
|
||||
emit('input', combinedIndicator.value);
|
||||
emit('update:modelValue', combinedIndicator.value);
|
||||
};
|
||||
|
||||
const clickCancel = () => {
|
||||
|
@ -136,13 +136,13 @@ export default defineComponent({
|
|||
};
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
[selAvailableIndicator.value] = Object.keys(props.value);
|
||||
[selAvailableIndicator.value] = Object.keys(props.modelValue);
|
||||
cancelled.value = false;
|
||||
if (selAvailableIndicator.value && props.value) {
|
||||
selColor.value = props.value[selAvailableIndicator.value].color || randomColor();
|
||||
graphType.value = props.value[selAvailableIndicator.value].type || ChartType.line;
|
||||
if (selAvailableIndicator.value && props.modelValue) {
|
||||
selColor.value = props.modelValue[selAvailableIndicator.value].color || randomColor();
|
||||
graphType.value = props.modelValue[selAvailableIndicator.value].type || ChartType.line;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</div>
|
||||
<b-form-group
|
||||
class="w-25 order-1"
|
||||
:class="showTitle ? 'ml-5 pl-5' : 'position-absolute'"
|
||||
:class="showTitle ? 'ms-5 ps-5' : 'position-absolute'"
|
||||
label="Bins"
|
||||
label-for="input-bins"
|
||||
label-cols="6"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-secondary float-right"
|
||||
class="btn btn-secondary float-end"
|
||||
title="Refresh"
|
||||
aria-label="Refresh"
|
||||
@click="botStore.activeBot.getBacktestHistory"
|
||||
|
@ -12,7 +12,7 @@
|
|||
Load Historic results from disk. You can click on multiple results to load all of them into
|
||||
freqUI.
|
||||
</p>
|
||||
<b-list-group v-if="botStore.activeBot.backtestHistoryList" class="ml-2">
|
||||
<b-list-group v-if="botStore.activeBot.backtestHistoryList" class="ms-2">
|
||||
<b-list-group-item
|
||||
v-for="(res, idx) in botStore.activeBot.backtestHistoryList"
|
||||
:key="idx"
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="col-md-11 text-left">
|
||||
<div class="col-md-11 text-start">
|
||||
<p>
|
||||
Graph will always show the latest values for the selected strategy. Timerange:
|
||||
{{ timerange }} - {{ strategy }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-1 text-right">
|
||||
<div class="col-md-1 text-end">
|
||||
<b-button
|
||||
aria-label="Close"
|
||||
title="Trade Navigation"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="container d-flex flex-column align-items-center">
|
||||
<h3>Available results:</h3>
|
||||
<b-list-group class="ml-2">
|
||||
<b-list-group class="ms-2">
|
||||
<b-list-group-item
|
||||
v-for="[key, strat] in Object.entries(backtestHistory)"
|
||||
:key="key"
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
<h3>Backtest-result for {{ backtestResult.strategy_name }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="row text-left ml-0">
|
||||
<div class="row text-start ms-0">
|
||||
<div class="row w-100">
|
||||
<div class="col-12 col-xl-6 px-0 px-xl-0 pr-xl-1">
|
||||
<div class="col-12 col-xl-6 px-0 px-xl-0 pe-xl-1">
|
||||
<b-card header="Strategy settings">
|
||||
<b-table
|
||||
small
|
||||
|
@ -17,7 +17,7 @@
|
|||
</b-table>
|
||||
</b-card>
|
||||
</div>
|
||||
<div class="col-12 col-xl-6 px-0 px-xl-0 pt-2 pt-xl-0 pl-xl-1">
|
||||
<div class="col-12 col-xl-6 px-0 px-xl-0 pt-2 pt-xl-0 ps-xl-1">
|
||||
<b-card header="Metrics">
|
||||
<b-table small borderless :items="backtestResultStats" :fields="backtestResultFields">
|
||||
</b-table>
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="mb-2">
|
||||
<label class="mr-auto h3">Balance</label>
|
||||
<b-button class="float-right" size="sm" @click="botStore.activeBot.getBalance"
|
||||
<label class="me-auto h3">Balance</label>
|
||||
<b-button class="float-end" size="sm" @click="botStore.activeBot.getBalance"
|
||||
>↻</b-button
|
||||
>
|
||||
<b-form-checkbox
|
||||
v-model="hideSmallBalances"
|
||||
class="float-right"
|
||||
class="float-end"
|
||||
size="sm"
|
||||
title="Hide small balances"
|
||||
button
|
||||
|
|
|
@ -3,28 +3,16 @@
|
|||
<b-alert
|
||||
v-for="(alert, index) in alertStore.activeMessages"
|
||||
:key="index"
|
||||
v-model="alert.timeout"
|
||||
variant="warning"
|
||||
dismissible
|
||||
:show="5"
|
||||
:value="!!alert.message"
|
||||
@dismissed="alertStore.removeAlert"
|
||||
@closed="alertStore.removeAlert(alert)"
|
||||
>{{ alert.message }}</b-alert
|
||||
>
|
||||
{{ alert.message }}
|
||||
</b-alert>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import { useAlertsStore } from '@/stores/alerts';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BotAlerts',
|
||||
setup() {
|
||||
const alertStore = useAlertsStore();
|
||||
return {
|
||||
alertStore,
|
||||
};
|
||||
},
|
||||
});
|
||||
const alertStore = useAlertsStore();
|
||||
</script>
|
||||
|
|
|
@ -2,7 +2,7 @@ forceexit
|
|||
<template>
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-secondary btn-sm ml-1"
|
||||
class="btn btn-secondary btn-sm ms-1"
|
||||
:disabled="!botStore.activeBot.isTrading || isRunning"
|
||||
title="Start Trading"
|
||||
@click="botStore.activeBot.startBot()"
|
||||
|
@ -10,7 +10,7 @@ forceexit
|
|||
<PlayIcon />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-secondary btn-sm ml-1"
|
||||
class="btn btn-secondary btn-sm ms-1"
|
||||
:disabled="!botStore.activeBot.isTrading || !isRunning"
|
||||
title="Stop Trading - Also stops handling open trades."
|
||||
@click="handleStopBot()"
|
||||
|
@ -18,7 +18,7 @@ forceexit
|
|||
<StopIcon />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-secondary btn-sm ml-1"
|
||||
class="btn btn-secondary btn-sm ms-1"
|
||||
:disabled="!botStore.activeBot.isTrading || !isRunning"
|
||||
title="StopBuy - Stops buying, but still handles open trades"
|
||||
@click="handleStopBuy()"
|
||||
|
@ -26,7 +26,7 @@ forceexit
|
|||
<PauseIcon />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-secondary btn-sm ml-1"
|
||||
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()"
|
||||
|
@ -34,7 +34,7 @@ forceexit
|
|||
<ReloadIcon />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-secondary btn-sm ml-1"
|
||||
class="btn btn-secondary btn-sm ms-1"
|
||||
:disabled="!botStore.activeBot.isTrading"
|
||||
title="Force exit all"
|
||||
@click="handleForceExit()"
|
||||
|
@ -47,23 +47,24 @@ forceexit
|
|||
(botStore.activeBot.botState.force_entry_enable ||
|
||||
botStore.activeBot.botState.forcebuy_enabled)
|
||||
"
|
||||
class="btn btn-secondary btn-sm ml-1"
|
||||
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="initiateForceenter"
|
||||
@click="forceEnter = true"
|
||||
>
|
||||
<ForceEntryIcon />
|
||||
</button>
|
||||
<button
|
||||
v-if="botStore.activeBot.isWebserverMode && false"
|
||||
:disabled="botStore.activeBot.isTrading"
|
||||
class="btn btn-secondary btn-sm ml-1"
|
||||
class="btn btn-secondary btn-sm ms-1"
|
||||
title="Start Trading mode"
|
||||
@click="botStore.activeBot.startTrade()"
|
||||
>
|
||||
<PlayIcon />
|
||||
</button>
|
||||
<ForceEntryForm :pair="botStore.activeBot.selectedPair" @close="hideForceenter" />
|
||||
<ForceEntryForm v-model="forceEnter" :pair="botStore.activeBot.selectedPair" />
|
||||
<MessageBox ref="msgBox" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -76,7 +77,8 @@ import ReloadIcon from 'vue-material-design-icons/Reload.vue';
|
|||
import ForceExitIcon from 'vue-material-design-icons/CloseBoxMultiple.vue';
|
||||
import ForceEntryIcon from 'vue-material-design-icons/PlusBoxMultipleOutline.vue';
|
||||
import ForceEntryForm from './ForceEntryForm.vue';
|
||||
import { defineComponent, computed, ref, getCurrentInstance } from 'vue';
|
||||
import MessageBox, { MsgBoxObject } from '@/components/general/MessageBox.vue';
|
||||
import { defineComponent, computed, ref } from 'vue';
|
||||
import { useBotStore } from '@/stores/ftbotwrapper';
|
||||
|
||||
export default defineComponent({
|
||||
|
@ -89,70 +91,74 @@ export default defineComponent({
|
|||
ReloadIcon,
|
||||
ForceExitIcon,
|
||||
ForceEntryIcon,
|
||||
MessageBox,
|
||||
},
|
||||
setup() {
|
||||
const root = getCurrentInstance();
|
||||
const botStore = useBotStore();
|
||||
const forcebuyShow = ref(false);
|
||||
const forceEnter = ref<boolean>(false);
|
||||
const msgBox = ref<typeof MessageBox>();
|
||||
|
||||
const isRunning = computed((): boolean => {
|
||||
return botStore.activeBot.botState?.state === 'running';
|
||||
});
|
||||
|
||||
const initiateForceenter = () => {
|
||||
root?.proxy.$bvModal.show('forceentry-modal');
|
||||
};
|
||||
const hideForceenter = () => {
|
||||
root?.proxy.$bvModal.hide('forceentry-modal');
|
||||
};
|
||||
|
||||
const handleStopBot = () => {
|
||||
root?.proxy.$bvModal.msgBoxConfirm('Stop Bot?').then((value: boolean) => {
|
||||
if (value) {
|
||||
const msg: MsgBoxObject = {
|
||||
title: 'Stop Bot',
|
||||
message: 'Stop the bot loop from running?',
|
||||
accept: () => {
|
||||
botStore.activeBot.stopBot();
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
msgBox.value?.show(msg);
|
||||
};
|
||||
|
||||
const handleStopBuy = () => {
|
||||
root?.proxy.$bvModal
|
||||
.msgBoxConfirm('Stop buying? Freqtrade will continue to handle open trades.')
|
||||
.then((value: boolean) => {
|
||||
if (value) {
|
||||
botStore.activeBot.stopBuy();
|
||||
}
|
||||
});
|
||||
const msg: MsgBoxObject = {
|
||||
title: 'Stop Buying',
|
||||
message: 'Freqtrade will continue to handle open trades.',
|
||||
accept: () => {
|
||||
botStore.activeBot.stopBuy();
|
||||
},
|
||||
};
|
||||
msgBox.value?.show(msg);
|
||||
};
|
||||
|
||||
const handleReloadConfig = () => {
|
||||
root?.proxy.$bvModal.msgBoxConfirm('Reload configuration?').then((value: boolean) => {
|
||||
if (value) {
|
||||
const msg: MsgBoxObject = {
|
||||
title: 'Reload',
|
||||
message: 'Reload configuration (including strategy)?',
|
||||
accept: () => {
|
||||
console.log('reload...');
|
||||
botStore.activeBot.reloadConfig();
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
msgBox.value?.show(msg);
|
||||
};
|
||||
|
||||
const handleForceExit = () => {
|
||||
root?.proxy.$bvModal.msgBoxConfirm(`Really forceexit ALL trades?`).then((value: boolean) => {
|
||||
if (value) {
|
||||
const msg: MsgBoxObject = {
|
||||
title: 'ForceExit all',
|
||||
message: 'Really forceexit ALL trades?',
|
||||
accept: () => {
|
||||
const payload: ForceSellPayload = {
|
||||
tradeid: 'all',
|
||||
// TODO: support ordertype (?)
|
||||
};
|
||||
botStore.activeBot.forceexit(payload);
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
msgBox.value?.show(msg);
|
||||
};
|
||||
return {
|
||||
initiateForceenter,
|
||||
hideForceenter,
|
||||
handleStopBot,
|
||||
handleStopBuy,
|
||||
handleReloadConfig,
|
||||
handleForceExit,
|
||||
forcebuyShow,
|
||||
forceEnter,
|
||||
botStore,
|
||||
isRunning,
|
||||
msgBox,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<div class="d-flex">
|
||||
<div
|
||||
class="px-1 d-flex flex-row flex-fill text-left justify-content-between align-items-center"
|
||||
class="px-1 d-flex flex-row flex-fill text-start justify-content-between align-items-center"
|
||||
>
|
||||
<span>
|
||||
<span class="mr-1 font-weight-bold">{{ trade.pair }}</span>
|
||||
<span class="me-1 fw-bold">{{ trade.pair }}</span>
|
||||
<small class="text-secondary">(#{{ trade.trade_id }})</small>
|
||||
</span>
|
||||
<small>
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="mb-2">
|
||||
<label class="mr-auto h3">Daily Stats</label>
|
||||
<b-button class="float-right" size="sm" @click="botStore.activeBot.getDaily"
|
||||
>↻</b-button
|
||||
>
|
||||
<label class="me-auto h3">Daily Stats</label>
|
||||
<b-button class="float-end" size="sm" @click="botStore.activeBot.getDaily">↻</b-button>
|
||||
</div>
|
||||
<div>
|
||||
<DailyChart
|
||||
|
|
|
@ -23,14 +23,14 @@
|
|||
<!-- Blacklsit -->
|
||||
<div>
|
||||
<label
|
||||
class="mr-auto h3"
|
||||
class="me-auto h3"
|
||||
title="Blacklist - Select (followed by a click on '-') to remove pairs"
|
||||
>Blacklist</label
|
||||
>
|
||||
<div class="float-right d-flex d-flex-columns pr-1">
|
||||
<div class="float-end d-flex d-flex-columns pe-1">
|
||||
<b-button
|
||||
id="blacklist-add-btn"
|
||||
class="mr-1"
|
||||
class="me-1"
|
||||
:class="botStore.activeBot.botApiVersion >= 1.12 ? 'col-6' : ''"
|
||||
size="sm"
|
||||
>+
|
||||
|
@ -49,7 +49,7 @@
|
|||
<b-popover
|
||||
title="Add to blacklist"
|
||||
target="blacklist-add-btn"
|
||||
triggers="click blur"
|
||||
triggers="click"
|
||||
:show.sync="blackListShow"
|
||||
>
|
||||
<form ref="form" @submit.prevent>
|
||||
|
@ -64,7 +64,7 @@
|
|||
</b-form-group>
|
||||
<b-button
|
||||
id="blacklist-submit"
|
||||
class="float-right mb-2"
|
||||
class="float-end mb-2"
|
||||
size="sm"
|
||||
type="submit"
|
||||
@click="addBlacklistPair"
|
||||
|
|
|
@ -1,130 +1,146 @@
|
|||
<template>
|
||||
<div>
|
||||
<b-modal
|
||||
id="forceentry-modal"
|
||||
ref="modal"
|
||||
title="Force entering a trade"
|
||||
@show="resetForm"
|
||||
@hidden="resetForm"
|
||||
@ok="handleEntry"
|
||||
>
|
||||
<form ref="form" @submit.stop.prevent="handleSubmit">
|
||||
<b-form-group
|
||||
v-if="botStore.activeBot.botApiVersion >= 2.13 && botStore.activeBot.shortAllowed"
|
||||
label="Order direction (Long or Short)"
|
||||
label-for="order-direction"
|
||||
invalid-feedback="Order direction must be empty or a positive number"
|
||||
>
|
||||
<b-form-radio-group
|
||||
id="order-direction"
|
||||
v-model="orderSide"
|
||||
:options="['long', 'short']"
|
||||
name="radios-btn-default"
|
||||
size="sm"
|
||||
buttons
|
||||
style="min-width: 10em"
|
||||
button-variant="outline-primary"
|
||||
></b-form-radio-group>
|
||||
</b-form-group>
|
||||
<b-form-group label="Pair" label-for="pair-input" invalid-feedback="Pair is required">
|
||||
<b-form-input
|
||||
id="pair-input"
|
||||
v-model="selectedPair"
|
||||
required
|
||||
@keydown.enter.native="handleEntry"
|
||||
@focus="inputSelect"
|
||||
></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group
|
||||
label="*Price [optional]"
|
||||
label-for="price-input"
|
||||
invalid-feedback="Price must be empty or a positive number"
|
||||
>
|
||||
<b-form-input
|
||||
id="price-input"
|
||||
v-model="price"
|
||||
type="number"
|
||||
step="0.00000001"
|
||||
@keydown.enter.native="handleEntry"
|
||||
></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group
|
||||
v-if="botStore.activeBot.botApiVersion > 1.12"
|
||||
:label="`*Stake-amount in ${botStore.activeBot.stakeCurrency} [optional]`"
|
||||
label-for="stake-input"
|
||||
invalid-feedback="Stake-amount must be empty or a positive number"
|
||||
>
|
||||
<b-form-input
|
||||
id="stake-input"
|
||||
v-model="stakeAmount"
|
||||
type="number"
|
||||
step="0.000001"
|
||||
@keydown.enter.native="handleEntry"
|
||||
></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group
|
||||
v-if="botStore.activeBot.botApiVersion > 2.16 && botStore.activeBot.shortAllowed"
|
||||
:label="`*Leverage to apply [optional]`"
|
||||
label-for="leverage-input"
|
||||
invalid-feedback="Leverage must be empty or a positive number"
|
||||
>
|
||||
<b-form-input
|
||||
id="leverage-input"
|
||||
v-model="leverage"
|
||||
type="number"
|
||||
step="0.01"
|
||||
@keydown.enter.native="handleEntry"
|
||||
></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group
|
||||
v-if="botStore.activeBot.botApiVersion > 1.1"
|
||||
label="*OrderType"
|
||||
label-for="ordertype-input"
|
||||
invalid-feedback="OrderType"
|
||||
>
|
||||
<b-form-radio-group
|
||||
id="ordertype-input"
|
||||
v-model="ordertype"
|
||||
:options="['market', 'limit']"
|
||||
name="radios-btn-default"
|
||||
buttons
|
||||
button-variant="outline-primary"
|
||||
style="min-width: 10em"
|
||||
size="sm"
|
||||
></b-form-radio-group>
|
||||
</b-form-group>
|
||||
</form>
|
||||
</b-modal>
|
||||
</div>
|
||||
<b-modal
|
||||
id="forceentry-modal"
|
||||
ref="modal"
|
||||
v-model="model"
|
||||
title="Force entering a trade"
|
||||
@show="resetForm"
|
||||
@hidden="resetForm"
|
||||
@ok="handleEntry"
|
||||
>
|
||||
<form ref="form" @submit.stop.prevent="handleSubmit">
|
||||
<b-form-group
|
||||
v-if="botStore.activeBot.botApiVersion >= 2.13 && botStore.activeBot.shortAllowed"
|
||||
label="Order direction (Long or Short)"
|
||||
label-for="order-direction"
|
||||
invalid-feedback="Order direction must be set"
|
||||
:state="orderSide !== undefined"
|
||||
>
|
||||
<b-form-radio-group
|
||||
id="order-direction"
|
||||
v-model="orderSide"
|
||||
:options="['long', 'short']"
|
||||
name="radios-btn-default"
|
||||
size="sm"
|
||||
buttons
|
||||
style="min-width: 10em"
|
||||
button-variant="outline-primary"
|
||||
></b-form-radio-group>
|
||||
</b-form-group>
|
||||
<b-form-group
|
||||
label="Pair"
|
||||
label-for="pair-input"
|
||||
invalid-feedback="Pair is required"
|
||||
:state="selectedPair !== undefined"
|
||||
>
|
||||
<b-form-input
|
||||
id="pair-input"
|
||||
v-model="selectedPair"
|
||||
required
|
||||
@keydown.enter.native="handleEntry"
|
||||
@focus="inputSelect"
|
||||
></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group
|
||||
label="*Price [optional]"
|
||||
label-for="price-input"
|
||||
invalid-feedback="Price must be empty or a positive number"
|
||||
:state="!price || price > 0"
|
||||
>
|
||||
<b-form-input
|
||||
id="price-input"
|
||||
v-model="price"
|
||||
type="number"
|
||||
step="0.00000001"
|
||||
@keydown.enter.native="handleEntry"
|
||||
></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group
|
||||
v-if="botStore.activeBot.botApiVersion > 1.12"
|
||||
:label="`*Stake-amount in ${botStore.activeBot.stakeCurrency} [optional]`"
|
||||
label-for="stake-input"
|
||||
invalid-feedback="Stake-amount must be empty or a positive number"
|
||||
:state="!stakeAmount || stakeAmount > 0"
|
||||
>
|
||||
<b-form-input
|
||||
id="stake-input"
|
||||
v-model="stakeAmount"
|
||||
type="number"
|
||||
step="0.000001"
|
||||
@keydown.enter.native="handleEntry"
|
||||
></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group
|
||||
v-if="botStore.activeBot.botApiVersion > 2.16 && botStore.activeBot.shortAllowed"
|
||||
:label="`*Leverage to apply [optional]`"
|
||||
label-for="leverage-input"
|
||||
invalid-feedback="Leverage must be empty or a positive number"
|
||||
:state="!leverage || leverage > 0"
|
||||
>
|
||||
<b-form-input
|
||||
id="leverage-input"
|
||||
v-model="leverage"
|
||||
type="number"
|
||||
step="0.01"
|
||||
@keydown.enter.native="handleEntry"
|
||||
></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group
|
||||
v-if="botStore.activeBot.botApiVersion > 1.1"
|
||||
label="*OrderType"
|
||||
label-for="ordertype-input"
|
||||
invalid-feedback="OrderType"
|
||||
:state="true"
|
||||
>
|
||||
<b-form-radio-group
|
||||
id="ordertype-input"
|
||||
v-model="ordertype"
|
||||
:options="['market', 'limit']"
|
||||
name="radios-btn-default"
|
||||
buttons
|
||||
button-variant="outline-primary"
|
||||
style="min-width: 10em"
|
||||
size="sm"
|
||||
></b-form-radio-group>
|
||||
</b-form-group>
|
||||
</form>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { useBotStore } from '@/stores/ftbotwrapper';
|
||||
import { ForceEnterPayload, OrderSides } from '@/types';
|
||||
|
||||
import { defineComponent, ref, nextTick, getCurrentInstance } from 'vue';
|
||||
import { computed, defineComponent, nextTick, ref } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ForceEntryForm',
|
||||
props: {
|
||||
pair: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
modelValue: { required: true, default: false, type: Boolean },
|
||||
pair: { type: String, default: '' },
|
||||
},
|
||||
setup(props) {
|
||||
const root = getCurrentInstance();
|
||||
emits: ['update:modelValue'],
|
||||
setup(props, { emit }) {
|
||||
const botStore = useBotStore();
|
||||
|
||||
const form = ref<HTMLFormElement>();
|
||||
const selectedPair = ref('');
|
||||
const price = ref<number | null>(null);
|
||||
const stakeAmount = ref<number | null>(null);
|
||||
const leverage = ref<number | null>(null);
|
||||
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 model = computed({
|
||||
get() {
|
||||
return props.modelValue;
|
||||
},
|
||||
set(value: boolean) {
|
||||
emit('update:modelValue', value);
|
||||
},
|
||||
});
|
||||
|
||||
const checkFormValidity = () => {
|
||||
const valid = form.value?.checkValidity();
|
||||
|
||||
|
@ -155,15 +171,14 @@ export default defineComponent({
|
|||
payload.leverage = leverage.value;
|
||||
}
|
||||
botStore.activeBot.forceentry(payload);
|
||||
|
||||
await nextTick();
|
||||
root?.proxy.$bvModal.hide('forceentry-modal');
|
||||
emit('update:modelValue', false);
|
||||
};
|
||||
const resetForm = () => {
|
||||
console.log('resetForm');
|
||||
selectedPair.value = props.pair;
|
||||
price.value = null;
|
||||
stakeAmount.value = null;
|
||||
price.value = undefined;
|
||||
stakeAmount.value = undefined;
|
||||
if (botStore.activeBot.botApiVersion > 1.1) {
|
||||
ordertype.value =
|
||||
botStore.activeBot.botState?.order_types?.forcebuy ||
|
||||
|
@ -174,9 +189,7 @@ export default defineComponent({
|
|||
}
|
||||
};
|
||||
|
||||
const handleEntry = (bvModalEvt) => {
|
||||
// Prevent modal from closing
|
||||
bvModalEvt.preventDefault();
|
||||
const handleEntry = () => {
|
||||
// Trigger submit handler
|
||||
handleSubmit();
|
||||
};
|
||||
|
@ -186,6 +199,7 @@ export default defineComponent({
|
|||
|
||||
return {
|
||||
handleSubmit,
|
||||
model,
|
||||
botStore,
|
||||
form,
|
||||
handleEntry,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div>
|
||||
<b-modal
|
||||
id="forceexit-modal"
|
||||
ref="modal"
|
||||
v-model="model"
|
||||
title="Force exiting a trade"
|
||||
@show="resetForm"
|
||||
@hidden="resetForm"
|
||||
|
@ -16,9 +16,10 @@
|
|||
</p>
|
||||
<b-form-group
|
||||
v-if="botStore.activeBot.botApiVersion > 1.12"
|
||||
:label="`*amount in ${trade.base_currency} [optional]`"
|
||||
:label="`*Amount in ${trade.base_currency} [optional]`"
|
||||
label-for="stake-input"
|
||||
invalid-feedback="Amount must be empty or a positive number"
|
||||
:state="amount !== undefined && amount > 0"
|
||||
>
|
||||
<b-form-input
|
||||
id="stake-input"
|
||||
|
@ -43,10 +44,11 @@
|
|||
label="*OrderType"
|
||||
label-for="ordertype-input"
|
||||
invalid-feedback="OrderType"
|
||||
:state="ordertype !== undefined"
|
||||
>
|
||||
<b-form-select
|
||||
v-model="ordertype"
|
||||
class="ml-2"
|
||||
class="ms-2"
|
||||
:options="['market', 'limit']"
|
||||
style="min-width: 7em"
|
||||
size="sm"
|
||||
|
@ -62,7 +64,7 @@
|
|||
import { useBotStore } from '@/stores/ftbotwrapper';
|
||||
import { ForceSellPayload, Trade } from '@/types';
|
||||
|
||||
import { defineComponent, ref, nextTick, getCurrentInstance } from 'vue';
|
||||
import { defineComponent, ref, computed } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ForceExitForm',
|
||||
|
@ -71,13 +73,14 @@ export default defineComponent({
|
|||
type: Object as () => Trade,
|
||||
required: true,
|
||||
},
|
||||
modelValue: { required: true, default: false, type: Boolean },
|
||||
},
|
||||
setup(props) {
|
||||
const root = getCurrentInstance();
|
||||
emits: ['update:modelValue'],
|
||||
setup(props, { emit }) {
|
||||
const botStore = useBotStore();
|
||||
|
||||
const form = ref<HTMLFormElement>();
|
||||
const amount = ref<number | null>(null);
|
||||
const amount = ref<number | undefined>(undefined);
|
||||
const ordertype = ref('limit');
|
||||
|
||||
const checkFormValidity = () => {
|
||||
|
@ -86,6 +89,15 @@ export default defineComponent({
|
|||
return valid;
|
||||
};
|
||||
|
||||
const model = computed({
|
||||
get() {
|
||||
return props.modelValue;
|
||||
},
|
||||
set(value: boolean) {
|
||||
emit('update:modelValue', value);
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = () => {
|
||||
// Exit when the form isn't valid
|
||||
if (!checkFormValidity()) {
|
||||
|
@ -101,9 +113,7 @@ export default defineComponent({
|
|||
payload.amount = amount.value;
|
||||
}
|
||||
botStore.activeBot.forceexit(payload);
|
||||
nextTick(() => {
|
||||
root?.proxy.$bvModal.hide('forceexit-modal');
|
||||
});
|
||||
model.value = false;
|
||||
};
|
||||
const resetForm = () => {
|
||||
amount.value = props.trade.amount;
|
||||
|
@ -115,9 +125,7 @@ export default defineComponent({
|
|||
}
|
||||
};
|
||||
|
||||
const handleEntry = (bvModalEvt) => {
|
||||
// Prevent modal from closing
|
||||
bvModalEvt.preventDefault();
|
||||
const handleEntry = () => {
|
||||
// Trigger submit handler
|
||||
handleSubmit();
|
||||
};
|
||||
|
@ -129,6 +137,7 @@ export default defineComponent({
|
|||
resetForm,
|
||||
amount,
|
||||
ordertype,
|
||||
model,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="mb-2">
|
||||
<label class="mr-auto h3">Pair Locks</label>
|
||||
<b-button class="float-right" size="sm" @click="botStore.activeBot.getLocks"
|
||||
>↻</b-button
|
||||
>
|
||||
<label class="me-auto h3">Pair Locks</label>
|
||||
<b-button class="float-end" size="sm" @click="botStore.activeBot.getLocks">↻</b-button>
|
||||
</div>
|
||||
<div>
|
||||
<b-table class="table-sm" :items="botStore.activeBot.activeLocks" :fields="tableFields">
|
||||
<template #cell(actions)="row">
|
||||
<b-button
|
||||
class="btn-xs ml-1"
|
||||
class="btn-xs ms-1"
|
||||
size="sm"
|
||||
title="Delete trade"
|
||||
@click="removePairLock(row.item)"
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<div class="d-flex flex-align-center ml-2">
|
||||
<div class="d-flex align-items-center ms-2">
|
||||
<b-form-checkbox
|
||||
v-model="autoRefreshLoc"
|
||||
class="ml-auto float-right my-auto"
|
||||
class="ms-auto float-end my-auto mt-1"
|
||||
title="AutoRefresh"
|
||||
></b-form-checkbox>
|
||||
<b-button
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
:options="botStore.activeBot.strategyList"
|
||||
>
|
||||
</b-form-select>
|
||||
<div class="ml-2">
|
||||
<div class="ms-2">
|
||||
<b-button @click="botStore.activeBot.getStrategyList">↻</b-button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -27,7 +27,7 @@ import { defineComponent, computed, onMounted } from 'vue';
|
|||
export default defineComponent({
|
||||
name: 'StrategySelect',
|
||||
props: {
|
||||
value: { type: String, required: true },
|
||||
modelValue: { type: String, required: true },
|
||||
showDetails: { default: false, required: false, type: Boolean },
|
||||
},
|
||||
emits: ['input'],
|
||||
|
@ -37,7 +37,7 @@ export default defineComponent({
|
|||
const strategyCode = computed((): string => botStore.activeBot.strategy?.code);
|
||||
const locStrategy = computed({
|
||||
get() {
|
||||
return props.value;
|
||||
return props.modelValue;
|
||||
},
|
||||
set(strategy: string) {
|
||||
botStore.activeBot.getStrategy(strategy);
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
></b-form-input>
|
||||
</b-input-group>
|
||||
</b-form-group>
|
||||
<b-form-group class="ml-2 col-md-6" label="End date" label-for="dp_dateTo">
|
||||
<b-form-group class="ms-2 col-md-6" label="End date" label-for="dp_dateTo">
|
||||
<b-input-group>
|
||||
<b-input-group-prepend>
|
||||
<b-form-datepicker v-model="dateTo" class="mb-1" button-only></b-form-datepicker>
|
||||
|
@ -30,7 +30,7 @@
|
|||
</b-input-group>
|
||||
</b-form-group>
|
||||
</div>
|
||||
<label class="text-left">
|
||||
<label class="text-start">
|
||||
Timerange: <b>{{ timeRange }}</b>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -45,7 +45,7 @@ const now = new Date();
|
|||
export default defineComponent({
|
||||
name: 'TimeRangeSelect',
|
||||
props: {
|
||||
value: { required: true, type: String },
|
||||
modelValue: { required: true, type: String },
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const dateFrom = ref<string>('');
|
||||
|
@ -63,7 +63,7 @@ export default defineComponent({
|
|||
};
|
||||
|
||||
const updateInput = () => {
|
||||
const tr = props.value.split('-');
|
||||
const tr = props.modelValue.split('-');
|
||||
if (tr[0]) {
|
||||
dateFrom.value = timestampToDateString(dateFromString(tr[0], 'yyyyMMdd'));
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ export default defineComponent({
|
|||
);
|
||||
|
||||
onMounted(() => {
|
||||
if (!props.value) {
|
||||
if (!props.modelValue) {
|
||||
dateFrom.value = timestampToDateString(new Date(now.getFullYear(), now.getMonth() - 1, 1));
|
||||
} else {
|
||||
updateInput();
|
||||
|
|
|
@ -2,48 +2,48 @@
|
|||
<div class="d-flex flex-column">
|
||||
<b-button
|
||||
v-if="botApiVersion <= 1.1"
|
||||
class="btn-xs text-left"
|
||||
class="btn-xs text-start"
|
||||
size="sm"
|
||||
title="Forceexit"
|
||||
@click="$emit('forceExit', trade)"
|
||||
>
|
||||
<ForceSellIcon :size="16" title="Forceexit" class="mr-1" />Forceexit
|
||||
<ForceSellIcon :size="16" title="Forceexit" class="me-1" />Forceexit
|
||||
</b-button>
|
||||
<b-button
|
||||
v-if="botApiVersion > 1.1"
|
||||
class="btn-xs text-left"
|
||||
class="btn-xs text-start"
|
||||
size="sm"
|
||||
title="Forceexit limit"
|
||||
@click="$emit('forceExit', trade, 'limit')"
|
||||
>
|
||||
<ForceSellIcon :size="16" title="Forceexit limit" class="mr-1" />Forceexit limit
|
||||
<ForceSellIcon :size="16" title="Forceexit limit" class="me-1" />Forceexit limit
|
||||
</b-button>
|
||||
<b-button
|
||||
v-if="botApiVersion > 1.1"
|
||||
class="btn-xs text-left mt-1"
|
||||
class="btn-xs text-start mt-1"
|
||||
size="sm"
|
||||
title="Forceexit market"
|
||||
@click="$emit('forceExit', trade, 'market')"
|
||||
>
|
||||
<ForceSellIcon :size="16" title="Forceexit market" class="mr-1" />Forceexit market
|
||||
<ForceSellIcon :size="16" title="Forceexit market" class="me-1" />Forceexit market
|
||||
</b-button>
|
||||
<b-button
|
||||
v-if="botApiVersion > 2.16"
|
||||
class="btn-xs text-left mt-1"
|
||||
class="btn-xs text-start mt-1"
|
||||
size="sm"
|
||||
title="Forceexit partial"
|
||||
@click="$emit('forceExitPartial', trade)"
|
||||
>
|
||||
<ForceSellIcon :size="16" title="Forceexit partial" class="mr-1" />Forceexit partial
|
||||
<ForceSellIcon :size="16" title="Forceexit partial" class="me-1" />Forceexit partial
|
||||
</b-button>
|
||||
|
||||
<b-button
|
||||
class="btn-xs text-left mt-1"
|
||||
class="btn-xs text-start mt-1"
|
||||
size="sm"
|
||||
title="Delete trade"
|
||||
@click="$emit('deleteTrade', trade)"
|
||||
>
|
||||
<DeleteIcon :size="16" title="Delete trade" class="mr-1" />
|
||||
<DeleteIcon :size="16" title="Delete trade" class="me-1" />
|
||||
Delete
|
||||
</b-button>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="container text-left">
|
||||
<div class="container text-start">
|
||||
<div class="row">
|
||||
<div class="col-lg-5">
|
||||
<h5 class="detail-header">General</h5>
|
||||
|
@ -25,7 +25,7 @@
|
|||
v-if="trade.profit_ratio && trade.profit_abs"
|
||||
:description="`${trade.is_open ? 'Current Profit' : 'Close Profit'}`"
|
||||
>
|
||||
<trade-profit class="ml-2" :trade="trade" />
|
||||
<trade-profit class="ms-2" :trade="trade" />
|
||||
</ValuePair>
|
||||
<details>
|
||||
<summary>Details</summary>
|
||||
|
@ -97,7 +97,7 @@
|
|||
:date="order.order_timestamp"
|
||||
show-timezone
|
||||
/>
|
||||
<b class="ml-1">{{ order.ft_order_side }}</b> for
|
||||
<b class="ms-1">{{ order.ft_order_side }}</b> for
|
||||
<b>{{ formatPrice(order.safe_price) }}</b> |
|
||||
<span v-if="order.remaining && order.remaining !== 0" title="remaining"
|
||||
>{{ formatPrice(order.remaining, 8) }} /
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
small
|
||||
hover
|
||||
stacked="md"
|
||||
:items="trades"
|
||||
:items="[...trades]"
|
||||
:fields="tableFields"
|
||||
show-empty
|
||||
:empty-text="emptyText"
|
||||
|
@ -29,7 +29,12 @@
|
|||
>
|
||||
<ActionIcon :size="16" title="Actions" />
|
||||
</b-button>
|
||||
<b-popover :target="`btn-actions_${row.index}`" triggers="focus" placement="left">
|
||||
<b-popover
|
||||
:target="`btn-actions_${row.index}`"
|
||||
:title="`Actions for ${row.item.pair}`"
|
||||
triggers="focus"
|
||||
placement="left"
|
||||
>
|
||||
<trade-actions
|
||||
:trade="row.item"
|
||||
:bot-api-version="botStore.activeBot.botApiVersion"
|
||||
|
@ -83,7 +88,15 @@
|
|||
style="width: unset"
|
||||
/>
|
||||
</div>
|
||||
<force-exit-form v-if="activeTrades" :trade="feTrade" />
|
||||
<force-exit-form v-if="activeTrades" v-model="forceExitVisible" :trade="feTrade" />
|
||||
<b-modal
|
||||
ref="removeTradeModal"
|
||||
v-model="removeTradeVisible"
|
||||
title="Exit trade"
|
||||
@ok="forceExitExecuter"
|
||||
>
|
||||
{{ confirmExitText }}
|
||||
</b-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -97,7 +110,7 @@ import TradeProfit from './TradeProfit.vue';
|
|||
import TradeActions from './TradeActions.vue';
|
||||
import ForceExitForm from '@/components/ftbot/ForceExitForm.vue';
|
||||
|
||||
import { defineComponent, ref, computed, watch, getCurrentInstance, nextTick } from 'vue';
|
||||
import { defineComponent, ref, computed, watch } from 'vue';
|
||||
import { useBotStore } from '@/stores/ftbotwrapper';
|
||||
|
||||
export default defineComponent({
|
||||
|
@ -113,7 +126,6 @@ export default defineComponent({
|
|||
emptyText: { default: 'No Trades to show.', type: String },
|
||||
},
|
||||
setup(props) {
|
||||
const root = getCurrentInstance();
|
||||
const botStore = useBotStore();
|
||||
const currentPage = ref(1);
|
||||
const selectedItemIndex = ref();
|
||||
|
@ -121,6 +133,10 @@ export default defineComponent({
|
|||
const feTrade = ref<Trade>({} as Trade);
|
||||
const perPage = props.activeTrades ? 200 : 15;
|
||||
const tradesTable = ref<HTMLFormElement>();
|
||||
const forceExitVisible = ref(false);
|
||||
const removeTradeVisible = ref(false);
|
||||
const confirmExitText = ref('');
|
||||
const removeTradeModal = ref<HTMLElement>();
|
||||
|
||||
const openFields: Record<string, string | Function>[] = [{ key: 'actions' }];
|
||||
const closedFields: Record<string, string | Function>[] = [
|
||||
|
@ -164,35 +180,51 @@ export default defineComponent({
|
|||
{ key: 'open_timestamp', label: 'Open date' },
|
||||
...(props.activeTrades ? openFields : closedFields),
|
||||
];
|
||||
|
||||
const feOrderType = ref<string | undefined>(undefined);
|
||||
const forceExitHandler = (item: Trade, ordertype: string | undefined = undefined) => {
|
||||
root?.proxy.$bvModal
|
||||
.msgBoxConfirm(
|
||||
`Really exit trade ${item.trade_id} (Pair ${item.pair}) using ${ordertype} Order?`,
|
||||
)
|
||||
.then((value: boolean) => {
|
||||
if (value) {
|
||||
const payload: MultiForcesellPayload = {
|
||||
tradeid: String(item.trade_id),
|
||||
botId: item.botId,
|
||||
};
|
||||
if (ordertype) {
|
||||
payload.ordertype = ordertype;
|
||||
}
|
||||
botStore
|
||||
.forceSellMulti(payload)
|
||||
.then((xxx) => console.log(xxx))
|
||||
.catch((error) => console.log(error.response));
|
||||
}
|
||||
});
|
||||
feTrade.value = item;
|
||||
confirmExitText.value = `Really exit trade ${item.trade_id} (Pair ${item.pair}) using ${ordertype} Order?`;
|
||||
removeTradeVisible.value = true;
|
||||
feOrderType.value = ordertype;
|
||||
};
|
||||
|
||||
const forceExitExecuter = () => {
|
||||
// TODO: this should be done properly.
|
||||
if (confirmExitText.value.includes('delete')) {
|
||||
const payload: MultiDeletePayload = {
|
||||
tradeid: String(feTrade.value.trade_id),
|
||||
botId: feTrade.value.botId,
|
||||
};
|
||||
botStore.deleteTradeMulti(payload).catch((error) => console.log(error.response));
|
||||
removeTradeVisible.value = false;
|
||||
return;
|
||||
}
|
||||
console.log(confirmExitText.value);
|
||||
|
||||
const payload: MultiForcesellPayload = {
|
||||
tradeid: String(feTrade.value.trade_id),
|
||||
botId: feTrade.value.botId,
|
||||
};
|
||||
if (feOrderType.value) {
|
||||
payload.ordertype = feOrderType.value;
|
||||
}
|
||||
botStore
|
||||
.forceSellMulti(payload)
|
||||
.then((xxx) => console.log(xxx))
|
||||
.catch((error) => console.log(error.response));
|
||||
feOrderType.value = undefined;
|
||||
removeTradeVisible.value = false;
|
||||
};
|
||||
|
||||
const removeTradeHandler = (item: Trade) => {
|
||||
confirmExitText.value = `Really delete trade ${item.trade_id} (Pair ${item.pair})?`;
|
||||
feTrade.value = item;
|
||||
removeTradeVisible.value = true;
|
||||
};
|
||||
|
||||
const forceExitPartialHandler = (item: Trade) => {
|
||||
feTrade.value = item;
|
||||
nextTick(() => {
|
||||
console.log('showing modal', item);
|
||||
root?.proxy.$bvModal.show('forceexit-modal');
|
||||
});
|
||||
forceExitVisible.value = true;
|
||||
};
|
||||
|
||||
const handleContextMenuEvent = (item, index, event) => {
|
||||
|
@ -205,31 +237,11 @@ export default defineComponent({
|
|||
console.log(item);
|
||||
};
|
||||
|
||||
const removeTradeHandler = (item) => {
|
||||
console.log(item);
|
||||
root?.proxy.$bvModal
|
||||
.msgBoxConfirm(`Really delete trade ${item.trade_id} (Pair ${item.pair})?`)
|
||||
.then((value: boolean) => {
|
||||
if (value) {
|
||||
const payload: MultiDeletePayload = {
|
||||
tradeid: String(item.trade_id),
|
||||
botId: item.botId,
|
||||
};
|
||||
botStore.deleteTradeMulti(payload).catch((error) => console.log(error.response));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onRowClicked = (item, index) => {
|
||||
const onRowClicked = (item) => {
|
||||
// Only allow single selection mode!
|
||||
if (
|
||||
item &&
|
||||
item.trade_id !== botStore.activeBot.detailTradeId &&
|
||||
!tradesTable.value?.isRowSelected(index)
|
||||
) {
|
||||
if (item && item.trade_id !== botStore.activeBot.detailTradeId) {
|
||||
botStore.activeBot.setDetailTrade(item);
|
||||
} else {
|
||||
console.log('unsetting item');
|
||||
botStore.activeBot.setDetailTrade(null);
|
||||
}
|
||||
};
|
||||
|
@ -275,6 +287,11 @@ export default defineComponent({
|
|||
onRowClicked,
|
||||
onRowSelected,
|
||||
feTrade,
|
||||
forceExitVisible,
|
||||
removeTradeVisible,
|
||||
confirmExitText,
|
||||
removeTradeModal,
|
||||
forceExitExecuter,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
42
src/components/general/MessageBox.vue
Normal file
42
src/components/general/MessageBox.vue
Normal file
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<b-modal
|
||||
ref="removeTradeModal"
|
||||
v-model="showRef"
|
||||
:title="title"
|
||||
@ok="msgBoxOK"
|
||||
@cancel="showRef = false"
|
||||
>
|
||||
{{ message }}
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
export interface MsgBoxObject {
|
||||
title: string;
|
||||
message: string;
|
||||
accept: () => void;
|
||||
}
|
||||
const showRef = ref<boolean>(false);
|
||||
const title = ref<string>('');
|
||||
const message = ref<string>('');
|
||||
const accept = ref<() => void>(() => {
|
||||
console.warn('Accepted not set.');
|
||||
});
|
||||
|
||||
const msgBoxOK = () => {
|
||||
accept.value();
|
||||
};
|
||||
|
||||
const show = (msg: MsgBoxObject) => {
|
||||
title.value = msg.title;
|
||||
message.value = msg.message;
|
||||
showRef.value = true;
|
||||
accept.value = msg.accept;
|
||||
};
|
||||
|
||||
defineExpose({ show });
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -1,12 +1,8 @@
|
|||
import ProfitPill from './ProfitPill.vue';
|
||||
import { createLocalVue } from '@vue/test-utils';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
|
||||
describe('ProfitPill.vue', () => {
|
||||
it('Shows a Green pill with positive profits', () => {
|
||||
cy.mount(ProfitPill, {
|
||||
localVue,
|
||||
propsData: {
|
||||
profitRatio: 0.051,
|
||||
profitAbs: 0.1,
|
||||
|
@ -21,7 +17,6 @@ describe('ProfitPill.vue', () => {
|
|||
});
|
||||
it('Shows a Red pill with positive profits', () => {
|
||||
cy.mount(ProfitPill, {
|
||||
localVue,
|
||||
propsData: {
|
||||
profitRatio: -0.1,
|
||||
profitAbs: -0.1,
|
||||
|
@ -38,7 +33,6 @@ describe('ProfitPill.vue', () => {
|
|||
});
|
||||
it('Shows a pill with 0.0 profits.', () => {
|
||||
cy.mount(ProfitPill, {
|
||||
localVue,
|
||||
propsData: {
|
||||
profitRatio: 0.0,
|
||||
profitAbs: 0.0,
|
||||
|
@ -54,7 +48,6 @@ describe('ProfitPill.vue', () => {
|
|||
});
|
||||
it('Shows a pill without relative profits.', () => {
|
||||
cy.mount(ProfitPill, {
|
||||
localVue,
|
||||
propsData: {
|
||||
profitRatio: undefined,
|
||||
profitAbs: 223,
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
<template>
|
||||
<div
|
||||
class="d-flex justify-content-between align-items-center profit-pill pl-2 pr-1"
|
||||
class="d-flex justify-content-between align-items-center profit-pill ps-2 pe-1"
|
||||
:class="isProfitable ? 'profit-pill-profit' : ''"
|
||||
:title="profitDesc"
|
||||
>
|
||||
<profit-symbol :profit="profitRatio || profitAbs" />
|
||||
<profit-symbol :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="ml-1"
|
||||
class="ms-1"
|
||||
:class="profitRatio ? 'small' : ''"
|
||||
:title="stakeCurrency"
|
||||
>{{ profitString }}</span
|
||||
|
|
|
@ -1,17 +1,14 @@
|
|||
import ProfitSymbol from '@/components/general/ProfitSymbol.vue';
|
||||
import { createLocalVue } from '@vue/test-utils';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
|
||||
describe('ProfitSymbol.vue', () => {
|
||||
it('calculates isProfitable with negative profit', () => {
|
||||
cy.mount(ProfitSymbol, { localVue, propsData: { profit: -0.5 } });
|
||||
cy.mount(ProfitSymbol, { propsData: { profit: -0.5 } });
|
||||
|
||||
cy.get('div').should('have.class', 'triangle-down');
|
||||
cy.get('div').should('not.have.class', 'triangle-up');
|
||||
});
|
||||
it('calculates isProfitable with positive profit', () => {
|
||||
cy.mount(ProfitSymbol, { localVue, propsData: { profit: 0.5 } });
|
||||
cy.mount(ProfitSymbol, { propsData: { profit: 0.5 } });
|
||||
cy.get('div').should('have.class', 'triangle-up');
|
||||
cy.get('div').should('not.have.class', 'triangle-down');
|
||||
});
|
||||
|
|
|
@ -19,7 +19,7 @@ export default defineComponent({
|
|||
},
|
||||
classLabel: {
|
||||
type: String,
|
||||
default: 'col-4 font-weight-bold mb-0',
|
||||
default: 'col-4 fw-bold mb-0',
|
||||
},
|
||||
classValue: {
|
||||
type: String,
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<template>
|
||||
<header>
|
||||
<b-navbar toggleable="sm" type="dark" variant="primary">
|
||||
<b-navbar toggleable="sm" dark variant="primary">
|
||||
<router-link class="navbar-brand" exact to="/">
|
||||
<img class="logo" src="@/assets/freqtrade-logo.png" alt="Home Logo" />
|
||||
<span class="navbar-brand-title d-sm-none d-md-inline">Freqtrade UI</span>
|
||||
</router-link>
|
||||
|
||||
<!-- TODO: For XS breakpoint, this should be here... -->
|
||||
<!-- <ReloadControl class="mr-3" /> -->
|
||||
<!-- <ReloadControl class="me-3" /> -->
|
||||
<b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
|
||||
|
||||
<b-collapse id="nav-collapse" class="text-right text-md-center" is-nav>
|
||||
|
@ -27,7 +27,7 @@
|
|||
</b-navbar-nav>
|
||||
|
||||
<!-- Right aligned nav items -->
|
||||
<b-navbar-nav class="ml-auto" menu-class="w-100">
|
||||
<b-navbar-nav class="ms-auto" menu-class="w-100">
|
||||
<!-- TODO This should show outside of the dropdown in XS mode -->
|
||||
<div class="d-flex justify-content-between">
|
||||
<b-dropdown
|
||||
|
@ -44,10 +44,12 @@
|
|||
</template>
|
||||
<BotList :small="true" />
|
||||
</b-dropdown>
|
||||
<ReloadControl class="mr-3" />
|
||||
<ReloadControl class="me-3" />
|
||||
</div>
|
||||
<li class="d-none d-sm-block nav-item text-secondary mr-2">
|
||||
<b-nav-text class="verticalCenter small mr-2">
|
||||
<li
|
||||
class="d-none d-sm-flex flex-sm-wrap flex-lg-nowrap align-items-center nav-item text-secondary me-2"
|
||||
>
|
||||
<b-nav-text class="verticalCenter small me-2">
|
||||
{{
|
||||
(botStore.activeBotorUndefined && botStore.activeBotorUndefined.botName) ||
|
||||
'No bot selected'
|
||||
|
@ -67,11 +69,11 @@
|
|||
<template #button-content>
|
||||
<b-avatar size="2em" button>FT</b-avatar>
|
||||
</template>
|
||||
<b-dropdown-item>V: {{ settingsStore.uiVersion }}</b-dropdown-item>
|
||||
<span class="ps-3">V: {{ settingsStore.uiVersion }}</span>
|
||||
<router-link class="dropdown-item" to="/settings">Settings</router-link>
|
||||
<b-form-checkbox v-model="layoutStore.layoutLocked" class="pl-5"
|
||||
>Lock layout</b-form-checkbox
|
||||
>
|
||||
<div class="ps-3">
|
||||
<b-form-checkbox v-model="layoutStore.layoutLocked">Lock layout</b-form-checkbox>
|
||||
</div>
|
||||
<b-dropdown-item @click="resetDynamicLayout">Reset Layout</b-dropdown-item>
|
||||
<router-link
|
||||
v-if="botStore.botCount === 1"
|
||||
|
@ -83,9 +85,9 @@
|
|||
</b-nav-item-dropdown>
|
||||
<div class="d-block d-sm-none">
|
||||
<!-- Visible only on XS -->
|
||||
<li class="nav-item text-secondary ml-2 d-sm-none d-flex justify-content-between">
|
||||
<li class="nav-item text-secondary ms-2 d-sm-none d-flex justify-content-between">
|
||||
<div class="d-flex">
|
||||
<b-nav-text class="verticalCenter small mr-2">
|
||||
<b-nav-text class="verticalCenter small me-2">
|
||||
{{
|
||||
(botStore.activeBotorUndefined && botStore.activeBotorUndefined.botName) ||
|
||||
'No bot selected'
|
||||
|
@ -112,7 +114,7 @@
|
|||
</li>
|
||||
<li v-else>
|
||||
<!-- should open Modal window! -->
|
||||
<LoginModal />
|
||||
<LoginModal v-if="route?.path !== '/login'" />
|
||||
</li>
|
||||
</b-navbar-nav>
|
||||
</b-collapse>
|
||||
|
@ -128,10 +130,10 @@ import ReloadControl from '@/components/ftbot/ReloadControl.vue';
|
|||
import BotEntry from '@/components/BotEntry.vue';
|
||||
import BotList from '@/components/BotList.vue';
|
||||
import { defineComponent, ref, onBeforeUnmount, onMounted, watch } from 'vue';
|
||||
import { useRoute } from '@/composables/router-helper';
|
||||
import { OpenTradeVizOptions, useSettingsStore } from '@/stores/settings';
|
||||
import { useLayoutStore } from '@/stores/layout';
|
||||
import { useBotStore } from '@/stores/ftbotwrapper';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NavBar',
|
||||
|
@ -199,7 +201,6 @@ export default defineComponent({
|
|||
onMounted(async () => {
|
||||
await settingsStore.loadUIVersion();
|
||||
pingInterval.value = window.setInterval(botStore.pingAll, 60000);
|
||||
botStore.allRefreshFull();
|
||||
});
|
||||
|
||||
settingsStore.$subscribe((_, state) => {
|
||||
|
@ -234,6 +235,7 @@ export default defineComponent({
|
|||
layoutStore,
|
||||
botStore,
|
||||
settingsStore,
|
||||
route,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@ -253,17 +255,11 @@ export default defineComponent({
|
|||
padding-left: 0.5em;
|
||||
}
|
||||
.navbar {
|
||||
padding: 0.2rem 1rem;
|
||||
padding: 0.2rem 0rem;
|
||||
}
|
||||
|
||||
.router-link-active,
|
||||
.nav-link:active {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.verticalCenter {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<footer class="d-md-none">
|
||||
<!-- Only visible on xs (phone) viewport! -->
|
||||
<hr class="my-0" />
|
||||
<div class="d-flex flex-align-center justify-content-center">
|
||||
<div class="d-flex flex-align-center justify-content-between px-2">
|
||||
<router-link v-if="!botStore.canRunBacktest" class="nav-link navbar-nav" to="/open_trades">
|
||||
<OpenTradesIcon />
|
||||
Trades
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
// TODO: This helper can be removed once
|
||||
// vue-router either releases a new version, or we update to vue3.
|
||||
import { effectScope, getCurrentInstance, reactive } from 'vue';
|
||||
|
||||
import { Route } from 'vue-router';
|
||||
|
||||
let currentRoute: Route;
|
||||
|
||||
function assign(target: Record<string, any>, source: Record<string, any>) {
|
||||
for (const key of Object.keys(source)) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
export function useRoute(): Route {
|
||||
const inst = getCurrentInstance();
|
||||
if (!inst) {
|
||||
return undefined as any;
|
||||
}
|
||||
if (!currentRoute) {
|
||||
const scope = effectScope(true);
|
||||
scope.run(() => {
|
||||
const { $router } = inst.proxy;
|
||||
currentRoute = reactive(assign({}, $router.currentRoute)) as any;
|
||||
$router.afterEach((to) => {
|
||||
assign(currentRoute, to);
|
||||
});
|
||||
});
|
||||
}
|
||||
return currentRoute;
|
||||
}
|
||||
export function useRouter() {
|
||||
const inst = getCurrentInstance();
|
||||
if (!inst) {
|
||||
throw new Error('No current instance found');
|
||||
}
|
||||
const { proxy } = inst;
|
||||
return proxy.$router;
|
||||
}
|
23
src/main.ts
23
src/main.ts
|
@ -1,20 +1,21 @@
|
|||
import Vue from 'vue';
|
||||
import './plugins/bootstrap-vue';
|
||||
import { createApp } from 'vue';
|
||||
import { BootstrapVue3 } from './plugins/bootstrap-vue';
|
||||
import App from './App.vue';
|
||||
import router from './router';
|
||||
import { createPinia, PiniaVuePlugin } from 'pinia';
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
|
||||
import VueRouter from 'vue-router';
|
||||
import GridLayout from 'vue3-drr-grid-layout';
|
||||
|
||||
Vue.use(PiniaVuePlugin);
|
||||
const myApp = createApp(App);
|
||||
|
||||
myApp.use(PiniaVuePlugin);
|
||||
const pinia = createPinia();
|
||||
pinia.use(piniaPluginPersistedstate);
|
||||
myApp.use(pinia);
|
||||
|
||||
Vue.use(VueRouter);
|
||||
myApp.use(router);
|
||||
myApp.use(BootstrapVue3);
|
||||
myApp.use(GridLayout);
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
new Vue({
|
||||
router,
|
||||
render: (h) => h(App),
|
||||
pinia,
|
||||
}).$mount('#app');
|
||||
// Vue.config.productionTip = false;
|
||||
myApp.mount('#app');
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
import BootstrapVue from 'bootstrap-vue';
|
||||
import BootstrapVue3 from 'bootstrap-vue-3';
|
||||
import 'bootstrap/dist/css/bootstrap.css';
|
||||
import 'bootstrap-vue-3/dist/bootstrap-vue-3.css';
|
||||
|
||||
import '@/styles/main.scss';
|
||||
|
||||
Vue.use(BootstrapVue);
|
||||
export { BootstrapVue3 };
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import VueRouter, { RouteConfig } from 'vue-router';
|
||||
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
|
||||
import { initBots, useBotStore } from '@/stores/ftbotwrapper';
|
||||
|
||||
const routes: Array<RouteConfig> = [
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
|
@ -68,15 +68,14 @@ const routes: Array<RouteConfig> = [
|
|||
},
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
path: '/(.*)*',
|
||||
name: '404',
|
||||
component: () => import('@/views/Error404.vue'),
|
||||
},
|
||||
];
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: 'history',
|
||||
base: import.meta.env.BASE_URL,
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes,
|
||||
});
|
||||
|
||||
|
|
|
@ -8,11 +8,13 @@ export function binData(data: number[], bins: number) {
|
|||
Math.round((minimum + i * binSize) * 1000) / 1000,
|
||||
0,
|
||||
]);
|
||||
|
||||
// console.log(baseBins);
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const index = Math.min(Math.floor((data[i] - minimum) / binSize), bins - 1);
|
||||
// console.log(data[i], index)
|
||||
baseBins[index][1]++;
|
||||
if (!isNaN(index)) {
|
||||
baseBins[index][1]++;
|
||||
}
|
||||
}
|
||||
|
||||
return baseBins;
|
||||
|
|
|
@ -9,13 +9,14 @@ export const useAlertsStore = defineStore('alerts', {
|
|||
addAlert(message: AlertType) {
|
||||
this.activeMessages.push(message);
|
||||
},
|
||||
removeAlert() {
|
||||
this.activeMessages.shift();
|
||||
removeAlert(alert: AlertType) {
|
||||
console.log('dismissed');
|
||||
this.activeMessages = this.activeMessages.filter((v) => v !== alert);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export function showAlert(message: string, severity = '') {
|
||||
const alertsStore = useAlertsStore();
|
||||
alertsStore.addAlert({ message, severity });
|
||||
alertsStore.addAlert({ message, severity, timeout: 5 });
|
||||
}
|
||||
|
|
|
@ -115,6 +115,7 @@ export function createBotSubStore(botId: string, botName: string) {
|
|||
);
|
||||
},
|
||||
tradeDetail: (state) => {
|
||||
// console.log('tradeDetail', state.openTrades.length, state.openTrades);
|
||||
let dTrade = state.openTrades.find((item) => item.trade_id === state.detailTradeId);
|
||||
if (!dTrade) {
|
||||
dTrade = state.trades.find((item) => item.trade_id === state.detailTradeId);
|
||||
|
|
|
@ -334,4 +334,5 @@ export function initBots() {
|
|||
});
|
||||
botStore.selectFirstBot();
|
||||
botStore.startRefresh();
|
||||
botStore.allRefreshFull();
|
||||
}
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
import { GridItemData } from '@/types';
|
||||
import { defineStore } from 'pinia';
|
||||
import { GridItemData } from 'vue-grid-layout';
|
||||
|
||||
export enum TradeLayout {
|
||||
multiPane = 'g-multiPane',
|
||||
openTrades = 'g-openTrades',
|
||||
tradeHistory = 'g-tradeHistory',
|
||||
tradeDetail = 'g-tradeDetail',
|
||||
chartView = 'g-chartView',
|
||||
multiPane = 0,
|
||||
openTrades = 1,
|
||||
tradeHistory = 2,
|
||||
tradeDetail = 3,
|
||||
chartView = 4,
|
||||
}
|
||||
|
||||
export enum DashboardLayout {
|
||||
dailyChart = 'g-dailyChart',
|
||||
botComparison = 'g-botComparison',
|
||||
allOpenTrades = 'g-allOpenTrades',
|
||||
cumChartChart = 'g-cumChartChart',
|
||||
allClosedTrades = 'g-allClosedTrades',
|
||||
profitDistributionChart = 'g-profitDistributionChart',
|
||||
tradesLogChart = 'g-TradesLogChart',
|
||||
dailyChart = 0,
|
||||
botComparison = 1,
|
||||
allOpenTrades = 2,
|
||||
cumChartChart = 3,
|
||||
allClosedTrades = 4,
|
||||
profitDistributionChart = 5,
|
||||
tradesLogChart = 6,
|
||||
}
|
||||
|
||||
// Define default layouts
|
||||
|
@ -88,7 +88,7 @@ migrateLayoutSettings();
|
|||
* @param gridLayout Array of grid layouts used in this layout. Must be passed to GridLayout, too.
|
||||
* @param name Name within the dashboard layout to find
|
||||
*/
|
||||
export function findGridLayout(gridLayout: GridItemData[], name: string): GridItemData {
|
||||
export function findGridLayout(gridLayout: GridItemData[], name: number): GridItemData {
|
||||
let layout = gridLayout.find((value) => value.i === name);
|
||||
if (!layout) {
|
||||
layout = { i: name, x: 0, y: 0, w: 4, h: 6 };
|
||||
|
@ -119,14 +119,24 @@ export const useLayoutStore = defineStore('layoutStore', {
|
|||
persist: {
|
||||
key: STORE_LAYOUTS,
|
||||
afterRestore: (context) => {
|
||||
console.log('after restore - ', context.store);
|
||||
if (
|
||||
context.store.dashboardLayout === null ||
|
||||
typeof context.store.dashboardLayout === 'string'
|
||||
typeof context.store.dashboardLayout === 'string' ||
|
||||
context.store.dashboardLayout.length === 0 ||
|
||||
typeof context.store.dashboardLayout[0]['i'] === 'string' ||
|
||||
context.store.dashboardLayout.length < DEFAULT_DASHBOARD_LAYOUT.length
|
||||
) {
|
||||
console.log('loading dashboard Layout from default.');
|
||||
context.store.dashboardLayout = JSON.parse(JSON.stringify(DEFAULT_DASHBOARD_LAYOUT));
|
||||
}
|
||||
if (context.store.tradingLayout === null || typeof context.store.tradingLayout === 'string') {
|
||||
if (
|
||||
context.store.tradingLayout === null ||
|
||||
typeof context.store.tradingLayout === 'string' ||
|
||||
context.store.tradingLayout.length === 0 ||
|
||||
typeof context.store.tradingLayout[0]['i'] === 'string' ||
|
||||
context.store.tradingLayout.length < DEFAULT_TRADING_LAYOUT.length
|
||||
) {
|
||||
console.log('loading trading Layout from default.');
|
||||
context.store.tradingLayout = JSON.parse(JSON.stringify(DEFAULT_TRADING_LAYOUT));
|
||||
}
|
||||
},
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
.text-profit {
|
||||
color: $color-profit;
|
||||
}
|
||||
|
||||
.text-loss {
|
||||
color: $color-loss;
|
||||
}
|
||||
|
@ -15,46 +16,62 @@
|
|||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.modal.show {
|
||||
// TODO: modal bootstrap fix (???)
|
||||
display: block;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #ffffff
|
||||
}
|
||||
|
||||
.text-bg-primary {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
$bg-dark: rgb(18, 18, 18);
|
||||
|
||||
$bg-darker: darken($bg-dark, 5%);
|
||||
$fg-color: #dedede;
|
||||
|
||||
background-color: darken($bg-dark, 5%) ;
|
||||
background-color: darken($bg-dark, 5%);
|
||||
color: $fg-color !important;
|
||||
|
||||
body {
|
||||
background: $bg-darker;
|
||||
color: $fg-color;
|
||||
}
|
||||
|
||||
.card {
|
||||
border-color: lighten($bg-dark, 10%);
|
||||
background-color: $bg-dark;
|
||||
|
||||
}
|
||||
|
||||
.card-body {
|
||||
background: $bg-dark;
|
||||
color: $fg-color;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background: lighten($bg-dark, 5%);
|
||||
color: $fg-color;
|
||||
}
|
||||
.table {
|
||||
color: $fg-color;
|
||||
}
|
||||
|
||||
.list-group-item {
|
||||
color: $fg-color;
|
||||
background: $bg-dark;
|
||||
}
|
||||
|
||||
// .custom-select {
|
||||
// color: $fg-color;
|
||||
// // background: $bg-dark;
|
||||
// }
|
||||
.nav-tabs {
|
||||
border-bottom: 1px solid lighten($bg-dark, 20%);
|
||||
.nav-link
|
||||
{
|
||||
|
||||
.nav-link {
|
||||
color: $primary;
|
||||
|
||||
&:hover {
|
||||
|
@ -63,26 +80,32 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-link.active {
|
||||
color: $fg-color;
|
||||
background: $bg-darker;
|
||||
border-color: lighten($bg-dark, 20%);
|
||||
border-color: lighten($bg-dark, 20%);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
color: $fg-color;
|
||||
background: $bg-dark;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
color: $fg-color;
|
||||
background: $bg-dark;
|
||||
}
|
||||
|
||||
.popover {
|
||||
background: $bg-dark;
|
||||
border-color: lighten($bg-dark, 20%);
|
||||
border-color: lighten($bg-dark, 20%);
|
||||
}
|
||||
|
||||
.popover-header {
|
||||
background: $bg-darker;
|
||||
}
|
||||
|
||||
.popover-body {
|
||||
color: $fg-color;
|
||||
}
|
||||
|
@ -90,26 +113,31 @@
|
|||
.logo-svg {
|
||||
background-color: $fg-color;
|
||||
}
|
||||
|
||||
textarea {
|
||||
color: $fg-color;
|
||||
background: $bg-dark;
|
||||
}
|
||||
|
||||
.text-dark {
|
||||
color: $fg-color !important;
|
||||
}
|
||||
.dropdown-menu
|
||||
{
|
||||
|
||||
.dropdown-menu {
|
||||
background-color: lighten($bg-dark, 5%);
|
||||
color: $fg-color;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
color: $fg-color;
|
||||
}
|
||||
|
||||
// Styles for searchable select
|
||||
.vs__dropdown-toggle{
|
||||
.vs__dropdown-toggle {
|
||||
border-color: lighten($bg-dark, 20%);
|
||||
// border: 1px solid $fg-color;
|
||||
}
|
||||
|
||||
.style-chooser .vs__search::placeholder,
|
||||
.style-chooser .vs__dropdown-toggle,
|
||||
.style-chooser .vs__dropdown-menu,
|
||||
|
@ -118,47 +146,69 @@
|
|||
color: $fg-color;
|
||||
}
|
||||
|
||||
.vs__search, .vs__dropdown-option {
|
||||
.vs__search,
|
||||
.vs__dropdown-option {
|
||||
color: $fg-color;
|
||||
}
|
||||
|
||||
.vs__open-indicator {
|
||||
fill: darken($fg-color, 10%);
|
||||
}
|
||||
|
||||
.vs__selected {
|
||||
color: $fg-color;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.table.b-table > tbody > .table-active, .table.b-table > tbody > .table-active > th, .table.b-table > tbody > .table-active > td {
|
||||
// Table selected cells
|
||||
background: darken($bg-dark, 10%);
|
||||
|
||||
.table {
|
||||
color: $fg-color;
|
||||
}
|
||||
|
||||
.table-hover tbody tr:hover td, .table-hover tbody tr:hover th,
|
||||
.table.b-table.table-hover > tbody > tr.table-active:hover td, .table.b-table.table-hover > tbody > tr.table-active:hover th{
|
||||
.table.b-table>tbody>.selected,
|
||||
.table.b-table>tbody>.selected>th,
|
||||
.table.b-table>tbody>.selected>td {
|
||||
// Table selected cells
|
||||
color: $fg-color;
|
||||
background: darken($bg-dark, 10%);
|
||||
}
|
||||
|
||||
|
||||
.table-hover tbody tr:hover td,
|
||||
.table-hover tbody tr:hover th,
|
||||
.table.b-table>tbody>.selected:hover,
|
||||
.table.b-table.table-hover>tbody>tr.selected th:hover {
|
||||
// Table selected cells hover
|
||||
color: $fg-color;
|
||||
background: lighten($bg-dark, 10%);
|
||||
--bs-table-hover-bg: #272727;
|
||||
}
|
||||
|
||||
.custom-select {
|
||||
.form-select {
|
||||
color: $fg-color;
|
||||
border-color: lighten($bg-dark, 20%);
|
||||
background: $bg-dark url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23dedede' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px
|
||||
}
|
||||
|
||||
.b-toast .toast {
|
||||
background: $bg-dark;
|
||||
border-color: lighten($bg-dark, 20%);
|
||||
}
|
||||
|
||||
.toast-header {
|
||||
color: $fg-color;
|
||||
background: darken($bg-dark, 10%);
|
||||
border-color: lighten($bg-dark, 20%);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
html.ft-theme-transition,
|
||||
html.ft-theme-transition *,
|
||||
html.ft-theme-transition *:before,
|
||||
html.ft-theme-transition *:after {
|
||||
transition:background 750ms ease-in-out,
|
||||
border-color 750ms ease-in-out;
|
||||
transition: background 750ms ease-in-out,
|
||||
border-color 750ms ease-in-out;
|
||||
transition-delay: 0 !important;
|
||||
}
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
@import 'bootstrap_variables_ovw';
|
||||
|
||||
@import 'bootstrap/scss/bootstrap';
|
||||
@import 'bootstrap-vue/src/index';
|
||||
// @import 'bootstrap-vue/src/index';
|
||||
|
||||
@import "vue-select/src/scss/vue-select.scss";
|
||||
// @import "vue-select/src/scss/vue-select.scss";
|
||||
@import 'vue-select/dist/vue-select.css';
|
||||
@import 'variables';
|
||||
@import 'styles_ovw';
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export interface AlertType {
|
||||
message: string;
|
||||
severity: string;
|
||||
timeout: number;
|
||||
}
|
||||
|
|
16
src/types/gridLayout.ts
Normal file
16
src/types/gridLayout.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
// TODO: This interface should really come from the grid-layout package
|
||||
export interface GridItemData {
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
i: number;
|
||||
isDraggable?: boolean;
|
||||
isResizable?: boolean;
|
||||
maxH?: number;
|
||||
maxW?: number;
|
||||
minH?: number;
|
||||
minW?: number;
|
||||
moved?: boolean;
|
||||
static?: boolean;
|
||||
}
|
|
@ -10,3 +10,4 @@ export * from './plot';
|
|||
export * from './profit';
|
||||
export * from './trades';
|
||||
export * from './types';
|
||||
export * from './gridLayout';
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
</div>
|
||||
<small
|
||||
v-show="botStore.activeBot.backtestRunning"
|
||||
class="text-right bt-running-label col-8 col-lg-3"
|
||||
class="text-end bt-running-label col-8 col-lg-3"
|
||||
>Backtest running: {{ botStore.activeBot.backtestStep }}
|
||||
{{ formatPercent(botStore.activeBot.backtestProgress, 2) }}</small
|
||||
>
|
||||
|
@ -70,7 +70,7 @@
|
|||
<div class="d-md-flex">
|
||||
<!-- Left bar -->
|
||||
<div
|
||||
:class="`${showLeftBar ? 'col-md-3' : ''} sticky-top sticky-offset mr-3 d-flex flex-column`"
|
||||
:class="`${showLeftBar ? 'col-md-3' : ''} sticky-top sticky-offset me-3 d-flex flex-column`"
|
||||
>
|
||||
<b-button
|
||||
v-if="btFormMode !== 'visualize'"
|
||||
|
@ -107,7 +107,7 @@
|
|||
label-cols-lg="2"
|
||||
label="Backtest params"
|
||||
label-size="sm"
|
||||
label-class="font-weight-bold pt-0"
|
||||
label-class="fw-bold pt-0"
|
||||
class="mb-0"
|
||||
>
|
||||
<b-form-group
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<GridLayout
|
||||
<grid-layout
|
||||
class="h-100 w-100"
|
||||
:row-height="50"
|
||||
:layout="gridLayout"
|
||||
:layout="gridLayoutData"
|
||||
:vertical-compact="false"
|
||||
:margin="[5, 5]"
|
||||
:responsive-layouts="responsiveGridLayouts"
|
||||
|
@ -11,124 +11,132 @@
|
|||
:responsive="true"
|
||||
:prevent-collision="true"
|
||||
:cols="{ lg: 12, md: 12, sm: 12, xs: 4, xxs: 2 }"
|
||||
:col-num="12"
|
||||
@layout-updated="layoutUpdatedEvent"
|
||||
@breakpoint-changed="breakpointChanged"
|
||||
>
|
||||
<GridItem
|
||||
:i="gridLayoutDaily.i"
|
||||
:x="gridLayoutDaily.x"
|
||||
:y="gridLayoutDaily.y"
|
||||
:w="gridLayoutDaily.w"
|
||||
:h="gridLayoutDaily.h"
|
||||
:min-w="3"
|
||||
:min-h="4"
|
||||
drag-allow-from=".drag-header"
|
||||
>
|
||||
<DraggableContainer :header="`Daily Profit ${botStore.botCount > 1 ? 'combined' : ''}`">
|
||||
<DailyChart
|
||||
v-if="botStore.allDailyStatsSelectedBots"
|
||||
:daily-stats="botStore.allDailyStatsSelectedBots"
|
||||
:show-title="false"
|
||||
/>
|
||||
</DraggableContainer>
|
||||
</GridItem>
|
||||
<GridItem
|
||||
:i="gridLayoutBotComparison.i"
|
||||
:x="gridLayoutBotComparison.x"
|
||||
:y="gridLayoutBotComparison.y"
|
||||
:w="gridLayoutBotComparison.w"
|
||||
:h="gridLayoutBotComparison.h"
|
||||
:min-w="3"
|
||||
:min-h="4"
|
||||
drag-allow-from=".drag-header"
|
||||
>
|
||||
<DraggableContainer header="Bot comparison">
|
||||
<bot-comparison-list />
|
||||
</DraggableContainer>
|
||||
</GridItem>
|
||||
<GridItem
|
||||
:i="gridLayoutAllOpenTrades.i"
|
||||
:x="gridLayoutAllOpenTrades.x"
|
||||
:y="gridLayoutAllOpenTrades.y"
|
||||
:w="gridLayoutAllOpenTrades.w"
|
||||
:h="gridLayoutAllOpenTrades.h"
|
||||
:min-w="3"
|
||||
:min-h="4"
|
||||
drag-allow-from=".drag-header"
|
||||
>
|
||||
<DraggableContainer header="Open Trades">
|
||||
<trade-list active-trades :trades="botStore.allOpenTradesSelectedBots" multi-bot-view />
|
||||
</DraggableContainer>
|
||||
</GridItem>
|
||||
<GridItem
|
||||
:i="gridLayoutCumChart.i"
|
||||
:x="gridLayoutCumChart.x"
|
||||
:y="gridLayoutCumChart.y"
|
||||
:w="gridLayoutCumChart.w"
|
||||
:h="gridLayoutCumChart.h"
|
||||
:min-w="3"
|
||||
:min-h="4"
|
||||
drag-allow-from=".drag-header"
|
||||
>
|
||||
<DraggableContainer header="Cumulative Profit">
|
||||
<CumProfitChart :trades="botStore.allTradesSelectedBots" :show-title="false" />
|
||||
</DraggableContainer>
|
||||
</GridItem>
|
||||
<GridItem
|
||||
:i="gridLayoutAllClosedTrades.i"
|
||||
:x="gridLayoutAllClosedTrades.x"
|
||||
:y="gridLayoutAllClosedTrades.y"
|
||||
:w="gridLayoutAllClosedTrades.w"
|
||||
:h="gridLayoutAllClosedTrades.h"
|
||||
:min-w="3"
|
||||
:min-h="4"
|
||||
drag-allow-from=".drag-header"
|
||||
>
|
||||
<DraggableContainer header="Closed Trades">
|
||||
<trade-list
|
||||
:active-trades="false"
|
||||
show-filter
|
||||
:trades="botStore.allClosedTradesSelectedBots"
|
||||
multi-bot-view
|
||||
/>
|
||||
</DraggableContainer>
|
||||
</GridItem>
|
||||
<GridItem
|
||||
:i="gridLayoutProfitDistribution.i"
|
||||
:x="gridLayoutProfitDistribution.x"
|
||||
:y="gridLayoutProfitDistribution.y"
|
||||
:w="gridLayoutProfitDistribution.w"
|
||||
:h="gridLayoutProfitDistribution.h"
|
||||
:min-w="3"
|
||||
:min-h="4"
|
||||
drag-allow-from=".drag-header"
|
||||
>
|
||||
<DraggableContainer header="Profit Distribution">
|
||||
<ProfitDistributionChart :trades="botStore.allTradesSelectedBots" :show-title="false" />
|
||||
</DraggableContainer>
|
||||
</GridItem>
|
||||
<GridItem
|
||||
:i="gridLayoutTradesLogChart.i"
|
||||
:x="gridLayoutTradesLogChart.x"
|
||||
:y="gridLayoutTradesLogChart.y"
|
||||
:w="gridLayoutTradesLogChart.w"
|
||||
:h="gridLayoutTradesLogChart.h"
|
||||
:min-w="3"
|
||||
:min-h="4"
|
||||
drag-allow-from=".drag-header"
|
||||
>
|
||||
<DraggableContainer header="Trades Log">
|
||||
<TradesLogChart :trades="botStore.allTradesSelectedBots" :show-title="false" />
|
||||
</DraggableContainer>
|
||||
</GridItem>
|
||||
</GridLayout>
|
||||
<template #default="{ gridItemProps }">
|
||||
<grid-item
|
||||
v-bind="gridItemProps"
|
||||
:i="gridLayoutDaily.i"
|
||||
:x="gridLayoutDaily.x"
|
||||
:y="gridLayoutDaily.y"
|
||||
:w="gridLayoutDaily.w"
|
||||
:h="gridLayoutDaily.h"
|
||||
:min-w="3"
|
||||
:min-h="4"
|
||||
drag-allow-from=".drag-header"
|
||||
>
|
||||
<DraggableContainer :header="`Daily Profit ${botStore.botCount > 1 ? 'combined' : ''}`">
|
||||
<DailyChart
|
||||
v-if="botStore.allDailyStatsSelectedBots"
|
||||
:daily-stats="botStore.allDailyStatsSelectedBots"
|
||||
:show-title="false"
|
||||
/>
|
||||
</DraggableContainer>
|
||||
</grid-item>
|
||||
<grid-item
|
||||
v-bind="gridItemProps"
|
||||
:i="gridLayoutBotComparison.i"
|
||||
:x="gridLayoutBotComparison.x"
|
||||
:y="gridLayoutBotComparison.y"
|
||||
:w="gridLayoutBotComparison.w"
|
||||
:h="gridLayoutBotComparison.h"
|
||||
:min-w="3"
|
||||
:min-h="4"
|
||||
drag-allow-from=".drag-header"
|
||||
>
|
||||
<DraggableContainer header="Bot comparison">
|
||||
<bot-comparison-list />
|
||||
</DraggableContainer>
|
||||
</grid-item>
|
||||
<grid-item
|
||||
v-bind="gridItemProps"
|
||||
:i="gridLayoutAllOpenTrades.i"
|
||||
:x="gridLayoutAllOpenTrades.x"
|
||||
:y="gridLayoutAllOpenTrades.y"
|
||||
:w="gridLayoutAllOpenTrades.w"
|
||||
:h="gridLayoutAllOpenTrades.h"
|
||||
:min-w="3"
|
||||
:min-h="4"
|
||||
drag-allow-from=".drag-header"
|
||||
>
|
||||
<DraggableContainer header="Open Trades">
|
||||
<trade-list active-trades :trades="botStore.allOpenTradesSelectedBots" multi-bot-view />
|
||||
</DraggableContainer>
|
||||
</grid-item>
|
||||
<grid-item
|
||||
v-bind="gridItemProps"
|
||||
:i="gridLayoutCumChart.i"
|
||||
:x="gridLayoutCumChart.x"
|
||||
:y="gridLayoutCumChart.y"
|
||||
:w="gridLayoutCumChart.w"
|
||||
:h="gridLayoutCumChart.h"
|
||||
:min-w="3"
|
||||
:min-h="4"
|
||||
drag-allow-from=".drag-header"
|
||||
>
|
||||
<DraggableContainer header="Cumulative Profit">
|
||||
<CumProfitChart :trades="botStore.allTradesSelectedBots" :show-title="false" />
|
||||
</DraggableContainer>
|
||||
</grid-item>
|
||||
<grid-item
|
||||
v-bind="gridItemProps"
|
||||
:i="gridLayoutAllClosedTrades.i"
|
||||
:x="gridLayoutAllClosedTrades.x"
|
||||
:y="gridLayoutAllClosedTrades.y"
|
||||
:w="gridLayoutAllClosedTrades.w"
|
||||
:h="gridLayoutAllClosedTrades.h"
|
||||
:min-w="3"
|
||||
:min-h="4"
|
||||
drag-allow-from=".drag-header"
|
||||
>
|
||||
<DraggableContainer header="Closed Trades">
|
||||
<trade-list
|
||||
:active-trades="false"
|
||||
show-filter
|
||||
:trades="botStore.allClosedTradesSelectedBots"
|
||||
multi-bot-view
|
||||
/>
|
||||
</DraggableContainer>
|
||||
</grid-item>
|
||||
<grid-item
|
||||
v-bind="gridItemProps"
|
||||
:i="gridLayoutProfitDistribution.i"
|
||||
:x="gridLayoutProfitDistribution.x"
|
||||
:y="gridLayoutProfitDistribution.y"
|
||||
:w="gridLayoutProfitDistribution.w"
|
||||
:h="gridLayoutProfitDistribution.h"
|
||||
:min-w="3"
|
||||
:min-h="4"
|
||||
drag-allow-from=".drag-header"
|
||||
>
|
||||
<DraggableContainer header="Profit Distribution">
|
||||
<ProfitDistributionChart :trades="botStore.allTradesSelectedBots" :show-title="false" />
|
||||
</DraggableContainer>
|
||||
</grid-item>
|
||||
<grid-item
|
||||
v-bind="gridItemProps"
|
||||
:i="gridLayoutTradesLogChart.i"
|
||||
:x="gridLayoutTradesLogChart.x"
|
||||
:y="gridLayoutTradesLogChart.y"
|
||||
:w="gridLayoutTradesLogChart.w"
|
||||
:h="gridLayoutTradesLogChart.h"
|
||||
:min-w="3"
|
||||
:min-h="4"
|
||||
drag-allow-from=".drag-header"
|
||||
>
|
||||
<DraggableContainer header="Trades Log">
|
||||
<TradesLogChart :trades="botStore.allTradesSelectedBots" :show-title="false" />
|
||||
</DraggableContainer>
|
||||
</grid-item>
|
||||
</template>
|
||||
</grid-layout>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { formatPrice } from '@/shared/formatters';
|
||||
|
||||
import { GridLayout, GridItem, GridItemData } from 'vue-grid-layout';
|
||||
|
||||
import DailyChart from '@/components/charts/DailyChart.vue';
|
||||
import CumProfitChart from '@/components/charts/CumProfitChart.vue';
|
||||
import TradesLogChart from '@/components/charts/TradesLog.vue';
|
||||
|
@ -140,12 +148,11 @@ import DraggableContainer from '@/components/layout/DraggableContainer.vue';
|
|||
import { defineComponent, ref, computed, onMounted } from 'vue';
|
||||
import { DashboardLayout, findGridLayout, useLayoutStore } from '@/stores/layout';
|
||||
import { useBotStore } from '@/stores/ftbotwrapper';
|
||||
import { GridItemData } from '@/types';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Dashboard',
|
||||
components: {
|
||||
GridLayout,
|
||||
GridItem,
|
||||
DailyChart,
|
||||
CumProfitChart,
|
||||
ProfitDistributionChart,
|
||||
|
@ -171,7 +178,7 @@ export default defineComponent({
|
|||
return layoutStore.layoutLocked || !isResizableLayout;
|
||||
});
|
||||
|
||||
const gridLayout = computed((): GridItemData[] => {
|
||||
const gridLayoutData = computed((): GridItemData[] => {
|
||||
if (isResizableLayout) {
|
||||
return layoutStore.dashboardLayout;
|
||||
}
|
||||
|
@ -187,28 +194,28 @@ export default defineComponent({
|
|||
};
|
||||
|
||||
const gridLayoutDaily = computed((): GridItemData => {
|
||||
return findGridLayout(gridLayout.value, DashboardLayout.dailyChart);
|
||||
return findGridLayout(gridLayoutData.value, DashboardLayout.dailyChart);
|
||||
});
|
||||
|
||||
const gridLayoutBotComparison = computed((): GridItemData => {
|
||||
return findGridLayout(gridLayout.value, DashboardLayout.botComparison);
|
||||
return findGridLayout(gridLayoutData.value, DashboardLayout.botComparison);
|
||||
});
|
||||
|
||||
const gridLayoutAllOpenTrades = computed((): GridItemData => {
|
||||
return findGridLayout(gridLayout.value, DashboardLayout.allOpenTrades);
|
||||
return findGridLayout(gridLayoutData.value, DashboardLayout.allOpenTrades);
|
||||
});
|
||||
const gridLayoutAllClosedTrades = computed((): GridItemData => {
|
||||
return findGridLayout(gridLayout.value, DashboardLayout.allClosedTrades);
|
||||
return findGridLayout(gridLayoutData.value, DashboardLayout.allClosedTrades);
|
||||
});
|
||||
|
||||
const gridLayoutCumChart = computed((): GridItemData => {
|
||||
return findGridLayout(gridLayout.value, DashboardLayout.cumChartChart);
|
||||
return findGridLayout(gridLayoutData.value, DashboardLayout.cumChartChart);
|
||||
});
|
||||
const gridLayoutProfitDistribution = computed((): GridItemData => {
|
||||
return findGridLayout(gridLayout.value, DashboardLayout.profitDistributionChart);
|
||||
return findGridLayout(gridLayoutData.value, DashboardLayout.profitDistributionChart);
|
||||
});
|
||||
const gridLayoutTradesLogChart = computed((): GridItemData => {
|
||||
return findGridLayout(gridLayout.value, DashboardLayout.tradesLogChart);
|
||||
return findGridLayout(gridLayoutData.value, DashboardLayout.tradesLogChart);
|
||||
});
|
||||
|
||||
const responsiveGridLayouts = computed(() => {
|
||||
|
@ -230,7 +237,7 @@ export default defineComponent({
|
|||
isLayoutLocked,
|
||||
layoutUpdatedEvent,
|
||||
breakpointChanged,
|
||||
gridLayout,
|
||||
gridLayoutData,
|
||||
gridLayoutDaily,
|
||||
gridLayoutBotComparison,
|
||||
gridLayoutAllOpenTrades,
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
<template>
|
||||
<div class="d-flex flex-column h-100">
|
||||
<!-- <div v-if="isWebserverMode" class="mr-auto ml-3"> -->
|
||||
<!-- <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">
|
||||
<div class="col-md-3 text-left">
|
||||
<div class="col-md-3 text-start">
|
||||
<span>Strategy</span>
|
||||
<StrategySelect v-model="strategy" class="mt-1"></StrategySelect>
|
||||
</div>
|
||||
<div class="col-md-3 text-left">
|
||||
<div class="col-md-3 text-start">
|
||||
<span>Timeframe</span>
|
||||
<TimeframeSelect v-model="selectedTimeframe" class="mt-1" />
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="home">
|
||||
<div class="container col-12 col-sm-6 col-lg-4">
|
||||
<div class="d-flex justify-content-center">
|
||||
<bot-list />
|
||||
</div>
|
||||
<hr />
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
<template>
|
||||
<div>
|
||||
<b-button v-b-modal.modal-prevent-closing>{{ loginText }}</b-button>
|
||||
<b-modal id="modal-prevent-closing" ref="modalRef" title="Login to your bot" @ok="handleOk">
|
||||
<b-button @click="loginViewOpen = true">{{ loginText }}</b-button>
|
||||
<b-modal
|
||||
id="modal-prevent-closing"
|
||||
v-model="loginViewOpen"
|
||||
title="Login to your bot"
|
||||
@ok="handleOk"
|
||||
>
|
||||
<Login ref="loginForm" in-modal @loginResult="handleLoginResult" />
|
||||
</b-modal>
|
||||
</div>
|
||||
|
@ -12,10 +17,6 @@ import { defineComponent, ref } from 'vue';
|
|||
|
||||
import Login from '@/components/Login.vue';
|
||||
|
||||
interface HTMLModal extends HTMLElement {
|
||||
hide: () => void;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'LoginModal',
|
||||
components: { Login },
|
||||
|
@ -23,19 +24,18 @@ export default defineComponent({
|
|||
loginText: { required: false, default: 'Login', type: String },
|
||||
},
|
||||
setup() {
|
||||
const modalRef = ref<HTMLModal>();
|
||||
const loginViewOpen = ref(false);
|
||||
const loginForm = ref<HTMLFormElement>();
|
||||
const handleLoginResult = (result: boolean) => {
|
||||
if (result) {
|
||||
modalRef.value?.hide();
|
||||
loginViewOpen.value = false;
|
||||
}
|
||||
};
|
||||
const handleOk = (evt) => {
|
||||
evt.preventDefault();
|
||||
const handleOk = () => {
|
||||
loginForm.value?.handleSubmit();
|
||||
};
|
||||
return {
|
||||
modalRef,
|
||||
loginViewOpen,
|
||||
loginForm,
|
||||
handleOk,
|
||||
handleLoginResult,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="container mt-3">
|
||||
<b-card header="FreqUI Settings">
|
||||
<div class="text-left">
|
||||
<div class="text-start">
|
||||
<p>UI Version: {{ settingsStore.uiVersion }}</p>
|
||||
<b-form-group
|
||||
description="Lock dynamic layouts, so they cannot move anymore. Can also be set from the navbar at the top."
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
<div v-if="botStore.activeBot.detailTradeId" class="d-flex flex-column">
|
||||
<b-button
|
||||
size="sm"
|
||||
class="align-self-start mt-1 ml-1"
|
||||
class="align-self-start mt-1 ms-1"
|
||||
@click="botStore.activeBot.setDetailTrade(null)"
|
||||
><BackIcon /> Back</b-button
|
||||
>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<GridLayout
|
||||
<grid-layout
|
||||
class="h-100 w-100"
|
||||
:row-height="50"
|
||||
:layout="gridLayout"
|
||||
:layout="gridLayoutData"
|
||||
:vertical-compact="false"
|
||||
:margin="[5, 5]"
|
||||
:responsive-layouts="responsiveGridLayouts"
|
||||
|
@ -10,132 +10,140 @@
|
|||
:is-draggable="!isLayoutLocked"
|
||||
:responsive="true"
|
||||
:cols="{ lg: 12, md: 12, sm: 12, xs: 4, xxs: 2 }"
|
||||
@layout-updated="layoutUpdatedEvent"
|
||||
@breakpoint-changed="breakpointChanged"
|
||||
:col-num="12"
|
||||
@update:layout="layoutUpdatedEvent"
|
||||
@update:breakpoint="breakpointChanged"
|
||||
>
|
||||
<GridItem
|
||||
v-if="gridLayoutMultiPane.h != 0"
|
||||
:i="gridLayoutMultiPane.i"
|
||||
:x="gridLayoutMultiPane.x"
|
||||
:y="gridLayoutMultiPane.y"
|
||||
:w="gridLayoutMultiPane.w"
|
||||
:h="gridLayoutMultiPane.h"
|
||||
drag-allow-from=".card-header"
|
||||
>
|
||||
<DraggableContainer header="Multi Pane">
|
||||
<div class="mt-1 d-flex justify-content-center">
|
||||
<BotControls class="mt-1 mb-2" />
|
||||
</div>
|
||||
<b-tabs content-class="mt-3" class="mt-1">
|
||||
<b-tab title="Pairs combined" active>
|
||||
<PairSummary
|
||||
:pairlist="botStore.activeBot.whitelist"
|
||||
:current-locks="botStore.activeBot.activeLocks"
|
||||
:trades="botStore.activeBot.openTrades"
|
||||
/>
|
||||
</b-tab>
|
||||
<b-tab title="General">
|
||||
<BotStatus />
|
||||
</b-tab>
|
||||
<b-tab title="Performance">
|
||||
<Performance class="performance-view" />
|
||||
</b-tab>
|
||||
<b-tab title="Balance" lazy>
|
||||
<Balance />
|
||||
</b-tab>
|
||||
<b-tab title="Daily Stats" lazy>
|
||||
<DailyStats />
|
||||
</b-tab>
|
||||
<template #default="{ gridItemProps }">
|
||||
<grid-item
|
||||
v-if="gridLayoutMultiPane.h != 0"
|
||||
v-bind="gridItemProps"
|
||||
:i="gridLayoutMultiPane.i"
|
||||
:x="gridLayoutMultiPane.x"
|
||||
:y="gridLayoutMultiPane.y"
|
||||
:w="gridLayoutMultiPane.w"
|
||||
:h="gridLayoutMultiPane.h"
|
||||
drag-allow-from=".card-header"
|
||||
>
|
||||
<DraggableContainer header="Multi Pane">
|
||||
<div class="mt-1 d-flex justify-content-center">
|
||||
<BotControls class="mt-1 mb-2" />
|
||||
</div>
|
||||
<b-tabs content-class="mt-3" class="mt-1">
|
||||
<b-tab title="Pairs combined" active>
|
||||
<PairSummary
|
||||
:pairlist="botStore.activeBot.whitelist"
|
||||
:current-locks="botStore.activeBot.activeLocks"
|
||||
:trades="botStore.activeBot.openTrades"
|
||||
/>
|
||||
</b-tab>
|
||||
<b-tab title="General">
|
||||
<BotStatus />
|
||||
</b-tab>
|
||||
<b-tab title="Performance">
|
||||
<Performance class="performance-view" />
|
||||
</b-tab>
|
||||
<b-tab title="Balance" lazy>
|
||||
<Balance />
|
||||
</b-tab>
|
||||
<b-tab title="Daily Stats" lazy>
|
||||
<DailyStats />
|
||||
</b-tab>
|
||||
|
||||
<b-tab title="Pairlist" lazy>
|
||||
<FTBotAPIPairList />
|
||||
</b-tab>
|
||||
<b-tab title="Pair Locks" lazy>
|
||||
<PairLockList />
|
||||
</b-tab>
|
||||
</b-tabs>
|
||||
</DraggableContainer>
|
||||
</GridItem>
|
||||
<GridItem
|
||||
v-if="gridLayoutOpenTrades.h != 0"
|
||||
:i="gridLayoutOpenTrades.i"
|
||||
:x="gridLayoutOpenTrades.x"
|
||||
:y="gridLayoutOpenTrades.y"
|
||||
:w="gridLayoutOpenTrades.w"
|
||||
:h="gridLayoutOpenTrades.h"
|
||||
drag-allow-from=".card-header"
|
||||
>
|
||||
<DraggableContainer header="Open Trades">
|
||||
<TradeList
|
||||
class="open-trades"
|
||||
:trades="botStore.activeBot.openTrades"
|
||||
title="Open trades"
|
||||
:active-trades="true"
|
||||
empty-text="Currently no open trades."
|
||||
/>
|
||||
</DraggableContainer>
|
||||
</GridItem>
|
||||
<GridItem
|
||||
v-if="gridLayoutTradeHistory.h != 0"
|
||||
:i="gridLayoutTradeHistory.i"
|
||||
:x="gridLayoutTradeHistory.x"
|
||||
:y="gridLayoutTradeHistory.y"
|
||||
:w="gridLayoutTradeHistory.w"
|
||||
:h="gridLayoutTradeHistory.h"
|
||||
drag-allow-from=".card-header"
|
||||
>
|
||||
<DraggableContainer header="Closed Trades">
|
||||
<trade-list
|
||||
class="trade-history"
|
||||
:trades="botStore.activeBot.closedTrades"
|
||||
title="Trade history"
|
||||
:show-filter="true"
|
||||
empty-text="No closed trades so far."
|
||||
/>
|
||||
</DraggableContainer>
|
||||
</GridItem>
|
||||
<GridItem
|
||||
v-if="botStore.activeBot.detailTradeId && gridLayoutTradeDetail.h != 0"
|
||||
:i="gridLayoutTradeDetail.i"
|
||||
:x="gridLayoutTradeDetail.x"
|
||||
:y="gridLayoutTradeDetail.y"
|
||||
:w="gridLayoutTradeDetail.w"
|
||||
:h="gridLayoutTradeDetail.h"
|
||||
:min-h="4"
|
||||
drag-allow-from=".card-header"
|
||||
>
|
||||
<DraggableContainer header="Trade Detail">
|
||||
<TradeDetail
|
||||
:trade="botStore.activeBot.tradeDetail"
|
||||
:stake-currency="botStore.activeBot.stakeCurrency"
|
||||
/>
|
||||
</DraggableContainer>
|
||||
</GridItem>
|
||||
<GridItem
|
||||
v-if="gridLayoutTradeDetail.h != 0"
|
||||
:i="gridLayoutChartView.i"
|
||||
:x="gridLayoutChartView.x"
|
||||
:y="gridLayoutChartView.y"
|
||||
:w="gridLayoutChartView.w"
|
||||
:h="gridLayoutChartView.h"
|
||||
:min-h="6"
|
||||
drag-allow-from=".card-header"
|
||||
>
|
||||
<DraggableContainer header="Chart">
|
||||
<CandleChartContainer
|
||||
:available-pairs="botStore.activeBot.whitelist"
|
||||
:historic-view="!!false"
|
||||
:timeframe="botStore.activeBot.timeframe"
|
||||
:trades="botStore.activeBot.allTrades"
|
||||
>
|
||||
</CandleChartContainer>
|
||||
</DraggableContainer>
|
||||
</GridItem>
|
||||
</GridLayout>
|
||||
<b-tab title="Pairlist" lazy>
|
||||
<FTBotAPIPairList />
|
||||
</b-tab>
|
||||
<b-tab title="Pair Locks" lazy>
|
||||
<PairLockList />
|
||||
</b-tab>
|
||||
</b-tabs>
|
||||
</DraggableContainer>
|
||||
</grid-item>
|
||||
<grid-item
|
||||
v-if="gridLayoutOpenTrades.h != 0"
|
||||
v-bind="gridItemProps"
|
||||
:i="gridLayoutOpenTrades.i"
|
||||
:x="gridLayoutOpenTrades.x"
|
||||
:y="gridLayoutOpenTrades.y"
|
||||
:w="gridLayoutOpenTrades.w"
|
||||
:h="gridLayoutOpenTrades.h"
|
||||
drag-allow-from=".card-header"
|
||||
>
|
||||
<DraggableContainer header="Open Trades">
|
||||
<TradeList
|
||||
class="open-trades"
|
||||
:trades="botStore.activeBot.openTrades"
|
||||
title="Open trades"
|
||||
:active-trades="true"
|
||||
empty-text="Currently no open trades."
|
||||
/>
|
||||
</DraggableContainer>
|
||||
</grid-item>
|
||||
<grid-item
|
||||
v-if="gridLayoutTradeHistory.h != 0"
|
||||
v-bind="gridItemProps"
|
||||
:i="gridLayoutTradeHistory.i"
|
||||
:x="gridLayoutTradeHistory.x"
|
||||
:y="gridLayoutTradeHistory.y"
|
||||
:w="gridLayoutTradeHistory.w"
|
||||
:h="gridLayoutTradeHistory.h"
|
||||
drag-allow-from=".card-header"
|
||||
>
|
||||
<DraggableContainer header="Closed Trades">
|
||||
<trade-list
|
||||
class="trade-history"
|
||||
:trades="botStore.activeBot.closedTrades"
|
||||
title="Trade history"
|
||||
:show-filter="true"
|
||||
empty-text="No closed trades so far."
|
||||
/>
|
||||
</DraggableContainer>
|
||||
</grid-item>
|
||||
<grid-item
|
||||
v-if="botStore.activeBot.detailTradeId && gridLayoutTradeDetail.h != 0"
|
||||
v-bind="gridItemProps"
|
||||
:i="gridLayoutTradeDetail.i"
|
||||
:x="gridLayoutTradeDetail.x"
|
||||
:y="gridLayoutTradeDetail.y"
|
||||
:w="gridLayoutTradeDetail.w"
|
||||
:h="gridLayoutTradeDetail.h"
|
||||
:min-h="4"
|
||||
drag-allow-from=".card-header"
|
||||
>
|
||||
<DraggableContainer header="Trade Detail">
|
||||
<TradeDetail
|
||||
:trade="botStore.activeBot.tradeDetail"
|
||||
:stake-currency="botStore.activeBot.stakeCurrency"
|
||||
/>
|
||||
</DraggableContainer>
|
||||
</grid-item>
|
||||
<grid-item
|
||||
v-if="gridLayoutTradeDetail.h != 0"
|
||||
v-bind="gridItemProps"
|
||||
:i="gridLayoutChartView.i"
|
||||
:x="gridLayoutChartView.x"
|
||||
:y="gridLayoutChartView.y"
|
||||
:w="gridLayoutChartView.w"
|
||||
:h="gridLayoutChartView.h"
|
||||
:min-h="6"
|
||||
drag-allow-from=".card-header"
|
||||
>
|
||||
<DraggableContainer header="Chart">
|
||||
<CandleChartContainer
|
||||
:available-pairs="botStore.activeBot.whitelist"
|
||||
:historic-view="!!false"
|
||||
:timeframe="botStore.activeBot.timeframe"
|
||||
:trades="botStore.activeBot.allTrades"
|
||||
>
|
||||
</CandleChartContainer>
|
||||
</DraggableContainer>
|
||||
</grid-item>
|
||||
</template>
|
||||
</grid-layout>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { GridLayout, GridItem, GridItemData } from 'vue-grid-layout';
|
||||
import { GridItemData } from '@/types';
|
||||
|
||||
import Balance from '@/components/ftbot/Balance.vue';
|
||||
import BotControls from '@/components/ftbot/BotControls.vue';
|
||||
|
@ -164,8 +172,6 @@ export default defineComponent({
|
|||
DailyStats,
|
||||
DraggableContainer,
|
||||
FTBotAPIPairList,
|
||||
GridItem,
|
||||
GridLayout,
|
||||
PairLockList,
|
||||
PairSummary,
|
||||
Performance,
|
||||
|
@ -187,7 +193,7 @@ export default defineComponent({
|
|||
const isLayoutLocked = computed(() => {
|
||||
return layoutStore.layoutLocked || !isResizableLayout;
|
||||
});
|
||||
const gridLayout = computed((): GridItemData[] => {
|
||||
const gridLayoutData = computed((): GridItemData[] => {
|
||||
if (isResizableLayout) {
|
||||
return layoutStore.tradingLayout;
|
||||
}
|
||||
|
@ -195,23 +201,23 @@ export default defineComponent({
|
|||
});
|
||||
|
||||
const gridLayoutMultiPane = computed(() => {
|
||||
return findGridLayout(gridLayout.value, TradeLayout.multiPane);
|
||||
return findGridLayout(gridLayoutData.value, TradeLayout.multiPane);
|
||||
});
|
||||
|
||||
const gridLayoutOpenTrades = computed(() => {
|
||||
return findGridLayout(gridLayout.value, TradeLayout.openTrades);
|
||||
return findGridLayout(gridLayoutData.value, TradeLayout.openTrades);
|
||||
});
|
||||
|
||||
const gridLayoutTradeHistory = computed(() => {
|
||||
return findGridLayout(gridLayout.value, TradeLayout.tradeHistory);
|
||||
return findGridLayout(gridLayoutData.value, TradeLayout.tradeHistory);
|
||||
});
|
||||
|
||||
const gridLayoutTradeDetail = computed(() => {
|
||||
return findGridLayout(gridLayout.value, TradeLayout.tradeDetail);
|
||||
return findGridLayout(gridLayoutData.value, TradeLayout.tradeDetail);
|
||||
});
|
||||
|
||||
const gridLayoutChartView = computed(() => {
|
||||
return findGridLayout(gridLayout.value, TradeLayout.chartView);
|
||||
return findGridLayout(gridLayoutData.value, TradeLayout.chartView);
|
||||
});
|
||||
|
||||
const responsiveGridLayouts = computed(() => {
|
||||
|
@ -233,7 +239,7 @@ export default defineComponent({
|
|||
breakpointChanged,
|
||||
layoutUpdatedEvent,
|
||||
isLayoutLocked,
|
||||
gridLayout,
|
||||
gridLayoutData,
|
||||
gridLayoutMultiPane,
|
||||
gridLayoutOpenTrades,
|
||||
gridLayoutTradeHistory,
|
||||
|
|
|
@ -35,10 +35,10 @@
|
|||
],
|
||||
"vueCompilerOptions": {
|
||||
"experimentalImplicitWrapComponentOptionsWithDefineComponent": false,
|
||||
"target": 2.7,
|
||||
"target": 3,
|
||||
"experimentalTemplateCompilerOptions": {
|
||||
"compatConfig": {
|
||||
"MODE": 2
|
||||
"MODE": 3
|
||||
} // optional
|
||||
},
|
||||
"experimentalModelPropName": {
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
import { defineConfig } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue2';
|
||||
import createVuePlugin from '@vitejs/plugin-vue';
|
||||
import { resolve } from 'path';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
plugins: [createVuePlugin({})],
|
||||
resolve: {
|
||||
alias: [
|
||||
{
|
||||
find: '@',
|
||||
replacement: resolve(__dirname, 'src'),
|
||||
},
|
||||
],
|
||||
dedupe: ['vue'],
|
||||
alias: {
|
||||
'@': resolve(__dirname, 'src'),
|
||||
},
|
||||
},
|
||||
build: {
|
||||
chunkSizeWarningLimit: 700, // Default is 500
|
||||
|
|
Loading…
Reference in New Issue
Block a user