mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-25 16:25:16 +00:00
refine setup steps
This commit is contained in:
parent
b33f08e9a0
commit
17d5e301dc
|
@ -2,12 +2,26 @@ 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 testSessionConnection(data, cb) {
|
export function testDatabaseConnection(dsn, cb) {
|
||||||
return axios.post(baseURL + '/api/sessions/test-connection', data, {
|
return axios.post(baseURL + '/api/setup/test-db', {dsn: dsn}).then(response => {
|
||||||
headers: {
|
cb(response.data)
|
||||||
'Content-Type': 'application/json',
|
});
|
||||||
}
|
}
|
||||||
}).then(response => {
|
|
||||||
|
export function configureDatabase(dsn, cb) {
|
||||||
|
return axios.post(baseURL + '/api/setup/configure-db', {dsn: dsn}).then(response => {
|
||||||
|
cb(response.data)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addSession(session, cb) {
|
||||||
|
return axios.post(baseURL + '/api/sessions', session).then(response => {
|
||||||
|
cb(response.data)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function testSessionConnection(session, cb) {
|
||||||
|
return axios.post(baseURL + '/api/sessions/test', session).then(response => {
|
||||||
cb(response.data)
|
cb(response.data)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import MenuItem from '@material-ui/core/MenuItem';
|
||||||
|
|
||||||
import Alert from '@material-ui/lab/Alert';
|
import Alert from '@material-ui/lab/Alert';
|
||||||
|
|
||||||
import {testSessionConnection} from '../api/bbgo';
|
import {addSession, testSessionConnection} from '../api/bbgo';
|
||||||
|
|
||||||
import {makeStyles} from '@material-ui/core/styles';
|
import {makeStyles} from '@material-ui/core/styles';
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ const useStyles = makeStyles((theme) => ({
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default function ExchangeSessionForm() {
|
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);
|
||||||
|
@ -45,6 +45,7 @@ export default function ExchangeSessionForm() {
|
||||||
|
|
||||||
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 [apiKey, setApiKey] = React.useState('');
|
const [apiKey, setApiKey] = React.useState('');
|
||||||
const [apiSecret, setApiSecret] = React.useState('');
|
const [apiSecret, setApiSecret] = React.useState('');
|
||||||
|
@ -76,6 +77,19 @@ export default function ExchangeSessionForm() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleAdd = (event) => {
|
||||||
|
const payload = createSessionConfig()
|
||||||
|
addSession(payload, (response) => {
|
||||||
|
setResponse(response)
|
||||||
|
if (onAdded) {
|
||||||
|
setTimeout(onAdded, 3000)
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
setResponse(error.response)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
const handleTestConnection = (event) => {
|
const handleTestConnection = (event) => {
|
||||||
const payload = createSessionConfig()
|
const payload = createSessionConfig()
|
||||||
setTesting(true)
|
setTesting(true)
|
||||||
|
@ -83,10 +97,10 @@ export default function ExchangeSessionForm() {
|
||||||
console.log(response)
|
console.log(response)
|
||||||
setTesting(false)
|
setTesting(false)
|
||||||
setTestResponse(response)
|
setTestResponse(response)
|
||||||
}).catch((reason) => {
|
}).catch((error) => {
|
||||||
console.error(reason)
|
console.error(error)
|
||||||
setTesting(false)
|
setTesting(false)
|
||||||
setTestResponse(reason)
|
setTestResponse(error.response)
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -174,7 +188,8 @@ export default function ExchangeSessionForm() {
|
||||||
}} value="1"/>}
|
}} value="1"/>}
|
||||||
label="Use margin trading."
|
label="Use margin trading."
|
||||||
/>
|
/>
|
||||||
<FormHelperText id="isMargin-helper-text">This is only available for Binance. Please use the leverage at your own risk.</FormHelperText>
|
<FormHelperText id="isMargin-helper-text">This is only available for Binance. Please use the
|
||||||
|
leverage at your own risk.</FormHelperText>
|
||||||
|
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={<Checkbox color="secondary" name="isIsolatedMargin"
|
control={<Checkbox color="secondary" name="isIsolatedMargin"
|
||||||
|
@ -184,7 +199,8 @@ export default function ExchangeSessionForm() {
|
||||||
}} value="1"/>}
|
}} value="1"/>}
|
||||||
label="Use isolated margin trading."
|
label="Use isolated margin trading."
|
||||||
/>
|
/>
|
||||||
<FormHelperText id="isIsolatedMargin-helper-text">This is only available for Binance. If this is set, you can only trade one symbol with one session.</FormHelperText>
|
<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 ?
|
{isIsolatedMargin ?
|
||||||
<TextField
|
<TextField
|
||||||
|
@ -204,17 +220,28 @@ export default function ExchangeSessionForm() {
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<div className={classes.buttons}>
|
<div className={classes.buttons}>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (onBack) {
|
||||||
|
onBack();
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={handleTestConnection}
|
onClick={handleTestConnection}
|
||||||
disabled={testing}>
|
disabled={testing}>
|
||||||
{ testing ? "Testing" : "Test Connection"}
|
{testing ? "Testing" : "Test Connection"}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary">
|
color="primary"
|
||||||
Create
|
onClick={handleAdd}
|
||||||
|
>
|
||||||
|
Add
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -230,6 +257,18 @@ export default function ExchangeSessionForm() {
|
||||||
) : null : null
|
) : 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>
|
</React.Fragment>
|
||||||
);
|
);
|
154
frontend/components/ConfigureDatabaseForm.js
Normal file
154
frontend/components/ConfigureDatabaseForm.js
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
import React from 'react';
|
||||||
|
import Grid from '@material-ui/core/Grid';
|
||||||
|
import Box from '@material-ui/core/Box';
|
||||||
|
import Button from '@material-ui/core/Button';
|
||||||
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
import TextField from '@material-ui/core/TextField';
|
||||||
|
import FormHelperText from '@material-ui/core/FormHelperText';
|
||||||
|
|
||||||
|
import Alert from '@material-ui/lab/Alert';
|
||||||
|
|
||||||
|
import {testDatabaseConnection, configureDatabase} from '../api/bbgo';
|
||||||
|
|
||||||
|
import {makeStyles} from '@material-ui/core/styles';
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default function ConfigureDatabaseForm({ onConfigured }) {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
const [mysqlURL, setMysqlURL] = React.useState("")
|
||||||
|
const [testing, setTesting] = React.useState(false);
|
||||||
|
const [testResponse, setTestResponse] = React.useState(null);
|
||||||
|
const [configured, setConfigured] = React.useState(false);
|
||||||
|
|
||||||
|
const resetTestResponse = () => {
|
||||||
|
setTestResponse(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleConfigureDatabase = (event) => {
|
||||||
|
configureDatabase(mysqlURL, (response) => {
|
||||||
|
console.log(response);
|
||||||
|
setTesting(false);
|
||||||
|
setTestResponse(response);
|
||||||
|
if (onConfigured) {
|
||||||
|
setConfigured(true);
|
||||||
|
setTimeout(onConfigured, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
}).catch((reason) => {
|
||||||
|
console.error(reason);
|
||||||
|
setTesting(false);
|
||||||
|
setTestResponse(reason);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTestConnection = (event) => {
|
||||||
|
setTesting(true);
|
||||||
|
testDatabaseConnection(mysqlURL, (response) => {
|
||||||
|
console.log(response)
|
||||||
|
setTesting(false)
|
||||||
|
setTestResponse(response)
|
||||||
|
}).catch((reason) => {
|
||||||
|
console.error(reason)
|
||||||
|
setTesting(false)
|
||||||
|
setTestResponse(reason)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
Configure Database
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography variant="body1" gutterBottom>
|
||||||
|
If you have database installed on your machine, you can enter the DSN string in the following field.
|
||||||
|
Please note this is optional, you CAN SKIP this step.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
<Grid item xs={12} sm={6}>
|
||||||
|
<TextField id="mysql_url" name="mysql_url" label="MySQL Data Source Name"
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
onChange={(event) => {
|
||||||
|
setMysqlURL(event.target.value)
|
||||||
|
resetTestResponse()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormHelperText id="session-name-helper-text">
|
||||||
|
If you have database installed on your machine, you can enter the DSN string like the
|
||||||
|
following
|
||||||
|
format:
|
||||||
|
<br/>
|
||||||
|
<code>
|
||||||
|
root:password@tcp(127.0.0.1:3306)/bbgo
|
||||||
|
</code>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
Be sure to create your database before using it. You need to execute the following statement
|
||||||
|
to
|
||||||
|
create a database:
|
||||||
|
<br/>
|
||||||
|
<code>
|
||||||
|
CREATE DATABASE bbgo CHARSET utf8;
|
||||||
|
</code>
|
||||||
|
|
||||||
|
</FormHelperText>
|
||||||
|
</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>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
89
frontend/components/ReviewSessions.js
Normal file
89
frontend/components/ReviewSessions.js
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
import React from 'react';
|
||||||
|
import Grid from '@material-ui/core/Grid';
|
||||||
|
import Button from '@material-ui/core/Button';
|
||||||
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
import List from '@material-ui/core/List';
|
||||||
|
import ListItem from '@material-ui/core/ListItem';
|
||||||
|
import ListItemText from '@material-ui/core/ListItemText';
|
||||||
|
import ListItemIcon from '@material-ui/core/ListItemIcon';
|
||||||
|
import PowerIcon from '@material-ui/icons/Power';
|
||||||
|
|
||||||
|
import {makeStyles} from '@material-ui/core/styles';
|
||||||
|
import {querySessions} from "../api/bbgo";
|
||||||
|
import {Power} from "@material-ui/icons";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default function ReviewSessions({onBack, onNext}) {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
const [sessions, setSessions] = React.useState([]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
querySessions((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>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -8,7 +8,9 @@ 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 ExchangeSessionForm from "../../components/ExchangeSessionForm";
|
import ConfigureDatabaseForm from "../../components/ConfigureDatabaseForm";
|
||||||
|
import AddExchangeSessionForm from "../../components/AddExchangeSessionForm";
|
||||||
|
import ReviewSessions from "../../components/ReviewSessions";
|
||||||
|
|
||||||
import PlainLayout from '../../layouts/PlainLayout';
|
import PlainLayout from '../../layouts/PlainLayout';
|
||||||
|
|
||||||
|
@ -20,14 +22,24 @@ const useStyles = makeStyles((theme) => ({
|
||||||
|
|
||||||
const steps = ['Configure Database', 'Add Exchange Session', 'Configure Strategy', 'Restart BBGO'];
|
const steps = ['Configure Database', 'Add Exchange Session', 'Configure Strategy', 'Restart BBGO'];
|
||||||
|
|
||||||
function getStepContent(step) {
|
function getStepContent(step, setActiveStep) {
|
||||||
switch (step) {
|
switch (step) {
|
||||||
case 0:
|
case 0:
|
||||||
return;
|
return <ConfigureDatabaseForm onConfigured={() => {
|
||||||
|
setActiveStep(1)
|
||||||
|
}}/>;
|
||||||
case 1:
|
case 1:
|
||||||
return <ExchangeSessionForm/>;
|
return <AddExchangeSessionForm onAdded={() => {
|
||||||
|
setActiveStep(2)
|
||||||
|
}} onBack={() => {
|
||||||
|
setActiveStep(0)
|
||||||
|
}}/>;
|
||||||
case 2:
|
case 2:
|
||||||
return;
|
return <ReviewSessions onBack={() => {
|
||||||
|
setActiveStep(1)
|
||||||
|
}} onNext={() => {
|
||||||
|
setActiveStep(3)
|
||||||
|
}}/>
|
||||||
case 3:
|
case 3:
|
||||||
return;
|
return;
|
||||||
default:
|
default:
|
||||||
|
@ -56,7 +68,7 @@ export default function Setup() {
|
||||||
</Stepper>
|
</Stepper>
|
||||||
|
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{getStepContent(activeStep)}
|
{getStepContent(activeStep, setActiveStep)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
@ -164,7 +164,7 @@ type Config struct {
|
||||||
|
|
||||||
Persistence *PersistenceConfig `json:"persistence,omitempty" yaml:"persistence,omitempty"`
|
Persistence *PersistenceConfig `json:"persistence,omitempty" yaml:"persistence,omitempty"`
|
||||||
|
|
||||||
Sessions map[string]Session `json:"sessions,omitempty" yaml:"sessions,omitempty"`
|
Sessions map[string]*ExchangeSession `json:"sessions,omitempty" yaml:"sessions,omitempty"`
|
||||||
|
|
||||||
RiskControls *RiskControls `json:"riskControls,omitempty" yaml:"riskControls,omitempty"`
|
RiskControls *RiskControls `json:"riskControls,omitempty" yaml:"riskControls,omitempty"`
|
||||||
|
|
||||||
|
|
|
@ -75,21 +75,17 @@ func (environ *Environment) Sessions() map[string]*ExchangeSession {
|
||||||
return environ.sessions
|
return environ.sessions
|
||||||
}
|
}
|
||||||
|
|
||||||
func (environ *Environment) ConfigureDatabase(ctx context.Context) error {
|
func (environ *Environment) ConfigureDatabase(ctx context.Context, dsn string) error {
|
||||||
if viper.IsSet("mysql-url") {
|
db, err := ConnectMySQL(dsn)
|
||||||
dsn := viper.GetString("mysql-url")
|
if err != nil {
|
||||||
db, err := ConnectMySQL(dsn)
|
return err
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := upgradeDB(ctx, "mysql", db.DB); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
environ.SetDB(db)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := upgradeDB(ctx, "mysql", db.DB); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
environ.SetDB(db)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,7 +138,7 @@ func (environ *Environment) AddExchangesByViperKeys() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewExchangeSessionFromConfig(name string, sessionConfig Session) (*ExchangeSession, error) {
|
func NewExchangeSessionFromConfig(name string, sessionConfig *ExchangeSession) (*ExchangeSession, error) {
|
||||||
exchangeName, err := types.ValidExchangeName(sessionConfig.ExchangeName)
|
exchangeName, err := types.ValidExchangeName(sessionConfig.ExchangeName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -175,13 +171,13 @@ func NewExchangeSessionFromConfig(name string, sessionConfig Session) (*Exchange
|
||||||
}
|
}
|
||||||
|
|
||||||
session := NewExchangeSession(name, exchange)
|
session := NewExchangeSession(name, exchange)
|
||||||
session.IsMargin = sessionConfig.Margin
|
session.Margin = sessionConfig.Margin
|
||||||
session.IsIsolatedMargin = sessionConfig.IsolatedMargin
|
session.IsolatedMargin = sessionConfig.IsolatedMargin
|
||||||
session.IsolatedMarginSymbol = sessionConfig.IsolatedMarginSymbol
|
session.IsolatedMarginSymbol = sessionConfig.IsolatedMarginSymbol
|
||||||
return session, nil
|
return session, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (environ *Environment) AddExchangesFromSessionConfig(sessions map[string]Session) error {
|
func (environ *Environment) AddExchangesFromSessionConfig(sessions map[string]*ExchangeSession) error {
|
||||||
for sessionName, sessionConfig := range sessions {
|
for sessionName, sessionConfig := range sessions {
|
||||||
session, err := NewExchangeSessionFromConfig(sessionName, sessionConfig)
|
session, err := NewExchangeSessionFromConfig(sessionName, sessionConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -136,8 +136,59 @@ func RunServer(ctx context.Context, userConfig *Config, environ *Environment, tr
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
|
|
||||||
r.POST("/api/sessions/test-connection", func(c *gin.Context) {
|
r.POST("/api/setup/test-db", func(c *gin.Context) {
|
||||||
var sessionConfig Session
|
payload := struct {
|
||||||
|
DSN string `json:"dsn"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
if err := c.BindJSON(&payload); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "missing arguments"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dsn := payload.DSN
|
||||||
|
if len(dsn) == 0 {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "missing dsn argument"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := ConnectMySQL(dsn)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = db.Close()
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"success": true})
|
||||||
|
})
|
||||||
|
|
||||||
|
r.POST("/api/setup/configure-db", func(c *gin.Context) {
|
||||||
|
payload := struct {
|
||||||
|
DSN string `json:"dsn"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
if err := c.BindJSON(&payload); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "missing arguments"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dsn := payload.DSN
|
||||||
|
if len(dsn) == 0 {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "missing dsn argument"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := environ.ConfigureDatabase(ctx, dsn); err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"success": true})
|
||||||
|
})
|
||||||
|
|
||||||
|
r.POST("/api/sessions", func(c *gin.Context) {
|
||||||
|
var sessionConfig ExchangeSession
|
||||||
if err := c.BindJSON(&sessionConfig); err != nil {
|
if err := c.BindJSON(&sessionConfig); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
|
@ -145,7 +196,31 @@ func RunServer(ctx context.Context, userConfig *Config, environ *Environment, tr
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
session, err := NewExchangeSessionFromConfig(sessionConfig.ExchangeName, sessionConfig)
|
session, err := NewExchangeSessionFromConfig(sessionConfig.ExchangeName, &sessionConfig)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
environ.AddExchangeSession(sessionConfig.Name, session)
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
r.POST("/api/sessions/test", func(c *gin.Context) {
|
||||||
|
var sessionConfig ExchangeSession
|
||||||
|
if err := c.BindJSON(&sessionConfig); err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := NewExchangeSessionFromConfig(sessionConfig.ExchangeName, &sessionConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
|
@ -173,7 +248,7 @@ func RunServer(ctx context.Context, userConfig *Config, environ *Environment, tr
|
||||||
})
|
})
|
||||||
|
|
||||||
r.GET("/api/sessions", func(c *gin.Context) {
|
r.GET("/api/sessions", func(c *gin.Context) {
|
||||||
var sessions []*ExchangeSession
|
var sessions = []*ExchangeSession{}
|
||||||
for _, session := range environ.Sessions() {
|
for _, session := range environ.Sessions() {
|
||||||
sessions = append(sessions, session)
|
sessions = append(sessions, session)
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,18 +100,29 @@ type ExchangeSession struct {
|
||||||
// we make it as a value field so that we can configure it separately
|
// we make it as a value field so that we can configure it separately
|
||||||
Notifiability `json:"-"`
|
Notifiability `json:"-"`
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// Session config fields
|
||||||
|
// ---------------------------
|
||||||
|
|
||||||
// Exchange Session name
|
// Exchange Session name
|
||||||
Name string `json:"name"`
|
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||||
|
ExchangeName string `json:"exchange" yaml:"exchange"`
|
||||||
|
EnvVarPrefix string `json:"envVarPrefix" yaml:"envVarPrefix"`
|
||||||
|
Key string `json:"key,omitempty" yaml:"key,omitempty"`
|
||||||
|
Secret string `json:"secret,omitempty" yaml:"secret,omitempty"`
|
||||||
|
|
||||||
|
PublicOnly bool `json:"publicOnly,omitempty" yaml:"publicOnly"`
|
||||||
|
Margin bool `json:"margin,omitempty" yaml:"margin"`
|
||||||
|
IsolatedMargin bool `json:"isolatedMargin,omitempty" yaml:"isolatedMargin,omitempty"`
|
||||||
|
IsolatedMarginSymbol string `json:"isolatedMarginSymbol,omitempty" yaml:"isolatedMarginSymbol,omitempty"`
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// Runtime fields
|
||||||
|
// ---------------------------
|
||||||
|
|
||||||
// The exchange account states
|
// The exchange account states
|
||||||
Account *types.Account `json:"account"`
|
Account *types.Account `json:"account"`
|
||||||
|
|
||||||
IsMargin bool `json:"isMargin"`
|
|
||||||
|
|
||||||
IsIsolatedMargin bool `json:"isIsolatedMargin,omitempty"`
|
|
||||||
|
|
||||||
IsolatedMarginSymbol string `json:"isolatedMarginSymbol,omitempty"`
|
|
||||||
|
|
||||||
IsInitialized bool `json:"isInitialized"`
|
IsInitialized bool `json:"isInitialized"`
|
||||||
|
|
||||||
// Stream is the connection stream of the exchange
|
// Stream is the connection stream of the exchange
|
||||||
|
|
|
@ -116,8 +116,11 @@ var BacktestCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
environ := bbgo.NewEnvironment()
|
environ := bbgo.NewEnvironment()
|
||||||
if err := environ.ConfigureDatabase(ctx); err != nil {
|
if viper.IsSet("mysql-url") {
|
||||||
return err
|
dsn := viper.GetString("mysql-url")
|
||||||
|
if err := environ.ConfigureDatabase(ctx, dsn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
backtestService := &service.BacktestService{DB: db}
|
backtestService := &service.BacktestService{DB: db}
|
||||||
|
|
|
@ -88,8 +88,11 @@ func runConfig(basectx context.Context, userConfig *bbgo.Config, enableApiServer
|
||||||
|
|
||||||
environ := bbgo.NewEnvironment()
|
environ := bbgo.NewEnvironment()
|
||||||
|
|
||||||
if err := environ.ConfigureDatabase(ctx); err != nil {
|
if viper.IsSet("mysql-url") {
|
||||||
return err
|
dsn := viper.GetString("mysql-url")
|
||||||
|
if err := environ.ConfigureDatabase(ctx, dsn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := environ.AddExchangesFromConfig(userConfig); err != nil {
|
if err := environ.AddExchangesFromConfig(userConfig); err != nil {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/bbgo"
|
"github.com/c9s/bbgo/pkg/bbgo"
|
||||||
)
|
)
|
||||||
|
@ -51,8 +52,12 @@ var SyncCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
environ := bbgo.NewEnvironment()
|
environ := bbgo.NewEnvironment()
|
||||||
if err := environ.ConfigureDatabase(ctx); err != nil {
|
|
||||||
return err
|
if viper.IsSet("mysql-url") {
|
||||||
|
dsn := viper.GetString("mysql-url")
|
||||||
|
if err := environ.ConfigureDatabase(ctx, dsn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := environ.AddExchangesFromConfig(userConfig); err != nil {
|
if err := environ.AddExchangesFromConfig(userConfig); err != nil {
|
||||||
|
@ -108,7 +113,7 @@ var SyncCmd = &cobra.Command{
|
||||||
func syncSession(ctx context.Context, environ *bbgo.Environment, session *bbgo.ExchangeSession, symbol string, startTime time.Time) error {
|
func syncSession(ctx context.Context, environ *bbgo.Environment, session *bbgo.ExchangeSession, symbol string, startTime time.Time) error {
|
||||||
log.Infof("starting syncing exchange session %s", session.Name)
|
log.Infof("starting syncing exchange session %s", session.Name)
|
||||||
|
|
||||||
if session.IsIsolatedMargin {
|
if session.IsolatedMargin {
|
||||||
log.Infof("session is configured as isolated margin session, using isolated margin symbol %s instead of %s", session.IsolatedMarginSymbol, symbol)
|
log.Infof("session is configured as isolated margin session, using isolated margin symbol %s instead of %s", session.IsolatedMarginSymbol, symbol)
|
||||||
symbol = session.IsolatedMarginSymbol
|
symbol = session.IsolatedMarginSymbol
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,8 +78,9 @@ case "$command" in
|
||||||
if [[ -n $currency ]] ; then
|
if [[ -n $currency ]] ; then
|
||||||
rewards_params[currency]=$currency
|
rewards_params[currency]=$currency
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# rewards rewards_params | jq -r '.[] | "\(.type)\t\((.amount | tonumber) * 1000 | floor / 1000)\t\(.currency) \(.state) \(.created_at | strflocaltime("%Y-%m-%dT%H:%M:%S %Z"))"'
|
# rewards rewards_params | jq -r '.[] | "\(.type)\t\((.amount | tonumber) * 1000 | floor / 1000)\t\(.currency) \(.state) \(.created_at | strflocaltime("%Y-%m-%dT%H:%M:%S %Z"))"'
|
||||||
rewards rewards_params | jq -r '.[] | [ .type, ((.amount | tonumber) * 10000 | floor / 10000), .currency, .state, (.created_at | strflocaltime("%Y-%m-%dT%H:%M:%S %Z")) ] | @tsv' \
|
rewards rewards_params | jq -r '.[] | [ .uuid, .type, ((.amount | tonumber) * 10000 | floor / 10000), .currency, .state, (.created_at | strflocaltime("%Y-%m-%dT%H:%M:%S %Z")), .note ] | @tsv' \
|
||||||
| column -ts $'\t'
|
| column -ts $'\t'
|
||||||
;;
|
;;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user