Operations Module moved to old repos

This commit is contained in:
Luis Fernando Molina committed 2020-11-20 19:46:12 +01:00
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