@backtest-kit/ui 4.0.1 → 5.1.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.
package/README.md CHANGED
@@ -67,25 +67,127 @@ setLogger({
67
67
 
68
68
  ## 📐 Dashboard Revenue Math
69
69
 
70
- The **Revenue** metrics on the dashboard are calculated in **dollar terms**, assuming a fixed position size of **$100 per DCA entry**.
70
+ The **Revenue** metrics on the dashboard are calculated in **dollar terms** by summing the `pnlCost` field from all closed signals within each time window.
71
71
 
72
72
  ### Dollar PnL formula
73
73
 
74
74
  ```
75
- dollar_pnl = pnlPercentage × totalEntries
75
+ revenue[window] = Σ signal.pnl.pnlCost (for all closed signals in that window)
76
76
  ```
77
77
 
78
- | `totalEntries` | Effective position | Dollar PnL (example: +5%) |
79
- |:--------------:|-------------------:|-------------------------:|
80
- | 1 | $100 | +$5.00 |
81
- | 2 | $200 | +$10.00 |
82
- | 3 | $300 | +$15.00 |
78
+ `pnlCost` is computed by the backend (`toProfitLossDto`) as:
83
79
 
84
- - **`pnlPercentage`** — percentage PnL as stored in the signal (e.g. `5` = 5 %). It does **not** change with DCA averaging.
85
- - **`totalEntries`** — number of DCA buy entries that were executed for the signal (`IPublicSignalRow.totalEntries`). A regular (non-DCA) signal has `totalEntries = 1`, so the formula degrades to the plain percentage.
86
- - **Fallback** — if `totalEntries` is `0` or absent (legacy data), the raw `pnlPercentage` is used as-is.
80
+ ```
81
+ pnlCost = (pnlPercentage / 100) × pnlEntries
82
+ ```
83
+
84
+ | Field | Source | Description |
85
+ |-------|--------|-------------|
86
+ | `pnl.pnlCost` | `IStorageSignalRow` | Absolute P&L in USD — the only value summed for revenue |
87
+ | `pnl.pnlPercentage` | `IStorageSignalRow` | Percentage P&L (accounts for DCA-weighted entry price, slippage, and fees) |
88
+ | `pnl.pnlEntries` | `IStorageSignalRow` | Total invested capital in USD — sum of all entry costs (`Σ entry.cost`) |
89
+
90
+ **Example** (1 DCA entry at $100, position closed +5%):
91
+
92
+ | DCA entries | `pnlEntries` | `pnlPercentage` | `pnlCost` |
93
+ |:-----------:|-------------:|----------------:|----------:|
94
+ | 1 | $100 | 5 % | +$5.00 |
95
+ | 2 | $200 | 5 % | +$10.00 |
96
+ | 3 | $300 | 5 % | +$15.00 |
97
+
98
+ ### Time windows
99
+
100
+ The anchor point depends on execution mode:
101
+
102
+ - **Backtest mode** — latest `updatedAt` across all closed signals (time windows are relative to the end of the run)
103
+ - **Live mode** — `Date.now()` (wall-clock time)
104
+
105
+ | Window | Range |
106
+ |--------|-------|
107
+ | Today | `>= startOf(anchorDay)` |
108
+ | Yesterday | `[anchorDay − 1d, anchorDay)` |
109
+ | 7 days | `>= anchorDay − 7d` |
110
+ | 31 days | `>= anchorDay − 31d` |
111
+
112
+ Revenue and signal count are tracked separately for each window and aggregated across all symbols on the Dashboard.
113
+
114
+ ## 📐 Position PNL Math
115
+
116
+ ### Effective entry price (DCA-weighted)
117
+
118
+ When multiple DCA entries exist, the effective open price is a **cost-weighted harmonic mean**:
119
+
120
+ ```
121
+ effectivePrice = Σcost / Σ(cost / price)
122
+ ```
123
+
124
+ This is the correct formula for fixed-dollar entries (not simple average), because buying $100 worth at different prices gives different coin quantities.
125
+
126
+ ### Partial closes (PP/PL)
127
+
128
+ Each partial stores a `costBasisAtClose` snapshot — the running dollar cost-basis **before** that partial fired. This avoids replaying the full entry history on every call.
129
+
130
+ **Cost-basis replay:**
131
+
132
+ ```
133
+ for each partial[i]:
134
+ closedDollar += (percent[i] / 100) × costBasisAtClose[i]
135
+ remainingCostBasis = costBasisAtClose[i] × (1 - percent[i] / 100)
136
+
137
+ # DCA entries added AFTER the last partial are appended:
138
+ remainingCostBasis += Σ entry.cost for entries[lastEntryCount..]
139
+
140
+ totalClosedPercent = closedDollar / totalInvested × 100
141
+ ```
142
+
143
+ **Effective price through partials** is computed iteratively so that a partial sell does not change the entry price of the remaining coins:
144
+
145
+ ```
146
+ # partial[0]:
147
+ effPrice = costBasisAtClose[0] / Σ(cost/price for entries[0..cnt[0]])
148
+
149
+ # partial[j]:
150
+ remainingCB = prev.costBasisAtClose × (1 - prev.percent / 100)
151
+ oldCoins = remainingCB / effPrice ← coins still held
152
+ newCoins = Σ(cost/price for DCA entries between j-1 and j)
153
+ effPrice = (remainingCB + newCost) / (oldCoins + newCoins)
154
+ ```
155
+
156
+ ### toProfitLossDto — weighted PNL with slippage & fees
157
+
158
+ **Without partials:**
159
+
160
+ ```
161
+ priceOpenSlip = effectivePrice × (1 ± slippage)
162
+ priceCloseSlip = priceClose × (1 ∓ slippage)
163
+
164
+ pnlPercentage = (priceCloseSlip - priceOpenSlip) / priceOpenSlip × 100
165
+ fee = CC_PERCENT_FEE × (1 + priceCloseSlip / priceOpenSlip)
166
+ pnlPercentage -= fee
167
+ ```
168
+
169
+ **With partials — dollar-weighted sum:**
170
+
171
+ ```
172
+ weight[i] = (percent[i] / 100 × costBasisAtClose[i]) / totalInvested
173
+
174
+ totalWeightedPnl = Σ weight[i] × pnl[i] # each partial at its own effectivePrice
175
+ + remainingWeight × pnlRemaining # rest closed at final priceClose
176
+
177
+ fee = CC_PERCENT_FEE # open (once)
178
+ + Σ CC_PERCENT_FEE × weight[i] × (closeSlip[i] / openSlip[i]) # per partial
179
+ + CC_PERCENT_FEE × remainingWeight × (closeSlip / openSlip) # final close
180
+
181
+ pnlPercentage = totalWeightedPnl - fee
182
+ pnlCost = pnlPercentage / 100 × totalInvested
183
+ ```
87
184
 
88
- This scaling is applied to all time-window revenue fields: *today*, *yesterday*, *7 days*, and *31 days*.
185
+ | Field | Description |
186
+ |-------|-------------|
187
+ | `totalInvested` | `Σ entry.cost` (or `CC_POSITION_ENTRY_COST` if no `_entry`) |
188
+ | `weight[i]` | Real dollar share of each partial relative to `totalInvested` |
189
+ | `effectivePrice` at partial `i` | Computed via iterative `costBasisAtClose` replay up to `partials[i]` |
190
+ | `priceOpen` in result | `getEffectivePriceOpen(signal)` — DCA-weighted harmonic mean across all entries |
89
191
 
90
192
  ## 🖥️ Dashboard Views
91
193
 
package/build/index.cjs CHANGED
@@ -68,12 +68,14 @@ const mockServices$1 = {
68
68
  storageMockService: Symbol("storageMockService"),
69
69
  exchangeMockService: Symbol("exchangeMockService"),
70
70
  logMockService: Symbol("logMockService"),
71
+ statusMockService: Symbol("statusMockService"),
71
72
  };
72
73
  const viewServices$1 = {
73
74
  notificationViewService: Symbol("notificationViewService"),
74
75
  storageViewService: Symbol("storageViewService"),
75
76
  exchangeViewService: Symbol("exchangeViewService"),
76
77
  logViewService: Symbol("logViewService"),
78
+ statusViewService: Symbol("statusViewService"),
77
79
  };
78
80
  const TYPES = {
79
81
  ...baseServices$1,
@@ -176,9 +178,9 @@ class LoggerService {
176
178
  }
177
179
  }
178
180
 
179
- const MOCK_PATH$2 = "./mock/notifications.json";
181
+ const MOCK_PATH$3 = "./mock/notifications.json";
180
182
  const READ_NOTIFICATION_LIST_FN = functoolsKit.singleshot(async () => {
181
- const data = await fs.readFile(MOCK_PATH$2, "utf-8");
183
+ const data = await fs.readFile(MOCK_PATH$3, "utf-8");
182
184
  return JSON.parse(data);
183
185
  });
184
186
  const DEFAULT_LIMIT$3 = 25;
@@ -238,9 +240,9 @@ class NotificationMockService {
238
240
  }
239
241
  }
240
242
 
241
- const MOCK_PATH$1 = "./mock/db";
243
+ const MOCK_PATH$2 = "./mock/db";
242
244
  const READ_BACKTEST_STORAGE_FN = functoolsKit.singleshot(async () => {
243
- const dbPath = path.join(process.cwd(), MOCK_PATH$1);
245
+ const dbPath = path.join(process.cwd(), MOCK_PATH$2);
244
246
  const files = await fs.readdir(dbPath);
245
247
  const signals = [];
246
248
  for (const file of files) {
@@ -275,6 +277,7 @@ class StorageMockService {
275
277
  }
276
278
  }
277
279
 
280
+ const MS_PER_MINUTE$1 = 60000;
278
281
  class ExchangeMockService {
279
282
  constructor() {
280
283
  this.loggerService = inject(TYPES.loggerService);
@@ -298,12 +301,31 @@ class ExchangeMockService {
298
301
  interval,
299
302
  });
300
303
  };
304
+ this.getLiveCandles = async (signalId, interval) => {
305
+ this.loggerService.log("exchangeMockService getLiveCandles", {
306
+ signalId,
307
+ interval,
308
+ });
309
+ const signal = await this.storageMockService.findSignalById(signalId);
310
+ if (!signal) {
311
+ throw new Error(`Signal with ID ${signalId} not found`);
312
+ }
313
+ const { pendingAt, scheduledAt, minuteEstimatedTime, } = signal;
314
+ const eventAt = pendingAt || scheduledAt;
315
+ return await this.exchangeService.getRangeCandles({
316
+ symbol: signal.symbol,
317
+ exchangeName: signal.exchangeName,
318
+ signalStartTime: eventAt,
319
+ signalStopTime: eventAt + minuteEstimatedTime * MS_PER_MINUTE$1,
320
+ interval,
321
+ });
322
+ };
301
323
  }
302
324
  }
303
325
 
304
- const MOCK_PATH = "./mock/logs.json";
326
+ const MOCK_PATH$1 = "./mock/logs.json";
305
327
  const READ_LOG_LIST_FN = functoolsKit.singleshot(async () => {
306
- const data = await fs.readFile(MOCK_PATH, "utf-8");
328
+ const data = await fs.readFile(MOCK_PATH$1, "utf-8");
307
329
  return JSON.parse(data);
308
330
  });
309
331
  const DEFAULT_LIMIT$2 = 25;
@@ -351,6 +373,68 @@ class LogMockService {
351
373
  }
352
374
  }
353
375
 
376
+ const MOCK_PATH = "./mock/status.json";
377
+ const READ_STATUS_LIST_FN = functoolsKit.singleshot(async () => {
378
+ const data = await fs.readFile(MOCK_PATH, "utf-8");
379
+ return JSON.parse(data);
380
+ });
381
+ class StatusMockService {
382
+ constructor() {
383
+ this.loggerService = inject(TYPES.loggerService);
384
+ this.getStatusList = async () => {
385
+ this.loggerService.log("statusMockService getStatusList");
386
+ const list = await READ_STATUS_LIST_FN();
387
+ return list.map(({ id, symbol, strategyName, exchangeName }) => ({
388
+ id,
389
+ symbol,
390
+ strategyName,
391
+ exchangeName,
392
+ status: "pending",
393
+ }));
394
+ };
395
+ this.getStatusMap = async () => {
396
+ this.loggerService.log("statusMockService getStatusMap");
397
+ const list = await this.getStatusList();
398
+ return list.reduce((acm, cur) => ({ ...acm, [cur.id]: cur }), {});
399
+ };
400
+ this.getStatusOne = async (id) => {
401
+ this.loggerService.log("statusMockService getStatusOne", { id });
402
+ const list = await READ_STATUS_LIST_FN();
403
+ const signal = list.find((s) => s.id === id);
404
+ if (!signal) {
405
+ return null;
406
+ }
407
+ const positionEntries = signal._entry ?? [];
408
+ const positionLevels = positionEntries.map((e) => e.price);
409
+ const positionPartials = signal._partial ?? [];
410
+ return {
411
+ signalId: signal.signalId,
412
+ position: signal.position,
413
+ symbol: signal.symbol,
414
+ exchangeName: signal.exchangeName,
415
+ strategyName: signal.strategyName,
416
+ totalEntries: signal.totalEntries,
417
+ totalPartials: signal.totalPartials,
418
+ originalPriceStopLoss: signal.originalPriceStopLoss,
419
+ originalPriceTakeProfit: signal.originalPriceTakeProfit,
420
+ originalPriceOpen: signal.originalPriceOpen,
421
+ priceOpen: signal.priceOpen,
422
+ priceTakeProfit: signal.priceTakeProfit,
423
+ priceStopLoss: signal.priceStopLoss,
424
+ pnlPercentage: signal.pnl.pnlPercentage,
425
+ pnlCost: signal.pnl.pnlCost,
426
+ pnlEntries: signal.pnl.pnlEntries,
427
+ partialExecuted: signal.partialExecuted,
428
+ minuteEstimatedTime: signal.minuteEstimatedTime,
429
+ pendingAt: signal.pendingAt,
430
+ positionLevels,
431
+ positionEntries,
432
+ positionPartials,
433
+ };
434
+ };
435
+ }
436
+ }
437
+
354
438
  const DEFAULT_LIMIT$1 = 25;
355
439
  const DEFAULT_OFFSET$1 = 0;
356
440
  const CREATE_FILTER_LIST_FN$1 = (filterData) => Object.keys(filterData).map((key) => (row) => new RegExp(filterData[key], "i").test(row[key]));
@@ -495,6 +579,7 @@ class StorageViewService {
495
579
  }
496
580
  }
497
581
 
582
+ const MS_PER_MINUTE = 60000;
498
583
  class ExchangeViewService {
499
584
  constructor() {
500
585
  this.loggerService = inject(TYPES.loggerService);
@@ -522,6 +607,28 @@ class ExchangeViewService {
522
607
  interval,
523
608
  });
524
609
  };
610
+ this.getLiveCandles = async (signalId, interval) => {
611
+ this.loggerService.log("exchangeViewService getLiveCandles", {
612
+ signalId,
613
+ interval,
614
+ });
615
+ if (CC_ENABLE_MOCK) {
616
+ return await this.exchangeMockService.getLiveCandles(signalId, interval);
617
+ }
618
+ const signal = await this.storageViewService.findSignalById(signalId);
619
+ if (!signal) {
620
+ throw new Error(`Signal with ID ${signalId} not found`);
621
+ }
622
+ const { pendingAt, scheduledAt, minuteEstimatedTime, } = signal;
623
+ const eventAt = pendingAt || scheduledAt;
624
+ return await this.exchangeService.getRangeCandles({
625
+ symbol: signal.symbol,
626
+ exchangeName: signal.exchangeName,
627
+ signalStartTime: eventAt,
628
+ signalStopTime: eventAt + minuteEstimatedTime * MS_PER_MINUTE,
629
+ interval,
630
+ });
631
+ };
525
632
  }
526
633
  }
527
634
 
@@ -580,6 +687,100 @@ class LogViewService {
580
687
  }
581
688
  }
582
689
 
690
+ class StatusViewService {
691
+ constructor() {
692
+ this.loggerService = inject(TYPES.loggerService);
693
+ this.statusMockService = inject(TYPES.statusMockService);
694
+ this.getStatusList = async () => {
695
+ this.loggerService.log("statusViewService getStatusList");
696
+ if (CC_ENABLE_MOCK) {
697
+ const liveList = await this.statusMockService.getStatusList();
698
+ return liveList.filter(({ status }) => status === "pending");
699
+ }
700
+ return await backtestKit.Live.list();
701
+ };
702
+ this.getStatusMap = async () => {
703
+ this.loggerService.log("statusViewService getStatusMap");
704
+ if (CC_ENABLE_MOCK) {
705
+ return await this.statusMockService.getStatusMap();
706
+ }
707
+ const liveList = await backtestKit.Live.list();
708
+ return liveList
709
+ .filter(({ status }) => status === "pending")
710
+ .reduce((acm, cur) => ({ ...acm, [cur.id]: cur }), {});
711
+ };
712
+ this.getStatusOne = async (id) => {
713
+ this.loggerService.log("statusViewService getStatusOne", {
714
+ id,
715
+ });
716
+ if (CC_ENABLE_MOCK) {
717
+ return await this.statusMockService.getStatusOne(id);
718
+ }
719
+ const liveList = await backtestKit.Live.list();
720
+ const liveOne = liveList.find((live) => live.id === id);
721
+ if (!liveOne) {
722
+ throw new Error(`Live with id ${id} not found`);
723
+ }
724
+ const { symbol, strategyName, exchangeName } = liveOne;
725
+ const currentPrice = await backtestKit.Exchange.getAveragePrice(symbol, {
726
+ exchangeName,
727
+ });
728
+ const pendingSignal = await backtestKit.Live.getPendingSignal(symbol, currentPrice, {
729
+ strategyName,
730
+ exchangeName,
731
+ });
732
+ if (!pendingSignal) {
733
+ return null;
734
+ }
735
+ const positionLevels = await backtestKit.Live.getPositionLevels(symbol, {
736
+ strategyName,
737
+ exchangeName,
738
+ });
739
+ if (!positionLevels) {
740
+ return null;
741
+ }
742
+ const positionEntries = await backtestKit.Live.getPositionEntries(symbol, {
743
+ strategyName,
744
+ exchangeName,
745
+ });
746
+ if (!positionEntries) {
747
+ return null;
748
+ }
749
+ const positionPartials = await backtestKit.Live.getPositionPartials(symbol, {
750
+ strategyName,
751
+ exchangeName,
752
+ });
753
+ if (!positionPartials) {
754
+ return null;
755
+ }
756
+ return {
757
+ signalId: pendingSignal.id,
758
+ position: pendingSignal.position,
759
+ symbol: pendingSignal.symbol,
760
+ exchangeName: pendingSignal.exchangeName,
761
+ strategyName: pendingSignal.strategyName,
762
+ totalEntries: pendingSignal.totalEntries,
763
+ totalPartials: pendingSignal.totalPartials,
764
+ originalPriceStopLoss: pendingSignal.originalPriceStopLoss,
765
+ originalPriceTakeProfit: pendingSignal.originalPriceTakeProfit,
766
+ originalPriceOpen: pendingSignal.originalPriceOpen,
767
+ priceOpen: pendingSignal.priceOpen,
768
+ priceTakeProfit: pendingSignal.priceTakeProfit,
769
+ priceStopLoss: pendingSignal.priceStopLoss,
770
+ pnlPercentage: pendingSignal.pnl.pnlPercentage,
771
+ pnlCost: pendingSignal.pnl.pnlCost,
772
+ pnlEntries: pendingSignal.pnl.pnlEntries,
773
+ partialExecuted: pendingSignal.partialExecuted,
774
+ pendingAt: pendingSignal.pendingAt,
775
+ minuteEstimatedTime: pendingSignal.minuteEstimatedTime,
776
+ positionEntries,
777
+ positionLevels,
778
+ positionPartials,
779
+ };
780
+ };
781
+ }
782
+ }
783
+
583
784
  const symbol_list = [
584
785
  {
585
786
  icon: "/icon/btc.png",
@@ -705,22 +906,6 @@ class SymbolMetaService {
705
906
  }
706
907
  }
707
908
 
708
- const PRICE_TIMEOUT = 120000;
709
- const CREATE_KEY_FN = (symbol, strategyName, exchangeName, frameName, backtest) => {
710
- const parts = [symbol, strategyName, exchangeName];
711
- if (frameName)
712
- parts.push(frameName);
713
- parts.push(backtest ? "backtest" : "live");
714
- return parts.join(":");
715
- };
716
- const GET_SUBJECT_FN = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
717
- const GET_PRICE_FN = async (symbol, strategyName, exchangeName, frameName, backtest) => {
718
- const priceSubject = GET_SUBJECT_FN(symbol, strategyName, exchangeName, frameName, backtest);
719
- if (priceSubject.data) {
720
- return priceSubject.data;
721
- }
722
- return await functoolsKit.waitForNext(priceSubject, (data) => !!data, PRICE_TIMEOUT);
723
- };
724
909
  class PriceConnectionService {
725
910
  constructor() {
726
911
  this.loggerService = inject(TYPES.loggerService);
@@ -732,19 +917,8 @@ class PriceConnectionService {
732
917
  frameName,
733
918
  backtest,
734
919
  });
735
- const currentPrice = await GET_PRICE_FN(symbol, strategyName, exchangeName, frameName, backtest);
736
- if (typeof currentPrice === "symbol") {
737
- throw new Error(`Price for ${CREATE_KEY_FN(symbol, strategyName, exchangeName, frameName, backtest)} not received within timeout`);
738
- }
739
- return currentPrice;
920
+ return await backtestKit.lib.priceMetaService.getCurrentPrice(symbol, { strategyName, exchangeName, frameName }, backtest);
740
921
  };
741
- this.init = functoolsKit.singleshot(async () => {
742
- this.loggerService.log("priceConnectionService init");
743
- backtestKit.listenSignal((event) => {
744
- const priceSubject = GET_SUBJECT_FN(event.symbol, event.strategyName, event.exchangeName, event.frameName, event.backtest);
745
- event.currentPrice && priceSubject.next(event.currentPrice);
746
- });
747
- });
748
922
  }
749
923
  }
750
924
 
@@ -764,12 +938,14 @@ class PriceConnectionService {
764
938
  provide(TYPES.storageMockService, () => new StorageMockService());
765
939
  provide(TYPES.exchangeMockService, () => new ExchangeMockService());
766
940
  provide(TYPES.logMockService, () => new LogMockService());
941
+ provide(TYPES.statusMockService, () => new StatusMockService());
767
942
  }
768
943
  {
769
944
  provide(TYPES.notificationViewService, () => new NotificationViewService());
770
945
  provide(TYPES.storageViewService, () => new StorageViewService());
771
946
  provide(TYPES.exchangeViewService, () => new ExchangeViewService());
772
947
  provide(TYPES.logViewService, () => new LogViewService());
948
+ provide(TYPES.statusViewService, () => new StatusViewService());
773
949
  }
774
950
 
775
951
  const baseServices = {
@@ -788,12 +964,14 @@ const mockServices = {
788
964
  storageMockService: inject(TYPES.storageMockService),
789
965
  exchangeMockService: inject(TYPES.exchangeMockService),
790
966
  logMockService: inject(TYPES.logMockService),
967
+ statusMockService: inject(TYPES.statusMockService),
791
968
  };
792
969
  const viewServices = {
793
970
  notificationViewService: inject(TYPES.notificationViewService),
794
971
  storageViewService: inject(TYPES.storageViewService),
795
972
  exchangeViewService: inject(TYPES.exchangeViewService),
796
973
  logViewService: inject(TYPES.logViewService),
974
+ statusViewService: inject(TYPES.statusViewService),
797
975
  };
798
976
  const ioc = {
799
977
  ...baseServices,
@@ -1127,6 +1305,92 @@ router$6.post("/api/v1/mock/log_filter", async (req, res) => {
1127
1305
  });
1128
1306
  }
1129
1307
  });
1308
+ router$6.post("/api/v1/mock/candles_live", async (req, res) => {
1309
+ try {
1310
+ const request = await micro.json(req);
1311
+ const { signalId, interval, requestId, serviceName } = request;
1312
+ const data = await ioc.exchangeMockService.getLiveCandles(signalId, interval);
1313
+ const result = {
1314
+ data,
1315
+ status: "ok",
1316
+ error: "",
1317
+ requestId,
1318
+ serviceName,
1319
+ };
1320
+ ioc.loggerService.log("/api/v1/mock/candles_live ok", {
1321
+ request,
1322
+ result: omit(result, "data"),
1323
+ });
1324
+ return await micro.send(res, 200, result);
1325
+ }
1326
+ catch (error) {
1327
+ ioc.loggerService.log("/api/v1/mock/candles_live error", {
1328
+ error: functoolsKit.errorData(error),
1329
+ });
1330
+ return await micro.send(res, 200, {
1331
+ status: "error",
1332
+ error: functoolsKit.getErrorMessage(error),
1333
+ });
1334
+ }
1335
+ });
1336
+ // StatusMockService endpoints
1337
+ router$6.post("/api/v1/mock/status_list", async (req, res) => {
1338
+ try {
1339
+ const request = await micro.json(req);
1340
+ const { requestId, serviceName } = request;
1341
+ const data = await ioc.statusMockService.getStatusList();
1342
+ const result = {
1343
+ data,
1344
+ status: "ok",
1345
+ error: "",
1346
+ requestId,
1347
+ serviceName,
1348
+ };
1349
+ ioc.loggerService.log("/api/v1/mock/status_list ok", {
1350
+ request,
1351
+ result: omit(result, "data"),
1352
+ });
1353
+ return await micro.send(res, 200, result);
1354
+ }
1355
+ catch (error) {
1356
+ ioc.loggerService.log("/api/v1/mock/status_list error", {
1357
+ error: functoolsKit.errorData(error),
1358
+ });
1359
+ return await micro.send(res, 200, {
1360
+ status: "error",
1361
+ error: functoolsKit.getErrorMessage(error),
1362
+ });
1363
+ }
1364
+ });
1365
+ router$6.post("/api/v1/mock/status_one/:id", async (req, res) => {
1366
+ try {
1367
+ const request = await micro.json(req);
1368
+ const { requestId, serviceName } = request;
1369
+ const id = req.params.id;
1370
+ const data = await ioc.statusMockService.getStatusOne(id);
1371
+ const result = {
1372
+ data,
1373
+ status: "ok",
1374
+ error: "",
1375
+ requestId,
1376
+ serviceName,
1377
+ };
1378
+ ioc.loggerService.log("/api/v1/mock/status_one/:id ok", {
1379
+ request,
1380
+ result: omit(result, "data"),
1381
+ });
1382
+ return await micro.send(res, 200, result);
1383
+ }
1384
+ catch (error) {
1385
+ ioc.loggerService.log("/api/v1/mock/status_one/:id error", {
1386
+ error: functoolsKit.errorData(error),
1387
+ });
1388
+ return await micro.send(res, 200, {
1389
+ status: "error",
1390
+ error: functoolsKit.getErrorMessage(error),
1391
+ });
1392
+ }
1393
+ });
1130
1394
 
1131
1395
  const router$5 = Router({
1132
1396
  params: true,
@@ -1193,6 +1457,34 @@ router$5.post("/api/v1/view/candles_point", async (req, res) => {
1193
1457
  });
1194
1458
  }
1195
1459
  });
1460
+ router$5.post("/api/v1/view/candles_live", async (req, res) => {
1461
+ try {
1462
+ const request = await micro.json(req);
1463
+ const { signalId, interval, requestId, serviceName } = request;
1464
+ const data = await ioc.exchangeViewService.getLiveCandles(signalId, interval);
1465
+ const result = {
1466
+ data,
1467
+ status: "ok",
1468
+ error: "",
1469
+ requestId,
1470
+ serviceName,
1471
+ };
1472
+ ioc.loggerService.log("/api/v1/view/candles_live ok", {
1473
+ request,
1474
+ result: omit(result, "data"),
1475
+ });
1476
+ return await micro.send(res, 200, result);
1477
+ }
1478
+ catch (error) {
1479
+ ioc.loggerService.log("/api/v1/view/candles_live error", {
1480
+ error: functoolsKit.errorData(error),
1481
+ });
1482
+ return await micro.send(res, 200, {
1483
+ status: "error",
1484
+ error: functoolsKit.getErrorMessage(error),
1485
+ });
1486
+ }
1487
+ });
1196
1488
  // NotificationViewService endpoints
1197
1489
  router$5.post("/api/v1/view/notification_list", async (req, res) => {
1198
1490
  try {
@@ -1451,6 +1743,64 @@ router$5.post("/api/v1/view/log_filter", async (req, res) => {
1451
1743
  });
1452
1744
  }
1453
1745
  });
1746
+ // StatusViewService endpoints
1747
+ router$5.post("/api/v1/view/status_list", async (req, res) => {
1748
+ try {
1749
+ const request = await micro.json(req);
1750
+ const { requestId, serviceName } = request;
1751
+ const data = await ioc.statusViewService.getStatusList();
1752
+ const result = {
1753
+ data,
1754
+ status: "ok",
1755
+ error: "",
1756
+ requestId,
1757
+ serviceName,
1758
+ };
1759
+ ioc.loggerService.log("/api/v1/view/status_list ok", {
1760
+ request,
1761
+ result: omit(result, "data"),
1762
+ });
1763
+ return await micro.send(res, 200, result);
1764
+ }
1765
+ catch (error) {
1766
+ ioc.loggerService.log("/api/v1/view/status_list error", {
1767
+ error: functoolsKit.errorData(error),
1768
+ });
1769
+ return await micro.send(res, 200, {
1770
+ status: "error",
1771
+ error: functoolsKit.getErrorMessage(error),
1772
+ });
1773
+ }
1774
+ });
1775
+ router$5.post("/api/v1/view/status_one/:id", async (req, res) => {
1776
+ try {
1777
+ const request = await micro.json(req);
1778
+ const { requestId, serviceName } = request;
1779
+ const id = req.params.id;
1780
+ const data = await ioc.statusViewService.getStatusOne(id);
1781
+ const result = {
1782
+ data,
1783
+ status: "ok",
1784
+ error: "",
1785
+ requestId,
1786
+ serviceName,
1787
+ };
1788
+ ioc.loggerService.log("/api/v1/view/status_one/:id ok", {
1789
+ request,
1790
+ result: omit(result, "data"),
1791
+ });
1792
+ return await micro.send(res, 200, result);
1793
+ }
1794
+ catch (error) {
1795
+ ioc.loggerService.log("/api/v1/view/status_one/:id error", {
1796
+ error: functoolsKit.errorData(error),
1797
+ });
1798
+ return await micro.send(res, 200, {
1799
+ status: "error",
1800
+ error: functoolsKit.getErrorMessage(error),
1801
+ });
1802
+ }
1803
+ });
1454
1804
 
1455
1805
  const require$2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
1456
1806
  function getModulesPath() {