Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/api/dist
/api/cache

.DS_Store
# Logs
Expand Down
32 changes: 11 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@


# BitMEX trading tool

This project is a trading tool based on **BitMEX API** *(Binance coming next)*. This project has a long way to go before becoming an actual usable tool. If you notice any bugs, open an issue.
<p align="center">
<img src="assets/app_4.png">
</p>

## Table of Contents

Expand All @@ -17,6 +15,10 @@ This project is a trading tool based on **BitMEX API** *(Binance coming next)*.
- [License](#license)
- [Useful material](#useful-material)

# BitMEX trading tool

This project is a trading tool based on **BitMEX API**. This project has a long way to go before becoming an actual usable tool. If you notice any bugs, open an issue.

### Current Features

- **Scaled orders:**
Expand Down Expand Up @@ -75,16 +77,6 @@ This project is a trading tool based on **BitMEX API** *(Binance coming next)*.
- if you set a sell cross order price above current price, it will trigger a market sell order when the current price crosses up and then down of the set cross price;
- if you set a buy cross order price below current price, it will trigger a market buy order when the current price crosses down and then up of the set cross price.

- **Open Orders:**

- See currently open orders;
- Add profit targets for open orders (uses limit stop-loss orders to achieve that);
- Cancel any open/profit order(s);

<p align="center">
<img src="assets/open-orders.png">
</p>

### Built With

The Backend was built using **Node + Express** and the Frontend, **React + Redux**. Styled components were taken from **Chakra UI**
Expand Down Expand Up @@ -172,15 +164,13 @@ npm run prod

These are the available distributions to choose from:

<p align="center">
<img src="assets/distributions.png">
</p>
<p align="center">
<img src="assets/distributions.png">
</p>

Probability density function is used to calculate distributions:

<p align="center">
<img src="https://i.stack.imgur.com/bBIbn.png">
</p>
![formula](https://i.stack.imgur.com/bBIbn.png)

</br>

Expand Down
8 changes: 5 additions & 3 deletions api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
"description": "server side of the application(express)",
"author": "Juozas Rimantas <etajuozas@gmail.com>",
"license": "MIT",
"main": "src/app",
"main": "src/server",
"scripts": {
"tsc": "tsc",
"start": "cross-env NODE_ENV=production tsc && node ./dist/app.js",
"start": "cross-env NODE_ENV=production tsc && node ./dist/server.js",
"client": "(cd ../client && npm run start)",
"server": "nodemon src/app.ts",
"server": "nodemon src/server.ts",
"clean": "rimraf ./dist ../client/build ",
"eslint:ts": "eslint . -c .eslintrc --ignore-path .eslintignore --ext .ts --max-warnings 20 -f stylish",
"dev": "NODE_ENV=development && concurrently \"npm run server\" \"npm run client\"",
Expand All @@ -23,6 +23,7 @@
"crypto": "^1.0.1",
"dotenv": "8.0.0",
"express": "4.17.1",
"flat-cache": "^3.0.4",
"helmet": "3.21.1",
"morgan": "^1.10.0",
"request": "2.88.0",
Expand All @@ -33,6 +34,7 @@
"@types/cors": "2.8.6",
"@types/dotenv": "6.1.1",
"@types/express": "4.17.1",
"@types/flat-cache": "^2.0.0",
"@types/helmet": "0.0.44",
"@types/morgan": "^1.9.0",
"@types/node": "12.11.1",
Expand Down
148 changes: 54 additions & 94 deletions api/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,109 +1,69 @@
import path from 'path';
import dotenv from 'dotenv';
dotenv.config({path: path.join(__dirname, '../../client/.env')});

import express from 'express';
import helmet from 'helmet';
import cors from 'cors';
import morgan from 'morgan';
import flatCache from 'flat-cache';
import {logger} from './util/logger';
import Router from './routes/bitmex';

const app: express.Application = express();

const port = process.env.PORT || 3001;

app.set('port', port);
app.use(helmet());
app.use(cors());
app.disable('etag').disable('x-powered-by');
import {SettingsRouter} from './routes/settingsRouter';
import {BitmexRouter} from './routes/bitmexRouter';

export const cache = flatCache.create('apiKeyCache', path.resolve('./cache'));

export function expressApp() {
const app: express.Application = express();

const port = process.env.PORT || 3003;

app
.set('port', port)
.use(helmet())
.use(cors())
.disable('etag')
.disable('x-powered-by')
.use(express.urlencoded({extended: true}))
.use(express.json())
.use('/bitmex', BitmexRouter())
.use('/settings', SettingsRouter());

if (process.env.NODE_ENV != 'development') {
console.log(`Server is running at http://localhost:${app.get('port')} in ${app.get('env')} mode`);
console.log('Press CTRL-C to stop\n');
// Serve any static files
app.use(express.static(path.join(__dirname, '../../client/build')));
// Handle React routing, return all requests to React app
app.get('/*', function (req: express.Request, res: express.Response) {
res.sendFile(path.join(__dirname, '../../', 'client/build/index.html'));
});
}

app.use(express.urlencoded({extended: true}));
app.use(express.json());
app.use('/bitmex', Router);
const morganFormat = process.env.NODE_ENV !== 'production' ? 'dev' : 'combined';
app.use(morgan(morganFormat, {skip: (req, res) => res.statusCode < 400, stream: process.stderr}));
app.use(morgan(morganFormat, {skip: (req, res) => res.statusCode >= 400, stream: process.stdout}));

if (process.env.NODE_ENV != 'development') {
console.log(`Server is running at http://localhost:${app.get('port')} in ${app.get('env')} mode`);
console.log('Press CTRL-C to stop\n');
// Serve any static files
app.use(express.static(path.join(__dirname, '../../client/build')));
// Handle React routing, return all requests to React app
app.get('/*', function (req: express.Request, res: express.Response) {
res.sendFile(path.join(__dirname, '../../', 'client/build/index.html'));
app.get('/', function (req, res) {
logger.debug('Debug statement');
logger.info('Info statement');
res.send(req.method + ' ' + req.originalUrl);
});
}

// ============LOGGING============
const morganFormat = process.env.NODE_ENV !== 'production' ? 'dev' : 'combined';

app.use(
morgan(morganFormat, {
skip: function (req, res) {
return res.statusCode < 400;
},
stream: process.stderr,
}),
);

app.use(
morgan(morganFormat, {
skip: function (req, res) {
return res.statusCode >= 400;
},
stream: process.stdout,
}),
);

app.get('/', function (req, res) {
logger.debug('Debug statement');
logger.info('Info statement');
res.send(req.method + ' ' + req.originalUrl);
});

app.get('/error', function (req, res) {
throw new Error('Problem Here!');
});

// // All errors are sent back as JSON
app.use((err: any, req: any, res: any, next: any) => {
// Fallback to default node handler
if (res.headersSent) {
next(err);
return;
}

logger.error(err.message, {url: req.originalUrl});

res.status(500);
res.json({error: err.message});
});
app.get('/error', (req, res) => {
throw new Error('Problem Here!');
});

// eslint-disable-next-line @typescript-eslint/no-empty-function
const server = app.listen(app.get('port'), () => {});
// // All errors are sent back as JSON
app.use((err: any, req: any, res: any, next: any) => {
// Fallback to default node handler
if (res.headersSent) {
next(err);
return;
}

// on kill
process.on('SIGTERM', () => {
logger.log('warn', 'process.on::SIGTERM');
server.close(function () {
process.exit(0);
});
});
logger.error(err.message, {url: req.originalUrl});

process.on('exit', () => {
logger.log('warn', 'process.on::exit');
console.log('exit');
server.close(function () {
process.exit(2);
res.status(500);
res.json({error: err.message});
});
});

// on crash
process.on('uncaughtException', (error) => {
logger.log('error', 'process.on::uncaughtException');
logger.log('error', `Something terrible happened: ${error}`);
server.close(function () {
process.exit(1);
}); // exit application
});

export default app;
return app;
}
17 changes: 0 additions & 17 deletions api/src/controllers/bitmexController.ts

This file was deleted.

8 changes: 0 additions & 8 deletions api/src/routes/bitmex.ts

This file was deleted.

20 changes: 20 additions & 0 deletions api/src/routes/bitmexRouter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {Request, Response, Router} from 'express';
import {fetchBitmexExchange} from '../util/auth';
import {logger} from '../util/logger';

export function BitmexRouter(): Router {
return Router().post('/*', async ({originalUrl, baseUrl, body: {data, method}, query}: Request, res: Response) => {
try {
const path = originalUrl.replace(`${baseUrl}/`, '');
const body =
method === 'GET' ? (query ? {filter: JSON.parse((query.filter as string) || '{}')} : undefined) : data;

const response = await fetchBitmexExchange(path, method, body);
logger.info(`Successful ${method} request (${originalUrl})`);

return res.send({data: response, statusCode: res.statusCode});
} catch (error) {
return res.status(400).send({error: error});
}
});
}
54 changes: 54 additions & 0 deletions api/src/routes/settingsRouter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {Router} from 'express';
import {cache} from '../app';
import {logger} from '../util/logger';

export function SettingsRouter(): Router {
return Router()
.post('/apiKey', async function saveApiKey({body}, res) {
try {
cache.setKey(body.exchange, {key: body.key, secret: body.secret});
cache.save(true);
console.log('GET API KEY', body);
logger.info('Successfully saved api key');
return res.send({data: {exchange: body.exchange}, statusCode: res.statusCode});
} catch (error) {
return res.status(400).send({error: error});
}
})
.get('/apiKey', async function getApiKey({query}, res) {
try {
const exchange = query.exchange as string;
const data = cache.getKey(exchange);
return res.send({data: {exchange, key: data.key ?? '', secret: data.secret ?? ''}, statusCode: res.statusCode});
} catch (error) {
return res.status(400).send({error: error});
}
})
.get('/apiKeys', async function getAllApiKeys(req, res) {
try {
const data = cache.keys();
return res.send({data: {exchanges: data}, statusCode: res.statusCode});
} catch (error) {
return res.status(400).send({error: error});
}
})
.delete('/apiKey', async function deleteApiKey({body: {data}}, res) {
try {
cache.removeKey(data.exchange);
cache.save(true);
logger.info('Successfully deleted api key');
return res.send({data: {exchange: data.exchange}, statusCode: res.statusCode});
} catch (error) {
return res.status(400).send({error: error});
}
})
.delete('/apiKeys', async function deleteAllApiKeys(req, res) {
try {
cache.destroy();
logger.info('Successfully deleted all api keys');
return res.send({statusCode: res.statusCode});
} catch (error) {
return res.status(400).send({error: error});
}
});
}
28 changes: 28 additions & 0 deletions api/src/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import path from 'path';
import dotenv from 'dotenv';
dotenv.config({path: path.join(__dirname, '../../client/.env')});

import {logger} from './util/logger';
import {expressApp} from './app';

const app = expressApp();

// eslint-disable-next-line @typescript-eslint/no-empty-function
const server = app.listen(app.get('port'), () => {});

// on kill
process
.on('SIGTERM', () => {
logger.log('warn', 'process.on::SIGTERM');
server.close(() => void process.exit(0));
})
.on('exit', () => {
logger.log('warn', 'process.on::exit');
console.log('exit');
server.close(() => void process.exit(2));
})
.on('uncaughtException', (error) => {
logger.log('error', 'process.on::uncaughtException');
logger.log('error', `Something terrible happened: ${error}`);
server.close(() => void process.exit(1)); // exit application
});
Loading