mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 14:55:16 +00:00
support exchange session test from the setup wizard
This commit is contained in:
parent
ee3c76d3fb
commit
73762d9888
|
@ -2,6 +2,16 @@ 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) {
|
||||||
|
return axios.post(baseURL + '/api/sessions/test-connection', data, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
}
|
||||||
|
}).then(response => {
|
||||||
|
cb(response.data)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function querySessions(cb) {
|
export function querySessions(cb) {
|
||||||
axios.get(baseURL + '/api/sessions', {})
|
axios.get(baseURL + '/api/sessions', {})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
@ -10,14 +20,14 @@ export function querySessions(cb) {
|
||||||
}
|
}
|
||||||
|
|
||||||
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.get(baseURL + '/api/orders/closed', {params: params})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
cb(response.data.orders)
|
cb(response.data.orders)
|
||||||
});
|
});
|
||||||
|
@ -31,7 +41,7 @@ export function queryAssets(cb) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function queryTradingVolume(params, cb) {
|
export function queryTradingVolume(params, cb) {
|
||||||
axios.get(baseURL + '/api/trading-volume', { params: params })
|
axios.get(baseURL + '/api/trading-volume', {params: params})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
cb(response.data.tradingVolumes)
|
cb(response.data.tradingVolumes)
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Grid from '@material-ui/core/Grid';
|
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 Typography from '@material-ui/core/Typography';
|
||||||
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';
|
||||||
|
import FormHelperText from '@material-ui/core/FormHelperText';
|
||||||
import InputLabel from '@material-ui/core/InputLabel';
|
import InputLabel from '@material-ui/core/InputLabel';
|
||||||
import FormControl from '@material-ui/core/FormControl';
|
import FormControl from '@material-ui/core/FormControl';
|
||||||
|
|
||||||
|
@ -10,6 +13,10 @@ import Checkbox from '@material-ui/core/Checkbox';
|
||||||
import Select from '@material-ui/core/Select';
|
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 {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) => ({
|
||||||
|
@ -18,28 +25,69 @@ const useStyles = makeStyles((theme) => ({
|
||||||
marginBottom: theme.spacing(1),
|
marginBottom: theme.spacing(1),
|
||||||
minWidth: 120,
|
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 ExchangeSessionForm() {
|
export default function ExchangeSessionForm() {
|
||||||
|
|
||||||
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 [sessionName, setSessionName] = React.useState(exchangeType);
|
||||||
|
|
||||||
|
const [testing, setTesting] = React.useState(false);
|
||||||
|
const [testResponse, setTestResponse] = React.useState(null);
|
||||||
|
|
||||||
|
const [apiKey, setApiKey] = React.useState('');
|
||||||
|
const [apiSecret, setApiSecret] = React.useState('');
|
||||||
|
|
||||||
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 = () => {
|
||||||
|
setTestResponse(null)
|
||||||
|
}
|
||||||
|
|
||||||
const handleExchangeTypeChange = (event) => {
|
const handleExchangeTypeChange = (event) => {
|
||||||
setExchangeType(event.target.value);
|
setExchangeType(event.target.value);
|
||||||
|
setSessionName(event.target.value);
|
||||||
|
resetTestResponse()
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleIsMarginChange = (event) => {
|
const createSessionConfig = () => {
|
||||||
setIsMargin(event.target.checked);
|
return {
|
||||||
};
|
name: sessionName,
|
||||||
|
exchange: exchangeType,
|
||||||
|
key: apiKey,
|
||||||
|
secret: apiSecret,
|
||||||
|
margin: isMargin,
|
||||||
|
envVarPrefix: exchangeType.toUpperCase() + "_",
|
||||||
|
isolatedMargin: isIsolatedMargin,
|
||||||
|
isolatedMarginSymbol: isolatedMarginSymbol,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleIsIsolatedMarginChange = (event) => {
|
const handleTestConnection = (event) => {
|
||||||
setIsIsolatedMargin(event.target.checked);
|
const payload = createSessionConfig()
|
||||||
|
setTesting(true)
|
||||||
|
testSessionConnection(payload, (response) => {
|
||||||
|
console.log(response)
|
||||||
|
setTesting(false)
|
||||||
|
setTestResponse(response)
|
||||||
|
}).catch((reason) => {
|
||||||
|
console.error(reason)
|
||||||
|
setTesting(false)
|
||||||
|
setTestResponse(reason)
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -64,71 +112,125 @@ export default function ExchangeSessionForm() {
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid item xs={12} sm={12}>
|
<Grid item xs={12} sm={6}>
|
||||||
<TextField
|
<TextField
|
||||||
required
|
|
||||||
id="name"
|
id="name"
|
||||||
name="name"
|
name="name"
|
||||||
label="Session Name"
|
label="Session Name"
|
||||||
fullWidth
|
fullWidth
|
||||||
autoComplete="given-name"
|
required
|
||||||
|
disabled={!customSessionName}
|
||||||
|
onChange={(event) => {
|
||||||
|
setSessionName(event.target.value)
|
||||||
|
}}
|
||||||
|
value={sessionName}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid item xs={12} sm={6}>
|
<Grid item xs={12} sm={6}>
|
||||||
<TextField id="state" name="state" label="State/Province/Region" fullWidth/>
|
<FormControlLabel
|
||||||
</Grid>
|
control={<Checkbox color="secondary" name="custom_session_name"
|
||||||
<Grid item xs={12} sm={6}>
|
onChange={(event) => {
|
||||||
<TextField
|
setCustomSessionName(event.target.checked);
|
||||||
required
|
}} value="1"/>}
|
||||||
id="zip"
|
label="Custom exchange session name"
|
||||||
name="zip"
|
|
||||||
label="Zip / Postal code"
|
|
||||||
fullWidth
|
|
||||||
autoComplete="shipping postal-code"
|
|
||||||
/>
|
/>
|
||||||
|
<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>
|
||||||
<Grid item xs={12} sm={6}>
|
|
||||||
<TextField
|
<Grid item xs={12}>
|
||||||
required
|
<TextField id="key" name="api_key" label="API Key"
|
||||||
id="country"
|
fullWidth
|
||||||
name="country"
|
required
|
||||||
label="Country"
|
onChange={(event) => {
|
||||||
fullWidth
|
setApiKey(event.target.value)
|
||||||
autoComplete="shipping country"
|
resetTestResponse()
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<FormControlLabel
|
<TextField id="secret" name="api_secret" label="API Secret"
|
||||||
control={<Checkbox color="secondary" name="isMargin" onChange={handleIsMarginChange}
|
fullWidth
|
||||||
value="1"/>}
|
required
|
||||||
label="Use margin trading. This is only available for Binance"
|
onChange={(event) => {
|
||||||
|
setApiSecret(event.target.value)
|
||||||
|
resetTestResponse()
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid item xs={12}>
|
{exchangeType === "binance" ? (
|
||||||
<FormControlLabel
|
|
||||||
control={<Checkbox color="secondary" name="isIsolatedMargin"
|
|
||||||
onChange={handleIsIsolatedMarginChange} value="1"/>}
|
|
||||||
label="Use isolated margin trading, if this is set, you can only trade one symbol with one session. This is only available for Binance"
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
{isIsolatedMargin ?
|
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<TextField
|
<FormControlLabel
|
||||||
required
|
control={<Checkbox color="secondary" name="isMargin" onChange={(event) => {
|
||||||
id="isolatedMarginSymbol"
|
setIsMargin(event.target.checked);
|
||||||
name="isolatedMarginSymbol"
|
resetTestResponse();
|
||||||
label="Isolated Margin Symbol"
|
}} value="1"/>}
|
||||||
fullWidth
|
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>
|
</Grid>
|
||||||
: null}
|
) : null}
|
||||||
|
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<div className={classes.buttons}>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
onClick={handleTestConnection}
|
||||||
|
disabled={testing}>
|
||||||
|
{ testing ? "Testing" : "Test Connection"}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary">
|
||||||
|
Create
|
||||||
|
</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>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
"@material-ui/core": "^4.11.2",
|
"@material-ui/core": "^4.11.2",
|
||||||
"@material-ui/data-grid": "^4.0.0-alpha.18",
|
"@material-ui/data-grid": "^4.0.0-alpha.18",
|
||||||
"@material-ui/icons": "^4.11.2",
|
"@material-ui/icons": "^4.11.2",
|
||||||
|
"@material-ui/lab": "^4.0.0-alpha.57",
|
||||||
"@nivo/bar": "^0.67.0",
|
"@nivo/bar": "^0.67.0",
|
||||||
"@nivo/core": "^0.67.0",
|
"@nivo/core": "^0.67.0",
|
||||||
"@nivo/pie": "^0.67.0",
|
"@nivo/pie": "^0.67.0",
|
||||||
|
|
|
@ -20,7 +20,7 @@ const useStyles = makeStyles((theme) => ({
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const steps = ['Add Exchange Session', 'Review Settings', 'Test Connection'];
|
const steps = ['Add Exchange Session', 'Configure Strategy', 'Restart'];
|
||||||
|
|
||||||
function getStepContent(step) {
|
function getStepContent(step) {
|
||||||
switch (step) {
|
switch (step) {
|
||||||
|
@ -35,7 +35,7 @@ function getStepContent(step) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SetupSession() {
|
export default function Setup() {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [activeStep, setActiveStep] = React.useState(0);
|
const [activeStep, setActiveStep] = React.useState(0);
|
||||||
|
|
|
@ -159,6 +159,17 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.4.4"
|
"@babel/runtime" "^7.4.4"
|
||||||
|
|
||||||
|
"@material-ui/lab@^4.0.0-alpha.57":
|
||||||
|
version "4.0.0-alpha.57"
|
||||||
|
resolved "https://registry.yarnpkg.com/@material-ui/lab/-/lab-4.0.0-alpha.57.tgz#e8961bcf6449e8a8dabe84f2700daacfcafbf83a"
|
||||||
|
integrity sha512-qo/IuIQOmEKtzmRD2E4Aa6DB4A87kmY6h0uYhjUmrrgmEAgbbw9etXpWPVXuRK6AGIQCjFzV6WO2i21m1R4FCw==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.4.4"
|
||||||
|
"@material-ui/utils" "^4.11.2"
|
||||||
|
clsx "^1.0.4"
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
react-is "^16.8.0 || ^17.0.0"
|
||||||
|
|
||||||
"@material-ui/styles@^4.11.2":
|
"@material-ui/styles@^4.11.2":
|
||||||
version "4.11.2"
|
version "4.11.2"
|
||||||
resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.11.2.tgz#e70558be3f41719e8c0d63c7a3c9ae163fdc84cb"
|
resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.11.2.tgz#e70558be3f41719e8c0d63c7a3c9ae163fdc84cb"
|
||||||
|
|
|
@ -53,10 +53,15 @@ type NotificationConfig struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Session struct {
|
type Session struct {
|
||||||
ExchangeName string `json:"exchange" yaml:"exchange"`
|
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||||
EnvVarPrefix string `json:"envVarPrefix" yaml:"envVarPrefix"`
|
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"`
|
PublicOnly bool `json:"publicOnly,omitempty" yaml:"publicOnly"`
|
||||||
Margin bool `json:"margin,omitempty" yaml:"margin"`
|
Margin bool `json:"margin,omitempty" yaml:"margin,omitempty"`
|
||||||
IsolatedMargin bool `json:"isolatedMargin,omitempty" yaml:"isolatedMargin,omitempty"`
|
IsolatedMargin bool `json:"isolatedMargin,omitempty" yaml:"isolatedMargin,omitempty"`
|
||||||
IsolatedMarginSymbol string `json:"isolatedMarginSymbol,omitempty" yaml:"isolatedMarginSymbol,omitempty"`
|
IsolatedMarginSymbol string `json:"isolatedMarginSymbol,omitempty" yaml:"isolatedMarginSymbol,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,36 +142,52 @@ func (environ *Environment) AddExchangesByViperKeys() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewExchangeSessionFromConfig(name string, sessionConfig Session) (*ExchangeSession, error) {
|
||||||
|
exchangeName, err := types.ValidExchangeName(sessionConfig.ExchangeName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var exchange types.Exchange
|
||||||
|
|
||||||
|
if sessionConfig.Key != "" && sessionConfig.Secret != "" {
|
||||||
|
exchange, err = cmdutil.NewExchangeStandard(exchangeName, sessionConfig.Key, sessionConfig.Secret)
|
||||||
|
} else {
|
||||||
|
exchange, err = cmdutil.NewExchangeWithEnvVarPrefix(exchangeName, sessionConfig.EnvVarPrefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// configure exchange
|
||||||
|
if sessionConfig.Margin {
|
||||||
|
marginExchange, ok := exchange.(types.MarginExchange)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("exchange %s does not support margin", exchangeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sessionConfig.IsolatedMargin {
|
||||||
|
marginExchange.UseIsolatedMargin(sessionConfig.IsolatedMarginSymbol)
|
||||||
|
} else {
|
||||||
|
marginExchange.UseMargin()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
session := NewExchangeSession(name, exchange)
|
||||||
|
session.IsMargin = sessionConfig.Margin
|
||||||
|
session.IsIsolatedMargin = sessionConfig.IsolatedMargin
|
||||||
|
session.IsolatedMarginSymbol = sessionConfig.IsolatedMarginSymbol
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (environ *Environment) AddExchangesFromSessionConfig(sessions map[string]Session) error {
|
func (environ *Environment) AddExchangesFromSessionConfig(sessions map[string]Session) error {
|
||||||
for sessionName, sessionConfig := range sessions {
|
for sessionName, sessionConfig := range sessions {
|
||||||
exchangeName, err := types.ValidExchangeName(sessionConfig.ExchangeName)
|
session, err := NewExchangeSessionFromConfig(sessionName, sessionConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
exchange, err := cmdutil.NewExchangeWithEnvVarPrefix(exchangeName, sessionConfig.EnvVarPrefix)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// configure exchange
|
|
||||||
if sessionConfig.Margin {
|
|
||||||
marginExchange, ok := exchange.(types.MarginExchange)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("exchange %s does not support margin", exchangeName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if sessionConfig.IsolatedMargin {
|
|
||||||
marginExchange.UseIsolatedMargin(sessionConfig.IsolatedMarginSymbol)
|
|
||||||
} else {
|
|
||||||
marginExchange.UseMargin()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
session := NewExchangeSession(sessionName, exchange)
|
|
||||||
session.IsMargin = sessionConfig.Margin
|
|
||||||
session.IsIsolatedMargin = sessionConfig.IsolatedMargin
|
|
||||||
session.IsolatedMarginSymbol = sessionConfig.IsolatedMarginSymbol
|
|
||||||
environ.AddExchangeSession(sessionName, session)
|
environ.AddExchangeSession(sessionName, session)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,7 +204,6 @@ func (environ *Environment) Init(ctx context.Context) (err error) {
|
||||||
for n := range environ.sessions {
|
for n := range environ.sessions {
|
||||||
var session = environ.sessions[n]
|
var session = environ.sessions[n]
|
||||||
|
|
||||||
|
|
||||||
if err := session.Init(ctx, environ); err != nil {
|
if err := session.Init(ctx, environ); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,12 +16,14 @@ import (
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RunServer(ctx context.Context, userConfig *Config, environ *Environment) error {
|
func RunServer(ctx context.Context, userConfig *Config, environ *Environment, trader *Trader) error {
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
r.Use(cors.New(cors.Config{
|
r.Use(cors.New(cors.Config{
|
||||||
AllowOrigins: []string{"*"},
|
AllowOrigins: []string{"*"},
|
||||||
AllowHeaders: []string{"Origin"},
|
AllowHeaders: []string{"Origin", "Content-Type"},
|
||||||
ExposeHeaders: []string{"Content-Length"},
|
ExposeHeaders: []string{"Content-Length"},
|
||||||
|
AllowMethods: []string{"GET", "POST"},
|
||||||
|
AllowWebSockets: true,
|
||||||
AllowCredentials: true,
|
AllowCredentials: true,
|
||||||
MaxAge: 12 * time.Hour,
|
MaxAge: 12 * time.Hour,
|
||||||
}))
|
}))
|
||||||
|
@ -134,6 +136,42 @@ func RunServer(ctx context.Context, userConfig *Config, environ *Environment) er
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
|
|
||||||
|
r.POST("/api/sessions/test-connection", func(c *gin.Context) {
|
||||||
|
var sessionConfig Session
|
||||||
|
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 {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var anyErr error
|
||||||
|
_, openOrdersErr := session.Exchange.QueryOpenOrders(ctx, "BTCUSDT")
|
||||||
|
if openOrdersErr != nil {
|
||||||
|
anyErr = openOrdersErr
|
||||||
|
}
|
||||||
|
|
||||||
|
_, balanceErr := session.Exchange.QueryAccountBalances(ctx)
|
||||||
|
if balanceErr != nil {
|
||||||
|
anyErr = balanceErr
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": anyErr == nil,
|
||||||
|
"error": anyErr,
|
||||||
|
"balance": balanceErr == nil,
|
||||||
|
"openOrders": openOrdersErr == nil,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
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() {
|
||||||
|
|
|
@ -11,29 +11,17 @@ import (
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewExchangeWithEnvVarPrefix(n types.ExchangeName, varPrefix string) (types.Exchange, error) {
|
func NewExchangeStandard(n types.ExchangeName, key, secret string) (types.Exchange, error) {
|
||||||
if len(varPrefix) == 0 {
|
if len(key) == 0 || len(secret) == 0 {
|
||||||
varPrefix = n.String()
|
return nil, errors.New("binance: empty key or secret")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch n {
|
switch n {
|
||||||
|
|
||||||
case types.ExchangeBinance:
|
case types.ExchangeBinance:
|
||||||
key := viper.GetString(varPrefix + "-api-key")
|
|
||||||
secret := viper.GetString(varPrefix + "-api-secret")
|
|
||||||
if len(key) == 0 || len(secret) == 0 {
|
|
||||||
return nil, errors.New("binance: empty key or secret")
|
|
||||||
}
|
|
||||||
|
|
||||||
return binance.New(key, secret), nil
|
return binance.New(key, secret), nil
|
||||||
|
|
||||||
case types.ExchangeMax:
|
case types.ExchangeMax:
|
||||||
key := viper.GetString(varPrefix + "-api-key")
|
|
||||||
secret := viper.GetString(varPrefix + "-api-secret")
|
|
||||||
if len(key) == 0 || len(secret) == 0 {
|
|
||||||
return nil, errors.New("max: empty key or secret")
|
|
||||||
}
|
|
||||||
|
|
||||||
return max.New(key, secret), nil
|
return max.New(key, secret), nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -42,6 +30,20 @@ func NewExchangeWithEnvVarPrefix(n types.ExchangeName, varPrefix string) (types.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewExchangeWithEnvVarPrefix(n types.ExchangeName, varPrefix string) (types.Exchange, error) {
|
||||||
|
if len(varPrefix) == 0 {
|
||||||
|
varPrefix = n.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
key := viper.GetString(varPrefix + "-api-key")
|
||||||
|
secret := viper.GetString(varPrefix + "-api-secret")
|
||||||
|
if len(key) == 0 || len(secret) == 0 {
|
||||||
|
return nil, errors.New("max: empty key or secret")
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewExchangeStandard(n, key, secret)
|
||||||
|
}
|
||||||
|
|
||||||
// NewExchange constructor exchange object from viper config.
|
// NewExchange constructor exchange object from viper config.
|
||||||
func NewExchange(n types.ExchangeName) (types.Exchange, error) {
|
func NewExchange(n types.ExchangeName) (types.Exchange, error) {
|
||||||
return NewExchangeWithEnvVarPrefix(n, "")
|
return NewExchangeWithEnvVarPrefix(n, "")
|
||||||
|
|
|
@ -55,6 +55,33 @@ var RunCmd = &cobra.Command{
|
||||||
RunE: run,
|
RunE: run,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runSetup(basectx context.Context, userConfig *bbgo.Config, enableApiServer bool) error {
|
||||||
|
ctx, cancelTrading := context.WithCancel(basectx)
|
||||||
|
defer cancelTrading()
|
||||||
|
|
||||||
|
environ := bbgo.NewEnvironment()
|
||||||
|
|
||||||
|
trader := bbgo.NewTrader(environ)
|
||||||
|
|
||||||
|
if enableApiServer {
|
||||||
|
go func() {
|
||||||
|
if err := bbgo.RunServer(ctx, userConfig, environ, trader); err != nil {
|
||||||
|
log.WithError(err).Errorf("server error")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdutil.WaitForSignal(ctx, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
cancelTrading()
|
||||||
|
|
||||||
|
shutdownCtx, cancelShutdown := context.WithDeadline(ctx, time.Now().Add(30*time.Second))
|
||||||
|
|
||||||
|
log.Infof("shutting down...")
|
||||||
|
trader.Graceful.Shutdown(shutdownCtx)
|
||||||
|
cancelShutdown()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func runConfig(basectx context.Context, userConfig *bbgo.Config, enableApiServer bool) error {
|
func runConfig(basectx context.Context, userConfig *bbgo.Config, enableApiServer bool) error {
|
||||||
ctx, cancelTrading := context.WithCancel(basectx)
|
ctx, cancelTrading := context.WithCancel(basectx)
|
||||||
defer cancelTrading()
|
defer cancelTrading()
|
||||||
|
@ -227,7 +254,7 @@ func runConfig(basectx context.Context, userConfig *bbgo.Config, enableApiServer
|
||||||
|
|
||||||
if enableApiServer {
|
if enableApiServer {
|
||||||
go func() {
|
go func() {
|
||||||
if err := bbgo.RunServer(ctx, userConfig, environ); err != nil {
|
if err := bbgo.RunServer(ctx, userConfig, environ, trader); err != nil {
|
||||||
log.WithError(err).Errorf("server error")
|
log.WithError(err).Errorf("server error")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -262,24 +289,7 @@ func run(cmd *cobra.Command, args []string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
configFile, err := cmd.Flags().GetString("config")
|
setup, err := cmd.Flags().GetBool("setup")
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(configFile) == 0 {
|
|
||||||
return errors.New("--config option is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
noCompile, err := cmd.Flags().GetBool("no-compile")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
userConfig, err := bbgo.Load(configFile, false)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -289,17 +299,56 @@ func run(cmd *cobra.Command, args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// for wrapper binary, we can just run the strategies
|
noCompile, err := cmd.Flags().GetBool("no-compile")
|
||||||
if bbgo.IsWrapperBinary || (userConfig.Build != nil && len(userConfig.Build.Imports) == 0) || noCompile {
|
if err != nil {
|
||||||
userConfig, err = bbgo.Load(configFile, true)
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
configFile, err := cmd.Flags().GetString("config")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var userConfig *bbgo.Config
|
||||||
|
|
||||||
|
if setup {
|
||||||
|
log.Infof("running in setup mode, skip reading config file")
|
||||||
|
enableApiServer = true
|
||||||
|
userConfig = &bbgo.Config{
|
||||||
|
Notifications: nil,
|
||||||
|
Persistence: nil,
|
||||||
|
Sessions: nil,
|
||||||
|
ExchangeStrategies: nil,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if len(configFile) == 0 {
|
||||||
|
return errors.New("--config option is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
userConfig, err = bbgo.Load(configFile, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// for wrapper binary, we can just run the strategies
|
||||||
|
if bbgo.IsWrapperBinary || (userConfig.Build != nil && len(userConfig.Build.Imports) == 0) || noCompile {
|
||||||
if bbgo.IsWrapperBinary {
|
if bbgo.IsWrapperBinary {
|
||||||
log.Infof("running wrapper binary...")
|
log.Infof("running wrapper binary...")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if setup {
|
||||||
|
return runSetup(ctx, userConfig, enableApiServer)
|
||||||
|
}
|
||||||
|
|
||||||
|
userConfig, err = bbgo.Load(configFile, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return runConfig(ctx, userConfig, enableApiServer)
|
return runConfig(ctx, userConfig, enableApiServer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user