bbgo_origin/apps/frontend/components/ConfigureGridStrategyForm.js

447 lines
12 KiB
JavaScript
Raw Permalink Normal View History

import React from 'react';
import PropTypes from 'prop-types';
2022-06-12 15:11:42 +00:00
import Grid from '@mui/material/Grid';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
2022-06-12 15:19:39 +00:00
import { makeStyles } from '@mui/styles';
2022-06-11 00:57:54 +00:00
import {
attachStrategyOn,
querySessions,
querySessionSymbols,
} from '../api/bbgo';
2022-06-12 15:11:42 +00:00
import TextField from '@mui/material/TextField';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormHelperText from '@mui/material/FormHelperText';
import InputLabel from '@mui/material/InputLabel';
import FormControl from '@mui/material/FormControl';
import Radio from '@mui/material/Radio';
import RadioGroup from '@mui/material/RadioGroup';
import FormLabel from '@mui/material/FormLabel';
import Select from '@mui/material/Select';
import MenuItem from '@mui/material/MenuItem';
2022-06-12 15:08:25 +00:00
import Alert from '@mui/lab/Alert';
2022-06-12 15:11:42 +00:00
import Box from '@mui/material/Box';
import NumberFormat from 'react-number-format';
2021-02-02 18:26:41 +00:00
function parseFloatValid(s) {
2022-06-11 00:57:54 +00:00
if (s) {
const f = parseFloat(s);
if (!isNaN(f)) {
return f;
2021-02-02 18:26:41 +00:00
}
2022-06-11 00:57:54 +00:00
}
2021-02-02 18:26:41 +00:00
2022-06-11 00:57:54 +00:00
return null;
2021-02-02 18:26:41 +00:00
}
function parseFloatCall(s, cb) {
2022-06-11 00:57:54 +00:00
if (s) {
const f = parseFloat(s);
if (!isNaN(f)) {
cb(f);
2021-02-02 18:26:41 +00:00
}
2022-06-11 00:57:54 +00:00
}
2021-02-02 18:26:41 +00:00
}
function StandardNumberFormat(props) {
2022-06-11 00:57:54 +00:00
const { inputRef, onChange, ...other } = props;
return (
<NumberFormat
{...other}
getInputRef={inputRef}
onValueChange={(values) => {
onChange({
target: {
name: props.name,
value: values.value,
},
});
}}
thousandSeparator
isNumericString
/>
);
2021-02-02 18:26:41 +00:00
}
StandardNumberFormat.propTypes = {
2022-06-11 00:57:54 +00:00
inputRef: PropTypes.func.isRequired,
name: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
2021-02-02 18:26:41 +00:00
};
function PriceNumberFormat(props) {
2022-06-11 00:57:54 +00:00
const { inputRef, onChange, ...other } = props;
return (
<NumberFormat
{...other}
getInputRef={inputRef}
onValueChange={(values) => {
onChange({
target: {
name: props.name,
value: values.value,
},
});
}}
thousandSeparator
isNumericString
prefix="$"
/>
);
}
2021-02-02 18:26:41 +00:00
PriceNumberFormat.propTypes = {
2022-06-11 00:57:54 +00:00
inputRef: PropTypes.func.isRequired,
name: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
};
const useStyles = makeStyles((theme) => ({
2022-06-11 00:57:54 +00:00
formControl: {
marginTop: theme.spacing(1),
marginBottom: theme.spacing(1),
minWidth: 120,
},
buttons: {
display: 'flex',
justifyContent: 'flex-end',
marginTop: theme.spacing(2),
paddingTop: theme.spacing(2),
paddingBottom: theme.spacing(2),
'& > *': {
marginLeft: theme.spacing(1),
},
2022-06-11 00:57:54 +00:00
},
}));
2022-06-11 00:57:54 +00:00
export default function ConfigureGridStrategyForm({ onBack, onAdded }) {
const classes = useStyles();
2022-06-11 00:57:54 +00:00
const [errors, setErrors] = React.useState({});
2022-06-11 00:57:54 +00:00
const [sessions, setSessions] = React.useState([]);
2022-06-11 00:57:54 +00:00
const [activeSessionSymbols, setActiveSessionSymbols] = React.useState([]);
2022-06-11 00:57:54 +00:00
const [selectedSessionName, setSelectedSessionName] = React.useState(null);
2022-06-11 00:57:54 +00:00
const [selectedSymbol, setSelectedSymbol] = React.useState('');
2022-06-11 00:57:54 +00:00
const [quantityBy, setQuantityBy] = React.useState('fixedAmount');
2021-02-02 18:26:41 +00:00
2022-06-11 00:57:54 +00:00
const [upperPrice, setUpperPrice] = React.useState(30000.0);
const [lowerPrice, setLowerPrice] = React.useState(10000.0);
2022-06-11 00:57:54 +00:00
const [fixedAmount, setFixedAmount] = React.useState(100.0);
const [fixedQuantity, setFixedQuantity] = React.useState(1.234);
const [gridNumber, setGridNumber] = React.useState(20);
const [profitSpread, setProfitSpread] = React.useState(100.0);
2021-02-02 18:26:41 +00:00
2022-06-11 00:57:54 +00:00
const [response, setResponse] = React.useState({});
2022-06-11 00:57:54 +00:00
React.useEffect(() => {
querySessions((sessions) => {
setSessions(sessions);
});
}, []);
2021-02-02 18:26:41 +00:00
2022-06-11 00:57:54 +00:00
const handleAdd = (event) => {
const payload = {
symbol: selectedSymbol,
gridNumber: parseFloatValid(gridNumber),
profitSpread: parseFloatValid(profitSpread),
upperPrice: parseFloatValid(upperPrice),
lowerPrice: parseFloatValid(lowerPrice),
};
2022-06-11 00:57:54 +00:00
switch (quantityBy) {
case 'fixedQuantity':
payload.quantity = parseFloatValid(fixedQuantity);
break;
case 'fixedAmount':
payload.amount = parseFloatValid(fixedAmount);
break;
}
2022-06-11 00:57:54 +00:00
if (!selectedSessionName) {
setErrors({ session: true });
return;
}
2022-06-11 00:57:54 +00:00
if (!selectedSymbol) {
setErrors({ symbol: true });
return;
}
2022-06-11 00:57:54 +00:00
console.log(payload);
attachStrategyOn(selectedSessionName, 'grid', payload, (response) => {
console.log(response);
setResponse(response);
if (onAdded) {
setTimeout(onAdded, 3000);
}
})
2022-06-11 00:57:54 +00:00
.catch((err) => {
console.error(err);
setResponse(err.response.data);
})
.finally(() => {
setErrors({});
});
};
const handleQuantityBy = (event) => {
setQuantityBy(event.target.value);
};
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 (
2022-06-11 00:57:54 +00:00
<MenuItem key={session.name} value={session.name}>
{session.name}
</MenuItem>
);
});
2022-06-11 00:57:54 +00:00
const symbolMenuItems = activeSessionSymbols.map((symbol, index) => {
return (
<MenuItem key={symbol} value={symbol}>
{symbol}
</MenuItem>
);
2022-06-11 00:57:54 +00:00
});
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>
);
}