Operations Module moved to old repos
This commit is contained in:
1 parent
765659d4a2
commit
9e00cd2468
88 files changed
-84
No files matched your search
@@ -0,0 +1,42 @@
|
||||
import gql from 'graphql-tag'
|
||||
import { clone } from '../Fragments'
|
||||
|
||||
const OPERATIONS_ADD_CLONE = gql`
|
||||
mutation Operations_AddClone($clone: operations_CloneInput!) {
|
||||
operations_AddClone(clone: $clone) {
|
||||
...clone
|
||||
}
|
||||
}
|
||||
${clone}
|
||||
`
|
||||
|
||||
const OPERATIONS_LIST_CLONES = gql`
|
||||
query Operations_Clones{
|
||||
operations_Clones(queryLogs: true) {
|
||||
...clone
|
||||
}
|
||||
}
|
||||
${clone}
|
||||
`
|
||||
|
||||
const OPERATIONS_HISTORY_CLONES = gql`
|
||||
query Operations_HistoryClones{
|
||||
operations_HistoryClones(botType: "Trading") {
|
||||
...clone
|
||||
}
|
||||
}
|
||||
${clone}
|
||||
`
|
||||
|
||||
const OPERATIONS_REMOVE_CLONE = gql`
|
||||
mutation Operations_RemoveClone($id: ID!){
|
||||
operations_RemoveClone( id: $id)
|
||||
}
|
||||
`
|
||||
|
||||
export default {
|
||||
OPERATIONS_LIST_CLONES,
|
||||
OPERATIONS_HISTORY_CLONES,
|
||||
OPERATIONS_ADD_CLONE,
|
||||
OPERATIONS_REMOVE_CLONE
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import clones from './clones'
|
||||
import teams from './teams'
|
||||
import keys from './keys'
|
||||
|
||||
export {
|
||||
clones,
|
||||
teams,
|
||||
keys
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const GET_ALL_KEYS = gql`
|
||||
query {
|
||||
keyVault_AvailableKeys {
|
||||
id
|
||||
key
|
||||
description
|
||||
exchange
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default {
|
||||
GET_ALL_KEYS
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const GET_ALL_TEAMS_QUERY = gql`
|
||||
query {
|
||||
teams_TeamsByOwner {
|
||||
id
|
||||
name
|
||||
slug
|
||||
fb {
|
||||
id
|
||||
name
|
||||
slug
|
||||
kind
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default {
|
||||
GET_ALL_TEAMS_QUERY
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const clone = gql`
|
||||
fragment clone on operations_Clone {
|
||||
id
|
||||
authId
|
||||
teamId
|
||||
botId
|
||||
mode
|
||||
resumeExecution
|
||||
beginDatetime
|
||||
endDatetime
|
||||
waitTime
|
||||
state
|
||||
stateDatetime
|
||||
createDatetime
|
||||
lastLogs
|
||||
summaryDate
|
||||
buyAverage
|
||||
sellAverage
|
||||
marketRate
|
||||
combinedProfitsA
|
||||
combinedProfitsB
|
||||
assetA
|
||||
assetB
|
||||
botType
|
||||
teamName
|
||||
botName
|
||||
botAvatar
|
||||
teamAvatar
|
||||
processName
|
||||
keyId
|
||||
botSlug
|
||||
exchangeName
|
||||
timePeriod
|
||||
balanceAssetB
|
||||
}
|
||||
`
|
||||
@@ -0,0 +1,53 @@
|
||||
const availableTimePeriods = ["24-hs", "12-hs", "08-hs", "06-hs", "04-hs", "03-hs", "02-hs", "01-hs", "45-min", "40-min", "30-min", "20-min", "15-min", "10-min", "05-min", "04-min", "03-min", "02-min", "01-min"]
|
||||
|
||||
const tradingStartModes = {
|
||||
live: 'Live',
|
||||
backtest: 'Backtest'
|
||||
}
|
||||
|
||||
const tradingStartModesList = {
|
||||
live: 'Live',
|
||||
backtest: 'Backtest',
|
||||
competition: 'Competition'
|
||||
}
|
||||
|
||||
const indicatorStartModes = {
|
||||
allMonths: 'All Months',
|
||||
oneMonth: 'One Month',
|
||||
noTime: 'Head of the Market'
|
||||
}
|
||||
|
||||
const sensorProcessNames = {
|
||||
Live: "Live-Trades",
|
||||
HoleFixing: "Hole-Fixing"
|
||||
}
|
||||
|
||||
const indicatorProcessNames = {
|
||||
Daily: 'Multi-Period-Daily',
|
||||
Market: 'Multi-Period-Market'
|
||||
}
|
||||
|
||||
|
||||
const botTypes = {
|
||||
Trading: "Trading",
|
||||
Indicator: "Indicator",
|
||||
Sensor: "Sensor"
|
||||
}
|
||||
|
||||
const exchanges = {
|
||||
// COSS: "COSS",
|
||||
Poloniex: "Poloniex"
|
||||
}
|
||||
|
||||
const markets = {
|
||||
USDT_BTC: "USDT_BTC"
|
||||
}
|
||||
|
||||
const baseAssets = {
|
||||
assetA: "USDT",
|
||||
assetB: "BTC"
|
||||
}
|
||||
|
||||
export {
|
||||
tradingStartModes, tradingStartModesList, exchanges, indicatorStartModes, availableTimePeriods, sensorProcessNames, indicatorProcessNames, botTypes, markets, baseAssets
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,21 @@
|
||||
import React from 'react'
|
||||
import { Route, Switch } from 'react-router-dom'
|
||||
import { MuiPickersUtilsProvider } from 'material-ui-pickers'
|
||||
import LuxonUtils from 'material-ui-pickers/utils/luxon-utils'
|
||||
import BrowseClones from './views/Clone/List'
|
||||
import HistoryClones from './views/Clone/History'
|
||||
import AddClone from './views/Clone/Add'
|
||||
import Home from './views/Home'
|
||||
|
||||
const App = () => (
|
||||
<MuiPickersUtilsProvider utils={LuxonUtils}>
|
||||
<Switch>
|
||||
<Route exact path='/clones/' component={Home} />
|
||||
<Route path='/clones/browse' component={BrowseClones} />
|
||||
<Route path='/clones/history' component={HistoryClones} />
|
||||
<Route path='/clones/add' component={AddClone} />
|
||||
</Switch>
|
||||
</MuiPickersUtilsProvider>
|
||||
)
|
||||
|
||||
export default App
|
||||
@@ -0,0 +1,71 @@
|
||||
import { createMuiTheme } from '@material-ui/core/styles'
|
||||
|
||||
export const theme = createMuiTheme({
|
||||
palette: {
|
||||
primary: { main: '#303036' }, // DARK.
|
||||
secondary: { main: '#CC5835' } // RUSTED_RED.
|
||||
},
|
||||
typography: {
|
||||
fontFamily: '"Saira","Saira Condenses", sans-serif',
|
||||
useNextVariants: true
|
||||
},
|
||||
overrides: {
|
||||
Paper: {
|
||||
root: {
|
||||
backgroundColor: '#ffffff'
|
||||
},
|
||||
elevation: 0,
|
||||
square: true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const globalStyles = theme => ({
|
||||
'@global': {
|
||||
body: {
|
||||
backgroundColor: theme.palette.common.white
|
||||
}
|
||||
},
|
||||
appBar: {
|
||||
position: 'relative'
|
||||
},
|
||||
toolbarTitle: {
|
||||
flex: 1
|
||||
},
|
||||
layout: {
|
||||
width: 'auto',
|
||||
marginLeft: theme.spacing.unit * 3,
|
||||
marginRight: theme.spacing.unit * 3,
|
||||
[theme.breakpoints.up(900 + theme.spacing.unit * 3 * 2)]: {
|
||||
width: 900,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto'
|
||||
}
|
||||
},
|
||||
heroContent: {
|
||||
maxWidth: 600,
|
||||
margin: '0 auto',
|
||||
padding: `${theme.spacing.unit * 8}px 0 ${theme.spacing.unit * 6}px`
|
||||
},
|
||||
cardHeader: {
|
||||
backgroundColor: theme.palette.grey[200]
|
||||
},
|
||||
cardPricing: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'baseline',
|
||||
marginBottom: theme.spacing.unit * 2
|
||||
},
|
||||
cardActions: {
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
paddingBottom: theme.spacing.unit * 2
|
||||
}
|
||||
},
|
||||
footer: {
|
||||
marginTop: theme.spacing.unit * 8,
|
||||
borderTop: `1px solid ${theme.palette.divider}`,
|
||||
padding: `${theme.spacing.unit * 6}px 0`
|
||||
}
|
||||
})
|
||||
|
||||
export default theme
|
||||
@@ -0,0 +1,75 @@
|
||||
export function toLocalTime (epoch) {
|
||||
const localDate = new Date(0)
|
||||
localDate.setUTCSeconds(epoch)
|
||||
const options = { year: 'numeric', month: 'long', day: '2-digit' }
|
||||
return (
|
||||
`${localDate.toLocaleDateString(options)
|
||||
} at ${
|
||||
localDate.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit' })}`
|
||||
)
|
||||
}
|
||||
|
||||
export function toUTCTimestamp (localDate) {
|
||||
if (isDefined(localDate)) {
|
||||
const date = new Date(localDate.valueOf())
|
||||
const utcDate = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(),
|
||||
date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(),
|
||||
date.getUTCSeconds())
|
||||
|
||||
return utcDate.valueOf() / 1000 | 0
|
||||
}
|
||||
}
|
||||
|
||||
export function isDefined (d) {
|
||||
return d !== null && typeof d !== 'undefined'
|
||||
}
|
||||
|
||||
export const isArray = Array.isArray
|
||||
|
||||
export function isObject (d) {
|
||||
return isDefined(d) && typeof d === 'object' && !Array.isArray(d)
|
||||
}
|
||||
|
||||
export function isEmpty (obj) {
|
||||
for (var key in obj) {
|
||||
if (obj.hasOwnProperty(key)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export function validObject (object, ...keys) {
|
||||
return keys.reduce((a, b) => (a || { })[b], object) !== undefined
|
||||
}
|
||||
|
||||
export function mergeDeep (target, ...sources) {
|
||||
if (!sources.length) return target
|
||||
const source = sources.shift()
|
||||
|
||||
if (isObject(target) && isObject(source)) {
|
||||
for (const key in source) {
|
||||
if (isObject(source[key])) {
|
||||
if (!target[key]) Object.assign(target, { [key]: {} })
|
||||
mergeDeep(target[key], source[key])
|
||||
} else {
|
||||
Object.assign(target, { [key]: source[key] })
|
||||
}
|
||||
}
|
||||
}
|
||||
return mergeDeep(target, ...sources)
|
||||
}
|
||||
|
||||
export function slugify (string) {
|
||||
const a = 'àáäâãåèéëêìíïîòóöôùúüûñçßÿœæŕśńṕẃǵǹḿǘẍźḧ·/_,:;'
|
||||
const b = 'aaaaaaeeeeiiiioooouuuuncsyoarsnpwgnmuxzh------'
|
||||
const p = new RegExp(a.split('').join('|'), 'g')
|
||||
|
||||
return string.toString().toLowerCase()
|
||||
.replace(/\s+/g, '-') // Replace spaces with -
|
||||
.replace(p, c => b.charAt(a.indexOf(c))) // Replace special characters
|
||||
.replace(/&/g, '-and-') // Replace & with 'and'
|
||||
.replace(/[^\w-]+/g, '') // Remove all non-word characters
|
||||
.replace(/--+/g, '-') // Replace multiple - with single -
|
||||
.replace(/^-+/, '') // Trim - from start of text
|
||||
.replace(/-+$/, '') // Trim - from end of text
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from 'react'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
|
||||
const TopBar = ({
|
||||
size,
|
||||
backgroundUrl,
|
||||
title,
|
||||
text
|
||||
}) => (
|
||||
<div className={`bannerTopBar ${size}`} style={backgroundUrl ? { backgroundImage: `url(${backgroundUrl})` } : {}}>
|
||||
<div className='caption'>
|
||||
<Typography className='title' variant='h1' align='center'>{title}</Typography>
|
||||
<Typography className='text' variant='h2' align='center'>{text}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default TopBar
|
||||
@@ -0,0 +1,685 @@
|
||||
import React, { Component } from 'react'
|
||||
import { graphql, compose, Query } from 'react-apollo'
|
||||
import { clones, teams, keys } from '../../../GraphQL/Calls'
|
||||
import { DateTime } from 'luxon';
|
||||
import TopBar from '../../BannerTopBar'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import classNames from 'classnames'
|
||||
import styles from './styles'
|
||||
import { isDefined } from '../../../utils'
|
||||
import {
|
||||
tradingStartModes, availableTimePeriods,
|
||||
sensorProcessNames, indicatorProcessNames, botTypes, exchanges, markets, baseAssets
|
||||
} from '../../../GraphQL/models'
|
||||
|
||||
import {
|
||||
MenuItem, Button, TextField, FormControl, InputLabel, Input, Typography,
|
||||
Paper, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions,
|
||||
Select, FormControlLabel, Checkbox, FormHelperText
|
||||
} from '@material-ui/core'
|
||||
import { DateTimePicker } from 'material-ui-pickers'
|
||||
import { ChevronLeft, ChevronRight } from '@material-ui/icons'
|
||||
|
||||
class AddClone extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
let user = localStorage.getItem('user')
|
||||
this.state = {
|
||||
user: JSON.parse(user),
|
||||
selectedBot: { 'id': '' },
|
||||
mode: tradingStartModes.backtest,
|
||||
resumeExecution: false,
|
||||
beginDatetime: DateTime.utc().minus({ days: 8 }).startOf('day'),
|
||||
endDatetime: DateTime.utc(),
|
||||
waitTime: 1,
|
||||
state: '',
|
||||
stateDatetime: 0,
|
||||
createDatetime: 0,
|
||||
teams: [],
|
||||
teamId: '',
|
||||
keyId: '',
|
||||
exchangeName: exchanges.Poloniex,
|
||||
processName: indicatorProcessNames.Daily,
|
||||
accessCodeOption: false,
|
||||
accessCode: '',
|
||||
baseAssetBalance: 0,
|
||||
market: markets.USDT_BTC,
|
||||
baseAsset: baseAssets.assetA,
|
||||
|
||||
// Indicator Bot
|
||||
startYear: 2019,
|
||||
endYear: 2019,
|
||||
month: 1,
|
||||
|
||||
//TradingBot
|
||||
timePeriod: '',
|
||||
|
||||
//Error handlers
|
||||
botError: false,
|
||||
modeError: false,
|
||||
isNewCloneConfirmationOpen: false,
|
||||
serverResponse: '',
|
||||
serverError: false,
|
||||
processNameError: false,
|
||||
keyIdError: false,
|
||||
accessCodeError: false,
|
||||
baseAssetBalanceError: false
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes } = this.props
|
||||
if (!isDefined(this.state.user)) {
|
||||
return (
|
||||
<TopBar
|
||||
size='big'
|
||||
title='Create a bot clone'
|
||||
text="Please login to create a bot clone."
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
)
|
||||
} else return (
|
||||
<React.Fragment>
|
||||
<TopBar
|
||||
size='medium'
|
||||
title='Create a bot clone'
|
||||
text='Create a bot clone here.'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
|
||||
<div className='container'>
|
||||
<Paper className={classNames('container', classes.root)}>
|
||||
|
||||
<form noValidate autoComplete="off" onSubmit={this.submitForm.bind(this)}>
|
||||
<Typography className={classes.typography} variant='h5' gutterBottom>
|
||||
Create a Bot Clone
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.typography} variant='subtitle1' align='justify'>
|
||||
A bot clone it's a copy of your bot running on a virtual machine.
|
||||
Please select from your teams and bots the one you want to clone!
|
||||
</Typography>
|
||||
|
||||
<FormControl className={classNames(classes.form, classes.textField)} fullWidth>
|
||||
<InputLabel shrink htmlFor='select'>
|
||||
Select Team and Bot
|
||||
</InputLabel>
|
||||
<Query query={teams.GET_ALL_TEAMS_QUERY} >
|
||||
{({ loading, error, data }) => {
|
||||
if (error) return `Error! There has been an error loading teams.`
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Select
|
||||
value='loading'
|
||||
input={<Input />}
|
||||
displayEmpty
|
||||
className={classes.selectEmpty}
|
||||
disabled
|
||||
>
|
||||
<MenuItem key='1' value='loading'>Loading...</MenuItem>
|
||||
</Select>
|
||||
)
|
||||
}
|
||||
|
||||
if (!isDefined(data.teams_TeamsByOwner)) {
|
||||
return (
|
||||
<Select
|
||||
value='noteams'
|
||||
input={<Input />}
|
||||
displayEmpty
|
||||
className={classes.selectEmpty}
|
||||
disabled
|
||||
>
|
||||
<MenuItem key='1' value='noteams'>You don't have any teams yet.</MenuItem>
|
||||
</Select>
|
||||
)
|
||||
} else {
|
||||
this.teams = data.teams_TeamsByOwner
|
||||
}
|
||||
|
||||
return (
|
||||
<Select
|
||||
value={this.state.selectedBot.id}
|
||||
displayEmpty
|
||||
className={classes.selectEmpty}
|
||||
onBlur={(e) => this.setState({ botError: false })}
|
||||
error={this.state.botError}
|
||||
onChange={e => this.setSelectedBot(e.target.value)}
|
||||
>
|
||||
{
|
||||
data.teams_TeamsByOwner.map(team => (
|
||||
team.fb.map(financialBeing => (
|
||||
<MenuItem key={financialBeing.id} value={financialBeing.id}>
|
||||
{team.name + '/' + financialBeing.name}
|
||||
</MenuItem>
|
||||
))
|
||||
))
|
||||
}
|
||||
</Select>
|
||||
)
|
||||
}}
|
||||
</Query>
|
||||
<FormHelperText>{this.state.selectedBot.kind}</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
<TextField label="Exchange"
|
||||
select
|
||||
className={classNames(classes.margin, classes.textField, classes.form)}
|
||||
value={this.state.exchangeName}
|
||||
onChange={(e) => this.setState({ exchangeName: e.target.value })}
|
||||
fullWidth
|
||||
>
|
||||
{Object.keys(exchanges).map((option, index) => (
|
||||
<MenuItem key={index} value={option}>
|
||||
{option}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
<TextField label="Simulation Time Period Output to use"
|
||||
select
|
||||
className={classNames(classes.margin, classes.textField, classes.form)}
|
||||
value={this.state.timePeriod}
|
||||
onChange={(e) => this.setState({ timePeriod: e.target.value })}
|
||||
fullWidth
|
||||
>
|
||||
{availableTimePeriods.map((option, index) => (
|
||||
<MenuItem key={index} value={option}>
|
||||
{option}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
{this.state.selectedBot.kind === botTypes.Trading &&
|
||||
<React.Fragment>
|
||||
|
||||
<TextField label="Market"
|
||||
select
|
||||
className={classNames(classes.margin, classes.textField, classes.form)}
|
||||
value={this.state.market}
|
||||
onChange={(e) => this.setState({ market: e.target.value })}
|
||||
fullWidth
|
||||
>
|
||||
{Object.keys(markets).map((option, index) => (
|
||||
<MenuItem key={index} value={option}>
|
||||
{option}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
<TextField label="Base Asset"
|
||||
select
|
||||
className={classNames(classes.margin, classes.textField, classes.form)}
|
||||
value={this.state.baseAsset}
|
||||
onChange={(e) => this.setState({ baseAsset: e.target.value })}
|
||||
fullWidth
|
||||
>
|
||||
{Object.keys(baseAssets).map((option, index) => (
|
||||
<MenuItem key={index} value={option}>
|
||||
{baseAssets[option]}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
<Typography className={classes.typography} variant='subtitle1' align='justify'>
|
||||
Available Running Modes are Backtest and Live.
|
||||
<ul>
|
||||
<li> Backtest mode does NOT put real orders at the exchange;
|
||||
it simulates the order execution at the specified price.
|
||||
</li>
|
||||
<li>
|
||||
Live mode DOES put real orders at the exchange.
|
||||
</li>
|
||||
</ul>
|
||||
</Typography>
|
||||
|
||||
|
||||
<TextField label="Running Mode"
|
||||
select
|
||||
className={classNames(classes.margin, classes.textField, classes.form)}
|
||||
value={this.state.mode}
|
||||
onChange={(e) => this.setState({ mode: e.target.value })}
|
||||
onBlur={(e) => this.setState({ modeError: false })}
|
||||
error={this.state.modeError}
|
||||
fullWidth
|
||||
>
|
||||
{Object.keys(tradingStartModes).map(option => (
|
||||
<MenuItem key={option} value={option}>
|
||||
{tradingStartModes[option]}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
{this.state.mode == "backtest" &&
|
||||
<React.Fragment>
|
||||
<DateTimePicker
|
||||
autoOk
|
||||
disableFuture
|
||||
format="DD' at 'HH:mm"
|
||||
ampm={false}
|
||||
showTabs={false}
|
||||
leftArrowIcon={<ChevronLeft />}
|
||||
rightArrowIcon={<ChevronRight />}
|
||||
value={this.state.beginDatetime}
|
||||
label='Backtest Start Date'
|
||||
onChange={newVal => this.setState({ beginDatetime: newVal })}
|
||||
className={classNames(classes.form, classes.textField)}
|
||||
fullWidth
|
||||
/>
|
||||
|
||||
<DateTimePicker
|
||||
autoOk
|
||||
disableFuture
|
||||
format="DD' at 'HH:mm"
|
||||
ampm={false}
|
||||
showTabs={false}
|
||||
leftArrowIcon={<ChevronLeft />}
|
||||
rightArrowIcon={<ChevronRight />}
|
||||
value={this.state.endDatetime}
|
||||
label='Backtest End Date'
|
||||
onChange={newVal => this.setState({ endDatetime: newVal })}
|
||||
className={classNames(classes.form, classes.textField)}
|
||||
fullWidth
|
||||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
{this.state.mode == "live" &&
|
||||
<React.Fragment>
|
||||
<Typography className={classes.typography} variant='subtitle1' align='justify'>
|
||||
Select one of your available Exchange API Keys to be used by this clone.
|
||||
</Typography>
|
||||
|
||||
<Query
|
||||
query={keys.GET_ALL_KEYS}
|
||||
>
|
||||
{({ loading, error, data }) => {
|
||||
if (loading) return (
|
||||
<TextField
|
||||
select
|
||||
label="Exchange API Key"
|
||||
className={classNames(classes.margin, classes.textField, classes.form)}
|
||||
value={this.state.keyId}
|
||||
onChange={(e) => this.setState({ keyId: e.target.value })}
|
||||
onBlur={(e) => this.setState({ keyIdError: false })}
|
||||
error={this.state.keyIdError}
|
||||
fullWidth
|
||||
>
|
||||
<MenuItem key='1' value='Loading keys...'>
|
||||
'Loading keys...'
|
||||
</MenuItem>
|
||||
</TextField>
|
||||
);
|
||||
if (error) return `Error! ${error.message}`;
|
||||
const list = data.keyVault_AvailableKeys.map((key) => (
|
||||
<MenuItem key={key.id} value={key.id}>
|
||||
{key.key.substr(0, 32) + "..." + " - " + key.exchange}
|
||||
</MenuItem>
|
||||
));
|
||||
return (
|
||||
<TextField
|
||||
select
|
||||
label="Exchange API Key"
|
||||
className={classNames(classes.margin, classes.textField, classes.form)}
|
||||
value={this.state.keyId}
|
||||
onChange={(e) => this.setState({ keyId: e.target.value })}
|
||||
onBlur={(e) => this.setState({ keyIdError: false })}
|
||||
error={this.state.keyIdError}
|
||||
fullWidth
|
||||
>
|
||||
{list}
|
||||
</TextField>
|
||||
);
|
||||
}}
|
||||
</Query>
|
||||
|
||||
<Typography className={classes.typography} variant='subtitle1' align='justify'>
|
||||
Because we are still in early alpha-testing phase,
|
||||
Live Trading Clones are limited to an initial investment
|
||||
of 0.001 BTC or 10 USDT. If you choose to run this bot or any modified
|
||||
version of the code, you are doing it at your own risk.
|
||||
</Typography>
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={this.state.accessCodeOption}
|
||||
onChange={(e) => this.setState({ accessCodeOption: e.target.checked })}
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label="I have an Access Code"
|
||||
className={classNames(classes.form, classes.textField)}
|
||||
/>
|
||||
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
{this.state.accessCodeOption &&
|
||||
<React.Fragment>
|
||||
<Typography className={classes.typography} variant='subtitle1' align='justify'>
|
||||
The Access Code is only used by alpha testers. Please contact the team for more details.
|
||||
</Typography>
|
||||
<TextField
|
||||
label="Access Code"
|
||||
className={classNames(classes.margin, classes.textField, classes.form)}
|
||||
value={this.state.accessCode}
|
||||
onChange={(e) => this.setState({ accessCode: e.target.value })}
|
||||
onBlur={(e) => this.setState({ accessCodeError: false })}
|
||||
error={this.state.accessCodeError}
|
||||
fullWidth
|
||||
/>
|
||||
|
||||
<TextField
|
||||
label="Initial Balance"
|
||||
className={classNames(classes.margin, classes.textField, classes.form)}
|
||||
value={this.state.baseAssetBalance}
|
||||
onChange={(e) => this.setState({ baseAssetBalance: e.target.value })}
|
||||
onBlur={(e) => this.setState({ baseAssetBalanceError: false })}
|
||||
error={this.state.baseAssetBalanceError}
|
||||
fullWidth
|
||||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
{this.state.selectedBot.kind === botTypes.Indicator &&
|
||||
<React.Fragment>
|
||||
<TextField label="Process Name"
|
||||
select
|
||||
className={classNames(classes.textField, classes.form)}
|
||||
value={this.state.processName}
|
||||
onChange={(e) => this.setState({ processName: e.target.value })}
|
||||
onBlur={(e) => this.setState({ processNameError: false })}
|
||||
error={this.state.processNameError}
|
||||
fullWidth
|
||||
>
|
||||
{Object.keys(indicatorProcessNames).map((option, index) => (
|
||||
<MenuItem key={index} value={indicatorProcessNames[option]}>
|
||||
{indicatorProcessNames[option]}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
{this.state.selectedBot.kind === botTypes.Sensor &&
|
||||
<React.Fragment>
|
||||
<TextField label="Process Name"
|
||||
select
|
||||
className={classNames(classes.textField, classes.form)}
|
||||
value={this.state.processName}
|
||||
onChange={(e) => this.setState({ processName: e.target.value })}
|
||||
onBlur={(e) => this.setState({ processNameError: false })}
|
||||
error={this.state.processNameError}
|
||||
fullWidth
|
||||
>
|
||||
{Object.keys(sensorProcessNames).map((option, index) => (
|
||||
<MenuItem key={index} value={sensorProcessNames[option]}>
|
||||
{sensorProcessNames[option]}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
{(this.state.mode === "noTime" && this.state.selectedBot.kind === botTypes.Indicator) &&
|
||||
<React.Fragment>
|
||||
<Typography className={classes.typography} variant='subtitle1' align='justify'>
|
||||
The Resume Execution option let's you pick up the context of
|
||||
the last execution and continue from there.
|
||||
</Typography>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={this.state.resumeExecution}
|
||||
onChange={(e) => this.setState({ resumeExecution: e.target.checked })}
|
||||
value="resumeExecution"
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label="Resume Execution"
|
||||
className={classNames(classes.form, classes.textField)}
|
||||
/>
|
||||
{!this.state.resumeExecution &&
|
||||
<DateTimePicker
|
||||
autoOk
|
||||
disableFuture
|
||||
format="DD' at 'HH:mm"
|
||||
ampm={false}
|
||||
showTabs={false}
|
||||
leftArrowIcon={<ChevronLeft />}
|
||||
rightArrowIcon={<ChevronRight />}
|
||||
value={this.state.beginDatetime}
|
||||
label='Start Date'
|
||||
onChange={newVal => this.setState({ beginDatetime: newVal })}
|
||||
className={classNames(classes.form, classes.textField)}
|
||||
fullWidth
|
||||
/>
|
||||
}
|
||||
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
<Typography className={classes.typography} variant='subtitle1' align='justify'>
|
||||
You will be able to see the clone information under Active Clones.
|
||||
Whenever you decide to stop the clone you can delete it. On History Clones will find all deleted clones.
|
||||
</Typography>
|
||||
|
||||
<div className={classes.actionButton} >
|
||||
<Button
|
||||
type="submit"
|
||||
variant='contained' color='secondary'>
|
||||
Clone Bot!
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<Dialog
|
||||
open={this.state.isNewCloneConfirmationOpen}
|
||||
onClose={this.handleNewCloneConfirmationClose}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Creating bot clone</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
{this.state.serverResponse}
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={this.handleNewCloneConfirmationClose}
|
||||
variant='contained' color='secondary' autoFocus>
|
||||
Ok
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Paper>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
setSelectedBot(botId) {
|
||||
if (isDefined(this.teams)) {
|
||||
for (var j = 0; j < this.teams.length; j++) {
|
||||
for (var i = 0; i < this.teams[j].fb.length; i++) {
|
||||
if (this.teams[j].fb[i].id === botId) {
|
||||
this.setState({ selectedBot: this.teams[j].fb[i] })
|
||||
this.setState({ teamId: this.teams[j].id })
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async submitForm(e) {
|
||||
e.preventDefault()
|
||||
let error = this.validate()
|
||||
if (!error) {
|
||||
let serverResponse = await this.createCloneOnServer()
|
||||
error = serverResponse.errors || !isDefined(serverResponse.data.operations_AddClone)
|
||||
if (error) {
|
||||
this.state.serverResponse = serverResponse.errors[0].message || serverResponse.errors[0]
|
||||
this.state.serverError = true
|
||||
} else {
|
||||
this.state.serverResponse = "The new clone was sucessfully created."
|
||||
this.state.serverError = false
|
||||
}
|
||||
|
||||
this.handleNewCloneConfirmationOpen()
|
||||
}
|
||||
}
|
||||
|
||||
async createCloneOnServer() {
|
||||
let variables = {
|
||||
clone: {
|
||||
teamId: this.state.teamId,
|
||||
botId: this.state.selectedBot.id,
|
||||
mode: this.state.mode,
|
||||
resumeExecution: this.state.resumeExecution,
|
||||
botType: this.state.selectedBot.kind,
|
||||
exchangeName: this.state.exchangeName
|
||||
}
|
||||
}
|
||||
|
||||
if (this.state.selectedBot.kind === "Trading") {
|
||||
variables.clone.processName = 'Trading-Process'
|
||||
variables.clone.timePeriod = this.state.timePeriod
|
||||
variables.clone.baseAsset = this.state.baseAsset
|
||||
variables.clone.market = this.state.market
|
||||
if (this.state.mode === "backtest") {
|
||||
let beginDate = DateTime.fromISO(this.state.beginDatetime.toISO({ includeOffset: false }), { zone: "UTC" });
|
||||
let endDate = DateTime.fromISO(this.state.endDatetime.toISO({ includeOffset: false }), { zone: "UTC" });
|
||||
variables.clone.beginDatetime = beginDate.valueOf() / 1000 | 0
|
||||
variables.clone.endDatetime = endDate.valueOf() / 1000 | 0
|
||||
variables.clone.waitTime = this.state.waitTime
|
||||
} else {
|
||||
variables.clone.keyId = this.state.keyId
|
||||
variables.clone.accessCode = this.state.accessCode
|
||||
variables.clone.baseAssetBalance = Number(this.state.baseAssetBalance)
|
||||
}
|
||||
} else {
|
||||
let beginDate = DateTime.fromISO(this.state.beginDatetime.toISO({ includeOffset: false }), { zone: "UTC" });
|
||||
|
||||
variables.clone.processName = this.state.processName
|
||||
variables.clone.startYear = this.state.startYear
|
||||
variables.clone.endYear = this.state.endYear
|
||||
variables.clone.month = this.state.month
|
||||
variables.clone.beginDatetime = beginDate.valueOf() / 1000 | 0
|
||||
}
|
||||
|
||||
return this.props.addCloneMutation({
|
||||
variables: variables
|
||||
})
|
||||
}
|
||||
|
||||
handleNewCloneConfirmationOpen = () => {
|
||||
this.setState({ isNewCloneConfirmationOpen: true })
|
||||
};
|
||||
|
||||
handleNewCloneConfirmationClose = () => {
|
||||
this.setState({
|
||||
isNewCloneConfirmationOpen: false
|
||||
})
|
||||
|
||||
if (!this.state.serverError)
|
||||
this.setState({
|
||||
selectedBot: { 'id': '' },
|
||||
mode: 'noTime',
|
||||
resumeExecution: false,
|
||||
beginDatetime: DateTime.utc().minus({ days: 8 }).startOf('day'),
|
||||
endDatetime: DateTime.utc(),
|
||||
waitTime: 1,
|
||||
state: '',
|
||||
stateDatetime: 0,
|
||||
createDatetime: 0,
|
||||
exchangeName: exchanges.Poloniex,
|
||||
processName: indicatorProcessNames.Daily,
|
||||
teams: [],
|
||||
teamId: '',
|
||||
keyId: '',
|
||||
accessCodeOption: false,
|
||||
accessCode: '',
|
||||
baseAssetBalance: 0,
|
||||
market: markets.USDT_BTC,
|
||||
baseAsset: baseAssets.assetA,
|
||||
|
||||
// Indicator Bot
|
||||
startYear: 2019,
|
||||
endYear: 2019,
|
||||
month: 1,
|
||||
|
||||
//TradingBot
|
||||
timePeriod: '',
|
||||
|
||||
//Error handlers
|
||||
botError: false,
|
||||
modeError: false,
|
||||
isNewCloneConfirmationOpen: false,
|
||||
serverResponse: '',
|
||||
serverError: false,
|
||||
processNameError: false,
|
||||
keyIdError: false,
|
||||
accessCodeError: false,
|
||||
baseAssetBalanceError: false
|
||||
})
|
||||
};
|
||||
|
||||
validate() {
|
||||
let isError = false
|
||||
|
||||
if (this.state.selectedBot.id.length < 1) {
|
||||
isError = true
|
||||
this.setState(state => ({ botError: true }));
|
||||
}
|
||||
|
||||
if (this.state.mode.length < 1) {
|
||||
isError = true
|
||||
this.setState(state => ({ modeError: true }));
|
||||
}
|
||||
|
||||
if (this.state.selectedBot.kind === "Trading"
|
||||
&& this.state.mode === 'noTime') {
|
||||
isError = true
|
||||
this.setState(state => ({ modeError: true }));
|
||||
}
|
||||
|
||||
if (this.state.selectedBot.kind !== "Trading"
|
||||
&& this.state.processName.length < 1) {
|
||||
isError = true
|
||||
this.setState(state => ({ processNameError: true }));
|
||||
}
|
||||
|
||||
if (this.state.mode === 'live' && this.state.keyId.length < 1) {
|
||||
isError = true
|
||||
this.setState(state => ({ keyIdError: true }));
|
||||
}
|
||||
|
||||
if (this.state.accessCodeOption) {
|
||||
if (this.state.accessCode.length < 1) {
|
||||
isError = true
|
||||
this.setState(state => ({ accessCodeError: true }));
|
||||
}
|
||||
if (this.state.baseAssetBalance.length < 1 || isNaN(this.state.baseAssetBalance)) {
|
||||
isError = true
|
||||
this.setState(state => ({ baseAssetBalanceError: true }));
|
||||
}
|
||||
}
|
||||
|
||||
return isError;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default compose(
|
||||
graphql(clones.OPERATIONS_ADD_CLONE, { name: 'addCloneMutation' }),
|
||||
withStyles(styles)
|
||||
)(AddClone)
|
||||
@@ -0,0 +1,37 @@
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
width: '100%',
|
||||
flexGrow: 1,
|
||||
padding: 10,
|
||||
marginTop: '5%',
|
||||
marginBottom: '10%'
|
||||
},
|
||||
container: {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
padding: 20,
|
||||
height: '100%'
|
||||
},
|
||||
textField: {
|
||||
width: '80%',
|
||||
marginLeft: '10%',
|
||||
marginBottom: 10
|
||||
},
|
||||
actionButton: {
|
||||
textAlign: 'center',
|
||||
marginTop: 10
|
||||
},
|
||||
typography: {
|
||||
width: '80%',
|
||||
marginLeft: '10%',
|
||||
marginTop: 20
|
||||
},
|
||||
form: {
|
||||
marginTop: 20
|
||||
},
|
||||
selectEmpty: {
|
||||
marginTop: theme.spacing.unit * 2
|
||||
}
|
||||
})
|
||||
|
||||
export default styles
|
||||
@@ -0,0 +1,127 @@
|
||||
import React, { Component } from 'react'
|
||||
import { graphql } from 'react-apollo'
|
||||
import { clones } from '../../../GraphQL/Calls'
|
||||
import TopBar from '../../BannerTopBar'
|
||||
import ListClones from '../List/ListClones'
|
||||
|
||||
class HistoryClones extends Component {
|
||||
|
||||
constructor (props) {
|
||||
super(props)
|
||||
let user = localStorage.getItem('user')
|
||||
this.state = {
|
||||
user: JSON.parse(user)
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
let data = this.props.data
|
||||
|
||||
if (data.loading) {
|
||||
return (
|
||||
<TopBar
|
||||
size='big'
|
||||
title='Bot Clone History'
|
||||
text='Loading your Bot Clones...'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
)
|
||||
} else if (data.operations_HistoryClones && data.operations_HistoryClones.length > 0) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<TopBar
|
||||
size='medium'
|
||||
title='Bot Clone History'
|
||||
text='All your Bot Clones are here.'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
|
||||
<div className='container'>
|
||||
{
|
||||
data.operations_HistoryClones.map((clone, i) => {
|
||||
return (
|
||||
<ListClones key={clone.id} currentClone={clone} isHistory />
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)
|
||||
} else if (data.error) {
|
||||
console.log('Error Listing clones.', data.error)
|
||||
return (
|
||||
<TopBar
|
||||
size='big'
|
||||
title='Bot Clone History'
|
||||
text='There has been an error listing the clones.'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<TopBar
|
||||
size='big'
|
||||
title='Bot Clone History'
|
||||
text="You don't have any Clone yet. Once you create one you will find it here."
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
getBodyContent (data) {
|
||||
if (data.loading) {
|
||||
return (
|
||||
<TopBar
|
||||
size='big'
|
||||
title='Manage your Bot Clones'
|
||||
text='Loading Bot Clones...'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
)
|
||||
} else if (data.operations_HistoryClones && data.operations_HistoryClones.length > 0) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<TopBar
|
||||
size='big'
|
||||
title='Manage your Bot Clones'
|
||||
text='Loading Bot Clones...'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
|
||||
<div className='container'>
|
||||
{
|
||||
data.operations_HistoryClones.map((clone, i) => {
|
||||
return (
|
||||
<ListClones key={clone.id} currentClone={clone} />
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)
|
||||
} else if (data.error) {
|
||||
return (
|
||||
<TopBar
|
||||
size='big'
|
||||
title='No Bot Clones to display'
|
||||
text='Please Login to access your Bot Clones.'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
|
||||
<TopBar
|
||||
size='big'
|
||||
title='No Bot Clones to display'
|
||||
text="You don't have any Bot Clone yet. After you create a new Clone, it will be listed here."
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default graphql(clones.OPERATIONS_HISTORY_CLONES)(HistoryClones)
|
||||
@@ -0,0 +1,56 @@
|
||||
import React, { Component } from 'react'
|
||||
import AddClone from './AddClone'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import styles from './styles'
|
||||
import {
|
||||
Button, Dialog, DialogContent, DialogContentText, DialogTitle
|
||||
} from '@material-ui/core'
|
||||
|
||||
class CloneDialog extends Component {
|
||||
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
selected:'',
|
||||
isNewCloneDialogOpen: false,
|
||||
}
|
||||
}
|
||||
|
||||
handleNewCloneDialogOpen = () => {
|
||||
this.setState({ isNewCloneDialogOpen: true })
|
||||
};
|
||||
|
||||
handleNewCloneDialogClose = () => {
|
||||
this.setState({ isNewCloneDialogOpen: false })
|
||||
};
|
||||
|
||||
render () {
|
||||
const { classes } = this.props
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Button variant="contained" color="secondary"
|
||||
aria-label="addNewClone" className={classes.button}
|
||||
onClick={this.handleNewCloneDialogOpen} >
|
||||
Add a new Clone
|
||||
</Button>
|
||||
<Dialog
|
||||
open={this.state.isNewCloneDialogOpen}
|
||||
onClose={this.handleNewCloneDialogClose}
|
||||
aria-labelledby="addClone-dialog-title"
|
||||
>
|
||||
<DialogTitle id="addClone-dialog-title">
|
||||
Add a new Clone
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
Description...
|
||||
</DialogContentText>
|
||||
<AddClone handleNewCloneDialogClose={this.handleNewCloneDialogClose}/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default withStyles(styles)(CloneDialog)
|
||||
@@ -0,0 +1,306 @@
|
||||
import React, { Component } from 'react'
|
||||
import { graphql, compose } from 'react-apollo'
|
||||
import {withRouter} from "react-router-dom";
|
||||
import { clones } from '../../../GraphQL/Calls'
|
||||
import {
|
||||
Typography, Button, TextField, Dialog, DialogContent,
|
||||
DialogContentText, DialogTitle, DialogActions,
|
||||
ExpansionPanel, ExpansionPanelDetails, ExpansionPanelSummary,
|
||||
ExpansionPanelActions, Divider
|
||||
|
||||
} from '@material-ui/core'
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
import { tradingStartModesList, indicatorStartModes } from '../../../GraphQL/models'
|
||||
import { isDefined, toLocalTime } from '../../../utils'
|
||||
import classNames from 'classnames'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import styles from './styles'
|
||||
|
||||
class ListClones extends Component {
|
||||
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
isRemoveDialogOpen: false,
|
||||
isLogsDialogOpen: false,
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { classes } = this.props
|
||||
const clone = this.props.currentClone
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ExpansionPanel className={classes.root}>
|
||||
<ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<div className={classes.heading}>
|
||||
<Avatar src={clone.teamAvatar} className={classes.avatar} />
|
||||
<Typography className={classes.heading}>{clone.teamName}</Typography>
|
||||
</div>
|
||||
<div className={classes.heading}>
|
||||
<Avatar src={clone.botAvatar} className={classes.avatar} />
|
||||
<Typography className={classes.heading}>{clone.botName}</Typography>
|
||||
</div>
|
||||
<div className={classes.heading}>
|
||||
<Typography className={classes.heading}>
|
||||
{ clone.processName } / { clone.botType === 'Trading' ? tradingStartModesList[clone.mode] : indicatorStartModes[clone.mode] }
|
||||
</Typography>
|
||||
</div>
|
||||
</ExpansionPanelSummary>
|
||||
<Divider />
|
||||
<ExpansionPanelDetails className={classes.details}>
|
||||
<div className={classes.column3}>
|
||||
<Typography className={classes.cloneInfoTitle}>Context Information</Typography>
|
||||
<div className={classes.details}>
|
||||
<div className={classes.column4}>
|
||||
<Typography className={classes.cloneInfoBold}>Created:</Typography>
|
||||
<Typography className={classes.cloneInfoBold}>Resume Execution:</Typography>
|
||||
{ isDefined(clone.beginDatetime) &&
|
||||
<Typography className={classes.cloneInfoBold}>Begin:</Typography>
|
||||
}
|
||||
{ isDefined(clone.endDatetime) &&
|
||||
<Typography className={classes.cloneInfoBold}>End:</Typography>
|
||||
}
|
||||
{ isDefined(clone.waitTime) &&
|
||||
<Typography className={classes.cloneInfoBold}>Wait Time:</Typography>
|
||||
}
|
||||
{ clone.botType === 'Trading' &&
|
||||
<React.Fragment>
|
||||
<Typography className={classes.cloneInfoBold}>Exchange:</Typography>
|
||||
<Typography className={classes.cloneInfoBold}>Time Period:</Typography>
|
||||
<Typography className={classes.cloneInfoBold}>Initial Balance (BTC):</Typography>
|
||||
</React.Fragment>
|
||||
}
|
||||
</div>
|
||||
<div className={classes.column4}>
|
||||
<Typography className={classes.cloneInfoNormal}>{toLocalTime(clone.createDatetime)}</Typography>
|
||||
<Typography className={classes.cloneInfoNormal}>{clone.resumeExecution ? 'Yes':'No'}</Typography>
|
||||
{ isDefined(clone.beginDatetime) &&
|
||||
<Typography className={classes.cloneInfoNormal}>{toLocalTime(clone.beginDatetime)}</Typography>
|
||||
}
|
||||
{ isDefined(clone.endDatetime) &&
|
||||
<Typography className={classes.cloneInfoNormal}>{toLocalTime(clone.endDatetime)}</Typography>
|
||||
}
|
||||
{ isDefined(clone.waitTime) &&
|
||||
<Typography className={classes.cloneInfoNormal}>{clone.waitTime}</Typography>
|
||||
}
|
||||
{ clone.botType === 'Trading' &&
|
||||
<React.Fragment>
|
||||
<Typography className={classes.cloneInfoNormal}>{clone.exchangeName}</Typography>
|
||||
<Typography className={classes.cloneInfoNormal}>{clone.timePeriod}</Typography>
|
||||
<Typography className={classes.cloneInfoNormal}>{clone.balanceAssetB}</Typography>
|
||||
</React.Fragment>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{ isDefined(clone.summaryDate) &&
|
||||
<React.Fragment>
|
||||
<div className={classNames(classes.column3, classes.helper)}>
|
||||
<Typography className={classes.cloneInfoTitle}>Execution Information</Typography>
|
||||
<div className={classes.details}>
|
||||
<div className={classes.column4}>
|
||||
<Typography className={classes.cloneInfoBold}>Last Execution:</Typography>
|
||||
{ (clone.botType === 'Trading' && isDefined(clone.assetA) ) &&
|
||||
<React.Fragment>
|
||||
<Typography className={classes.cloneInfoBold}>Buy Average:</Typography>
|
||||
<Typography className={classes.cloneInfoBold}>Sell Average:</Typography>
|
||||
<Typography className={classes.cloneInfoBold}>Market Rate:</Typography>
|
||||
<Typography className={classes.cloneInfoBold}>ROI {clone.assetA}:</Typography>
|
||||
<Typography className={classes.cloneInfoBold}>ROI {clone.assetB}:</Typography>
|
||||
</React.Fragment>
|
||||
}
|
||||
</div>
|
||||
<div className={classes.column4}>
|
||||
<Typography className={classes.cloneInfoNormal}>{ toLocalTime(clone.summaryDate) }</Typography>
|
||||
{ (clone.botType === 'Trading' && isDefined(clone.assetA) ) &&
|
||||
<React.Fragment>
|
||||
<Typography className={classes.cloneInfoNormal}>{clone.buyAverage}</Typography>
|
||||
<Typography className={classes.cloneInfoNormal}>{clone.sellAverage}</Typography>
|
||||
<Typography className={classes.cloneInfoNormal}>{clone.marketRate}</Typography>
|
||||
<Typography className={classes.cloneInfoNormal}>{clone.combinedProfitsA}%</Typography>
|
||||
<Typography className={classes.cloneInfoNormal}>{clone.combinedProfitsB}%</Typography>
|
||||
</React.Fragment>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
}
|
||||
{ (clone.state !== 'UNPUBLISHED') &&
|
||||
<React.Fragment>
|
||||
<div className={classNames(classes.column3, classes.helper)}>
|
||||
<Typography className={classes.cloneInfoTitle}>Virtual Machine Status</Typography>
|
||||
<TextField
|
||||
id="outlined-state"
|
||||
defaultValue={clone.state}
|
||||
margin="normal"
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
}}
|
||||
fullWidth
|
||||
multiline
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
}
|
||||
</ExpansionPanelDetails>
|
||||
{
|
||||
!this.props.isHistory &&
|
||||
<Divider />
|
||||
}
|
||||
{
|
||||
!this.props.isHistory &&
|
||||
<ExpansionPanelActions>
|
||||
<Button
|
||||
className={classes.buttonList}
|
||||
variant='contained' color='secondary' size='small'
|
||||
onClick={() => this.setState({ isLogsDialogOpen: true })}>
|
||||
View Process Output
|
||||
</Button>
|
||||
<Button
|
||||
className={classes.buttonList}
|
||||
variant='contained' color='secondary' size='small'
|
||||
onClick={() => this.showCloneLogs(clone)}>
|
||||
View Logs
|
||||
</Button>
|
||||
<Button
|
||||
className={classes.buttonList}
|
||||
variant='contained' color='secondary' size='small'
|
||||
onClick={this.handleRemoveDialogOpen}>
|
||||
Delete
|
||||
</Button>
|
||||
</ExpansionPanelActions>
|
||||
}
|
||||
</ExpansionPanel>
|
||||
|
||||
<Dialog
|
||||
open={this.state.isRemoveDialogOpen}
|
||||
onClose={this.handleRemoveDialogClose}
|
||||
aria-labelledby="removeClone-dialog-title"
|
||||
>
|
||||
<DialogTitle id="removeClone-dialog-title">
|
||||
Delete Clone
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
Are you sure you want to delete this clone?
|
||||
Deleting this clone will prevent any running bots associated to this clone to access your account at the exchange.
|
||||
</DialogContentText>
|
||||
|
||||
<DialogActions>
|
||||
<Button onClick={this.handleRemoveDialogOK} className={classes.buttonList}
|
||||
variant='contained' color='secondary' size='small'
|
||||
>
|
||||
Proceed
|
||||
</Button>
|
||||
<Button onClick={this.handleRemoveDialogCancel}
|
||||
className={classes.buttonList}
|
||||
variant='contained' color='secondary' size='small'
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog
|
||||
open={this.state.isLogsDialogOpen}
|
||||
onClose={this.handleLogsDialogClose}
|
||||
aria-labelledby="logs-dialog-title"
|
||||
fullScreen
|
||||
>
|
||||
<DialogTitle id="logs-dialog-title">
|
||||
Last clone logs
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography className={classes.typography} variant='subtitle1' align='justify'>
|
||||
Here you can check the clone output directly from the virtual machine.
|
||||
The last 20 lines are displayed, but you can check the Logs Module for more details.
|
||||
</Typography>
|
||||
<div className={classes.logsWrapper}>
|
||||
<TextField
|
||||
id="outlined-logs"
|
||||
defaultValue={this.props.currentClone.lastLogs}
|
||||
className={classes.textArea}
|
||||
margin="normal"
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
}}
|
||||
fullWidth
|
||||
multiline
|
||||
/>
|
||||
</div>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={this.handleLogsDialogClose} className={classes.buttonList}
|
||||
variant='contained' color='secondary' size='small' autoFocus
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
showCloneLogs(clone) {
|
||||
this.props.history.push("/logs/show/"+clone.botSlug+"-"+clone.id+".1.0");
|
||||
}
|
||||
|
||||
canStopClone(){
|
||||
const state = JSON.parse(this.props.currentClone.state)
|
||||
if(isDefined(state) && state.hasOwnProperty("running")){
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
canRestartClone(){
|
||||
const state = JSON.parse(this.props.currentClone.state)
|
||||
if(isDefined(state) && state.hasOwnProperty("terminated")){
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
removeClone (clone) {
|
||||
this.props.mutate({
|
||||
variables:{
|
||||
id: clone.id
|
||||
},
|
||||
refetchQueries: [{query: clones.OPERATIONS_LIST_CLONES}]
|
||||
})
|
||||
}
|
||||
|
||||
showLogs () {
|
||||
this.setState({ isLogsDialogOpen: true })
|
||||
}
|
||||
|
||||
handleLogsDialogClose = () => {
|
||||
this.setState({ isLogsDialogOpen: false })
|
||||
}
|
||||
|
||||
handleRemoveDialogOpen = () => {
|
||||
this.setState({ isRemoveDialogOpen: true })
|
||||
}
|
||||
|
||||
handleRemoveDialogOK = () => {
|
||||
this.removeClone(this.props.currentClone)
|
||||
this.setState({ isRemoveDialogOpen: false })
|
||||
}
|
||||
|
||||
handleRemoveDialogCancel = () => {
|
||||
this.setState({ isRemoveDialogOpen: false })
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
graphql(clones.OPERATIONS_REMOVE_CLONE),
|
||||
withStyles(styles),
|
||||
withRouter
|
||||
)(ListClones)
|
||||
@@ -0,0 +1,127 @@
|
||||
import React, { Component } from 'react'
|
||||
import { graphql } from 'react-apollo'
|
||||
import { clones } from '../../../GraphQL/Calls'
|
||||
import TopBar from '../../BannerTopBar'
|
||||
import ListClones from './ListClones'
|
||||
|
||||
class BrowseClones extends Component {
|
||||
|
||||
constructor (props) {
|
||||
super(props)
|
||||
let user = localStorage.getItem('user')
|
||||
this.state = {
|
||||
user: JSON.parse(user)
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
let data = this.props.data
|
||||
|
||||
if (data.loading) {
|
||||
return (
|
||||
<TopBar
|
||||
size='big'
|
||||
title='Your Bot Clones'
|
||||
text='Loading your Bot Clones...'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
)
|
||||
} else if (data.operations_Clones && data.operations_Clones.length > 0) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<TopBar
|
||||
size='medium'
|
||||
title='Your Bot Clones'
|
||||
text='All your Bot Clones are here.'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
|
||||
<div className='container'>
|
||||
{
|
||||
data.operations_Clones.map((clone, i) => {
|
||||
return (
|
||||
<ListClones key={clone.id} currentClone={clone} />
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)
|
||||
} else if (data.error) {
|
||||
console.log('Error Listing clones.', data.error)
|
||||
return (
|
||||
<TopBar
|
||||
size='big'
|
||||
title='Your Bot Clones'
|
||||
text='There has been an error listing the clones.'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<TopBar
|
||||
size='big'
|
||||
title='Your Bot Clones'
|
||||
text="You don't have any Bot Clone yet. Once you create one you will find it here."
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
getBodyContent (data) {
|
||||
if (data.loading) {
|
||||
return (
|
||||
<TopBar
|
||||
size='big'
|
||||
title='Manage your Bot Clones'
|
||||
text='Loading Bot Clones...'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
)
|
||||
} else if (data.operations_Clones && data.operations_Clones.length > 0) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<TopBar
|
||||
size='big'
|
||||
title='Manage your Bot Clones'
|
||||
text='Loading Bot Clones...'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
|
||||
<div className='container'>
|
||||
{
|
||||
data.operations_Clones.map((clone, i) => {
|
||||
return (
|
||||
<ListClones key={clone.id} currentClone={clone} />
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)
|
||||
} else if (data.error) {
|
||||
return (
|
||||
<TopBar
|
||||
size='big'
|
||||
title='No Bot Clones to display'
|
||||
text='Please Login to access your Bot Clones.'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
|
||||
<TopBar
|
||||
size='big'
|
||||
title='No Bot Clones to display'
|
||||
text="You don't have any Bot Clone yet. After you create a new Clone, it will be listed here."
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default graphql(clones.OPERATIONS_LIST_CLONES)(BrowseClones)
|
||||
@@ -0,0 +1,89 @@
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
flexGrow: 1,
|
||||
margin: 10
|
||||
},
|
||||
image: {
|
||||
width: 128,
|
||||
height: '100%',
|
||||
cursor: 'default'
|
||||
},
|
||||
img: {
|
||||
margin: 'auto',
|
||||
display: 'block',
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%'
|
||||
|
||||
},
|
||||
buttonList: {
|
||||
margin: theme.spacing.unit,
|
||||
float: 'right'
|
||||
},
|
||||
buttonGrid: {
|
||||
marginTop: -20
|
||||
},
|
||||
button: {
|
||||
display: 'block',
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto'
|
||||
},
|
||||
textField: {
|
||||
marginLeft: theme.spacing.unit,
|
||||
marginRight: theme.spacing.unit
|
||||
},
|
||||
textArea: {
|
||||
margin: theme.spacing.unit,
|
||||
whiteSpace: 'pre',
|
||||
width: '98%',
|
||||
overflowY: 'hidden',
|
||||
overflowX: 'hidden'
|
||||
},
|
||||
heading: {
|
||||
fontSize: theme.typography.pxToRem(18),
|
||||
flexBasis: '33.33%',
|
||||
fontWeight: '600',
|
||||
whiteSpace: 'nowrap',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
paddingRight: theme.typography.pxToRem(20)
|
||||
},
|
||||
helper: {
|
||||
borderLeft: `2px solid ${theme.palette.divider}`,
|
||||
padding: `0px ${theme.spacing.unit * 2}px`
|
||||
},
|
||||
logsWrapper: {
|
||||
width: '1750px',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden'
|
||||
},
|
||||
details: {
|
||||
display: 'flex'
|
||||
},
|
||||
column2: {
|
||||
flexBasis: '50%',
|
||||
maxWidth: '50%'
|
||||
},
|
||||
column3: {
|
||||
flexBasis: '33.33%',
|
||||
maxWidth: '33.33%'
|
||||
},
|
||||
cloneInfoTitle: {
|
||||
fontWeight: '600',
|
||||
flexBasis: '100%',
|
||||
maxWidth: '100%'
|
||||
},
|
||||
cloneInfoBold: {
|
||||
fontWeight: '600',
|
||||
paddingLeft: '10px',
|
||||
paddingTop: '10px'
|
||||
},
|
||||
cloneInfoNormal: {
|
||||
paddingLeft: '10px',
|
||||
paddingTop: '10px'
|
||||
},
|
||||
avatar: {
|
||||
margin: 10
|
||||
}
|
||||
})
|
||||
|
||||
export default styles
|
||||
@@ -0,0 +1,51 @@
|
||||
import React, { Component } from 'react'
|
||||
import { Typography } from '@material-ui/core'
|
||||
import TopBar from './BannerTopBar'
|
||||
|
||||
class Home extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<TopBar
|
||||
size='big'
|
||||
title='Clones Module'
|
||||
text='Creating and managing bot clones'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
|
||||
<div className='homePage container'>
|
||||
<Typography variant='h1' align='center' className='title'>
|
||||
Clones Module
|
||||
</Typography>
|
||||
<Typography variant='h2' align='center' className='subtitle'>
|
||||
Creating and managing bot clones
|
||||
</Typography>
|
||||
<div className='column'>
|
||||
<Typography align='justify'>
|
||||
The clones module allows creating and managing the execution of bot’s code.
|
||||
It also stores the metadata information of the clones and handles the interaction
|
||||
with the orchestration software.
|
||||
</Typography>
|
||||
<Typography align='justify'>
|
||||
It integrates with other system modules to enable secure and encapsulated execution of the
|
||||
bots. On the clone creation process the encapsulation is achieved by deploying a Clone Executor
|
||||
Image on a Kubernetes container.
|
||||
</Typography>
|
||||
</div>
|
||||
<div className='column'>
|
||||
<Typography align='justify'>
|
||||
Bots are executed using the common framework called Clone Executor which provides support for commonly used resources.
|
||||
</Typography>
|
||||
<Typography align='justify'>
|
||||
When creating a new clone, specific execution parameters are selected, for example the exchange and the time period that the bot will operate in.
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Home
|
||||
Reference in new issue
Block a user