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

@ -20,297 +20,323 @@ import Alert from '@material-ui/lab/Alert';
import VisibilityOff from '@material-ui/icons/VisibilityOff'; import VisibilityOff from '@material-ui/icons/VisibilityOff';
import Visibility from '@material-ui/icons/Visibility'; import Visibility from '@material-ui/icons/Visibility';
import {addSession, testSessionConnection} from '../api/bbgo'; import { addSession, testSessionConnection } from '../api/bbgo';
import {makeStyles} from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
formControl: { formControl: {
marginTop: theme.spacing(1), marginTop: theme.spacing(1),
marginBottom: theme.spacing(1), marginBottom: theme.spacing(1),
minWidth: 120, minWidth: 120,
}, },
buttons: { buttons: {
display: 'flex', display: 'flex',
justifyContent: 'flex-end', justifyContent: 'flex-end',
marginTop: theme.spacing(2), marginTop: theme.spacing(2),
paddingTop: theme.spacing(2), paddingTop: theme.spacing(2),
paddingBottom: theme.spacing(2), paddingBottom: theme.spacing(2),
'& > *': { '& > *': {
marginLeft: theme.spacing(1), marginLeft: theme.spacing(1),
}
}, },
},
})); }));
export default function AddExchangeSessionForm({onBack, onAdded}) { export default function AddExchangeSessionForm({ onBack, onAdded }) {
const classes = useStyles(); const classes = useStyles();
const [exchangeType, setExchangeType] = React.useState('max'); const [exchangeType, setExchangeType] = React.useState('max');
const [customSessionName, setCustomSessionName] = React.useState(false); const [customSessionName, setCustomSessionName] = React.useState(false);
const [sessionName, setSessionName] = React.useState(exchangeType); const [sessionName, setSessionName] = React.useState(exchangeType);
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 [response, setResponse] = React.useState(null); const [response, setResponse] = React.useState(null);
const [apiKey, setApiKey] = React.useState(''); const [apiKey, setApiKey] = React.useState('');
const [apiSecret, setApiSecret] = React.useState(''); const [apiSecret, setApiSecret] = React.useState('');
const [showApiKey, setShowApiKey] = React.useState(false); const [showApiKey, setShowApiKey] = React.useState(false);
const [showApiSecret, setShowApiSecret] = React.useState(false); const [showApiSecret, setShowApiSecret] = React.useState(false);
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 = () => {
return {
name: sessionName,
exchange: exchangeType,
key: apiKey,
secret: apiSecret,
margin: isMargin,
envVarPrefix: exchangeType.toUpperCase(),
isolatedMargin: isIsolatedMargin,
isolatedMarginSymbol: isolatedMarginSymbol,
}; };
};
const createSessionConfig = () => { const handleAdd = (event) => {
return { const payload = createSessionConfig();
name: sessionName, addSession(payload, (response) => {
exchange: exchangeType, setResponse(response);
key: apiKey, if (onAdded) {
secret: apiSecret, setTimeout(onAdded, 3000);
margin: isMargin, }
envVarPrefix: exchangeType.toUpperCase(), }).catch((error) => {
isolatedMargin: isIsolatedMargin, console.error(error);
isolatedMarginSymbol: isolatedMarginSymbol, setResponse(error.response);
} });
} };
const handleAdd = (event) => { const handleTestConnection = (event) => {
const payload = createSessionConfig() const payload = createSessionConfig();
addSession(payload, (response) => { setTesting(true);
setResponse(response) testSessionConnection(payload, (response) => {
if (onAdded) { console.log(response);
setTimeout(onAdded, 3000) setTesting(false);
setTestResponse(response);
}).catch((error) => {
console.error(error);
setTesting(false);
setTestResponse(error.response);
});
};
return (
<React.Fragment>
<Typography variant="h6" gutterBottom>
Add Exchange Session
</Typography>
<Grid container spacing={3}>
<Grid item xs={12}>
<FormControl className={classes.formControl}>
<InputLabel id="exchange-type-select-label">Exchange</InputLabel>
<Select
labelId="exchange-type-select-label"
id="exchange-type-select"
value={exchangeType}
onChange={handleExchangeTypeChange}
>
<MenuItem value={'binance'}>Binance</MenuItem>
<MenuItem value={'max'}>Max</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
id="name"
name="name"
label="Session Name"
fullWidth
required
disabled={!customSessionName}
onChange={(event) => {
setSessionName(event.target.value);
}}
value={sessionName}
/>
</Grid>
<Grid item xs={12} sm={6}>
<FormControlLabel
control={
<Checkbox
color="secondary"
name="custom_session_name"
onChange={(event) => {
setCustomSessionName(event.target.checked);
}}
value="1"
/>
} }
}).catch((error) => { label="Custom exchange session name"
console.error(error) />
setResponse(error.response) <FormHelperText id="session-name-helper-text">
}) By default, the session name will be the exchange type name, e.g.{' '}
}; <code>binance</code> or <code>max</code>.<br />
If you're using multiple exchange sessions, you might need to custom
the session name. <br />
This is for advanced users.
</FormHelperText>
</Grid>
const handleTestConnection = (event) => { <Grid item xs={12}>
const payload = createSessionConfig() <FormControl fullWidth variant="filled">
setTesting(true) <InputLabel htmlFor="apiKey">API Key</InputLabel>
testSessionConnection(payload, (response) => { <FilledInput
console.log(response) id="apiKey"
setTesting(false) type={showApiKey ? 'text' : 'password'}
setTestResponse(response) value={apiKey}
}).catch((error) => { endAdornment={
console.error(error) <InputAdornment position="end">
setTesting(false) <IconButton
setTestResponse(error.response) aria-label="toggle key visibility"
})
};
return (
<React.Fragment>
<Typography variant="h6" gutterBottom>
Add Exchange Session
</Typography>
<Grid container spacing={3}>
<Grid item xs={12}>
<FormControl className={classes.formControl}>
<InputLabel id="exchange-type-select-label">Exchange</InputLabel>
<Select
labelId="exchange-type-select-label"
id="exchange-type-select"
value={exchangeType}
onChange={handleExchangeTypeChange}
>
<MenuItem value={"binance"}>Binance</MenuItem>
<MenuItem value={"max"}>Max</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
id="name"
name="name"
label="Session Name"
fullWidth
required
disabled={!customSessionName}
onChange={(event) => {
setSessionName(event.target.value)
}}
value={sessionName}
/>
</Grid>
<Grid item xs={12} sm={6}>
<FormControlLabel
control={<Checkbox color="secondary" name="custom_session_name"
onChange={(event) => {
setCustomSessionName(event.target.checked);
}} value="1"/>}
label="Custom exchange session name"
/>
<FormHelperText id="session-name-helper-text">
By default, the session name will be the exchange type name,
e.g. <code>binance</code> or <code>max</code>.<br/>
If you're using multiple exchange sessions, you might need to custom the session name. <br/>
This is for advanced users.
</FormHelperText>
</Grid>
<Grid item xs={12}>
<FormControl fullWidth variant="filled">
<InputLabel htmlFor="apiKey">API Key</InputLabel>
<FilledInput
id="apiKey"
type={showApiKey ? 'text' : 'password'}
value={apiKey}
endAdornment={
<InputAdornment position="end">
<IconButton
aria-label="toggle key visibility"
onClick={() => { setShowApiKey(!showApiKey) }}
onMouseDown={(event) => { event.preventDefault() }}
edge="end"
>
{showApiKey ? <Visibility /> : <VisibilityOff />}
</IconButton>
</InputAdornment>
}
onChange={(event) => {
setApiKey(event.target.value)
resetTestResponse()
}}
/>
</FormControl>
</Grid>
<Grid item xs={12}>
<FormControl fullWidth variant="filled">
<InputLabel htmlFor="apiSecret">API Secret</InputLabel>
<FilledInput
id="apiSecret"
type={showApiSecret ? 'text' : 'password'}
value={apiSecret}
endAdornment={
<InputAdornment position="end">
<IconButton
aria-label="toggle key visibility"
onClick={() => { setShowApiSecret(!showApiSecret) }}
onMouseDown={(event) => { event.preventDefault() }}
edge="end"
>
{showApiSecret ? <Visibility /> : <VisibilityOff />}
</IconButton>
</InputAdornment>
}
onChange={(event) => {
setApiSecret(event.target.value)
resetTestResponse()
}}
/>
</FormControl>
</Grid>
{exchangeType === "binance" ? (
<Grid item xs={12}>
<FormControlLabel
control={<Checkbox color="secondary" name="isMargin" onChange={(event) => {
setIsMargin(event.target.checked);
resetTestResponse();
}} value="1"/>}
label="Use margin trading."
/>
<FormHelperText id="isMargin-helper-text">This is only available for Binance. Please use the
leverage at your own risk.</FormHelperText>
<FormControlLabel
control={<Checkbox color="secondary" name="isIsolatedMargin"
onChange={(event) => {
setIsIsolatedMargin(event.target.checked);
resetTestResponse()
}} value="1"/>}
label="Use isolated margin trading."
/>
<FormHelperText id="isIsolatedMargin-helper-text">This is only available for Binance. If this is
set, you can only trade one symbol with one session.</FormHelperText>
{isIsolatedMargin ?
<TextField
id="isolatedMarginSymbol"
name="isolatedMarginSymbol"
label="Isolated Margin Symbol"
onChange={(event) => {
setIsolatedMarginSymbol(event.target.value);
resetTestResponse()
}}
fullWidth
required
/>
: null}
</Grid>
) : null}
</Grid>
<div className={classes.buttons}>
<Button
onClick={() => { onClick={() => {
if (onBack) { setShowApiKey(!showApiKey);
onBack(); }}
} onMouseDown={(event) => {
}}> event.preventDefault();
Back }}
</Button> edge="end"
>
{showApiKey ? <Visibility /> : <VisibilityOff />}
</IconButton>
</InputAdornment>
}
onChange={(event) => {
setApiKey(event.target.value);
resetTestResponse();
}}
/>
</FormControl>
</Grid>
<Button <Grid item xs={12}>
color="primary" <FormControl fullWidth variant="filled">
onClick={handleTestConnection} <InputLabel htmlFor="apiSecret">API Secret</InputLabel>
disabled={testing}> <FilledInput
{testing ? "Testing" : "Test Connection"} id="apiSecret"
</Button> type={showApiSecret ? 'text' : 'password'}
value={apiSecret}
endAdornment={
<InputAdornment position="end">
<IconButton
aria-label="toggle key visibility"
onClick={() => {
setShowApiSecret(!showApiSecret);
}}
onMouseDown={(event) => {
event.preventDefault();
}}
edge="end"
>
{showApiSecret ? <Visibility /> : <VisibilityOff />}
</IconButton>
</InputAdornment>
}
onChange={(event) => {
setApiSecret(event.target.value);
resetTestResponse();
}}
/>
</FormControl>
</Grid>
<Button {exchangeType === 'binance' ? (
variant="contained" <Grid item xs={12}>
color="primary" <FormControlLabel
onClick={handleAdd} control={
> <Checkbox
Add color="secondary"
</Button> name="isMargin"
</div> onChange={(event) => {
setIsMargin(event.target.checked);
resetTestResponse();
}}
value="1"
/>
}
label="Use margin trading."
/>
<FormHelperText id="isMargin-helper-text">
This is only available for Binance. Please use the leverage at
your own risk.
</FormHelperText>
{ <FormControlLabel
testResponse ? testResponse.error ? ( control={
<Box m={2}> <Checkbox
<Alert severity="error">{testResponse.error}</Alert> color="secondary"
</Box> name="isIsolatedMargin"
) : testResponse.success ? ( onChange={(event) => {
<Box m={2}> setIsIsolatedMargin(event.target.checked);
<Alert severity="success">Connection Test Succeeded</Alert> resetTestResponse();
</Box> }}
) : null : null value="1"
/>
}
label="Use isolated margin trading."
/>
<FormHelperText id="isIsolatedMargin-helper-text">
This is only available for Binance. If this is set, you can only
trade one symbol with one session.
</FormHelperText>
{isIsolatedMargin ? (
<TextField
id="isolatedMarginSymbol"
name="isolatedMarginSymbol"
label="Isolated Margin Symbol"
onChange={(event) => {
setIsolatedMarginSymbol(event.target.value);
resetTestResponse();
}}
fullWidth
required
/>
) : null}
</Grid>
) : null}
</Grid>
<div className={classes.buttons}>
<Button
onClick={() => {
if (onBack) {
onBack();
} }
}}
>
Back
</Button>
{ <Button
response ? response.error ? ( color="primary"
<Box m={2}> onClick={handleTestConnection}
<Alert severity="error">{response.error}</Alert> disabled={testing}
</Box> >
) : response.success ? ( {testing ? 'Testing' : 'Test Connection'}
<Box m={2}> </Button>
<Alert severity="success">Exchange Session Added</Alert>
</Box>
) : null : null
}
<Button variant="contained" color="primary" onClick={handleAdd}>
Add
</Button>
</div>
</React.Fragment> {testResponse ? (
); testResponse.error ? (
<Box m={2}>
<Alert severity="error">{testResponse.error}</Alert>
</Box>
) : testResponse.success ? (
<Box m={2}>
<Alert severity="success">Connection Test Succeeded</Alert>
</Box>
) : null
) : null}
{response ? (
response.error ? (
<Box m={2}>
<Alert severity="error">{response.error}</Alert>
</Box>
) : response.success ? (
<Box m={2}>
<Alert severity="success">Exchange Session Added</Alert>
</Box>
) : null
) : null}
</React.Fragment>
);
} }

View File

@ -13,184 +13,197 @@ import FormLabel from '@material-ui/core/FormLabel';
import Alert from '@material-ui/lab/Alert'; import Alert from '@material-ui/lab/Alert';
import {configureDatabase, testDatabaseConnection} from '../api/bbgo'; import { configureDatabase, testDatabaseConnection } from '../api/bbgo';
import {makeStyles} from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
formControl: { formControl: {
marginTop: theme.spacing(1), marginTop: theme.spacing(1),
marginBottom: theme.spacing(1), marginBottom: theme.spacing(1),
minWidth: 120, minWidth: 120,
}, },
buttons: { buttons: {
display: 'flex', display: 'flex',
justifyContent: 'flex-end', justifyContent: 'flex-end',
marginTop: theme.spacing(2), marginTop: theme.spacing(2),
paddingTop: theme.spacing(2), paddingTop: theme.spacing(2),
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);
setTesting(false); setTesting(false);
setTestResponse(response); setTestResponse(response);
if (onConfigured) { if (onConfigured) {
setConfigured(true); setConfigured(true);
setTimeout(onConfigured, 3000); setTimeout(onConfigured, 3000);
} }
}).catch((err) => {
console.error(err);
setTesting(false);
setTestResponse(err.response.data);
});
};
}).catch((err) => { const handleTestConnection = (event) => {
console.error(err); const dsn = getDSN();
setTesting(false);
setTestResponse(err.response.data);
})
}
const handleTestConnection = (event) => { setTesting(true);
const dsn = getDSN() testDatabaseConnection({ driver, dsn }, (response) => {
console.log(response);
setTesting(false);
setTestResponse(response);
}).catch((err) => {
console.error(err);
setTesting(false);
setTestResponse(err.response.data);
});
};
setTesting(true); return (
testDatabaseConnection({driver, dsn}, (response) => { <React.Fragment>
console.log(response) <Typography variant="h6" gutterBottom>
setTesting(false) Configure Database
setTestResponse(response) </Typography>
}).catch((err) => {
console.error(err)
setTesting(false)
setTestResponse(err.response.data)
})
};
return ( <Typography variant="body1" gutterBottom>
<React.Fragment> If you have database installed on your machine, you can enter the DSN
<Typography variant="h6" gutterBottom> string in the following field. Please note this is optional, you CAN
Configure Database SKIP this step.
</Typography> </Typography>
<Grid container spacing={3}>
<Grid item xs={12} sm={4}>
<Box m={6}>
<FormControl component="fieldset" required={true}>
<FormLabel component="legend">Database Driver</FormLabel>
<RadioGroup
aria-label="driver"
name="driver"
value={driver}
onChange={(event) => {
setDriver(event.target.value);
}}
>
<FormControlLabel
value="sqlite3"
control={<Radio />}
label="Standard (Default)"
/>
<FormControlLabel
value="mysql"
control={<Radio />}
label="MySQL"
/>
</RadioGroup>
</FormControl>
<FormHelperText></FormHelperText>
</Box>
</Grid>
{driver === 'mysql' ? (
<Grid item xs={12} sm={8}>
<TextField
id="mysql_url"
name="mysql_url"
label="MySQL Data Source Name"
fullWidth
required
defaultValue={mysqlURL}
onChange={(event) => {
setMysqlURL(event.target.value);
resetTestResponse();
}}
/>
<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 in the following field. If you have database installed on your machine, you can enter the
Please note this is optional, you CAN SKIP this step. DSN string like the following format:
<br />
<pre>
<code>root:password@tcp(127.0.0.1:3306)/bbgo</code>
</pre>
<br />
Be sure to create your database before using it. You need to
execute the following statement to create a database:
<br />
<pre>
<code>CREATE DATABASE bbgo CHARSET utf8;</code>
</pre>
</Typography> </Typography>
</Grid>
) : (
<Grid item xs={12} sm={8}>
<Box m={6}>
<Typography variant="body1" gutterBottom>
If you don't know what to choose, just pick the standard driver
(sqlite3).
<br />
For professionals, you can pick MySQL driver, BBGO works best
with MySQL, especially for larger data scale.
</Typography>
</Box>
</Grid>
)}
</Grid>
<div className={classes.buttons}>
<Button
color="primary"
onClick={handleTestConnection}
disabled={testing || configured}
>
{testing ? 'Testing' : 'Test Connection'}
</Button>
<Grid container spacing={3}> <Button
<Grid item xs={12} sm={4}> variant="contained"
<Box m={6}> color="primary"
<FormControl component="fieldset" required={true}> disabled={testing || configured}
<FormLabel component="legend">Database Driver</FormLabel> onClick={handleConfigureDatabase}
<RadioGroup aria-label="driver" name="driver" value={driver} onChange={(event) => { >
setDriver(event.target.value); Configure
}}> </Button>
<FormControlLabel value="sqlite3" control={<Radio/>} label="Standard (Default)"/> </div>
<FormControlLabel value="mysql" control={<Radio/>} label="MySQL"/>
</RadioGroup>
</FormControl>
<FormHelperText>
</FormHelperText>
</Box>
</Grid>
{driver === "mysql" ? (
<Grid item xs={12} sm={8}>
<TextField id="mysql_url" name="mysql_url" label="MySQL Data Source Name"
fullWidth
required
defaultValue={mysqlURL}
onChange={(event) => {
setMysqlURL(event.target.value)
resetTestResponse()
}}
/>
<FormHelperText>MySQL DSN</FormHelperText>
<Typography variant="body1" gutterBottom>
If you have database installed on your machine, you can enter the DSN string like the
following
format:
<br/>
<pre><code>root:password@tcp(127.0.0.1:3306)/bbgo</code></pre>
<br/>
Be sure to create your database before using it. You need to execute the following statement
to
create a database:
<br/>
<pre><code>CREATE DATABASE bbgo CHARSET utf8;</code></pre>
</Typography>
</Grid>
) : (
<Grid item xs={12} sm={8}>
<Box m={6}>
<Typography variant="body1" gutterBottom>
If you don't know what to choose, just pick the standard driver (sqlite3).
<br/>
For professionals, you can pick MySQL driver, BBGO works best with MySQL, especially for
larger data scale.
</Typography>
</Box>
</Grid>
)}
</Grid>
<div className={classes.buttons}>
<Button
color="primary"
onClick={handleTestConnection}
disabled={testing || configured}
>
{testing ? "Testing" : "Test Connection"}
</Button>
<Button
variant="contained"
color="primary"
disabled={testing || configured}
onClick={handleConfigureDatabase}
>
Configure
</Button>
</div>
{
testResponse ? testResponse.error ? (
<Box m={2}>
<Alert severity="error">{testResponse.error}</Alert>
</Box>
) : testResponse.success ? (
<Box m={2}>
<Alert severity="success">Connection Test Succeeded</Alert>
</Box>
) : null : null
}
</React.Fragment>
);
{testResponse ? (
testResponse.error ? (
<Box m={2}>
<Alert severity="error">{testResponse.error}</Alert>
</Box>
) : testResponse.success ? (
<Box m={2}>
<Alert severity="success">Connection Test Succeeded</Alert>
</Box>
) : null
) : null}
</React.Fragment>
);
} }

View File

@ -5,8 +5,12 @@ import Grid from '@material-ui/core/Grid';
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 {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,409 +24,423 @@ 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);
}
} }
}
} }
function StandardNumberFormat(props) { function StandardNumberFormat(props) {
const {inputRef, onChange, ...other} = props; const { inputRef, onChange, ...other } = props;
return ( return (
<NumberFormat <NumberFormat
{...other} {...other}
getInputRef={inputRef} getInputRef={inputRef}
onValueChange={(values) => { onValueChange={(values) => {
onChange({ onChange({
target: { target: {
name: props.name, name: props.name,
value: values.value, value: values.value,
}, },
}); });
}} }}
thousandSeparator thousandSeparator
isNumericString isNumericString
/> />
); );
} }
StandardNumberFormat.propTypes = { StandardNumberFormat.propTypes = {
inputRef: PropTypes.func.isRequired, inputRef: PropTypes.func.isRequired,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
}; };
function PriceNumberFormat(props) { function PriceNumberFormat(props) {
const {inputRef, onChange, ...other} = props; const { inputRef, onChange, ...other } = props;
return ( return (
<NumberFormat <NumberFormat
{...other} {...other}
getInputRef={inputRef} getInputRef={inputRef}
onValueChange={(values) => { onValueChange={(values) => {
onChange({ onChange({
target: { target: {
name: props.name, name: props.name,
value: values.value, value: values.value,
}, },
}); });
}} }}
thousandSeparator thousandSeparator
isNumericString isNumericString
prefix="$" prefix="$"
/> />
); );
} }
PriceNumberFormat.propTypes = { PriceNumberFormat.propTypes = {
inputRef: PropTypes.func.isRequired, inputRef: PropTypes.func.isRequired,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
}; };
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
formControl: { formControl: {
marginTop: theme.spacing(1), marginTop: theme.spacing(1),
marginBottom: theme.spacing(1), marginBottom: theme.spacing(1),
minWidth: 120, minWidth: 120,
}, },
buttons: { buttons: {
display: 'flex', display: 'flex',
justifyContent: 'flex-end', justifyContent: 'flex-end',
marginTop: theme.spacing(2), marginTop: theme.spacing(2),
paddingTop: theme.spacing(2), paddingTop: theme.spacing(2),
paddingBottom: theme.spacing(2), paddingBottom: theme.spacing(2),
'& > *': { '& > *': {
marginLeft: theme.spacing(1), marginLeft: theme.spacing(1),
}
}, },
},
})); }));
export default function ConfigureGridStrategyForm({ onBack, onAdded }) {
const classes = useStyles();
export default function ConfigureGridStrategyForm({onBack, onAdded}) { const [errors, setErrors] = React.useState({});
const classes = useStyles();
const [errors, setErrors] = React.useState({}) const [sessions, setSessions] = React.useState([]);
const [sessions, setSessions] = React.useState([]); const [activeSessionSymbols, setActiveSessionSymbols] = React.useState([]);
const [activeSessionSymbols, setActiveSessionSymbols] = React.useState([]); const [selectedSessionName, setSelectedSessionName] = React.useState(null);
const [selectedSessionName, setSelectedSessionName] = React.useState(null); const [selectedSymbol, setSelectedSymbol] = React.useState('');
const [selectedSymbol, setSelectedSymbol] = React.useState(''); const [quantityBy, setQuantityBy] = React.useState('fixedAmount');
const [quantityBy, setQuantityBy] = React.useState('fixedAmount'); const [upperPrice, setUpperPrice] = React.useState(30000.0);
const [lowerPrice, setLowerPrice] = React.useState(10000.0);
const [upperPrice, setUpperPrice] = React.useState(30000.0); const [fixedAmount, setFixedAmount] = React.useState(100.0);
const [lowerPrice, setLowerPrice] = React.useState(10000.0); const [fixedQuantity, setFixedQuantity] = React.useState(1.234);
const [gridNumber, setGridNumber] = React.useState(20);
const [profitSpread, setProfitSpread] = React.useState(100.0);
const [fixedAmount, setFixedAmount] = React.useState(100.0); const [response, setResponse] = React.useState({});
const [fixedQuantity, setFixedQuantity] = React.useState(1.234);
const [gridNumber, setGridNumber] = React.useState(20);
const [profitSpread, setProfitSpread] = React.useState(100.0);
const [response, setResponse] = React.useState({}); React.useEffect(() => {
querySessions((sessions) => {
setSessions(sessions);
});
}, []);
React.useEffect(() => { const handleAdd = (event) => {
querySessions((sessions) => { const payload = {
setSessions(sessions) symbol: selectedSymbol,
}); gridNumber: parseFloatValid(gridNumber),
}, []) profitSpread: parseFloatValid(profitSpread),
upperPrice: parseFloatValid(upperPrice),
const handleAdd = (event) => { lowerPrice: parseFloatValid(lowerPrice),
const payload = {
symbol: selectedSymbol,
gridNumber: parseFloatValid(gridNumber),
profitSpread: parseFloatValid(profitSpread),
upperPrice: parseFloatValid(upperPrice),
lowerPrice: parseFloatValid(lowerPrice),
}
switch (quantityBy) {
case "fixedQuantity":
payload.quantity = parseFloatValid(fixedQuantity);
break;
case "fixedAmount":
payload.amount = parseFloatValid(fixedAmount);
break;
}
if (!selectedSessionName) {
setErrors({ session: true })
return
}
if (!selectedSymbol) {
setErrors({ symbol: true })
return
}
console.log(payload)
attachStrategyOn(selectedSessionName, "grid", payload, (response) => {
console.log(response)
setResponse(response)
if (onAdded) {
setTimeout(onAdded, 3000)
}
}).catch((err) => {
console.error(err);
setResponse(err.response.data)
}).finally(() => {
setErrors({})
})
}; };
switch (quantityBy) {
case 'fixedQuantity':
payload.quantity = parseFloatValid(fixedQuantity);
break;
const handleQuantityBy = (event) => { case 'fixedAmount':
setQuantityBy(event.target.value); payload.amount = parseFloatValid(fixedAmount);
}; break;
}
const handleSessionChange = (event) => { if (!selectedSessionName) {
const sessionName = event.target.value; setErrors({ session: true });
setSelectedSessionName(sessionName) return;
}
querySessionSymbols(sessionName, (symbols) => { if (!selectedSymbol) {
setActiveSessionSymbols(symbols); setErrors({ symbol: true });
}).catch((err) => { return;
console.error(err); }
setResponse(err.response.data)
})
};
const sessionMenuItems = sessions.map((session, index) => { console.log(payload);
return ( attachStrategyOn(selectedSessionName, 'grid', payload, (response) => {
<MenuItem key={session.name} value={session.name}> console.log(response);
{session.name} setResponse(response);
</MenuItem> if (onAdded) {
); setTimeout(onAdded, 3000);
}
}) })
.catch((err) => {
console.error(err);
setResponse(err.response.data);
})
.finally(() => {
setErrors({});
});
};
const symbolMenuItems = activeSessionSymbols.map((symbol, index) => { const handleQuantityBy = (event) => {
return ( setQuantityBy(event.target.value);
<MenuItem key={symbol} value={symbol}> };
{symbol}
</MenuItem>
);
})
const handleSessionChange = (event) => {
const sessionName = event.target.value;
setSelectedSessionName(sessionName);
querySessionSymbols(sessionName, (symbols) => {
setActiveSessionSymbols(symbols);
}).catch((err) => {
console.error(err);
setResponse(err.response.data);
});
};
const sessionMenuItems = sessions.map((session, index) => {
return ( return (
<React.Fragment> <MenuItem key={session.name} value={session.name}>
<Typography variant="h6" gutterBottom> {session.name}
Add Grid Strategy </MenuItem>
</Typography>
<Typography variant="body1" gutterBottom>
Fixed price band grid strategy uses the fixed price band to place buy/sell orders.
This strategy places sell orders above the current price, places buy orders below the current price.
If any of the order is executed, then it will automatically place a new profit order on the reverse
side.
</Typography>
<Grid container spacing={3}>
<Grid item xs={12}>
<FormControl required className={classes.formControl} error={errors.session}>
<InputLabel id="session-select-label">Session</InputLabel>
<Select
labelId="session-select-label"
id="session-select"
value={selectedSessionName ? selectedSessionName : ''}
onChange={handleSessionChange}
>
{sessionMenuItems}
</Select>
</FormControl>
<FormHelperText id="session-select-helper-text">
Select the exchange session you want to mount this strategy.
</FormHelperText>
</Grid>
<Grid item xs={12}>
<FormControl required className={classes.formControl} error={errors.symbol}>
<InputLabel id="symbol-select-label">Market</InputLabel>
<Select
labelId="symbol-select-label"
id="symbol-select"
value={selectedSymbol ? selectedSymbol : ''}
onChange={(event) => {
setSelectedSymbol(event.target.value);
}}>
{symbolMenuItems}
</Select>
</FormControl>
<FormHelperText id="session-select-helper-text">
Select the market you want to run this strategy
</FormHelperText>
</Grid>
<Grid item xs={12}>
<TextField
id="upperPrice"
name="upper_price"
label="Upper Price"
fullWidth
required
onChange={(event) => {
parseFloatCall(event.target.value, setUpperPrice)
}}
value={upperPrice}
InputProps={{
inputComponent: PriceNumberFormat,
}}
/>
</Grid>
<Grid item xs={12}>
<TextField
id="lowerPrice"
name="lower_price"
label="Lower Price"
fullWidth
required
onChange={(event) => {
parseFloatCall(event.target.value, setLowerPrice)
}}
value={lowerPrice}
InputProps={{
inputComponent: PriceNumberFormat,
}}
/>
</Grid>
<Grid item xs={12}>
<TextField
id="profitSpread"
name="profit_spread"
label="Profit Spread"
fullWidth
required
onChange={(event) => {
parseFloatCall(event.target.value, setProfitSpread)
}}
value={profitSpread}
InputProps={{
inputComponent: StandardNumberFormat,
}}
/>
</Grid>
<Grid item xs={12} sm={3}>
<FormControl component="fieldset">
<FormLabel component="legend">Order Quantity By</FormLabel>
<RadioGroup name="quantityBy" value={quantityBy} onChange={handleQuantityBy}>
<FormControlLabel value="fixedAmount" control={<Radio/>} label="Fixed Amount"/>
<FormControlLabel value="fixedQuantity" control={<Radio/>} label="Fixed Quantity"/>
</RadioGroup>
</FormControl>
</Grid>
<Grid item xs={12} sm={9}>
{quantityBy === "fixedQuantity" ? (
<TextField
id="fixedQuantity"
name="order_quantity"
label="Fixed Quantity"
fullWidth
required
onChange={(event) => {
parseFloatCall(event.target.value, setFixedQuantity)
}}
value={fixedQuantity}
InputProps={{
inputComponent: StandardNumberFormat,
}}
/>
) : null}
{quantityBy === "fixedAmount" ? (
<TextField
id="orderAmount"
name="order_amount"
label="Fixed Amount"
fullWidth
required
onChange={(event) => {
parseFloatCall(event.target.value, setFixedAmount)
}}
value={fixedAmount}
InputProps={{
inputComponent: PriceNumberFormat,
}}
/>
) : null}
</Grid>
<Grid item xs={12}>
<TextField
id="gridNumber"
name="grid_number"
label="Number of Grid"
fullWidth
required
onChange={(event) => {
parseFloatCall(event.target.value, setGridNumber)
}}
value={gridNumber}
InputProps={{
inputComponent: StandardNumberFormat,
}}
/>
</Grid>
</Grid>
<div className={classes.buttons}>
<Button
onClick={() => {
if (onBack) {
onBack();
}
}}>
Back
</Button>
<Button
variant="contained"
color="primary"
onClick={handleAdd}
>
Add Strategy
</Button>
</div>
{
response ? response.error ? (
<Box m={2}>
<Alert severity="error">{response.error}</Alert>
</Box>
) : response.success ? (
<Box m={2}>
<Alert severity="success">Strategy Added</Alert>
</Box>
) : null : null
}
</React.Fragment>
); );
});
const symbolMenuItems = activeSessionSymbols.map((symbol, index) => {
return (
<MenuItem key={symbol} value={symbol}>
{symbol}
</MenuItem>
);
});
return (
<React.Fragment>
<Typography variant="h6" gutterBottom>
Add Grid Strategy
</Typography>
<Typography variant="body1" gutterBottom>
Fixed price band grid strategy uses the fixed price band to place
buy/sell orders. This strategy places sell orders above the current
price, places buy orders below the current price. If any of the order is
executed, then it will automatically place a new profit order on the
reverse side.
</Typography>
<Grid container spacing={3}>
<Grid item xs={12}>
<FormControl
required
className={classes.formControl}
error={errors.session}
>
<InputLabel id="session-select-label">Session</InputLabel>
<Select
labelId="session-select-label"
id="session-select"
value={selectedSessionName ? selectedSessionName : ''}
onChange={handleSessionChange}
>
{sessionMenuItems}
</Select>
</FormControl>
<FormHelperText id="session-select-helper-text">
Select the exchange session you want to mount this strategy.
</FormHelperText>
</Grid>
<Grid item xs={12}>
<FormControl
required
className={classes.formControl}
error={errors.symbol}
>
<InputLabel id="symbol-select-label">Market</InputLabel>
<Select
labelId="symbol-select-label"
id="symbol-select"
value={selectedSymbol ? selectedSymbol : ''}
onChange={(event) => {
setSelectedSymbol(event.target.value);
}}
>
{symbolMenuItems}
</Select>
</FormControl>
<FormHelperText id="session-select-helper-text">
Select the market you want to run this strategy
</FormHelperText>
</Grid>
<Grid item xs={12}>
<TextField
id="upperPrice"
name="upper_price"
label="Upper Price"
fullWidth
required
onChange={(event) => {
parseFloatCall(event.target.value, setUpperPrice);
}}
value={upperPrice}
InputProps={{
inputComponent: PriceNumberFormat,
}}
/>
</Grid>
<Grid item xs={12}>
<TextField
id="lowerPrice"
name="lower_price"
label="Lower Price"
fullWidth
required
onChange={(event) => {
parseFloatCall(event.target.value, setLowerPrice);
}}
value={lowerPrice}
InputProps={{
inputComponent: PriceNumberFormat,
}}
/>
</Grid>
<Grid item xs={12}>
<TextField
id="profitSpread"
name="profit_spread"
label="Profit Spread"
fullWidth
required
onChange={(event) => {
parseFloatCall(event.target.value, setProfitSpread);
}}
value={profitSpread}
InputProps={{
inputComponent: StandardNumberFormat,
}}
/>
</Grid>
<Grid item xs={12} sm={3}>
<FormControl component="fieldset">
<FormLabel component="legend">Order Quantity By</FormLabel>
<RadioGroup
name="quantityBy"
value={quantityBy}
onChange={handleQuantityBy}
>
<FormControlLabel
value="fixedAmount"
control={<Radio />}
label="Fixed Amount"
/>
<FormControlLabel
value="fixedQuantity"
control={<Radio />}
label="Fixed Quantity"
/>
</RadioGroup>
</FormControl>
</Grid>
<Grid item xs={12} sm={9}>
{quantityBy === 'fixedQuantity' ? (
<TextField
id="fixedQuantity"
name="order_quantity"
label="Fixed Quantity"
fullWidth
required
onChange={(event) => {
parseFloatCall(event.target.value, setFixedQuantity);
}}
value={fixedQuantity}
InputProps={{
inputComponent: StandardNumberFormat,
}}
/>
) : null}
{quantityBy === 'fixedAmount' ? (
<TextField
id="orderAmount"
name="order_amount"
label="Fixed Amount"
fullWidth
required
onChange={(event) => {
parseFloatCall(event.target.value, setFixedAmount);
}}
value={fixedAmount}
InputProps={{
inputComponent: PriceNumberFormat,
}}
/>
) : null}
</Grid>
<Grid item xs={12}>
<TextField
id="gridNumber"
name="grid_number"
label="Number of Grid"
fullWidth
required
onChange={(event) => {
parseFloatCall(event.target.value, setGridNumber);
}}
value={gridNumber}
InputProps={{
inputComponent: StandardNumberFormat,
}}
/>
</Grid>
</Grid>
<div className={classes.buttons}>
<Button
onClick={() => {
if (onBack) {
onBack();
}
}}
>
Back
</Button>
<Button variant="contained" color="primary" onClick={handleAdd}>
Add Strategy
</Button>
</div>
{response ? (
response.error ? (
<Box m={2}>
<Alert severity="error">{response.error}</Alert>
</Box>
) : response.success ? (
<Box m={2}>
<Alert severity="success">Strategy Added</Alert>
</Box>
) : null
) : null}
</React.Fragment>
);
} }

View File

@ -1,6 +1,6 @@
import React from "react"; import React from 'react';
import {makeStyles} from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
import ClickAwayListener from '@material-ui/core/ClickAwayListener'; import ClickAwayListener from '@material-ui/core/ClickAwayListener';
@ -9,123 +9,135 @@ 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: {
margin: theme.spacing(1), margin: theme.spacing(1),
padding: theme.spacing(1), padding: theme.spacing(1),
}, },
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 { activateBrowserWallet, account } = useEthers();
const etherBalance = useEtherBalance(account);
const tokenBalance = useTokenBalance(BBG, account);
const [open, setOpen] = React.useState(false);
const classes = useStyles(); const anchorRef = React.useRef(null);
const { activateBrowserWallet, account } = useEthers()
const etherBalance = useEtherBalance(account)
const tokenBalance = useTokenBalance(BBG, account)
const [open, setOpen] = React.useState(false); const handleToggle = () => {
const anchorRef = React.useRef(null); setOpen((prevOpen) => !prevOpen);
};
const handleToggle = () => {
setOpen((prevOpen) => !prevOpen); const handleClose = (event) => {
}; if (anchorRef.current && anchorRef.current.contains(event.target)) {
return;
const handleClose = (event) => {
if (anchorRef.current && anchorRef.current.contains(event.target)) {
return;
}
setOpen(false);
};
function handleListKeyDown(event) {
if (event.key === 'Tab') {
event.preventDefault();
setOpen(false);
} else if (event.key === 'Escape') {
setOpen(false);
}
} }
// return focus to the button when we transitioned from !open -> open
const prevOpen = React.useRef(open);
React.useEffect(() => {
if (prevOpen.current === true && open === false) {
anchorRef.current.focus();
}
prevOpen.current = open;
}, [open]);
return ( setOpen(false);
<> };
{account?
(<>
<Button
ref={anchorRef}
id="composition-button"
aria-controls={open ? 'composition-menu' : undefined}
aria-expanded={open ? 'true' : undefined}
aria-haspopup="true"
onClick={handleToggle}
>
<PersonIcon/>
<ListItemText primary="Profile"/>
</Button>
<Popper
open={open}
anchorEl={anchorRef.current}
role={undefined}
placement="bottom-start"
transition
disablePortal
>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{
transformOrigin:
placement === 'bottom-start' ? 'left top' : 'left bottom',
}}
>
<Paper>
<ClickAwayListener onClickAway={handleClose}>
<MenuList
autoFocusItem={open}
id="composition-menu"
aria-labelledby="composition-button"
onKeyDown={handleListKeyDown}
>
<MenuItem onClick={handleClose}>{account && <p>Account: {account}</p>}</MenuItem>
<MenuItem onClick={handleClose}>{etherBalance && <a>ETH Balance: {formatEther(etherBalance)}</a>}</MenuItem>
<MenuItem onClick={handleClose}>{tokenBalance && <a>BBG Balance: {formatEther(tokenBalance)}</a>}</MenuItem>
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</>):(<div>
<button onClick={() => activateBrowserWallet()} className={classes.buttons}>Connect Wallet</button>
</div>)}
</>
)
function handleListKeyDown(event) {
if (event.key === 'Tab') {
event.preventDefault();
setOpen(false);
} else if (event.key === 'Escape') {
setOpen(false);
}
}
// return focus to the button when we transitioned from !open -> open
const prevOpen = React.useRef(open);
React.useEffect(() => {
if (prevOpen.current === true && open === false) {
anchorRef.current.focus();
}
prevOpen.current = open;
}, [open]);
return (
<>
{account ? (
<>
<Button
ref={anchorRef}
id="composition-button"
aria-controls={open ? 'composition-menu' : undefined}
aria-expanded={open ? 'true' : undefined}
aria-haspopup="true"
onClick={handleToggle}
>
<PersonIcon />
<ListItemText primary="Profile" />
</Button>
<Popper
open={open}
anchorEl={anchorRef.current}
role={undefined}
placement="bottom-start"
transition
disablePortal
>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{
transformOrigin:
placement === 'bottom-start' ? 'left top' : 'left bottom',
}}
>
<Paper>
<ClickAwayListener onClickAway={handleClose}>
<MenuList
autoFocusItem={open}
id="composition-menu"
aria-labelledby="composition-button"
onKeyDown={handleListKeyDown}
>
<MenuItem onClick={handleClose}>
{account && <p>Account: {account}</p>}
</MenuItem>
<MenuItem onClick={handleClose}>
{etherBalance && (
<a>ETH Balance: {formatEther(etherBalance)}</a>
)}
</MenuItem>
<MenuItem onClick={handleClose}>
{tokenBalance && (
<a>BBG Balance: {formatEther(tokenBalance)}</a>
)}
</MenuItem>
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</>
) : (
<div>
<button
onClick={() => activateBrowserWallet()}
className={classes.buttons}
>
Connect Wallet
</button>
</div>
)}
</>
);
} }

View File

@ -1,49 +1,49 @@
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() {
const classes = useStyles(); const classes = useStyles();
const [tabIndex, setTabIndex] = React.useState(0); const [tabIndex, setTabIndex] = React.useState(0);
const handleTabClick = (event, newValue) => { const handleTabClick = (event, newValue) => {
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 (
<Typography variant="h4" gutterBottom> <Paper className={classes.paper}>
Sessions <Typography variant="h4" gutterBottom>
</Typography> Sessions
<Tabs </Typography>
value={tabIndex} <Tabs
onChange={handleTabClick} value={tabIndex}
indicatorColor="primary" onChange={handleTabClick}
textColor="primary" indicatorColor="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

@ -8,81 +8,81 @@ import ListItemText from '@material-ui/core/ListItemText';
import ListItemIcon from '@material-ui/core/ListItemIcon'; 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: {
marginTop: theme.spacing(1), marginTop: theme.spacing(1),
marginBottom: theme.spacing(1), marginBottom: theme.spacing(1),
minWidth: 120, minWidth: 120,
}, },
buttons: { buttons: {
display: 'flex', display: 'flex',
justifyContent: 'flex-end', justifyContent: 'flex-end',
marginTop: theme.spacing(2), marginTop: theme.spacing(2),
paddingTop: theme.spacing(2), paddingTop: theme.spacing(2),
paddingBottom: theme.spacing(2), paddingBottom: theme.spacing(2),
'& > *': { '& > *': {
marginLeft: theme.spacing(1), marginLeft: theme.spacing(1),
}
}, },
},
})); }));
export default function ReviewSessions({onBack, onNext}) { export default function ReviewSessions({ onBack, onNext }) {
const classes = useStyles(); const classes = useStyles();
const [sessions, setSessions] = React.useState([]); const [sessions, setSessions] = React.useState([]);
React.useEffect(() => { React.useEffect(() => {
querySessions((sessions) => { querySessions((sessions) => {
setSessions(sessions) setSessions(sessions);
}); });
}, []) }, []);
const items = sessions.map((session, i) => {
console.log(session)
return (
<ListItem key={session.name}>
<ListItemIcon>
<PowerIcon/>
</ListItemIcon>
<ListItemText primary={session.name} secondary={session.exchange}/>
</ListItem>
);
})
const items = sessions.map((session, i) => {
console.log(session);
return ( return (
<React.Fragment> <ListItem key={session.name}>
<Typography variant="h6" gutterBottom> <ListItemIcon>
Review Sessions <PowerIcon />
</Typography> </ListItemIcon>
<ListItemText primary={session.name} secondary={session.exchange} />
<List component="nav"> </ListItem>
{items}
</List>
<div className={classes.buttons}>
<Button
onClick={() => {
if (onBack) {
onBack();
}
}}>
Back
</Button>
<Button
variant="contained"
color="primary"
onClick={() => {
if (onNext) {
onNext();
}
}}>
Next
</Button>
</div>
</React.Fragment>
); );
});
return (
<React.Fragment>
<Typography variant="h6" gutterBottom>
Review Sessions
</Typography>
<List component="nav">{items}</List>
<div className={classes.buttons}>
<Button
onClick={() => {
if (onBack) {
onBack();
}
}}
>
Back
</Button>
<Button
variant="contained"
color="primary"
onClick={() => {
if (onNext) {
onNext();
}
}}
>
Next
</Button>
</div>
</React.Fragment>
);
} }

View File

@ -15,144 +15,143 @@ import TableContainer from '@material-ui/core/TableContainer';
import TableHead from '@material-ui/core/TableHead'; 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: {
margin: theme.spacing(1), margin: theme.spacing(1),
}, },
formControl: { formControl: {
marginTop: theme.spacing(1), marginTop: theme.spacing(1),
marginBottom: theme.spacing(1), marginBottom: theme.spacing(1),
minWidth: 120, minWidth: 120,
}, },
buttons: { buttons: {
display: 'flex', display: 'flex',
justifyContent: 'flex-end', justifyContent: 'flex-end',
marginTop: theme.spacing(2), marginTop: theme.spacing(2),
paddingTop: theme.spacing(2), paddingTop: theme.spacing(2),
paddingBottom: theme.spacing(2), paddingBottom: theme.spacing(2),
'& > *': { '& > *': {
marginLeft: theme.spacing(1), marginLeft: theme.spacing(1),
}
}, },
},
})); }));
function configToTable(config) { function configToTable(config) {
const rows = Object.getOwnPropertyNames(config).map((k) => { const rows = Object.getOwnPropertyNames(config).map((k) => {
return { return {
key: k, key: k,
val: config[k], val: config[k],
} };
}) });
return ( return (
<TableContainer> <TableContainer>
<Table aria-label="strategy attributes"> <Table aria-label="strategy attributes">
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell>Field</TableCell> <TableCell>Field</TableCell>
<TableCell align="right">Value</TableCell> <TableCell align="right">Value</TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{rows.map((row) => ( {rows.map((row) => (
<TableRow key={row.key}> <TableRow key={row.key}>
<TableCell component="th" scope="row"> <TableCell component="th" scope="row">
{row.key} {row.key}
</TableCell> </TableCell>
<TableCell align="right">{row.val}</TableCell> <TableCell align="right">{row.val}</TableCell>
</TableRow> </TableRow>
))} ))}
</TableBody> </TableBody>
</Table> </Table>
</TableContainer> </TableContainer>
); );
} }
export default function ReviewStrategies({onBack, onNext}) { export default function ReviewStrategies({ onBack, onNext }) {
const classes = useStyles(); const classes = useStyles();
const [strategies, setStrategies] = React.useState([]); const [strategies, setStrategies] = React.useState([]);
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 (
<Card key={i} className={classes.strategyCard}>
<CardHeader
avatar={
<Avatar aria-label="strategy">G</Avatar>
}
action={
<IconButton aria-label="settings">
<MoreVertIcon/>
</IconButton>
}
title={title}
subheader={`Exchange ${mounts.map((m) => m.toUpperCase())}`}
/>
<CardContent>
<Typography variant="body2" color="textSecondary" component="p">
Strategy will be executed on session {mounts.join(',')} with the following configuration:
</Typography>
{configToTable(config)}
</CardContent>
</Card>
);
})
return ( return (
<React.Fragment> <Card key={i} className={classes.strategyCard}>
<Typography variant="h6" gutterBottom> <CardHeader
Review Strategies avatar={<Avatar aria-label="strategy">G</Avatar>}
</Typography> action={
<IconButton aria-label="settings">
<MoreVertIcon />
</IconButton>
}
title={title}
subheader={`Exchange ${mounts.map((m) => m.toUpperCase())}`}
/>
<CardContent>
<Typography variant="body2" color="textSecondary" component="p">
Strategy will be executed on session {mounts.join(',')} with the
following configuration:
</Typography>
<List component="nav"> {configToTable(config)}
{items} </CardContent>
</List> </Card>
<div className={classes.buttons}>
<Button onClick={() => {
if (onBack) {
onBack()
}
}}>
Add New Strategy
</Button>
<Button
variant="contained"
color="primary"
onClick={() => {
if (onNext) {
onNext();
}
}}>
Next
</Button>
</div>
</React.Fragment>
); );
});
return (
<React.Fragment>
<Typography variant="h6" gutterBottom>
Review Strategies
</Typography>
<List component="nav">{items}</List>
<div className={classes.buttons}>
<Button
onClick={() => {
if (onBack) {
onBack();
}
}}
>
Add New Strategy
</Button>
<Button
variant="contained"
color="primary"
onClick={() => {
if (onNext) {
onNext();
}
}}
>
Next
</Button>
</div>
</React.Fragment>
);
} }

View File

@ -1,107 +1,105 @@
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: {
margin: theme.spacing(1), margin: theme.spacing(1),
}, },
formControl: { formControl: {
marginTop: theme.spacing(1), marginTop: theme.spacing(1),
marginBottom: theme.spacing(1), marginBottom: theme.spacing(1),
minWidth: 120, minWidth: 120,
}, },
buttons: { buttons: {
display: 'flex', display: 'flex',
justifyContent: 'flex-end', justifyContent: 'flex-end',
marginTop: theme.spacing(2), marginTop: theme.spacing(2),
paddingTop: theme.spacing(2), paddingTop: theme.spacing(2),
paddingBottom: theme.spacing(2), paddingBottom: theme.spacing(2),
'& > *': { '& > *': {
marginLeft: theme.spacing(1), marginLeft: theme.spacing(1),
}
}, },
},
})); }));
export default function SaveConfigAndRestart({onBack, onRestarted}) { export default function SaveConfigAndRestart({ onBack, onRestarted }) {
const classes = useStyles(); const classes = useStyles();
const {push} = useRouter(); const { push } = useRouter();
const [response, setResponse] = React.useState({}); const [response, setResponse] = React.useState({});
const handleRestart = () => { const handleRestart = () => {
saveConfig((resp) => { saveConfig((resp) => {
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) => {
console.error(err); console.error(err);
setResponse(err.response.data); setResponse(err.response.data);
}); });
}; };
return ( return (
<React.Fragment> <React.Fragment>
<Typography variant="h6" gutterBottom> <Typography variant="h6" gutterBottom>
Save Config and Restart Save Config and Restart
</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
</Typography> dotenv file <code>.env.local</code>.
</Typography>
<div className={classes.buttons}> <div className={classes.buttons}>
<Button onClick={() => { <Button
if (onBack) { onClick={() => {
onBack() if (onBack) {
} onBack();
}}>
Back
</Button>
<Button
variant="contained"
color="primary"
onClick={handleRestart}>
Save and Restart
</Button>
</div>
{
response ? response.error ? (
<Box m={2}>
<Alert severity="error">{response.error}</Alert>
</Box>
) : response.success ? (
<Box m={2}>
<Alert severity="success">Config Saved</Alert>
</Box>
) : null : null
} }
}}
>
Back
</Button>
</React.Fragment> <Button variant="contained" color="primary" onClick={handleRestart}>
); Save and Restart
</Button>
</div>
{response ? (
response.error ? (
<Box m={2}>
<Alert severity="error">{response.error}</Alert>
</Box>
) : response.success ? (
<Box m={2}>
<Alert severity="success">Config Saved</Alert>
</Box>
) : null
) : null}
</React.Fragment>
);
} }

View File

@ -1,102 +1,101 @@
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;
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
root: { root: {
flexGrow: 1, flexGrow: 1,
display: 'flex', display: 'flex',
}, },
toolbar: { toolbar: {
paddingRight: 24, // keep right padding when drawer closed paddingRight: 24, // keep right padding when drawer closed
}, },
toolbarIcon: { toolbarIcon: {
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'flex-end', justifyContent: 'flex-end',
padding: '0 8px', padding: '0 8px',
...theme.mixins.toolbar, ...theme.mixins.toolbar,
}, },
appBarSpacer: theme.mixins.toolbar, appBarSpacer: theme.mixins.toolbar,
drawerPaper: { drawerPaper: {
[theme.breakpoints.up('sm')]: { [theme.breakpoints.up('sm')]: {
width: drawerWidth, width: drawerWidth,
flexShrink: 0, flexShrink: 0,
},
position: 'relative',
whiteSpace: 'nowrap',
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
drawer: {
width: drawerWidth,
}, },
position: 'relative',
whiteSpace: 'nowrap',
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
drawer: {
width: drawerWidth,
},
})); }));
export default function SideBar() { export default function SideBar() {
const classes = useStyles(); const classes = useStyles();
return <Drawer return (
variant="permanent" <Drawer
className={classes.drawer} variant="permanent"
PaperProps={{ className={classes.drawer}
className: classes.drawerPaper, PaperProps={{
}} className: classes.drawerPaper,
anchor={"left"} }}
open={true}> anchor={'left'}
open={true}
>
<div className={classes.appBarSpacer} />
<div className={classes.appBarSpacer}/> <List>
<Link href={'/'}>
<List> <ListItem button>
<Link href={"/"}> <ListItemIcon>
<ListItem button> <DashboardIcon />
<ListItemIcon> </ListItemIcon>
<DashboardIcon/> <ListItemText primary="Dashboard" />
</ListItemIcon> </ListItem>
<ListItemText primary="Dashboard"/> </Link>
</ListItem> </List>
</Link> <Divider />
</List> <List>
<Divider/> <Link href={'/orders'}>
<List> <ListItem button>
<Link href={"/orders"}> <ListItemIcon>
<ListItem button> <ListIcon />
<ListItemIcon> </ListItemIcon>
<ListIcon/> <ListItemText primary="Orders" />
</ListItemIcon> </ListItem>
<ListItemText primary="Orders"/> </Link>
</ListItem> <Link href={'/trades'}>
</Link> <ListItem button>
<Link href={"/trades"}> <ListItemIcon>
<ListItem button> <ListIcon />
<ListItemIcon> </ListItemIcon>
<ListIcon/> <ListItemText primary="Trades" />
</ListItemIcon> </ListItem>
<ListItemText primary="Trades"/> </Link>
</ListItem> <ListItem button>
</Link> <ListItemIcon>
<ListItem button> <TrendingUpIcon />
<ListItemIcon> </ListItemIcon>
<TrendingUpIcon/> <ListItemText primary="Strategies" />
</ListItemIcon> </ListItem>
<ListItemText primary="Strategies"/> </List>
</ListItem>
</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';
@ -9,78 +9,79 @@ import ListItemAvatar from '@material-ui/core/ListItemAvatar';
import Avatar from '@material-ui/core/Avatar'; import Avatar from '@material-ui/core/Avatar';
const useStyles = makeStyles((theme) => ({ 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();
const sortedAssets = []; const sortedAssets = [];
for (let k in assets) { for (let k in assets) {
sortedAssets.push(assets[k]); sortedAssets.push(assets[k]);
}
sortedAssets.sort((a, b) => {
if (a.inUSD > b.inUSD) {
return -1;
} }
sortedAssets.sort((a, b) => {
if (a.inUSD > b.inUSD) {
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) => {
return (
<ListItem key={a.currency} dense>
{
(a.currency in logoCurrencies) ? (
<ListItemAvatar>
<Avatar alt={a.currency} src={`/images/${a.currency.toLowerCase()}-logo.svg`}/>
</ListItemAvatar>
) : (
<ListItemAvatar>
<Avatar alt={a.currency}/>
</ListItemAvatar>
)
}
<ListItemText primary={`${a.currency} ${a.total}`} secondary={`=~ ${Math.round(a.inUSD)} USD`}/>
</ListItem>
)
})
const items = sortedAssets.map((a) => {
return ( return (
<Card className={classes.root} variant="outlined"> <ListItem key={a.currency} dense>
<CardContent className={classes.cardContent}> {a.currency in logoCurrencies ? (
<List dense> <ListItemAvatar>
{items} <Avatar
</List> alt={a.currency}
</CardContent> src={`/images/${a.currency.toLowerCase()}-logo.svg`}
</Card> />
</ListItemAvatar>
) : (
<ListItemAvatar>
<Avatar alt={a.currency} />
</ListItemAvatar>
)}
<ListItemText
primary={`${a.currency} ${a.total}`}
secondary={`=~ ${Math.round(a.inUSD)} USD`}
/>
</ListItem>
); );
});
return (
<Card className={classes.root} variant="outlined">
<CardContent className={classes.cardContent}>
<List dense>{items}</List>
</CardContent>
</Card>
);
} }

View File

@ -1,95 +1,94 @@
import React, {useEffect, useState} from 'react'; 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) => ({
root: { root: {
margin: theme.spacing(1), margin: theme.spacing(1),
}, },
cardContent: { cardContent: {
height: 350, height: 350,
} },
})); }));
export default function TotalAssetsPie({ assets }) { export default function TotalAssetsPie({ assets }) {
const classes = useStyles(); const classes = useStyles();
return ( return (
<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}
padAngle={1.0} padAngle={1.0}
valueFormat=" >-$f" valueFormat=" >-$f"
colors={{datum: 'data.color'}} colors={{ datum: 'data.color' }}
// colors={{scheme: 'nivo'}} // colors={{scheme: 'nivo'}}
cornerRadius={0.1} cornerRadius={0.1}
borderWidth={1} borderWidth={1}
borderColor={{from: 'color', modifiers: [['darker', 0.2]]}} borderColor={{ from: 'color', modifiers: [['darker', 0.2]] }}
radialLabelsSkipAngle={10} radialLabelsSkipAngle={10}
radialLabelsTextColor="#333333" radialLabelsTextColor="#333333"
radialLabelsLinkColor={{from: 'color'}} radialLabelsLinkColor={{ from: 'color' }}
sliceLabelsSkipAngle={30} sliceLabelsSkipAngle={30}
sliceLabelsTextColor="#fff" sliceLabelsTextColor="#fff"
legends={[ legends={[
{ {
anchor: 'right', anchor: 'right',
direction: 'column', direction: 'column',
justify: false, justify: false,
translateX: 70, translateX: 70,
translateY: 0, translateY: 0,
itemsSpacing: 5, itemsSpacing: 5,
itemWidth: 80, itemWidth: 80,
itemHeight: 24, itemHeight: 24,
itemTextColor: '#999', itemTextColor: '#999',
itemOpacity: 1, itemOpacity: 1,
symbolSize: 18, symbolSize: 18,
symbolShape: 'circle', symbolShape: 'circle',
effects: [ effects: [
{ {
on: 'hover', on: 'hover',
style: { style: {
itemTextColor: '#000' itemTextColor: '#000',
} },
} },
] ],
} },
]} ]}
/> />
</CardContent> </CardContent>
</Card> </Card>
); );
} }

View File

@ -1,52 +1,60 @@
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) => ({
root: { root: {
margin: theme.spacing(1), margin: theme.spacing(1),
}, },
title: { title: {
fontSize: 14, fontSize: 14,
}, },
pos: { pos: {
marginTop: 12, marginTop: 12,
}, },
})); }));
export default function TotalAssetSummary({ assets }) { export default function TotalAssetSummary({ assets }) {
const classes = useStyles(); const classes = useStyles();
return <Card className={classes.root} variant="outlined"> return (
<CardContent> <Card className={classes.root} variant="outlined">
<Typography className={classes.title} color="textSecondary" gutterBottom> <CardContent>
Total Account Balance <Typography
</Typography> className={classes.title}
<Typography variant="h5" component="h2"> color="textSecondary"
{Math.round(aggregateAssetsBy(assets, "inBTC") * 1e8) / 1e8} <span>BTC</span> gutterBottom
</Typography> >
Total Account Balance
</Typography>
<Typography variant="h5" component="h2">
{Math.round(aggregateAssetsBy(assets, 'inBTC') * 1e8) / 1e8}{' '}
<span>BTC</span>
</Typography>
<Typography className={classes.pos} color="textSecondary"> <Typography className={classes.pos} color="textSecondary">
Estimated Value Estimated Value
</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}{' '}
</Typography> <span>USD</span>
</CardContent> </Typography>
</CardContent>
</Card> </Card>
);
} }

View File

@ -1,152 +1,161 @@
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
const dateStr = toPeriodDateString(time, period)
const key = v[segment]
keys[key] = true
const k = key ? key : "total"
const quoteVolume = Math.round(v.quoteVolume * 100) / 100
if (dateIndex[dateStr]) {
dateIndex[dateStr][k] = quoteVolume
} else {
dateIndex[dateStr] = {
date: dateStr,
year: time.getFullYear(),
month: time.getMonth() + 1,
day: time.getDate(),
[k]: quoteVolume,
}
}
})
let data = []
while (startTime < endTime) {
const dateStr = toPeriodDateString(startTime, period)
const groupData = dateIndex[dateStr]
if (groupData) {
data.push(groupData)
} else {
data.push({
date: dateStr,
year: startTime.getFullYear(),
month: startTime.getMonth() + 1,
day: startTime.getDate(),
total: 0,
})
}
switch (period) {
case "day":
startTime.setDate(startTime.getDate() + 1)
break
case "month":
startTime.setMonth(startTime.getMonth() + 1)
break
case "year":
startTime.setFullYear(startTime.getFullYear() + 1)
break
}
} }
return [data, Object.keys(keys)] endTime = time;
const dateStr = toPeriodDateString(time, period);
const key = v[segment];
keys[key] = true;
const k = key ? key : 'total';
const quoteVolume = Math.round(v.quoteVolume * 100) / 100;
if (dateIndex[dateStr]) {
dateIndex[dateStr][k] = quoteVolume;
} else {
dateIndex[dateStr] = {
date: dateStr,
year: time.getFullYear(),
month: time.getMonth() + 1,
day: time.getDate(),
[k]: quoteVolume,
};
}
});
let data = [];
while (startTime < endTime) {
const dateStr = toPeriodDateString(startTime, period);
const groupData = dateIndex[dateStr];
if (groupData) {
data.push(groupData);
} else {
data.push({
date: dateStr,
year: startTime.getFullYear(),
month: startTime.getMonth() + 1,
day: startTime.getDate(),
total: 0,
});
}
switch (period) {
case 'day':
startTime.setDate(startTime.getDate() + 1);
break;
case 'month':
startTime.setMonth(startTime.getMonth() + 1);
break;
case 'year':
startTime.setFullYear(startTime.getFullYear() + 1);
break;
}
}
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) {
setPeriod(props.period); setPeriod(props.period);
} }
if (props.segment !== segment) { if (props.segment !== segment) {
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 (
data={data} <ResponsiveBar
indexBy={"date"} keys={keys}
margin={{top: 50, right: 160, bottom: 100, left: 60}} data={data}
padding={0.3} indexBy={'date'}
valueScale={{type: 'linear'}} margin={{ top: 50, right: 160, bottom: 100, left: 60 }}
indexScale={{type: 'band', round: true}} padding={0.3}
labelSkipWidth={30} valueScale={{ type: 'linear' }}
labelSkipHeight={20} indexScale={{ type: 'band', round: true }}
enableGridY={true} labelSkipWidth={30}
colors={{scheme: 'paired'}} labelSkipHeight={20}
axisBottom={{ enableGridY={true}
tickRotation: -90, colors={{ scheme: 'paired' }}
legend: period, axisBottom={{
legendPosition: 'middle', tickRotation: -90,
legendOffset: 80 legend: period,
}} legendPosition: 'middle',
legends={[ legendOffset: 80,
{ }}
dataFrom: 'keys', legends={[
anchor: 'right', {
direction: 'column', dataFrom: 'keys',
justify: false, anchor: 'right',
translateX: 120, direction: 'column',
translateY: 0, justify: false,
itemsSpacing: 2, translateX: 120,
itemWidth: 100, translateY: 0,
itemHeight: 20, itemsSpacing: 2,
itemDirection: 'left-to-right', itemWidth: 100,
itemOpacity: 0.85, itemHeight: 20,
symbolSize: 20, itemDirection: 'left-to-right',
effects: [ itemOpacity: 0.85,
{ symbolSize: 20,
on: 'hover', effects: [
style: { {
itemOpacity: 1 on: 'hover',
} style: {
} itemOpacity: 1,
] },
} },
]} ],
animate={true} },
motionStiffness={90} ]}
motionDamping={15} animate={true}
/>; motionStiffness={90}
motionDamping={15}
/>
);
} }

View File

@ -1,66 +1,72 @@
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: {
height: 400, height: 400,
}, },
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);
}; };
const handleSegmentChange = (event, newValue) => { const handleSegmentChange = (event, newValue) => {
setSegment(newValue); setSegment(newValue);
}; };
return <Paper className={classes.paper}> return (
<Typography variant="h4" gutterBottom> <Paper className={classes.paper}>
Trading Volume <Typography variant="h4" gutterBottom>
</Typography> Trading Volume
</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
onChange={handlePeriodChange} value={period}
indicatorColor="primary" onChange={handlePeriodChange}
textColor="primary"> indicatorColor="primary"
<Tab label="Day" value={"day"}/> textColor="primary"
<Tab label="Month" value={"month"}/> >
<Tab label="Year" value={"year"}/> <Tab label="Day" value={'day'} />
</Tabs> <Tab label="Month" value={'month'} />
</Grid> <Tab label="Year" value={'year'} />
<Grid item xs={12} md={6}> </Tabs>
<Grid container justifyContent={"flex-end"}>
<Tabs value={segment}
onChange={handleSegmentChange}
indicatorColor="primary"
textColor="primary">
<Tab label="By Exchange" value={"exchange"}/>
<Tab label="By Symbol" value={"symbol"}/>
</Tabs>
</Grid>
</Grid>
</Grid> </Grid>
<Grid item xs={12} md={6}>
<Grid container justifyContent={'flex-end'}>
<Tabs
value={segment}
onChange={handleSegmentChange}
indicatorColor="primary"
textColor="primary"
>
<Tab label="By Exchange" value={'exchange'} />
<Tab label="By Symbol" value={'symbol'} />
</Tabs>
</Grid>
</Grid>
</Grid>
<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,58 +1,62 @@
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';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
root: { root: {
flexGrow: 1, flexGrow: 1,
display: 'flex', display: 'flex',
}, },
content: { content: {
flexGrow: 1, flexGrow: 1,
height: '100vh', height: '100vh',
overflow: 'auto', overflow: 'auto',
}, },
appBar: { appBar: {
zIndex: theme.zIndex.drawer + 1, zIndex: theme.zIndex.drawer + 1,
}, },
appBarSpacer: theme.mixins.toolbar, appBarSpacer: theme.mixins.toolbar,
container: { }, container: {},
toolbar:{ toolbar: {
justifyContent: 'space-between', justifyContent: 'space-between',
} },
})); }));
export default function DashboardLayout({children}) { export default function DashboardLayout({ children }) {
const classes = useStyles(); const classes = useStyles();
return ( return (
<div className={classes.root}> <div className={classes.root}>
<AppBar className={classes.appBar}> <AppBar className={classes.appBar}>
<Toolbar className={classes.toolbar}> <Toolbar className={classes.toolbar}>
<Typography variant="h6" className={classes.title}> <Typography variant="h6" className={classes.title}>
BBGO BBGO
</Typography> </Typography>
{/* <Button color="inherit">Login</Button> */} {/* <Button color="inherit">Login</Button> */}
<ConnectWallet /> <ConnectWallet />
</Toolbar> </Toolbar>
</AppBar> </AppBar>
<SideBar/> <SideBar />
<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
{children} className={classes.container}
</Container> maxWidth={false}
</main> disableGutters={true}
</div> >
); {children}
</Container>
</main>
</div>
);
} }

View File

@ -1,43 +1,43 @@
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) => ({
root: { root: {
// flexGrow: 1, // flexGrow: 1,
display: 'flex', display: 'flex',
}, },
content: { content: {
flexGrow: 1, flexGrow: 1,
height: '100vh', height: '100vh',
overflow: 'auto', overflow: 'auto',
}, },
appBar: { appBar: {
zIndex: theme.zIndex.drawer + 1, zIndex: theme.zIndex.drawer + 1,
}, },
appBarSpacer: theme.mixins.toolbar, appBarSpacer: theme.mixins.toolbar,
})); }));
export default function PlainLayout(props) { export default function PlainLayout(props) {
const classes = useStyles(); const classes = useStyles();
return <div className={classes.root}> return (
<AppBar className={classes.appBar}> <div className={classes.root}>
<Toolbar> <AppBar className={classes.appBar}>
<Typography variant="h6" className={classes.title}> <Toolbar>
{ props && props.title ? props.title : "BBGO Setup Wizard" } <Typography variant="h6" className={classes.title}>
</Typography> {props && props.title ? props.title : 'BBGO Setup Wizard'}
</Toolbar> </Typography>
</AppBar> </Toolbar>
</AppBar>
<main className={classes.content}> <main className={classes.content}>
<div className={classes.appBarSpacer}/> <div className={classes.appBarSpacer} />
<Container> <Container>{props.children}</Container>
{props.children} </main>
</Container> </div>
</main> );
</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

@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Head from 'next/head'; import Head from 'next/head';
import {ThemeProvider} from '@material-ui/core/styles'; import { ThemeProvider } from '@material-ui/core/styles';
import Dialog from '@material-ui/core/Dialog'; import Dialog from '@material-ui/core/Dialog';
import DialogContent from '@material-ui/core/DialogContent'; import DialogContent from '@material-ui/core/DialogContent';
@ -13,117 +13,121 @@ 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.
const jssStyles = document.querySelector('#jss-server-side'); const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles) { if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles); jssStyles.parentElement.removeChild(jssStyles);
} }
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;
case SyncDone: case SyncDone:
clearInterval(syncStatusPoller); clearInterval(syncStatusPoller);
setLoading(false); setLoading(false);
setSyncing(false); setSyncing(false);
break; break;
}
}).catch((err) => {
console.error(err)
})
}
syncStatusPoller = setInterval(pollSyncStatus, 1000)
} else {
// no session found, so we can not sync any data
setLoading(false)
setSyncing(false)
} }
}).catch((err) => { }).catch((err) => {
console.error(err) console.error(err);
}) });
};
}, []); syncStatusPoller = setInterval(pollSyncStatus, 1000);
} else {
// no session found, so we can not sync any data
setLoading(false);
setSyncing(false);
}
}).catch((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
</Head> name="viewport"
<ThemeProvider theme={theme}> content="minimum-scale=1, initial-scale=1, width=device-width"
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */} />
<CssBaseline/> </Head>
{ <ThemeProvider theme={theme}>
loading ? (syncing ? ( {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<Dialog <CssBaseline />
open={syncing} {loading ? (
aria-labelledby="alert-dialog-title" syncing ? (
aria-describedby="alert-dialog-description" <Dialog
> open={syncing}
<DialogTitle id="alert-dialog-title">{"Syncing Trades"}</DialogTitle> aria-labelledby="alert-dialog-title"
<DialogContent> aria-describedby="alert-dialog-description"
<DialogContentText id="alert-dialog-description"> >
The environment is syncing trades from the exchange sessions. <DialogTitle id="alert-dialog-title">
Please wait a moment... {'Syncing Trades'}
</DialogContentText> </DialogTitle>
<Box m={2}> <DialogContent>
<LinearProgress/> <DialogContentText id="alert-dialog-description">
</Box> The environment is syncing trades from the exchange sessions.
</DialogContent> Please wait a moment...
</Dialog> </DialogContentText>
) : ( <Box m={2}>
<Dialog <LinearProgress />
open={loading} </Box>
aria-labelledby="alert-dialog-title" </DialogContent>
aria-describedby="alert-dialog-description" </Dialog>
> ) : (
<DialogTitle id="alert-dialog-title">{"Loading"}</DialogTitle> <Dialog
<DialogContent> open={loading}
<DialogContentText id="alert-dialog-description"> aria-labelledby="alert-dialog-title"
Loading... aria-describedby="alert-dialog-description"
</DialogContentText> >
<Box m={2}> <DialogTitle id="alert-dialog-title">{'Loading'}</DialogTitle>
<LinearProgress/> <DialogContent>
</Box> <DialogContentText id="alert-dialog-description">
</DialogContent> Loading...
</Dialog> </DialogContentText>
)) : ( <Box m={2}>
<Component {...pageProps}/> <LinearProgress />
) </Box>
} </DialogContent>
</ThemeProvider> </Dialog>
</React.Fragment> )
); ) : (
<Component {...pageProps} />
)}
</ThemeProvider>
</React.Fragment>
);
} }
MyApp.propTypes = { MyApp.propTypes = {
Component: PropTypes.elementType.isRequired, Component: PropTypes.elementType.isRequired,
pageProps: PropTypes.object.isRequired, pageProps: PropTypes.object.isRequired,
}; };

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

@ -1,57 +1,55 @@
import React, {useEffect, useState} from 'react'; import React, { useEffect, useState } from 'react';
import {makeStyles} from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography'; import Typography from '@material-ui/core/Typography';
import Paper from '@material-ui/core/Paper'; import Paper from '@material-ui/core/Paper';
import PlainLayout from '../../layouts/PlainLayout'; import PlainLayout from '../../layouts/PlainLayout';
import {QRCodeSVG} from 'qrcode.react'; import { QRCodeSVG } from 'qrcode.react';
import {queryOutboundIP} from '../../api/bbgo'; import { queryOutboundIP } from '../../api/bbgo';
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),
}, },
dataGridContainer: { dataGridContainer: {
display: 'flex', display: 'flex',
textAlign: 'center', textAlign: 'center',
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} </div>
style={{flexGrow: 1}} </Paper>
value={connectUrl}/> </PlainLayout>
</div> );
</Paper>
</PlainLayout>
);
} }

View File

@ -1,7 +1,7 @@
import React, {useState} from 'react'; import React, { useState } from 'react';
import {useRouter} from 'next/router'; import { useRouter } from 'next/router';
import {makeStyles} from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography'; import Typography from '@material-ui/core/Typography';
import Box from '@material-ui/core/Box'; import Box from '@material-ui/core/Box';
import Grid from '@material-ui/core/Grid'; import Grid from '@material-ui/core/Grid';
@ -16,100 +16,98 @@ 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),
padding: theme.spacing(2), padding: theme.spacing(2),
}, },
grid: { grid: {
flexGrow: 1, flexGrow: 1,
}, },
control: { control: {
padding: theme.spacing(2), padding: theme.spacing(2),
}, },
})); }));
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) {
return (
<DashboardLayout>
<Box m={4}>
<Typography variant="h4" gutterBottom>
Loading
</Typography>
</Box>
</DashboardLayout>
);
}
console.log("index: assets", assets)
if (sessions.length == 0) {
return ( return (
<DAppProvider config={config}> <DashboardLayout>
<DashboardLayout> <Box m={4}>
<Paper className={classes.totalAssetsSummary}> <Typography variant="h4" gutterBottom>
<Typography variant="h4" gutterBottom> Loading
Total Assets </Typography>
</Typography> </Box>
</DashboardLayout>
<div className={classes.grid}>
<Grid container
direction="row"
justifyContent="space-around"
alignItems="flex-start"
spacing={1}>
<Grid item xs={12} md={8}>
<TotalAssetSummary assets={assets}/>
<TotalAssetsPie assets={assets}/>
</Grid>
<Grid item xs={12} md={4}>
<TotalAssetDetails assets={assets}/>
</Grid>
</Grid>
</div>
</Paper>
<TradingVolumePanel/>
<ExchangeSessionTabPanel/>
</DashboardLayout>
</DAppProvider>
); );
} }
console.log('index: assets', assets);
return (
<DAppProvider config={config}>
<DashboardLayout>
<Paper className={classes.totalAssetsSummary}>
<Typography variant="h4" gutterBottom>
Total Assets
</Typography>
<div className={classes.grid}>
<Grid
container
direction="row"
justifyContent="space-around"
alignItems="flex-start"
spacing={1}
>
<Grid item xs={12} md={8}>
<TotalAssetSummary assets={assets} />
<TotalAssetsPie assets={assets} />
</Grid>
<Grid item xs={12} md={4}>
<TotalAssetDetails assets={assets} />
</Grid>
</Grid>
</div>
</Paper>
<TradingVolumePanel />
<ExchangeSessionTabPanel />
</DashboardLayout>
</DAppProvider>
);
}

View File

@ -1,72 +1,81 @@
import React, {useEffect, useState} from 'react'; import React, { useEffect, useState } from 'react';
import {makeStyles} from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography'; import Typography from '@material-ui/core/Typography';
import Paper from '@material-ui/core/Paper'; import Paper from '@material-ui/core/Paper';
import {queryClosedOrders} from '../api/bbgo'; 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 },
{field: 'exchange', headerName: 'Exchange'}, { field: 'exchange', headerName: 'Exchange' },
{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: 'quantity', headerName: 'Quantity', type: 'number'}, field: 'averagePrice',
{field: 'executedQuantity', headerName: 'Executed Quantity', type: 'number'}, headerName: 'Average Price',
{field: 'status', headerName: 'Status'}, type: 'number',
{field: 'isMargin', headerName: 'Margin'}, width: 120,
{field: 'isIsolated', headerName: 'Isolated'}, },
{field: 'creationTime', headerName: 'Create Time', width: 200}, { field: 'quantity', headerName: 'Quantity', type: 'number' },
{
field: 'executedQuantity',
headerName: 'Executed Quantity',
type: 'number',
},
{ field: 'status', headerName: 'Status' },
{ field: 'isMargin', headerName: 'Margin' },
{ field: 'isIsolated', headerName: 'Isolated' },
{ field: 'creationTime', headerName: 'Create Time', width: 200 },
]; ];
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),
}, },
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(
o.id = o.gid; orders.map((o) => {
return o o.id = o.gid;
})) return o;
}) })
}, []) );
});
}, []);
return ( return (
<DashboardLayout> <DashboardLayout>
<Paper className={classes.paper}> <Paper className={classes.paper}>
<Typography variant="h4" gutterBottom> <Typography variant="h4" gutterBottom>
Orders Orders
</Typography> </Typography>
<div className={classes.dataGridContainer}> <div className={classes.dataGridContainer}>
<div style={{flexGrow: 1}}> <div style={{ flexGrow: 1 }}>
<DataGrid <DataGrid
rows={orders} rows={orders}
columns={columns} columns={columns}
pageSize={50} pageSize={50}
autoPageSize={true} autoPageSize={true}
/> />
</div> </div>
</div> </div>
</Paper> </Paper>
</DashboardLayout> </DashboardLayout>
); );
} }

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import {makeStyles} from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography'; import Typography from '@material-ui/core/Typography';
import Box from '@material-ui/core/Box'; import Box from '@material-ui/core/Box';
import Paper from '@material-ui/core/Paper'; import Paper from '@material-ui/core/Paper';
@ -8,99 +8,125 @@ 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';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
paper: { paper: {
padding: theme.spacing(2), padding: theme.spacing(2),
}, },
})); }));
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={() => {
case 1: setActiveStep(1);
return ( }}
<AddExchangeSessionForm />
onBack={() => { setActiveStep(0) }} );
onAdded={() => { setActiveStep(2) }} case 1:
/> return (
); <AddExchangeSessionForm
case 2: onBack={() => {
return ( setActiveStep(0);
<ReviewSessions }}
onBack={() => { setActiveStep(1) }} onAdded={() => {
onNext={() => { setActiveStep(3) }} setActiveStep(2);
/> }}
); />
case 3: );
return ( case 2:
<ConfigureGridStrategyForm return (
onBack={() => { setActiveStep(2) }} <ReviewSessions
onAdded={() => { setActiveStep(4) }} onBack={() => {
/> setActiveStep(1);
); }}
case 4: onNext={() => {
return ( setActiveStep(3);
<ReviewStrategies }}
onBack={() => { setActiveStep(3) }} />
onNext={() => { setActiveStep(5) }} );
/> case 3:
); return (
<ConfigureGridStrategyForm
onBack={() => {
setActiveStep(2);
}}
onAdded={() => {
setActiveStep(4);
}}
/>
);
case 4:
return (
<ReviewStrategies
onBack={() => {
setActiveStep(3);
}}
onNext={() => {
setActiveStep(5);
}}
/>
);
case 5: case 5:
return ( return (
<SaveConfigAndRestart <SaveConfigAndRestart
onBack={() => { setActiveStep(4) }} onBack={() => {
onRestarted={() => { setActiveStep(4);
}}
onRestarted={() => {}}
/>
);
}} default:
/> throw new Error('Unknown step');
) }
default:
throw new Error('Unknown step');
}
} }
export default function Setup() { export default function Setup() {
const classes = useStyles(); const classes = useStyles();
const [activeStep, setActiveStep] = React.useState(0); const [activeStep, setActiveStep] = React.useState(0);
return ( return (
<PlainLayout> <PlainLayout>
<Box m={4}> <Box m={4}>
<Paper className={classes.paper}> <Paper className={classes.paper}>
<Typography variant="h4" component="h2" gutterBottom> <Typography variant="h4" component="h2" gutterBottom>
Setup Session Setup Session
</Typography> </Typography>
<Stepper activeStep={activeStep} className={classes.stepper}> <Stepper activeStep={activeStep} className={classes.stepper}>
{steps.map((label) => ( {steps.map((label) => (
<Step key={label}> <Step key={label}>
<StepLabel>{label}</StepLabel> <StepLabel>{label}</StepLabel>
</Step> </Step>
))} ))}
</Stepper> </Stepper>
<React.Fragment> <React.Fragment>
{getStepContent(activeStep, setActiveStep)} {getStepContent(activeStep, setActiveStep)}
</React.Fragment> </React.Fragment>
</Paper> </Paper>
</Box> </Box>
</PlainLayout> </PlainLayout>
); );
} }

View File

@ -1,67 +1,68 @@
import React, {useEffect, useState} from 'react'; import React, { useEffect, useState } from 'react';
import {makeStyles} from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography'; import Typography from '@material-ui/core/Typography';
import Paper from '@material-ui/core/Paper'; import Paper from '@material-ui/core/Paper';
import {queryTrades} from '../api/bbgo'; import { queryTrades } 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: 'exchange', headerName: 'Exchange'}, { field: 'exchange', headerName: 'Exchange' },
{field: 'symbol', headerName: 'Symbol'}, { field: 'symbol', headerName: 'Symbol' },
{field: 'side', headerName: 'Side', width: 90}, { field: 'side', headerName: 'Side', width: 90 },
{field: 'price', headerName: 'Price', type: 'number', width: 120}, { field: 'price', headerName: 'Price', type: 'number', width: 120 },
{field: 'quantity', headerName: 'Quantity', type: 'number'}, { field: 'quantity', headerName: 'Quantity', type: 'number' },
{field: 'isMargin', headerName: 'Margin'}, { field: 'isMargin', headerName: 'Margin' },
{field: 'isIsolated', headerName: 'Isolated'}, { field: 'isIsolated', headerName: 'Isolated' },
{field: 'tradedAt', headerName: 'Trade Time', width: 200}, { field: 'tradedAt', headerName: 'Trade Time', width: 200 },
]; ];
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),
}, },
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(
o.id = o.gid; trades.map((o) => {
return o o.id = o.gid;
})) return o;
}) })
}, []) );
});
}, []);
return ( return (
<DashboardLayout> <DashboardLayout>
<Paper className={classes.paper}> <Paper className={classes.paper}>
<Typography variant="h4" gutterBottom> <Typography variant="h4" gutterBottom>
Trades Trades
</Typography> </Typography>
<div className={classes.dataGridContainer}> <div className={classes.dataGridContainer}>
<div style={{ flexGrow: 1 }}> <div style={{ flexGrow: 1 }}>
<DataGrid <DataGrid
rows={trades} rows={trades}
columns={columns} columns={columns}
showToolbar={true} showToolbar={true}
autoPageSize={true} autoPageSize={true}
/> />
</div> </div>
</div> </div>
</Paper> </Paper>
</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"
]
} }