backtest-kit 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -9
- package/build/index.cjs +111 -45
- package/build/index.mjs +109 -45
- package/package.json +1 -1
- package/types.d.ts +35 -23
package/README.md
CHANGED
|
@@ -21,12 +21,12 @@ npm install
|
|
|
21
21
|
|
|
22
22
|
## Quick Start
|
|
23
23
|
|
|
24
|
-
### 1. Add Data Source (
|
|
24
|
+
### 1. Add Data Source (Exchange)
|
|
25
25
|
|
|
26
26
|
```typescript
|
|
27
|
-
import {
|
|
27
|
+
import { addExchange } from "./src/function/add";
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
addExchange({
|
|
30
30
|
getCandles: async (symbol, interval, since, limit) => {
|
|
31
31
|
// Fetch candle data from your source (exchange API, database, etc.)
|
|
32
32
|
return [
|
|
@@ -164,16 +164,17 @@ console.log(result.accumulator);
|
|
|
164
164
|
```
|
|
165
165
|
src/
|
|
166
166
|
├── function/ # High-level API functions
|
|
167
|
-
│ ├── add.ts # Add schemas (strategy,
|
|
167
|
+
│ ├── add.ts # Add schemas (strategy, exchange)
|
|
168
168
|
│ ├── backtest.ts # Backtesting functions
|
|
169
169
|
│ ├── reduce.ts # Reduce pattern for accumulation
|
|
170
|
-
│
|
|
170
|
+
│ ├── run.ts # Real-time execution
|
|
171
|
+
│ └── exchange.ts # Exchange data functions
|
|
171
172
|
├── client/ # Client implementations
|
|
172
|
-
│ ├──
|
|
173
|
+
│ ├── ClientExchange.ts # Exchange client with VWAP
|
|
173
174
|
│ └── ClientStrategy.ts # Strategy client with signal lifecycle
|
|
174
175
|
├── interfaces/ # TypeScript interfaces
|
|
175
176
|
│ ├── Strategy.interface.ts
|
|
176
|
-
│ └──
|
|
177
|
+
│ └── Exchange.interface.ts
|
|
177
178
|
└── lib/ # Core library with DI
|
|
178
179
|
├── core/ # Dependency injection
|
|
179
180
|
└── services/ # Services (schema, connection, public)
|
|
@@ -200,12 +201,18 @@ export const PERCENT_FEE = 0.1; // 0.1%
|
|
|
200
201
|
|
|
201
202
|
### Functions
|
|
202
203
|
|
|
203
|
-
#### `
|
|
204
|
-
Add
|
|
204
|
+
#### `addExchange(exchangeSchema: IExchangeSchema)`
|
|
205
|
+
Add exchange data source for candles.
|
|
205
206
|
|
|
206
207
|
#### `addStrategy(strategySchema: IStrategySchema)`
|
|
207
208
|
Add trading strategy.
|
|
208
209
|
|
|
210
|
+
#### `getCandles(symbol, interval, limit): Promise<ICandleData[]>`
|
|
211
|
+
Get candle data from exchange.
|
|
212
|
+
|
|
213
|
+
#### `getAveragePrice(symbol): Promise<number>`
|
|
214
|
+
Get VWAP average price based on last 5 1m candles.
|
|
215
|
+
|
|
209
216
|
#### `runBacktest(symbol: string, timeframes: Date[]): Promise<IBacktestResult>`
|
|
210
217
|
Run backtest and return closed trades only.
|
|
211
218
|
|
package/build/index.cjs
CHANGED
|
@@ -40,15 +40,15 @@ const contextServices$1 = {
|
|
|
40
40
|
executionContextService: Symbol('executionContextService'),
|
|
41
41
|
};
|
|
42
42
|
const connectionServices$1 = {
|
|
43
|
-
|
|
43
|
+
exchangeConnectionService: Symbol('exchangeConnectionService'),
|
|
44
44
|
strategyConnectionService: Symbol('strategyConnectionService'),
|
|
45
45
|
};
|
|
46
46
|
const schemaServices$1 = {
|
|
47
|
-
|
|
47
|
+
exchangeSchemaService: Symbol('exchangeSchemaService'),
|
|
48
48
|
strategySchemaService: Symbol('strategySchemaService'),
|
|
49
49
|
};
|
|
50
50
|
const publicServices$1 = {
|
|
51
|
-
|
|
51
|
+
exchangePublicService: Symbol('exchangePublicService'),
|
|
52
52
|
strategyPublicService: Symbol('strategyPublicService'),
|
|
53
53
|
};
|
|
54
54
|
const TYPES = {
|
|
@@ -71,11 +71,11 @@ const INTERVAL_MINUTES = {
|
|
|
71
71
|
"6h": 360,
|
|
72
72
|
"8h": 480,
|
|
73
73
|
};
|
|
74
|
-
class
|
|
74
|
+
class ClientExchange {
|
|
75
75
|
constructor(params) {
|
|
76
76
|
this.params = params;
|
|
77
77
|
this.getCandles = async (symbol, interval, limit) => {
|
|
78
|
-
this.params.logger.debug(`
|
|
78
|
+
this.params.logger.debug(`ClientExchange getCandles`, {
|
|
79
79
|
symbol,
|
|
80
80
|
interval,
|
|
81
81
|
limit,
|
|
@@ -83,7 +83,7 @@ class ClientCandle {
|
|
|
83
83
|
const step = INTERVAL_MINUTES[interval];
|
|
84
84
|
const adjust = step * limit;
|
|
85
85
|
if (!adjust) {
|
|
86
|
-
throw new Error(`
|
|
86
|
+
throw new Error(`ClientExchange unknown time adjust for interval=${interval}`);
|
|
87
87
|
}
|
|
88
88
|
const since = new Date(this.params.execution.context.when.getTime() - adjust * 60 * 1000);
|
|
89
89
|
const data = await this.params.getCandles(symbol, interval, since, limit);
|
|
@@ -93,12 +93,12 @@ class ClientCandle {
|
|
|
93
93
|
return data;
|
|
94
94
|
};
|
|
95
95
|
this.getAveragePrice = async (symbol) => {
|
|
96
|
-
this.params.logger.debug(`
|
|
96
|
+
this.params.logger.debug(`ClientExchange getAveragePrice`, {
|
|
97
97
|
symbol,
|
|
98
98
|
});
|
|
99
99
|
const candles = await this.getCandles(symbol, "1m", 5);
|
|
100
100
|
if (candles.length === 0) {
|
|
101
|
-
throw new Error(`
|
|
101
|
+
throw new Error(`ClientExchange getAveragePrice: no candles data for symbol=${symbol}`);
|
|
102
102
|
}
|
|
103
103
|
// VWAP (Volume Weighted Average Price)
|
|
104
104
|
// Используем типичную цену (typical price) = (high + low + close) / 3
|
|
@@ -115,36 +115,66 @@ class ClientCandle {
|
|
|
115
115
|
const vwap = sumPriceVolume / totalVolume;
|
|
116
116
|
return vwap;
|
|
117
117
|
};
|
|
118
|
+
this.formatQuantity = async (symbol, quantity) => {
|
|
119
|
+
this.params.logger.debug("binanceService formatQuantity", {
|
|
120
|
+
symbol,
|
|
121
|
+
quantity,
|
|
122
|
+
});
|
|
123
|
+
return await this.params.formatQuantity(symbol, quantity);
|
|
124
|
+
};
|
|
125
|
+
this.formatPrice = async (symbol, price) => {
|
|
126
|
+
this.params.logger.debug("binanceService formatPrice", {
|
|
127
|
+
symbol,
|
|
128
|
+
price,
|
|
129
|
+
});
|
|
130
|
+
return await this.params.formatPrice(symbol, price);
|
|
131
|
+
};
|
|
118
132
|
}
|
|
119
133
|
}
|
|
120
134
|
|
|
121
|
-
class
|
|
135
|
+
class ExchangeConnectionService {
|
|
122
136
|
constructor() {
|
|
123
137
|
this.loggerService = inject(TYPES.loggerService);
|
|
124
138
|
this.executionContextService = inject(TYPES.executionContextService);
|
|
125
|
-
this.
|
|
126
|
-
this.
|
|
127
|
-
const { getCandles, callbacks } = this.
|
|
128
|
-
return new
|
|
139
|
+
this.exchangeSchemaService = inject(TYPES.exchangeSchemaService);
|
|
140
|
+
this.getExchange = functoolsKit.memoize((symbol) => `${symbol}`, () => {
|
|
141
|
+
const { getCandles, formatPrice, formatQuantity, callbacks } = this.exchangeSchemaService.getSchema();
|
|
142
|
+
return new ClientExchange({
|
|
129
143
|
execution: this.executionContextService,
|
|
130
144
|
logger: this.loggerService,
|
|
131
145
|
getCandles,
|
|
146
|
+
formatPrice,
|
|
147
|
+
formatQuantity,
|
|
132
148
|
callbacks,
|
|
133
149
|
});
|
|
134
150
|
});
|
|
135
151
|
this.getCandles = async (symbol, interval, limit) => {
|
|
136
|
-
this.loggerService.log("
|
|
152
|
+
this.loggerService.log("exchangeConnectionService getCandles", {
|
|
137
153
|
symbol,
|
|
138
154
|
interval,
|
|
139
155
|
limit,
|
|
140
156
|
});
|
|
141
|
-
return await this.
|
|
157
|
+
return await this.getExchange(symbol).getCandles(symbol, interval, limit);
|
|
142
158
|
};
|
|
143
159
|
this.getAveragePrice = async (symbol) => {
|
|
144
|
-
this.loggerService.log("
|
|
160
|
+
this.loggerService.log("exchangeConnectionService getAveragePrice", {
|
|
161
|
+
symbol,
|
|
162
|
+
});
|
|
163
|
+
return await this.getExchange(symbol).getAveragePrice(symbol);
|
|
164
|
+
};
|
|
165
|
+
this.formatPrice = async (symbol, price) => {
|
|
166
|
+
this.loggerService.log("exchangeConnectionService getAveragePrice", {
|
|
145
167
|
symbol,
|
|
168
|
+
price,
|
|
146
169
|
});
|
|
147
|
-
return await this.
|
|
170
|
+
return await this.getExchange(symbol).formatPrice(symbol, price);
|
|
171
|
+
};
|
|
172
|
+
this.formatQuantity = async (symbol, quantity) => {
|
|
173
|
+
this.loggerService.log("exchangeConnectionService getAveragePrice", {
|
|
174
|
+
symbol,
|
|
175
|
+
quantity,
|
|
176
|
+
});
|
|
177
|
+
return await this.getExchange(symbol).formatQuantity(symbol, quantity);
|
|
148
178
|
};
|
|
149
179
|
}
|
|
150
180
|
}
|
|
@@ -217,7 +247,7 @@ class ClientStrategy {
|
|
|
217
247
|
const when = this.params.execution.context.when;
|
|
218
248
|
const signal = this._pendingSignal;
|
|
219
249
|
// Получаем среднюю цену
|
|
220
|
-
const averagePrice = await this.params.
|
|
250
|
+
const averagePrice = await this.params.exchange.getAveragePrice(symbol);
|
|
221
251
|
this.params.logger.debug("ClientStrategy tick check", {
|
|
222
252
|
symbol,
|
|
223
253
|
averagePrice,
|
|
@@ -290,14 +320,14 @@ class StrategyConnectionService {
|
|
|
290
320
|
this.loggerService = inject(TYPES.loggerService);
|
|
291
321
|
this.executionContextService = inject(TYPES.executionContextService);
|
|
292
322
|
this.strategySchemaService = inject(TYPES.strategySchemaService);
|
|
293
|
-
this.
|
|
323
|
+
this.exchangeConnectionService = inject(TYPES.exchangeConnectionService);
|
|
294
324
|
this.getStrategy = functoolsKit.memoize((symbol) => `${symbol}`, (symbol) => {
|
|
295
325
|
const { getSignal, callbacks } = this.strategySchemaService.getSchema();
|
|
296
326
|
return new ClientStrategy({
|
|
297
327
|
symbol,
|
|
298
328
|
execution: this.executionContextService,
|
|
299
329
|
logger: this.loggerService,
|
|
300
|
-
|
|
330
|
+
exchange: this.exchangeConnectionService,
|
|
301
331
|
getSignal,
|
|
302
332
|
callbacks,
|
|
303
333
|
});
|
|
@@ -317,12 +347,12 @@ const ExecutionContextService = diScoped.scoped(class {
|
|
|
317
347
|
}
|
|
318
348
|
});
|
|
319
349
|
|
|
320
|
-
class
|
|
350
|
+
class ExchangePublicService {
|
|
321
351
|
constructor() {
|
|
322
352
|
this.loggerService = inject(TYPES.loggerService);
|
|
323
|
-
this.
|
|
353
|
+
this.exchangeConnectionService = inject(TYPES.exchangeConnectionService);
|
|
324
354
|
this.getCandles = async (symbol, interval, limit, when, backtest) => {
|
|
325
|
-
this.loggerService.log("
|
|
355
|
+
this.loggerService.log("exchangePublicService getCandles", {
|
|
326
356
|
symbol,
|
|
327
357
|
interval,
|
|
328
358
|
limit,
|
|
@@ -330,20 +360,48 @@ class CandlePublicService {
|
|
|
330
360
|
backtest,
|
|
331
361
|
});
|
|
332
362
|
return await ExecutionContextService.runInContext(async () => {
|
|
333
|
-
return await this.
|
|
363
|
+
return await this.exchangeConnectionService.getCandles(symbol, interval, limit);
|
|
334
364
|
}, {
|
|
335
365
|
when,
|
|
336
366
|
backtest,
|
|
337
367
|
});
|
|
338
368
|
};
|
|
339
369
|
this.getAveragePrice = async (symbol, when, backtest) => {
|
|
340
|
-
this.loggerService.log("
|
|
370
|
+
this.loggerService.log("exchangePublicService getAveragePrice", {
|
|
371
|
+
symbol,
|
|
372
|
+
when,
|
|
373
|
+
backtest,
|
|
374
|
+
});
|
|
375
|
+
return await ExecutionContextService.runInContext(async () => {
|
|
376
|
+
return await this.exchangeConnectionService.getAveragePrice(symbol);
|
|
377
|
+
}, {
|
|
378
|
+
when,
|
|
379
|
+
backtest,
|
|
380
|
+
});
|
|
381
|
+
};
|
|
382
|
+
this.formatPrice = async (symbol, price, when, backtest) => {
|
|
383
|
+
this.loggerService.log("exchangePublicService formatPrice", {
|
|
341
384
|
symbol,
|
|
385
|
+
price,
|
|
342
386
|
when,
|
|
343
387
|
backtest,
|
|
344
388
|
});
|
|
345
389
|
return await ExecutionContextService.runInContext(async () => {
|
|
346
|
-
return await this.
|
|
390
|
+
return await this.exchangeConnectionService.formatPrice(symbol, price);
|
|
391
|
+
}, {
|
|
392
|
+
when,
|
|
393
|
+
backtest,
|
|
394
|
+
});
|
|
395
|
+
};
|
|
396
|
+
this.formatQuantity = async (symbol, quantity, when, backtest) => {
|
|
397
|
+
this.loggerService.log("exchangePublicService formatQuantity", {
|
|
398
|
+
symbol,
|
|
399
|
+
quantity,
|
|
400
|
+
when,
|
|
401
|
+
backtest,
|
|
402
|
+
});
|
|
403
|
+
return await ExecutionContextService.runInContext(async () => {
|
|
404
|
+
return await this.exchangeConnectionService.formatQuantity(symbol, quantity);
|
|
347
405
|
}, {
|
|
348
406
|
when,
|
|
349
407
|
backtest,
|
|
@@ -372,19 +430,19 @@ class StrategyPublicService {
|
|
|
372
430
|
}
|
|
373
431
|
}
|
|
374
432
|
|
|
375
|
-
class
|
|
433
|
+
class ExchangeSchemaService {
|
|
376
434
|
constructor() {
|
|
377
435
|
this.loggerService = inject(TYPES.loggerService);
|
|
378
436
|
this.getSchema = () => {
|
|
379
|
-
this.loggerService.log("
|
|
380
|
-
if (!this.
|
|
381
|
-
throw new Error("
|
|
437
|
+
this.loggerService.log("exchangeSchemaService getSchema");
|
|
438
|
+
if (!this._exchangeSchema) {
|
|
439
|
+
throw new Error("ExchangeSchemaService no exchange source provided");
|
|
382
440
|
}
|
|
383
|
-
return this.
|
|
441
|
+
return this._exchangeSchema;
|
|
384
442
|
};
|
|
385
|
-
this.addSchema = (
|
|
386
|
-
this.loggerService.log("
|
|
387
|
-
this.
|
|
443
|
+
this.addSchema = (exchangeSchema) => {
|
|
444
|
+
this.loggerService.log("exchangeSchemaService addSchema");
|
|
445
|
+
this._exchangeSchema = exchangeSchema;
|
|
388
446
|
};
|
|
389
447
|
}
|
|
390
448
|
}
|
|
@@ -413,15 +471,15 @@ class StrategySchemaService {
|
|
|
413
471
|
provide(TYPES.executionContextService, () => new ExecutionContextService());
|
|
414
472
|
}
|
|
415
473
|
{
|
|
416
|
-
provide(TYPES.
|
|
474
|
+
provide(TYPES.exchangeConnectionService, () => new ExchangeConnectionService());
|
|
417
475
|
provide(TYPES.strategyConnectionService, () => new StrategyConnectionService());
|
|
418
476
|
}
|
|
419
477
|
{
|
|
420
|
-
provide(TYPES.
|
|
478
|
+
provide(TYPES.exchangeSchemaService, () => new ExchangeSchemaService());
|
|
421
479
|
provide(TYPES.strategySchemaService, () => new StrategySchemaService());
|
|
422
480
|
}
|
|
423
481
|
{
|
|
424
|
-
provide(TYPES.
|
|
482
|
+
provide(TYPES.exchangePublicService, () => new ExchangePublicService());
|
|
425
483
|
provide(TYPES.strategyPublicService, () => new StrategyPublicService());
|
|
426
484
|
}
|
|
427
485
|
|
|
@@ -432,15 +490,15 @@ const contextServices = {
|
|
|
432
490
|
executionContextService: inject(TYPES.executionContextService),
|
|
433
491
|
};
|
|
434
492
|
const connectionServices = {
|
|
435
|
-
|
|
493
|
+
exchangeConnectionService: inject(TYPES.exchangeConnectionService),
|
|
436
494
|
strategyConnectionService: inject(TYPES.strategyConnectionService),
|
|
437
495
|
};
|
|
438
496
|
const schemaServices = {
|
|
439
|
-
|
|
497
|
+
exchangeSchemaService: inject(TYPES.exchangeSchemaService),
|
|
440
498
|
strategySchemaService: inject(TYPES.strategySchemaService),
|
|
441
499
|
};
|
|
442
500
|
const publicServices = {
|
|
443
|
-
|
|
501
|
+
exchangePublicService: inject(TYPES.exchangePublicService),
|
|
444
502
|
strategyPublicService: inject(TYPES.strategyPublicService),
|
|
445
503
|
};
|
|
446
504
|
const backtest = {
|
|
@@ -455,8 +513,8 @@ init();
|
|
|
455
513
|
function addStrategy(strategySchema) {
|
|
456
514
|
backtest.strategySchemaService.addSchema(strategySchema);
|
|
457
515
|
}
|
|
458
|
-
function
|
|
459
|
-
backtest.
|
|
516
|
+
function addExchange(exchangeSchema) {
|
|
517
|
+
backtest.exchangeSchemaService.addSchema(exchangeSchema);
|
|
460
518
|
}
|
|
461
519
|
|
|
462
520
|
async function runBacktest(symbol, timeframes) {
|
|
@@ -573,16 +631,24 @@ function stopAll() {
|
|
|
573
631
|
}
|
|
574
632
|
|
|
575
633
|
async function getCandles(symbol, interval, limit) {
|
|
576
|
-
return await backtest.
|
|
634
|
+
return await backtest.exchangeConnectionService.getCandles(symbol, interval, limit);
|
|
577
635
|
}
|
|
578
636
|
async function getAveragePrice(symbol) {
|
|
579
|
-
return await backtest.
|
|
637
|
+
return await backtest.exchangeConnectionService.getAveragePrice(symbol);
|
|
638
|
+
}
|
|
639
|
+
async function formatPrice(symbol, price) {
|
|
640
|
+
return await backtest.exchangeConnectionService.formatPrice(symbol, price);
|
|
641
|
+
}
|
|
642
|
+
async function formatQuantity(symbol, quantity) {
|
|
643
|
+
return await backtest.exchangeConnectionService.formatQuantity(symbol, quantity);
|
|
580
644
|
}
|
|
581
645
|
|
|
582
646
|
exports.ExecutionContextService = ExecutionContextService;
|
|
583
|
-
exports.
|
|
647
|
+
exports.addExchange = addExchange;
|
|
584
648
|
exports.addStrategy = addStrategy;
|
|
585
649
|
exports.backtest = backtest;
|
|
650
|
+
exports.formatPrice = formatPrice;
|
|
651
|
+
exports.formatQuantity = formatQuantity;
|
|
586
652
|
exports.getAveragePrice = getAveragePrice;
|
|
587
653
|
exports.getCandles = getCandles;
|
|
588
654
|
exports.reduce = reduce;
|
package/build/index.mjs
CHANGED
|
@@ -38,15 +38,15 @@ const contextServices$1 = {
|
|
|
38
38
|
executionContextService: Symbol('executionContextService'),
|
|
39
39
|
};
|
|
40
40
|
const connectionServices$1 = {
|
|
41
|
-
|
|
41
|
+
exchangeConnectionService: Symbol('exchangeConnectionService'),
|
|
42
42
|
strategyConnectionService: Symbol('strategyConnectionService'),
|
|
43
43
|
};
|
|
44
44
|
const schemaServices$1 = {
|
|
45
|
-
|
|
45
|
+
exchangeSchemaService: Symbol('exchangeSchemaService'),
|
|
46
46
|
strategySchemaService: Symbol('strategySchemaService'),
|
|
47
47
|
};
|
|
48
48
|
const publicServices$1 = {
|
|
49
|
-
|
|
49
|
+
exchangePublicService: Symbol('exchangePublicService'),
|
|
50
50
|
strategyPublicService: Symbol('strategyPublicService'),
|
|
51
51
|
};
|
|
52
52
|
const TYPES = {
|
|
@@ -69,11 +69,11 @@ const INTERVAL_MINUTES = {
|
|
|
69
69
|
"6h": 360,
|
|
70
70
|
"8h": 480,
|
|
71
71
|
};
|
|
72
|
-
class
|
|
72
|
+
class ClientExchange {
|
|
73
73
|
constructor(params) {
|
|
74
74
|
this.params = params;
|
|
75
75
|
this.getCandles = async (symbol, interval, limit) => {
|
|
76
|
-
this.params.logger.debug(`
|
|
76
|
+
this.params.logger.debug(`ClientExchange getCandles`, {
|
|
77
77
|
symbol,
|
|
78
78
|
interval,
|
|
79
79
|
limit,
|
|
@@ -81,7 +81,7 @@ class ClientCandle {
|
|
|
81
81
|
const step = INTERVAL_MINUTES[interval];
|
|
82
82
|
const adjust = step * limit;
|
|
83
83
|
if (!adjust) {
|
|
84
|
-
throw new Error(`
|
|
84
|
+
throw new Error(`ClientExchange unknown time adjust for interval=${interval}`);
|
|
85
85
|
}
|
|
86
86
|
const since = new Date(this.params.execution.context.when.getTime() - adjust * 60 * 1000);
|
|
87
87
|
const data = await this.params.getCandles(symbol, interval, since, limit);
|
|
@@ -91,12 +91,12 @@ class ClientCandle {
|
|
|
91
91
|
return data;
|
|
92
92
|
};
|
|
93
93
|
this.getAveragePrice = async (symbol) => {
|
|
94
|
-
this.params.logger.debug(`
|
|
94
|
+
this.params.logger.debug(`ClientExchange getAveragePrice`, {
|
|
95
95
|
symbol,
|
|
96
96
|
});
|
|
97
97
|
const candles = await this.getCandles(symbol, "1m", 5);
|
|
98
98
|
if (candles.length === 0) {
|
|
99
|
-
throw new Error(`
|
|
99
|
+
throw new Error(`ClientExchange getAveragePrice: no candles data for symbol=${symbol}`);
|
|
100
100
|
}
|
|
101
101
|
// VWAP (Volume Weighted Average Price)
|
|
102
102
|
// Используем типичную цену (typical price) = (high + low + close) / 3
|
|
@@ -113,36 +113,66 @@ class ClientCandle {
|
|
|
113
113
|
const vwap = sumPriceVolume / totalVolume;
|
|
114
114
|
return vwap;
|
|
115
115
|
};
|
|
116
|
+
this.formatQuantity = async (symbol, quantity) => {
|
|
117
|
+
this.params.logger.debug("binanceService formatQuantity", {
|
|
118
|
+
symbol,
|
|
119
|
+
quantity,
|
|
120
|
+
});
|
|
121
|
+
return await this.params.formatQuantity(symbol, quantity);
|
|
122
|
+
};
|
|
123
|
+
this.formatPrice = async (symbol, price) => {
|
|
124
|
+
this.params.logger.debug("binanceService formatPrice", {
|
|
125
|
+
symbol,
|
|
126
|
+
price,
|
|
127
|
+
});
|
|
128
|
+
return await this.params.formatPrice(symbol, price);
|
|
129
|
+
};
|
|
116
130
|
}
|
|
117
131
|
}
|
|
118
132
|
|
|
119
|
-
class
|
|
133
|
+
class ExchangeConnectionService {
|
|
120
134
|
constructor() {
|
|
121
135
|
this.loggerService = inject(TYPES.loggerService);
|
|
122
136
|
this.executionContextService = inject(TYPES.executionContextService);
|
|
123
|
-
this.
|
|
124
|
-
this.
|
|
125
|
-
const { getCandles, callbacks } = this.
|
|
126
|
-
return new
|
|
137
|
+
this.exchangeSchemaService = inject(TYPES.exchangeSchemaService);
|
|
138
|
+
this.getExchange = memoize((symbol) => `${symbol}`, () => {
|
|
139
|
+
const { getCandles, formatPrice, formatQuantity, callbacks } = this.exchangeSchemaService.getSchema();
|
|
140
|
+
return new ClientExchange({
|
|
127
141
|
execution: this.executionContextService,
|
|
128
142
|
logger: this.loggerService,
|
|
129
143
|
getCandles,
|
|
144
|
+
formatPrice,
|
|
145
|
+
formatQuantity,
|
|
130
146
|
callbacks,
|
|
131
147
|
});
|
|
132
148
|
});
|
|
133
149
|
this.getCandles = async (symbol, interval, limit) => {
|
|
134
|
-
this.loggerService.log("
|
|
150
|
+
this.loggerService.log("exchangeConnectionService getCandles", {
|
|
135
151
|
symbol,
|
|
136
152
|
interval,
|
|
137
153
|
limit,
|
|
138
154
|
});
|
|
139
|
-
return await this.
|
|
155
|
+
return await this.getExchange(symbol).getCandles(symbol, interval, limit);
|
|
140
156
|
};
|
|
141
157
|
this.getAveragePrice = async (symbol) => {
|
|
142
|
-
this.loggerService.log("
|
|
158
|
+
this.loggerService.log("exchangeConnectionService getAveragePrice", {
|
|
159
|
+
symbol,
|
|
160
|
+
});
|
|
161
|
+
return await this.getExchange(symbol).getAveragePrice(symbol);
|
|
162
|
+
};
|
|
163
|
+
this.formatPrice = async (symbol, price) => {
|
|
164
|
+
this.loggerService.log("exchangeConnectionService getAveragePrice", {
|
|
143
165
|
symbol,
|
|
166
|
+
price,
|
|
144
167
|
});
|
|
145
|
-
return await this.
|
|
168
|
+
return await this.getExchange(symbol).formatPrice(symbol, price);
|
|
169
|
+
};
|
|
170
|
+
this.formatQuantity = async (symbol, quantity) => {
|
|
171
|
+
this.loggerService.log("exchangeConnectionService getAveragePrice", {
|
|
172
|
+
symbol,
|
|
173
|
+
quantity,
|
|
174
|
+
});
|
|
175
|
+
return await this.getExchange(symbol).formatQuantity(symbol, quantity);
|
|
146
176
|
};
|
|
147
177
|
}
|
|
148
178
|
}
|
|
@@ -215,7 +245,7 @@ class ClientStrategy {
|
|
|
215
245
|
const when = this.params.execution.context.when;
|
|
216
246
|
const signal = this._pendingSignal;
|
|
217
247
|
// Получаем среднюю цену
|
|
218
|
-
const averagePrice = await this.params.
|
|
248
|
+
const averagePrice = await this.params.exchange.getAveragePrice(symbol);
|
|
219
249
|
this.params.logger.debug("ClientStrategy tick check", {
|
|
220
250
|
symbol,
|
|
221
251
|
averagePrice,
|
|
@@ -288,14 +318,14 @@ class StrategyConnectionService {
|
|
|
288
318
|
this.loggerService = inject(TYPES.loggerService);
|
|
289
319
|
this.executionContextService = inject(TYPES.executionContextService);
|
|
290
320
|
this.strategySchemaService = inject(TYPES.strategySchemaService);
|
|
291
|
-
this.
|
|
321
|
+
this.exchangeConnectionService = inject(TYPES.exchangeConnectionService);
|
|
292
322
|
this.getStrategy = memoize((symbol) => `${symbol}`, (symbol) => {
|
|
293
323
|
const { getSignal, callbacks } = this.strategySchemaService.getSchema();
|
|
294
324
|
return new ClientStrategy({
|
|
295
325
|
symbol,
|
|
296
326
|
execution: this.executionContextService,
|
|
297
327
|
logger: this.loggerService,
|
|
298
|
-
|
|
328
|
+
exchange: this.exchangeConnectionService,
|
|
299
329
|
getSignal,
|
|
300
330
|
callbacks,
|
|
301
331
|
});
|
|
@@ -315,12 +345,12 @@ const ExecutionContextService = scoped(class {
|
|
|
315
345
|
}
|
|
316
346
|
});
|
|
317
347
|
|
|
318
|
-
class
|
|
348
|
+
class ExchangePublicService {
|
|
319
349
|
constructor() {
|
|
320
350
|
this.loggerService = inject(TYPES.loggerService);
|
|
321
|
-
this.
|
|
351
|
+
this.exchangeConnectionService = inject(TYPES.exchangeConnectionService);
|
|
322
352
|
this.getCandles = async (symbol, interval, limit, when, backtest) => {
|
|
323
|
-
this.loggerService.log("
|
|
353
|
+
this.loggerService.log("exchangePublicService getCandles", {
|
|
324
354
|
symbol,
|
|
325
355
|
interval,
|
|
326
356
|
limit,
|
|
@@ -328,20 +358,48 @@ class CandlePublicService {
|
|
|
328
358
|
backtest,
|
|
329
359
|
});
|
|
330
360
|
return await ExecutionContextService.runInContext(async () => {
|
|
331
|
-
return await this.
|
|
361
|
+
return await this.exchangeConnectionService.getCandles(symbol, interval, limit);
|
|
332
362
|
}, {
|
|
333
363
|
when,
|
|
334
364
|
backtest,
|
|
335
365
|
});
|
|
336
366
|
};
|
|
337
367
|
this.getAveragePrice = async (symbol, when, backtest) => {
|
|
338
|
-
this.loggerService.log("
|
|
368
|
+
this.loggerService.log("exchangePublicService getAveragePrice", {
|
|
369
|
+
symbol,
|
|
370
|
+
when,
|
|
371
|
+
backtest,
|
|
372
|
+
});
|
|
373
|
+
return await ExecutionContextService.runInContext(async () => {
|
|
374
|
+
return await this.exchangeConnectionService.getAveragePrice(symbol);
|
|
375
|
+
}, {
|
|
376
|
+
when,
|
|
377
|
+
backtest,
|
|
378
|
+
});
|
|
379
|
+
};
|
|
380
|
+
this.formatPrice = async (symbol, price, when, backtest) => {
|
|
381
|
+
this.loggerService.log("exchangePublicService formatPrice", {
|
|
339
382
|
symbol,
|
|
383
|
+
price,
|
|
340
384
|
when,
|
|
341
385
|
backtest,
|
|
342
386
|
});
|
|
343
387
|
return await ExecutionContextService.runInContext(async () => {
|
|
344
|
-
return await this.
|
|
388
|
+
return await this.exchangeConnectionService.formatPrice(symbol, price);
|
|
389
|
+
}, {
|
|
390
|
+
when,
|
|
391
|
+
backtest,
|
|
392
|
+
});
|
|
393
|
+
};
|
|
394
|
+
this.formatQuantity = async (symbol, quantity, when, backtest) => {
|
|
395
|
+
this.loggerService.log("exchangePublicService formatQuantity", {
|
|
396
|
+
symbol,
|
|
397
|
+
quantity,
|
|
398
|
+
when,
|
|
399
|
+
backtest,
|
|
400
|
+
});
|
|
401
|
+
return await ExecutionContextService.runInContext(async () => {
|
|
402
|
+
return await this.exchangeConnectionService.formatQuantity(symbol, quantity);
|
|
345
403
|
}, {
|
|
346
404
|
when,
|
|
347
405
|
backtest,
|
|
@@ -370,19 +428,19 @@ class StrategyPublicService {
|
|
|
370
428
|
}
|
|
371
429
|
}
|
|
372
430
|
|
|
373
|
-
class
|
|
431
|
+
class ExchangeSchemaService {
|
|
374
432
|
constructor() {
|
|
375
433
|
this.loggerService = inject(TYPES.loggerService);
|
|
376
434
|
this.getSchema = () => {
|
|
377
|
-
this.loggerService.log("
|
|
378
|
-
if (!this.
|
|
379
|
-
throw new Error("
|
|
435
|
+
this.loggerService.log("exchangeSchemaService getSchema");
|
|
436
|
+
if (!this._exchangeSchema) {
|
|
437
|
+
throw new Error("ExchangeSchemaService no exchange source provided");
|
|
380
438
|
}
|
|
381
|
-
return this.
|
|
439
|
+
return this._exchangeSchema;
|
|
382
440
|
};
|
|
383
|
-
this.addSchema = (
|
|
384
|
-
this.loggerService.log("
|
|
385
|
-
this.
|
|
441
|
+
this.addSchema = (exchangeSchema) => {
|
|
442
|
+
this.loggerService.log("exchangeSchemaService addSchema");
|
|
443
|
+
this._exchangeSchema = exchangeSchema;
|
|
386
444
|
};
|
|
387
445
|
}
|
|
388
446
|
}
|
|
@@ -411,15 +469,15 @@ class StrategySchemaService {
|
|
|
411
469
|
provide(TYPES.executionContextService, () => new ExecutionContextService());
|
|
412
470
|
}
|
|
413
471
|
{
|
|
414
|
-
provide(TYPES.
|
|
472
|
+
provide(TYPES.exchangeConnectionService, () => new ExchangeConnectionService());
|
|
415
473
|
provide(TYPES.strategyConnectionService, () => new StrategyConnectionService());
|
|
416
474
|
}
|
|
417
475
|
{
|
|
418
|
-
provide(TYPES.
|
|
476
|
+
provide(TYPES.exchangeSchemaService, () => new ExchangeSchemaService());
|
|
419
477
|
provide(TYPES.strategySchemaService, () => new StrategySchemaService());
|
|
420
478
|
}
|
|
421
479
|
{
|
|
422
|
-
provide(TYPES.
|
|
480
|
+
provide(TYPES.exchangePublicService, () => new ExchangePublicService());
|
|
423
481
|
provide(TYPES.strategyPublicService, () => new StrategyPublicService());
|
|
424
482
|
}
|
|
425
483
|
|
|
@@ -430,15 +488,15 @@ const contextServices = {
|
|
|
430
488
|
executionContextService: inject(TYPES.executionContextService),
|
|
431
489
|
};
|
|
432
490
|
const connectionServices = {
|
|
433
|
-
|
|
491
|
+
exchangeConnectionService: inject(TYPES.exchangeConnectionService),
|
|
434
492
|
strategyConnectionService: inject(TYPES.strategyConnectionService),
|
|
435
493
|
};
|
|
436
494
|
const schemaServices = {
|
|
437
|
-
|
|
495
|
+
exchangeSchemaService: inject(TYPES.exchangeSchemaService),
|
|
438
496
|
strategySchemaService: inject(TYPES.strategySchemaService),
|
|
439
497
|
};
|
|
440
498
|
const publicServices = {
|
|
441
|
-
|
|
499
|
+
exchangePublicService: inject(TYPES.exchangePublicService),
|
|
442
500
|
strategyPublicService: inject(TYPES.strategyPublicService),
|
|
443
501
|
};
|
|
444
502
|
const backtest = {
|
|
@@ -453,8 +511,8 @@ init();
|
|
|
453
511
|
function addStrategy(strategySchema) {
|
|
454
512
|
backtest.strategySchemaService.addSchema(strategySchema);
|
|
455
513
|
}
|
|
456
|
-
function
|
|
457
|
-
backtest.
|
|
514
|
+
function addExchange(exchangeSchema) {
|
|
515
|
+
backtest.exchangeSchemaService.addSchema(exchangeSchema);
|
|
458
516
|
}
|
|
459
517
|
|
|
460
518
|
async function runBacktest(symbol, timeframes) {
|
|
@@ -571,10 +629,16 @@ function stopAll() {
|
|
|
571
629
|
}
|
|
572
630
|
|
|
573
631
|
async function getCandles(symbol, interval, limit) {
|
|
574
|
-
return await backtest.
|
|
632
|
+
return await backtest.exchangeConnectionService.getCandles(symbol, interval, limit);
|
|
575
633
|
}
|
|
576
634
|
async function getAveragePrice(symbol) {
|
|
577
|
-
return await backtest.
|
|
635
|
+
return await backtest.exchangeConnectionService.getAveragePrice(symbol);
|
|
636
|
+
}
|
|
637
|
+
async function formatPrice(symbol, price) {
|
|
638
|
+
return await backtest.exchangeConnectionService.formatPrice(symbol, price);
|
|
639
|
+
}
|
|
640
|
+
async function formatQuantity(symbol, quantity) {
|
|
641
|
+
return await backtest.exchangeConnectionService.formatQuantity(symbol, quantity);
|
|
578
642
|
}
|
|
579
643
|
|
|
580
|
-
export { ExecutionContextService,
|
|
644
|
+
export { ExecutionContextService, addExchange, addStrategy, backtest, formatPrice, formatQuantity, getAveragePrice, getCandles, reduce, runBacktest, runBacktestGUI, startRun, stopAll, stopRun };
|
package/package.json
CHANGED
package/types.d.ts
CHANGED
|
@@ -46,19 +46,23 @@ interface ICandleData {
|
|
|
46
46
|
close: number;
|
|
47
47
|
volume: number;
|
|
48
48
|
}
|
|
49
|
-
interface
|
|
49
|
+
interface IExchangeParams extends IExchangeSchema {
|
|
50
50
|
logger: ILogger;
|
|
51
51
|
execution: TExecutionContextService;
|
|
52
52
|
}
|
|
53
|
-
interface
|
|
53
|
+
interface IExchangeCallbacks {
|
|
54
54
|
onCandleData: (symbol: string, interval: CandleInterval, since: Date, limit: number, data: ICandleData[]) => void;
|
|
55
55
|
}
|
|
56
|
-
interface
|
|
56
|
+
interface IExchangeSchema {
|
|
57
57
|
getCandles: (symbol: string, interval: CandleInterval, since: Date, limit: number) => Promise<ICandleData[]>;
|
|
58
|
-
|
|
58
|
+
formatQuantity: (symbol: string, quantity: number) => Promise<string>;
|
|
59
|
+
formatPrice: (symbol: string, price: number) => Promise<string>;
|
|
60
|
+
callbacks?: Partial<IExchangeCallbacks>;
|
|
59
61
|
}
|
|
60
|
-
interface
|
|
62
|
+
interface IExchange {
|
|
61
63
|
getCandles: (symbol: string, interval: CandleInterval, limit: number) => Promise<ICandleData[]>;
|
|
64
|
+
formatQuantity: (symbol: string, quantity: number) => Promise<string>;
|
|
65
|
+
formatPrice: (symbol: string, price: number) => Promise<string>;
|
|
62
66
|
getAveragePrice: (symbol: string) => Promise<number>;
|
|
63
67
|
}
|
|
64
68
|
|
|
@@ -112,7 +116,7 @@ interface IStrategy {
|
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
declare function addStrategy(strategySchema: IStrategySchema): void;
|
|
115
|
-
declare function
|
|
119
|
+
declare function addExchange(exchangeSchema: IExchangeSchema): void;
|
|
116
120
|
|
|
117
121
|
interface IBacktestResult {
|
|
118
122
|
symbol: string;
|
|
@@ -139,6 +143,8 @@ declare function stopAll(): void;
|
|
|
139
143
|
|
|
140
144
|
declare function getCandles(symbol: string, interval: CandleInterval, limit: number): Promise<ICandleData[]>;
|
|
141
145
|
declare function getAveragePrice(symbol: string): Promise<number>;
|
|
146
|
+
declare function formatPrice(symbol: string, price: number): Promise<string>;
|
|
147
|
+
declare function formatQuantity(symbol: string, quantity: number): Promise<string>;
|
|
142
148
|
|
|
143
149
|
declare class LoggerService implements ILogger {
|
|
144
150
|
private _commonLogger;
|
|
@@ -148,27 +154,31 @@ declare class LoggerService implements ILogger {
|
|
|
148
154
|
setLogger: (logger: ILogger) => void;
|
|
149
155
|
}
|
|
150
156
|
|
|
151
|
-
declare class
|
|
152
|
-
readonly params:
|
|
153
|
-
constructor(params:
|
|
157
|
+
declare class ClientExchange implements IExchange {
|
|
158
|
+
readonly params: IExchangeParams;
|
|
159
|
+
constructor(params: IExchangeParams);
|
|
154
160
|
getCandles: (symbol: string, interval: CandleInterval, limit: number) => Promise<ICandleData[]>;
|
|
155
161
|
getAveragePrice: (symbol: string) => Promise<number>;
|
|
162
|
+
formatQuantity: (symbol: string, quantity: number) => Promise<string>;
|
|
163
|
+
formatPrice: (symbol: string, price: number) => Promise<string>;
|
|
156
164
|
}
|
|
157
165
|
|
|
158
|
-
declare class
|
|
166
|
+
declare class ExchangeConnectionService implements IExchange {
|
|
159
167
|
private readonly loggerService;
|
|
160
168
|
private readonly executionContextService;
|
|
161
|
-
private readonly
|
|
162
|
-
|
|
169
|
+
private readonly exchangeSchemaService;
|
|
170
|
+
getExchange: ((symbol: string) => ClientExchange) & functools_kit.IClearableMemoize<string> & functools_kit.IControlMemoize<string, ClientExchange>;
|
|
163
171
|
getCandles: (symbol: string, interval: CandleInterval, limit: number) => Promise<ICandleData[]>;
|
|
164
172
|
getAveragePrice: (symbol: string) => Promise<number>;
|
|
173
|
+
formatPrice: (symbol: string, price: number) => Promise<string>;
|
|
174
|
+
formatQuantity: (symbol: string, quantity: number) => Promise<string>;
|
|
165
175
|
}
|
|
166
176
|
|
|
167
|
-
declare class
|
|
177
|
+
declare class ExchangeSchemaService {
|
|
168
178
|
private readonly loggerService;
|
|
169
|
-
private
|
|
170
|
-
getSchema: () =>
|
|
171
|
-
addSchema: (
|
|
179
|
+
private _exchangeSchema;
|
|
180
|
+
getSchema: () => IExchangeSchema;
|
|
181
|
+
addSchema: (exchangeSchema: IExchangeSchema) => void;
|
|
172
182
|
}
|
|
173
183
|
|
|
174
184
|
declare class StrategySchemaService {
|
|
@@ -182,16 +192,18 @@ declare class StrategyConnectionService implements IStrategy {
|
|
|
182
192
|
private readonly loggerService;
|
|
183
193
|
private readonly executionContextService;
|
|
184
194
|
private readonly strategySchemaService;
|
|
185
|
-
private readonly
|
|
195
|
+
private readonly exchangeConnectionService;
|
|
186
196
|
private getStrategy;
|
|
187
197
|
tick: (symbol: string) => Promise<IStrategyTickResult>;
|
|
188
198
|
}
|
|
189
199
|
|
|
190
|
-
declare class
|
|
200
|
+
declare class ExchangePublicService {
|
|
191
201
|
private readonly loggerService;
|
|
192
|
-
private readonly
|
|
202
|
+
private readonly exchangeConnectionService;
|
|
193
203
|
getCandles: (symbol: string, interval: CandleInterval, limit: number, when: Date, backtest: boolean) => Promise<ICandleData[]>;
|
|
194
204
|
getAveragePrice: (symbol: string, when: Date, backtest: boolean) => Promise<number>;
|
|
205
|
+
formatPrice: (symbol: string, price: number, when: Date, backtest: boolean) => Promise<string>;
|
|
206
|
+
formatQuantity: (symbol: string, quantity: number, when: Date, backtest: boolean) => Promise<string>;
|
|
195
207
|
}
|
|
196
208
|
|
|
197
209
|
declare class StrategyPublicService {
|
|
@@ -201,11 +213,11 @@ declare class StrategyPublicService {
|
|
|
201
213
|
}
|
|
202
214
|
|
|
203
215
|
declare const backtest: {
|
|
204
|
-
|
|
216
|
+
exchangePublicService: ExchangePublicService;
|
|
205
217
|
strategyPublicService: StrategyPublicService;
|
|
206
|
-
|
|
218
|
+
exchangeSchemaService: ExchangeSchemaService;
|
|
207
219
|
strategySchemaService: StrategySchemaService;
|
|
208
|
-
|
|
220
|
+
exchangeConnectionService: ExchangeConnectionService;
|
|
209
221
|
strategyConnectionService: StrategyConnectionService;
|
|
210
222
|
executionContextService: {
|
|
211
223
|
readonly context: IExecutionContext;
|
|
@@ -213,4 +225,4 @@ declare const backtest: {
|
|
|
213
225
|
loggerService: LoggerService;
|
|
214
226
|
};
|
|
215
227
|
|
|
216
|
-
export { ExecutionContextService,
|
|
228
|
+
export { type CandleInterval, ExecutionContextService, type ICandleData, type IExchangeSchema, type ISignalData, type IStrategyPnL, type IStrategySchema, type IStrategyTickResult, type IStrategyTickResultActive, type IStrategyTickResultClosed, type IStrategyTickResultIdle, type IStrategyTickResultOpened, addExchange, addStrategy, backtest, formatPrice, formatQuantity, getAveragePrice, getCandles, reduce, runBacktest, runBacktestGUI, startRun, stopAll, stopRun };
|