backtest-kit 2.2.9 → 2.2.11

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/build/index.mjs CHANGED
@@ -711,6 +711,11 @@ const PERSIST_BASE_METHOD_NAME_READ_VALUE = "PersistBase.readValue";
711
711
  const PERSIST_BASE_METHOD_NAME_WRITE_VALUE = "PersistBase.writeValue";
712
712
  const PERSIST_BASE_METHOD_NAME_HAS_VALUE = "PersistBase.hasValue";
713
713
  const PERSIST_BASE_METHOD_NAME_KEYS = "PersistBase.keys";
714
+ const PERSIST_STORAGE_UTILS_METHOD_NAME_READ_DATA = "PersistStorageUtils.readStorageData";
715
+ const PERSIST_STORAGE_UTILS_METHOD_NAME_WRITE_DATA = "PersistStorageUtils.writeStorageData";
716
+ const PERSIST_STORAGE_UTILS_METHOD_NAME_USE_JSON = "PersistStorageUtils.useJson";
717
+ const PERSIST_STORAGE_UTILS_METHOD_NAME_USE_DUMMY = "PersistStorageUtils.useDummy";
718
+ const PERSIST_STORAGE_UTILS_METHOD_NAME_USE_PERSIST_STORAGE_ADAPTER = "PersistStorageUtils.usePersistStorageAdapter";
714
719
  const BASE_WAIT_FOR_INIT_FN_METHOD_NAME = "PersistBase.waitForInitFn";
715
720
  const BASE_UNLINK_RETRY_COUNT = 5;
716
721
  const BASE_UNLINK_RETRY_DELAY = 1000;
@@ -929,7 +934,7 @@ class PersistDummy {
929
934
  class PersistSignalUtils {
930
935
  constructor() {
931
936
  this.PersistSignalFactory = PersistBase;
932
- this.getSignalStorage = memoize(([symbol, strategyName, exchangeName]) => `${symbol}:${strategyName}:${exchangeName}`, (symbol, strategyName, exchangeName) => Reflect.construct(this.PersistSignalFactory, [
937
+ this.getStorage = memoize(([symbol, strategyName, exchangeName]) => `${symbol}:${strategyName}:${exchangeName}`, (symbol, strategyName, exchangeName) => Reflect.construct(this.PersistSignalFactory, [
933
938
  `${symbol}_${strategyName}_${exchangeName}`,
934
939
  `./dump/data/signal/`,
935
940
  ]));
@@ -947,8 +952,8 @@ class PersistSignalUtils {
947
952
  this.readSignalData = async (symbol, strategyName, exchangeName) => {
948
953
  bt.loggerService.info(PERSIST_SIGNAL_UTILS_METHOD_NAME_READ_DATA);
949
954
  const key = `${symbol}:${strategyName}:${exchangeName}`;
950
- const isInitial = !this.getSignalStorage.has(key);
951
- const stateStorage = this.getSignalStorage(symbol, strategyName, exchangeName);
955
+ const isInitial = !this.getStorage.has(key);
956
+ const stateStorage = this.getStorage(symbol, strategyName, exchangeName);
952
957
  await stateStorage.waitForInit(isInitial);
953
958
  if (await stateStorage.hasValue(symbol)) {
954
959
  return await stateStorage.readValue(symbol);
@@ -970,8 +975,8 @@ class PersistSignalUtils {
970
975
  this.writeSignalData = async (signalRow, symbol, strategyName, exchangeName) => {
971
976
  bt.loggerService.info(PERSIST_SIGNAL_UTILS_METHOD_NAME_WRITE_DATA);
972
977
  const key = `${symbol}:${strategyName}:${exchangeName}`;
973
- const isInitial = !this.getSignalStorage.has(key);
974
- const stateStorage = this.getSignalStorage(symbol, strategyName, exchangeName);
978
+ const isInitial = !this.getStorage.has(key);
979
+ const stateStorage = this.getStorage(symbol, strategyName, exchangeName);
975
980
  await stateStorage.waitForInit(isInitial);
976
981
  await stateStorage.writeValue(symbol, signalRow);
977
982
  };
@@ -1659,6 +1664,101 @@ class PersistCandleUtils {
1659
1664
  * ```
1660
1665
  */
1661
1666
  const PersistCandleAdapter = new PersistCandleUtils();
1667
+ /**
1668
+ * Utility class for managing signal storage persistence.
1669
+ *
1670
+ * Features:
1671
+ * - Memoized storage instances
1672
+ * - Custom adapter support
1673
+ * - Atomic read/write operations for StorageData
1674
+ * - Each signal stored as separate file keyed by id
1675
+ * - Crash-safe signal state management
1676
+ *
1677
+ * Used by SignalLiveUtils for live mode persistence of signals.
1678
+ */
1679
+ class PersistStorageUtils {
1680
+ constructor() {
1681
+ this.PersistStorageFactory = PersistBase;
1682
+ this.getStorageStorage = memoize(([backtest]) => backtest ? `backtest` : `live`, (backtest) => Reflect.construct(this.PersistStorageFactory, [
1683
+ backtest ? `backtest` : `live`,
1684
+ `./dump/data/signal-storage/`,
1685
+ ]));
1686
+ /**
1687
+ * Reads persisted signals data.
1688
+ *
1689
+ * Called by StorageLiveUtils/StorageBacktestUtils.waitForInit() to restore state.
1690
+ * Uses keys() from PersistBase to iterate over all stored signals.
1691
+ * Returns empty array if no signals exist.
1692
+ *
1693
+ * @param backtest - If true, reads from backtest storage; otherwise from live storage
1694
+ * @returns Promise resolving to array of signal entries
1695
+ */
1696
+ this.readStorageData = async (backtest) => {
1697
+ bt.loggerService.info(PERSIST_STORAGE_UTILS_METHOD_NAME_READ_DATA);
1698
+ const key = backtest ? `backtest` : `live`;
1699
+ const isInitial = !this.getStorageStorage.has(key);
1700
+ const stateStorage = this.getStorageStorage(backtest);
1701
+ await stateStorage.waitForInit(isInitial);
1702
+ const signals = [];
1703
+ for await (const signalId of stateStorage.keys()) {
1704
+ const signal = await stateStorage.readValue(signalId);
1705
+ signals.push(signal);
1706
+ }
1707
+ return signals;
1708
+ };
1709
+ /**
1710
+ * Writes signal data to disk with atomic file writes.
1711
+ *
1712
+ * Called by StorageLiveUtils/StorageBacktestUtils after signal changes to persist state.
1713
+ * Uses signal.id as the storage key for individual file storage.
1714
+ * Uses atomic writes to prevent corruption on crashes.
1715
+ *
1716
+ * @param signalData - Signal entries to persist
1717
+ * @param backtest - If true, writes to backtest storage; otherwise to live storage
1718
+ * @returns Promise that resolves when write is complete
1719
+ */
1720
+ this.writeStorageData = async (signalData, backtest) => {
1721
+ bt.loggerService.info(PERSIST_STORAGE_UTILS_METHOD_NAME_WRITE_DATA);
1722
+ const key = backtest ? `backtest` : `live`;
1723
+ const isInitial = !this.getStorageStorage.has(key);
1724
+ const stateStorage = this.getStorageStorage(backtest);
1725
+ await stateStorage.waitForInit(isInitial);
1726
+ for (const signal of signalData) {
1727
+ await stateStorage.writeValue(signal.id, signal);
1728
+ }
1729
+ };
1730
+ }
1731
+ /**
1732
+ * Registers a custom persistence adapter.
1733
+ *
1734
+ * @param Ctor - Custom PersistBase constructor
1735
+ */
1736
+ usePersistStorageAdapter(Ctor) {
1737
+ bt.loggerService.info(PERSIST_STORAGE_UTILS_METHOD_NAME_USE_PERSIST_STORAGE_ADAPTER);
1738
+ this.PersistStorageFactory = Ctor;
1739
+ }
1740
+ /**
1741
+ * Switches to the default JSON persist adapter.
1742
+ * All future persistence writes will use JSON storage.
1743
+ */
1744
+ useJson() {
1745
+ bt.loggerService.log(PERSIST_STORAGE_UTILS_METHOD_NAME_USE_JSON);
1746
+ this.usePersistStorageAdapter(PersistBase);
1747
+ }
1748
+ /**
1749
+ * Switches to a dummy persist adapter that discards all writes.
1750
+ * All future persistence writes will be no-ops.
1751
+ */
1752
+ useDummy() {
1753
+ bt.loggerService.log(PERSIST_STORAGE_UTILS_METHOD_NAME_USE_DUMMY);
1754
+ this.usePersistStorageAdapter(PersistDummy);
1755
+ }
1756
+ }
1757
+ /**
1758
+ * Global singleton instance of PersistStorageUtils.
1759
+ * Used by SignalLiveUtils for signal storage persistence.
1760
+ */
1761
+ const PersistStorageAdapter = new PersistStorageUtils();
1662
1762
 
1663
1763
  const MS_PER_MINUTE$1 = 60000;
1664
1764
  const INTERVAL_MINUTES$4 = {
@@ -12087,6 +12187,7 @@ class BacktestLogicPrivateService {
12087
12187
  if (result.action === "scheduled") {
12088
12188
  const signalStartTime = performance.now();
12089
12189
  const signal = result.signal;
12190
+ yield result;
12090
12191
  this.loggerService.info("backtestLogicPrivateService scheduled signal detected", {
12091
12192
  symbol,
12092
12193
  signalId: signal.id,
@@ -12230,6 +12331,7 @@ class BacktestLogicPrivateService {
12230
12331
  if (result.action === "opened") {
12231
12332
  const signalStartTime = performance.now();
12232
12333
  const signal = result.signal;
12334
+ yield result;
12233
12335
  this.loggerService.info("backtestLogicPrivateService signal opened", {
12234
12336
  symbol,
12235
12337
  signalId: signal.id,
@@ -31427,6 +31529,480 @@ class ConstantUtils {
31427
31529
  */
31428
31530
  const Constant = new ConstantUtils();
31429
31531
 
31532
+ const MAX_SIGNALS = 25;
31533
+ const STORAGE_BACKTEST_METHOD_NAME_WAIT_FOR_INIT = "StorageBacktestUtils.waitForInit";
31534
+ const STORAGE_BACKTEST_METHOD_NAME_UPDATE_STORAGE = "StorageBacktestUtils._updateStorage";
31535
+ const STORAGE_BACKTEST_METHOD_NAME_HANDLE_OPENED = "StorageBacktestUtils.handleOpened";
31536
+ const STORAGE_BACKTEST_METHOD_NAME_HANDLE_CLOSED = "StorageBacktestUtils.handleClosed";
31537
+ const STORAGE_BACKTEST_METHOD_NAME_HANDLE_SCHEDULED = "StorageBacktestUtils.handleScheduled";
31538
+ const STORAGE_BACKTEST_METHOD_NAME_HANDLE_CANCELLED = "StorageBacktestUtils.handleCancelled";
31539
+ const STORAGE_BACKTEST_METHOD_NAME_FIND_BY_ID = "StorageBacktestUtils.findById";
31540
+ const STORAGE_BACKTEST_METHOD_NAME_LIST = "StorageBacktestUtils.list";
31541
+ const STORAGE_LIVE_METHOD_NAME_WAIT_FOR_INIT = "StorageLiveUtils.waitForInit";
31542
+ const STORAGE_LIVE_METHOD_NAME_UPDATE_STORAGE = "StorageLiveUtils._updateStorage";
31543
+ const STORAGE_LIVE_METHOD_NAME_HANDLE_OPENED = "StorageLiveUtils.handleOpened";
31544
+ const STORAGE_LIVE_METHOD_NAME_HANDLE_CLOSED = "StorageLiveUtils.handleClosed";
31545
+ const STORAGE_LIVE_METHOD_NAME_HANDLE_SCHEDULED = "StorageLiveUtils.handleScheduled";
31546
+ const STORAGE_LIVE_METHOD_NAME_HANDLE_CANCELLED = "StorageLiveUtils.handleCancelled";
31547
+ const STORAGE_LIVE_METHOD_NAME_FIND_BY_ID = "StorageLiveUtils.findById";
31548
+ const STORAGE_LIVE_METHOD_NAME_LIST = "StorageLiveUtils.list";
31549
+ const STORAGE_ADAPTER_METHOD_NAME_ENABLE = "StorageAdapter.enable";
31550
+ const STORAGE_ADAPTER_METHOD_NAME_DISABLE = "StorageAdapter.disable";
31551
+ const STORAGE_ADAPTER_METHOD_NAME_FIND_SIGNAL_BY_ID = "StorageAdapter.findSignalById";
31552
+ const STORAGE_ADAPTER_METHOD_NAME_LIST_SIGNAL_BACKTEST = "StorageAdapter.listSignalBacktest";
31553
+ const STORAGE_ADAPTER_METHOD_NAME_LIST_SIGNAL_LIVE = "StorageAdapter.listSignalLive";
31554
+ /**
31555
+ * Utility class for managing backtest signal history.
31556
+ *
31557
+ * Stores trading signal history for admin dashboard display during backtesting
31558
+ * with automatic initialization, deduplication, and storage limits.
31559
+ *
31560
+ * @example
31561
+ * ```typescript
31562
+ * import { StorageBacktestUtils } from "./classes/Storage";
31563
+ *
31564
+ * const storage = new StorageBacktestUtils();
31565
+ *
31566
+ * // Handle signal events
31567
+ * await storage.handleOpened(tickResult);
31568
+ * await storage.handleClosed(tickResult);
31569
+ *
31570
+ * // Query signals
31571
+ * const signal = await storage.findById("signal-123");
31572
+ * const allSignals = await storage.list();
31573
+ * ```
31574
+ */
31575
+ class StorageBacktestUtils {
31576
+ constructor() {
31577
+ /**
31578
+ * Initializes storage by loading existing signal history from persist layer.
31579
+ * Uses singleshot to ensure initialization happens only once.
31580
+ */
31581
+ this.waitForInit = singleshot(async () => {
31582
+ bt.loggerService.info(STORAGE_BACKTEST_METHOD_NAME_WAIT_FOR_INIT);
31583
+ const signalList = await PersistStorageAdapter.readStorageData(true);
31584
+ signalList.sort((a, b) => a.priority - b.priority);
31585
+ this._signals = new Map(signalList
31586
+ .slice(-MAX_SIGNALS)
31587
+ .map((signal) => [signal.id, signal]));
31588
+ });
31589
+ /**
31590
+ * Handles signal opened event.
31591
+ *
31592
+ * @param tick - Tick result containing opened signal data
31593
+ * @returns Promise resolving when storage is updated
31594
+ */
31595
+ this.handleOpened = async (tick) => {
31596
+ bt.loggerService.info(STORAGE_BACKTEST_METHOD_NAME_HANDLE_OPENED, {
31597
+ signalId: tick.signal.id,
31598
+ });
31599
+ await this.waitForInit();
31600
+ const lastStorage = this._signals.get(tick.signal.id);
31601
+ if (lastStorage && lastStorage.updatedAt > tick.createdAt) {
31602
+ return;
31603
+ }
31604
+ this._signals.set(tick.signal.id, {
31605
+ ...tick.signal,
31606
+ status: "opened",
31607
+ priority: Date.now(),
31608
+ updatedAt: tick.createdAt,
31609
+ });
31610
+ await this._updateStorage();
31611
+ };
31612
+ /**
31613
+ * Handles signal closed event.
31614
+ *
31615
+ * @param tick - Tick result containing closed signal data
31616
+ * @returns Promise resolving when storage is updated
31617
+ */
31618
+ this.handleClosed = async (tick) => {
31619
+ bt.loggerService.info(STORAGE_BACKTEST_METHOD_NAME_HANDLE_CLOSED, {
31620
+ signalId: tick.signal.id,
31621
+ });
31622
+ await this.waitForInit();
31623
+ const lastStorage = this._signals.get(tick.signal.id);
31624
+ if (lastStorage && lastStorage.updatedAt > tick.createdAt) {
31625
+ return;
31626
+ }
31627
+ this._signals.set(tick.signal.id, {
31628
+ ...tick.signal,
31629
+ status: "closed",
31630
+ priority: Date.now(),
31631
+ updatedAt: tick.createdAt,
31632
+ });
31633
+ await this._updateStorage();
31634
+ };
31635
+ /**
31636
+ * Handles signal scheduled event.
31637
+ *
31638
+ * @param tick - Tick result containing scheduled signal data
31639
+ * @returns Promise resolving when storage is updated
31640
+ */
31641
+ this.handleScheduled = async (tick) => {
31642
+ bt.loggerService.info(STORAGE_BACKTEST_METHOD_NAME_HANDLE_SCHEDULED, {
31643
+ signalId: tick.signal.id,
31644
+ });
31645
+ await this.waitForInit();
31646
+ const lastStorage = this._signals.get(tick.signal.id);
31647
+ if (lastStorage && lastStorage.updatedAt > tick.createdAt) {
31648
+ return;
31649
+ }
31650
+ this._signals.set(tick.signal.id, {
31651
+ ...tick.signal,
31652
+ status: "scheduled",
31653
+ priority: Date.now(),
31654
+ updatedAt: tick.createdAt,
31655
+ });
31656
+ await this._updateStorage();
31657
+ };
31658
+ /**
31659
+ * Handles signal cancelled event.
31660
+ *
31661
+ * @param tick - Tick result containing cancelled signal data
31662
+ * @returns Promise resolving when storage is updated
31663
+ */
31664
+ this.handleCancelled = async (tick) => {
31665
+ bt.loggerService.info(STORAGE_BACKTEST_METHOD_NAME_HANDLE_CANCELLED, {
31666
+ signalId: tick.signal.id,
31667
+ });
31668
+ await this.waitForInit();
31669
+ const lastStorage = this._signals.get(tick.signal.id);
31670
+ if (lastStorage && lastStorage.updatedAt > tick.createdAt) {
31671
+ return;
31672
+ }
31673
+ this._signals.set(tick.signal.id, {
31674
+ ...tick.signal,
31675
+ status: "cancelled",
31676
+ priority: Date.now(),
31677
+ updatedAt: tick.createdAt,
31678
+ });
31679
+ await this._updateStorage();
31680
+ };
31681
+ /**
31682
+ * Finds a signal by its unique identifier.
31683
+ *
31684
+ * @param id - Signal identifier
31685
+ * @returns Promise resolving to signal row or null if not found
31686
+ */
31687
+ this.findById = async (id) => {
31688
+ bt.loggerService.info(STORAGE_BACKTEST_METHOD_NAME_FIND_BY_ID, { id });
31689
+ await this.waitForInit();
31690
+ return this._signals.get(id) ?? null;
31691
+ };
31692
+ /**
31693
+ * Lists all stored backtest signals.
31694
+ *
31695
+ * @returns Promise resolving to array of signal rows
31696
+ */
31697
+ this.list = async () => {
31698
+ bt.loggerService.info(STORAGE_BACKTEST_METHOD_NAME_LIST);
31699
+ await this.waitForInit();
31700
+ return Array.from(this._signals.values());
31701
+ };
31702
+ }
31703
+ /**
31704
+ * Persists current signal history to storage.
31705
+ * Sorts by priority and limits to MAX_SIGNALS entries.
31706
+ *
31707
+ * @throws Error if storage not initialized
31708
+ */
31709
+ async _updateStorage() {
31710
+ bt.loggerService.info(STORAGE_BACKTEST_METHOD_NAME_UPDATE_STORAGE);
31711
+ if (!this._signals) {
31712
+ throw new Error("StorageBacktestUtils not initialized. Call waitForInit first.");
31713
+ }
31714
+ const signalList = Array.from(this._signals.values());
31715
+ signalList.sort((a, b) => a.priority - b.priority);
31716
+ await PersistStorageAdapter.writeStorageData(signalList.slice(-MAX_SIGNALS), true);
31717
+ }
31718
+ }
31719
+ /**
31720
+ * Utility class for managing live trading signal history.
31721
+ *
31722
+ * Stores trading signal history for admin dashboard display during live trading
31723
+ * with automatic initialization, deduplication, and storage limits.
31724
+ *
31725
+ * @example
31726
+ * ```typescript
31727
+ * import { StorageLiveUtils } from "./classes/Storage";
31728
+ *
31729
+ * const storage = new StorageLiveUtils();
31730
+ *
31731
+ * // Handle signal events
31732
+ * await storage.handleOpened(tickResult);
31733
+ * await storage.handleClosed(tickResult);
31734
+ *
31735
+ * // Query signals
31736
+ * const signal = await storage.findById("signal-123");
31737
+ * const allSignals = await storage.list();
31738
+ * ```
31739
+ */
31740
+ class StorageLiveUtils {
31741
+ constructor() {
31742
+ /**
31743
+ * Initializes storage by loading existing signal history from persist layer.
31744
+ * Uses singleshot to ensure initialization happens only once.
31745
+ */
31746
+ this.waitForInit = singleshot(async () => {
31747
+ bt.loggerService.info(STORAGE_LIVE_METHOD_NAME_WAIT_FOR_INIT);
31748
+ const signalList = await PersistStorageAdapter.readStorageData(false);
31749
+ signalList.sort((a, b) => a.priority - b.priority);
31750
+ this._signals = new Map(signalList
31751
+ .slice(-MAX_SIGNALS)
31752
+ .map((signal) => [signal.id, signal]));
31753
+ });
31754
+ /**
31755
+ * Handles signal opened event.
31756
+ *
31757
+ * @param tick - Tick result containing opened signal data
31758
+ * @returns Promise resolving when history is updated
31759
+ */
31760
+ this.handleOpened = async (tick) => {
31761
+ bt.loggerService.info(STORAGE_LIVE_METHOD_NAME_HANDLE_OPENED, {
31762
+ signalId: tick.signal.id,
31763
+ });
31764
+ await this.waitForInit();
31765
+ const lastStorage = this._signals.get(tick.signal.id);
31766
+ if (lastStorage && lastStorage.updatedAt > tick.createdAt) {
31767
+ return;
31768
+ }
31769
+ this._signals.set(tick.signal.id, {
31770
+ ...tick.signal,
31771
+ status: "opened",
31772
+ priority: Date.now(),
31773
+ updatedAt: tick.createdAt,
31774
+ });
31775
+ await this._updateStorage();
31776
+ };
31777
+ /**
31778
+ * Handles signal closed event.
31779
+ *
31780
+ * @param tick - Tick result containing closed signal data
31781
+ * @returns Promise resolving when history is updated
31782
+ */
31783
+ this.handleClosed = async (tick) => {
31784
+ bt.loggerService.info(STORAGE_LIVE_METHOD_NAME_HANDLE_CLOSED, {
31785
+ signalId: tick.signal.id,
31786
+ });
31787
+ await this.waitForInit();
31788
+ const lastStorage = this._signals.get(tick.signal.id);
31789
+ if (lastStorage && lastStorage.updatedAt > tick.createdAt) {
31790
+ return;
31791
+ }
31792
+ this._signals.set(tick.signal.id, {
31793
+ ...tick.signal,
31794
+ status: "closed",
31795
+ priority: Date.now(),
31796
+ updatedAt: tick.createdAt,
31797
+ });
31798
+ await this._updateStorage();
31799
+ };
31800
+ /**
31801
+ * Handles signal scheduled event.
31802
+ *
31803
+ * @param tick - Tick result containing scheduled signal data
31804
+ * @returns Promise resolving when history is updated
31805
+ */
31806
+ this.handleScheduled = async (tick) => {
31807
+ bt.loggerService.info(STORAGE_LIVE_METHOD_NAME_HANDLE_SCHEDULED, {
31808
+ signalId: tick.signal.id,
31809
+ });
31810
+ await this.waitForInit();
31811
+ const lastStorage = this._signals.get(tick.signal.id);
31812
+ if (lastStorage && lastStorage.updatedAt > tick.createdAt) {
31813
+ return;
31814
+ }
31815
+ this._signals.set(tick.signal.id, {
31816
+ ...tick.signal,
31817
+ status: "scheduled",
31818
+ priority: Date.now(),
31819
+ updatedAt: tick.createdAt,
31820
+ });
31821
+ await this._updateStorage();
31822
+ };
31823
+ /**
31824
+ * Handles signal cancelled event.
31825
+ *
31826
+ * @param tick - Tick result containing cancelled signal data
31827
+ * @returns Promise resolving when history is updated
31828
+ */
31829
+ this.handleCancelled = async (tick) => {
31830
+ bt.loggerService.info(STORAGE_LIVE_METHOD_NAME_HANDLE_CANCELLED, {
31831
+ signalId: tick.signal.id,
31832
+ });
31833
+ await this.waitForInit();
31834
+ const lastStorage = this._signals.get(tick.signal.id);
31835
+ if (lastStorage && lastStorage.updatedAt > tick.createdAt) {
31836
+ return;
31837
+ }
31838
+ this._signals.set(tick.signal.id, {
31839
+ ...tick.signal,
31840
+ status: "cancelled",
31841
+ priority: Date.now(),
31842
+ updatedAt: tick.createdAt,
31843
+ });
31844
+ await this._updateStorage();
31845
+ };
31846
+ /**
31847
+ * Finds a signal by its unique identifier.
31848
+ *
31849
+ * @param id - Signal identifier
31850
+ * @returns Promise resolving to signal row or null if not found
31851
+ */
31852
+ this.findById = async (id) => {
31853
+ bt.loggerService.info(STORAGE_LIVE_METHOD_NAME_FIND_BY_ID, { id });
31854
+ await this.waitForInit();
31855
+ return this._signals.get(id) ?? null;
31856
+ };
31857
+ /**
31858
+ * Lists all stored live signals.
31859
+ *
31860
+ * @returns Promise resolving to array of signal rows
31861
+ */
31862
+ this.list = async () => {
31863
+ bt.loggerService.info(STORAGE_LIVE_METHOD_NAME_LIST);
31864
+ await this.waitForInit();
31865
+ return Array.from(this._signals.values());
31866
+ };
31867
+ }
31868
+ /**
31869
+ * Persists current signal history to storage.
31870
+ * Sorts by priority and limits to MAX_SIGNALS entries.
31871
+ *
31872
+ * @throws Error if storage not initialized
31873
+ */
31874
+ async _updateStorage() {
31875
+ bt.loggerService.info(STORAGE_LIVE_METHOD_NAME_UPDATE_STORAGE);
31876
+ if (!this._signals) {
31877
+ throw new Error("StorageLiveUtils not initialized. Call waitForInit first.");
31878
+ }
31879
+ const signalList = Array.from(this._signals.values());
31880
+ signalList.sort((a, b) => a.priority - b.priority);
31881
+ await PersistStorageAdapter.writeStorageData(signalList.slice(-MAX_SIGNALS), false);
31882
+ }
31883
+ }
31884
+ /**
31885
+ * Main storage adapter for signal history management.
31886
+ *
31887
+ * Provides unified interface for accessing backtest and live signal history
31888
+ * for admin dashboard. Subscribes to signal emitters and automatically
31889
+ * updates history on signal events.
31890
+ *
31891
+ * @example
31892
+ * ```typescript
31893
+ * import { Storage } from "./classes/Storage";
31894
+ *
31895
+ * // Enable signal history tracking
31896
+ * const unsubscribe = Storage.enable();
31897
+ *
31898
+ * // Query signals
31899
+ * const backtestSignals = await Storage.listSignalBacktest();
31900
+ * const liveSignals = await Storage.listSignalLive();
31901
+ * const signal = await Storage.findSignalById("signal-123");
31902
+ *
31903
+ * // Disable tracking
31904
+ * Storage.disable();
31905
+ * ```
31906
+ */
31907
+ class StorageAdapter {
31908
+ constructor() {
31909
+ this._signalLiveUtils = new StorageLiveUtils();
31910
+ this._signalBacktestUtils = new StorageBacktestUtils();
31911
+ /**
31912
+ * Enables signal history tracking by subscribing to emitters.
31913
+ *
31914
+ * @returns Cleanup function to unsubscribe from all emitters
31915
+ */
31916
+ this.enable = singleshot(() => {
31917
+ bt.loggerService.info(STORAGE_ADAPTER_METHOD_NAME_ENABLE);
31918
+ let unLive;
31919
+ let unBacktest;
31920
+ {
31921
+ const unBacktestOpen = signalBacktestEmitter
31922
+ .filter(({ action }) => action === "opened")
31923
+ .connect((tick) => this._signalBacktestUtils.handleOpened(tick));
31924
+ const unBacktestClose = signalBacktestEmitter
31925
+ .filter(({ action }) => action === "closed")
31926
+ .connect((tick) => this._signalBacktestUtils.handleClosed(tick));
31927
+ const unBacktestScheduled = signalBacktestEmitter
31928
+ .filter(({ action }) => action === "scheduled")
31929
+ .connect((tick) => this._signalBacktestUtils.handleScheduled(tick));
31930
+ const unBacktestCancelled = signalBacktestEmitter
31931
+ .filter(({ action }) => action === "cancelled")
31932
+ .connect((tick) => this._signalBacktestUtils.handleCancelled(tick));
31933
+ unBacktest = compose(() => unBacktestOpen(), () => unBacktestClose(), () => unBacktestScheduled(), () => unBacktestCancelled());
31934
+ }
31935
+ {
31936
+ const unLiveOpen = signalLiveEmitter
31937
+ .filter(({ action }) => action === "opened")
31938
+ .connect((tick) => this._signalLiveUtils.handleOpened(tick));
31939
+ const unLiveClose = signalLiveEmitter
31940
+ .filter(({ action }) => action === "closed")
31941
+ .connect((tick) => this._signalLiveUtils.handleClosed(tick));
31942
+ const unLiveScheduled = signalLiveEmitter
31943
+ .filter(({ action }) => action === "scheduled")
31944
+ .connect((tick) => this._signalLiveUtils.handleScheduled(tick));
31945
+ const unLiveCancelled = signalLiveEmitter
31946
+ .filter(({ action }) => action === "cancelled")
31947
+ .connect((tick) => this._signalLiveUtils.handleCancelled(tick));
31948
+ unLive = compose(() => unLiveOpen(), () => unLiveClose(), () => unLiveScheduled(), () => unLiveCancelled());
31949
+ }
31950
+ return () => {
31951
+ unLive();
31952
+ unBacktest();
31953
+ this.enable.clear();
31954
+ };
31955
+ });
31956
+ /**
31957
+ * Disables signal history tracking by unsubscribing from emitters.
31958
+ */
31959
+ this.disable = () => {
31960
+ bt.loggerService.info(STORAGE_ADAPTER_METHOD_NAME_DISABLE);
31961
+ if (this.enable.hasValue()) {
31962
+ const lastSubscription = this.enable();
31963
+ lastSubscription();
31964
+ }
31965
+ };
31966
+ /**
31967
+ * Finds a signal by ID across both backtest and live history.
31968
+ *
31969
+ * @param id - Signal identifier
31970
+ * @returns Promise resolving to signal row
31971
+ * @throws Error if signal not found in either storage
31972
+ */
31973
+ this.findSignalById = async (id) => {
31974
+ bt.loggerService.info(STORAGE_ADAPTER_METHOD_NAME_FIND_SIGNAL_BY_ID, { id });
31975
+ let result = null;
31976
+ if ((result = await this._signalBacktestUtils.findById(id))) {
31977
+ return result;
31978
+ }
31979
+ if ((result = await this._signalLiveUtils.findById(id))) {
31980
+ return result;
31981
+ }
31982
+ throw new Error(`Storage signal with id ${id} not found`);
31983
+ };
31984
+ /**
31985
+ * Lists all backtest signal history.
31986
+ *
31987
+ * @returns Promise resolving to array of backtest signal rows
31988
+ */
31989
+ this.listSignalBacktest = async () => {
31990
+ bt.loggerService.info(STORAGE_ADAPTER_METHOD_NAME_LIST_SIGNAL_BACKTEST);
31991
+ return await this._signalBacktestUtils.list();
31992
+ };
31993
+ /**
31994
+ * Lists all live signal history.
31995
+ *
31996
+ * @returns Promise resolving to array of live signal rows
31997
+ */
31998
+ this.listSignalLive = async () => {
31999
+ bt.loggerService.info(STORAGE_ADAPTER_METHOD_NAME_LIST_SIGNAL_LIVE);
32000
+ return await this._signalLiveUtils.list();
32001
+ };
32002
+ }
32003
+ }
32004
+ const Storage = new StorageAdapter();
32005
+
31430
32006
  const EXCHANGE_METHOD_NAME_GET_CANDLES = "ExchangeUtils.getCandles";
31431
32007
  const EXCHANGE_METHOD_NAME_GET_AVERAGE_PRICE = "ExchangeUtils.getAveragePrice";
31432
32008
  const EXCHANGE_METHOD_NAME_FORMAT_QUANTITY = "ExchangeUtils.formatQuantity";
@@ -33432,4 +34008,4 @@ const set = (object, path, value) => {
33432
34008
  }
33433
34009
  };
33434
34010
 
33435
- export { ActionBase, Backtest, Breakeven, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Strategy, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialProfit, commitTrailingStop, commitTrailingTake, emitters, formatPrice, formatQuantity, get, getActionSchema, getAveragePrice, getBacktestTimeframe, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getExchangeSchema, getFrameSchema, getMode, getOrderBook, getRawCandles, getRiskSchema, getSizingSchema, getStrategySchema, getSymbol, getWalkerSchema, hasTradeContext, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, roundTicks, set, setColumns, setConfig, setLogger, stopStrategy, validate };
34011
+ export { ActionBase, Backtest, Breakeven, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Storage, Strategy, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialProfit, commitTrailingStop, commitTrailingTake, emitters, formatPrice, formatQuantity, get, getActionSchema, getAveragePrice, getBacktestTimeframe, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getExchangeSchema, getFrameSchema, getMode, getOrderBook, getRawCandles, getRiskSchema, getSizingSchema, getStrategySchema, getSymbol, getWalkerSchema, hasTradeContext, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, roundTicks, set, setColumns, setConfig, setLogger, stopStrategy, validate };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backtest-kit",
3
- "version": "2.2.9",
3
+ "version": "2.2.11",
4
4
  "description": "A TypeScript library for trading system backtest",
5
5
  "author": {
6
6
  "name": "Petr Tripolsky",