mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 14:55:16 +00:00
Merge pull request #625 from c9s/feature/backtest-report
feature: web-based back-test report - add mantine UI framework
This commit is contained in:
commit
038781a094
|
@ -1,9 +1,125 @@
|
||||||
import React, {useEffect, useState} from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
|
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
import TradingViewChart from './TradingViewChart';
|
import TradingViewChart from './TradingViewChart';
|
||||||
|
|
||||||
import {Container} from '@nextui-org/react';
|
import {BalanceMap, ReportSummary} from "../types";
|
||||||
import {ReportSummary} from "../types";
|
|
||||||
|
import {
|
||||||
|
Badge,
|
||||||
|
Container,
|
||||||
|
createStyles,
|
||||||
|
Grid,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
SimpleGrid,
|
||||||
|
Skeleton,
|
||||||
|
Table,
|
||||||
|
Text,
|
||||||
|
ThemeIcon,
|
||||||
|
Title
|
||||||
|
} from '@mantine/core';
|
||||||
|
|
||||||
|
import {ArrowDownRight, ArrowUpRight,} from 'tabler-icons-react';
|
||||||
|
|
||||||
|
const useStyles = createStyles((theme) => ({
|
||||||
|
root: {
|
||||||
|
paddingTop: theme.spacing.xl * 1.5,
|
||||||
|
paddingBottom: theme.spacing.xl * 1.5,
|
||||||
|
},
|
||||||
|
|
||||||
|
label: {
|
||||||
|
fontFamily: `Greycliff CF, ${theme.fontFamily}`,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface StatsGridIconsProps {
|
||||||
|
data: {
|
||||||
|
title: string;
|
||||||
|
value: string;
|
||||||
|
diff?: number
|
||||||
|
dir?: string;
|
||||||
|
desc?: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function StatsGridIcons({data}: StatsGridIconsProps) {
|
||||||
|
const {classes} = useStyles();
|
||||||
|
const stats = data.map((stat) => {
|
||||||
|
const DiffIcon = stat.diff && stat.diff > 0 ? ArrowUpRight : ArrowDownRight;
|
||||||
|
const DirIcon = stat.dir && stat.dir == "up" ? ArrowUpRight : ArrowDownRight;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper withBorder p="xs" radius="md" key={stat.title}>
|
||||||
|
<Group position="apart">
|
||||||
|
<div>
|
||||||
|
<Text
|
||||||
|
color="dimmed"
|
||||||
|
transform="uppercase"
|
||||||
|
weight={700}
|
||||||
|
size="xs"
|
||||||
|
className={classes.label}
|
||||||
|
>
|
||||||
|
{stat.title}
|
||||||
|
</Text>
|
||||||
|
<Text weight={700} size="xl">
|
||||||
|
{stat.value}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{stat.dir ?
|
||||||
|
<ThemeIcon
|
||||||
|
color="gray"
|
||||||
|
variant="light"
|
||||||
|
sx={(theme) => ({color: stat.dir == "up" ? theme.colors.teal[6] : theme.colors.red[6]})}
|
||||||
|
size={38}
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
<DirIcon size={28}/>
|
||||||
|
</ThemeIcon>
|
||||||
|
: null}
|
||||||
|
|
||||||
|
{stat.diff ?
|
||||||
|
<ThemeIcon
|
||||||
|
color="gray"
|
||||||
|
variant="light"
|
||||||
|
sx={(theme) => ({color: stat.diff && stat.diff > 0 ? theme.colors.teal[6] : theme.colors.red[6]})}
|
||||||
|
size={38}
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
<DiffIcon size={28}/>
|
||||||
|
</ThemeIcon>
|
||||||
|
: null}
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{stat.diff ?
|
||||||
|
<Text color="dimmed" size="sm" mt="md">
|
||||||
|
<Text component="span" color={stat.diff && stat.diff > 0 ? 'teal' : 'red'} weight={700}>
|
||||||
|
{stat.diff}%
|
||||||
|
</Text>{' '}
|
||||||
|
{stat.diff && stat.diff > 0 ? 'increase' : 'decrease'} compared to last month
|
||||||
|
</Text> : null}
|
||||||
|
|
||||||
|
{stat.desc ? (
|
||||||
|
<Text color="dimmed" size="sm" mt="md">
|
||||||
|
{stat.desc}
|
||||||
|
</Text>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.root}>
|
||||||
|
<SimpleGrid cols={4} breakpoints={[{maxWidth: 'sm', cols: 1}]}>
|
||||||
|
{stats}
|
||||||
|
</SimpleGrid>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
interface ReportDetailsProps {
|
interface ReportDetailsProps {
|
||||||
basePath: string;
|
basePath: string;
|
||||||
|
@ -20,6 +136,32 @@ const fetchReportSummary = (basePath: string, runID: string) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const skeleton = <Skeleton height={140} radius="md" animate={false}/>;
|
||||||
|
|
||||||
|
|
||||||
|
interface BalanceDetailsProps {
|
||||||
|
balances: BalanceMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BalanceDetails = (props: BalanceDetailsProps) => {
|
||||||
|
const rows = Object.entries(props.balances).map(([k, v]) => {
|
||||||
|
return <tr key={k}>
|
||||||
|
<td>{k}</td>
|
||||||
|
<td>{v.available}</td>
|
||||||
|
</tr>;
|
||||||
|
});
|
||||||
|
|
||||||
|
return <Table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Currency</th>
|
||||||
|
<th>Balance</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>{rows}</tbody>
|
||||||
|
</Table>;
|
||||||
|
};
|
||||||
|
|
||||||
const ReportDetails = (props: ReportDetailsProps) => {
|
const ReportDetails = (props: ReportDetailsProps) => {
|
||||||
const [reportSummary, setReportSummary] = useState<ReportSummary>()
|
const [reportSummary, setReportSummary] = useState<ReportSummary>()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -30,22 +172,78 @@ const ReportDetails = (props: ReportDetailsProps) => {
|
||||||
}, [props.runID])
|
}, [props.runID])
|
||||||
|
|
||||||
if (!reportSummary) {
|
if (!reportSummary) {
|
||||||
return <Container>
|
return <div>
|
||||||
<h2>Loading {props.runID}</h2>
|
<h2>Loading {props.runID}</h2>
|
||||||
</Container>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Container>
|
const totalProfit = Math.round(reportSummary.symbolReports.map((report) => report.pnl.profit).reduce((prev, cur) => prev + cur) * 100) / 100
|
||||||
<h2>Back-test Run {props.runID}</h2>
|
const totalUnrealizedProfit = Math.round(reportSummary.symbolReports.map((report) => report.pnl.unrealizedProfit).reduce((prev, cur) => prev + cur) * 100) / 100
|
||||||
<div>
|
const totalTrades = reportSummary.symbolReports.map((report) => report.pnl.numTrades).reduce((prev, cur) => prev + cur)
|
||||||
{
|
|
||||||
reportSummary.symbols.map((symbol: string) => {
|
|
||||||
return <TradingViewChart basePath={props.basePath} runID={props.runID} reportSummary={reportSummary} symbol={symbol} intervals={["1m", "5m", "1h"]}/>
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
</div>
|
const totalBuyVolume = reportSummary.symbolReports.map((report) => report.pnl.buyVolume).reduce((prev, cur) => prev + cur)
|
||||||
</Container>;
|
const totalSellVolume = reportSummary.symbolReports.map((report) => report.pnl.sellVolume).reduce((prev, cur) => prev + cur)
|
||||||
|
|
||||||
|
const volumeUnit = reportSummary.symbolReports.length == 1 ? reportSummary.symbolReports[0].market.baseCurrency : '';
|
||||||
|
|
||||||
|
// reportSummary.startTime
|
||||||
|
|
||||||
|
return <div>
|
||||||
|
<Container my="md" mx="xs">
|
||||||
|
<Title order={2}>RUN {props.runID}</Title>
|
||||||
|
<div>
|
||||||
|
{reportSummary.sessions.map((session) => <Badge key={session}>Exchange {session}</Badge>)}
|
||||||
|
{reportSummary.symbols.map((symbol) => <Badge key={symbol}>{symbol}</Badge>)}
|
||||||
|
|
||||||
|
<Badge>{reportSummary.startTime.toString()} ~ {reportSummary.endTime.toString()}</Badge>
|
||||||
|
<Badge>{
|
||||||
|
moment.duration((new Date(reportSummary.endTime)).getTime() - (new Date(reportSummary.startTime)).getTime()).humanize()
|
||||||
|
}</Badge>
|
||||||
|
</div>
|
||||||
|
<StatsGridIcons data={[
|
||||||
|
{title: "Profit", value: "$" + totalProfit.toString(), dir: totalProfit > 0 ? "up" : "down"},
|
||||||
|
{
|
||||||
|
title: "Unrealized Profit",
|
||||||
|
value: "$" + totalUnrealizedProfit.toString(),
|
||||||
|
dir: totalUnrealizedProfit > 0 ? "up" : "down"
|
||||||
|
},
|
||||||
|
{title: "Trades", value: totalTrades.toString()},
|
||||||
|
{title: "Buy Volume", value: totalBuyVolume.toString() + ` ${volumeUnit}`},
|
||||||
|
{title: "Sell Volume", value: totalSellVolume.toString() + ` ${volumeUnit}`},
|
||||||
|
]}/>
|
||||||
|
|
||||||
|
<Grid p={"xs"} mb={"lg"}>
|
||||||
|
<Grid.Col xs={6}>
|
||||||
|
<Title order={5}>Initial Total Balances</Title>
|
||||||
|
<BalanceDetails balances={reportSummary.initialTotalBalances}/>
|
||||||
|
</Grid.Col>
|
||||||
|
<Grid.Col xs={6}>
|
||||||
|
<Title order={5}>Final Total Balances</Title>
|
||||||
|
<BalanceDetails balances={reportSummary.finalTotalBalances}/>
|
||||||
|
</Grid.Col>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
<Grid>
|
||||||
|
<Grid.Col span={6}>
|
||||||
|
<Skeleton height={300} radius="md" animate={false}/>
|
||||||
|
</Grid.Col>
|
||||||
|
<Grid.Col xs={4}>{skeleton}</Grid.Col>
|
||||||
|
</Grid>
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
reportSummary.symbols.map((symbol: string, i: number) => {
|
||||||
|
return <TradingViewChart key={i} basePath={props.basePath} runID={props.runID} reportSummary={reportSummary}
|
||||||
|
symbol={symbol} intervals={["1m", "5m", "1h"]}/>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</Container>
|
||||||
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ReportDetails;
|
export default ReportDetails;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import React, {useEffect, useState} from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import {Link} from '@nextui-org/react';
|
import {List, ThemeIcon} from '@mantine/core';
|
||||||
|
import {CircleCheck} from 'tabler-icons-react';
|
||||||
|
|
||||||
import {ReportEntry, ReportIndex} from '../types';
|
import {ReportEntry, ReportIndex} from '../types';
|
||||||
|
|
||||||
function fetchIndex(basePath: string, setter: (data: any) => void) {
|
function fetchIndex(basePath: string, setter: (data: any) => void) {
|
||||||
|
@ -39,16 +41,35 @@ const ReportNavigator = (props: ReportNavigatorProps) => {
|
||||||
return <div>No back-test report data</div>
|
return <div>No back-test report data</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div>
|
return <div className={"report-navigator"}>
|
||||||
{
|
<List
|
||||||
reportIndex.runs.map((entry) => {
|
spacing="xs"
|
||||||
return <Link key={entry.id} onClick={() => {
|
size="xs"
|
||||||
if (props.onSelect) {
|
center
|
||||||
props.onSelect(entry);
|
icon={
|
||||||
}
|
<ThemeIcon color="teal" size={24} radius="xl">
|
||||||
}}>{entry.id}</Link>
|
<CircleCheck size={16}/>
|
||||||
})
|
</ThemeIcon>
|
||||||
}
|
}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
reportIndex.runs.map((entry) => {
|
||||||
|
return <List.Item key={entry.id} onClick={() => {
|
||||||
|
if (props.onSelect) {
|
||||||
|
props.onSelect(entry);
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
"textOverflow": "ellipsis",
|
||||||
|
"overflow": "hidden",
|
||||||
|
"inlineSize": "190px",
|
||||||
|
}}>
|
||||||
|
{entry.id}
|
||||||
|
</div>
|
||||||
|
</List.Item>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</List>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
import React, {useEffect, useRef, useState} from 'react';
|
import React, {useEffect, useRef, useState} from 'react';
|
||||||
import {tsvParse} from "d3-dsv";
|
import {tsvParse} from "d3-dsv";
|
||||||
|
import { Button } from '@mantine/core';
|
||||||
|
|
||||||
// https://github.com/tradingview/lightweight-charts/issues/543
|
// https://github.com/tradingview/lightweight-charts/issues/543
|
||||||
// const createChart = dynamic(() => import('lightweight-charts'));
|
// const createChart = dynamic(() => import('lightweight-charts'));
|
||||||
import {createChart, CrosshairMode} from 'lightweight-charts';
|
import {createChart, CrosshairMode} from 'lightweight-charts';
|
||||||
import {Button} from "@nextui-org/react";
|
|
||||||
|
|
||||||
// const parseDate = timeParse("%Y-%m-%d");
|
|
||||||
|
|
||||||
const parseKline = () => {
|
const parseKline = () => {
|
||||||
return (d) => {
|
return (d) => {
|
||||||
|
@ -426,15 +424,15 @@ const TradingViewChart = (props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button.Group>
|
<span>
|
||||||
{intervals.map((interval) => {
|
{intervals.map((interval) => {
|
||||||
return <Button size="xs" key={interval} onPress={(e) => {
|
return <Button key={interval} compact onClick={(e) => {
|
||||||
setCurrentInterval(interval)
|
setCurrentInterval(interval)
|
||||||
}}>
|
}}>
|
||||||
{interval}
|
{interval}
|
||||||
</Button>
|
</Button>
|
||||||
})}
|
})}
|
||||||
</Button.Group>
|
</span>
|
||||||
<div ref={chartContainerRef} style={{'flex': 1, 'minHeight': 300}}>
|
<div ref={chartContainerRef} style={{'flex': 1, 'minHeight': 300}}>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,14 +9,18 @@
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nextui-org/react": "^1.0.0-beta.7",
|
"@mantine/core": "^4.2.5",
|
||||||
|
"@mantine/hooks": "^4.2.5",
|
||||||
|
"@mantine/next": "^4.2.5",
|
||||||
"d3-dsv": "^3.0.1",
|
"d3-dsv": "^3.0.1",
|
||||||
"d3-format": "^3.1.0",
|
"d3-format": "^3.1.0",
|
||||||
"d3-time-format": "^4.1.0",
|
"d3-time-format": "^4.1.0",
|
||||||
"lightweight-charts": "^3.8.0",
|
"lightweight-charts": "^3.8.0",
|
||||||
|
"moment": "^2.29.3",
|
||||||
"next": "12.1.6",
|
"next": "12.1.6",
|
||||||
"react": "18.1.0",
|
"react": "18.1.0",
|
||||||
"react-dom": "18.1.0"
|
"react-dom": "18.1.0",
|
||||||
|
"tabler-icons-react": "^1.48.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/d3-dsv": "^3.0.0",
|
"@types/d3-dsv": "^3.0.0",
|
||||||
|
|
|
@ -1,11 +1,26 @@
|
||||||
import '../styles/globals.css'
|
import '../styles/globals.css'
|
||||||
import type {AppProps} from 'next/app'
|
import type {AppProps} from 'next/app'
|
||||||
import {NextUIProvider} from '@nextui-org/react'
|
import Head from 'next/head';
|
||||||
|
|
||||||
|
import { MantineProvider } from '@mantine/core';
|
||||||
|
|
||||||
function MyApp({Component, pageProps}: AppProps) {
|
function MyApp({Component, pageProps}: AppProps) {
|
||||||
return <NextUIProvider>
|
return <>
|
||||||
<Component {...pageProps} />
|
<Head>
|
||||||
</NextUIProvider>
|
<title>Page title</title>
|
||||||
|
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
|
||||||
|
</Head>
|
||||||
|
<MantineProvider
|
||||||
|
withGlobalStyles
|
||||||
|
withNormalizeCSS
|
||||||
|
theme={{
|
||||||
|
/** Put your mantine theme override here */
|
||||||
|
colorScheme: 'light',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</MantineProvider>
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MyApp
|
export default MyApp
|
||||||
|
|
|
@ -1,24 +1,41 @@
|
||||||
import Document, {DocumentContext, Head, Html, Main, NextScript} from 'next/document';
|
import Document, {DocumentContext, Head, Html, Main, NextScript} from 'next/document';
|
||||||
import {CssBaseline} from '@nextui-org/react';
|
|
||||||
|
// ----- mantine setup
|
||||||
|
import {createStylesServer, ServerStyles} from '@mantine/next';
|
||||||
|
import {DocumentInitialProps} from "next/dist/shared/lib/utils";
|
||||||
|
|
||||||
|
// const getInitialProps = createGetInitialProps();
|
||||||
|
const stylesServer = createStylesServer();
|
||||||
|
// -----
|
||||||
|
|
||||||
class MyDocument extends Document {
|
class MyDocument extends Document {
|
||||||
static async getInitialProps(ctx: DocumentContext) {
|
// this is for mantine
|
||||||
|
// static getInitialProps = getInitialProps;
|
||||||
|
|
||||||
|
static async getInitialProps(ctx: DocumentContext): Promise<DocumentInitialProps> {
|
||||||
const initialProps = await Document.getInitialProps(ctx);
|
const initialProps = await Document.getInitialProps(ctx);
|
||||||
|
|
||||||
// @ts-ignore
|
return {
|
||||||
initialProps.styles = <>{initialProps.styles}</>;
|
...initialProps,
|
||||||
return initialProps;
|
|
||||||
|
// use bracket [] instead of () to fix the type error
|
||||||
|
styles: [
|
||||||
|
<>
|
||||||
|
{initialProps.styles}
|
||||||
|
<ServerStyles html={initialProps.html} server={stylesServer}/>
|
||||||
|
</>
|
||||||
|
],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Html lang="en">
|
<Html lang="en">
|
||||||
<Head>
|
<Head>
|
||||||
{CssBaseline.flush()}
|
|
||||||
</Head>
|
</Head>
|
||||||
<body>
|
<body>
|
||||||
<Main/>
|
<Main/>
|
||||||
<NextScript/>
|
<NextScript/>
|
||||||
</body>
|
</body>
|
||||||
</Html>
|
</Html>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,27 +1,48 @@
|
||||||
import type {NextPage} from 'next'
|
import type {NextPage} from 'next'
|
||||||
import Head from 'next/head'
|
import Head from 'next/head'
|
||||||
import styles from '../styles/Home.module.css'
|
import styles from '../styles/Home.module.css'
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import {AppShell, Header, Navbar, Text} from '@mantine/core';
|
||||||
|
|
||||||
import ReportDetails from '../components/ReportDetails';
|
import ReportDetails from '../components/ReportDetails';
|
||||||
import ReportNavigator from '../components/ReportNavigator';
|
import ReportNavigator from '../components/ReportNavigator';
|
||||||
import {useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
|
|
||||||
const Home: NextPage = () => {
|
const Home: NextPage = () => {
|
||||||
const [currentReport, setCurrentReport] = useState<any>();
|
const [currentReport, setCurrentReport] = useState<any>();
|
||||||
|
const { query } = useRouter();
|
||||||
|
const basePath = query.basePath ? query.basePath as string : '/output';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div>
|
||||||
<Head>
|
<Head>
|
||||||
<title>Back-Test Report</title>
|
<title>BBGO Back-Test Report</title>
|
||||||
<meta name="description" content="Generated by create next app"/>
|
<meta name="description" content="Generated by create next app"/>
|
||||||
<link rel="icon" href="/favicon.ico"/>
|
<link rel="icon" href="/favicon.ico"/>
|
||||||
</Head>
|
</Head>
|
||||||
<main className={styles.main}>
|
<main className={styles.main}>
|
||||||
<ReportNavigator onSelect={(reportEntry) => {
|
<AppShell
|
||||||
setCurrentReport(reportEntry)
|
padding="md"
|
||||||
}}/>
|
navbar={<Navbar width={{base: 250}} height={500} p="xs">
|
||||||
{
|
|
||||||
currentReport ? <ReportDetails basePath={'/output'} runID={currentReport.id}/> : null
|
<ReportNavigator onSelect={(reportEntry) => {
|
||||||
}
|
setCurrentReport(reportEntry)
|
||||||
|
}}/>
|
||||||
|
|
||||||
|
</Navbar>}
|
||||||
|
header={
|
||||||
|
<Header height={60} p="md">
|
||||||
|
<Text>BBGO Back-Test Report</Text>
|
||||||
|
</Header>
|
||||||
|
}
|
||||||
|
styles={(theme) => ({
|
||||||
|
main: {backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[8] : theme.colors.gray[0]},
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
currentReport ? <ReportDetails basePath={basePath} runID={currentReport.id}/> : null
|
||||||
|
}
|
||||||
|
</AppShell>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,25 +1,3 @@
|
||||||
import { tsvParse, csvParse } from "d3-dsv";
|
import { tsvParse, csvParse } from "d3-dsv";
|
||||||
import { timeParse } from "d3-time-format";
|
import { timeParse } from "d3-time-format";
|
||||||
|
|
||||||
function parseData(parse) {
|
|
||||||
return function(d) {
|
|
||||||
d.date = parse(d.date);
|
|
||||||
d.open = +d.open;
|
|
||||||
d.high = +d.high;
|
|
||||||
d.low = +d.low;
|
|
||||||
d.close = +d.close;
|
|
||||||
d.volume = +d.volume;
|
|
||||||
|
|
||||||
return d;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseDate = timeParse("%Y-%m-%d");
|
|
||||||
|
|
||||||
export function getData() {
|
|
||||||
// original source: https://cdn.rawgit.com/rrag/react-stockcharts/master/docs/data/MSFT.tsv
|
|
||||||
const promiseMSFT = fetch("https://cdn.jsdelivr.net/gh/rrag/react-stockcharts@master/docs/data/MSFT.tsv")
|
|
||||||
.then(response => response.text())
|
|
||||||
.then(data => tsvParse(data, parseData(parseDate)))
|
|
||||||
return promiseMSFT;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
.container {
|
|
||||||
padding: 0 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
padding: 2rem 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user