├── frontend ├── README.md ├── .eslintrc.json ├── jsconfig.json ├── public │ ├── favicon.ico │ └── vercel.svg ├── next.config.js ├── src │ ├── createEmotionCache.js │ └── theme.js ├── pages │ ├── api │ │ ├── hello.js │ │ └── activate │ │ │ └── [id] │ │ │ └── index.js │ ├── _app.js │ ├── _document.js │ └── index.js ├── styles │ ├── globals.css │ └── Home.module.css ├── components │ ├── Header.js │ ├── Logging.js │ ├── Status.js │ ├── NavBar.js │ └── Params.js ├── .gitignore ├── package.json └── yarn.lock ├── api ├── package.json ├── app.js └── yarn.lock ├── aioquant-app ├── run_strategy.py ├── strategy_config.json └── demo_strategy.py ├── README.md └── .gitignore /frontend/README.md: -------------------------------------------------------------------------------- 1 | # The frontend for Aioquant 2 | 3 | -------------------------------------------------------------------------------- /frontend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /frontend/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "." 4 | } 5 | } -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getjake/AIOQuant-Frontend/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | } 5 | 6 | module.exports = nextConfig 7 | -------------------------------------------------------------------------------- /frontend/src/createEmotionCache.js: -------------------------------------------------------------------------------- 1 | import createCache from '@emotion/cache'; 2 | 3 | export default function createEmotionCache() { 4 | return createCache({ key: 'css', prepend: true }); 5 | } 6 | -------------------------------------------------------------------------------- /api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "amqplib": "^0.8.0", 4 | "console-stamp": "^3.0.5", 5 | "express": "^4.18.1", 6 | "http": "^0.0.1-security", 7 | "websocket": "^1.0.34", 8 | "ws": "^8.6.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/pages/api/hello.js: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | 3 | console.log("Running Hello Js API!!") 4 | export default function handler(req, res) { 5 | res.status(200).json({ name: 'John Doe' }) 6 | } 7 | -------------------------------------------------------------------------------- /aioquant-app/run_strategy.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | # Start main strategy! 3 | from aioquant import quant 4 | 5 | 6 | 7 | def initialize(): 8 | from demo_strategy import MyStrategy 9 | MyStrategy() 10 | 11 | 12 | if __name__ == "__main__": 13 | config_file = "strategy_config.json" 14 | quant.start(config_file, initialize) 15 | -------------------------------------------------------------------------------- /frontend/styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | } 8 | 9 | a { 10 | color: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | -------------------------------------------------------------------------------- /frontend/components/Header.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import Head from 'next/head'; 3 | 4 | const Header = ({ title }) => { 5 | return ( 6 | 7 | {title} 8 | 9 | 10 | 11 | ); 12 | }; 13 | 14 | export default Header; 15 | -------------------------------------------------------------------------------- /frontend/src/theme.js: -------------------------------------------------------------------------------- 1 | import { createTheme } from '@mui/material/styles'; 2 | import { red } from '@mui/material/colors'; 3 | 4 | // Create a theme instance. 5 | const theme = createTheme({ 6 | palette: { 7 | primary: { 8 | main: '#556cd6', 9 | }, 10 | secondary: { 11 | main: '#19857b', 12 | }, 13 | error: { 14 | main: red.A400, 15 | }, 16 | }, 17 | }); 18 | 19 | export default theme; 20 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | -------------------------------------------------------------------------------- /aioquant-app/strategy_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "LOG": { 3 | "console": true, 4 | "level": "INFO", 5 | "path": "/home/code", 6 | "name": "trade.log", 7 | "clear": false, 8 | "backup_count": 5 9 | }, 10 | "RABBITMQ": { 11 | "host": "127.0.0.1", 12 | "port": 5672, 13 | "username": "chris", 14 | "password": "chris" 15 | }, 16 | "MONGODB": { 17 | "host": "127.0.0.1", 18 | "port": 27017, 19 | "username": "aioquant", 20 | "password": "aioquant" 21 | }, 22 | "REDIS": { 23 | "host": "127.0.0.1", 24 | "port": 6379 25 | }, 26 | "HEARTBEAT": { 27 | "interval": 0 28 | }, 29 | 30 | "ACCOUNTS": { 31 | "binance_swap": { 32 | "ACCOUNT_NAME": "default", 33 | "ACCESS_KEY": "", 34 | "SECRET_KEY": "", 35 | "PASSPHRASE": "" 36 | } 37 | 38 | }, 39 | "SERVER_ID": "USDT_ARBITRAGE_SERVER", 40 | "RUN_TIME_UPDATE": true, 41 | "STRATEGY_NAME": "USDT_ARBITRAGE" 42 | } 43 | -------------------------------------------------------------------------------- /frontend/public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@emotion/react": "^11.9.0", 13 | "@emotion/server": "^11.4.0", 14 | "@emotion/styled": "^11.8.1", 15 | "@mui/icons-material": "^5.6.2", 16 | "@mui/material": "^5.6.4", 17 | "amqplib": "^0.9.0", 18 | "console-stamp": "^3.0.5", 19 | "express": "^4.18.1", 20 | "find-free-port": "^2.0.0", 21 | "http": "^0.0.1-security", 22 | "js-cookie": "^3.0.1", 23 | "moment": "^2.29.3", 24 | "next": "12.1.6", 25 | "next-connect": "^0.12.2", 26 | "notistack": "^2.0.4", 27 | "react": "18.1.0", 28 | "react-dom": "18.1.0", 29 | "react-moment": "1.1.2", 30 | "react-use-websocket": "^4.0.0", 31 | "validator": "^13.7.0", 32 | "websocket": "^1.0.34", 33 | "ws": "^8.6.0" 34 | }, 35 | "devDependencies": { 36 | "eslint": "8.14.0", 37 | "eslint-config-next": "12.1.6" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # How it works 2 | 3 | `api` : Set up a local Websocket server as the middleware to communicate with frontend and backend AioQ program via RabbitMQ. 4 | 5 | `frontend`: Built with Next.js 6 | 7 | `aioquant-app`: Backend Running Strategy. 8 | 9 | # Websocket Data Structure 10 | 11 | ```javascript 12 | 13 | target = 'frontend' / 'backend' 14 | 15 | // This message is from frontend 2 backend 16 | message = { 17 | // request to change params 18 | request: 19 | { 20 | title: targetValue, // ex: allow open trade 21 | value: valueToChange // bool: 22 | } 23 | }; 24 | 25 | 26 | // This message is from backend 2 frontend 27 | message = { 28 | // The `status` and the `response` field may not be send 29 | // list all status - read only 30 | // example: account value, leverage, ... only 2 columns 31 | 32 | // The default READ-ONLY status that has been sent every 5 seconds. 33 | status: [{ 34 | title: String 35 | value: String 36 | }, {}...], 37 | 38 | // the Changable Params 39 | params: [{ 40 | title: String, 41 | type: "String, Boolean, Number", // 3 options 42 | value: String, 43 | }, {}...], 44 | 45 | // Response to the frontend's command to change `params` 46 | response: [{ 47 | title: String, 48 | isSucceed: Boolean 49 | }, ... ], 50 | 51 | // THE LOG 52 | loggingInfo: "", 53 | loggingWarning: "", 54 | loggingError: " 55 | } 56 | ``` 57 | 58 | 59 | -------------------------------------------------------------------------------- /frontend/pages/_app.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Head from 'next/head'; 4 | import { ThemeProvider } from '@mui/material/styles'; 5 | import CssBaseline from '@mui/material/CssBaseline'; 6 | import { CacheProvider } from '@emotion/react'; 7 | import theme from 'src/theme'; 8 | import createEmotionCache from 'src/createEmotionCache'; 9 | import { SnackbarProvider } from 'notistack'; 10 | // Client-side cache shared for the whole session 11 | // of the user in the browser. 12 | 13 | const clientSideEmotionCache = createEmotionCache(); 14 | 15 | export default function MyApp(props) { 16 | const { Component, emotionCache = clientSideEmotionCache, pageProps } = props; 17 | 18 | return ( 19 | 20 | 21 | 22 | 23 | 24 | 27 | {/* CssBaseline kickstart an elegant, 28 | consistent, and simple baseline to 29 | build upon. */} 30 | 31 | 32 | 33 | 34 | 35 | 36 | ); 37 | } 38 | 39 | MyApp.propTypes = { 40 | Component: PropTypes.elementType.isRequired, 41 | emotionCache: PropTypes.object, 42 | pageProps: PropTypes.object.isRequired, 43 | }; 44 | -------------------------------------------------------------------------------- /frontend/components/Logging.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { 3 | Button, 4 | Card, 5 | CardHeader, 6 | CardContent, 7 | Grid, 8 | Typography, 9 | TextareaAutosize, 10 | } from '@mui/material'; 11 | 12 | const Logging = ({ loggingHistory, setLoggingHistory }) => { 13 | const [loggingText, setLoggingText] = useState(''); 14 | 15 | useEffect(() => { 16 | if (loggingHistory.length < 1) return; 17 | const _loggingText = loggingHistory.map( 18 | (log) => `${log.logTime}:${log.logLevel}:: ${log.logMsg}\n\n` 19 | ); 20 | setLoggingText(_loggingText); 21 | }, [loggingHistory]); 22 | 23 | return ( 24 | 25 | 26 | 27 | 28 | 29 | 30 | 39 | 40 | 41 | 42 | 43 | 52 | 53 | 54 | ); 55 | }; 56 | 57 | export default Logging; 58 | -------------------------------------------------------------------------------- /frontend/components/Status.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Grid, 4 | Item, 5 | Card, 6 | CardHeader, 7 | CardContent, 8 | Typography, 9 | List, 10 | ListItem, 11 | Table, 12 | TableBody, 13 | TableCell, 14 | TableContainer, 15 | TableHead, 16 | TableRow, 17 | Paper, 18 | } from '@mui/material'; 19 | 20 | const Status = ({ status }) => { 21 | return ( 22 | <> 23 | {status.length === 0 ? ( 24 | Loading Status... 25 | ) : ( 26 | 27 | 28 | 29 | 30 | 34 | 35 | 36 | Title 37 | Value 38 | 39 | 40 | 41 | {status.map((item) => ( 42 | 48 | 49 | {item[0]} 50 | 51 | 52 | {item[1]} 53 | 54 | 55 | ))} 56 | 57 |
58 |
59 |
60 |
61 | )} 62 | 63 | ); 64 | }; 65 | 66 | export default Status; 67 | -------------------------------------------------------------------------------- /frontend/components/NavBar.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { 3 | AppBar, 4 | Box, 5 | Toolbar, 6 | Typography, 7 | TextField, 8 | Button, 9 | } from '@mui/material'; 10 | import Moment from 'react-moment'; 11 | import { useSnackbar } from 'notistack'; 12 | 13 | const NavBar = ({ 14 | strategy, 15 | setStrategy, 16 | wsConnectionStatus, 17 | updatedTimestamp, 18 | }) => { 19 | 20 | const { enqueueSnackbar } = useSnackbar(); 21 | const [strategyNameInput, setStrategyNameInput] = useState(''); 22 | const updateStrategyName = () => { 23 | if (strategyNameInput.length === 0) { 24 | enqueueSnackbar('Please enter strategy name!', {variant: 'error'}) 25 | return 26 | } 27 | setStrategy(strategyNameInput) 28 | enqueueSnackbar('Strategy Set Successfully!', {variant: 'success'}) 29 | }; 30 | 31 | return ( 32 | 33 | 34 | 35 | 36 | {strategy} 37 | 38 | { 43 | setStrategyNameInput(e.target.value); 44 | }} 45 | /> 46 | 53 | 54 | {wsConnectionStatus} 55 | 56 | 57 | {updatedTimestamp === 0 ? ( 58 | '' 59 | ) : ( 60 | <> 61 | Updated at 62 | 63 | {new Date(Math.floor(updatedTimestamp)).toISOString()} 64 | 65 | 66 | )} 67 | 68 | 69 | 70 | 71 | ); 72 | }; 73 | 74 | export default NavBar; 75 | -------------------------------------------------------------------------------- /frontend/styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 0 2rem; 3 | } 4 | 5 | .main { 6 | min-height: 100vh; 7 | padding: 4rem 0; 8 | flex: 1; 9 | display: flex; 10 | flex-direction: column; 11 | justify-content: center; 12 | align-items: center; 13 | } 14 | 15 | .footer { 16 | display: flex; 17 | flex: 1; 18 | padding: 2rem 0; 19 | border-top: 1px solid #eaeaea; 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | 24 | .footer a { 25 | display: flex; 26 | justify-content: center; 27 | align-items: center; 28 | flex-grow: 1; 29 | } 30 | 31 | .title a { 32 | color: #0070f3; 33 | text-decoration: none; 34 | } 35 | 36 | .title a:hover, 37 | .title a:focus, 38 | .title a:active { 39 | text-decoration: underline; 40 | } 41 | 42 | .title { 43 | margin: 0; 44 | line-height: 1.15; 45 | font-size: 4rem; 46 | } 47 | 48 | .title, 49 | .description { 50 | text-align: center; 51 | } 52 | 53 | .description { 54 | margin: 4rem 0; 55 | line-height: 1.5; 56 | font-size: 1.5rem; 57 | } 58 | 59 | .code { 60 | background: #fafafa; 61 | border-radius: 5px; 62 | padding: 0.75rem; 63 | font-size: 1.1rem; 64 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, 65 | Bitstream Vera Sans Mono, Courier New, monospace; 66 | } 67 | 68 | .grid { 69 | display: flex; 70 | align-items: center; 71 | justify-content: center; 72 | flex-wrap: wrap; 73 | max-width: 800px; 74 | } 75 | 76 | .card { 77 | margin: 1rem; 78 | padding: 1.5rem; 79 | text-align: left; 80 | color: inherit; 81 | text-decoration: none; 82 | border: 1px solid #eaeaea; 83 | border-radius: 10px; 84 | transition: color 0.15s ease, border-color 0.15s ease; 85 | max-width: 300px; 86 | } 87 | 88 | .card:hover, 89 | .card:focus, 90 | .card:active { 91 | color: #0070f3; 92 | border-color: #0070f3; 93 | } 94 | 95 | .card h2 { 96 | margin: 0 0 1rem 0; 97 | font-size: 1.5rem; 98 | } 99 | 100 | .card p { 101 | margin: 0; 102 | font-size: 1.25rem; 103 | line-height: 1.5; 104 | } 105 | 106 | .logo { 107 | height: 1em; 108 | margin-left: 0.5rem; 109 | } 110 | 111 | @media (max-width: 600px) { 112 | .grid { 113 | width: 100%; 114 | flex-direction: column; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /frontend/pages/_document.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Document, { Html, Head, Main, NextScript } from 'next/document'; 3 | import createEmotionServer from '@emotion/server/create-instance'; 4 | import theme from 'src/theme'; 5 | import createEmotionCache from 'src/createEmotionCache'; 6 | 7 | export default class MyDocument extends Document { 8 | render() { 9 | return ( 10 | 11 | 12 | {/* PWA primary color */} 13 | 15 | 17 | 22 | {/* Inject MUI styles first to match with the prepend: true configuration. */} 23 | {this.props.emotionStyleTags} 24 | 25 | 26 |
27 | 28 | 29 | 30 | ); 31 | } 32 | } 33 | 34 | // `getInitialProps` belongs to `_document` (instead of `_app`), 35 | // it's compatible with static-site generation (SSG). 36 | MyDocument.getInitialProps = async (ctx) => { 37 | 38 | const originalRenderPage = ctx.renderPage; 39 | 40 | // You can consider sharing the same emotion cache between 41 | // all the SSR requests to speed up performance. 42 | // However, be aware that it can have global side effects. 43 | 44 | const cache = createEmotionCache(); 45 | const { extractCriticalToChunks } = createEmotionServer(cache); 46 | 47 | ctx.renderPage = () => 48 | originalRenderPage({ 49 | enhanceApp: (App) => 50 | function EnhanceApp(props) { 51 | return ; 52 | }, 53 | }); 54 | 55 | const initialProps = await Document.getInitialProps(ctx); 56 | 57 | // This is important. It prevents emotion to render invalid HTML. 58 | // See 59 | // https://github.com/mui-org/material-ui/issues/26561#issuecomment-855286153 60 | 61 | const emotionStyles = extractCriticalToChunks(initialProps.html); 62 | const emotionStyleTags = emotionStyles.styles.map((style) => ( 63 |