From 746a3a564a4af0d616c283582066922f62e2f4b2 Mon Sep 17 00:00:00 2001 From: UniverseDolphin Date: Sun, 26 May 2024 13:55:37 +0900 Subject: [PATCH] =?UTF-8?q?[feat]:=EB=B0=B1=ED=85=8C=EC=8A=A4=ED=8C=85=20?= =?UTF-8?q?=EB=B3=B4=EA=B3=A0=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=BD=94?= =?UTF-8?q?=EB=A9=98=ED=8A=B8=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/BackTestCircleChart.jsx | 63 ----------- frontend/src/components/CoinPriceChart.jsx | 65 +++++++++++ frontend/src/components/TradingLogTable.jsx | 51 +++++++-- .../src/components/TradingProfitRateChart.jsx | 61 +++++++++++ frontend/src/components/WinLossChart.jsx | 96 +++++++++++++++++ .../layouts/DashBoard/Run/BackTestChart.jsx | 102 +++++++++--------- .../layouts/DashBoard/Run/BackTestTable.jsx | 54 ++++++++-- .../DashBoard/Strategy/StrategyChart.jsx | 2 +- .../DashBoard/Strategy/StrategySetting.jsx | 14 ++- frontend/src/pages/DashBoard.jsx | 4 + frontend/src/pages/LoginHome.jsx | 9 +- frontend/src/pages/Side/Portfolio.jsx | 18 ++++ frontend/src/pages/Side/Run.jsx | 14 +-- frontend/src/utils/token.js | 17 +++ frontend/src/utils/useResponseStore.js | 2 +- 15 files changed, 425 insertions(+), 147 deletions(-) delete mode 100644 frontend/src/components/BackTestCircleChart.jsx create mode 100644 frontend/src/components/CoinPriceChart.jsx create mode 100644 frontend/src/components/TradingProfitRateChart.jsx create mode 100644 frontend/src/components/WinLossChart.jsx create mode 100644 frontend/src/utils/token.js diff --git a/frontend/src/components/BackTestCircleChart.jsx b/frontend/src/components/BackTestCircleChart.jsx deleted file mode 100644 index b163615..0000000 --- a/frontend/src/components/BackTestCircleChart.jsx +++ /dev/null @@ -1,63 +0,0 @@ -import ReactEcharts from "echarts-for-react"; -import { useEffect, useState } from "react"; -import useResponseStore from '../../utils/useResponseStore'; - - -const BackTestCircleChart = () => { - - const { responseBackTest } = useResponseStore(); - const [positiveTradeCount, setPositiveTradeCount] = useState(); - const [negativeTradeCount, setnegativeTradeCount] = useState(); - const {trading} = responseBackTest.payload; - useEffect(() =>{ - setPositiveTradeCount(trading.positive_trade_count) - setnegativeTradeCount(trading.negative_trade_count) - }, [trading.positive_trade_count,trading.negative_trade_count]) - - const option = { - tooltip: { - trigger: 'item' - }, - legend: { - top: '5%', - left: 'center' - }, - series: [ - { - name: 'Access From', - type: 'pie', - radius: ['40%', '70%'], - avoidLabelOverlap: false, - padAngle: 5, - itemStyle: { - borderRadius: 10 - }, - label: { - show: false, - position: 'center' - }, - emphasis: { - label: { - show: true, - fontSize: 40, - fontWeight: 'bold' - } - }, - labelLine: { - show: false - }, - data: [ - { value: positiveTradeCount, name: 'Positive Trade' }, - { value: negativeTradeCount, name: 'Negative Trade' }, - ] - } - ] - }; - - - return ( - - ) -} - -export default BackTestCircleChart \ No newline at end of file diff --git a/frontend/src/components/CoinPriceChart.jsx b/frontend/src/components/CoinPriceChart.jsx new file mode 100644 index 0000000..c41beba --- /dev/null +++ b/frontend/src/components/CoinPriceChart.jsx @@ -0,0 +1,65 @@ +import ReactEcharts from "echarts-for-react"; +import useResponseStore from '../utils/useResponseStore'; + +const CoinPriceChart = () => { + + const { responseBackTest } = useResponseStore(); + + const { + trading_logs + } = responseBackTest.payload; + + const option = { + legend: { + data: ['매수', '매도'] + }, + xAxis: { + type: 'category', + boundaryGap: false, + axisLine: { + onZero: false + }, + data: trading_logs.map((log) =>{ + const dateObj = new Date(log.date) + const date = dateObj.toLocaleDateString() + return date + }) + }, + yAxis: { + axisLabel: { + formatter: function(value) { + if(value >= 1000000) return (value / 1000000) + 'M' + if(value >= 1000) return (value / 1000000) + 'K' + return value + } + } + }, + series: [ + { + name: "매수", + type: "bar", + data: trading_logs.map((log) => { + if(log.type === '매수') return log.coin_price + return 0 + }) + }, + { + name: '매도', + type: 'bar', + data: trading_logs.map((log) => { + if(log.type === '매도') return log.coin_price + return 0 + }) + } + ] + } + + return ( +
+ +
+ + ) +} + +export default CoinPriceChart diff --git a/frontend/src/components/TradingLogTable.jsx b/frontend/src/components/TradingLogTable.jsx index 306acad..e27f105 100644 --- a/frontend/src/components/TradingLogTable.jsx +++ b/frontend/src/components/TradingLogTable.jsx @@ -1,28 +1,65 @@ -const TradingLogTable = ({trading_logs}) => { +import { useState } from 'react'; + +const TradingLogTable = ({ trading_logs }) => { + const [sortConfig, setSortConfig] = useState({ key: null, direction: 'ascending' }); + + const sortedLogs = [...trading_logs].sort((a, b) => { + if (sortConfig.key === null) return 0; + + const aValue = a[sortConfig.key]; + const bValue = b[sortConfig.key]; + + if (sortConfig.key === 'date') { + const aDate = new Date(aValue); + const bDate = new Date(bValue); + if (sortConfig.direction === 'ascending') { + return aDate - bDate; + } else { + return bDate - aDate; + } + } + + if (aValue < bValue) { + return sortConfig.direction === 'ascending' ? -1 : 1; + } + if (aValue > bValue) { + return sortConfig.direction === 'ascending' ? 1 : -1; + } + return 0; + }); + const formatTime = (date) => { const hours = date.getHours(); const minutes = date.getMinutes().toString().padStart(2, '0'); const seconds = date.getSeconds().toString().padStart(2, '0'); const period = hours >= 12 ? 'PM' : 'AM'; - const adjustedHours = hours % 12 || 12; // 0을 12로 변환 + const adjustedHours = hours % 12 || 12; return `${period} ${adjustedHours}:${minutes}:${seconds}`; }; + const requestSort = (key) => { + let direction = 'ascending'; + if (sortConfig.key === key && sortConfig.direction === 'ascending') { + direction = 'descending'; + } + setSortConfig({ key, direction }); + }; + return ( - - + + - - + + - {trading_logs.map((log, index) => { + {sortedLogs.map((log, index) => { const dateObj = new Date(log.date); const date = dateObj.toLocaleDateString(); const time = formatTime(dateObj); diff --git a/frontend/src/components/TradingProfitRateChart.jsx b/frontend/src/components/TradingProfitRateChart.jsx new file mode 100644 index 0000000..574ed12 --- /dev/null +++ b/frontend/src/components/TradingProfitRateChart.jsx @@ -0,0 +1,61 @@ +import ReactEcharts from "echarts-for-react"; +import useResponseStore from '../utils/useResponseStore'; + +const TradingProfitRateChart = () => { + + const { responseBackTest } = useResponseStore(); + + const { + trading_logs + } = responseBackTest.payload; + + const tradeDate = trading_logs.map((log) =>{ + if(log.type === '매도'){ + const dateObj = new Date(log.date) + const date = dateObj.toLocaleDateString() + return date + } + }) + const tradeRate = trading_logs.map((log) => { + if(log.type ==='매도') return log.rate + }) + + const option = { + legend: { + data: ['매도'] + }, + xAxis: { + type: 'category', + boundaryGap: false, + axisLine: { + onZero: false + }, + data: tradeDate.filter(item => item !== undefined) + }, + yAxis: { + scale:true, + axisLabel: { + formatter: function(value) { + return value + } + } + }, + series: [ + { + name: '매도', + type: 'bar', + data: tradeRate.filter(item => item !== undefined) + } + ] + } + + + + return ( +
+ +
+ ) +} + +export default TradingProfitRateChart \ No newline at end of file diff --git a/frontend/src/components/WinLossChart.jsx b/frontend/src/components/WinLossChart.jsx new file mode 100644 index 0000000..366d881 --- /dev/null +++ b/frontend/src/components/WinLossChart.jsx @@ -0,0 +1,96 @@ +import ReactEcharts from "echarts-for-react"; + +const WinLossChart = ({performance}) => { + + const option = { + series: [ + { + type: 'gauge', + startAngle: 180, + endAngle: 0, + center: ['50%', '86%'], + radius: '125%', + min: 0, + max: 100, + splitNumber: 8, + axisLine: { + lineStyle: { + width: 6, + color: [ + [0.25, '#FF6E76'], + [0.5, '#FDDD60'], + [0.75, '#58D9F9'], + [1, '#7CFFB2'] + ] + } + }, + pointer: { + icon: 'path://M12.8,0.7l12,40.1H0.7L12.8,0.7z', + length: '12%', + width: 20, + offsetCenter: [0, '-60%'], + itemStyle: { + color: 'auto' + } + }, + axisTick: { + length: 12, + lineStyle: { + color: 'auto', + width: 2 + } + }, + splitLine: { + length: 20, + lineStyle: { + color: 'auto', + width: 5 + } + }, + axisLabel: { + color: '#464646', + fontSize: 16, + distance: -50, + rotate: 'tangential', + formatter: function (value) { + if (value === 87.5) { + return 'Grade A'; + } else if (value === 62.5) { + return 'Grade B'; + } else if (value === 37.5) { + return 'Grade C'; + } else if (value === 12.5) { + return 'Grade D'; + } + return ''; + } + }, + title: { + offsetCenter: [0, '-10%'], + fontSize: 18 + }, + detail: { + fontSize: 32, + offsetCenter: [0, '-35%'], + valueAnimation: true, + formatter: function (value) { + return Math.round(value) + '%'; + }, + color: 'inherit' + }, + data: [ + { + value: performance.win_rate, + name: 'Win Rating' + } + ] + } + ] + } + + return ( + + ) +} + +export default WinLossChart \ No newline at end of file diff --git a/frontend/src/layouts/DashBoard/Run/BackTestChart.jsx b/frontend/src/layouts/DashBoard/Run/BackTestChart.jsx index e61469d..fd9cb3f 100644 --- a/frontend/src/layouts/DashBoard/Run/BackTestChart.jsx +++ b/frontend/src/layouts/DashBoard/Run/BackTestChart.jsx @@ -1,61 +1,65 @@ -import { FaRegArrowAltCircleUp } from "react-icons/fa"; +import { useState } from 'react'; +import CoinPriceChart from "../../../components/CoinPriceChart"; +import TradingProfitRateChart from "../../../components/TradingProfitRateChart"; +import { useForm } from 'react-hook-form'; +import axios from 'axios'; +import { z } from 'zod'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useMutation } from '@tanstack/react-query'; + +const schema = z.object({ + comment: z.string().min(1, { message: "Comment is required" }).max(200, { message: "Comment must be less than 200 characters" }), +}); const BackTestChart = ({trading, performance, trading_logs}) => { + const { register, handleSubmit, formState: { errors } } = useForm({ + resolver: zodResolver(schema) + }); + const [charCount, setCharCount] = useState(0); + + const mutation = useMutation(commentData => axios.post('http://localhost:8081/api/v1/backtesting/save', commentData)); + + const onSubmit = (data) => { + mutation.mutate(data); + }; - const calProfit = (trading.final_capital)-(trading.initial_capital) + const handleCommentChange = (e) => { + setCharCount(e.target.value.length); + }; - const updownProfitResult = calProfit >= 0 ? "text-green-400": "text-red-400" return ( -
-
-
- {calProfit >= 0 ? "Proceeds":"Loss"} - {calProfit >= 0 ? "+":""}{calProfit} - - {performance.total_rate}% - - -
-
- Trade Period - - {trading.average_trade_period} - days - -
-
- Max Proceeds - +{performance.high_value_strategy} -
-
- Min Proceeds - +{performance.low_value_strategy} -
- {/*
- {optProfitResult ? "Proceeds":"Loss"} - {optProfitResult ? "+":""}{calProfit} - - {performance.total_rate}% - - -
-
- Trade Period - {trading.average_trade_period} days +
+
+ +
+
+
+
-
- Positive Trade +
+
+

Comment

+
+
TypeDate requestSort('type')}>Type requestSort('date')}>Date TimeCoin PriceRate requestSort('coin_price')}>Coin Price requestSort('rate')}>Rate