mirror of
https://github.com/freqtrade/frequi.git
synced 2024-11-11 02:33:51 +00:00
Merge branch 'master' into vue-grid-merge
This commit is contained in:
commit
6637135db8
|
@ -53,7 +53,7 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Emit, Prop } from 'vue-property-decorator';
|
||||
import { Mutation } from 'vuex-class';
|
||||
import { Action } from 'vuex-class';
|
||||
import userService from '@/shared/userService';
|
||||
import { setBaseUrl } from '@/shared/apiService';
|
||||
|
||||
|
@ -63,7 +63,7 @@ const defaultURL = 'http://localhost:8080';
|
|||
|
||||
@Component({})
|
||||
export default class Login extends Vue {
|
||||
@Mutation setLoggedIn;
|
||||
@Action setLoggedIn;
|
||||
|
||||
@Prop({ default: false }) inModal!: boolean;
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
import { namespace } from 'vuex-class';
|
||||
import { AlertActions } from '@/store/modules/alerts';
|
||||
|
||||
const alerts = namespace('alerts');
|
||||
|
||||
|
@ -24,10 +25,10 @@ const alerts = namespace('alerts');
|
|||
export default class BotAlerts extends Vue {
|
||||
@alerts.State activeMessages;
|
||||
|
||||
@alerts.Mutation removeAlert;
|
||||
@alerts.Action [AlertActions.removeAlert];
|
||||
|
||||
closeAlert() {
|
||||
this.removeAlert();
|
||||
this[AlertActions.removeAlert]();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -3,9 +3,10 @@
|
|||
<p>
|
||||
Running Freqtrade <strong>{{ version }}</strong>
|
||||
</p>
|
||||
<p v-if="profit.profit_all_coin">
|
||||
Avg Profit {{ profit.profit_all_coin.toFixed(2) }}% in {{ profit.trade_count }} Trades, with
|
||||
an average duration of {{ profit.avg_duration }}. Best pair: {{ profit.best_pair }}.
|
||||
<p>
|
||||
Avg Profit {{ formatPercent(profit.profit_all_ratio_mean) }} (∑
|
||||
{{ formatPercent(profit.profit_all_ratio_sum) }}) in {{ profit.trade_count }} Trades, with an
|
||||
average duration of {{ profit.avg_duration }}. Best pair: {{ profit.best_pair }}.
|
||||
</p>
|
||||
<p v-if="profit.first_trade_timestamp">
|
||||
First trade opened:
|
||||
|
@ -38,6 +39,8 @@ import { Component, Vue } from 'vue-property-decorator';
|
|||
import { namespace } from 'vuex-class';
|
||||
import { BotState } from '@/types';
|
||||
|
||||
import { formatPercent } from '@/shared/formatters';
|
||||
|
||||
const ftbot = namespace('ftbot');
|
||||
|
||||
@Component({})
|
||||
|
@ -48,6 +51,8 @@ export default class BotStatus extends Vue {
|
|||
|
||||
@ftbot.State botState!: BotState;
|
||||
|
||||
formatPercent = formatPercent;
|
||||
|
||||
formatTimestamp(timestamp) {
|
||||
return new Date(timestamp).toUTCString();
|
||||
}
|
||||
|
|
39
src/components/ftbot/ProfitSymbol.vue
Normal file
39
src/components/ftbot/ProfitSymbol.vue
Normal file
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<div class="h-100 d-inline-block">
|
||||
<div :class="isProfitable ? 'triangle-up' : 'triangle-down'"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop } from 'vue-property-decorator';
|
||||
import { Trade } from '@/types';
|
||||
|
||||
@Component({})
|
||||
export default class ProfitSymbol extends Vue {
|
||||
@Prop({ required: true }) trade!: Trade;
|
||||
|
||||
get isProfitable() {
|
||||
console.log(this.trade);
|
||||
const res = (this.trade.close_profit ?? 1) < 0 || (this.trade.current_profit ?? 1) < 0;
|
||||
console.log(res);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.triangle-up {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 0 0.5rem 0.9rem 0.5rem;
|
||||
border-color: transparent transparent #00db58 transparent;
|
||||
}
|
||||
.triangle-down {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 0.9rem 0.5rem 0 0.5rem;
|
||||
border-color: #ff0000 transparent transparent transparent;
|
||||
}
|
||||
</style>
|
|
@ -2,6 +2,7 @@
|
|||
<div class="h-100 d-flex overflow-auto">
|
||||
<div>
|
||||
<b-table
|
||||
ref="tradesTable"
|
||||
class="table-sm"
|
||||
:items="trades"
|
||||
:fields="tableFields"
|
||||
|
@ -9,9 +10,11 @@
|
|||
:empty-text="emptyText"
|
||||
:per-page="perPage"
|
||||
:current-page="currentPage"
|
||||
primary-key="trade_id"
|
||||
selectable
|
||||
select-mode="single"
|
||||
@row-contextmenu="handleContextMenuEvent"
|
||||
@row-clicked="onRowClicked"
|
||||
@row-selected="onRowSelected"
|
||||
>
|
||||
<template v-slot:cell(actions)="row">
|
||||
|
@ -21,7 +24,7 @@
|
|||
</b-button>
|
||||
</template>
|
||||
<template v-slot:cell(pair)="row">
|
||||
<span class="mr-1" v-html="profitSymbol(row.item)"></span>
|
||||
<ProfitSymbol :trade="row.item" />
|
||||
<span>
|
||||
{{ row.item.pair }}
|
||||
</span>
|
||||
|
@ -45,11 +48,18 @@ import { namespace } from 'vuex-class';
|
|||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import { formatPercent } from '@/shared/formatters';
|
||||
import { Trade } from '@/types';
|
||||
import ProfitSymbol from './ProfitSymbol.vue';
|
||||
|
||||
const ftbot = namespace('ftbot');
|
||||
|
||||
@Component({})
|
||||
@Component({
|
||||
components: { ProfitSymbol },
|
||||
})
|
||||
export default class TradeList extends Vue {
|
||||
$refs!: {
|
||||
tradesTable: HTMLFormElement;
|
||||
};
|
||||
|
||||
@Prop({ required: true })
|
||||
trades!: Array<Trade>;
|
||||
|
||||
|
@ -62,9 +72,9 @@ export default class TradeList extends Vue {
|
|||
@Prop({ default: 'No Trades to show.' })
|
||||
emptyText!: string;
|
||||
|
||||
@ftbot.State detailTradeId?: string;
|
||||
@ftbot.State detailTradeId?: number;
|
||||
|
||||
@ftbot.Mutation setDetailTrade;
|
||||
@ftbot.Action setDetailTrade;
|
||||
|
||||
@ftbot.Action forcesell!: (tradeid: string) => Promise<string>;
|
||||
|
||||
|
@ -72,6 +82,8 @@ export default class TradeList extends Vue {
|
|||
|
||||
currentPage = 1;
|
||||
|
||||
selectedItemIndex? = undefined;
|
||||
|
||||
get rows(): number {
|
||||
return this.trades.length;
|
||||
}
|
||||
|
@ -98,11 +110,6 @@ export default class TradeList extends Vue {
|
|||
...(this.activeTrades ? [{ key: 'actions' }] : []),
|
||||
];
|
||||
|
||||
profitSymbol(item) {
|
||||
// Red arrow / green circle
|
||||
return item.close_profit < 0 || item.current_profit < 0 ? `🔴` : `🟢`;
|
||||
}
|
||||
|
||||
forcesellHandler(item) {
|
||||
this.$bvModal
|
||||
.msgBoxConfirm(`Really forcesell trade ${item.trade_id} (Pair ${item.pair})?`)
|
||||
|
@ -136,14 +143,34 @@ export default class TradeList extends Vue {
|
|||
});
|
||||
}
|
||||
|
||||
onRowSelected(items) {
|
||||
onRowClicked(item, index) {
|
||||
// Only allow single selection mode!
|
||||
if (items.length > 0) {
|
||||
this.setDetailTrade(items[0]);
|
||||
if (
|
||||
item &&
|
||||
item.trade_id !== this.detailTradeId &&
|
||||
!this.$refs.tradesTable.isRowSelected(index)
|
||||
) {
|
||||
this.setDetailTrade(item);
|
||||
} else {
|
||||
console.log('unsetting item');
|
||||
this.setDetailTrade(null);
|
||||
}
|
||||
}
|
||||
|
||||
onRowSelected(items) {
|
||||
// console.log('onRowSelected1');
|
||||
// console.log(items);
|
||||
if (this.detailTradeId) {
|
||||
// console.log('onRowSelected2');
|
||||
const itemIndex = this.trades.findIndex((v) => v.trade_id === this.detailTradeId);
|
||||
if (itemIndex >= 0) {
|
||||
this.$refs.tradesTable.selectRow(itemIndex);
|
||||
} else {
|
||||
console.log(`Unsetting item for tradeid ${this.selectedItemIndex}`);
|
||||
this.selectedItemIndex = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
import LoginModal from '@/views/LoginModal.vue';
|
||||
import { State, Mutation, namespace } from 'vuex-class';
|
||||
import { State, Action, namespace } from 'vuex-class';
|
||||
import userService from '@/shared/userService';
|
||||
import BootswatchThemeSelect from '@/components/BootswatchThemeSelect.vue';
|
||||
|
||||
|
@ -61,7 +61,7 @@ export default class NavBar extends Vue {
|
|||
|
||||
@State isBotOnline!: boolean;
|
||||
|
||||
@Mutation setLoggedIn;
|
||||
@Action setLoggedIn;
|
||||
|
||||
@ftbot.Action ping;
|
||||
|
||||
|
|
|
@ -43,6 +43,9 @@ export default new Vuex.Store({
|
|||
commit('setAutoRefresh', newRefreshValue);
|
||||
localStorage.setItem(AUTO_REFRESH, JSON.stringify(newRefreshValue));
|
||||
},
|
||||
setLoggedIn({ commit }, loggedin: boolean) {
|
||||
commit('setLoggedIn', loggedin);
|
||||
},
|
||||
setIsBotOnline({ commit, dispatch }, isOnline) {
|
||||
commit('setIsBotOnline', isOnline);
|
||||
if (isOnline === false) {
|
||||
|
|
|
@ -1,20 +1,33 @@
|
|||
export enum AlertActions {
|
||||
addAlert = 'addAlert',
|
||||
removeAlert = 'removeAlert',
|
||||
}
|
||||
|
||||
export enum AlertMutations {
|
||||
addAlert = 'addAlert',
|
||||
removeAlert = 'removeAlert',
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
activeMessages: [],
|
||||
},
|
||||
mutations: {
|
||||
addAlert(state, message) {
|
||||
[AlertMutations.addAlert](state, message) {
|
||||
console.log(`adding message '${message.message}' to message queue`);
|
||||
state.activeMessages.push(message);
|
||||
},
|
||||
removeAlert(state) {
|
||||
[AlertMutations.removeAlert](state) {
|
||||
state.activeMessages.shift();
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
addAlert({ commit }, message) {
|
||||
commit('addAlert', message);
|
||||
[AlertActions.addAlert]({ commit }, message) {
|
||||
commit(AlertMutations.addAlert, message);
|
||||
},
|
||||
[AlertActions.removeAlert]({ commit }) {
|
||||
commit(AlertMutations.removeAlert);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { api } from '@/shared/apiService';
|
||||
import { BotState, BlacklistPayload, ForcebuyPayload, Logs, DailyPayload } from '@/types';
|
||||
import { BotState, BlacklistPayload, ForcebuyPayload, Logs, DailyPayload, Trade } from '@/types';
|
||||
|
||||
export enum UserStoreGetters {
|
||||
openTrades = 'openTrades',
|
||||
|
@ -76,7 +76,7 @@ export default {
|
|||
updateLogs(state, logs: Logs) {
|
||||
state.lastLogs = logs.logs;
|
||||
},
|
||||
setDetailTrade(state, trade) {
|
||||
setDetailTrade(state, trade: Trade) {
|
||||
state.detailTradeId = trade ? trade.trade_id : null;
|
||||
},
|
||||
},
|
||||
|
@ -90,6 +90,9 @@ export default {
|
|||
})
|
||||
.catch(console.error);
|
||||
},
|
||||
setDetailTrade({ commit }, trade: Trade) {
|
||||
commit('setDetailTrade', trade);
|
||||
},
|
||||
getTrades({ commit }) {
|
||||
return api
|
||||
.get('/trades')
|
||||
|
|
|
@ -10,6 +10,7 @@ export enum TradeLayout {
|
|||
}
|
||||
|
||||
export enum DashboardLayout {
|
||||
KPI = 'g-kpi',
|
||||
dailyChart = 'g-dailyChart',
|
||||
hourlyChart = 'g-hourlyChart',
|
||||
cumChartChart = 'g-cumChartChart',
|
||||
|
@ -26,8 +27,9 @@ const DEFAULT_TRADING_LAYOUT: GridItemData[] = [
|
|||
];
|
||||
|
||||
const DEFAULT_DASHBOARD_LAYOUT: GridItemData[] = [
|
||||
{ i: DashboardLayout.dailyChart, x: 0, y: 0, w: 4, h: 6 },
|
||||
{ i: DashboardLayout.hourlyChart, x: 4, y: 0, w: 4, h: 6 },
|
||||
{ i: DashboardLayout.KPI, x: 0, y: 0, w: 4, h: 6 },
|
||||
{ i: DashboardLayout.dailyChart, x: 4, y: 0, w: 4, h: 6 },
|
||||
{ i: DashboardLayout.hourlyChart, x: 4, y: 6, w: 4, h: 6 },
|
||||
{ i: DashboardLayout.cumChartChart, x: 0, y: 6, w: 4, h: 6 },
|
||||
];
|
||||
|
||||
|
|
|
@ -12,4 +12,6 @@ export interface DailyRecord {
|
|||
|
||||
export interface DailyReturnValue {
|
||||
data: Array<DailyRecord>;
|
||||
fiat_display_currency: string;
|
||||
stake_currency: string;
|
||||
}
|
||||
|
|
|
@ -2,4 +2,5 @@ export * from './auth';
|
|||
export * from './blacklist';
|
||||
export * from './chart';
|
||||
export * from './daily';
|
||||
export * from './profit';
|
||||
export * from './types';
|
||||
|
|
25
src/types/profit.ts
Normal file
25
src/types/profit.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
export interface ProfitInterface {
|
||||
profit_closed_coin: number;
|
||||
profit_closed_percent_mean: number;
|
||||
profit_closed_ratio_mean: number;
|
||||
profit_closed_percent_sum: number;
|
||||
profit_closed_ratio_sum: number;
|
||||
profit_closed_fiat: number;
|
||||
profit_all_coin: number;
|
||||
profit_all_percent_mean: number;
|
||||
profit_all_ratio_mean: number;
|
||||
profit_all_percent_sum: number;
|
||||
profit_all_ratio_sum: number;
|
||||
profit_all_fiat: number;
|
||||
trade_count: number;
|
||||
closed_trade_count: number;
|
||||
first_trade_date: string;
|
||||
first_trade_timestamp: number;
|
||||
latest_trade_date: string;
|
||||
latest_trade_timestamp: number;
|
||||
avg_duration: string;
|
||||
best_pair: string;
|
||||
best_rate: number;
|
||||
winning_trades: number;
|
||||
losing_trades: number;
|
||||
}
|
|
@ -90,6 +90,7 @@ export interface Trade {
|
|||
fee_close_currency?: string;
|
||||
|
||||
current_rate?: number;
|
||||
current_profit?: number;
|
||||
sell_reason?: string;
|
||||
min_rate?: number;
|
||||
max_rate?: number;
|
||||
|
|
|
@ -6,6 +6,56 @@
|
|||
:vertical-compact="false"
|
||||
@layout-updated="layoutUpdatedEvent"
|
||||
>
|
||||
<GridItem
|
||||
:i="gridLayoutKPI.i"
|
||||
:x="gridLayoutKPI.x"
|
||||
:y="gridLayoutKPI.y"
|
||||
:w="gridLayoutKPI.w"
|
||||
:h="gridLayoutKPI.h"
|
||||
:min-w="3"
|
||||
:min-h="4"
|
||||
drag-allow-from=".drag-header"
|
||||
>
|
||||
<DraggableContainer header="Bot KPI">
|
||||
<div>
|
||||
<b-card-group deck>
|
||||
<b-card header="Open / Total trades">
|
||||
<b-card-text>
|
||||
<span class="text-primary">{{ openTrades.length }}</span> /
|
||||
<span class="text-secondary">{{ profit.trade_count }}</span>
|
||||
</b-card-text>
|
||||
</b-card>
|
||||
<b-card header="Won / lost trades">
|
||||
<b-card-text>
|
||||
<span class="text-success">{{ profit.winning_trades }}</span> /
|
||||
<span class="text-danger">{{ profit.losing_trades }}</span>
|
||||
</b-card-text>
|
||||
</b-card>
|
||||
<b-card header="Last trade">
|
||||
<b-card-text>{{ profit.latest_trade_date }}</b-card-text>
|
||||
</b-card>
|
||||
</b-card-group>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<b-card-group deck>
|
||||
<b-card header="Best performing">
|
||||
<b-card-text>{{ profit.best_pair }}</b-card-text>
|
||||
</b-card>
|
||||
<b-card header="Total Balance">
|
||||
<b-card-text
|
||||
>{{ formatPrice(balance.total) }} {{ dailyStats.stake_currency }}</b-card-text
|
||||
>
|
||||
</b-card>
|
||||
<b-card v-if="profit.profit_closed_fiat" header="Total profit">
|
||||
<b-card-text
|
||||
>{{ formatPrice(profit.profit_closed_fiat) }}
|
||||
{{ dailyStats.fiat_display_currency }}</b-card-text
|
||||
>
|
||||
</b-card>
|
||||
</b-card-group>
|
||||
</div>
|
||||
</DraggableContainer>
|
||||
</GridItem>
|
||||
<GridItem
|
||||
:i="gridLayoutDaily.i"
|
||||
:x="gridLayoutDaily.x"
|
||||
|
@ -52,6 +102,8 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { formatPrice } from '@/shared/formatters';
|
||||
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
import { namespace } from 'vuex-class';
|
||||
import { GridLayout, GridItem, GridItemData } from 'vue-grid-layout';
|
||||
|
@ -61,8 +113,8 @@ import HourlyChart from '@/components/charts/HourlyChart.vue';
|
|||
import CumProfitChart from '@/components/charts/CumProfitChart.vue';
|
||||
import DraggableContainer from '@/components/layout/DraggableContainer.vue';
|
||||
|
||||
import { Trade, DailyReturnValue } from '@/types';
|
||||
import { DashboardLayout, findGridLayout } from '@/store/modules/layout';
|
||||
import { Trade, DailyReturnValue, BalanceInterface, ProfitInterface } from '@/types';
|
||||
|
||||
const ftbot = namespace('ftbot');
|
||||
const layoutNs = namespace('layout');
|
||||
|
@ -82,6 +134,16 @@ export default class Dashboard extends Vue {
|
|||
|
||||
@ftbot.State dailyStats!: DailyReturnValue;
|
||||
|
||||
@ftbot.Getter openTrades!: Array<Trade>;
|
||||
|
||||
@ftbot.State balance!: BalanceInterface;
|
||||
|
||||
@ftbot.State profit!: ProfitInterface;
|
||||
|
||||
@ftbot.State performanceStats!: Array<PerformanceEntry>;
|
||||
|
||||
@ftbot.Action getPerformance;
|
||||
|
||||
@ftbot.Action getDaily;
|
||||
|
||||
@ftbot.Action getTrades;
|
||||
|
@ -90,10 +152,22 @@ export default class Dashboard extends Vue {
|
|||
|
||||
@layoutNs.Mutation setDashboardLayout;
|
||||
|
||||
@ftbot.Action getOpenTrades;
|
||||
|
||||
@ftbot.Action getBalance;
|
||||
|
||||
@ftbot.Action getProfit;
|
||||
|
||||
formatPrice = formatPrice;
|
||||
|
||||
get gridLayout() {
|
||||
return this.getDashboardLayout;
|
||||
}
|
||||
|
||||
get gridLayoutKPI(): GridItemData {
|
||||
return findGridLayout(this.gridLayout, DashboardLayout.KPI);
|
||||
}
|
||||
|
||||
get gridLayoutDaily(): GridItemData {
|
||||
return findGridLayout(this.gridLayout, DashboardLayout.dailyChart);
|
||||
}
|
||||
|
@ -109,6 +183,10 @@ export default class Dashboard extends Vue {
|
|||
mounted() {
|
||||
this.getDaily();
|
||||
this.getTrades();
|
||||
this.getOpenTrades();
|
||||
this.getBalance();
|
||||
this.getPerformance();
|
||||
this.getProfit();
|
||||
}
|
||||
|
||||
layoutUpdatedEvent(newLayout) {
|
||||
|
|
|
@ -150,7 +150,7 @@ const layoutNs = namespace('layout');
|
|||
},
|
||||
})
|
||||
export default class Trading extends Vue {
|
||||
@ftbot.State detailTradeId!: string;
|
||||
@ftbot.State detailTradeId!: number;
|
||||
|
||||
@ftbot.Getter openTrades!: Trade[];
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user