Merge pull request #708 from c9s/refactor/format-js

format js code by prettier
This commit is contained in:
YC 2022-06-11 13:59:27 +08:00 committed by GitHub
commit c9e451791e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 2394 additions and 2271 deletions

View File

@ -1,114 +1,121 @@
import axios from "axios"; import axios from 'axios';
const baseURL = process.env.NODE_ENV === "development" ? "http://localhost:8080" : "" const baseURL =
process.env.NODE_ENV === 'development' ? 'http://localhost:8080' : '';
export function ping(cb) { export function ping(cb) {
return axios.get(baseURL + '/api/ping').then(response => { return axios.get(baseURL + '/api/ping').then((response) => {
cb(response.data) cb(response.data);
}); });
} }
export function queryOutboundIP(cb) { export function queryOutboundIP(cb) {
return axios.get(baseURL + '/api/outbound-ip').then(response => { return axios.get(baseURL + '/api/outbound-ip').then((response) => {
cb(response.data.outboundIP) cb(response.data.outboundIP);
}); });
} }
export function querySyncStatus(cb) { export function querySyncStatus(cb) {
return axios.get(baseURL + '/api/environment/syncing').then(response => { return axios.get(baseURL + '/api/environment/syncing').then((response) => {
cb(response.data.syncing) cb(response.data.syncing);
}); });
} }
export function testDatabaseConnection(params, cb) { export function testDatabaseConnection(params, cb) {
return axios.post(baseURL + '/api/setup/test-db', params).then(response => { return axios.post(baseURL + '/api/setup/test-db', params).then((response) => {
cb(response.data) cb(response.data);
}); });
} }
export function configureDatabase(params, cb) { export function configureDatabase(params, cb) {
return axios.post(baseURL + '/api/setup/configure-db', params).then(response => { return axios
cb(response.data) .post(baseURL + '/api/setup/configure-db', params)
.then((response) => {
cb(response.data);
}); });
} }
export function saveConfig(cb) { export function saveConfig(cb) {
return axios.post(baseURL + '/api/setup/save').then(response => { return axios.post(baseURL + '/api/setup/save').then((response) => {
cb(response.data) cb(response.data);
}); });
} }
export function setupRestart(cb) { export function setupRestart(cb) {
return axios.post(baseURL + '/api/setup/restart').then(response => { return axios.post(baseURL + '/api/setup/restart').then((response) => {
cb(response.data) cb(response.data);
}); });
} }
export function addSession(session, cb) { export function addSession(session, cb) {
return axios.post(baseURL + '/api/sessions', session).then(response => { return axios.post(baseURL + '/api/sessions', session).then((response) => {
cb(response.data || []) cb(response.data || []);
}); });
} }
export function attachStrategyOn(session, strategyID, strategy, cb) { export function attachStrategyOn(session, strategyID, strategy, cb) {
return axios.post(baseURL + `/api/setup/strategy/single/${strategyID}/session/${session}`, strategy).then(response => { return axios
cb(response.data) .post(
baseURL + `/api/setup/strategy/single/${strategyID}/session/${session}`,
strategy
)
.then((response) => {
cb(response.data);
}); });
} }
export function testSessionConnection(session, cb) { export function testSessionConnection(session, cb) {
return axios.post(baseURL + '/api/sessions/test', session).then(response => { return axios
cb(response.data) .post(baseURL + '/api/sessions/test', session)
.then((response) => {
cb(response.data);
}); });
} }
export function queryStrategies(cb) { export function queryStrategies(cb) {
return axios.get(baseURL + '/api/strategies/single').then(response => { return axios.get(baseURL + '/api/strategies/single').then((response) => {
cb(response.data.strategies || []) cb(response.data.strategies || []);
}); });
} }
export function querySessions(cb) { export function querySessions(cb) {
return axios.get(baseURL + '/api/sessions', {}) return axios.get(baseURL + '/api/sessions', {}).then((response) => {
.then(response => { cb(response.data.sessions || []);
cb(response.data.sessions || [])
}); });
} }
export function querySessionSymbols(sessionName, cb) { export function querySessionSymbols(sessionName, cb) {
return axios.get(baseURL + `/api/sessions/${sessionName}/symbols`, {}) return axios
.then(response => { .get(baseURL + `/api/sessions/${sessionName}/symbols`, {})
cb(response.data.symbols || []) .then((response) => {
cb(response.data.symbols || []);
}); });
} }
export function queryTrades(params, cb) { export function queryTrades(params, cb) {
axios.get(baseURL + '/api/trades', {params: params}) axios.get(baseURL + '/api/trades', { params: params }).then((response) => {
.then(response => { cb(response.data.trades || []);
cb(response.data.trades || [])
}); });
} }
export function queryClosedOrders(params, cb) { export function queryClosedOrders(params, cb) {
axios.get(baseURL + '/api/orders/closed', {params: params}) axios
.then(response => { .get(baseURL + '/api/orders/closed', { params: params })
cb(response.data.orders || []) .then((response) => {
cb(response.data.orders || []);
}); });
} }
export function queryAssets(cb) { export function queryAssets(cb) {
axios.get(baseURL + '/api/assets', {}) axios.get(baseURL + '/api/assets', {}).then((response) => {
.then(response => { cb(response.data.assets || []);
cb(response.data.assets || [])
}); });
} }
export function queryTradingVolume(params, cb) { export function queryTradingVolume(params, cb) {
axios.get(baseURL + '/api/trading-volume', {params: params}) axios
.then(response => { .get(baseURL + '/api/trading-volume', { params: params })
cb(response.data.tradingVolumes || []) .then((response) => {
cb(response.data.tradingVolumes || []);
}); });
} }

View File

@ -38,7 +38,7 @@ const useStyles = makeStyles((theme) => ({
paddingBottom: theme.spacing(2), paddingBottom: theme.spacing(2),
'& > *': { '& > *': {
marginLeft: theme.spacing(1), marginLeft: theme.spacing(1),
} },
}, },
})); }));
@ -60,16 +60,16 @@ export default function AddExchangeSessionForm({onBack, onAdded}) {
const [isMargin, setIsMargin] = React.useState(false); const [isMargin, setIsMargin] = React.useState(false);
const [isIsolatedMargin, setIsIsolatedMargin] = React.useState(false); const [isIsolatedMargin, setIsIsolatedMargin] = React.useState(false);
const [isolatedMarginSymbol, setIsolatedMarginSymbol] = React.useState(""); const [isolatedMarginSymbol, setIsolatedMarginSymbol] = React.useState('');
const resetTestResponse = () => { const resetTestResponse = () => {
setTestResponse(null) setTestResponse(null);
} };
const handleExchangeTypeChange = (event) => { const handleExchangeTypeChange = (event) => {
setExchangeType(event.target.value); setExchangeType(event.target.value);
setSessionName(event.target.value); setSessionName(event.target.value);
resetTestResponse() resetTestResponse();
}; };
const createSessionConfig = () => { const createSessionConfig = () => {
@ -82,34 +82,34 @@ export default function AddExchangeSessionForm({onBack, onAdded}) {
envVarPrefix: exchangeType.toUpperCase(), envVarPrefix: exchangeType.toUpperCase(),
isolatedMargin: isIsolatedMargin, isolatedMargin: isIsolatedMargin,
isolatedMarginSymbol: isolatedMarginSymbol, isolatedMarginSymbol: isolatedMarginSymbol,
} };
} };
const handleAdd = (event) => { const handleAdd = (event) => {
const payload = createSessionConfig() const payload = createSessionConfig();
addSession(payload, (response) => { addSession(payload, (response) => {
setResponse(response) setResponse(response);
if (onAdded) { if (onAdded) {
setTimeout(onAdded, 3000) setTimeout(onAdded, 3000);
} }
}).catch((error) => { }).catch((error) => {
console.error(error) console.error(error);
setResponse(error.response) setResponse(error.response);
}) });
}; };
const handleTestConnection = (event) => { const handleTestConnection = (event) => {
const payload = createSessionConfig() const payload = createSessionConfig();
setTesting(true) setTesting(true);
testSessionConnection(payload, (response) => { testSessionConnection(payload, (response) => {
console.log(response) console.log(response);
setTesting(false) setTesting(false);
setTestResponse(response) setTestResponse(response);
}).catch((error) => { }).catch((error) => {
console.error(error) console.error(error);
setTesting(false) setTesting(false);
setTestResponse(error.response) setTestResponse(error.response);
}) });
}; };
return ( return (
@ -118,7 +118,6 @@ export default function AddExchangeSessionForm({onBack, onAdded}) {
Add Exchange Session Add Exchange Session
</Typography> </Typography>
<Grid container spacing={3}> <Grid container spacing={3}>
<Grid item xs={12}> <Grid item xs={12}>
<FormControl className={classes.formControl}> <FormControl className={classes.formControl}>
<InputLabel id="exchange-type-select-label">Exchange</InputLabel> <InputLabel id="exchange-type-select-label">Exchange</InputLabel>
@ -128,8 +127,8 @@ export default function AddExchangeSessionForm({onBack, onAdded}) {
value={exchangeType} value={exchangeType}
onChange={handleExchangeTypeChange} onChange={handleExchangeTypeChange}
> >
<MenuItem value={"binance"}>Binance</MenuItem> <MenuItem value={'binance'}>Binance</MenuItem>
<MenuItem value={"max"}>Max</MenuItem> <MenuItem value={'max'}>Max</MenuItem>
</Select> </Select>
</FormControl> </FormControl>
</Grid> </Grid>
@ -143,7 +142,7 @@ export default function AddExchangeSessionForm({onBack, onAdded}) {
required required
disabled={!customSessionName} disabled={!customSessionName}
onChange={(event) => { onChange={(event) => {
setSessionName(event.target.value) setSessionName(event.target.value);
}} }}
value={sessionName} value={sessionName}
/> />
@ -151,16 +150,23 @@ export default function AddExchangeSessionForm({onBack, onAdded}) {
<Grid item xs={12} sm={6}> <Grid item xs={12} sm={6}>
<FormControlLabel <FormControlLabel
control={<Checkbox color="secondary" name="custom_session_name" control={
<Checkbox
color="secondary"
name="custom_session_name"
onChange={(event) => { onChange={(event) => {
setCustomSessionName(event.target.checked); setCustomSessionName(event.target.checked);
}} value="1"/>} }}
value="1"
/>
}
label="Custom exchange session name" label="Custom exchange session name"
/> />
<FormHelperText id="session-name-helper-text"> <FormHelperText id="session-name-helper-text">
By default, the session name will be the exchange type name, By default, the session name will be the exchange type name, e.g.{' '}
e.g. <code>binance</code> or <code>max</code>.<br/> <code>binance</code> or <code>max</code>.<br />
If you're using multiple exchange sessions, you might need to custom the session name. <br/> If you're using multiple exchange sessions, you might need to custom
the session name. <br />
This is for advanced users. This is for advanced users.
</FormHelperText> </FormHelperText>
</Grid> </Grid>
@ -176,8 +182,12 @@ export default function AddExchangeSessionForm({onBack, onAdded}) {
<InputAdornment position="end"> <InputAdornment position="end">
<IconButton <IconButton
aria-label="toggle key visibility" aria-label="toggle key visibility"
onClick={() => { setShowApiKey(!showApiKey) }} onClick={() => {
onMouseDown={(event) => { event.preventDefault() }} setShowApiKey(!showApiKey);
}}
onMouseDown={(event) => {
event.preventDefault();
}}
edge="end" edge="end"
> >
{showApiKey ? <Visibility /> : <VisibilityOff />} {showApiKey ? <Visibility /> : <VisibilityOff />}
@ -185,15 +195,14 @@ export default function AddExchangeSessionForm({onBack, onAdded}) {
</InputAdornment> </InputAdornment>
} }
onChange={(event) => { onChange={(event) => {
setApiKey(event.target.value) setApiKey(event.target.value);
resetTestResponse() resetTestResponse();
}} }}
/> />
</FormControl> </FormControl>
</Grid> </Grid>
<Grid item xs={12}> <Grid item xs={12}>
<FormControl fullWidth variant="filled"> <FormControl fullWidth variant="filled">
<InputLabel htmlFor="apiSecret">API Secret</InputLabel> <InputLabel htmlFor="apiSecret">API Secret</InputLabel>
<FilledInput <FilledInput
@ -204,8 +213,12 @@ export default function AddExchangeSessionForm({onBack, onAdded}) {
<InputAdornment position="end"> <InputAdornment position="end">
<IconButton <IconButton
aria-label="toggle key visibility" aria-label="toggle key visibility"
onClick={() => { setShowApiSecret(!showApiSecret) }} onClick={() => {
onMouseDown={(event) => { event.preventDefault() }} setShowApiSecret(!showApiSecret);
}}
onMouseDown={(event) => {
event.preventDefault();
}}
edge="end" edge="end"
> >
{showApiSecret ? <Visibility /> : <VisibilityOff />} {showApiSecret ? <Visibility /> : <VisibilityOff />}
@ -213,49 +226,66 @@ export default function AddExchangeSessionForm({onBack, onAdded}) {
</InputAdornment> </InputAdornment>
} }
onChange={(event) => { onChange={(event) => {
setApiSecret(event.target.value) setApiSecret(event.target.value);
resetTestResponse() resetTestResponse();
}} }}
/> />
</FormControl> </FormControl>
</Grid> </Grid>
{exchangeType === "binance" ? ( {exchangeType === 'binance' ? (
<Grid item xs={12}> <Grid item xs={12}>
<FormControlLabel <FormControlLabel
control={<Checkbox color="secondary" name="isMargin" onChange={(event) => { control={
<Checkbox
color="secondary"
name="isMargin"
onChange={(event) => {
setIsMargin(event.target.checked); setIsMargin(event.target.checked);
resetTestResponse(); resetTestResponse();
}} value="1"/>} }}
value="1"
/>
}
label="Use margin trading." label="Use margin trading."
/> />
<FormHelperText id="isMargin-helper-text">This is only available for Binance. Please use the <FormHelperText id="isMargin-helper-text">
leverage at your own risk.</FormHelperText> This is only available for Binance. Please use the leverage at
your own risk.
</FormHelperText>
<FormControlLabel <FormControlLabel
control={<Checkbox color="secondary" name="isIsolatedMargin" control={
<Checkbox
color="secondary"
name="isIsolatedMargin"
onChange={(event) => { onChange={(event) => {
setIsIsolatedMargin(event.target.checked); setIsIsolatedMargin(event.target.checked);
resetTestResponse() resetTestResponse();
}} value="1"/>} }}
value="1"
/>
}
label="Use isolated margin trading." label="Use isolated margin trading."
/> />
<FormHelperText id="isIsolatedMargin-helper-text">This is only available for Binance. If this is <FormHelperText id="isIsolatedMargin-helper-text">
set, you can only trade one symbol with one session.</FormHelperText> This is only available for Binance. If this is set, you can only
trade one symbol with one session.
</FormHelperText>
{isIsolatedMargin ? {isIsolatedMargin ? (
<TextField <TextField
id="isolatedMarginSymbol" id="isolatedMarginSymbol"
name="isolatedMarginSymbol" name="isolatedMarginSymbol"
label="Isolated Margin Symbol" label="Isolated Margin Symbol"
onChange={(event) => { onChange={(event) => {
setIsolatedMarginSymbol(event.target.value); setIsolatedMarginSymbol(event.target.value);
resetTestResponse() resetTestResponse();
}} }}
fullWidth fullWidth
required required
/> />
: null} ) : null}
</Grid> </Grid>
) : null} ) : null}
</Grid> </Grid>
@ -266,28 +296,26 @@ export default function AddExchangeSessionForm({onBack, onAdded}) {
if (onBack) { if (onBack) {
onBack(); onBack();
} }
}}> }}
>
Back Back
</Button> </Button>
<Button <Button
color="primary" color="primary"
onClick={handleTestConnection} onClick={handleTestConnection}
disabled={testing}> disabled={testing}
{testing ? "Testing" : "Test Connection"} >
{testing ? 'Testing' : 'Test Connection'}
</Button> </Button>
<Button <Button variant="contained" color="primary" onClick={handleAdd}>
variant="contained"
color="primary"
onClick={handleAdd}
>
Add Add
</Button> </Button>
</div> </div>
{ {testResponse ? (
testResponse ? testResponse.error ? ( testResponse.error ? (
<Box m={2}> <Box m={2}>
<Alert severity="error">{testResponse.error}</Alert> <Alert severity="error">{testResponse.error}</Alert>
</Box> </Box>
@ -295,11 +323,11 @@ export default function AddExchangeSessionForm({onBack, onAdded}) {
<Box m={2}> <Box m={2}>
<Alert severity="success">Connection Test Succeeded</Alert> <Alert severity="success">Connection Test Succeeded</Alert>
</Box> </Box>
) : null : null ) : null
} ) : null}
{ {response ? (
response ? response.error ? ( response.error ? (
<Box m={2}> <Box m={2}>
<Alert severity="error">{response.error}</Alert> <Alert severity="error">{response.error}</Alert>
</Box> </Box>
@ -307,10 +335,8 @@ export default function AddExchangeSessionForm({onBack, onAdded}) {
<Box m={2}> <Box m={2}>
<Alert severity="success">Exchange Session Added</Alert> <Alert severity="success">Exchange Session Added</Alert>
</Box> </Box>
) : null : null ) : null
} ) : null}
</React.Fragment> </React.Fragment>
); );
} }

View File

@ -31,28 +31,30 @@ const useStyles = makeStyles((theme) => ({
paddingBottom: theme.spacing(2), paddingBottom: theme.spacing(2),
'& > *': { '& > *': {
marginLeft: theme.spacing(1), marginLeft: theme.spacing(1),
} },
}, },
})); }));
export default function ConfigureDatabaseForm({ onConfigured }) { export default function ConfigureDatabaseForm({ onConfigured }) {
const classes = useStyles(); const classes = useStyles();
const [mysqlURL, setMysqlURL] = React.useState("root@tcp(127.0.0.1:3306)/bbgo") const [mysqlURL, setMysqlURL] = React.useState(
'root@tcp(127.0.0.1:3306)/bbgo'
);
const [driver, setDriver] = React.useState("sqlite3"); const [driver, setDriver] = React.useState('sqlite3');
const [testing, setTesting] = React.useState(false); const [testing, setTesting] = React.useState(false);
const [testResponse, setTestResponse] = React.useState(null); const [testResponse, setTestResponse] = React.useState(null);
const [configured, setConfigured] = React.useState(false); const [configured, setConfigured] = React.useState(false);
const getDSN = () => driver === "sqlite3" ? "file:bbgo.sqlite3" : mysqlURL const getDSN = () => (driver === 'sqlite3' ? 'file:bbgo.sqlite3' : mysqlURL);
const resetTestResponse = () => { const resetTestResponse = () => {
setTestResponse(null) setTestResponse(null);
} };
const handleConfigureDatabase = (event) => { const handleConfigureDatabase = (event) => {
const dsn = getDSN() const dsn = getDSN();
configureDatabase({ driver, dsn }, (response) => { configureDatabase({ driver, dsn }, (response) => {
console.log(response); console.log(response);
@ -62,27 +64,26 @@ export default function ConfigureDatabaseForm({onConfigured}) {
setConfigured(true); setConfigured(true);
setTimeout(onConfigured, 3000); setTimeout(onConfigured, 3000);
} }
}).catch((err) => { }).catch((err) => {
console.error(err); console.error(err);
setTesting(false); setTesting(false);
setTestResponse(err.response.data); setTestResponse(err.response.data);
}) });
} };
const handleTestConnection = (event) => { const handleTestConnection = (event) => {
const dsn = getDSN() const dsn = getDSN();
setTesting(true); setTesting(true);
testDatabaseConnection({ driver, dsn }, (response) => { testDatabaseConnection({ driver, dsn }, (response) => {
console.log(response) console.log(response);
setTesting(false) setTesting(false);
setTestResponse(response) setTestResponse(response);
}).catch((err) => { }).catch((err) => {
console.error(err) console.error(err);
setTesting(false) setTesting(false);
setTestResponse(err.response.data) setTestResponse(err.response.data);
}) });
}; };
return ( return (
@ -92,79 +93,94 @@ export default function ConfigureDatabaseForm({onConfigured}) {
</Typography> </Typography>
<Typography variant="body1" gutterBottom> <Typography variant="body1" gutterBottom>
If you have database installed on your machine, you can enter the DSN string in the following field. If you have database installed on your machine, you can enter the DSN
Please note this is optional, you CAN SKIP this step. string in the following field. Please note this is optional, you CAN
SKIP this step.
</Typography> </Typography>
<Grid container spacing={3}> <Grid container spacing={3}>
<Grid item xs={12} sm={4}> <Grid item xs={12} sm={4}>
<Box m={6}> <Box m={6}>
<FormControl component="fieldset" required={true}> <FormControl component="fieldset" required={true}>
<FormLabel component="legend">Database Driver</FormLabel> <FormLabel component="legend">Database Driver</FormLabel>
<RadioGroup aria-label="driver" name="driver" value={driver} onChange={(event) => { <RadioGroup
aria-label="driver"
name="driver"
value={driver}
onChange={(event) => {
setDriver(event.target.value); setDriver(event.target.value);
}}> }}
<FormControlLabel value="sqlite3" control={<Radio/>} label="Standard (Default)"/> >
<FormControlLabel value="mysql" control={<Radio/>} label="MySQL"/> <FormControlLabel
value="sqlite3"
control={<Radio />}
label="Standard (Default)"
/>
<FormControlLabel
value="mysql"
control={<Radio />}
label="MySQL"
/>
</RadioGroup> </RadioGroup>
</FormControl> </FormControl>
<FormHelperText> <FormHelperText></FormHelperText>
</FormHelperText>
</Box> </Box>
</Grid> </Grid>
{driver === "mysql" ? ( {driver === 'mysql' ? (
<Grid item xs={12} sm={8}> <Grid item xs={12} sm={8}>
<TextField id="mysql_url" name="mysql_url" label="MySQL Data Source Name" <TextField
id="mysql_url"
name="mysql_url"
label="MySQL Data Source Name"
fullWidth fullWidth
required required
defaultValue={mysqlURL} defaultValue={mysqlURL}
onChange={(event) => { onChange={(event) => {
setMysqlURL(event.target.value) setMysqlURL(event.target.value);
resetTestResponse() resetTestResponse();
}} }}
/> />
<FormHelperText>MySQL DSN</FormHelperText> <FormHelperText>MySQL DSN</FormHelperText>
<Typography variant="body1" gutterBottom> <Typography variant="body1" gutterBottom>
If you have database installed on your machine, you can enter the DSN string like the If you have database installed on your machine, you can enter the
following DSN string like the following format:
format:
<br /> <br />
<pre><code>root:password@tcp(127.0.0.1:3306)/bbgo</code></pre> <pre>
<code>root:password@tcp(127.0.0.1:3306)/bbgo</code>
</pre>
<br /> <br />
Be sure to create your database before using it. You need to execute the following statement Be sure to create your database before using it. You need to
to execute the following statement to create a database:
create a database:
<br /> <br />
<pre><code>CREATE DATABASE bbgo CHARSET utf8;</code></pre> <pre>
<code>CREATE DATABASE bbgo CHARSET utf8;</code>
</pre>
</Typography> </Typography>
</Grid> </Grid>
) : ( ) : (
<Grid item xs={12} sm={8}> <Grid item xs={12} sm={8}>
<Box m={6}> <Box m={6}>
<Typography variant="body1" gutterBottom> <Typography variant="body1" gutterBottom>
If you don't know what to choose, just pick the standard driver (sqlite3). If you don't know what to choose, just pick the standard driver
(sqlite3).
<br /> <br />
For professionals, you can pick MySQL driver, BBGO works best with MySQL, especially for For professionals, you can pick MySQL driver, BBGO works best
larger data scale. with MySQL, especially for larger data scale.
</Typography> </Typography>
</Box> </Box>
</Grid> </Grid>
)} )}
</Grid> </Grid>
<div className={classes.buttons}> <div className={classes.buttons}>
<Button <Button
color="primary" color="primary"
onClick={handleTestConnection} onClick={handleTestConnection}
disabled={testing || configured} disabled={testing || configured}
> >
{testing ? "Testing" : "Test Connection"} {testing ? 'Testing' : 'Test Connection'}
</Button> </Button>
<Button <Button
@ -177,8 +193,8 @@ export default function ConfigureDatabaseForm({onConfigured}) {
</Button> </Button>
</div> </div>
{ {testResponse ? (
testResponse ? testResponse.error ? ( testResponse.error ? (
<Box m={2}> <Box m={2}>
<Alert severity="error">{testResponse.error}</Alert> <Alert severity="error">{testResponse.error}</Alert>
</Box> </Box>
@ -186,11 +202,8 @@ export default function ConfigureDatabaseForm({onConfigured}) {
<Box m={2}> <Box m={2}>
<Alert severity="success">Connection Test Succeeded</Alert> <Alert severity="success">Connection Test Succeeded</Alert>
</Box> </Box>
) : null : null ) : null
} ) : null}
</React.Fragment> </React.Fragment>
); );
} }

View File

@ -6,7 +6,11 @@ import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography'; import Typography from '@material-ui/core/Typography';
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import {attachStrategyOn, querySessions, querySessionSymbols} from "../api/bbgo"; import {
attachStrategyOn,
querySessions,
querySessionSymbols,
} from '../api/bbgo';
import TextField from '@material-ui/core/TextField'; import TextField from '@material-ui/core/TextField';
import FormControlLabel from '@material-ui/core/FormControlLabel'; import FormControlLabel from '@material-ui/core/FormControlLabel';
@ -20,26 +24,26 @@ import Select from '@material-ui/core/Select';
import MenuItem from '@material-ui/core/MenuItem'; import MenuItem from '@material-ui/core/MenuItem';
import Alert from '@material-ui/lab/Alert'; import Alert from '@material-ui/lab/Alert';
import Box from "@material-ui/core/Box"; import Box from '@material-ui/core/Box';
import NumberFormat from 'react-number-format'; import NumberFormat from 'react-number-format';
function parseFloatValid(s) { function parseFloatValid(s) {
if (s) { if (s) {
const f = parseFloat(s) const f = parseFloat(s);
if (!isNaN(f)) { if (!isNaN(f)) {
return f return f;
} }
} }
return null return null;
} }
function parseFloatCall(s, cb) { function parseFloatCall(s, cb) {
if (s) { if (s) {
const f = parseFloat(s) const f = parseFloat(s);
if (!isNaN(f)) { if (!isNaN(f)) {
cb(f) cb(f);
} }
} }
} }
@ -112,15 +116,14 @@ const useStyles = makeStyles((theme) => ({
paddingBottom: theme.spacing(2), paddingBottom: theme.spacing(2),
'& > *': { '& > *': {
marginLeft: theme.spacing(1), marginLeft: theme.spacing(1),
} },
}, },
})); }));
export default function ConfigureGridStrategyForm({ onBack, onAdded }) { export default function ConfigureGridStrategyForm({ onBack, onAdded }) {
const classes = useStyles(); const classes = useStyles();
const [errors, setErrors] = React.useState({}) const [errors, setErrors] = React.useState({});
const [sessions, setSessions] = React.useState([]); const [sessions, setSessions] = React.useState([]);
@ -144,54 +147,53 @@ export default function ConfigureGridStrategyForm({onBack, onAdded}) {
React.useEffect(() => { React.useEffect(() => {
querySessions((sessions) => { querySessions((sessions) => {
setSessions(sessions) setSessions(sessions);
}); });
}, []) }, []);
const handleAdd = (event) => { const handleAdd = (event) => {
const payload = { const payload = {
symbol: selectedSymbol, symbol: selectedSymbol,
gridNumber: parseFloatValid(gridNumber), gridNumber: parseFloatValid(gridNumber),
profitSpread: parseFloatValid(profitSpread), profitSpread: parseFloatValid(profitSpread),
upperPrice: parseFloatValid(upperPrice), upperPrice: parseFloatValid(upperPrice),
lowerPrice: parseFloatValid(lowerPrice), lowerPrice: parseFloatValid(lowerPrice),
} };
switch (quantityBy) { switch (quantityBy) {
case "fixedQuantity": case 'fixedQuantity':
payload.quantity = parseFloatValid(fixedQuantity); payload.quantity = parseFloatValid(fixedQuantity);
break; break;
case "fixedAmount": case 'fixedAmount':
payload.amount = parseFloatValid(fixedAmount); payload.amount = parseFloatValid(fixedAmount);
break; break;
} }
if (!selectedSessionName) { if (!selectedSessionName) {
setErrors({ session: true }) setErrors({ session: true });
return return;
} }
if (!selectedSymbol) { if (!selectedSymbol) {
setErrors({ symbol: true }) setErrors({ symbol: true });
return return;
} }
console.log(payload) console.log(payload);
attachStrategyOn(selectedSessionName, "grid", payload, (response) => { attachStrategyOn(selectedSessionName, 'grid', payload, (response) => {
console.log(response) console.log(response);
setResponse(response) setResponse(response);
if (onAdded) { if (onAdded) {
setTimeout(onAdded, 3000) setTimeout(onAdded, 3000);
} }
}).catch((err) => {
console.error(err);
setResponse(err.response.data)
}).finally(() => {
setErrors({})
}) })
.catch((err) => {
console.error(err);
setResponse(err.response.data);
})
.finally(() => {
setErrors({});
});
}; };
const handleQuantityBy = (event) => { const handleQuantityBy = (event) => {
@ -200,14 +202,14 @@ export default function ConfigureGridStrategyForm({onBack, onAdded}) {
const handleSessionChange = (event) => { const handleSessionChange = (event) => {
const sessionName = event.target.value; const sessionName = event.target.value;
setSelectedSessionName(sessionName) setSelectedSessionName(sessionName);
querySessionSymbols(sessionName, (symbols) => { querySessionSymbols(sessionName, (symbols) => {
setActiveSessionSymbols(symbols); setActiveSessionSymbols(symbols);
}).catch((err) => { }).catch((err) => {
console.error(err); console.error(err);
setResponse(err.response.data) setResponse(err.response.data);
}) });
}; };
const sessionMenuItems = sessions.map((session, index) => { const sessionMenuItems = sessions.map((session, index) => {
@ -216,7 +218,7 @@ export default function ConfigureGridStrategyForm({onBack, onAdded}) {
{session.name} {session.name}
</MenuItem> </MenuItem>
); );
}) });
const symbolMenuItems = activeSessionSymbols.map((symbol, index) => { const symbolMenuItems = activeSessionSymbols.map((symbol, index) => {
return ( return (
@ -224,7 +226,7 @@ export default function ConfigureGridStrategyForm({onBack, onAdded}) {
{symbol} {symbol}
</MenuItem> </MenuItem>
); );
}) });
return ( return (
<React.Fragment> <React.Fragment>
@ -233,15 +235,20 @@ export default function ConfigureGridStrategyForm({onBack, onAdded}) {
</Typography> </Typography>
<Typography variant="body1" gutterBottom> <Typography variant="body1" gutterBottom>
Fixed price band grid strategy uses the fixed price band to place buy/sell orders. Fixed price band grid strategy uses the fixed price band to place
This strategy places sell orders above the current price, places buy orders below the current price. buy/sell orders. This strategy places sell orders above the current
If any of the order is executed, then it will automatically place a new profit order on the reverse price, places buy orders below the current price. If any of the order is
side. executed, then it will automatically place a new profit order on the
reverse side.
</Typography> </Typography>
<Grid container spacing={3}> <Grid container spacing={3}>
<Grid item xs={12}> <Grid item xs={12}>
<FormControl required className={classes.formControl} error={errors.session}> <FormControl
required
className={classes.formControl}
error={errors.session}
>
<InputLabel id="session-select-label">Session</InputLabel> <InputLabel id="session-select-label">Session</InputLabel>
<Select <Select
labelId="session-select-label" labelId="session-select-label"
@ -258,7 +265,11 @@ export default function ConfigureGridStrategyForm({onBack, onAdded}) {
</Grid> </Grid>
<Grid item xs={12}> <Grid item xs={12}>
<FormControl required className={classes.formControl} error={errors.symbol}> <FormControl
required
className={classes.formControl}
error={errors.symbol}
>
<InputLabel id="symbol-select-label">Market</InputLabel> <InputLabel id="symbol-select-label">Market</InputLabel>
<Select <Select
labelId="symbol-select-label" labelId="symbol-select-label"
@ -266,7 +277,8 @@ export default function ConfigureGridStrategyForm({onBack, onAdded}) {
value={selectedSymbol ? selectedSymbol : ''} value={selectedSymbol ? selectedSymbol : ''}
onChange={(event) => { onChange={(event) => {
setSelectedSymbol(event.target.value); setSelectedSymbol(event.target.value);
}}> }}
>
{symbolMenuItems} {symbolMenuItems}
</Select> </Select>
</FormControl> </FormControl>
@ -283,7 +295,7 @@ export default function ConfigureGridStrategyForm({onBack, onAdded}) {
fullWidth fullWidth
required required
onChange={(event) => { onChange={(event) => {
parseFloatCall(event.target.value, setUpperPrice) parseFloatCall(event.target.value, setUpperPrice);
}} }}
value={upperPrice} value={upperPrice}
InputProps={{ InputProps={{
@ -300,7 +312,7 @@ export default function ConfigureGridStrategyForm({onBack, onAdded}) {
fullWidth fullWidth
required required
onChange={(event) => { onChange={(event) => {
parseFloatCall(event.target.value, setLowerPrice) parseFloatCall(event.target.value, setLowerPrice);
}} }}
value={lowerPrice} value={lowerPrice}
InputProps={{ InputProps={{
@ -317,7 +329,7 @@ export default function ConfigureGridStrategyForm({onBack, onAdded}) {
fullWidth fullWidth
required required
onChange={(event) => { onChange={(event) => {
parseFloatCall(event.target.value, setProfitSpread) parseFloatCall(event.target.value, setProfitSpread);
}} }}
value={profitSpread} value={profitSpread}
InputProps={{ InputProps={{
@ -326,19 +338,30 @@ export default function ConfigureGridStrategyForm({onBack, onAdded}) {
/> />
</Grid> </Grid>
<Grid item xs={12} sm={3}> <Grid item xs={12} sm={3}>
<FormControl component="fieldset"> <FormControl component="fieldset">
<FormLabel component="legend">Order Quantity By</FormLabel> <FormLabel component="legend">Order Quantity By</FormLabel>
<RadioGroup name="quantityBy" value={quantityBy} onChange={handleQuantityBy}> <RadioGroup
<FormControlLabel value="fixedAmount" control={<Radio/>} label="Fixed Amount"/> name="quantityBy"
<FormControlLabel value="fixedQuantity" control={<Radio/>} label="Fixed Quantity"/> value={quantityBy}
onChange={handleQuantityBy}
>
<FormControlLabel
value="fixedAmount"
control={<Radio />}
label="Fixed Amount"
/>
<FormControlLabel
value="fixedQuantity"
control={<Radio />}
label="Fixed Quantity"
/>
</RadioGroup> </RadioGroup>
</FormControl> </FormControl>
</Grid> </Grid>
<Grid item xs={12} sm={9}> <Grid item xs={12} sm={9}>
{quantityBy === "fixedQuantity" ? ( {quantityBy === 'fixedQuantity' ? (
<TextField <TextField
id="fixedQuantity" id="fixedQuantity"
name="order_quantity" name="order_quantity"
@ -346,7 +369,7 @@ export default function ConfigureGridStrategyForm({onBack, onAdded}) {
fullWidth fullWidth
required required
onChange={(event) => { onChange={(event) => {
parseFloatCall(event.target.value, setFixedQuantity) parseFloatCall(event.target.value, setFixedQuantity);
}} }}
value={fixedQuantity} value={fixedQuantity}
InputProps={{ InputProps={{
@ -355,7 +378,7 @@ export default function ConfigureGridStrategyForm({onBack, onAdded}) {
/> />
) : null} ) : null}
{quantityBy === "fixedAmount" ? ( {quantityBy === 'fixedAmount' ? (
<TextField <TextField
id="orderAmount" id="orderAmount"
name="order_amount" name="order_amount"
@ -363,7 +386,7 @@ export default function ConfigureGridStrategyForm({onBack, onAdded}) {
fullWidth fullWidth
required required
onChange={(event) => { onChange={(event) => {
parseFloatCall(event.target.value, setFixedAmount) parseFloatCall(event.target.value, setFixedAmount);
}} }}
value={fixedAmount} value={fixedAmount}
InputProps={{ InputProps={{
@ -381,7 +404,7 @@ export default function ConfigureGridStrategyForm({onBack, onAdded}) {
fullWidth fullWidth
required required
onChange={(event) => { onChange={(event) => {
parseFloatCall(event.target.value, setGridNumber) parseFloatCall(event.target.value, setGridNumber);
}} }}
value={gridNumber} value={gridNumber}
InputProps={{ InputProps={{
@ -397,21 +420,18 @@ export default function ConfigureGridStrategyForm({onBack, onAdded}) {
if (onBack) { if (onBack) {
onBack(); onBack();
} }
}}> }}
>
Back Back
</Button> </Button>
<Button <Button variant="contained" color="primary" onClick={handleAdd}>
variant="contained"
color="primary"
onClick={handleAdd}
>
Add Strategy Add Strategy
</Button> </Button>
</div> </div>
{ {response ? (
response ? response.error ? ( response.error ? (
<Box m={2}> <Box m={2}>
<Alert severity="error">{response.error}</Alert> <Alert severity="error">{response.error}</Alert>
</Box> </Box>
@ -419,10 +439,8 @@ export default function ConfigureGridStrategyForm({onBack, onAdded}) {
<Box m={2}> <Box m={2}>
<Alert severity="success">Strategy Added</Alert> <Alert severity="success">Strategy Added</Alert>
</Box> </Box>
) : null : null ) : null
} ) : null}
</React.Fragment> </React.Fragment>
); );
} }

View File

@ -1,4 +1,4 @@
import React from "react"; import React from 'react';
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
@ -9,13 +9,11 @@ import Paper from '@material-ui/core/Paper';
import Popper from '@material-ui/core/Popper'; import Popper from '@material-ui/core/Popper';
import MenuItem from '@material-ui/core/MenuItem'; import MenuItem from '@material-ui/core/MenuItem';
import MenuList from '@material-ui/core/MenuList'; import MenuList from '@material-ui/core/MenuList';
import ListItemText from "@material-ui/core/ListItemText"; import ListItemText from '@material-ui/core/ListItemText';
import PersonIcon from "@material-ui/icons/Person"; import PersonIcon from '@material-ui/icons/Person';
import { useEtherBalance, useTokenBalance, useEthers } from '@usedapp/core'
import { formatEther } from '@ethersproject/units'
import { useEtherBalance, useTokenBalance, useEthers } from '@usedapp/core';
import { formatEther } from '@ethersproject/units';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
buttons: { buttons: {
@ -25,20 +23,17 @@ const useStyles = makeStyles((theme) => ({
profile: { profile: {
margin: theme.spacing(1), margin: theme.spacing(1),
padding: theme.spacing(1), padding: theme.spacing(1),
} },
})); }));
const BBG = '0x3Afe98235d680e8d7A52e1458a59D60f45F935C0' const BBG = '0x3Afe98235d680e8d7A52e1458a59D60f45F935C0';
export default function ConnectWallet() { export default function ConnectWallet() {
const classes = useStyles(); const classes = useStyles();
const { activateBrowserWallet, account } = useEthers() const { activateBrowserWallet, account } = useEthers();
const etherBalance = useEtherBalance(account) const etherBalance = useEtherBalance(account);
const tokenBalance = useTokenBalance(BBG, account) const tokenBalance = useTokenBalance(BBG, account);
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
const anchorRef = React.useRef(null); const anchorRef = React.useRef(null);
@ -76,8 +71,8 @@ export default function ConnectWallet() {
return ( return (
<> <>
{account? {account ? (
(<> <>
<Button <Button
ref={anchorRef} ref={anchorRef}
id="composition-button" id="composition-button"
@ -113,19 +108,36 @@ export default function ConnectWallet() {
aria-labelledby="composition-button" aria-labelledby="composition-button"
onKeyDown={handleListKeyDown} onKeyDown={handleListKeyDown}
> >
<MenuItem onClick={handleClose}>{account && <p>Account: {account}</p>}</MenuItem> <MenuItem onClick={handleClose}>
<MenuItem onClick={handleClose}>{etherBalance && <a>ETH Balance: {formatEther(etherBalance)}</a>}</MenuItem> {account && <p>Account: {account}</p>}
<MenuItem onClick={handleClose}>{tokenBalance && <a>BBG Balance: {formatEther(tokenBalance)}</a>}</MenuItem> </MenuItem>
<MenuItem onClick={handleClose}>
{etherBalance && (
<a>ETH Balance: {formatEther(etherBalance)}</a>
)}
</MenuItem>
<MenuItem onClick={handleClose}>
{tokenBalance && (
<a>BBG Balance: {formatEther(tokenBalance)}</a>
)}
</MenuItem>
</MenuList> </MenuList>
</ClickAwayListener> </ClickAwayListener>
</Paper> </Paper>
</Grow> </Grow>
)} )}
</Popper> </Popper>
</>):(<div>
<button onClick={() => activateBrowserWallet()} className={classes.buttons}>Connect Wallet</button>
</div>)}
</> </>
) ) : (
<div>
<button
onClick={() => activateBrowserWallet()}
className={classes.buttons}
>
Connect Wallet
</button>
</div>
)}
</>
);
} }

View File

@ -1,16 +1,16 @@
import Paper from "@material-ui/core/Paper"; import Paper from '@material-ui/core/Paper';
import Tabs from "@material-ui/core/Tabs"; import Tabs from '@material-ui/core/Tabs';
import Tab from "@material-ui/core/Tab"; import Tab from '@material-ui/core/Tab';
import React, {useEffect, useState} from "react"; import React, { useEffect, useState } from 'react';
import {querySessions} from '../api/bbgo' import { querySessions } from '../api/bbgo';
import Typography from "@material-ui/core/Typography"; import Typography from '@material-ui/core/Typography';
import {makeStyles} from "@material-ui/core/styles"; import { makeStyles } from '@material-ui/core/styles';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
paper: { paper: {
margin: theme.spacing(2), margin: theme.spacing(2),
padding: theme.spacing(2), padding: theme.spacing(2),
} },
})); }));
export default function ExchangeSessionTabPanel() { export default function ExchangeSessionTabPanel() {
@ -21,15 +21,16 @@ export default function ExchangeSessionTabPanel() {
setTabIndex(newValue); setTabIndex(newValue);
}; };
const [sessions, setSessions] = useState([]) const [sessions, setSessions] = useState([]);
useEffect(() => { useEffect(() => {
querySessions((sessions) => { querySessions((sessions) => {
setSessions(sessions) setSessions(sessions);
}) });
}, []) }, []);
return <Paper className={classes.paper}> return (
<Paper className={classes.paper}>
<Typography variant="h4" gutterBottom> <Typography variant="h4" gutterBottom>
Sessions Sessions
</Typography> </Typography>
@ -39,11 +40,10 @@ export default function ExchangeSessionTabPanel() {
indicatorColor="primary" indicatorColor="primary"
textColor="primary" textColor="primary"
> >
{ {sessions.map((session) => {
sessions.map((session) => { return <Tab key={session.name} label={session.name} />;
return <Tab key={session.name} label={session.name}/> })}
})
}
</Tabs> </Tabs>
</Paper> </Paper>
);
} }

View File

@ -9,7 +9,7 @@ import ListItemIcon from '@material-ui/core/ListItemIcon';
import PowerIcon from '@material-ui/icons/Power'; import PowerIcon from '@material-ui/icons/Power';
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import {querySessions} from "../api/bbgo"; import { querySessions } from '../api/bbgo';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
formControl: { formControl: {
@ -25,7 +25,7 @@ const useStyles = makeStyles((theme) => ({
paddingBottom: theme.spacing(2), paddingBottom: theme.spacing(2),
'& > *': { '& > *': {
marginLeft: theme.spacing(1), marginLeft: theme.spacing(1),
} },
}, },
})); }));
@ -36,12 +36,12 @@ export default function ReviewSessions({onBack, onNext}) {
React.useEffect(() => { React.useEffect(() => {
querySessions((sessions) => { querySessions((sessions) => {
setSessions(sessions) setSessions(sessions);
}); });
}, []) }, []);
const items = sessions.map((session, i) => { const items = sessions.map((session, i) => {
console.log(session) console.log(session);
return ( return (
<ListItem key={session.name}> <ListItem key={session.name}>
<ListItemIcon> <ListItemIcon>
@ -50,7 +50,7 @@ export default function ReviewSessions({onBack, onNext}) {
<ListItemText primary={session.name} secondary={session.exchange} /> <ListItemText primary={session.name} secondary={session.exchange} />
</ListItem> </ListItem>
); );
}) });
return ( return (
<React.Fragment> <React.Fragment>
@ -58,9 +58,7 @@ export default function ReviewSessions({onBack, onNext}) {
Review Sessions Review Sessions
</Typography> </Typography>
<List component="nav"> <List component="nav">{items}</List>
{items}
</List>
<div className={classes.buttons}> <div className={classes.buttons}>
<Button <Button
@ -68,7 +66,8 @@ export default function ReviewSessions({onBack, onNext}) {
if (onBack) { if (onBack) {
onBack(); onBack();
} }
}}> }}
>
Back Back
</Button> </Button>
@ -79,7 +78,8 @@ export default function ReviewSessions({onBack, onNext}) {
if (onNext) { if (onNext) {
onNext(); onNext();
} }
}}> }}
>
Next Next
</Button> </Button>
</div> </div>

View File

@ -16,7 +16,7 @@ import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow'; import TableRow from '@material-ui/core/TableRow';
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import {queryStrategies} from "../api/bbgo"; import { queryStrategies } from '../api/bbgo';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
strategyCard: { strategyCard: {
@ -35,7 +35,7 @@ const useStyles = makeStyles((theme) => ({
paddingBottom: theme.spacing(2), paddingBottom: theme.spacing(2),
'& > *': { '& > *': {
marginLeft: theme.spacing(1), marginLeft: theme.spacing(1),
} },
}, },
})); }));
@ -44,8 +44,8 @@ function configToTable(config) {
return { return {
key: k, key: k,
val: config[k], val: config[k],
} };
}) });
return ( return (
<TableContainer> <TableContainer>
@ -78,31 +78,29 @@ export default function ReviewStrategies({onBack, onNext}) {
React.useEffect(() => { React.useEffect(() => {
queryStrategies((strategies) => { queryStrategies((strategies) => {
setStrategies(strategies || []) setStrategies(strategies || []);
}).catch((err) => { }).catch((err) => {
console.error(err); console.error(err);
}); });
}, []) }, []);
const items = strategies.map((o, i) => { const items = strategies.map((o, i) => {
const mounts = o.on || []; const mounts = o.on || [];
delete o.on delete o.on;
const config = o[o.strategy] const config = o[o.strategy];
const titleComps = [o.strategy.toUpperCase()] const titleComps = [o.strategy.toUpperCase()];
if (config.symbol) { if (config.symbol) {
titleComps.push(config.symbol) titleComps.push(config.symbol);
} }
const title = titleComps.join(" ") const title = titleComps.join(' ');
return ( return (
<Card key={i} className={classes.strategyCard}> <Card key={i} className={classes.strategyCard}>
<CardHeader <CardHeader
avatar={ avatar={<Avatar aria-label="strategy">G</Avatar>}
<Avatar aria-label="strategy">G</Avatar>
}
action={ action={
<IconButton aria-label="settings"> <IconButton aria-label="settings">
<MoreVertIcon /> <MoreVertIcon />
@ -113,15 +111,15 @@ export default function ReviewStrategies({onBack, onNext}) {
/> />
<CardContent> <CardContent>
<Typography variant="body2" color="textSecondary" component="p"> <Typography variant="body2" color="textSecondary" component="p">
Strategy will be executed on session {mounts.join(',')} with the following configuration: Strategy will be executed on session {mounts.join(',')} with the
following configuration:
</Typography> </Typography>
{configToTable(config)} {configToTable(config)}
</CardContent> </CardContent>
</Card> </Card>
); );
}) });
return ( return (
<React.Fragment> <React.Fragment>
@ -129,16 +127,16 @@ export default function ReviewStrategies({onBack, onNext}) {
Review Strategies Review Strategies
</Typography> </Typography>
<List component="nav"> <List component="nav">{items}</List>
{items}
</List>
<div className={classes.buttons}> <div className={classes.buttons}>
<Button onClick={() => { <Button
onClick={() => {
if (onBack) { if (onBack) {
onBack() onBack();
} }
}}> }}
>
Add New Strategy Add New Strategy
</Button> </Button>
@ -149,7 +147,8 @@ export default function ReviewStrategies({onBack, onNext}) {
if (onNext) { if (onNext) {
onNext(); onNext();
} }
}}> }}
>
Next Next
</Button> </Button>
</div> </div>

View File

@ -1,15 +1,14 @@
import React from 'react'; import React from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography'; import Typography from '@material-ui/core/Typography';
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import {ping, saveConfig, setupRestart} from "../api/bbgo"; import { ping, saveConfig, setupRestart } from '../api/bbgo';
import Box from "@material-ui/core/Box"; import Box from '@material-ui/core/Box';
import Alert from "@material-ui/lab/Alert"; import Alert from '@material-ui/lab/Alert';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
strategyCard: { strategyCard: {
@ -28,7 +27,7 @@ const useStyles = makeStyles((theme) => ({
paddingBottom: theme.spacing(2), paddingBottom: theme.spacing(2),
'& > *': { '& > *': {
marginLeft: theme.spacing(1), marginLeft: theme.spacing(1),
} },
}, },
})); }));
@ -43,17 +42,17 @@ export default function SaveConfigAndRestart({onBack, onRestarted}) {
setResponse(resp); setResponse(resp);
setupRestart((resp) => { setupRestart((resp) => {
let t let t;
t = setInterval(() => { t = setInterval(() => {
ping(() => { ping(() => {
clearInterval(t) clearInterval(t);
push("/"); push('/');
}) });
}, 1000); }, 1000);
}).catch((err) => { }).catch((err) => {
console.error(err); console.error(err);
setResponse(err.response.data); setResponse(err.response.data);
}) });
// call restart here // call restart here
}).catch((err) => { }).catch((err) => {
@ -69,29 +68,29 @@ export default function SaveConfigAndRestart({onBack, onRestarted}) {
</Typography> </Typography>
<Typography variant="body1" gutterBottom> <Typography variant="body1" gutterBottom>
Click "Save and Restart" to save the configurations to the config file <code>bbgo.yaml</code>, Click "Save and Restart" to save the configurations to the config file{' '}
and save the exchange session credentials to the dotenv file <code>.env.local</code>. <code>bbgo.yaml</code>, and save the exchange session credentials to the
dotenv file <code>.env.local</code>.
</Typography> </Typography>
<div className={classes.buttons}> <div className={classes.buttons}>
<Button onClick={() => { <Button
onClick={() => {
if (onBack) { if (onBack) {
onBack() onBack();
} }
}}> }}
>
Back Back
</Button> </Button>
<Button <Button variant="contained" color="primary" onClick={handleRestart}>
variant="contained"
color="primary"
onClick={handleRestart}>
Save and Restart Save and Restart
</Button> </Button>
</div> </div>
{ {response ? (
response ? response.error ? ( response.error ? (
<Box m={2}> <Box m={2}>
<Alert severity="error">{response.error}</Alert> <Alert severity="error">{response.error}</Alert>
</Box> </Box>
@ -99,9 +98,8 @@ export default function SaveConfigAndRestart({onBack, onRestarted}) {
<Box m={2}> <Box m={2}>
<Alert severity="success">Config Saved</Alert> <Alert severity="success">Config Saved</Alert>
</Box> </Box>
) : null : null ) : null
} ) : null}
</React.Fragment> </React.Fragment>
); );
} }

View File

@ -1,15 +1,15 @@
import Drawer from "@material-ui/core/Drawer"; import Drawer from '@material-ui/core/Drawer';
import Divider from "@material-ui/core/Divider"; import Divider from '@material-ui/core/Divider';
import List from "@material-ui/core/List"; import List from '@material-ui/core/List';
import Link from "next/link"; import Link from 'next/link';
import ListItem from "@material-ui/core/ListItem"; import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from "@material-ui/core/ListItemIcon"; import ListItemIcon from '@material-ui/core/ListItemIcon';
import DashboardIcon from "@material-ui/icons/Dashboard"; import DashboardIcon from '@material-ui/icons/Dashboard';
import ListItemText from "@material-ui/core/ListItemText"; import ListItemText from '@material-ui/core/ListItemText';
import ListIcon from "@material-ui/icons/List"; import ListIcon from '@material-ui/icons/List';
import TrendingUpIcon from "@material-ui/icons/TrendingUp"; import TrendingUpIcon from '@material-ui/icons/TrendingUp';
import React from "react"; import React from 'react';
import {makeStyles} from "@material-ui/core/styles"; import { makeStyles } from '@material-ui/core/styles';
const drawerWidth = 240; const drawerWidth = 240;
@ -46,23 +46,23 @@ const useStyles = makeStyles((theme) => ({
}, },
})); }));
export default function SideBar() { export default function SideBar() {
const classes = useStyles(); const classes = useStyles();
return <Drawer return (
<Drawer
variant="permanent" variant="permanent"
className={classes.drawer} className={classes.drawer}
PaperProps={{ PaperProps={{
className: classes.drawerPaper, className: classes.drawerPaper,
}} }}
anchor={"left"} anchor={'left'}
open={true}> open={true}
>
<div className={classes.appBarSpacer} /> <div className={classes.appBarSpacer} />
<List> <List>
<Link href={"/"}> <Link href={'/'}>
<ListItem button> <ListItem button>
<ListItemIcon> <ListItemIcon>
<DashboardIcon /> <DashboardIcon />
@ -73,7 +73,7 @@ export default function SideBar() {
</List> </List>
<Divider /> <Divider />
<List> <List>
<Link href={"/orders"}> <Link href={'/orders'}>
<ListItem button> <ListItem button>
<ListItemIcon> <ListItemIcon>
<ListIcon /> <ListIcon />
@ -81,7 +81,7 @@ export default function SideBar() {
<ListItemText primary="Orders" /> <ListItemText primary="Orders" />
</ListItem> </ListItem>
</Link> </Link>
<Link href={"/trades"}> <Link href={'/trades'}>
<ListItem button> <ListItem button>
<ListItemIcon> <ListItemIcon>
<ListIcon /> <ListIcon />
@ -97,6 +97,5 @@ export default function SideBar() {
</ListItem> </ListItem>
</List> </List>
</Drawer> </Drawer>
);
} }

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import CardContent from "@material-ui/core/CardContent"; import CardContent from '@material-ui/core/CardContent';
import Card from "@material-ui/core/Card"; import Card from '@material-ui/core/Card';
import {makeStyles} from "@material-ui/core/styles"; import { makeStyles } from '@material-ui/core/styles';
import List from '@material-ui/core/List'; import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem'; import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText'; import ListItemText from '@material-ui/core/ListItemText';
@ -12,28 +12,28 @@ const useStyles = makeStyles((theme) => ({
root: { root: {
margin: theme.spacing(1), margin: theme.spacing(1),
}, },
cardContent: {} cardContent: {},
})); }));
const logoCurrencies = { const logoCurrencies = {
"BTC": true, BTC: true,
"ETH": true, ETH: true,
"BCH": true, BCH: true,
"LTC": true, LTC: true,
"USDT": true, USDT: true,
"BNB": true, BNB: true,
"COMP": true, COMP: true,
"XRP": true, XRP: true,
"LINK": true, LINK: true,
"DOT": true, DOT: true,
"SXP": true, SXP: true,
"DAI": true, DAI: true,
"MAX": true, MAX: true,
"TWD": true, TWD: true,
"SNT": true, SNT: true,
"YFI": true, YFI: true,
"GRT": true, GRT: true,
} };
export default function TotalAssetsDetails({ assets }) { export default function TotalAssetsDetails({ assets }) {
const classes = useStyles(); const classes = useStyles();
@ -44,43 +44,44 @@ export default function TotalAssetsDetails({assets}) {
} }
sortedAssets.sort((a, b) => { sortedAssets.sort((a, b) => {
if (a.inUSD > b.inUSD) { if (a.inUSD > b.inUSD) {
return -1 return -1;
} }
if (a.inUSD < b.inUSD) { if (a.inUSD < b.inUSD) {
return 1 return 1;
} }
return 0; return 0;
}) });
const items = sortedAssets.map((a) => { const items = sortedAssets.map((a) => {
return ( return (
<ListItem key={a.currency} dense> <ListItem key={a.currency} dense>
{ {a.currency in logoCurrencies ? (
(a.currency in logoCurrencies) ? (
<ListItemAvatar> <ListItemAvatar>
<Avatar alt={a.currency} src={`/images/${a.currency.toLowerCase()}-logo.svg`}/> <Avatar
alt={a.currency}
src={`/images/${a.currency.toLowerCase()}-logo.svg`}
/>
</ListItemAvatar> </ListItemAvatar>
) : ( ) : (
<ListItemAvatar> <ListItemAvatar>
<Avatar alt={a.currency} /> <Avatar alt={a.currency} />
</ListItemAvatar> </ListItemAvatar>
) )}
} <ListItemText
<ListItemText primary={`${a.currency} ${a.total}`} secondary={`=~ ${Math.round(a.inUSD)} USD`}/> primary={`${a.currency} ${a.total}`}
secondary={`=~ ${Math.round(a.inUSD)} USD`}
/>
</ListItem> </ListItem>
) );
}) });
return ( return (
<Card className={classes.root} variant="outlined"> <Card className={classes.root} variant="outlined">
<CardContent className={classes.cardContent}> <CardContent className={classes.cardContent}>
<List dense> <List dense>{items}</List>
{items}
</List>
</CardContent> </CardContent>
</Card> </Card>
); );
} }

View File

@ -3,33 +3,33 @@ import React, {useEffect, useState} from 'react';
import { ResponsivePie } from '@nivo/pie'; import { ResponsivePie } from '@nivo/pie';
import { queryAssets } from '../api/bbgo'; import { queryAssets } from '../api/bbgo';
import { currencyColor } from '../src/utils'; import { currencyColor } from '../src/utils';
import CardContent from "@material-ui/core/CardContent"; import CardContent from '@material-ui/core/CardContent';
import Card from "@material-ui/core/Card"; import Card from '@material-ui/core/Card';
import {makeStyles} from "@material-ui/core/styles"; import { makeStyles } from '@material-ui/core/styles';
function reduceAssetsBy(assets, field, minimum) { function reduceAssetsBy(assets, field, minimum) {
let as = [] let as = [];
let others = {id: "others", labels: "others", value: 0.0} let others = { id: 'others', labels: 'others', value: 0.0 };
for (let key in assets) { for (let key in assets) {
if (assets[key]) { if (assets[key]) {
let a = assets[key] let a = assets[key];
let value = a[field] let value = a[field];
if (value < minimum) { if (value < minimum) {
others.value += value others.value += value;
} else { } else {
as.push({ as.push({
id: a.currency, id: a.currency,
label: a.currency, label: a.currency,
color: currencyColor(a.currency), color: currencyColor(a.currency),
value: Math.round(value, 1), value: Math.round(value, 1),
}) });
} }
} }
} }
return as return as;
} }
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
@ -38,7 +38,7 @@ const useStyles = makeStyles((theme) => ({
}, },
cardContent: { cardContent: {
height: 350, height: 350,
} },
})); }));
export default function TotalAssetsPie({ assets }) { export default function TotalAssetsPie({ assets }) {
@ -47,7 +47,7 @@ export default function TotalAssetsPie({ assets }) {
<Card className={classes.root} variant="outlined"> <Card className={classes.root} variant="outlined">
<CardContent className={classes.cardContent}> <CardContent className={classes.cardContent}>
<ResponsivePie <ResponsivePie
data={reduceAssetsBy(assets, "inUSD", 2)} data={reduceAssetsBy(assets, 'inUSD', 2)}
margin={{ top: 20, right: 80, bottom: 10, left: 0 }} margin={{ top: 20, right: 80, bottom: 10, left: 0 }}
padding={0.1} padding={0.1}
innerRadius={0.8} innerRadius={0.8}
@ -81,15 +81,14 @@ export default function TotalAssetsPie({ assets }) {
{ {
on: 'hover', on: 'hover',
style: { style: {
itemTextColor: '#000' itemTextColor: '#000',
} },
} },
] ],
} },
]} ]}
/> />
</CardContent> </CardContent>
</Card> </Card>
); );
} }

View File

@ -1,20 +1,20 @@
import {useEffect, useState} from "react"; import { useEffect, useState } from 'react';
import Card from '@material-ui/core/Card'; import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent'; import CardContent from '@material-ui/core/CardContent';
import Typography from '@material-ui/core/Typography'; import Typography from '@material-ui/core/Typography';
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
function aggregateAssetsBy(assets, field) { function aggregateAssetsBy(assets, field) {
let total = 0.0 let total = 0.0;
for (let key in assets) { for (let key in assets) {
if (assets[key]) { if (assets[key]) {
let a = assets[key] let a = assets[key];
let value = a[field] let value = a[field];
total += value total += value;
} }
} }
return total return total;
} }
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
@ -31,13 +31,19 @@ const useStyles = makeStyles((theme) => ({
export default function TotalAssetSummary({ assets }) { export default function TotalAssetSummary({ assets }) {
const classes = useStyles(); const classes = useStyles();
return <Card className={classes.root} variant="outlined"> return (
<Card className={classes.root} variant="outlined">
<CardContent> <CardContent>
<Typography className={classes.title} color="textSecondary" gutterBottom> <Typography
className={classes.title}
color="textSecondary"
gutterBottom
>
Total Account Balance Total Account Balance
</Typography> </Typography>
<Typography variant="h5" component="h2"> <Typography variant="h5" component="h2">
{Math.round(aggregateAssetsBy(assets, "inBTC") * 1e8) / 1e8} <span>BTC</span> {Math.round(aggregateAssetsBy(assets, 'inBTC') * 1e8) / 1e8}{' '}
<span>BTC</span>
</Typography> </Typography>
<Typography className={classes.pos} color="textSecondary"> <Typography className={classes.pos} color="textSecondary">
@ -45,8 +51,10 @@ export default function TotalAssetSummary({ assets }) {
</Typography> </Typography>
<Typography variant="h5" component="h3"> <Typography variant="h5" component="h3">
{Math.round(aggregateAssetsBy(assets, "inUSD") * 100) / 100} <span>USD</span> {Math.round(aggregateAssetsBy(assets, 'inUSD') * 100) / 100}{' '}
<span>USD</span>
</Typography> </Typography>
</CardContent> </CardContent>
</Card> </Card>
);
} }

View File

@ -1,45 +1,48 @@
import { ResponsiveBar } from '@nivo/bar'; import { ResponsiveBar } from '@nivo/bar';
import { queryTradingVolume } from '../api/bbgo'; import { queryTradingVolume } from '../api/bbgo';
import {useEffect, useState} from "react"; import { useEffect, useState } from 'react';
function toPeriodDateString(time, period) { function toPeriodDateString(time, period) {
switch (period) { switch (period) {
case "day": case 'day':
return time.getFullYear() + "-" + (time.getMonth() + 1) + "-" + time.getDate() return (
case "month": time.getFullYear() + '-' + (time.getMonth() + 1) + '-' + time.getDate()
return time.getFullYear() + "-" + (time.getMonth() + 1) );
case "year": case 'month':
return time.getFullYear() return time.getFullYear() + '-' + (time.getMonth() + 1);
case 'year':
return time.getFullYear();
} }
return time.getFullYear() + "-" + (time.getMonth() + 1) + "-" + time.getDate() return (
time.getFullYear() + '-' + (time.getMonth() + 1) + '-' + time.getDate()
);
} }
function groupData(rows, period, segment) { function groupData(rows, period, segment) {
let dateIndex = {} let dateIndex = {};
let startTime = null let startTime = null;
let endTime = null let endTime = null;
let keys = {} let keys = {};
rows.forEach((v) => { rows.forEach((v) => {
const time = new Date(v.time) const time = new Date(v.time);
if (!startTime) { if (!startTime) {
startTime = time startTime = time;
} }
endTime = time endTime = time;
const dateStr = toPeriodDateString(time, period) const dateStr = toPeriodDateString(time, period);
const key = v[segment] const key = v[segment];
keys[key] = true keys[key] = true;
const k = key ? key : "total" const k = key ? key : 'total';
const quoteVolume = Math.round(v.quoteVolume * 100) / 100 const quoteVolume = Math.round(v.quoteVolume * 100) / 100;
if (dateIndex[dateStr]) { if (dateIndex[dateStr]) {
dateIndex[dateStr][k] = quoteVolume dateIndex[dateStr][k] = quoteVolume;
} else { } else {
dateIndex[dateStr] = { dateIndex[dateStr] = {
date: dateStr, date: dateStr,
@ -47,16 +50,16 @@ function groupData(rows, period, segment) {
month: time.getMonth() + 1, month: time.getMonth() + 1,
day: time.getDate(), day: time.getDate(),
[k]: quoteVolume, [k]: quoteVolume,
};
} }
} });
})
let data = [] let data = [];
while (startTime < endTime) { while (startTime < endTime) {
const dateStr = toPeriodDateString(startTime, period) const dateStr = toPeriodDateString(startTime, period);
const groupData = dateIndex[dateStr] const groupData = dateIndex[dateStr];
if (groupData) { if (groupData) {
data.push(groupData) data.push(groupData);
} else { } else {
data.push({ data.push({
date: dateStr, date: dateStr,
@ -64,29 +67,29 @@ function groupData(rows, period, segment) {
month: startTime.getMonth() + 1, month: startTime.getMonth() + 1,
day: startTime.getDate(), day: startTime.getDate(),
total: 0, total: 0,
}) });
} }
switch (period) { switch (period) {
case "day": case 'day':
startTime.setDate(startTime.getDate() + 1) startTime.setDate(startTime.getDate() + 1);
break break;
case "month": case 'month':
startTime.setMonth(startTime.getMonth() + 1) startTime.setMonth(startTime.getMonth() + 1);
break break;
case "year": case 'year':
startTime.setFullYear(startTime.getFullYear() + 1) startTime.setFullYear(startTime.getFullYear() + 1);
break break;
} }
} }
return [data, Object.keys(keys)] return [data, Object.keys(keys)];
} }
export default function TradingVolumeBar(props) { export default function TradingVolumeBar(props) {
const [tradingVolumes, setTradingVolumes] = useState([]) const [tradingVolumes, setTradingVolumes] = useState([]);
const [period, setPeriod] = useState(props.period) const [period, setPeriod] = useState(props.period);
const [segment, setSegment] = useState(props.segment) const [segment, setSegment] = useState(props.segment);
useEffect(() => { useEffect(() => {
if (props.period !== period) { if (props.period !== period) {
@ -97,16 +100,21 @@ export default function TradingVolumeBar(props) {
setSegment(props.segment); setSegment(props.segment);
} }
queryTradingVolume({period: props.period, segment: props.segment }, (tradingVolumes) => { queryTradingVolume(
setTradingVolumes(tradingVolumes) { period: props.period, segment: props.segment },
}) (tradingVolumes) => {
}, [props.period, props.segment]) setTradingVolumes(tradingVolumes);
}
);
}, [props.period, props.segment]);
const [data, keys] = groupData(tradingVolumes, period, segment) const [data, keys] = groupData(tradingVolumes, period, segment);
return <ResponsiveBar keys={keys} return (
<ResponsiveBar
keys={keys}
data={data} data={data}
indexBy={"date"} indexBy={'date'}
margin={{ top: 50, right: 160, bottom: 100, left: 60 }} margin={{ top: 50, right: 160, bottom: 100, left: 60 }}
padding={0.3} padding={0.3}
valueScale={{ type: 'linear' }} valueScale={{ type: 'linear' }}
@ -119,7 +127,7 @@ export default function TradingVolumeBar(props) {
tickRotation: -90, tickRotation: -90,
legend: period, legend: period,
legendPosition: 'middle', legendPosition: 'middle',
legendOffset: 80 legendOffset: 80,
}} }}
legends={[ legends={[
{ {
@ -139,14 +147,15 @@ export default function TradingVolumeBar(props) {
{ {
on: 'hover', on: 'hover',
style: { style: {
itemOpacity: 1 itemOpacity: 1,
} },
} },
] ],
} },
]} ]}
animate={true} animate={true}
motionStiffness={90} motionStiffness={90}
motionDamping={15} motionDamping={15}
/>; />
);
} }

View File

@ -1,12 +1,12 @@
import Paper from "@material-ui/core/Paper"; import Paper from '@material-ui/core/Paper';
import Box from "@material-ui/core/Box"; import Box from '@material-ui/core/Box';
import Tabs from "@material-ui/core/Tabs"; import Tabs from '@material-ui/core/Tabs';
import Tab from "@material-ui/core/Tab"; import Tab from '@material-ui/core/Tab';
import React from "react"; import React from 'react';
import TradingVolumeBar from "./TradingVolumeBar"; import TradingVolumeBar from './TradingVolumeBar';
import {makeStyles} from "@material-ui/core/styles"; import { makeStyles } from '@material-ui/core/styles';
import Grid from "@material-ui/core/Grid"; import Grid from '@material-ui/core/Grid';
import Typography from "@material-ui/core/Typography"; import Typography from '@material-ui/core/Typography';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
tradingVolumeBarBox: { tradingVolumeBarBox: {
@ -15,12 +15,12 @@ const useStyles = makeStyles((theme) => ({
paper: { paper: {
margin: theme.spacing(2), margin: theme.spacing(2),
padding: theme.spacing(2), padding: theme.spacing(2),
} },
})); }));
export default function TradingVolumePanel() { export default function TradingVolumePanel() {
const [period, setPeriod] = React.useState("day"); const [period, setPeriod] = React.useState('day');
const [segment, setSegment] = React.useState("exchange"); const [segment, setSegment] = React.useState('exchange');
const classes = useStyles(); const classes = useStyles();
const handlePeriodChange = (event, newValue) => { const handlePeriodChange = (event, newValue) => {
setPeriod(newValue); setPeriod(newValue);
@ -30,30 +30,35 @@ export default function TradingVolumePanel() {
setSegment(newValue); setSegment(newValue);
}; };
return <Paper className={classes.paper}> return (
<Paper className={classes.paper}>
<Typography variant="h4" gutterBottom> <Typography variant="h4" gutterBottom>
Trading Volume Trading Volume
</Typography> </Typography>
<Grid container spacing={0}> <Grid container spacing={0}>
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
<Tabs value={period} <Tabs
value={period}
onChange={handlePeriodChange} onChange={handlePeriodChange}
indicatorColor="primary" indicatorColor="primary"
textColor="primary"> textColor="primary"
<Tab label="Day" value={"day"}/> >
<Tab label="Month" value={"month"}/> <Tab label="Day" value={'day'} />
<Tab label="Year" value={"year"}/> <Tab label="Month" value={'month'} />
<Tab label="Year" value={'year'} />
</Tabs> </Tabs>
</Grid> </Grid>
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
<Grid container justifyContent={"flex-end"}> <Grid container justifyContent={'flex-end'}>
<Tabs value={segment} <Tabs
value={segment}
onChange={handleSegmentChange} onChange={handleSegmentChange}
indicatorColor="primary" indicatorColor="primary"
textColor="primary"> textColor="primary"
<Tab label="By Exchange" value={"exchange"}/> >
<Tab label="By Symbol" value={"symbol"}/> <Tab label="By Exchange" value={'exchange'} />
<Tab label="By Symbol" value={'symbol'} />
</Tabs> </Tabs>
</Grid> </Grid>
</Grid> </Grid>
@ -62,5 +67,6 @@ export default function TradingVolumePanel() {
<Box className={classes.tradingVolumeBarBox}> <Box className={classes.tradingVolumeBarBox}>
<TradingVolumeBar period={period} segment={segment} /> <TradingVolumeBar period={period} segment={segment} />
</Box> </Box>
</Paper>; </Paper>
);
} }

View File

@ -1,12 +1,12 @@
import React from "react"; import React from 'react';
import {makeStyles} from "@material-ui/core/styles"; import { makeStyles } from '@material-ui/core/styles';
import AppBar from "@material-ui/core/AppBar"; import AppBar from '@material-ui/core/AppBar';
import Toolbar from "@material-ui/core/Toolbar"; import Toolbar from '@material-ui/core/Toolbar';
import Typography from "@material-ui/core/Typography"; import Typography from '@material-ui/core/Typography';
import Container from '@material-ui/core/Container'; import Container from '@material-ui/core/Container';
import SideBar from "../components/SideBar"; import SideBar from '../components/SideBar';
import ConnectWallet from '../components/ConnectWallet'; import ConnectWallet from '../components/ConnectWallet';
@ -27,7 +27,7 @@ const useStyles = makeStyles((theme) => ({
container: {}, container: {},
toolbar: { toolbar: {
justifyContent: 'space-between', justifyContent: 'space-between',
} },
})); }));
export default function DashboardLayout({ children }) { export default function DashboardLayout({ children }) {
@ -49,7 +49,11 @@ export default function DashboardLayout({children}) {
<main className={classes.content}> <main className={classes.content}>
<div className={classes.appBarSpacer} /> <div className={classes.appBarSpacer} />
<Container className={classes.container} maxWidth={false} disableGutters={true}> <Container
className={classes.container}
maxWidth={false}
disableGutters={true}
>
{children} {children}
</Container> </Container>
</main> </main>

View File

@ -1,9 +1,9 @@
import React from "react"; import React from 'react';
import {makeStyles} from "@material-ui/core/styles"; import { makeStyles } from '@material-ui/core/styles';
import AppBar from "@material-ui/core/AppBar"; import AppBar from '@material-ui/core/AppBar';
import Toolbar from "@material-ui/core/Toolbar"; import Toolbar from '@material-ui/core/Toolbar';
import Typography from "@material-ui/core/Typography"; import Typography from '@material-ui/core/Typography';
import Container from '@material-ui/core/Container'; import Container from '@material-ui/core/Container';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
@ -24,20 +24,20 @@ const useStyles = makeStyles((theme) => ({
export default function PlainLayout(props) { export default function PlainLayout(props) {
const classes = useStyles(); const classes = useStyles();
return <div className={classes.root}> return (
<div className={classes.root}>
<AppBar className={classes.appBar}> <AppBar className={classes.appBar}>
<Toolbar> <Toolbar>
<Typography variant="h6" className={classes.title}> <Typography variant="h6" className={classes.title}>
{ props && props.title ? props.title : "BBGO Setup Wizard" } {props && props.title ? props.title : 'BBGO Setup Wizard'}
</Typography> </Typography>
</Toolbar> </Toolbar>
</AppBar> </AppBar>
<main className={classes.content}> <main className={classes.content}>
<div className={classes.appBarSpacer} /> <div className={classes.appBarSpacer} />
<Container> <Container>{props.children}</Container>
{props.children}
</Container>
</main> </main>
</div>; </div>
);
} }

View File

@ -1,8 +1,7 @@
const withTM = require('next-transpile-modules') const withTM = require('next-transpile-modules')([
([
'@react-spring/three', '@react-spring/three',
'@react-spring/web', '@react-spring/web',
]) ]);
module.exports = withTM({ module.exports = withTM({
// disable webpack 5 to make it compatible with the following rules // disable webpack 5 to make it compatible with the following rules
@ -11,7 +10,7 @@ module.exports = withTM({
config.module.rules.push({ config.module.rules.push({
test: /react-spring/, test: /react-spring/,
sideEffects: true, sideEffects: true,
}) });
return config return config;
}, },
}) });

View File

@ -13,21 +13,21 @@ import Box from '@material-ui/core/Box';
import CssBaseline from '@material-ui/core/CssBaseline'; import CssBaseline from '@material-ui/core/CssBaseline';
import theme from '../src/theme'; import theme from '../src/theme';
import '../styles/globals.css' import '../styles/globals.css';
import {querySessions, querySyncStatus} from "../api/bbgo"; import { querySessions, querySyncStatus } from '../api/bbgo';
const SyncNotStarted = 0 const SyncNotStarted = 0;
const Syncing = 1 const Syncing = 1;
const SyncDone = 2 const SyncDone = 2;
// session is configured, check if we're syncing data // session is configured, check if we're syncing data
let syncStatusPoller = null let syncStatusPoller = null;
export default function MyApp(props) { export default function MyApp(props) {
const { Component, pageProps } = props; const { Component, pageProps } = props;
const [loading, setLoading] = React.useState(true) const [loading, setLoading] = React.useState(true);
const [syncing, setSyncing] = React.useState(false) const [syncing, setSyncing] = React.useState(false);
React.useEffect(() => { React.useEffect(() => {
// Remove the server-side injected CSS. // Remove the server-side injected CSS.
@ -38,13 +38,13 @@ export default function MyApp(props) {
querySessions((sessions) => { querySessions((sessions) => {
if (sessions.length > 0) { if (sessions.length > 0) {
setSyncing(true) setSyncing(true);
const pollSyncStatus = () => { const pollSyncStatus = () => {
querySyncStatus((status) => { querySyncStatus((status) => {
switch (status) { switch (status) {
case SyncNotStarted: case SyncNotStarted:
break break;
case Syncing: case Syncing:
setSyncing(true); setSyncing(true);
break; break;
@ -55,39 +55,43 @@ export default function MyApp(props) {
break; break;
} }
}).catch((err) => { }).catch((err) => {
console.error(err) console.error(err);
}) });
} };
syncStatusPoller = setInterval(pollSyncStatus, 1000) syncStatusPoller = setInterval(pollSyncStatus, 1000);
} else { } else {
// no session found, so we can not sync any data // no session found, so we can not sync any data
setLoading(false) setLoading(false);
setSyncing(false) setSyncing(false);
} }
}).catch((err) => { }).catch((err) => {
console.error(err) console.error(err);
}) });
}, []); }, []);
return ( return (
<React.Fragment> <React.Fragment>
<Head> <Head>
<title>BBGO</title> <title>BBGO</title>
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width"/> <meta
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width"
/>
</Head> </Head>
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */} {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline /> <CssBaseline />
{ {loading ? (
loading ? (syncing ? ( syncing ? (
<Dialog <Dialog
open={syncing} open={syncing}
aria-labelledby="alert-dialog-title" aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description" aria-describedby="alert-dialog-description"
> >
<DialogTitle id="alert-dialog-title">{"Syncing Trades"}</DialogTitle> <DialogTitle id="alert-dialog-title">
{'Syncing Trades'}
</DialogTitle>
<DialogContent> <DialogContent>
<DialogContentText id="alert-dialog-description"> <DialogContentText id="alert-dialog-description">
The environment is syncing trades from the exchange sessions. The environment is syncing trades from the exchange sessions.
@ -104,7 +108,7 @@ export default function MyApp(props) {
aria-labelledby="alert-dialog-title" aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description" aria-describedby="alert-dialog-description"
> >
<DialogTitle id="alert-dialog-title">{"Loading"}</DialogTitle> <DialogTitle id="alert-dialog-title">{'Loading'}</DialogTitle>
<DialogContent> <DialogContent>
<DialogContentText id="alert-dialog-description"> <DialogContentText id="alert-dialog-description">
Loading... Loading...
@ -114,10 +118,10 @@ export default function MyApp(props) {
</Box> </Box>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
)) : (
<Component {...pageProps}/>
) )
} ) : (
<Component {...pageProps} />
)}
</ThemeProvider> </ThemeProvider>
</React.Fragment> </React.Fragment>
); );

View File

@ -1,8 +1,6 @@
/* eslint-disable react/jsx-filename-extension */ /* eslint-disable react/jsx-filename-extension */
import React from 'react'; import React from 'react';
import Document, { import Document, { Html, Head, Main, NextScript } from 'next/document';
Html, Head, Main, NextScript,
} from 'next/document';
import { ServerStyleSheets } from '@material-ui/styles'; import { ServerStyleSheets } from '@material-ui/styles';
import theme from '../src/theme'; import theme from '../src/theme';
@ -66,6 +64,9 @@ MyDocument.getInitialProps = async (ctx) => {
return { return {
...initialProps, ...initialProps,
// Styles fragment is rendered after the app and page rendering finish. // Styles fragment is rendered after the app and page rendering finish.
styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()], styles: [
...React.Children.toArray(initialProps.styles),
sheets.getStyleElement(),
],
}; };
}; };

View File

@ -1,6 +1,6 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
export default (req, res) => { export default (req, res) => {
res.statusCode = 200 res.statusCode = 200;
res.json({ name: 'John Doe' }) res.json({ name: 'John Doe' });
} };

View File

@ -18,40 +18,38 @@ const useStyles = makeStyles((theme) => ({
alignItems: 'center', alignItems: 'center',
alignContent: 'center', alignContent: 'center',
height: 320, height: 320,
} },
})); }));
function fetchConnectUrl(cb) { function fetchConnectUrl(cb) {
return queryOutboundIP((outboundIP) => { return queryOutboundIP((outboundIP) => {
cb(window.location.protocol + "//" + outboundIP + ":" + window.location.port) cb(
}) window.location.protocol + '//' + outboundIP + ':' + window.location.port
);
});
} }
export default function Connect() { export default function Connect() {
const classes = useStyles(); const classes = useStyles();
const [connectUrl, setConnectUrl] = useState([]) const [connectUrl, setConnectUrl] = useState([]);
useEffect(() => { useEffect(() => {
fetchConnectUrl(function (url) { fetchConnectUrl(function (url) {
setConnectUrl(url) setConnectUrl(url);
}) });
}, []) }, []);
return ( return (
<PlainLayout title={"Connect"}> <PlainLayout title={'Connect'}>
<Paper className={classes.paper}> <Paper className={classes.paper}>
<Typography variant="h4" gutterBottom> <Typography variant="h4" gutterBottom>
Sign In Using QR Codes Sign In Using QR Codes
</Typography> </Typography>
<div className={classes.dataGridContainer}> <div className={classes.dataGridContainer}>
<QRCodeSVG <QRCodeSVG size={160} style={{ flexGrow: 1 }} value={connectUrl} />
size={160}
style={{flexGrow: 1}}
value={connectUrl}/>
</div> </div>
</Paper> </Paper>
</PlainLayout> </PlainLayout>
); );
} }

View File

@ -16,11 +16,10 @@ import ExchangeSessionTabPanel from '../components/ExchangeSessionTabPanel';
import DashboardLayout from '../layouts/DashboardLayout'; import DashboardLayout from '../layouts/DashboardLayout';
import {queryAssets, querySessions} from "../api/bbgo"; import { queryAssets, querySessions } from '../api/bbgo';
import { ChainId, Config, DAppProvider } from '@usedapp/core'; import { ChainId, Config, DAppProvider } from '@usedapp/core';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
totalAssetsSummary: { totalAssetsSummary: {
margin: theme.spacing(2), margin: theme.spacing(2),
@ -34,35 +33,34 @@ const useStyles = makeStyles((theme) => ({
}, },
})); }));
const config: Config = { const config: Config = {
readOnlyChainId: ChainId.Mainnet, readOnlyChainId: ChainId.Mainnet,
readOnlyUrls: { readOnlyUrls: {
[ChainId.Mainnet]: 'https://mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161', [ChainId.Mainnet]:
'https://mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161',
}, },
} };
// props are pageProps passed from _app.tsx // props are pageProps passed from _app.tsx
export default function Home() { export default function Home() {
const classes = useStyles(); const classes = useStyles();
const router = useRouter(); const router = useRouter();
const [assets, setAssets] = useState({}) const [assets, setAssets] = useState({});
const [sessions, setSessions] = React.useState([]) const [sessions, setSessions] = React.useState([]);
React.useEffect(() => { React.useEffect(() => {
querySessions((sessions) => { querySessions((sessions) => {
if (sessions && sessions.length > 0) { if (sessions && sessions.length > 0) {
setSessions(sessions) setSessions(sessions);
queryAssets(setAssets) queryAssets(setAssets);
} else { } else {
router.push("/setup"); router.push('/setup');
} }
}).catch((err) => { }).catch((err) => {
console.error(err); console.error(err);
}) });
}, [router]) }, [router]);
if (sessions.length == 0) { if (sessions.length == 0) {
return ( return (
@ -76,7 +74,7 @@ export default function Home() {
); );
} }
console.log("index: assets", assets) console.log('index: assets', assets);
return ( return (
<DAppProvider config={config}> <DAppProvider config={config}>
@ -87,11 +85,13 @@ export default function Home() {
</Typography> </Typography>
<div className={classes.grid}> <div className={classes.grid}>
<Grid container <Grid
container
direction="row" direction="row"
justifyContent="space-around" justifyContent="space-around"
alignItems="flex-start" alignItems="flex-start"
spacing={1}> spacing={1}
>
<Grid item xs={12} md={8}> <Grid item xs={12} md={8}>
<TotalAssetSummary assets={assets} /> <TotalAssetSummary assets={assets} />
<TotalAssetsPie assets={assets} /> <TotalAssetsPie assets={assets} />
@ -109,7 +109,5 @@ export default function Home() {
<ExchangeSessionTabPanel /> <ExchangeSessionTabPanel />
</DashboardLayout> </DashboardLayout>
</DAppProvider> </DAppProvider>
); );
} }

View File

@ -7,7 +7,6 @@ import {queryClosedOrders} from '../api/bbgo';
import { DataGrid } from '@material-ui/data-grid'; import { DataGrid } from '@material-ui/data-grid';
import DashboardLayout from '../layouts/DashboardLayout'; import DashboardLayout from '../layouts/DashboardLayout';
const columns = [ const columns = [
{ field: 'gid', headerName: 'GID', width: 80, type: 'number' }, { field: 'gid', headerName: 'GID', width: 80, type: 'number' },
{ field: 'clientOrderID', headerName: 'Client Order ID', width: 130 }, { field: 'clientOrderID', headerName: 'Client Order ID', width: 130 },
@ -15,9 +14,18 @@ const columns = [
{ field: 'symbol', headerName: 'Symbol' }, { field: 'symbol', headerName: 'Symbol' },
{ field: 'orderType', headerName: 'Type' }, { field: 'orderType', headerName: 'Type' },
{ field: 'side', headerName: 'Side', width: 90 }, { field: 'side', headerName: 'Side', width: 90 },
{field: 'averagePrice', headerName: 'Average Price', type: 'number', width: 120}, {
field: 'averagePrice',
headerName: 'Average Price',
type: 'number',
width: 120,
},
{ field: 'quantity', headerName: 'Quantity', type: 'number' }, { field: 'quantity', headerName: 'Quantity', type: 'number' },
{field: 'executedQuantity', headerName: 'Executed Quantity', type: 'number'}, {
field: 'executedQuantity',
headerName: 'Executed Quantity',
type: 'number',
},
{ field: 'status', headerName: 'Status' }, { field: 'status', headerName: 'Status' },
{ field: 'isMargin', headerName: 'Margin' }, { field: 'isMargin', headerName: 'Margin' },
{ field: 'isIsolated', headerName: 'Isolated' }, { field: 'isIsolated', headerName: 'Isolated' },
@ -32,22 +40,24 @@ const useStyles = makeStyles((theme) => ({
dataGridContainer: { dataGridContainer: {
display: 'flex', display: 'flex',
height: 'calc(100vh - 64px - 120px)', height: 'calc(100vh - 64px - 120px)',
} },
})); }));
export default function Orders() { export default function Orders() {
const classes = useStyles(); const classes = useStyles();
const [orders, setOrders] = useState([]) const [orders, setOrders] = useState([]);
useEffect(() => { useEffect(() => {
queryClosedOrders({}, (orders) => { queryClosedOrders({}, (orders) => {
setOrders(orders.map((o) => { setOrders(
orders.map((o) => {
o.id = o.gid; o.id = o.gid;
return o return o;
}))
}) })
}, []) );
});
}, []);
return ( return (
<DashboardLayout> <DashboardLayout>
@ -69,4 +79,3 @@ export default function Orders() {
</DashboardLayout> </DashboardLayout>
); );
} }

View File

@ -8,12 +8,12 @@ import Stepper from '@material-ui/core/Stepper';
import Step from '@material-ui/core/Step'; import Step from '@material-ui/core/Step';
import StepLabel from '@material-ui/core/StepLabel'; import StepLabel from '@material-ui/core/StepLabel';
import ConfigureDatabaseForm from "../../components/ConfigureDatabaseForm"; import ConfigureDatabaseForm from '../../components/ConfigureDatabaseForm';
import AddExchangeSessionForm from "../../components/AddExchangeSessionForm"; import AddExchangeSessionForm from '../../components/AddExchangeSessionForm';
import ReviewSessions from "../../components/ReviewSessions"; import ReviewSessions from '../../components/ReviewSessions';
import ConfigureGridStrategyForm from "../../components/ConfigureGridStrategyForm"; import ConfigureGridStrategyForm from '../../components/ConfigureGridStrategyForm';
import ReviewStrategies from "../../components/ReviewStrategies"; import ReviewStrategies from '../../components/ReviewStrategies';
import SaveConfigAndRestart from "../../components/SaveConfigAndRestart"; import SaveConfigAndRestart from '../../components/SaveConfigAndRestart';
import PlainLayout from '../../layouts/PlainLayout'; import PlainLayout from '../../layouts/PlainLayout';
@ -23,52 +23,79 @@ const useStyles = makeStyles((theme) => ({
}, },
})); }));
const steps = ['Configure Database', 'Add Exchange Session', 'Review Sessions', 'Configure Strategy', 'Review Strategies', 'Save Config and Restart']; const steps = [
'Configure Database',
'Add Exchange Session',
'Review Sessions',
'Configure Strategy',
'Review Strategies',
'Save Config and Restart',
];
function getStepContent(step, setActiveStep) { function getStepContent(step, setActiveStep) {
switch (step) { switch (step) {
case 0: case 0:
return <ConfigureDatabaseForm onConfigured={() => { return (
setActiveStep(1) <ConfigureDatabaseForm
}}/>; onConfigured={() => {
setActiveStep(1);
}}
/>
);
case 1: case 1:
return ( return (
<AddExchangeSessionForm <AddExchangeSessionForm
onBack={() => { setActiveStep(0) }} onBack={() => {
onAdded={() => { setActiveStep(2) }} setActiveStep(0);
}}
onAdded={() => {
setActiveStep(2);
}}
/> />
); );
case 2: case 2:
return ( return (
<ReviewSessions <ReviewSessions
onBack={() => { setActiveStep(1) }} onBack={() => {
onNext={() => { setActiveStep(3) }} setActiveStep(1);
}}
onNext={() => {
setActiveStep(3);
}}
/> />
); );
case 3: case 3:
return ( return (
<ConfigureGridStrategyForm <ConfigureGridStrategyForm
onBack={() => { setActiveStep(2) }} onBack={() => {
onAdded={() => { setActiveStep(4) }} setActiveStep(2);
}}
onAdded={() => {
setActiveStep(4);
}}
/> />
); );
case 4: case 4:
return ( return (
<ReviewStrategies <ReviewStrategies
onBack={() => { setActiveStep(3) }} onBack={() => {
onNext={() => { setActiveStep(5) }} setActiveStep(3);
}}
onNext={() => {
setActiveStep(5);
}}
/> />
); );
case 5: case 5:
return ( return (
<SaveConfigAndRestart <SaveConfigAndRestart
onBack={() => { setActiveStep(4) }} onBack={() => {
onRestarted={() => { setActiveStep(4);
}} }}
onRestarted={() => {}}
/> />
) );
default: default:
throw new Error('Unknown step'); throw new Error('Unknown step');
@ -103,4 +130,3 @@ export default function Setup() {
</PlainLayout> </PlainLayout>
); );
} }

View File

@ -27,22 +27,24 @@ const useStyles = makeStyles((theme) => ({
dataGridContainer: { dataGridContainer: {
display: 'flex', display: 'flex',
height: 'calc(100vh - 64px - 120px)', height: 'calc(100vh - 64px - 120px)',
} },
})); }));
export default function Trades() { export default function Trades() {
const classes = useStyles(); const classes = useStyles();
const [trades, setTrades] = useState([]) const [trades, setTrades] = useState([]);
useEffect(() => { useEffect(() => {
queryTrades({}, (trades) => { queryTrades({}, (trades) => {
setTrades(trades.map((o) => { setTrades(
trades.map((o) => {
o.id = o.gid; o.id = o.gid;
return o return o;
}))
}) })
}, []) );
});
}, []);
return ( return (
<DashboardLayout> <DashboardLayout>
@ -64,4 +66,3 @@ export default function Trades() {
</DashboardLayout> </DashboardLayout>
); );
} }

View File

@ -6,13 +6,13 @@ module.exports = {
'postcss-preset-env', 'postcss-preset-env',
{ {
autoprefixer: { autoprefixer: {
flexbox: 'no-2009' flexbox: 'no-2009',
}, },
stage: 3, stage: 3,
features: { features: {
'custom-properties': false 'custom-properties': false,
} },
} },
] ],
] ],
} };

View File

@ -1,28 +1,26 @@
export function currencyColor(currency) { export function currencyColor(currency) {
switch (currency) { switch (currency) {
case "BTC": case 'BTC':
return "#f69c3d" return '#f69c3d';
case "ETH": case 'ETH':
return "#497493" return '#497493';
case "MCO": case 'MCO':
return "#032144" return '#032144';
case "OMG": case 'OMG':
return "#2159ec" return '#2159ec';
case "LTC": case 'LTC':
return "#949494" return '#949494';
case "USDT": case 'USDT':
return "#2ea07b" return '#2ea07b';
case "SAND": case 'SAND':
return "#2E9AD0" return '#2E9AD0';
case "XRP": case 'XRP':
return "#00AAE4" return '#00AAE4';
case "BCH": case 'BCH':
return "#8DC351" return '#8DC351';
case "MAX": case 'MAX':
return "#2D4692" return '#2D4692';
case "TWD": case 'TWD':
return "#4A7DED" return '#4A7DED';
} }
} }

View File

@ -1,11 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "es5",
"lib": [ "lib": ["dom", "dom.iterable", "esnext"],
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"strict": false, "strict": false,
@ -18,12 +14,6 @@
"isolatedModules": true, "isolatedModules": true,
"jsx": "preserve" "jsx": "preserve"
}, },
"include": [ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"next-env.d.ts", "exclude": ["node_modules"]
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules"
]
} }