backtest-kit 1.0.0

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.
@@ -0,0 +1,573 @@
1
+ import { createActivator } from 'di-kit';
2
+ import { memoize, singlerun, sleep } from 'functools-kit';
3
+ import { scoped } from 'di-scoped';
4
+ import Table from 'cli-table3';
5
+
6
+ const NOOP_LOGGER = {
7
+ log() {
8
+ },
9
+ debug() {
10
+ },
11
+ info() {
12
+ },
13
+ };
14
+ class LoggerService {
15
+ constructor() {
16
+ this._commonLogger = NOOP_LOGGER;
17
+ this.log = async (topic, ...args) => {
18
+ await this._commonLogger.log(topic, ...args);
19
+ };
20
+ this.debug = async (topic, ...args) => {
21
+ await this._commonLogger.debug(topic, ...args);
22
+ };
23
+ this.info = async (topic, ...args) => {
24
+ await this._commonLogger.info(topic, ...args);
25
+ };
26
+ this.setLogger = (logger) => {
27
+ this._commonLogger = logger;
28
+ };
29
+ }
30
+ }
31
+
32
+ const { init, inject, provide } = createActivator("backtest");
33
+
34
+ const baseServices$1 = {
35
+ loggerService: Symbol('loggerService'),
36
+ };
37
+ const contextServices$1 = {
38
+ executionContextService: Symbol('executionContextService'),
39
+ };
40
+ const connectionServices$1 = {
41
+ candleConnectionService: Symbol('candleConnectionService'),
42
+ strategyConnectionService: Symbol('strategyConnectionService'),
43
+ };
44
+ const schemaServices$1 = {
45
+ candleSchemaService: Symbol('candleSchemaService'),
46
+ strategySchemaService: Symbol('strategySchemaService'),
47
+ };
48
+ const publicServices$1 = {
49
+ candlePublicService: Symbol('candlePublicService'),
50
+ strategyPublicService: Symbol('strategyPublicService'),
51
+ };
52
+ const TYPES = {
53
+ ...baseServices$1,
54
+ ...contextServices$1,
55
+ ...connectionServices$1,
56
+ ...schemaServices$1,
57
+ ...publicServices$1,
58
+ };
59
+
60
+ const INTERVAL_MINUTES = {
61
+ "1m": 1,
62
+ "3m": 3,
63
+ "5m": 5,
64
+ "15m": 15,
65
+ "30m": 30,
66
+ "1h": 60,
67
+ "2h": 120,
68
+ "4h": 240,
69
+ "6h": 360,
70
+ "8h": 480,
71
+ };
72
+ class ClientCandle {
73
+ constructor(params) {
74
+ this.params = params;
75
+ this.getCandles = async (symbol, interval, limit) => {
76
+ this.params.logger.debug(`ClientCandle getCandles`, {
77
+ symbol,
78
+ interval,
79
+ limit,
80
+ });
81
+ const step = INTERVAL_MINUTES[interval];
82
+ const adjust = step * limit;
83
+ if (!adjust) {
84
+ throw new Error(`ClientCandle unknown time adjust for interval=${interval}`);
85
+ }
86
+ const since = new Date(this.params.execution.context.when.getTime() - adjust * 60 * 1000);
87
+ const data = await this.params.getCandles(symbol, interval, since, limit);
88
+ if (this.params.callbacks?.onCandleData) {
89
+ this.params.callbacks.onCandleData(symbol, interval, since, limit, data);
90
+ }
91
+ return data;
92
+ };
93
+ this.getAveragePrice = async (symbol) => {
94
+ this.params.logger.debug(`ClientCandle getAveragePrice`, {
95
+ symbol,
96
+ });
97
+ const candles = await this.getCandles(symbol, "1m", 5);
98
+ if (candles.length === 0) {
99
+ throw new Error(`ClientCandle getAveragePrice: no candles data for symbol=${symbol}`);
100
+ }
101
+ // VWAP (Volume Weighted Average Price)
102
+ // Используем типичную цену (typical price) = (high + low + close) / 3
103
+ const sumPriceVolume = candles.reduce((acc, candle) => {
104
+ const typicalPrice = (candle.high + candle.low + candle.close) / 3;
105
+ return acc + typicalPrice * candle.volume;
106
+ }, 0);
107
+ const totalVolume = candles.reduce((acc, candle) => acc + candle.volume, 0);
108
+ if (totalVolume === 0) {
109
+ // Если объем нулевой, возвращаем простое среднее close цен
110
+ const sum = candles.reduce((acc, candle) => acc + candle.close, 0);
111
+ return sum / candles.length;
112
+ }
113
+ const vwap = sumPriceVolume / totalVolume;
114
+ return vwap;
115
+ };
116
+ }
117
+ }
118
+
119
+ class CandleConnectionService {
120
+ constructor() {
121
+ this.loggerService = inject(TYPES.loggerService);
122
+ this.executionContextService = inject(TYPES.executionContextService);
123
+ this.candleSchemaService = inject(TYPES.candleSchemaService);
124
+ this.getCandle = memoize((symbol) => `${symbol}`, () => {
125
+ const { getCandles, callbacks } = this.candleSchemaService.getSchema();
126
+ return new ClientCandle({
127
+ execution: this.executionContextService,
128
+ logger: this.loggerService,
129
+ getCandles,
130
+ callbacks,
131
+ });
132
+ });
133
+ this.getCandles = async (symbol, interval, limit) => {
134
+ this.loggerService.log("candleConnectionService getCandles", {
135
+ symbol,
136
+ interval,
137
+ limit,
138
+ });
139
+ return await this.getCandle(symbol).getCandles(symbol, interval, limit);
140
+ };
141
+ this.getAveragePrice = async (symbol) => {
142
+ this.loggerService.log("candleConnectionService getAveragePrice", {
143
+ symbol,
144
+ });
145
+ return await this.getCandle(symbol).getAveragePrice(symbol);
146
+ };
147
+ }
148
+ }
149
+
150
+ const PERCENT_SLIPPAGE = 0.1;
151
+ const PERCENT_FEE = 0.1;
152
+ const GET_PNL_FN = (signal, priceClose) => {
153
+ const priceOpen = signal.priceOpen;
154
+ let priceOpenWithSlippage;
155
+ let priceCloseWithSlippage;
156
+ if (signal.position === "long") {
157
+ // LONG: покупаем дороже, продаем дешевле
158
+ priceOpenWithSlippage = priceOpen * (1 + PERCENT_SLIPPAGE / 100);
159
+ priceCloseWithSlippage = priceClose * (1 - PERCENT_SLIPPAGE / 100);
160
+ }
161
+ else {
162
+ // SHORT: продаем дешевле, покупаем дороже
163
+ priceOpenWithSlippage = priceOpen * (1 - PERCENT_SLIPPAGE / 100);
164
+ priceCloseWithSlippage = priceClose * (1 + PERCENT_SLIPPAGE / 100);
165
+ }
166
+ // Применяем комиссию дважды (при открытии и закрытии)
167
+ const totalFee = PERCENT_FEE * 2;
168
+ let pnlPercentage;
169
+ if (signal.position === "long") {
170
+ // LONG: прибыль при росте цены
171
+ pnlPercentage =
172
+ ((priceCloseWithSlippage - priceOpenWithSlippage) /
173
+ priceOpenWithSlippage) *
174
+ 100;
175
+ }
176
+ else {
177
+ // SHORT: прибыль при падении цены
178
+ pnlPercentage =
179
+ ((priceOpenWithSlippage - priceCloseWithSlippage) /
180
+ priceOpenWithSlippage) *
181
+ 100;
182
+ }
183
+ // Вычитаем комиссии
184
+ pnlPercentage -= totalFee;
185
+ return {
186
+ pnlPercentage,
187
+ priceOpen,
188
+ priceClose,
189
+ };
190
+ };
191
+ class ClientStrategy {
192
+ constructor(params) {
193
+ this.params = params;
194
+ this._pendingSignal = null;
195
+ this.tick = async (symbol) => {
196
+ this.params.logger.debug("ClientStrategy tick", {
197
+ symbol,
198
+ });
199
+ if (!this._pendingSignal) {
200
+ this._pendingSignal = await this.params.getSignal(this.params.symbol);
201
+ if (this._pendingSignal) {
202
+ if (this.params.callbacks?.onOpen) {
203
+ this.params.callbacks.onOpen(this.params.execution.context.backtest, symbol, this._pendingSignal);
204
+ }
205
+ return {
206
+ action: "opened",
207
+ signal: this._pendingSignal,
208
+ };
209
+ }
210
+ return {
211
+ action: "idle",
212
+ signal: null,
213
+ };
214
+ }
215
+ const when = this.params.execution.context.when;
216
+ const signal = this._pendingSignal;
217
+ // Получаем среднюю цену
218
+ const averagePrice = await this.params.candle.getAveragePrice(symbol);
219
+ this.params.logger.debug("ClientStrategy tick check", {
220
+ symbol,
221
+ averagePrice,
222
+ signalId: signal.id,
223
+ position: signal.position,
224
+ });
225
+ let shouldClose = false;
226
+ let closeReason;
227
+ // Проверяем истечение времени
228
+ const signalEndTime = signal.timestamp + signal.minuteEstimatedTime * 60 * 1000;
229
+ if (when.getTime() >= signalEndTime) {
230
+ shouldClose = true;
231
+ closeReason = "time_expired";
232
+ }
233
+ // Проверяем достижение TP/SL для long позиции
234
+ if (signal.position === "long") {
235
+ if (averagePrice >= signal.priceTakeProfit) {
236
+ shouldClose = true;
237
+ closeReason = "take_profit";
238
+ }
239
+ else if (averagePrice <= signal.priceStopLoss) {
240
+ shouldClose = true;
241
+ closeReason = "stop_loss";
242
+ }
243
+ }
244
+ // Проверяем достижение TP/SL для short позиции
245
+ if (signal.position === "short") {
246
+ if (averagePrice <= signal.priceTakeProfit) {
247
+ shouldClose = true;
248
+ closeReason = "take_profit";
249
+ }
250
+ else if (averagePrice >= signal.priceStopLoss) {
251
+ shouldClose = true;
252
+ closeReason = "stop_loss";
253
+ }
254
+ }
255
+ // Закрываем сигнал если выполнены условия
256
+ if (shouldClose) {
257
+ const pnl = GET_PNL_FN(signal, averagePrice);
258
+ this.params.logger.debug("ClientStrategy closing", {
259
+ symbol,
260
+ signalId: signal.id,
261
+ reason: closeReason,
262
+ priceClose: averagePrice,
263
+ pnlPercentage: pnl.pnlPercentage,
264
+ });
265
+ if (this.params.callbacks?.onClose) {
266
+ this.params.callbacks.onClose(this.params.execution.context.backtest, symbol, averagePrice, signal);
267
+ }
268
+ this._pendingSignal = null;
269
+ return {
270
+ action: "closed",
271
+ signal: signal,
272
+ currentPrice: averagePrice,
273
+ closeReason: closeReason,
274
+ pnl: pnl,
275
+ };
276
+ }
277
+ return {
278
+ action: "active",
279
+ signal: signal,
280
+ currentPrice: averagePrice,
281
+ };
282
+ };
283
+ }
284
+ }
285
+
286
+ class StrategyConnectionService {
287
+ constructor() {
288
+ this.loggerService = inject(TYPES.loggerService);
289
+ this.executionContextService = inject(TYPES.executionContextService);
290
+ this.strategySchemaService = inject(TYPES.strategySchemaService);
291
+ this.candleConnectionService = inject(TYPES.candleConnectionService);
292
+ this.getStrategy = memoize((symbol) => `${symbol}`, (symbol) => {
293
+ const { getSignal, callbacks } = this.strategySchemaService.getSchema();
294
+ return new ClientStrategy({
295
+ symbol,
296
+ execution: this.executionContextService,
297
+ logger: this.loggerService,
298
+ candle: this.candleConnectionService,
299
+ getSignal,
300
+ callbacks,
301
+ });
302
+ });
303
+ this.tick = async (symbol) => {
304
+ this.loggerService.log("strategyConnectionService tick", {
305
+ symbol,
306
+ });
307
+ return await this.getStrategy(symbol).tick(symbol);
308
+ };
309
+ }
310
+ }
311
+
312
+ const ExecutionContextService = scoped(class {
313
+ constructor(context) {
314
+ this.context = context;
315
+ }
316
+ });
317
+
318
+ class CandlePublicService {
319
+ constructor() {
320
+ this.loggerService = inject(TYPES.loggerService);
321
+ this.candleConnectionService = inject(TYPES.candleConnectionService);
322
+ this.getCandles = async (symbol, interval, limit, when, backtest) => {
323
+ this.loggerService.log("candlePublicService getCandles", {
324
+ symbol,
325
+ interval,
326
+ limit,
327
+ when,
328
+ backtest,
329
+ });
330
+ return await ExecutionContextService.runInContext(async () => {
331
+ return await this.candleConnectionService.getCandles(symbol, interval, limit);
332
+ }, {
333
+ when,
334
+ backtest,
335
+ });
336
+ };
337
+ this.getAveragePrice = async (symbol, when, backtest) => {
338
+ this.loggerService.log("candlePublicService getAveragePrice", {
339
+ symbol,
340
+ when,
341
+ backtest,
342
+ });
343
+ return await ExecutionContextService.runInContext(async () => {
344
+ return await this.candleConnectionService.getAveragePrice(symbol);
345
+ }, {
346
+ when,
347
+ backtest,
348
+ });
349
+ };
350
+ }
351
+ }
352
+
353
+ class StrategyPublicService {
354
+ constructor() {
355
+ this.loggerService = inject(TYPES.loggerService);
356
+ this.strategyConnectionService = inject(TYPES.strategyConnectionService);
357
+ this.tick = async (symbol, when, backtest) => {
358
+ this.loggerService.log("strategyPublicService tick", {
359
+ symbol,
360
+ when,
361
+ backtest,
362
+ });
363
+ return await ExecutionContextService.runInContext(async () => {
364
+ return await this.strategyConnectionService.tick(symbol);
365
+ }, {
366
+ when,
367
+ backtest,
368
+ });
369
+ };
370
+ }
371
+ }
372
+
373
+ class CandleSchemaService {
374
+ constructor() {
375
+ this.loggerService = inject(TYPES.loggerService);
376
+ this.getSchema = () => {
377
+ this.loggerService.log("candleSchemaService getSchema");
378
+ if (!this._candleSchema) {
379
+ throw new Error("CandleSchemaService no candle source provided");
380
+ }
381
+ return this._candleSchema;
382
+ };
383
+ this.addSchema = (candleSchema) => {
384
+ this.loggerService.log("candleSchemaService addSchema");
385
+ this._candleSchema = candleSchema;
386
+ };
387
+ }
388
+ }
389
+
390
+ class StrategySchemaService {
391
+ constructor() {
392
+ this.loggerService = inject(TYPES.loggerService);
393
+ this.getSchema = () => {
394
+ this.loggerService.log("strategySchemaService getSchema");
395
+ if (!this._strategySchema) {
396
+ throw new Error("StrategySchemaService no strategy provided");
397
+ }
398
+ return this._strategySchema;
399
+ };
400
+ this.addSchema = (strategySchema) => {
401
+ this.loggerService.log("strategySchemaService addSchema");
402
+ this._strategySchema = strategySchema;
403
+ };
404
+ }
405
+ }
406
+
407
+ {
408
+ provide(TYPES.loggerService, () => new LoggerService());
409
+ }
410
+ {
411
+ provide(TYPES.executionContextService, () => new ExecutionContextService());
412
+ }
413
+ {
414
+ provide(TYPES.candleConnectionService, () => new CandleConnectionService());
415
+ provide(TYPES.strategyConnectionService, () => new StrategyConnectionService());
416
+ }
417
+ {
418
+ provide(TYPES.candleSchemaService, () => new CandleSchemaService());
419
+ provide(TYPES.strategySchemaService, () => new StrategySchemaService());
420
+ }
421
+ {
422
+ provide(TYPES.candlePublicService, () => new CandlePublicService());
423
+ provide(TYPES.strategyPublicService, () => new StrategyPublicService());
424
+ }
425
+
426
+ const baseServices = {
427
+ loggerService: inject(TYPES.loggerService),
428
+ };
429
+ const contextServices = {
430
+ executionContextService: inject(TYPES.executionContextService),
431
+ };
432
+ const connectionServices = {
433
+ candleConnectionService: inject(TYPES.candleConnectionService),
434
+ strategyConnectionService: inject(TYPES.strategyConnectionService),
435
+ };
436
+ const schemaServices = {
437
+ candleSchemaService: inject(TYPES.candleSchemaService),
438
+ strategySchemaService: inject(TYPES.strategySchemaService),
439
+ };
440
+ const publicServices = {
441
+ candlePublicService: inject(TYPES.candlePublicService),
442
+ strategyPublicService: inject(TYPES.strategyPublicService),
443
+ };
444
+ const backtest = {
445
+ ...baseServices,
446
+ ...contextServices,
447
+ ...connectionServices,
448
+ ...schemaServices,
449
+ ...publicServices,
450
+ };
451
+ init();
452
+
453
+ function addStrategy(strategySchema) {
454
+ backtest.strategySchemaService.addSchema(strategySchema);
455
+ }
456
+ function addCandle(candleSchema) {
457
+ backtest.candleSchemaService.addSchema(candleSchema);
458
+ }
459
+
460
+ async function runBacktest(symbol, timeframes) {
461
+ const results = [];
462
+ for (const when of timeframes) {
463
+ const result = await backtest.strategyPublicService.tick(symbol, when, true);
464
+ // Сохраняем только результаты closed
465
+ if (result.action === "closed") {
466
+ results.push(result);
467
+ }
468
+ }
469
+ return {
470
+ symbol,
471
+ results,
472
+ };
473
+ }
474
+ async function runBacktestGUI(symbol, timeframes) {
475
+ const backtestResult = await runBacktest(symbol, timeframes);
476
+ const { results } = backtestResult;
477
+ // Таблица для терминала
478
+ const table = new Table({
479
+ head: ["#", "Time", "Note", "Price", "Reason", "PNL %"],
480
+ });
481
+ let totalPnl = 0;
482
+ let winCount = 0;
483
+ let lossCount = 0;
484
+ results.forEach((result, index) => {
485
+ if (result.action === "closed") {
486
+ const pnl = result.pnl.pnlPercentage;
487
+ totalPnl += pnl;
488
+ if (pnl > 0)
489
+ winCount++;
490
+ else if (pnl < 0)
491
+ lossCount++;
492
+ const pnlFormatted = pnl > 0 ? `+${pnl.toFixed(2)}%` : `${pnl.toFixed(2)}%`;
493
+ const emoji = pnl > 0 ? "🟢" : pnl < 0 ? "🔴" : "⚪";
494
+ table.push([
495
+ index + 1,
496
+ new Date(result.signal.timestamp).toISOString(),
497
+ result.signal.note,
498
+ result.currentPrice.toFixed(2),
499
+ result.closeReason,
500
+ `${emoji} ${pnlFormatted}`,
501
+ ]);
502
+ }
503
+ });
504
+ // Добавляем статистику
505
+ const closedCount = results.length;
506
+ table.push([]);
507
+ table.push([
508
+ "TOTAL",
509
+ `${closedCount} trades`,
510
+ `Win: ${winCount}`,
511
+ `Loss: ${lossCount}`,
512
+ "-",
513
+ `WR: ${closedCount > 0 ? ((winCount / closedCount) * 100).toFixed(1) : 0}%`,
514
+ `${totalPnl > 0 ? "+" : ""}${totalPnl.toFixed(2)}%`,
515
+ ]);
516
+ console.log("\n");
517
+ console.log(table.toString());
518
+ console.log("\n");
519
+ }
520
+
521
+ async function reduce(symbol, timeframes, callback, initialValue) {
522
+ let accumulator = initialValue;
523
+ for (let i = 0; i < timeframes.length; i++) {
524
+ const when = timeframes[i];
525
+ accumulator = await callback(accumulator, i, when, symbol);
526
+ }
527
+ return {
528
+ symbol,
529
+ accumulator,
530
+ totalTicks: timeframes.length,
531
+ };
532
+ }
533
+
534
+ const instances = new Map();
535
+ function startRun(config) {
536
+ const { symbol, interval } = config;
537
+ // Останавливаем предыдущий инстанс для этого символа
538
+ if (instances.has(symbol)) {
539
+ stopRun(symbol);
540
+ }
541
+ const doWork = singlerun(async () => {
542
+ const now = new Date();
543
+ const result = await backtest.strategyPublicService.tick(symbol, now, false);
544
+ const instance = instances.get(symbol);
545
+ if (instance) {
546
+ instance.tickCount++;
547
+ }
548
+ await sleep(interval);
549
+ return result;
550
+ });
551
+ const intervalId = setInterval(doWork, interval);
552
+ instances.set(symbol, {
553
+ config,
554
+ tickCount: 0,
555
+ intervalId,
556
+ doWork,
557
+ });
558
+ }
559
+ function stopRun(symbol) {
560
+ const instance = instances.get(symbol);
561
+ if (instance) {
562
+ clearInterval(instance.intervalId);
563
+ instances.delete(symbol);
564
+ }
565
+ }
566
+ function stopAll() {
567
+ instances.forEach((instance) => {
568
+ clearInterval(instance.intervalId);
569
+ });
570
+ instances.clear();
571
+ }
572
+
573
+ export { ExecutionContextService, addCandle, addStrategy, backtest, reduce, runBacktest, runBacktestGUI, startRun, stopAll, stopRun };
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "backtest-kit",
3
+ "version": "1.0.0",
4
+ "description": "A TypeScript library for trading system backtest",
5
+ "author": {
6
+ "name": "Petr Tripolsky",
7
+ "email": "tripolskypetr@gmail.com",
8
+ "url": "https://github.com/tripolskypetr"
9
+ },
10
+ "funding": {
11
+ "type": "individual",
12
+ "url": "http://paypal.me/tripolskypetr"
13
+ },
14
+ "license": "MIT",
15
+ "homepage": "https://backtest-kit.github.io/modules.html",
16
+ "keywords": [
17
+ "backtesting",
18
+ "backtest",
19
+ "finance",
20
+ "trading",
21
+ "candles",
22
+ "indicators",
23
+ "multi value",
24
+ "multi symbol",
25
+ "framework"
26
+ ],
27
+ "files": [
28
+ "build",
29
+ "types.d.ts",
30
+ "README.md"
31
+ ],
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/tripolskypetr/backtest-kit",
35
+ "documentation": "https://github.com/tripolskypetr/backtest-kit/tree/master/docs"
36
+ },
37
+ "bugs": {
38
+ "url": "https://github.com/tripolskypetr/backtest-kit/issues"
39
+ },
40
+ "scripts": {
41
+ "build": "rollup -c",
42
+ "build:docs": "rimraf docs && mkdir docs && node ./scripts/dts-docs.cjs ./types.d.ts ./docs",
43
+ "docs:gpt": "npm run build && node ./scripts/gpt-docs.mjs",
44
+ "docs:uml": "npm run build && node ./scripts/uml.mjs",
45
+ "docs:www": "rimraf docs/wwwroot && typedoc",
46
+ "repl": "dotenv -e .env -- npm run build && node -e \"import('./scripts/repl.mjs')\" --interactive"
47
+ },
48
+ "main": "build/index.cjs",
49
+ "module": "build/index.mjs",
50
+ "source": "src/index.ts",
51
+ "types": "./types.d.ts",
52
+ "exports": {
53
+ "require": "./build/index.cjs",
54
+ "types": "./types.d.ts",
55
+ "import": "./build/index.mjs",
56
+ "default": "./build/index.cjs"
57
+ },
58
+ "devDependencies": {
59
+ "@rollup/plugin-typescript": "11.1.6",
60
+ "@types/lodash-es": "4.17.12",
61
+ "@types/node": "22.9.0",
62
+ "@types/xml2js": "0.4.14",
63
+ "glob": "11.0.1",
64
+ "ollama": "0.5.14",
65
+ "rimraf": "6.0.1",
66
+ "rollup": "3.29.5",
67
+ "rollup-plugin-dts": "6.1.1",
68
+ "rollup-plugin-peer-deps-external": "2.2.4",
69
+ "tslib": "2.7.0",
70
+ "typedoc": "0.27.9",
71
+ "undici": "6.21.2",
72
+ "worker-testbed": "1.0.10"
73
+ },
74
+ "peerDependencies": {
75
+ "typescript": "^5.0.0"
76
+ },
77
+ "dependencies": {
78
+ "cli-table3": "^0.6.5",
79
+ "di-kit": "^1.0.18",
80
+ "di-scoped": "^1.0.20",
81
+ "functools-kit": "^1.0.93",
82
+ "get-moment-stamp": "^1.1.1",
83
+ "lodash-es": "4.17.21",
84
+ "xml2js": "0.6.2"
85
+ }
86
+ }