Skip to content
This repository was archived by the owner on Jul 17, 2021. It is now read-only.

Commit ee92b35

Browse files
committed
Add Gridbot Backtesting PoC
1 parent 04272ac commit ee92b35

8 files changed

Lines changed: 121 additions & 15 deletions

File tree

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"editor.formatOnSave": true
3+
}

src/emulator/strategy_optimizer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export class StrategyOptimizer {
3333
let value = elem[1][0] + (elem[1][1] - elem[1][0]) * Math.random();
3434

3535
if (elem[1][2] === 'float') {
36-
value = _.round(value, elem[1][3]);
36+
value = _.floor(value, elem[1][3]);
3737
}
3838

3939
if (elem[1][2] === 'int') {

src/httpserver/controllers/backtest.controller.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import { OptimizeConfig, BacktestService } from '../services/backtest.service';
55

66
@Controller('backtest')
77
export class BacktestController {
8-
constructor(private backtestService: BacktestService) {}
8+
constructor(private backtestService: BacktestService) { }
99

1010
@Post('optimize')
11-
async getAll(@Body() optimizeConfig: OptimizeConfig): Promise<any> {
11+
async optimize(@Body() optimizeConfig: OptimizeConfig): Promise<any> {
1212
try {
1313
return await this.backtestService.optimize(optimizeConfig);
1414
} catch (err) {
@@ -17,4 +17,16 @@ export class BacktestController {
1717
throw new InternalServerErrorException();
1818
}
1919
}
20+
21+
@Post('gridbot')
22+
async gridbot(@Body() optimizeConfig: OptimizeConfig): Promise<any> {
23+
try {
24+
return await this.backtestService.optimize(optimizeConfig);
25+
} catch (err) {
26+
logger.error(`NestJS API error, ${err}`);
27+
28+
throw new InternalServerErrorException();
29+
}
30+
}
31+
2032
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Controller, Post, InternalServerErrorException, Body } from '@nestjs/common';
2+
3+
import { logger } from '../../logger';
4+
import { GridbotService } from '../services/gridbot.service';
5+
6+
@Controller('gridbot')
7+
export class GridbotController {
8+
constructor(private gridbotService: GridbotService) { }
9+
10+
@Post('backtest')
11+
async backtest(@Body() gridbotConfig: any): Promise<any> {
12+
try {
13+
return await this.gridbotService.backtest(gridbotConfig);
14+
} catch (err) {
15+
logger.error(`NestJS API error, ${err}`);
16+
17+
throw new InternalServerErrorException();
18+
}
19+
}
20+
}

src/httpserver/main.module.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import { StrategyService } from './services/strategy.service';
55
import { StrategyController } from './controllers/strategy.controller';
66
import { BacktestService } from './services/backtest.service';
77
import { BacktestController } from './controllers/backtest.controller';
8+
import { GridbotService } from './services/gridbot.service';
9+
import { GridbotController } from './controllers/gridbot.controller';
810

911
@Module({
10-
providers: [TradepairsService, StrategyService, BacktestService],
11-
controllers: [TradepairsController, StrategyController, BacktestController],
12+
providers: [TradepairsService, StrategyService, BacktestService, GridbotService],
13+
controllers: [TradepairsController, StrategyController, BacktestController, GridbotController],
1214
})
13-
export class MainModule {}
15+
export class MainModule { }
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { OHLCV } from 'sand-ex/build/types';
3+
import _ from 'lodash';
4+
5+
import tradePairs from '../../tradepairs/tradepairs';
6+
import { GridBotConfig, GridBot } from '../../grid_bot';
7+
8+
@Injectable()
9+
export class GridbotService {
10+
async backtest(config: any): Promise<any> {
11+
const exchange = 'binance';
12+
const symbol = 'BTC/USDT';
13+
const candleLimit = 1440 * 30;
14+
15+
const candleData = await tradePairs.getCandlestickFromDB(exchange, symbol, 60, candleLimit);
16+
17+
if (candleData) {
18+
const candleSticks = candleData.map(c => [c.time, c.open, c.high, c.low, c.close, c.volume]);
19+
20+
// Strategy optimizer, helper function
21+
const gridBotConfig: GridBotConfig = {
22+
priceLow: 7500,
23+
priceHigh: 10000,
24+
gridQuantity: 8,
25+
balanceQuote: 10000,
26+
fee: 0.00075, // 0.0075% / 100,
27+
};
28+
29+
const gridBot = new GridBot(gridBotConfig);
30+
31+
console.log(candleSticks.length);
32+
33+
for (let i = 0; i < candleSticks.length; i++) {
34+
gridBot.update((candleSticks[i] as unknown) as OHLCV);
35+
}
36+
37+
console.log(gridBot.exchange.getOrders().length);
38+
39+
console.log(gridBot.balanceAsset);
40+
console.log(gridBot.balanceQuote + gridBot.balanceAsset * candleSticks[candleSticks.length - 1][4]);
41+
42+
return '';
43+
}
44+
}
45+
}

src/tradepairs/tradepairs.ts

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,21 @@ class TradePairs {
1919
limit: number,
2020
): Promise<batchedOHLCV | undefined> {
2121
try {
22-
const limitCandlestick = ~~(limit + ((_.max(intervalsTimeInSec) as number) / EXCHANGE_BASE_INTERVAL_IN_SEC) * 1.5);
22+
const limitCandlestick = ~~(
23+
limit +
24+
((_.max(intervalsTimeInSec) as number) / EXCHANGE_BASE_INTERVAL_IN_SEC) * 1.5
25+
);
2326

2427
const batch = {};
2528
const result = new Map();
2629

2730
if (exchange && symbol && intervalsTimeInSec && limitCandlestick) {
28-
const candledata = await this._getCandlestickFromDB(exchange, symbol, EXCHANGE_BASE_INTERVAL_IN_SEC, limitCandlestick);
31+
const candledata = await this.getCandlestickFromDB(
32+
exchange,
33+
symbol,
34+
EXCHANGE_BASE_INTERVAL_IN_SEC,
35+
limitCandlestick,
36+
);
2937

3038
batch[EXCHANGE_BASE_INTERVAL_IN_SEC] = candledata;
3139

@@ -55,14 +63,19 @@ class TradePairs {
5563
}
5664
}
5765

58-
private async _getCandlestickFromDB(exchange: string, symbol: string, interval: number, limit: number): Promise<OHLCV[] | undefined> {
66+
public async getCandlestickFromDB(
67+
exchange: string,
68+
symbol: string,
69+
interval: number,
70+
limit: number,
71+
): Promise<OHLCV[] | undefined> {
5972
try {
6073
// eslint-disable-next-line @typescript-eslint/no-explicit-any
6174
let rows: any = [];
6275

6376
// TODO add proper support Tick Chart values
6477
if ([16, 32, 64, 128, 256, 512, 1024].indexOf(interval) >= 0) {
65-
rows = await this.getTickchartFromDB(exchange, symbol, interval, limit);
78+
rows = await this._getTickchartFromDB(exchange, symbol, interval, limit);
6679

6780
// Sort by time asc
6881
rows = _.sortBy(rows, ['time']);
@@ -71,10 +84,12 @@ class TradePairs {
7184
}
7285

7386
// Converted Candles
74-
if (interval != EXCHANGE_BASE_INTERVAL_IN_SEC && limit != 0) {
87+
if (interval !== EXCHANGE_BASE_INTERVAL_IN_SEC && limit !== 0) {
88+
// eslint-disable-next-line no-param-reassign
7589
limit *= interval / EXCHANGE_BASE_INTERVAL_IN_SEC;
7690

7791
// Limit should be always higher than convert ratio * 1,5 + 1
92+
// eslint-disable-next-line no-param-reassign
7893
limit += (interval / EXCHANGE_BASE_INTERVAL_IN_SEC) * 1.5;
7994
}
8095

@@ -83,7 +98,7 @@ class TradePairs {
8398
[rows] = await ExchangeDB.query('SELECT * FROM ?? ORDER BY `time` DESC LIMIT ?;', [tableName, ~~limit + 1]);
8499

85100
// Convert into new time frame
86-
if (interval != EXCHANGE_BASE_INTERVAL_IN_SEC) {
101+
if (interval !== EXCHANGE_BASE_INTERVAL_IN_SEC) {
87102
rows = CandleConvert.json(rows, EXCHANGE_BASE_INTERVAL_IN_SEC, interval);
88103
}
89104

@@ -96,11 +111,21 @@ class TradePairs {
96111
}
97112
}
98113

99-
public async getTickchartFromDB(exchange: string, symbol: string, tickLength: number, limit: number, time = 0): Promise<OHLCV[] | undefined> {
114+
private async _getTickchartFromDB(
115+
exchange: string,
116+
symbol: string,
117+
tickLength: number,
118+
limit: number,
119+
time = 0,
120+
): Promise<OHLCV[] | undefined> {
100121
try {
101122
const tableName = Utils.tradesName(exchange, symbol);
102123

103-
const [rows] = await ExchangeDB.query('SELECT * FROM ?? WHERE time > ? ORDER BY `time` DESC LIMIT ?;', [tableName, time, limit]);
124+
const [rows] = await ExchangeDB.query('SELECT * FROM ?? WHERE time > ? ORDER BY `time` DESC LIMIT ?;', [
125+
tableName,
126+
time,
127+
limit,
128+
]);
104129

105130
return CandleConvert.tick_chart(rows as Trade[], tickLength);
106131
} catch (e) {

tsconfig.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
"allowSyntheticDefaultImports": true,
99
"suppressImplicitAnyIndexErrors": true,
1010
"target": "es2017",
11-
"newLine": "lf",
1211
"noImplicitAny": true,
1312
"moduleResolution": "node",
1413
"sourceMap": true,

0 commit comments

Comments
 (0)