diff --git a/package.json b/package.json
index 906ee545..8e0a93ab 100644
--- a/package.json
+++ b/package.json
@@ -13,8 +13,12 @@
"cy:open-ct": "cypress open-ct",
"cy:run-ct": "cypress run-ct"
},
+ "resolutions": {
+ "vue-demi": "0.12.1"
+ },
"dependencies": {
"@cypress/vue": "^2.2.3",
+ "@vue/composition-api": "^1.4.3",
"axios": "^0.24.0",
"bootstrap": "^4.6.0",
"bootstrap-vue": "^2.21.2",
@@ -27,6 +31,7 @@
"humanize-duration": "^3.27.1",
"vue": "^2.6.14",
"vue-class-component": "^7.2.5",
+ "vue-demi": "0.12.1",
"vue-echarts": "^6.0.0",
"vue-grid-layout": "^2.3.12",
"vue-material-design-icons": "^5.0.0",
@@ -34,7 +39,8 @@
"vue-router": "^3.5.3",
"vue-select": "^3.16.0",
"vuex": "^3.6.2",
- "vuex-class": "^0.3.2"
+ "vuex-class": "^0.3.2",
+ "vuex-composition-helpers": "^1.1.0"
},
"devDependencies": {
"@cypress/webpack-dev-server": "^1.8.0",
@@ -49,7 +55,6 @@
"@vue/cli-plugin-unit-jest": "~4.5.15",
"@vue/cli-plugin-vuex": "~4.5.15",
"@vue/cli-service": "~4.5.15",
- "@vue/composition-api": "^1.4.3",
"@vue/eslint-config-airbnb": "^5.1.0",
"@vue/eslint-config-prettier": "^6.0.0",
"@vue/eslint-config-typescript": "^5.1.0",
diff --git a/src/App.vue b/src/App.vue
index 1d371ae2..bd2f9241 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -2,12 +2,14 @@
+
diff --git a/src/components/ftbot/CustomTradeListEntry.vue b/src/components/ftbot/CustomTradeListEntry.vue
new file mode 100644
index 00000000..fa7a6218
--- /dev/null
+++ b/src/components/ftbot/CustomTradeListEntry.vue
@@ -0,0 +1,66 @@
+
+
+
+
+ {{ trade.pair }}
+ (#{{ trade.trade_id }})
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/ftbot/PairSummary.vue b/src/components/ftbot/PairSummary.vue
index 51a167fe..d370b206 100644
--- a/src/components/ftbot/PairSummary.vue
+++ b/src/components/ftbot/PairSummary.vue
@@ -17,7 +17,11 @@
-
+
@@ -63,6 +67,8 @@ export default class PairSummary extends Vue {
@ftbot.Getter [BotStoreGetters.selectedPair]!: string;
+ @ftbot.Getter [BotStoreGetters.stakeCurrency]!: string;
+
timestampms = timestampms;
formatPercent = formatPercent;
diff --git a/src/components/ftbot/TradeActions.vue b/src/components/ftbot/TradeActions.vue
new file mode 100644
index 00000000..8cdfb66d
--- /dev/null
+++ b/src/components/ftbot/TradeActions.vue
@@ -0,0 +1,69 @@
+
+
+
+ Forcesell
+
+
+ Forcesell limit
+
+
+ Forcesell market
+
+
+
+
+ Delete
+
+
+
+
+
+
+
diff --git a/src/components/ftbot/TradeDetail.vue b/src/components/ftbot/TradeDetail.vue
index 1abba239..9aefda91 100644
--- a/src/components/ftbot/TradeDetail.vue
+++ b/src/components/ftbot/TradeDetail.vue
@@ -5,6 +5,9 @@
{{ trade.trade_id }}
{{ trade.pair }}
+ {{
+ formatPriceCurrency(trade.stake_amount, stakeCurrency)
+ }}
{{ timestampms(trade.open_timestamp) }}
{{ trade.buy_tag }}
{{ formatPrice(trade.open_rate) }}
@@ -24,10 +27,10 @@
v-if="trade.profit_ratio && trade.profit_abs"
:description="`${trade.is_open ? 'Current Profit' : 'Close Profit'}`"
>
- {{ formatPercent(trade.profit_ratio) }} | {{ trade.profit_abs }}
+
-
+
{{ formatPercent(trade.stop_loss_pct / 100) }} |
@@ -57,21 +60,26 @@
diff --git a/src/main.ts b/src/main.ts
index 3186db92..3cc97cf7 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,5 +1,6 @@
import Vue from 'vue';
import './plugins/bootstrap-vue';
+import './plugins/composition_api';
import App from './App.vue';
import store from './store';
import router from './router';
diff --git a/src/plugins/composition_api.ts b/src/plugins/composition_api.ts
new file mode 100644
index 00000000..2a11e75d
--- /dev/null
+++ b/src/plugins/composition_api.ts
@@ -0,0 +1,4 @@
+import Vue from 'vue';
+import VueCompositionAPI from '@vue/composition-api';
+
+Vue.use(VueCompositionAPI);
diff --git a/src/router/index.ts b/src/router/index.ts
index fbc87006..ce4cad45 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -42,6 +42,26 @@ const routes: Array = [
name: 'Freqtrade Dashboard',
component: () => import(/* webpackChunkName: "dashboard" */ '@/views/Dashboard.vue'),
},
+ {
+ path: '/balance',
+ name: 'Freqtrade Balance',
+ component: () => import(/* webpackChunkName: "balance" */ '@/components/ftbot/Balance.vue'),
+ },
+ {
+ path: '/open_trades',
+ component: () => import(/* webpackChunkName: "trades" */ '@/views/TradesList.vue'),
+ },
+
+ {
+ path: '/trade_history',
+ component: () => import(/* webpackChunkName: "trades" */ '@/views/TradesList.vue'),
+ props: { history: true },
+ },
+ {
+ path: '/pairlist',
+ component: () =>
+ import(/* webpackChunkName: "pairlist" */ '@/components/ftbot/FTBotAPIPairList.vue'),
+ },
{
path: '/settings',
name: 'Freqtrade Settings',
diff --git a/src/shared/formatters.ts b/src/shared/formatters.ts
index 1b6c4aea..e140b7b4 100644
--- a/src/shared/formatters.ts
+++ b/src/shared/formatters.ts
@@ -20,6 +20,17 @@ export function formatPrice(value: number, decimals = 8): string {
return !isUndefined(value) ? parseFloat(value.toFixed(decimals)).toString() : '';
}
+/**
+ * Formats price in the format " " using "deciaml" decimals
+ * @param price Price to format
+ * @param currency currency to use
+ * @param decimals Decimals
+ * @returns
+ */
+export function formatPriceCurrency(price, currency: string, decimals = 3) {
+ return `${formatPrice(price, decimals)} ${currency}`;
+}
+
export function dateFromString(datestring: string, format: string): Date {
return parse(datestring, format, 0);
}
diff --git a/src/store/modules/botStoreWrapper.ts b/src/store/modules/botStoreWrapper.ts
index 57ead006..3ed81a2a 100644
--- a/src/store/modules/botStoreWrapper.ts
+++ b/src/store/modules/botStoreWrapper.ts
@@ -316,7 +316,11 @@ export default function createBotStore(store) {
async pingAll({ getters, dispatch }) {
await Promise.all(
getters.allAvailableBotsList.map(async (e) => {
- await dispatch(`${e}/ping`);
+ try {
+ await dispatch(`${e}/ping`);
+ } catch {
+ // pass
+ }
}),
);
},
diff --git a/src/store/modules/ftbot/index.ts b/src/store/modules/ftbot/index.ts
index 131089d3..50fa28ad 100644
--- a/src/store/modules/ftbot/index.ts
+++ b/src/store/modules/ftbot/index.ts
@@ -76,6 +76,7 @@ export enum BotStoreGetters {
selectedBacktestResult = 'selectedBacktestResult',
canRunBacktest = 'canRunBacktest',
stakeCurrencyDecimals = 'stakeCurrencyDecimals',
+ stakeCurrency = 'stakeCurrency',
strategyPlotConfig = 'strategyPlotConfig',
version = 'version',
botApiVersion = 'botApiVersion',
@@ -251,6 +252,9 @@ export function createBotSubStore(botId: string, botName: string) {
[BotStoreGetters.stakeCurrencyDecimals](state: FtbotStateType): number {
return state.botState?.stake_currency_decimals || 3;
},
+ [BotStoreGetters.stakeCurrency](state: FtbotStateType): string {
+ return state.botState?.stake_currency || '';
+ },
[BotStoreGetters.strategyPlotConfig](state: FtbotStateType): PlotConfig | undefined {
return state.strategyPlotConfig;
},
@@ -458,6 +462,7 @@ export function createBotSubStore(botId: string, botName: string) {
return Promise.resolve();
} catch (error) {
//
+ commit('setIsBotOnline', false);
return Promise.reject();
}
},
diff --git a/src/store/modules/layout.ts b/src/store/modules/layout.ts
index ecc3f36c..3f8a7307 100644
--- a/src/store/modules/layout.ts
+++ b/src/store/modules/layout.ts
@@ -19,6 +19,7 @@ export enum DashboardLayout {
export enum LayoutGetters {
getDashboardLayoutSm = 'getDashboardLayoutSm',
getDashboardLayout = 'getDashboardLayout',
+ getTradingLayoutSm = 'getTradingLayoutSm',
getTradingLayout = 'getTradingLayout',
getLayoutLocked = 'getLayoutLocked',
}
@@ -45,6 +46,15 @@ const DEFAULT_TRADING_LAYOUT: GridItemData[] = [
{ i: TradeLayout.tradeHistory, x: 3, y: 25, w: 9, h: 10 },
];
+// Currently only multiPane is visible
+const DEFAULT_TRADING_LAYOUT_SM: GridItemData[] = [
+ { i: TradeLayout.multiPane, x: 0, y: 0, w: 12, h: 10 },
+ { i: TradeLayout.chartView, x: 0, y: 10, w: 12, h: 0 },
+ { i: TradeLayout.tradeDetail, x: 0, y: 19, w: 12, h: 0 },
+ { i: TradeLayout.openTrades, x: 0, y: 8, w: 12, h: 0 },
+ { i: TradeLayout.tradeHistory, x: 0, y: 25, w: 12, h: 0 },
+];
+
const DEFAULT_DASHBOARD_LAYOUT: GridItemData[] = [
{ i: DashboardLayout.botComparison, x: 0, y: 0, w: 8, h: 6 } /* Bot Comparison */,
{ i: DashboardLayout.dailyChart, x: 8, y: 0, w: 4, h: 6 },
@@ -110,6 +120,9 @@ export default {
[LayoutGetters.getDashboardLayout](state) {
return state.dashboardLayout;
},
+ [LayoutGetters.getTradingLayoutSm]() {
+ return [...DEFAULT_TRADING_LAYOUT_SM];
+ },
[LayoutGetters.getTradingLayout](state) {
return state.tradingLayout;
},
diff --git a/src/views/Dashboard.vue b/src/views/Dashboard.vue
index 9acc4c9d..39fd68f3 100644
--- a/src/views/Dashboard.vue
+++ b/src/views/Dashboard.vue
@@ -10,6 +10,7 @@
:is-draggable="!isLayoutLocked"
:responsive="true"
:prevent-collision="true"
+ :cols="{ lg: 12, md: 12, sm: 12, xs: 4, xxs: 2 }"
@layout-updated="layoutUpdated"
@breakpoint-changed="breakpointChanged"
>
@@ -171,7 +172,7 @@ export default class Dashboard extends Vue {
}
get isResizableLayout() {
- return ['', 'md', 'lg', 'xl'].includes(this.currentBreakpoint);
+ return ['', 'sm', 'md', 'lg', 'xl'].includes(this.currentBreakpoint);
}
get gridLayout() {
diff --git a/src/views/TradesList.vue b/src/views/TradesList.vue
new file mode 100644
index 00000000..afc64cdb
--- /dev/null
+++ b/src/views/TradesList.vue
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
diff --git a/src/views/Trading.vue b/src/views/Trading.vue
index c14adb68..0ed009fc 100644
--- a/src/views/Trading.vue
+++ b/src/views/Trading.vue
@@ -5,11 +5,16 @@
:layout="gridLayout"
:vertical-compact="false"
:margin="[5, 5]"
- :is-resizable="!getLayoutLocked"
- :is-draggable="!getLayoutLocked"
+ :responsive-layouts="responsiveGridLayouts"
+ :is-resizable="!isLayoutLocked"
+ :is-draggable="!isLayoutLocked"
+ :responsive="true"
+ :cols="{ lg: 12, md: 12, sm: 12, xs: 4, xxs: 2 }"
@layout-updated="layoutUpdatedEvent"
+ @breakpoint-changed="breakpointChanged"
>
-
+
diff --git a/tests/unit/formatters.spec.ts b/tests/unit/formatters.spec.ts
index 97192581..ceb6e70c 100644
--- a/tests/unit/formatters.spec.ts
+++ b/tests/unit/formatters.spec.ts
@@ -1,7 +1,13 @@
-import { formatPercent } from '@/shared/formatters';
+import { formatPercent, formatPriceCurrency } from '@/shared/formatters';
describe('formatters.ts', () => {
it('Format percent correctly', () => {
expect(formatPercent(0.5)).toEqual('50.000%');
});
+
+ it('format price currency as expected', () => {
+ expect(formatPriceCurrency(5123.551123, 'USDT', 3)).toEqual('5123.551 USDT');
+ expect(formatPriceCurrency(5123.551123, 'USDT')).toEqual('5123.551 USDT');
+ expect(formatPriceCurrency(5123.551123, 'USDT', 5)).toEqual('5123.55112 USDT');
+ });
});
diff --git a/yarn.lock b/yarn.lock
index 813a73ef..c2e235ae 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -11370,10 +11370,10 @@ vue-class-component@^7.2.5:
resolved "https://registry.yarnpkg.com/vue-class-component/-/vue-class-component-7.2.6.tgz#8471e037b8e4762f5a464686e19e5afc708502e4"
integrity sha512-+eaQXVrAm/LldalI272PpDe3+i4mPis0ORiMYxF6Ae4hyuCh15W8Idet7wPUEs4N4YptgFHGys4UrgNQOMyO6w==
-vue-demi@^0.11.2:
- version "0.11.2"
- resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.11.2.tgz#faa06da53887c493a695b997f4fcb4784a667990"
- integrity sha512-J+X8Au6BhQdcej6LY4O986634hZLu55L0ewU2j8my7WIKlu8cK0dqmdUxqVHHMd/cMrKKZ9SywB/id6aLhwCtA==
+vue-demi@0.12.1, vue-demi@^0.11.2:
+ version "0.12.1"
+ resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.12.1.tgz#f7e18efbecffd11ab069d1472d7a06e319b4174c"
+ integrity sha512-QL3ny+wX8c6Xm1/EZylbgzdoDolye+VpCXRhI2hug9dJTP3OUJ3lmiKN3CsVV3mOJKwFi0nsstbgob0vG7aoIw==
vue-echarts@^6.0.0:
version "6.0.0"
@@ -11506,6 +11506,11 @@ vuex-class@^0.3.2:
resolved "https://registry.yarnpkg.com/vuex-class/-/vuex-class-0.3.2.tgz#c7e96a076c1682137d4d23a8dcfdc63f220e17a8"
integrity sha512-m0w7/FMsNcwJgunJeM+wcNaHzK2KX1K1rw2WUQf7Q16ndXHo7pflRyOV/E8795JO/7fstyjH3EgqBI4h4n4qXQ==
+vuex-composition-helpers@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/vuex-composition-helpers/-/vuex-composition-helpers-1.1.0.tgz#a18d00192fbb0205630202aade1ec6d5f05d4c28"
+ integrity sha512-36f3MWRCW6QqtP3NLyLbtTPv8qWwbac7gAK9fM4ZtDWTCWuAeBoZEiM+bmPQweAQoMM7GRSXmw/90Egiqg0DCA==
+
vuex@^3.6.2:
version "3.6.2"
resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.6.2.tgz#236bc086a870c3ae79946f107f16de59d5895e71"