Merge pull request #724 from c9s/grid-panel

Grid panel draft
This commit is contained in:
Yo-An Lin 2022-06-16 12:03:59 +08:00 committed by GitHub
commit aec1973953
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 229 additions and 37 deletions

View File

@ -17,17 +17,17 @@ export function queryOutboundIP(cb) {
export async function triggerSync() {
return axios.post<any>(baseURL + '/api/environment/sync');
};
}
export enum SyncStatus {
SyncNotStarted = 0,
Syncing = 1,
SyncDone = 2
SyncDone = 2,
}
export async function querySyncStatus(): Promise<SyncStatus> {
const resp = await axios.get<any>(baseURL + '/api/environment/syncing')
return resp.data.syncing
const resp = await axios.get<any>(baseURL + '/api/environment/syncing');
return resp.data.syncing;
}
export function testDatabaseConnection(params, cb) {
@ -130,3 +130,29 @@ export function queryTradingVolume(params, cb) {
cb(response.data.tradingVolumes || []);
});
}
export async function queryStrategiesMetrics() {
return [
{
id: 'uuid',
instanceID: 'testInstanceID',
strategy: 'grid',
grid: {
symbol: 'BTCUSDT',
},
stats: {
oneDayArbs: 0,
totalArbs: 3,
investment: 100,
totalProfits: 5.6,
gridProfits: 2.5,
floatingPNL: 3.1,
currentPrice: 29000,
lowestPrice: 25000,
highestPrice: 35000,
},
status: 'RUNNING',
startTime: 1654938187102,
},
];
}

View File

@ -89,12 +89,14 @@ export default function SideBar() {
<ListItemText primary="Trades" />
</ListItem>
</Link>
<ListItem button>
<ListItemIcon>
<TrendingUpIcon />
</ListItemIcon>
<ListItemText primary="Strategies" />
</ListItem>
<Link href={'/strategies'}>
<ListItem button>
<ListItemIcon>
<TrendingUpIcon />
</ListItemIcon>
<ListItemText primary="Strategies" />
</ListItem>
</Link>
</List>
</Drawer>
);

View File

@ -1,7 +1,7 @@
import {styled} from "@mui/styles";
import React, {useEffect, useState} from "react";
import {querySyncStatus, SyncStatus, triggerSync} from "../api/bbgo";
import useInterval from "../hooks/useInterval";
import { styled } from '@mui/styles';
import React, { useEffect, useState } from 'react';
import { querySyncStatus, SyncStatus, triggerSync } from '../api/bbgo';
import useInterval from '../hooks/useInterval';
const ToolbarButton = styled('button')(({ theme }) => ({
padding: theme.spacing(1),
@ -9,7 +9,7 @@ const ToolbarButton = styled('button')(({ theme }) => ({
export default function SyncButton() {
const [syncing, setSyncing] = useState(false);
const sync = async () => {
try {
setSyncing(true);
@ -18,26 +18,22 @@ export default function SyncButton() {
setSyncing(false);
}
};
useEffect(() => {
sync();
}, [])
}, []);
useInterval(() => {
querySyncStatus().then(s => {
querySyncStatus().then((s) => {
if (s !== SyncStatus.Syncing) {
setSyncing(false);
}
})
}, 2000)
});
}, 2000);
return (
<ToolbarButton
disabled={syncing}
onClick={sync}
>
<ToolbarButton disabled={syncing} onClick={sync}>
{syncing ? 'Syncing...' : 'Sync'}
</ToolbarButton>
);
}

View File

@ -1,20 +1,20 @@
import {useEffect, useRef} from "react";
import { useEffect, useRef } from 'react';
export default function useInterval(cb: Function, delayMs: number | null) {
const savedCallback = useRef<Function>();
useEffect(() => {
savedCallback.current = cb
}, [cb])
savedCallback.current = cb;
}, [cb]);
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delayMs !== null) {
let timerId = setInterval(tick, delayMs)
return () => clearInterval(timerId)
let timerId = setInterval(tick, delayMs);
return () => clearInterval(timerId);
}
}, [delayMs])
}
}, [delayMs]);
}

View File

@ -0,0 +1,168 @@
import DashboardLayout from '../layouts/DashboardLayout';
import { styled } from '@mui/styles';
import { queryStrategiesMetrics } from '../api/bbgo';
import { useEffect, useState } from 'react';
const StrategyContainer = styled('section')(() => ({
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-around',
width: '400px',
height: '400px',
border: '1px solid rgb(248, 149, 35)',
borderRadius: '10px',
padding: '10px',
}));
const Strategy = styled('div')(() => ({
fontSize: '20px',
}));
const StatusSign = styled('span')(() => ({
width: '10px',
height: '10px',
display: 'block',
backgroundColor: 'rgb(113, 218, 113)',
borderRadius: '50%',
marginRight: '5px',
}));
const RunningTime = styled('div')(() => ({
display: 'flex',
alignItems: 'center',
}));
const Description = styled('div')(() => ({
color: 'rgb(140, 140, 140)',
'& .duration': {
marginLeft: '3px',
},
}));
const Summary = styled('div')(() => ({
width: '100%',
display: 'flex',
justifyContent: 'space-around',
backgroundColor: 'rgb(255, 245, 232)',
}));
const SummaryBlock = styled('div')(() => ({
padding: '5px 0 5px 0',
}));
const StatsTitle = styled('div')(() => ({
margin: '0 0 10px 0',
}));
const StatsValue = styled('div')(() => ({
marginBottom: '10px',
color: 'rgb(123, 169, 90)',
}));
const Percentage = styled('div')(() => ({
color: 'rgb(123, 169, 90)',
}));
const Stats = styled('div')(() => ({
display: 'grid',
gridTemplateColumns: '1fr 1fr 1fr',
columnGap: '10px',
rowGap: '20px',
}));
export default function Strategies() {
const [details, setDetails] = useState([]);
useEffect(() => {
queryStrategiesMetrics().then((value) => {
setDetails(value);
});
}, []);
return (
<DashboardLayout>
{details.map((element) => {
return <Detail key={element.id} data={element} />;
})}
</DashboardLayout>
);
}
export function Detail({ data }) {
const { strategy, stats, startTime } = data;
const totalProfitsPercentage = (stats.totalProfits / stats.investment) * 100;
const gridProfitsPercentage = (stats.gridProfits / stats.investment) * 100;
const gridAprPercentage = (stats.gridProfits / 5) * 365;
const now = Date.now();
const durationMilliseconds = now - startTime;
const seconds = durationMilliseconds / 1000;
const day = Math.floor(seconds / (60 * 60 * 24));
const hour = Math.floor((seconds % (60 * 60 * 24)) / 3600);
const min = Math.floor(((seconds % (60 * 60 * 24)) % 3600) / 60);
return (
<StrategyContainer>
<Strategy>{strategy}</Strategy>
<div>{data[strategy].symbol}</div>
<RunningTime>
<StatusSign />
<Description>
Running for
<span className="duration">{day}</span>D
<span className="duration">{hour}</span>H
<span className="duration">{min}</span>M
</Description>
</RunningTime>
<Description>
0 arbitrages in 24 hours / Total <span>{stats.totalArbs}</span>{' '}
arbitrages
</Description>
<Summary>
<SummaryBlock>
<StatsTitle>Investment USDT</StatsTitle>
<div>{stats.investment}</div>
</SummaryBlock>
<SummaryBlock>
<StatsTitle>Total Profit USDT</StatsTitle>
<StatsValue>{stats.totalProfits}</StatsValue>
<Percentage>{totalProfitsPercentage}%</Percentage>
</SummaryBlock>
</Summary>
<Stats>
<div>
<StatsTitle>Grid Profits</StatsTitle>
<StatsValue>{stats.gridProfits}</StatsValue>
<Percentage>{gridProfitsPercentage}%</Percentage>
</div>
<div>
<StatsTitle>Floating PNL</StatsTitle>
<StatsValue>{stats.floatingPNL}</StatsValue>
</div>
<div>
<StatsTitle>Grid APR</StatsTitle>
<Percentage>{gridAprPercentage}%</Percentage>
</div>
<div>
<StatsTitle>Current Price</StatsTitle>
<div>{stats.currentPrice}</div>
</div>
<div>
<StatsTitle>Price Range</StatsTitle>
<div>
{stats.lowestPrice}~{stats.highestPrice}
</div>
</div>
</Stats>
</StrategyContainer>
);
}