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.cjs +583 -5
- package/build/index.mjs +582 -6
- package/package.json +1 -1
- package/types.d.ts +300 -9
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.
|
|
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.
|
|
951
|
-
const stateStorage = this.
|
|
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.
|
|
974
|
-
const stateStorage = this.
|
|
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 };
|