|
|
@@ -4,149 +4,205 @@
|
|
|
<lay-space>
|
|
|
<span class="robot-name">{{ robotDetail.name }}</span>
|
|
|
<lay-tag size="sm">
|
|
|
- <span class="robot-status" v-if="robotDetail.status == 'RUNNING'">
|
|
|
+ <span class="robot-status" v-if="robotDetail.status">
|
|
|
<lay-badge type="dot" theme="blue" ripple />
|
|
|
<span>{{ ROBOT_STATUS[robotDetail.status] }}</span>
|
|
|
</span>
|
|
|
- <span class="robot-status" v-else-if="robotDetail.status == 'ERROR'">
|
|
|
- <lay-badge type="dot" ripple />
|
|
|
- <span>{{ ROBOT_STATUS[robotDetail.status] }}</span>
|
|
|
- </span>
|
|
|
- <span class="robot-status" v-else>
|
|
|
- <lay-badge type="dot" theme="orange" ripple />
|
|
|
+ <span class="robot-status" v-else-if="!robotDetail.status">
|
|
|
+ <lay-badge type="dot" />
|
|
|
<span>{{ ROBOT_STATUS[robotDetail.status] }}</span>
|
|
|
</span>
|
|
|
</lay-tag>
|
|
|
</lay-space>
|
|
|
</div>
|
|
|
|
|
|
- <lay-card v-show="apiList?.includes('/remaining/list')" class="custom-card">
|
|
|
- <template v-slot:title>
|
|
|
- <span class="card-title">净值图</span>
|
|
|
- </template>
|
|
|
- <template v-slot:body>
|
|
|
- <div class="profit-chart" ref="balanceChartRef"></div>
|
|
|
- </template>
|
|
|
- </lay-card>
|
|
|
-
|
|
|
<lay-card class="custom-card">
|
|
|
<template v-slot:title>
|
|
|
- <span class="card-title">内部数据</span>
|
|
|
- </template>
|
|
|
- <template v-slot:extra>
|
|
|
- <lay-button class="card-button" @click="handleReloadIframe()">{{ robotRunDataIsShow ? "刷新" : "获取" }} </lay-button>
|
|
|
- <lay-button class="card-button" @click="handleOtherOpen()">站外打开</lay-button>
|
|
|
+ <span class="card-title">收益图</span>
|
|
|
+ <span class="card-subtitle">(近3天)</span>
|
|
|
</template>
|
|
|
<template v-slot:body>
|
|
|
- <div :class="{ 'card-body-wp': true, 'height-1200': robotRunDataIsShow }">
|
|
|
- <iframe v-if="robotRunDataIsShow" :class="{ chart: true, 'height-1200': robotRunDataIsShow }" :src="robotRunData" frameborder="0"></iframe>
|
|
|
- <div v-else>内部数据,数据可能较大,请勿频繁获取、刷新!</div>
|
|
|
- </div>
|
|
|
+ <div class="profit-chart" ref="profitChartRef"></div>
|
|
|
</template>
|
|
|
</lay-card>
|
|
|
-
|
|
|
- <lay-card v-if="apiList?.includes('/robot/getRobotLog')" class="custom-card">
|
|
|
+ <lay-card class="custom-card">
|
|
|
<template v-slot:title>
|
|
|
- <span class="card-title">运行日志</span>
|
|
|
+ <span class="card-title">订单列表</span>
|
|
|
</template>
|
|
|
|
|
|
<template v-slot:body>
|
|
|
- <lay-table :columns="columns" size="sm" resize :data-source="logsList">
|
|
|
+ <lay-table :page="ordersPage" :columns="orderColumns" size="sm" resize :data-source="ordersList" :loading="pageConfig.ordersLoading" @change="handleCurrentChange">
|
|
|
<template v-slot:text="{ row }">
|
|
|
- <span class="ellipsis-2" @click="showLog(row.text)">{{ row.text }}</span>
|
|
|
+ {{ row.text }}
|
|
|
+ </template>
|
|
|
+ <template v-slot:symbol="{ row }">
|
|
|
+ {{ JSON.parse(row.resJson).symbol.split(":")[0] }}
|
|
|
+ </template>
|
|
|
+ <template v-slot:price="{ row }">
|
|
|
+ {{ JSON.parse(row.resJson).price }}
|
|
|
+ </template>
|
|
|
+ <template v-slot:average="{ row }">
|
|
|
+ {{ JSON.parse(row.resJson).average }}
|
|
|
+ </template>
|
|
|
+ <template v-slot:side="{ row }">
|
|
|
+ {{ JSON.parse(row.resJson).side }}
|
|
|
+ </template>
|
|
|
+ <template v-slot:sideType="{ row }">
|
|
|
+ {{ JSON.parse(row.resJson).reduceOnly ? "平仓" : "开仓" }}
|
|
|
+ </template>
|
|
|
+ <template v-slot:amount="{ row }">
|
|
|
+ {{ JSON.parse(row.resJson).amount }}
|
|
|
+ </template>
|
|
|
+ <template v-slot:timestamp="{ row }">
|
|
|
+ {{ formatDate(JSON.parse(row.resJson).timestamp) }}
|
|
|
+ </template>
|
|
|
+ <template v-slot:resJson="{ row }">
|
|
|
+ <span @click="showInfoLayer(row.resJson)">{{ row.resJson }}</span>
|
|
|
</template>
|
|
|
</lay-table>
|
|
|
</template>
|
|
|
</lay-card>
|
|
|
</div>
|
|
|
- <LogText ref="logtextRef" />
|
|
|
+ <InfoLayer ref="infoLayerRef" />
|
|
|
</template>
|
|
|
-<script lang="ts" setup name="BotManageDetail">
|
|
|
+<script lang="ts" setup name="BotCtaDetail">
|
|
|
import { ref, reactive, onMounted, onUnmounted, shallowRef } from "vue";
|
|
|
import { useRoute } from "vue-router";
|
|
|
+import { formatDate } from "@/utils/index";
|
|
|
import * as echarts from "echarts";
|
|
|
-import LogText from "./components/LogText.vue";
|
|
|
-import { get_robot_detail, get_robot_logs, get_remaining_detail } from "@/api";
|
|
|
+import Decimal from "decimal.js";
|
|
|
+import dayjs from "dayjs";
|
|
|
+import InfoLayer from "./components/InfoLayer.vue";
|
|
|
+import { get_cta_robot_detail, get_cta_robot_order_list, get_cta_robot_profit } from "@/api";
|
|
|
|
|
|
const ROBOT_STATUS: any = reactive({
|
|
|
- STOPPED: "已停止",
|
|
|
- STOP_PENDING: "停止中",
|
|
|
- RUNNING: "运行中",
|
|
|
- START_PENDING: "启动中",
|
|
|
- RESTART_PENDING: "重启中",
|
|
|
- DOWNLOADING: "下载中",
|
|
|
- ERROR: "错误",
|
|
|
+ 0: "已停止",
|
|
|
+ 1: "运行中",
|
|
|
});
|
|
|
|
|
|
-const apiList = ref(window.sessionStorage.getItem("_4L_API_LIST"));
|
|
|
+// const apiList = ref(window.sessionStorage.getItem("_4L_API_LIST"));
|
|
|
|
|
|
-const balanceChartRef = ref();
|
|
|
-const logtextRef = ref();
|
|
|
+const profitChartRef = ref();
|
|
|
+const infoLayerRef = ref();
|
|
|
|
|
|
const route = useRoute();
|
|
|
|
|
|
interface PageConfig {
|
|
|
- predictorLoading: boolean;
|
|
|
- robotLoading: boolean;
|
|
|
- balanceLoading: boolean;
|
|
|
- logsLoading: boolean;
|
|
|
+ detailLoading: boolean;
|
|
|
+ ordersLoading: boolean;
|
|
|
+ profitLoading: boolean;
|
|
|
}
|
|
|
-interface Logs {
|
|
|
+interface Order {
|
|
|
time?: string;
|
|
|
text?: string;
|
|
|
}
|
|
|
|
|
|
let pageConfig: PageConfig = reactive({
|
|
|
- predictorLoading: false,
|
|
|
- robotLoading: false,
|
|
|
- balanceLoading: false,
|
|
|
- logsLoading: false,
|
|
|
+ detailLoading: true,
|
|
|
+ ordersLoading: true,
|
|
|
+ profitLoading: true,
|
|
|
});
|
|
|
|
|
|
-const columns = ref([
|
|
|
- { title: "时间", key: "time", width: 150 },
|
|
|
- { title: "日志", key: "text", customSlot: "text" },
|
|
|
+interface OrderItem {
|
|
|
+ pageNum?: Number;
|
|
|
+ pageSize?: Number;
|
|
|
+ name?: String;
|
|
|
+ symbol?: String;
|
|
|
+ robotState?: String;
|
|
|
+}
|
|
|
+const ordersParams: OrderItem = reactive({ pageNum: 1, pageSize: 50 });
|
|
|
+
|
|
|
+interface OrderPage {
|
|
|
+ current: number;
|
|
|
+ limit: number;
|
|
|
+ total: number;
|
|
|
+}
|
|
|
+const ordersPage: OrderPage = reactive({ current: 1, limit: 50, total: 0, limits: [20, 50, 100, 200, 500] });
|
|
|
+
|
|
|
+const orderColumns = ref([
|
|
|
+ { title: "订单号", key: "id", width: 150 },
|
|
|
+ { title: "币对", key: "symbol", customSlot: "symbol", align: "center" },
|
|
|
+ { title: "价格", key: "price", customSlot: "price", align: "center" },
|
|
|
+ { title: "成交均价", key: "average", customSlot: "average", align: "center" },
|
|
|
+ { title: "方向", key: "side", customSlot: "side", align: "center" },
|
|
|
+ { title: "类型", key: "sideType", customSlot: "sideType", align: "center" },
|
|
|
+ { title: "数量", key: "amount", customSlot: "amount", align: "center" },
|
|
|
+ { title: "订单时间", key: "timestamp", customSlot: "timestamp", align: "center" },
|
|
|
+ { title: "源数据", key: "resJson", customSlot: "resJson", ellipsisTooltip: true, align: "center" },
|
|
|
]);
|
|
|
|
|
|
-let logsList = ref<Array<Logs>>();
|
|
|
+let ordersList = ref<Array<Order>>();
|
|
|
let robotDetail = ref<any>({});
|
|
|
-let balanceList = ref([]);
|
|
|
-let balanceChart = shallowRef();
|
|
|
+let profitList = ref([]);
|
|
|
+let profitChart = shallowRef();
|
|
|
let predictorChart = shallowRef();
|
|
|
let timer = ref();
|
|
|
-let robotRunData = ref();
|
|
|
-let robotRunDataIsShow = ref(false);
|
|
|
|
|
|
// 获取机器人详情
|
|
|
-const getRobotDetail = () => {
|
|
|
+const getCtaRobotDetail = () => {
|
|
|
const params = { id: route.params.id };
|
|
|
- pageConfig.robotLoading = true;
|
|
|
- get_robot_detail(params, (data: any) => {
|
|
|
- pageConfig.robotLoading = false;
|
|
|
+ pageConfig.detailLoading = true;
|
|
|
+ get_cta_robot_detail(params, (data: any) => {
|
|
|
+ pageConfig.detailLoading = false;
|
|
|
if (data.code == 200) {
|
|
|
robotDetail.value = data.data;
|
|
|
document.title = data.data.name;
|
|
|
- getBalanceInfo(data.data.accId);
|
|
|
- robotRunData.value = `https://4lapi.skyfffire.com/downloadDataFile?url=http://${robotDetail.value.serverIp}:3000/downloadDataFile?id=${robotDetail.value.id}`;
|
|
|
+ getCtaRobotOrderList();
|
|
|
+ getProfitInfo(data.data.id);
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const getCtaRobotOrderList = () => {
|
|
|
+ const params = { ...ordersParams, tId: route.params.id };
|
|
|
+ pageConfig.ordersLoading = true;
|
|
|
+ get_cta_robot_order_list(params, (data: any) => {
|
|
|
+ pageConfig.ordersLoading = false;
|
|
|
+ if (data.code == 200) {
|
|
|
+ ordersList.value = data.data.list;
|
|
|
+ ordersPage.total = data.data.total;
|
|
|
}
|
|
|
});
|
|
|
};
|
|
|
|
|
|
-const getBalanceInfo = (id: number) => {
|
|
|
- const params = { id: id };
|
|
|
- pageConfig.balanceLoading = true;
|
|
|
- get_remaining_detail(params, (data: any) => {
|
|
|
- pageConfig.balanceLoading = false;
|
|
|
+// 分页设置
|
|
|
+const handleCurrentChange = (val: any) => {
|
|
|
+ ordersParams.pageNum = val.current;
|
|
|
+ ordersParams.pageSize = val.limit;
|
|
|
+ getCtaRobotOrderList();
|
|
|
+};
|
|
|
+const getProfitInfo = (id: number) => {
|
|
|
+ const params = { tId: id, startTime: +dayjs().subtract(72, "hour"), endTime: +dayjs() };
|
|
|
+ pageConfig.profitLoading = true;
|
|
|
+ get_cta_robot_profit(params, (data: any) => {
|
|
|
+ pageConfig.profitLoading = false;
|
|
|
if (data.code == 200) {
|
|
|
- balanceList.value = data.data;
|
|
|
+ profitList.value = data.data;
|
|
|
|
|
|
- const xData = data.data.map((item: any) => item.creationTime);
|
|
|
- const sData = data.data.map((item: any) => [item.creationTime, item.afterU, item.changeU, item.pair, item.openNum, item.closeNum]);
|
|
|
- const yMinData = Math.min(sData.map((item: any) => item[1]));
|
|
|
+ let balance = new Decimal(0);
|
|
|
+ const sData = data.data.map((item: any) => {
|
|
|
+ balance = new Decimal(item.profitLoss).add(balance);
|
|
|
+ return [
|
|
|
+ formatDate(item.timestamp),
|
|
|
+ balance.toString(),
|
|
|
+ item.symbol.split(":")[0],
|
|
|
+ item.openSide,
|
|
|
+ item.openPrice,
|
|
|
+ item.openSize,
|
|
|
+ item.openTradeVolume,
|
|
|
+ item.closedSide,
|
|
|
+ item.closedPrice,
|
|
|
+ item.closedSize,
|
|
|
+ item.closedTradeVolume,
|
|
|
+ item.profitLoss,
|
|
|
+ ];
|
|
|
+ });
|
|
|
+ const xData = sData.map((item: any) => item[0]);
|
|
|
+ const yMinData = Math.min(...sData.map((item: any) => item[1]));
|
|
|
|
|
|
- !balanceChart.value
|
|
|
- ? initBalanceChart(data.data)
|
|
|
- : balanceChart.value.setOption({
|
|
|
+ !profitChart.value
|
|
|
+ ? initProfitChart(data.data)
|
|
|
+ : profitChart.value.setOption({
|
|
|
xAxis: {
|
|
|
type: "category",
|
|
|
boundaryGap: false,
|
|
|
@@ -156,7 +212,7 @@ const getBalanceInfo = (id: number) => {
|
|
|
type: "value",
|
|
|
boundaryGap: [0, "100%"],
|
|
|
min: yMinData,
|
|
|
- interval: 1,
|
|
|
+ splitNumber: 5,
|
|
|
},
|
|
|
series: {
|
|
|
name: "Balance",
|
|
|
@@ -173,43 +229,60 @@ const getBalanceInfo = (id: number) => {
|
|
|
};
|
|
|
|
|
|
// 请求机器人日志
|
|
|
-const getLogsInfo = () => {
|
|
|
- const params = { id: route.params.id, n: 500 };
|
|
|
- pageConfig.logsLoading = true;
|
|
|
- get_robot_logs(params, (data: any) => {
|
|
|
- pageConfig.logsLoading = false;
|
|
|
- if (data.code == 200) {
|
|
|
- logsList.value = handlePageInfo(data.data);
|
|
|
- }
|
|
|
- });
|
|
|
-};
|
|
|
-getLogsInfo();
|
|
|
+// const getLogsInfo = () => {
|
|
|
+// const params = { id: route.params.id, n: 500 };
|
|
|
+// pageConfig.logsLoading = true;
|
|
|
+// get_robot_logs(params, (data: any) => {
|
|
|
+// pageConfig.logsLoading = false;
|
|
|
+// if (data.code == 200) {
|
|
|
+// logsList.value = handlePageInfo(data.data);
|
|
|
+// }
|
|
|
+// });
|
|
|
+// };
|
|
|
+// getLogsInfo();
|
|
|
|
|
|
-const showLog = (data: any) => {
|
|
|
- logtextRef.value.show(data);
|
|
|
-};
|
|
|
+// const handlePageInfo = (data: any) => {
|
|
|
+// let infoList = data;
|
|
|
+// let result = infoList.map((item: string) => {
|
|
|
+// return { time: item.slice(0, 18), text: item.slice(19).trim() };
|
|
|
+// });
|
|
|
+// return result;
|
|
|
+// };
|
|
|
|
|
|
-const handlePageInfo = (data: any) => {
|
|
|
- let infoList = data;
|
|
|
- let result = infoList.map((item: string) => {
|
|
|
- return { time: item.slice(0, 18), text: item.slice(19).trim() };
|
|
|
- });
|
|
|
- return result;
|
|
|
+const showInfoLayer = (data: any) => {
|
|
|
+ infoLayerRef.value.show(data);
|
|
|
};
|
|
|
|
|
|
-const initBalanceChart = (data: any) => {
|
|
|
- if (balanceChart.value != null && !balanceChart.value.isDisposed()) echarts.dispose(balanceChart.value);
|
|
|
+const initProfitChart = (data: any) => {
|
|
|
+ if (profitChart.value != null && !profitChart.value.isDisposed()) echarts.dispose(profitChart.value);
|
|
|
|
|
|
- balanceChart.value = echarts.init(balanceChartRef.value);
|
|
|
+ profitChart.value = echarts.init(profitChartRef.value);
|
|
|
|
|
|
- window.removeEventListener("resize", () => balanceChart.value.resize());
|
|
|
- window.addEventListener("resize", () => balanceChart.value.resize());
|
|
|
+ window.removeEventListener("resize", () => profitChart.value.resize());
|
|
|
+ window.addEventListener("resize", () => profitChart.value.resize());
|
|
|
|
|
|
- const xData = data.map((item: any) => item.creationTime);
|
|
|
- const sData = data.map((item: any) => [item.creationTime, item.afterU, item.changeU, item.pair, item.openNum, item.closeNum]);
|
|
|
- const yMinData = Math.min(sData);
|
|
|
+ let balance = new Decimal(0);
|
|
|
+ const sData = data.map((item: any) => {
|
|
|
+ balance = new Decimal(item.profitLoss).add(balance);
|
|
|
+ return [
|
|
|
+ formatDate(item.timestamp),
|
|
|
+ balance.toString(),
|
|
|
+ item.symbol.split(":")[0],
|
|
|
+ item.openSide,
|
|
|
+ item.openPrice,
|
|
|
+ item.openSize,
|
|
|
+ item.openTradeVolume,
|
|
|
+ item.closedSide,
|
|
|
+ item.closedPrice,
|
|
|
+ item.closedSize,
|
|
|
+ item.closedTradeVolume,
|
|
|
+ item.profitLoss,
|
|
|
+ ];
|
|
|
+ });
|
|
|
+ const xData = sData.map((item: any) => item[0]);
|
|
|
+ const yMinData = Math.min(...sData.map((item: any) => item[1]));
|
|
|
|
|
|
- const balanceChartOption = {
|
|
|
+ const profitChartOption = {
|
|
|
tooltip: {
|
|
|
trigger: "axis",
|
|
|
axisPointer: {
|
|
|
@@ -217,7 +290,16 @@ const initBalanceChart = (data: any) => {
|
|
|
},
|
|
|
formatter: (params: any) => {
|
|
|
let info = params[0];
|
|
|
- return `${info.marker}${info.seriesName}<br/>时间:${info.value[0]} <br/>余额:${info.value[1]}<br/>收益:${info.value[2]}<br/>币对:${info.value[3]}<br/>开仓:${info.value[4]}<br/>平仓:${info.value[5]}`;
|
|
|
+ return `${info.marker}${info.seriesName}<br/>
|
|
|
+ 时间:${info.value[0]} <br/>
|
|
|
+ 币对:${info.value[2]}<br/>
|
|
|
+ 总体收益:${info.value[1]}<br/>
|
|
|
+ 单笔收益:${info.value[11]}<br/>
|
|
|
+ 开仓方向:${info.value[3]}<br/>
|
|
|
+ 开仓价格:${info.value[4]} 开仓数量:${info.value[5]} 开仓价值:${info.value[6]}<br/>
|
|
|
+ 平仓方向:${info.value[7]}<br/>
|
|
|
+ 平仓价格:${info.value[8]} 平仓数量:${info.value[9]} 平仓价值:${info.value[10]}<br/>
|
|
|
+ `;
|
|
|
},
|
|
|
},
|
|
|
dataZoom: [
|
|
|
@@ -240,7 +322,7 @@ const initBalanceChart = (data: any) => {
|
|
|
type: "value",
|
|
|
boundaryGap: [0, "100%"],
|
|
|
min: yMinData,
|
|
|
- interval: 1,
|
|
|
+ splitNumber: 5,
|
|
|
},
|
|
|
series: {
|
|
|
name: "Balance",
|
|
|
@@ -249,33 +331,22 @@ const initBalanceChart = (data: any) => {
|
|
|
data: sData,
|
|
|
},
|
|
|
};
|
|
|
- balanceChart.value.setOption(balanceChartOption);
|
|
|
+ profitChart.value.setOption(profitChartOption);
|
|
|
};
|
|
|
|
|
|
timer.value = setInterval(() => {
|
|
|
- getBalanceInfo(robotDetail.value.accId);
|
|
|
- getLogsInfo();
|
|
|
-}, 5000);
|
|
|
-
|
|
|
-getLogsInfo();
|
|
|
+ getProfitInfo(robotDetail.value.id);
|
|
|
+ // getLogsInfo();
|
|
|
+}, 10000);
|
|
|
|
|
|
-const handleReloadIframe = () => {
|
|
|
- robotRunDataIsShow.value = true;
|
|
|
- robotRunData.value = ``;
|
|
|
- setTimeout(() => {
|
|
|
- robotRunData.value = `https://4lapi.skyfffire.com/downloadDataFile?url=http://${robotDetail.value.serverIp}:3000/downloadDataFile?id=${robotDetail.value.id}`; // 重新赋值
|
|
|
- }, 0);
|
|
|
-};
|
|
|
-const handleOtherOpen = () => {
|
|
|
- window.open(`https://4lapi.skyfffire.com/downloadDataFile?url=http://${robotDetail.value.serverIp}:3000/downloadDataFile?id=${robotDetail.value.id}`, "_blank");
|
|
|
-};
|
|
|
+// getLogsInfo();
|
|
|
|
|
|
onMounted(() => {
|
|
|
- getRobotDetail();
|
|
|
+ getCtaRobotDetail();
|
|
|
});
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
- window.removeEventListener("resize", () => balanceChart.value.resize());
|
|
|
+ window.removeEventListener("resize", () => profitChart.value.resize());
|
|
|
clearInterval(timer.value);
|
|
|
predictorChart.value.dispose();
|
|
|
});
|
|
|
@@ -315,6 +386,7 @@ onUnmounted(() => {
|
|
|
height: 1200px;
|
|
|
}
|
|
|
.robot-info-header {
|
|
|
+ min-width: 1352px;
|
|
|
background-color: white;
|
|
|
padding: 16px 24px;
|
|
|
margin-bottom: 20px;
|