backtest-kit 3.0.0 → 3.0.2
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 +2116 -947
- package/build/index.mjs +2114 -948
- package/package.json +1 -1
- package/types.d.ts +396 -117
package/build/index.cjs
CHANGED
|
@@ -750,6 +750,11 @@ const PERSIST_STORAGE_UTILS_METHOD_NAME_WRITE_DATA = "PersistStorageUtils.writeS
|
|
|
750
750
|
const PERSIST_STORAGE_UTILS_METHOD_NAME_USE_JSON = "PersistStorageUtils.useJson";
|
|
751
751
|
const PERSIST_STORAGE_UTILS_METHOD_NAME_USE_DUMMY = "PersistStorageUtils.useDummy";
|
|
752
752
|
const PERSIST_STORAGE_UTILS_METHOD_NAME_USE_PERSIST_STORAGE_ADAPTER = "PersistStorageUtils.usePersistStorageAdapter";
|
|
753
|
+
const PERSIST_NOTIFICATION_UTILS_METHOD_NAME_READ_DATA = "PersistNotificationUtils.readNotificationData";
|
|
754
|
+
const PERSIST_NOTIFICATION_UTILS_METHOD_NAME_WRITE_DATA = "PersistNotificationUtils.writeNotificationData";
|
|
755
|
+
const PERSIST_NOTIFICATION_UTILS_METHOD_NAME_USE_JSON = "PersistNotificationUtils.useJson";
|
|
756
|
+
const PERSIST_NOTIFICATION_UTILS_METHOD_NAME_USE_DUMMY = "PersistNotificationUtils.useDummy";
|
|
757
|
+
const PERSIST_NOTIFICATION_UTILS_METHOD_NAME_USE_PERSIST_NOTIFICATION_ADAPTER = "PersistNotificationUtils.usePersistNotificationAdapter";
|
|
753
758
|
const BASE_WAIT_FOR_INIT_FN_METHOD_NAME = "PersistBase.waitForInitFn";
|
|
754
759
|
const BASE_UNLINK_RETRY_COUNT = 5;
|
|
755
760
|
const BASE_UNLINK_RETRY_DELAY = 1000;
|
|
@@ -1823,6 +1828,101 @@ class PersistStorageUtils {
|
|
|
1823
1828
|
* Used by SignalLiveUtils for signal storage persistence.
|
|
1824
1829
|
*/
|
|
1825
1830
|
const PersistStorageAdapter = new PersistStorageUtils();
|
|
1831
|
+
/**
|
|
1832
|
+
* Utility class for managing notification persistence.
|
|
1833
|
+
*
|
|
1834
|
+
* Features:
|
|
1835
|
+
* - Memoized storage instances
|
|
1836
|
+
* - Custom adapter support
|
|
1837
|
+
* - Atomic read/write operations for NotificationData
|
|
1838
|
+
* - Each notification stored as separate file keyed by id
|
|
1839
|
+
* - Crash-safe notification state management
|
|
1840
|
+
*
|
|
1841
|
+
* Used by NotificationPersistLiveUtils/NotificationPersistBacktestUtils for persistence.
|
|
1842
|
+
*/
|
|
1843
|
+
class PersistNotificationUtils {
|
|
1844
|
+
constructor() {
|
|
1845
|
+
this.PersistNotificationFactory = PersistBase;
|
|
1846
|
+
this.getNotificationStorage = functoolsKit.memoize(([backtest]) => backtest ? `backtest` : `live`, (backtest) => Reflect.construct(this.PersistNotificationFactory, [
|
|
1847
|
+
backtest ? `backtest` : `live`,
|
|
1848
|
+
`./dump/data/notification/`,
|
|
1849
|
+
]));
|
|
1850
|
+
/**
|
|
1851
|
+
* Reads persisted notifications data.
|
|
1852
|
+
*
|
|
1853
|
+
* Called by NotificationPersistLiveUtils/NotificationPersistBacktestUtils.waitForInit() to restore state.
|
|
1854
|
+
* Uses keys() from PersistBase to iterate over all stored notifications.
|
|
1855
|
+
* Returns empty array if no notifications exist.
|
|
1856
|
+
*
|
|
1857
|
+
* @param backtest - If true, reads from backtest storage; otherwise from live storage
|
|
1858
|
+
* @returns Promise resolving to array of notification entries
|
|
1859
|
+
*/
|
|
1860
|
+
this.readNotificationData = async (backtest) => {
|
|
1861
|
+
bt.loggerService.info(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_READ_DATA);
|
|
1862
|
+
const key = backtest ? `backtest` : `live`;
|
|
1863
|
+
const isInitial = !this.getNotificationStorage.has(key);
|
|
1864
|
+
const stateStorage = this.getNotificationStorage(backtest);
|
|
1865
|
+
await stateStorage.waitForInit(isInitial);
|
|
1866
|
+
const notifications = [];
|
|
1867
|
+
for await (const notificationId of stateStorage.keys()) {
|
|
1868
|
+
const notification = await stateStorage.readValue(notificationId);
|
|
1869
|
+
notifications.push(notification);
|
|
1870
|
+
}
|
|
1871
|
+
return notifications;
|
|
1872
|
+
};
|
|
1873
|
+
/**
|
|
1874
|
+
* Writes notification data to disk with atomic file writes.
|
|
1875
|
+
*
|
|
1876
|
+
* Called by NotificationPersistLiveUtils/NotificationPersistBacktestUtils after notification changes to persist state.
|
|
1877
|
+
* Uses notification.id as the storage key for individual file storage.
|
|
1878
|
+
* Uses atomic writes to prevent corruption on crashes.
|
|
1879
|
+
*
|
|
1880
|
+
* @param notificationData - Notification entries to persist
|
|
1881
|
+
* @param backtest - If true, writes to backtest storage; otherwise to live storage
|
|
1882
|
+
* @returns Promise that resolves when write is complete
|
|
1883
|
+
*/
|
|
1884
|
+
this.writeNotificationData = async (notificationData, backtest) => {
|
|
1885
|
+
bt.loggerService.info(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_WRITE_DATA);
|
|
1886
|
+
const key = backtest ? `backtest` : `live`;
|
|
1887
|
+
const isInitial = !this.getNotificationStorage.has(key);
|
|
1888
|
+
const stateStorage = this.getNotificationStorage(backtest);
|
|
1889
|
+
await stateStorage.waitForInit(isInitial);
|
|
1890
|
+
for (const notification of notificationData) {
|
|
1891
|
+
await stateStorage.writeValue(notification.id, notification);
|
|
1892
|
+
}
|
|
1893
|
+
};
|
|
1894
|
+
}
|
|
1895
|
+
/**
|
|
1896
|
+
* Registers a custom persistence adapter.
|
|
1897
|
+
*
|
|
1898
|
+
* @param Ctor - Custom PersistBase constructor
|
|
1899
|
+
*/
|
|
1900
|
+
usePersistNotificationAdapter(Ctor) {
|
|
1901
|
+
bt.loggerService.info(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_USE_PERSIST_NOTIFICATION_ADAPTER);
|
|
1902
|
+
this.PersistNotificationFactory = Ctor;
|
|
1903
|
+
}
|
|
1904
|
+
/**
|
|
1905
|
+
* Switches to the default JSON persist adapter.
|
|
1906
|
+
* All future persistence writes will use JSON storage.
|
|
1907
|
+
*/
|
|
1908
|
+
useJson() {
|
|
1909
|
+
bt.loggerService.log(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_USE_JSON);
|
|
1910
|
+
this.usePersistNotificationAdapter(PersistBase);
|
|
1911
|
+
}
|
|
1912
|
+
/**
|
|
1913
|
+
* Switches to a dummy persist adapter that discards all writes.
|
|
1914
|
+
* All future persistence writes will be no-ops.
|
|
1915
|
+
*/
|
|
1916
|
+
useDummy() {
|
|
1917
|
+
bt.loggerService.log(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_USE_DUMMY);
|
|
1918
|
+
this.usePersistNotificationAdapter(PersistDummy);
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
/**
|
|
1922
|
+
* Global singleton instance of PersistNotificationUtils.
|
|
1923
|
+
* Used by NotificationPersistLiveUtils/NotificationPersistBacktestUtils for notification persistence.
|
|
1924
|
+
*/
|
|
1925
|
+
const PersistNotificationAdapter = new PersistNotificationUtils();
|
|
1826
1926
|
|
|
1827
1927
|
const MS_PER_MINUTE$1 = 60000;
|
|
1828
1928
|
const INTERVAL_MINUTES$4 = {
|
|
@@ -33640,325 +33740,2048 @@ const StorageLive = new StorageLiveAdapter();
|
|
|
33640
33740
|
*/
|
|
33641
33741
|
const StorageBacktest = new StorageBacktestAdapter();
|
|
33642
33742
|
|
|
33643
|
-
const EXCHANGE_METHOD_NAME_GET_CANDLES = "ExchangeUtils.getCandles";
|
|
33644
|
-
const EXCHANGE_METHOD_NAME_GET_AVERAGE_PRICE = "ExchangeUtils.getAveragePrice";
|
|
33645
|
-
const EXCHANGE_METHOD_NAME_FORMAT_QUANTITY = "ExchangeUtils.formatQuantity";
|
|
33646
|
-
const EXCHANGE_METHOD_NAME_FORMAT_PRICE = "ExchangeUtils.formatPrice";
|
|
33647
|
-
const EXCHANGE_METHOD_NAME_GET_ORDER_BOOK = "ExchangeUtils.getOrderBook";
|
|
33648
|
-
const EXCHANGE_METHOD_NAME_GET_RAW_CANDLES = "ExchangeUtils.getRawCandles";
|
|
33649
|
-
const MS_PER_MINUTE = 60000;
|
|
33650
33743
|
/**
|
|
33651
|
-
*
|
|
33652
|
-
*
|
|
33744
|
+
* Maximum number of notifications to keep in storage.
|
|
33745
|
+
* Older notifications are removed when this limit is exceeded.
|
|
33653
33746
|
*/
|
|
33654
|
-
const
|
|
33655
|
-
if (ExecutionContextService.hasContext()) {
|
|
33656
|
-
return new Date(bt.executionContextService.context.when);
|
|
33657
|
-
}
|
|
33658
|
-
return new Date();
|
|
33659
|
-
};
|
|
33747
|
+
const MAX_NOTIFICATIONS = 250;
|
|
33660
33748
|
/**
|
|
33661
|
-
*
|
|
33662
|
-
*
|
|
33749
|
+
* Generates a unique key for notification identification.
|
|
33750
|
+
* @returns Random string identifier
|
|
33663
33751
|
*/
|
|
33664
|
-
const
|
|
33665
|
-
if (ExecutionContextService.hasContext()) {
|
|
33666
|
-
return bt.executionContextService.context.backtest;
|
|
33667
|
-
}
|
|
33668
|
-
return false;
|
|
33669
|
-
};
|
|
33752
|
+
const CREATE_KEY_FN$1 = () => functoolsKit.randomString();
|
|
33670
33753
|
/**
|
|
33671
|
-
*
|
|
33672
|
-
*
|
|
33754
|
+
* Creates a notification model from signal tick result.
|
|
33755
|
+
* Handles opened, closed, scheduled, and cancelled signal actions.
|
|
33756
|
+
* @param data - The strategy tick result data
|
|
33757
|
+
* @returns NotificationModel or null if action is not recognized
|
|
33673
33758
|
*/
|
|
33674
|
-
const
|
|
33675
|
-
|
|
33759
|
+
const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
33760
|
+
if (data.action === "opened") {
|
|
33761
|
+
return {
|
|
33762
|
+
type: "signal.opened",
|
|
33763
|
+
id: CREATE_KEY_FN$1(),
|
|
33764
|
+
timestamp: data.signal.pendingAt,
|
|
33765
|
+
backtest: data.backtest,
|
|
33766
|
+
symbol: data.symbol,
|
|
33767
|
+
strategyName: data.strategyName,
|
|
33768
|
+
exchangeName: data.exchangeName,
|
|
33769
|
+
signalId: data.signal.id,
|
|
33770
|
+
position: data.signal.position,
|
|
33771
|
+
priceOpen: data.signal.priceOpen,
|
|
33772
|
+
priceTakeProfit: data.signal.priceTakeProfit,
|
|
33773
|
+
priceStopLoss: data.signal.priceStopLoss,
|
|
33774
|
+
originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
|
|
33775
|
+
originalPriceStopLoss: data.signal.originalPriceStopLoss,
|
|
33776
|
+
note: data.signal.note,
|
|
33777
|
+
scheduledAt: data.signal.scheduledAt,
|
|
33778
|
+
pendingAt: data.signal.pendingAt,
|
|
33779
|
+
createdAt: data.createdAt,
|
|
33780
|
+
};
|
|
33781
|
+
}
|
|
33782
|
+
if (data.action === "closed") {
|
|
33783
|
+
const durationMs = data.closeTimestamp - data.signal.pendingAt;
|
|
33784
|
+
const durationMin = Math.round(durationMs / 60000);
|
|
33785
|
+
return {
|
|
33786
|
+
type: "signal.closed",
|
|
33787
|
+
id: CREATE_KEY_FN$1(),
|
|
33788
|
+
timestamp: data.closeTimestamp,
|
|
33789
|
+
backtest: data.backtest,
|
|
33790
|
+
symbol: data.symbol,
|
|
33791
|
+
strategyName: data.strategyName,
|
|
33792
|
+
exchangeName: data.exchangeName,
|
|
33793
|
+
signalId: data.signal.id,
|
|
33794
|
+
position: data.signal.position,
|
|
33795
|
+
priceOpen: data.signal.priceOpen,
|
|
33796
|
+
priceClose: data.currentPrice,
|
|
33797
|
+
priceTakeProfit: data.signal.priceTakeProfit,
|
|
33798
|
+
priceStopLoss: data.signal.priceStopLoss,
|
|
33799
|
+
originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
|
|
33800
|
+
originalPriceStopLoss: data.signal.originalPriceStopLoss,
|
|
33801
|
+
pnlPercentage: data.pnl.pnlPercentage,
|
|
33802
|
+
closeReason: data.closeReason,
|
|
33803
|
+
duration: durationMin,
|
|
33804
|
+
note: data.signal.note,
|
|
33805
|
+
scheduledAt: data.signal.scheduledAt,
|
|
33806
|
+
pendingAt: data.signal.pendingAt,
|
|
33807
|
+
createdAt: data.createdAt,
|
|
33808
|
+
};
|
|
33809
|
+
}
|
|
33810
|
+
if (data.action === "scheduled") {
|
|
33811
|
+
return {
|
|
33812
|
+
type: "signal.scheduled",
|
|
33813
|
+
id: CREATE_KEY_FN$1(),
|
|
33814
|
+
timestamp: data.signal.scheduledAt,
|
|
33815
|
+
backtest: data.backtest,
|
|
33816
|
+
symbol: data.symbol,
|
|
33817
|
+
strategyName: data.strategyName,
|
|
33818
|
+
exchangeName: data.exchangeName,
|
|
33819
|
+
signalId: data.signal.id,
|
|
33820
|
+
position: data.signal.position,
|
|
33821
|
+
priceOpen: data.signal.priceOpen,
|
|
33822
|
+
priceTakeProfit: data.signal.priceTakeProfit,
|
|
33823
|
+
priceStopLoss: data.signal.priceStopLoss,
|
|
33824
|
+
originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
|
|
33825
|
+
originalPriceStopLoss: data.signal.originalPriceStopLoss,
|
|
33826
|
+
scheduledAt: data.signal.scheduledAt,
|
|
33827
|
+
currentPrice: data.currentPrice,
|
|
33828
|
+
createdAt: data.createdAt,
|
|
33829
|
+
};
|
|
33830
|
+
}
|
|
33831
|
+
if (data.action === "cancelled") {
|
|
33832
|
+
const durationMs = data.closeTimestamp - data.signal.scheduledAt;
|
|
33833
|
+
const durationMin = Math.round(durationMs / 60000);
|
|
33834
|
+
return {
|
|
33835
|
+
type: "signal.cancelled",
|
|
33836
|
+
id: CREATE_KEY_FN$1(),
|
|
33837
|
+
timestamp: data.closeTimestamp,
|
|
33838
|
+
backtest: data.backtest,
|
|
33839
|
+
symbol: data.symbol,
|
|
33840
|
+
strategyName: data.strategyName,
|
|
33841
|
+
exchangeName: data.exchangeName,
|
|
33842
|
+
signalId: data.signal.id,
|
|
33843
|
+
position: data.signal.position,
|
|
33844
|
+
priceOpen: data.signal.priceOpen,
|
|
33845
|
+
priceTakeProfit: data.signal.priceTakeProfit,
|
|
33846
|
+
priceStopLoss: data.signal.priceStopLoss,
|
|
33847
|
+
originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
|
|
33848
|
+
originalPriceStopLoss: data.signal.originalPriceStopLoss,
|
|
33849
|
+
cancelReason: data.reason,
|
|
33850
|
+
cancelId: data.cancelId,
|
|
33851
|
+
duration: durationMin,
|
|
33852
|
+
scheduledAt: data.signal.scheduledAt,
|
|
33853
|
+
pendingAt: data.signal.pendingAt,
|
|
33854
|
+
createdAt: data.createdAt,
|
|
33855
|
+
};
|
|
33856
|
+
}
|
|
33857
|
+
return null;
|
|
33676
33858
|
};
|
|
33677
33859
|
/**
|
|
33678
|
-
*
|
|
33679
|
-
*
|
|
33680
|
-
|
|
33681
|
-
|
|
33682
|
-
|
|
33683
|
-
|
|
33860
|
+
* Creates a notification model for partial profit availability.
|
|
33861
|
+
* @param data - The partial profit contract data
|
|
33862
|
+
* @returns NotificationModel for partial profit event
|
|
33863
|
+
*/
|
|
33864
|
+
const CREATE_PARTIAL_PROFIT_NOTIFICATION_FN = (data) => ({
|
|
33865
|
+
type: "partial_profit.available",
|
|
33866
|
+
id: CREATE_KEY_FN$1(),
|
|
33867
|
+
timestamp: data.timestamp,
|
|
33868
|
+
backtest: data.backtest,
|
|
33869
|
+
symbol: data.symbol,
|
|
33870
|
+
strategyName: data.strategyName,
|
|
33871
|
+
exchangeName: data.exchangeName,
|
|
33872
|
+
signalId: data.data.id,
|
|
33873
|
+
level: data.level,
|
|
33874
|
+
currentPrice: data.currentPrice,
|
|
33875
|
+
priceOpen: data.data.priceOpen,
|
|
33876
|
+
position: data.data.position,
|
|
33877
|
+
priceTakeProfit: data.data.priceTakeProfit,
|
|
33878
|
+
priceStopLoss: data.data.priceStopLoss,
|
|
33879
|
+
originalPriceTakeProfit: data.data.originalPriceTakeProfit,
|
|
33880
|
+
originalPriceStopLoss: data.data.originalPriceStopLoss,
|
|
33881
|
+
scheduledAt: data.data.scheduledAt,
|
|
33882
|
+
pendingAt: data.data.pendingAt,
|
|
33883
|
+
createdAt: data.timestamp,
|
|
33884
|
+
});
|
|
33684
33885
|
/**
|
|
33685
|
-
*
|
|
33686
|
-
*
|
|
33687
|
-
|
|
33688
|
-
|
|
33689
|
-
|
|
33690
|
-
|
|
33886
|
+
* Creates a notification model for partial loss availability.
|
|
33887
|
+
* @param data - The partial loss contract data
|
|
33888
|
+
* @returns NotificationModel for partial loss event
|
|
33889
|
+
*/
|
|
33890
|
+
const CREATE_PARTIAL_LOSS_NOTIFICATION_FN = (data) => ({
|
|
33891
|
+
type: "partial_loss.available",
|
|
33892
|
+
id: CREATE_KEY_FN$1(),
|
|
33893
|
+
timestamp: data.timestamp,
|
|
33894
|
+
backtest: data.backtest,
|
|
33895
|
+
symbol: data.symbol,
|
|
33896
|
+
strategyName: data.strategyName,
|
|
33897
|
+
exchangeName: data.exchangeName,
|
|
33898
|
+
signalId: data.data.id,
|
|
33899
|
+
level: data.level,
|
|
33900
|
+
currentPrice: data.currentPrice,
|
|
33901
|
+
priceOpen: data.data.priceOpen,
|
|
33902
|
+
position: data.data.position,
|
|
33903
|
+
priceTakeProfit: data.data.priceTakeProfit,
|
|
33904
|
+
priceStopLoss: data.data.priceStopLoss,
|
|
33905
|
+
originalPriceTakeProfit: data.data.originalPriceTakeProfit,
|
|
33906
|
+
originalPriceStopLoss: data.data.originalPriceStopLoss,
|
|
33907
|
+
scheduledAt: data.data.scheduledAt,
|
|
33908
|
+
pendingAt: data.data.pendingAt,
|
|
33909
|
+
createdAt: data.timestamp,
|
|
33910
|
+
});
|
|
33691
33911
|
/**
|
|
33692
|
-
*
|
|
33693
|
-
*
|
|
33694
|
-
*
|
|
33695
|
-
|
|
33696
|
-
|
|
33697
|
-
|
|
33698
|
-
|
|
33699
|
-
|
|
33700
|
-
|
|
33701
|
-
|
|
33702
|
-
|
|
33703
|
-
|
|
33704
|
-
|
|
33705
|
-
|
|
33706
|
-
|
|
33707
|
-
|
|
33708
|
-
|
|
33709
|
-
|
|
33710
|
-
|
|
33711
|
-
|
|
33712
|
-
|
|
33713
|
-
|
|
33714
|
-
|
|
33715
|
-
};
|
|
33912
|
+
* Creates a notification model for breakeven availability.
|
|
33913
|
+
* @param data - The breakeven contract data
|
|
33914
|
+
* @returns NotificationModel for breakeven event
|
|
33915
|
+
*/
|
|
33916
|
+
const CREATE_BREAKEVEN_NOTIFICATION_FN = (data) => ({
|
|
33917
|
+
type: "breakeven.available",
|
|
33918
|
+
id: CREATE_KEY_FN$1(),
|
|
33919
|
+
timestamp: data.timestamp,
|
|
33920
|
+
backtest: data.backtest,
|
|
33921
|
+
symbol: data.symbol,
|
|
33922
|
+
strategyName: data.strategyName,
|
|
33923
|
+
exchangeName: data.exchangeName,
|
|
33924
|
+
signalId: data.data.id,
|
|
33925
|
+
currentPrice: data.currentPrice,
|
|
33926
|
+
priceOpen: data.data.priceOpen,
|
|
33927
|
+
position: data.data.position,
|
|
33928
|
+
priceTakeProfit: data.data.priceTakeProfit,
|
|
33929
|
+
priceStopLoss: data.data.priceStopLoss,
|
|
33930
|
+
originalPriceTakeProfit: data.data.originalPriceTakeProfit,
|
|
33931
|
+
originalPriceStopLoss: data.data.originalPriceStopLoss,
|
|
33932
|
+
scheduledAt: data.data.scheduledAt,
|
|
33933
|
+
pendingAt: data.data.pendingAt,
|
|
33934
|
+
createdAt: data.timestamp,
|
|
33935
|
+
});
|
|
33716
33936
|
/**
|
|
33717
|
-
*
|
|
33718
|
-
*
|
|
33719
|
-
*
|
|
33720
|
-
*
|
|
33721
|
-
*
|
|
33722
|
-
* - Candle with timestamp 00:00 covers period [00:00, 00:15) for 15m interval
|
|
33723
|
-
*
|
|
33724
|
-
* Adapter contract:
|
|
33725
|
-
* - Adapter must return candles with timestamp = openTime
|
|
33726
|
-
* - First returned candle.timestamp must equal aligned since
|
|
33727
|
-
* - Adapter must return exactly `limit` candles
|
|
33728
|
-
*
|
|
33729
|
-
* @param timestamp - Timestamp in milliseconds
|
|
33730
|
-
* @param intervalMinutes - Interval in minutes
|
|
33731
|
-
* @returns Aligned timestamp rounded down to interval boundary
|
|
33937
|
+
* Creates a notification model for strategy commit events.
|
|
33938
|
+
* Handles partial-profit, partial-loss, breakeven, trailing-stop,
|
|
33939
|
+
* trailing-take, and activate-scheduled actions.
|
|
33940
|
+
* @param data - The strategy commit contract data
|
|
33941
|
+
* @returns NotificationModel or null if action is not recognized
|
|
33732
33942
|
*/
|
|
33733
|
-
const
|
|
33734
|
-
|
|
33735
|
-
|
|
33943
|
+
const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
33944
|
+
if (data.action === "partial-profit") {
|
|
33945
|
+
return {
|
|
33946
|
+
type: "partial_profit.commit",
|
|
33947
|
+
id: CREATE_KEY_FN$1(),
|
|
33948
|
+
timestamp: data.timestamp,
|
|
33949
|
+
backtest: data.backtest,
|
|
33950
|
+
symbol: data.symbol,
|
|
33951
|
+
strategyName: data.strategyName,
|
|
33952
|
+
exchangeName: data.exchangeName,
|
|
33953
|
+
signalId: data.signalId,
|
|
33954
|
+
percentToClose: data.percentToClose,
|
|
33955
|
+
currentPrice: data.currentPrice,
|
|
33956
|
+
position: data.position,
|
|
33957
|
+
priceOpen: data.priceOpen,
|
|
33958
|
+
priceTakeProfit: data.priceTakeProfit,
|
|
33959
|
+
priceStopLoss: data.priceStopLoss,
|
|
33960
|
+
originalPriceTakeProfit: data.originalPriceTakeProfit,
|
|
33961
|
+
originalPriceStopLoss: data.originalPriceStopLoss,
|
|
33962
|
+
scheduledAt: data.scheduledAt,
|
|
33963
|
+
pendingAt: data.pendingAt,
|
|
33964
|
+
createdAt: data.timestamp,
|
|
33965
|
+
};
|
|
33966
|
+
}
|
|
33967
|
+
if (data.action === "partial-loss") {
|
|
33968
|
+
return {
|
|
33969
|
+
type: "partial_loss.commit",
|
|
33970
|
+
id: CREATE_KEY_FN$1(),
|
|
33971
|
+
timestamp: data.timestamp,
|
|
33972
|
+
backtest: data.backtest,
|
|
33973
|
+
symbol: data.symbol,
|
|
33974
|
+
strategyName: data.strategyName,
|
|
33975
|
+
exchangeName: data.exchangeName,
|
|
33976
|
+
signalId: data.signalId,
|
|
33977
|
+
percentToClose: data.percentToClose,
|
|
33978
|
+
currentPrice: data.currentPrice,
|
|
33979
|
+
position: data.position,
|
|
33980
|
+
priceOpen: data.priceOpen,
|
|
33981
|
+
priceTakeProfit: data.priceTakeProfit,
|
|
33982
|
+
priceStopLoss: data.priceStopLoss,
|
|
33983
|
+
originalPriceTakeProfit: data.originalPriceTakeProfit,
|
|
33984
|
+
originalPriceStopLoss: data.originalPriceStopLoss,
|
|
33985
|
+
scheduledAt: data.scheduledAt,
|
|
33986
|
+
pendingAt: data.pendingAt,
|
|
33987
|
+
createdAt: data.timestamp,
|
|
33988
|
+
};
|
|
33989
|
+
}
|
|
33990
|
+
if (data.action === "breakeven") {
|
|
33991
|
+
return {
|
|
33992
|
+
type: "breakeven.commit",
|
|
33993
|
+
id: CREATE_KEY_FN$1(),
|
|
33994
|
+
timestamp: data.timestamp,
|
|
33995
|
+
backtest: data.backtest,
|
|
33996
|
+
symbol: data.symbol,
|
|
33997
|
+
strategyName: data.strategyName,
|
|
33998
|
+
exchangeName: data.exchangeName,
|
|
33999
|
+
signalId: data.signalId,
|
|
34000
|
+
currentPrice: data.currentPrice,
|
|
34001
|
+
position: data.position,
|
|
34002
|
+
priceOpen: data.priceOpen,
|
|
34003
|
+
priceTakeProfit: data.priceTakeProfit,
|
|
34004
|
+
priceStopLoss: data.priceStopLoss,
|
|
34005
|
+
originalPriceTakeProfit: data.originalPriceTakeProfit,
|
|
34006
|
+
originalPriceStopLoss: data.originalPriceStopLoss,
|
|
34007
|
+
scheduledAt: data.scheduledAt,
|
|
34008
|
+
pendingAt: data.pendingAt,
|
|
34009
|
+
createdAt: data.timestamp,
|
|
34010
|
+
};
|
|
34011
|
+
}
|
|
34012
|
+
if (data.action === "trailing-stop") {
|
|
34013
|
+
return {
|
|
34014
|
+
type: "trailing_stop.commit",
|
|
34015
|
+
id: CREATE_KEY_FN$1(),
|
|
34016
|
+
timestamp: data.timestamp,
|
|
34017
|
+
backtest: data.backtest,
|
|
34018
|
+
symbol: data.symbol,
|
|
34019
|
+
strategyName: data.strategyName,
|
|
34020
|
+
exchangeName: data.exchangeName,
|
|
34021
|
+
signalId: data.signalId,
|
|
34022
|
+
percentShift: data.percentShift,
|
|
34023
|
+
currentPrice: data.currentPrice,
|
|
34024
|
+
position: data.position,
|
|
34025
|
+
priceOpen: data.priceOpen,
|
|
34026
|
+
priceTakeProfit: data.priceTakeProfit,
|
|
34027
|
+
priceStopLoss: data.priceStopLoss,
|
|
34028
|
+
originalPriceTakeProfit: data.originalPriceTakeProfit,
|
|
34029
|
+
originalPriceStopLoss: data.originalPriceStopLoss,
|
|
34030
|
+
scheduledAt: data.scheduledAt,
|
|
34031
|
+
pendingAt: data.pendingAt,
|
|
34032
|
+
createdAt: data.timestamp,
|
|
34033
|
+
};
|
|
34034
|
+
}
|
|
34035
|
+
if (data.action === "trailing-take") {
|
|
34036
|
+
return {
|
|
34037
|
+
type: "trailing_take.commit",
|
|
34038
|
+
id: CREATE_KEY_FN$1(),
|
|
34039
|
+
timestamp: data.timestamp,
|
|
34040
|
+
backtest: data.backtest,
|
|
34041
|
+
symbol: data.symbol,
|
|
34042
|
+
strategyName: data.strategyName,
|
|
34043
|
+
exchangeName: data.exchangeName,
|
|
34044
|
+
signalId: data.signalId,
|
|
34045
|
+
percentShift: data.percentShift,
|
|
34046
|
+
currentPrice: data.currentPrice,
|
|
34047
|
+
position: data.position,
|
|
34048
|
+
priceOpen: data.priceOpen,
|
|
34049
|
+
priceTakeProfit: data.priceTakeProfit,
|
|
34050
|
+
priceStopLoss: data.priceStopLoss,
|
|
34051
|
+
originalPriceTakeProfit: data.originalPriceTakeProfit,
|
|
34052
|
+
originalPriceStopLoss: data.originalPriceStopLoss,
|
|
34053
|
+
scheduledAt: data.scheduledAt,
|
|
34054
|
+
pendingAt: data.pendingAt,
|
|
34055
|
+
createdAt: data.timestamp,
|
|
34056
|
+
};
|
|
34057
|
+
}
|
|
34058
|
+
if (data.action === "activate-scheduled") {
|
|
34059
|
+
return {
|
|
34060
|
+
type: "activate_scheduled.commit",
|
|
34061
|
+
id: CREATE_KEY_FN$1(),
|
|
34062
|
+
timestamp: data.timestamp,
|
|
34063
|
+
backtest: data.backtest,
|
|
34064
|
+
symbol: data.symbol,
|
|
34065
|
+
strategyName: data.strategyName,
|
|
34066
|
+
exchangeName: data.exchangeName,
|
|
34067
|
+
signalId: data.signalId,
|
|
34068
|
+
activateId: data.activateId,
|
|
34069
|
+
currentPrice: data.currentPrice,
|
|
34070
|
+
position: data.position,
|
|
34071
|
+
priceOpen: data.priceOpen,
|
|
34072
|
+
priceTakeProfit: data.priceTakeProfit,
|
|
34073
|
+
priceStopLoss: data.priceStopLoss,
|
|
34074
|
+
originalPriceTakeProfit: data.originalPriceTakeProfit,
|
|
34075
|
+
originalPriceStopLoss: data.originalPriceStopLoss,
|
|
34076
|
+
scheduledAt: data.scheduledAt,
|
|
34077
|
+
pendingAt: data.pendingAt,
|
|
34078
|
+
createdAt: data.timestamp,
|
|
34079
|
+
};
|
|
34080
|
+
}
|
|
34081
|
+
return null;
|
|
33736
34082
|
};
|
|
33737
34083
|
/**
|
|
33738
|
-
* Creates
|
|
33739
|
-
*
|
|
33740
|
-
*
|
|
33741
|
-
|
|
33742
|
-
|
|
33743
|
-
|
|
33744
|
-
|
|
33745
|
-
|
|
33746
|
-
|
|
33747
|
-
|
|
33748
|
-
|
|
33749
|
-
|
|
33750
|
-
|
|
33751
|
-
|
|
33752
|
-
|
|
33753
|
-
|
|
33754
|
-
|
|
33755
|
-
|
|
34084
|
+
* Creates a notification model for risk rejection events.
|
|
34085
|
+
* @param data - The risk contract data
|
|
34086
|
+
* @returns NotificationModel for risk rejection event
|
|
34087
|
+
*/
|
|
34088
|
+
const CREATE_RISK_NOTIFICATION_FN = (data) => ({
|
|
34089
|
+
type: "risk.rejection",
|
|
34090
|
+
id: CREATE_KEY_FN$1(),
|
|
34091
|
+
timestamp: data.timestamp,
|
|
34092
|
+
backtest: data.backtest,
|
|
34093
|
+
symbol: data.symbol,
|
|
34094
|
+
strategyName: data.strategyName,
|
|
34095
|
+
exchangeName: data.exchangeName,
|
|
34096
|
+
rejectionNote: data.rejectionNote,
|
|
34097
|
+
rejectionId: data.rejectionId,
|
|
34098
|
+
activePositionCount: data.activePositionCount,
|
|
34099
|
+
currentPrice: data.currentPrice,
|
|
34100
|
+
signalId: data.currentSignal.id,
|
|
34101
|
+
position: data.currentSignal.position,
|
|
34102
|
+
priceOpen: data.currentSignal.priceOpen,
|
|
34103
|
+
priceTakeProfit: data.currentSignal.priceTakeProfit,
|
|
34104
|
+
priceStopLoss: data.currentSignal.priceStopLoss,
|
|
34105
|
+
minuteEstimatedTime: data.currentSignal.minuteEstimatedTime,
|
|
34106
|
+
signalNote: data.currentSignal.note,
|
|
34107
|
+
createdAt: data.timestamp,
|
|
34108
|
+
});
|
|
33756
34109
|
/**
|
|
33757
|
-
*
|
|
33758
|
-
*
|
|
33759
|
-
*
|
|
33760
|
-
* sinceTimestamp + i * stepMs for i = 0..limit-1
|
|
33761
|
-
* Returns all candles if found, null if any missing.
|
|
33762
|
-
*
|
|
33763
|
-
* @param dto - Data transfer object containing symbol, interval, and limit
|
|
33764
|
-
* @param sinceTimestamp - Aligned start timestamp (openTime of first candle)
|
|
33765
|
-
* @param untilTimestamp - Unused, kept for API compatibility
|
|
33766
|
-
* @param exchangeName - Exchange name
|
|
33767
|
-
* @returns Cached candles array (exactly limit) or null if cache miss
|
|
34110
|
+
* Creates a notification model for error events.
|
|
34111
|
+
* @param error - The error object
|
|
34112
|
+
* @returns NotificationModel for error event
|
|
33768
34113
|
*/
|
|
33769
|
-
const
|
|
33770
|
-
|
|
33771
|
-
|
|
33772
|
-
|
|
33773
|
-
|
|
33774
|
-
|
|
33775
|
-
if (cachedCandles?.length === dto.limit) {
|
|
33776
|
-
bt.loggerService.debug(`ExchangeInstance READ_CANDLES_CACHE_FN: cache hit for exchangeName=${exchangeName}, symbol=${dto.symbol}, interval=${dto.interval}, limit=${dto.limit}`);
|
|
33777
|
-
return cachedCandles;
|
|
33778
|
-
}
|
|
33779
|
-
bt.loggerService.warn(`ExchangeInstance READ_CANDLES_CACHE_FN: cache inconsistent (count or range mismatch) for exchangeName=${exchangeName}, symbol=${dto.symbol}, interval=${dto.interval}, limit=${dto.limit}`);
|
|
33780
|
-
return null;
|
|
33781
|
-
}, {
|
|
33782
|
-
fallback: async (error) => {
|
|
33783
|
-
const message = `ExchangeInstance READ_CANDLES_CACHE_FN: cache read failed`;
|
|
33784
|
-
const payload = {
|
|
33785
|
-
error: functoolsKit.errorData(error),
|
|
33786
|
-
message: functoolsKit.getErrorMessage(error),
|
|
33787
|
-
};
|
|
33788
|
-
bt.loggerService.warn(message, payload);
|
|
33789
|
-
console.warn(message, payload);
|
|
33790
|
-
errorEmitter.next(error);
|
|
33791
|
-
},
|
|
33792
|
-
defaultValue: null,
|
|
34114
|
+
const CREATE_ERROR_NOTIFICATION_FN = (error) => ({
|
|
34115
|
+
type: "error.info",
|
|
34116
|
+
id: CREATE_KEY_FN$1(),
|
|
34117
|
+
error: functoolsKit.errorData(error),
|
|
34118
|
+
message: functoolsKit.getErrorMessage(error),
|
|
34119
|
+
backtest: false,
|
|
33793
34120
|
});
|
|
33794
34121
|
/**
|
|
33795
|
-
*
|
|
33796
|
-
*
|
|
33797
|
-
*
|
|
33798
|
-
* - First candle.timestamp equals aligned sinceTimestamp (openTime)
|
|
33799
|
-
* - Exact number of candles as requested (limit)
|
|
33800
|
-
* - Sequential timestamps: sinceTimestamp + i * stepMs
|
|
33801
|
-
*
|
|
33802
|
-
* @param candles - Array of validated candle data to cache
|
|
33803
|
-
* @param dto - Data transfer object containing symbol, interval, and limit
|
|
33804
|
-
* @param exchangeName - Exchange name
|
|
34122
|
+
* Creates a notification model for critical error events.
|
|
34123
|
+
* @param error - The error object
|
|
34124
|
+
* @returns NotificationModel for critical error event
|
|
33805
34125
|
*/
|
|
33806
|
-
const
|
|
33807
|
-
|
|
33808
|
-
|
|
33809
|
-
|
|
33810
|
-
|
|
33811
|
-
|
|
33812
|
-
const payload = {
|
|
33813
|
-
error: functoolsKit.errorData(error),
|
|
33814
|
-
message: functoolsKit.getErrorMessage(error),
|
|
33815
|
-
};
|
|
33816
|
-
bt.loggerService.warn(message, payload);
|
|
33817
|
-
console.warn(message, payload);
|
|
33818
|
-
errorEmitter.next(error);
|
|
33819
|
-
},
|
|
33820
|
-
defaultValue: null,
|
|
34126
|
+
const CREATE_CRITICAL_ERROR_NOTIFICATION_FN = (error) => ({
|
|
34127
|
+
type: "error.critical",
|
|
34128
|
+
id: CREATE_KEY_FN$1(),
|
|
34129
|
+
error: functoolsKit.errorData(error),
|
|
34130
|
+
message: functoolsKit.getErrorMessage(error),
|
|
34131
|
+
backtest: false,
|
|
33821
34132
|
});
|
|
33822
34133
|
/**
|
|
33823
|
-
*
|
|
33824
|
-
*
|
|
33825
|
-
*
|
|
33826
|
-
|
|
33827
|
-
|
|
34134
|
+
* Creates a notification model for validation error events.
|
|
34135
|
+
* @param error - The error object
|
|
34136
|
+
* @returns NotificationModel for validation error event
|
|
34137
|
+
*/
|
|
34138
|
+
const CREATE_VALIDATION_ERROR_NOTIFICATION_FN = (error) => ({
|
|
34139
|
+
type: "error.validation",
|
|
34140
|
+
id: CREATE_KEY_FN$1(),
|
|
34141
|
+
error: functoolsKit.errorData(error),
|
|
34142
|
+
message: functoolsKit.getErrorMessage(error),
|
|
34143
|
+
backtest: false,
|
|
34144
|
+
});
|
|
34145
|
+
const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_SIGNAL = "NotificationMemoryBacktestUtils.handleSignal";
|
|
34146
|
+
const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_PARTIAL_PROFIT = "NotificationMemoryBacktestUtils.handlePartialProfit";
|
|
34147
|
+
const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_PARTIAL_LOSS = "NotificationMemoryBacktestUtils.handlePartialLoss";
|
|
34148
|
+
const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_BREAKEVEN = "NotificationMemoryBacktestUtils.handleBreakeven";
|
|
34149
|
+
const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_STRATEGY_COMMIT = "NotificationMemoryBacktestUtils.handleStrategyCommit";
|
|
34150
|
+
const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_RISK = "NotificationMemoryBacktestUtils.handleRisk";
|
|
34151
|
+
const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_ERROR = "NotificationMemoryBacktestUtils.handleError";
|
|
34152
|
+
const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_CRITICAL_ERROR = "NotificationMemoryBacktestUtils.handleCriticalError";
|
|
34153
|
+
const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_VALIDATION_ERROR = "NotificationMemoryBacktestUtils.handleValidationError";
|
|
34154
|
+
const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_GET_DATA = "NotificationMemoryBacktestUtils.getData";
|
|
34155
|
+
const NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_CLEAR = "NotificationMemoryBacktestUtils.clear";
|
|
34156
|
+
const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_SIGNAL = "NotificationMemoryLiveUtils.handleSignal";
|
|
34157
|
+
const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_PARTIAL_PROFIT = "NotificationMemoryLiveUtils.handlePartialProfit";
|
|
34158
|
+
const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_PARTIAL_LOSS = "NotificationMemoryLiveUtils.handlePartialLoss";
|
|
34159
|
+
const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_BREAKEVEN = "NotificationMemoryLiveUtils.handleBreakeven";
|
|
34160
|
+
const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_STRATEGY_COMMIT = "NotificationMemoryLiveUtils.handleStrategyCommit";
|
|
34161
|
+
const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_RISK = "NotificationMemoryLiveUtils.handleRisk";
|
|
34162
|
+
const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_ERROR = "NotificationMemoryLiveUtils.handleError";
|
|
34163
|
+
const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_CRITICAL_ERROR = "NotificationMemoryLiveUtils.handleCriticalError";
|
|
34164
|
+
const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_VALIDATION_ERROR = "NotificationMemoryLiveUtils.handleValidationError";
|
|
34165
|
+
const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_GET_DATA = "NotificationMemoryLiveUtils.getData";
|
|
34166
|
+
const NOTIFICATION_MEMORY_LIVE_METHOD_NAME_CLEAR = "NotificationMemoryLiveUtils.clear";
|
|
34167
|
+
const NOTIFICATION_ADAPTER_METHOD_NAME_ENABLE = "NotificationAdapter.enable";
|
|
34168
|
+
const NOTIFICATION_ADAPTER_METHOD_NAME_DISABLE = "NotificationAdapter.disable";
|
|
34169
|
+
const NOTIFICATION_ADAPTER_METHOD_NAME_GET_DATA_BACKTEST = "NotificationAdapter.getDataBacktest";
|
|
34170
|
+
const NOTIFICATION_ADAPTER_METHOD_NAME_GET_DATA_LIVE = "NotificationAdapter.getDataLive";
|
|
34171
|
+
const NOTIFICATION_ADAPTER_METHOD_NAME_CLEAR_BACKTEST = "NotificationAdapter.clearBacktest";
|
|
34172
|
+
const NOTIFICATION_ADAPTER_METHOD_NAME_CLEAR_LIVE = "NotificationAdapter.clearLive";
|
|
34173
|
+
const NOTIFICATION_BACKTEST_ADAPTER_METHOD_NAME_USE_ADAPTER = "NotificationBacktestAdapter.useNotificationAdapter";
|
|
34174
|
+
const NOTIFICATION_BACKTEST_ADAPTER_METHOD_NAME_USE_DUMMY = "NotificationBacktestAdapter.useDummy";
|
|
34175
|
+
const NOTIFICATION_BACKTEST_ADAPTER_METHOD_NAME_USE_MEMORY = "NotificationBacktestAdapter.useMemory";
|
|
34176
|
+
const NOTIFICATION_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST = "NotificationBacktestAdapter.usePersist";
|
|
34177
|
+
const NOTIFICATION_LIVE_ADAPTER_METHOD_NAME_USE_ADAPTER = "NotificationLiveAdapter.useNotificationAdapter";
|
|
34178
|
+
const NOTIFICATION_LIVE_ADAPTER_METHOD_NAME_USE_DUMMY = "NotificationLiveAdapter.useDummy";
|
|
34179
|
+
const NOTIFICATION_LIVE_ADAPTER_METHOD_NAME_USE_MEMORY = "NotificationLiveAdapter.useMemory";
|
|
34180
|
+
const NOTIFICATION_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST = "NotificationLiveAdapter.usePersist";
|
|
34181
|
+
const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_WAIT_FOR_INIT = "NotificationPersistBacktestUtils.waitForInit";
|
|
34182
|
+
const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_UPDATE_NOTIFICATIONS = "NotificationPersistBacktestUtils._updateNotifications";
|
|
34183
|
+
const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_SIGNAL = "NotificationPersistBacktestUtils.handleSignal";
|
|
34184
|
+
const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_PARTIAL_PROFIT = "NotificationPersistBacktestUtils.handlePartialProfit";
|
|
34185
|
+
const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_PARTIAL_LOSS = "NotificationPersistBacktestUtils.handlePartialLoss";
|
|
34186
|
+
const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_BREAKEVEN = "NotificationPersistBacktestUtils.handleBreakeven";
|
|
34187
|
+
const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_STRATEGY_COMMIT = "NotificationPersistBacktestUtils.handleStrategyCommit";
|
|
34188
|
+
const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_RISK = "NotificationPersistBacktestUtils.handleRisk";
|
|
34189
|
+
const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_ERROR = "NotificationPersistBacktestUtils.handleError";
|
|
34190
|
+
const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_CRITICAL_ERROR = "NotificationPersistBacktestUtils.handleCriticalError";
|
|
34191
|
+
const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_VALIDATION_ERROR = "NotificationPersistBacktestUtils.handleValidationError";
|
|
34192
|
+
const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_GET_DATA = "NotificationPersistBacktestUtils.getData";
|
|
34193
|
+
const NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_CLEAR = "NotificationPersistBacktestUtils.clear";
|
|
34194
|
+
const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_WAIT_FOR_INIT = "NotificationPersistLiveUtils.waitForInit";
|
|
34195
|
+
const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_UPDATE_NOTIFICATIONS = "NotificationPersistLiveUtils._updateNotifications";
|
|
34196
|
+
const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_SIGNAL = "NotificationPersistLiveUtils.handleSignal";
|
|
34197
|
+
const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_PARTIAL_PROFIT = "NotificationPersistLiveUtils.handlePartialProfit";
|
|
34198
|
+
const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_PARTIAL_LOSS = "NotificationPersistLiveUtils.handlePartialLoss";
|
|
34199
|
+
const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_BREAKEVEN = "NotificationPersistLiveUtils.handleBreakeven";
|
|
34200
|
+
const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_STRATEGY_COMMIT = "NotificationPersistLiveUtils.handleStrategyCommit";
|
|
34201
|
+
const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_RISK = "NotificationPersistLiveUtils.handleRisk";
|
|
34202
|
+
const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_ERROR = "NotificationPersistLiveUtils.handleError";
|
|
34203
|
+
const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_CRITICAL_ERROR = "NotificationPersistLiveUtils.handleCriticalError";
|
|
34204
|
+
const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_VALIDATION_ERROR = "NotificationPersistLiveUtils.handleValidationError";
|
|
34205
|
+
const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_GET_DATA = "NotificationPersistLiveUtils.getData";
|
|
34206
|
+
const NOTIFICATION_PERSIST_LIVE_METHOD_NAME_CLEAR = "NotificationPersistLiveUtils.clear";
|
|
34207
|
+
/**
|
|
34208
|
+
* In-memory notification adapter for backtest signals.
|
|
33828
34209
|
*
|
|
33829
|
-
*
|
|
33830
|
-
*
|
|
33831
|
-
*
|
|
34210
|
+
* Features:
|
|
34211
|
+
* - Stores notifications in memory only (no persistence)
|
|
34212
|
+
* - Fast read/write operations
|
|
34213
|
+
* - Data is lost when application restarts
|
|
34214
|
+
* - Maintains up to MAX_NOTIFICATIONS (250) most recent notifications
|
|
34215
|
+
* - Handles all notification types: signals, partial profit/loss, breakeven, risk, errors
|
|
33832
34216
|
*
|
|
33833
|
-
*
|
|
33834
|
-
* const vwap = await instance.getAveragePrice("BTCUSDT");
|
|
33835
|
-
* const formattedQty = await instance.formatQuantity("BTCUSDT", 0.001);
|
|
33836
|
-
* const formattedPrice = await instance.formatPrice("BTCUSDT", 50000.123);
|
|
33837
|
-
* ```
|
|
34217
|
+
* Use this adapter for testing or when persistence is not required.
|
|
33838
34218
|
*/
|
|
33839
|
-
class
|
|
33840
|
-
|
|
33841
|
-
|
|
33842
|
-
|
|
33843
|
-
* @param exchangeName - Exchange name (e.g., "binance")
|
|
33844
|
-
*/
|
|
33845
|
-
constructor(exchangeName) {
|
|
33846
|
-
this.exchangeName = exchangeName;
|
|
34219
|
+
class NotificationMemoryBacktestUtils {
|
|
34220
|
+
constructor() {
|
|
34221
|
+
/** Array of notification models */
|
|
34222
|
+
this._notifications = [];
|
|
33847
34223
|
/**
|
|
33848
|
-
*
|
|
33849
|
-
*
|
|
33850
|
-
*
|
|
33851
|
-
* Uses the same logic as ClientExchange to ensure backwards compatibility.
|
|
33852
|
-
*
|
|
33853
|
-
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
33854
|
-
* @param interval - Candle time interval (e.g., "1m", "1h")
|
|
33855
|
-
* @param limit - Maximum number of candles to fetch
|
|
33856
|
-
* @returns Promise resolving to array of OHLCV candle data
|
|
33857
|
-
*
|
|
33858
|
-
* @example
|
|
33859
|
-
* ```typescript
|
|
33860
|
-
* const instance = new ExchangeInstance("binance");
|
|
33861
|
-
* const candles = await instance.getCandles("BTCUSDT", "1m", 100);
|
|
33862
|
-
* ```
|
|
34224
|
+
* Handles signal events.
|
|
34225
|
+
* Creates and stores notification for opened, closed, scheduled, cancelled signals.
|
|
34226
|
+
* @param data - The strategy tick result data
|
|
33863
34227
|
*/
|
|
33864
|
-
this.
|
|
33865
|
-
bt.loggerService.info(
|
|
33866
|
-
|
|
33867
|
-
|
|
33868
|
-
interval,
|
|
33869
|
-
limit,
|
|
34228
|
+
this.handleSignal = async (data) => {
|
|
34229
|
+
bt.loggerService.info(NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_SIGNAL, {
|
|
34230
|
+
signalId: data.signal.id,
|
|
34231
|
+
action: data.action,
|
|
33870
34232
|
});
|
|
33871
|
-
const
|
|
33872
|
-
|
|
33873
|
-
|
|
33874
|
-
throw new Error(`ExchangeInstance unknown interval=${interval}`);
|
|
33875
|
-
}
|
|
33876
|
-
const stepMs = step * MS_PER_MINUTE;
|
|
33877
|
-
// Align when down to interval boundary
|
|
33878
|
-
const when = await GET_TIMESTAMP_FN();
|
|
33879
|
-
const whenTimestamp = when.getTime();
|
|
33880
|
-
const alignedWhen = ALIGN_TO_INTERVAL_FN(whenTimestamp, step);
|
|
33881
|
-
// Calculate since: go back limit candles from aligned when
|
|
33882
|
-
const sinceTimestamp = alignedWhen - limit * stepMs;
|
|
33883
|
-
const since = new Date(sinceTimestamp);
|
|
33884
|
-
const untilTimestamp = alignedWhen;
|
|
33885
|
-
// Try to read from cache first
|
|
33886
|
-
const cachedCandles = await READ_CANDLES_CACHE_FN({ symbol, interval, limit }, sinceTimestamp, untilTimestamp, this.exchangeName);
|
|
33887
|
-
if (cachedCandles !== null) {
|
|
33888
|
-
return cachedCandles;
|
|
33889
|
-
}
|
|
33890
|
-
let allData = [];
|
|
33891
|
-
// If limit exceeds CC_MAX_CANDLES_PER_REQUEST, fetch data in chunks
|
|
33892
|
-
if (limit > GLOBAL_CONFIG.CC_MAX_CANDLES_PER_REQUEST) {
|
|
33893
|
-
let remaining = limit;
|
|
33894
|
-
let currentSince = new Date(since.getTime());
|
|
33895
|
-
const isBacktest = await GET_BACKTEST_FN();
|
|
33896
|
-
while (remaining > 0) {
|
|
33897
|
-
const chunkLimit = Math.min(remaining, GLOBAL_CONFIG.CC_MAX_CANDLES_PER_REQUEST);
|
|
33898
|
-
const chunkData = await getCandles(symbol, interval, currentSince, chunkLimit, isBacktest);
|
|
33899
|
-
allData.push(...chunkData);
|
|
33900
|
-
remaining -= chunkLimit;
|
|
33901
|
-
if (remaining > 0) {
|
|
33902
|
-
// Move currentSince forward by the number of candles fetched
|
|
33903
|
-
currentSince = new Date(currentSince.getTime() + chunkLimit * stepMs);
|
|
33904
|
-
}
|
|
33905
|
-
}
|
|
33906
|
-
}
|
|
33907
|
-
else {
|
|
33908
|
-
const isBacktest = await GET_BACKTEST_FN();
|
|
33909
|
-
allData = await getCandles(symbol, interval, since, limit, isBacktest);
|
|
33910
|
-
}
|
|
33911
|
-
// Apply distinct by timestamp to remove duplicates
|
|
33912
|
-
const uniqueData = Array.from(new Map(allData.map((candle) => [candle.timestamp, candle])).values());
|
|
33913
|
-
if (allData.length !== uniqueData.length) {
|
|
33914
|
-
bt.loggerService.warn(`ExchangeInstance getCandles: Removed ${allData.length - uniqueData.length} duplicate candles by timestamp`);
|
|
33915
|
-
}
|
|
33916
|
-
// Validate adapter returned data
|
|
33917
|
-
if (uniqueData.length === 0) {
|
|
33918
|
-
throw new Error(`ExchangeInstance getCandles: adapter returned empty array. ` +
|
|
33919
|
-
`Expected ${limit} candles starting from openTime=${sinceTimestamp}.`);
|
|
33920
|
-
}
|
|
33921
|
-
if (uniqueData[0].timestamp !== sinceTimestamp) {
|
|
33922
|
-
throw new Error(`ExchangeInstance getCandles: first candle timestamp mismatch. ` +
|
|
33923
|
-
`Expected openTime=${sinceTimestamp}, got=${uniqueData[0].timestamp}. ` +
|
|
33924
|
-
`Adapter must return candles with timestamp=openTime, starting from aligned since.`);
|
|
33925
|
-
}
|
|
33926
|
-
if (uniqueData.length !== limit) {
|
|
33927
|
-
throw new Error(`ExchangeInstance getCandles: candle count mismatch. ` +
|
|
33928
|
-
`Expected ${limit} candles, got ${uniqueData.length}. ` +
|
|
33929
|
-
`Adapter must return exact number of candles requested.`);
|
|
34233
|
+
const notification = CREATE_SIGNAL_NOTIFICATION_FN(data);
|
|
34234
|
+
if (notification) {
|
|
34235
|
+
this._addNotification(notification);
|
|
33930
34236
|
}
|
|
33931
|
-
// Write to cache after successful fetch
|
|
33932
|
-
await WRITE_CANDLES_CACHE_FN(uniqueData, { symbol, interval, limit }, this.exchangeName);
|
|
33933
|
-
return uniqueData;
|
|
33934
34237
|
};
|
|
33935
34238
|
/**
|
|
33936
|
-
*
|
|
33937
|
-
*
|
|
33938
|
-
*
|
|
33939
|
-
* Formula:
|
|
33940
|
-
* - Typical Price = (high + low + close) / 3
|
|
33941
|
-
* - VWAP = sum(typical_price * volume) / sum(volume)
|
|
33942
|
-
*
|
|
33943
|
-
* If volume is zero, returns simple average of close prices.
|
|
33944
|
-
*
|
|
33945
|
-
* @param symbol - Trading pair symbol
|
|
33946
|
-
* @returns Promise resolving to VWAP price
|
|
33947
|
-
* @throws Error if no candles available
|
|
33948
|
-
*
|
|
33949
|
-
* @example
|
|
33950
|
-
* ```typescript
|
|
33951
|
-
* const instance = new ExchangeInstance("binance");
|
|
33952
|
-
* const vwap = await instance.getAveragePrice("BTCUSDT");
|
|
33953
|
-
* console.log(vwap); // 50125.43
|
|
33954
|
-
* ```
|
|
34239
|
+
* Handles partial profit availability event.
|
|
34240
|
+
* @param data - The partial profit contract data
|
|
33955
34241
|
*/
|
|
33956
|
-
this.
|
|
33957
|
-
bt.loggerService.
|
|
33958
|
-
|
|
33959
|
-
|
|
34242
|
+
this.handlePartialProfit = async (data) => {
|
|
34243
|
+
bt.loggerService.info(NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_PARTIAL_PROFIT, {
|
|
34244
|
+
signalId: data.data.id,
|
|
34245
|
+
level: data.level,
|
|
33960
34246
|
});
|
|
33961
|
-
|
|
34247
|
+
this._addNotification(CREATE_PARTIAL_PROFIT_NOTIFICATION_FN(data));
|
|
34248
|
+
};
|
|
34249
|
+
/**
|
|
34250
|
+
* Handles partial loss availability event.
|
|
34251
|
+
* @param data - The partial loss contract data
|
|
34252
|
+
*/
|
|
34253
|
+
this.handlePartialLoss = async (data) => {
|
|
34254
|
+
bt.loggerService.info(NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_PARTIAL_LOSS, {
|
|
34255
|
+
signalId: data.data.id,
|
|
34256
|
+
level: data.level,
|
|
34257
|
+
});
|
|
34258
|
+
this._addNotification(CREATE_PARTIAL_LOSS_NOTIFICATION_FN(data));
|
|
34259
|
+
};
|
|
34260
|
+
/**
|
|
34261
|
+
* Handles breakeven availability event.
|
|
34262
|
+
* @param data - The breakeven contract data
|
|
34263
|
+
*/
|
|
34264
|
+
this.handleBreakeven = async (data) => {
|
|
34265
|
+
bt.loggerService.info(NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_BREAKEVEN, {
|
|
34266
|
+
signalId: data.data.id,
|
|
34267
|
+
});
|
|
34268
|
+
this._addNotification(CREATE_BREAKEVEN_NOTIFICATION_FN(data));
|
|
34269
|
+
};
|
|
34270
|
+
/**
|
|
34271
|
+
* Handles strategy commit events.
|
|
34272
|
+
* @param data - The strategy commit contract data
|
|
34273
|
+
*/
|
|
34274
|
+
this.handleStrategyCommit = async (data) => {
|
|
34275
|
+
bt.loggerService.info(NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_STRATEGY_COMMIT, {
|
|
34276
|
+
signalId: data.signalId,
|
|
34277
|
+
action: data.action,
|
|
34278
|
+
});
|
|
34279
|
+
const notification = CREATE_STRATEGY_COMMIT_NOTIFICATION_FN(data);
|
|
34280
|
+
if (notification) {
|
|
34281
|
+
this._addNotification(notification);
|
|
34282
|
+
}
|
|
34283
|
+
};
|
|
34284
|
+
/**
|
|
34285
|
+
* Handles risk rejection event.
|
|
34286
|
+
* @param data - The risk contract data
|
|
34287
|
+
*/
|
|
34288
|
+
this.handleRisk = async (data) => {
|
|
34289
|
+
bt.loggerService.info(NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_RISK, {
|
|
34290
|
+
signalId: data.currentSignal.id,
|
|
34291
|
+
rejectionId: data.rejectionId,
|
|
34292
|
+
});
|
|
34293
|
+
this._addNotification(CREATE_RISK_NOTIFICATION_FN(data));
|
|
34294
|
+
};
|
|
34295
|
+
/**
|
|
34296
|
+
* Handles error event.
|
|
34297
|
+
* @param error - The error object
|
|
34298
|
+
*/
|
|
34299
|
+
this.handleError = async (error) => {
|
|
34300
|
+
bt.loggerService.info(NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_ERROR, {
|
|
34301
|
+
message: functoolsKit.getErrorMessage(error),
|
|
34302
|
+
});
|
|
34303
|
+
this._addNotification(CREATE_ERROR_NOTIFICATION_FN(error));
|
|
34304
|
+
};
|
|
34305
|
+
/**
|
|
34306
|
+
* Handles critical error event.
|
|
34307
|
+
* @param error - The error object
|
|
34308
|
+
*/
|
|
34309
|
+
this.handleCriticalError = async (error) => {
|
|
34310
|
+
bt.loggerService.info(NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_CRITICAL_ERROR, {
|
|
34311
|
+
message: functoolsKit.getErrorMessage(error),
|
|
34312
|
+
});
|
|
34313
|
+
this._addNotification(CREATE_CRITICAL_ERROR_NOTIFICATION_FN(error));
|
|
34314
|
+
};
|
|
34315
|
+
/**
|
|
34316
|
+
* Handles validation error event.
|
|
34317
|
+
* @param error - The error object
|
|
34318
|
+
*/
|
|
34319
|
+
this.handleValidationError = async (error) => {
|
|
34320
|
+
bt.loggerService.info(NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_VALIDATION_ERROR, {
|
|
34321
|
+
message: functoolsKit.getErrorMessage(error),
|
|
34322
|
+
});
|
|
34323
|
+
this._addNotification(CREATE_VALIDATION_ERROR_NOTIFICATION_FN(error));
|
|
34324
|
+
};
|
|
34325
|
+
/**
|
|
34326
|
+
* Gets all stored notifications.
|
|
34327
|
+
* @returns Copy of notifications array
|
|
34328
|
+
*/
|
|
34329
|
+
this.getData = async () => {
|
|
34330
|
+
bt.loggerService.info(NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_GET_DATA);
|
|
34331
|
+
return [...this._notifications];
|
|
34332
|
+
};
|
|
34333
|
+
/**
|
|
34334
|
+
* Clears all stored notifications.
|
|
34335
|
+
*/
|
|
34336
|
+
this.clear = async () => {
|
|
34337
|
+
bt.loggerService.info(NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_CLEAR);
|
|
34338
|
+
this._notifications = [];
|
|
34339
|
+
};
|
|
34340
|
+
}
|
|
34341
|
+
/**
|
|
34342
|
+
* Adds a notification to the beginning of the list.
|
|
34343
|
+
* Removes oldest notification if limit is exceeded.
|
|
34344
|
+
* @param notification - The notification model to add
|
|
34345
|
+
*/
|
|
34346
|
+
_addNotification(notification) {
|
|
34347
|
+
this._notifications.unshift(notification);
|
|
34348
|
+
if (this._notifications.length > MAX_NOTIFICATIONS) {
|
|
34349
|
+
this._notifications.pop();
|
|
34350
|
+
}
|
|
34351
|
+
}
|
|
34352
|
+
}
|
|
34353
|
+
/**
|
|
34354
|
+
* Dummy notification adapter for backtest signals that discards all writes.
|
|
34355
|
+
*
|
|
34356
|
+
* Features:
|
|
34357
|
+
* - No-op implementation for all methods
|
|
34358
|
+
* - getData always returns empty array
|
|
34359
|
+
*
|
|
34360
|
+
* Use this adapter to disable backtest notification storage completely.
|
|
34361
|
+
*/
|
|
34362
|
+
class NotificationDummyBacktestUtils {
|
|
34363
|
+
constructor() {
|
|
34364
|
+
/**
|
|
34365
|
+
* No-op handler for signal events.
|
|
34366
|
+
*/
|
|
34367
|
+
this.handleSignal = async () => {
|
|
34368
|
+
};
|
|
34369
|
+
/**
|
|
34370
|
+
* No-op handler for partial profit event.
|
|
34371
|
+
*/
|
|
34372
|
+
this.handlePartialProfit = async () => {
|
|
34373
|
+
};
|
|
34374
|
+
/**
|
|
34375
|
+
* No-op handler for partial loss event.
|
|
34376
|
+
*/
|
|
34377
|
+
this.handlePartialLoss = async () => {
|
|
34378
|
+
};
|
|
34379
|
+
/**
|
|
34380
|
+
* No-op handler for breakeven event.
|
|
34381
|
+
*/
|
|
34382
|
+
this.handleBreakeven = async () => {
|
|
34383
|
+
};
|
|
34384
|
+
/**
|
|
34385
|
+
* No-op handler for strategy commit event.
|
|
34386
|
+
*/
|
|
34387
|
+
this.handleStrategyCommit = async () => {
|
|
34388
|
+
};
|
|
34389
|
+
/**
|
|
34390
|
+
* No-op handler for risk rejection event.
|
|
34391
|
+
*/
|
|
34392
|
+
this.handleRisk = async () => {
|
|
34393
|
+
};
|
|
34394
|
+
/**
|
|
34395
|
+
* No-op handler for error event.
|
|
34396
|
+
*/
|
|
34397
|
+
this.handleError = async () => {
|
|
34398
|
+
};
|
|
34399
|
+
/**
|
|
34400
|
+
* No-op handler for critical error event.
|
|
34401
|
+
*/
|
|
34402
|
+
this.handleCriticalError = async () => {
|
|
34403
|
+
};
|
|
34404
|
+
/**
|
|
34405
|
+
* No-op handler for validation error event.
|
|
34406
|
+
*/
|
|
34407
|
+
this.handleValidationError = async () => {
|
|
34408
|
+
};
|
|
34409
|
+
/**
|
|
34410
|
+
* Always returns empty array (no storage).
|
|
34411
|
+
* @returns Empty array
|
|
34412
|
+
*/
|
|
34413
|
+
this.getData = async () => {
|
|
34414
|
+
return [];
|
|
34415
|
+
};
|
|
34416
|
+
/**
|
|
34417
|
+
* No-op clear operation.
|
|
34418
|
+
*/
|
|
34419
|
+
this.clear = async () => {
|
|
34420
|
+
};
|
|
34421
|
+
}
|
|
34422
|
+
}
|
|
34423
|
+
/**
|
|
34424
|
+
* Persistent notification adapter for backtest signals.
|
|
34425
|
+
*
|
|
34426
|
+
* Features:
|
|
34427
|
+
* - Persists notifications to disk using PersistNotificationAdapter
|
|
34428
|
+
* - Lazy initialization with singleshot pattern
|
|
34429
|
+
* - Maintains up to MAX_NOTIFICATIONS (250) most recent notifications
|
|
34430
|
+
* - Handles all notification types: signals, partial profit/loss, breakeven, risk, errors
|
|
34431
|
+
*
|
|
34432
|
+
* Use this adapter (default) for backtest notification persistence across sessions.
|
|
34433
|
+
*/
|
|
34434
|
+
class NotificationPersistBacktestUtils {
|
|
34435
|
+
constructor() {
|
|
34436
|
+
/**
|
|
34437
|
+
* Singleshot initialization function that loads notifications from disk.
|
|
34438
|
+
* Protected by singleshot to ensure one-time execution.
|
|
34439
|
+
*/
|
|
34440
|
+
this.waitForInit = functoolsKit.singleshot(async () => {
|
|
34441
|
+
bt.loggerService.info(NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_WAIT_FOR_INIT);
|
|
34442
|
+
const notificationList = await PersistNotificationAdapter.readNotificationData(true);
|
|
34443
|
+
notificationList.sort((a, b) => {
|
|
34444
|
+
const aTime = 'createdAt' in a ? a.createdAt : 0;
|
|
34445
|
+
const bTime = 'createdAt' in b ? b.createdAt : 0;
|
|
34446
|
+
return aTime - bTime;
|
|
34447
|
+
});
|
|
34448
|
+
this._notifications = new Map(notificationList
|
|
34449
|
+
.slice(-MAX_NOTIFICATIONS)
|
|
34450
|
+
.map((notification) => [notification.id, notification]));
|
|
34451
|
+
});
|
|
34452
|
+
/**
|
|
34453
|
+
* Handles signal events.
|
|
34454
|
+
* Creates and stores notification for opened, closed, scheduled, cancelled signals.
|
|
34455
|
+
* @param data - The strategy tick result data
|
|
34456
|
+
*/
|
|
34457
|
+
this.handleSignal = async (data) => {
|
|
34458
|
+
bt.loggerService.info(NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_SIGNAL, {
|
|
34459
|
+
signalId: data.signal.id,
|
|
34460
|
+
action: data.action,
|
|
34461
|
+
});
|
|
34462
|
+
await this.waitForInit();
|
|
34463
|
+
const notification = CREATE_SIGNAL_NOTIFICATION_FN(data);
|
|
34464
|
+
if (notification) {
|
|
34465
|
+
this._addNotification(notification);
|
|
34466
|
+
await this._updateNotifications();
|
|
34467
|
+
}
|
|
34468
|
+
};
|
|
34469
|
+
/**
|
|
34470
|
+
* Handles partial profit availability event.
|
|
34471
|
+
* @param data - The partial profit contract data
|
|
34472
|
+
*/
|
|
34473
|
+
this.handlePartialProfit = async (data) => {
|
|
34474
|
+
bt.loggerService.info(NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_PARTIAL_PROFIT, {
|
|
34475
|
+
signalId: data.data.id,
|
|
34476
|
+
level: data.level,
|
|
34477
|
+
});
|
|
34478
|
+
await this.waitForInit();
|
|
34479
|
+
this._addNotification(CREATE_PARTIAL_PROFIT_NOTIFICATION_FN(data));
|
|
34480
|
+
await this._updateNotifications();
|
|
34481
|
+
};
|
|
34482
|
+
/**
|
|
34483
|
+
* Handles partial loss availability event.
|
|
34484
|
+
* @param data - The partial loss contract data
|
|
34485
|
+
*/
|
|
34486
|
+
this.handlePartialLoss = async (data) => {
|
|
34487
|
+
bt.loggerService.info(NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_PARTIAL_LOSS, {
|
|
34488
|
+
signalId: data.data.id,
|
|
34489
|
+
level: data.level,
|
|
34490
|
+
});
|
|
34491
|
+
await this.waitForInit();
|
|
34492
|
+
this._addNotification(CREATE_PARTIAL_LOSS_NOTIFICATION_FN(data));
|
|
34493
|
+
await this._updateNotifications();
|
|
34494
|
+
};
|
|
34495
|
+
/**
|
|
34496
|
+
* Handles breakeven availability event.
|
|
34497
|
+
* @param data - The breakeven contract data
|
|
34498
|
+
*/
|
|
34499
|
+
this.handleBreakeven = async (data) => {
|
|
34500
|
+
bt.loggerService.info(NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_BREAKEVEN, {
|
|
34501
|
+
signalId: data.data.id,
|
|
34502
|
+
});
|
|
34503
|
+
await this.waitForInit();
|
|
34504
|
+
this._addNotification(CREATE_BREAKEVEN_NOTIFICATION_FN(data));
|
|
34505
|
+
await this._updateNotifications();
|
|
34506
|
+
};
|
|
34507
|
+
/**
|
|
34508
|
+
* Handles strategy commit events.
|
|
34509
|
+
* @param data - The strategy commit contract data
|
|
34510
|
+
*/
|
|
34511
|
+
this.handleStrategyCommit = async (data) => {
|
|
34512
|
+
bt.loggerService.info(NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_STRATEGY_COMMIT, {
|
|
34513
|
+
signalId: data.signalId,
|
|
34514
|
+
action: data.action,
|
|
34515
|
+
});
|
|
34516
|
+
await this.waitForInit();
|
|
34517
|
+
const notification = CREATE_STRATEGY_COMMIT_NOTIFICATION_FN(data);
|
|
34518
|
+
if (notification) {
|
|
34519
|
+
this._addNotification(notification);
|
|
34520
|
+
await this._updateNotifications();
|
|
34521
|
+
}
|
|
34522
|
+
};
|
|
34523
|
+
/**
|
|
34524
|
+
* Handles risk rejection event.
|
|
34525
|
+
* @param data - The risk contract data
|
|
34526
|
+
*/
|
|
34527
|
+
this.handleRisk = async (data) => {
|
|
34528
|
+
bt.loggerService.info(NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_RISK, {
|
|
34529
|
+
signalId: data.currentSignal.id,
|
|
34530
|
+
rejectionId: data.rejectionId,
|
|
34531
|
+
});
|
|
34532
|
+
await this.waitForInit();
|
|
34533
|
+
this._addNotification(CREATE_RISK_NOTIFICATION_FN(data));
|
|
34534
|
+
await this._updateNotifications();
|
|
34535
|
+
};
|
|
34536
|
+
/**
|
|
34537
|
+
* Handles error event.
|
|
34538
|
+
* Note: Error notifications are not persisted to disk.
|
|
34539
|
+
* @param error - The error object
|
|
34540
|
+
*/
|
|
34541
|
+
this.handleError = async (error) => {
|
|
34542
|
+
bt.loggerService.info(NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_ERROR, {
|
|
34543
|
+
message: functoolsKit.getErrorMessage(error),
|
|
34544
|
+
});
|
|
34545
|
+
await this.waitForInit();
|
|
34546
|
+
this._addNotification(CREATE_ERROR_NOTIFICATION_FN(error));
|
|
34547
|
+
};
|
|
34548
|
+
/**
|
|
34549
|
+
* Handles critical error event.
|
|
34550
|
+
* Note: Error notifications are not persisted to disk.
|
|
34551
|
+
* @param error - The error object
|
|
34552
|
+
*/
|
|
34553
|
+
this.handleCriticalError = async (error) => {
|
|
34554
|
+
bt.loggerService.info(NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_CRITICAL_ERROR, {
|
|
34555
|
+
message: functoolsKit.getErrorMessage(error),
|
|
34556
|
+
});
|
|
34557
|
+
await this.waitForInit();
|
|
34558
|
+
this._addNotification(CREATE_CRITICAL_ERROR_NOTIFICATION_FN(error));
|
|
34559
|
+
};
|
|
34560
|
+
/**
|
|
34561
|
+
* Handles validation error event.
|
|
34562
|
+
* Note: Error notifications are not persisted to disk.
|
|
34563
|
+
* @param error - The error object
|
|
34564
|
+
*/
|
|
34565
|
+
this.handleValidationError = async (error) => {
|
|
34566
|
+
bt.loggerService.info(NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_VALIDATION_ERROR, {
|
|
34567
|
+
message: functoolsKit.getErrorMessage(error),
|
|
34568
|
+
});
|
|
34569
|
+
await this.waitForInit();
|
|
34570
|
+
this._addNotification(CREATE_VALIDATION_ERROR_NOTIFICATION_FN(error));
|
|
34571
|
+
};
|
|
34572
|
+
/**
|
|
34573
|
+
* Gets all stored notifications.
|
|
34574
|
+
* @returns Array of all notification models
|
|
34575
|
+
*/
|
|
34576
|
+
this.getData = async () => {
|
|
34577
|
+
bt.loggerService.info(NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_GET_DATA);
|
|
34578
|
+
await this.waitForInit();
|
|
34579
|
+
return Array.from(this._notifications.values());
|
|
34580
|
+
};
|
|
34581
|
+
/**
|
|
34582
|
+
* Clears all stored notifications.
|
|
34583
|
+
*/
|
|
34584
|
+
this.clear = async () => {
|
|
34585
|
+
bt.loggerService.info(NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_CLEAR);
|
|
34586
|
+
await this.waitForInit();
|
|
34587
|
+
this._notifications.clear();
|
|
34588
|
+
await this._updateNotifications();
|
|
34589
|
+
};
|
|
34590
|
+
}
|
|
34591
|
+
/**
|
|
34592
|
+
* Persists the current notification map to disk storage.
|
|
34593
|
+
* Sorts notifications by createdAt and keeps only the most recent MAX_NOTIFICATIONS.
|
|
34594
|
+
* @throws Error if not initialized
|
|
34595
|
+
*/
|
|
34596
|
+
async _updateNotifications() {
|
|
34597
|
+
bt.loggerService.info(NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_UPDATE_NOTIFICATIONS);
|
|
34598
|
+
if (!this._notifications) {
|
|
34599
|
+
throw new Error("NotificationPersistBacktestUtils not initialized. Call waitForInit first.");
|
|
34600
|
+
}
|
|
34601
|
+
const notificationList = Array.from(this._notifications.values());
|
|
34602
|
+
notificationList.sort((a, b) => {
|
|
34603
|
+
const aTime = 'createdAt' in a ? a.createdAt : 0;
|
|
34604
|
+
const bTime = 'createdAt' in b ? b.createdAt : 0;
|
|
34605
|
+
return aTime - bTime;
|
|
34606
|
+
});
|
|
34607
|
+
await PersistNotificationAdapter.writeNotificationData(notificationList.slice(-MAX_NOTIFICATIONS), true);
|
|
34608
|
+
}
|
|
34609
|
+
/**
|
|
34610
|
+
* Adds a notification to the map.
|
|
34611
|
+
* Removes oldest notification if limit is exceeded.
|
|
34612
|
+
* @param notification - The notification model to add
|
|
34613
|
+
*/
|
|
34614
|
+
_addNotification(notification) {
|
|
34615
|
+
this._notifications.set(notification.id, notification);
|
|
34616
|
+
if (this._notifications.size > MAX_NOTIFICATIONS) {
|
|
34617
|
+
const firstKey = this._notifications.keys().next().value;
|
|
34618
|
+
if (firstKey) {
|
|
34619
|
+
this._notifications.delete(firstKey);
|
|
34620
|
+
}
|
|
34621
|
+
}
|
|
34622
|
+
}
|
|
34623
|
+
}
|
|
34624
|
+
/**
|
|
34625
|
+
* In-memory notification adapter for live trading signals.
|
|
34626
|
+
*
|
|
34627
|
+
* Features:
|
|
34628
|
+
* - Stores notifications in memory only (no persistence)
|
|
34629
|
+
* - Fast read/write operations
|
|
34630
|
+
* - Data is lost when application restarts
|
|
34631
|
+
* - Maintains up to MAX_NOTIFICATIONS (250) most recent notifications
|
|
34632
|
+
* - Handles all notification types: signals, partial profit/loss, breakeven, risk, errors
|
|
34633
|
+
*
|
|
34634
|
+
* Use this adapter for testing or when persistence is not required.
|
|
34635
|
+
*/
|
|
34636
|
+
class NotificationMemoryLiveUtils {
|
|
34637
|
+
constructor() {
|
|
34638
|
+
/** Array of notification models */
|
|
34639
|
+
this._notifications = [];
|
|
34640
|
+
/**
|
|
34641
|
+
* Handles signal events.
|
|
34642
|
+
* Creates and stores notification for opened, closed, scheduled, cancelled signals.
|
|
34643
|
+
* @param data - The strategy tick result data
|
|
34644
|
+
*/
|
|
34645
|
+
this.handleSignal = async (data) => {
|
|
34646
|
+
bt.loggerService.info(NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_SIGNAL, {
|
|
34647
|
+
signalId: data.signal.id,
|
|
34648
|
+
action: data.action,
|
|
34649
|
+
});
|
|
34650
|
+
const notification = CREATE_SIGNAL_NOTIFICATION_FN(data);
|
|
34651
|
+
if (notification) {
|
|
34652
|
+
this._addNotification(notification);
|
|
34653
|
+
}
|
|
34654
|
+
};
|
|
34655
|
+
/**
|
|
34656
|
+
* Handles partial profit availability event.
|
|
34657
|
+
* @param data - The partial profit contract data
|
|
34658
|
+
*/
|
|
34659
|
+
this.handlePartialProfit = async (data) => {
|
|
34660
|
+
bt.loggerService.info(NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_PARTIAL_PROFIT, {
|
|
34661
|
+
signalId: data.data.id,
|
|
34662
|
+
level: data.level,
|
|
34663
|
+
});
|
|
34664
|
+
this._addNotification(CREATE_PARTIAL_PROFIT_NOTIFICATION_FN(data));
|
|
34665
|
+
};
|
|
34666
|
+
/**
|
|
34667
|
+
* Handles partial loss availability event.
|
|
34668
|
+
* @param data - The partial loss contract data
|
|
34669
|
+
*/
|
|
34670
|
+
this.handlePartialLoss = async (data) => {
|
|
34671
|
+
bt.loggerService.info(NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_PARTIAL_LOSS, {
|
|
34672
|
+
signalId: data.data.id,
|
|
34673
|
+
level: data.level,
|
|
34674
|
+
});
|
|
34675
|
+
this._addNotification(CREATE_PARTIAL_LOSS_NOTIFICATION_FN(data));
|
|
34676
|
+
};
|
|
34677
|
+
/**
|
|
34678
|
+
* Handles breakeven availability event.
|
|
34679
|
+
* @param data - The breakeven contract data
|
|
34680
|
+
*/
|
|
34681
|
+
this.handleBreakeven = async (data) => {
|
|
34682
|
+
bt.loggerService.info(NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_BREAKEVEN, {
|
|
34683
|
+
signalId: data.data.id,
|
|
34684
|
+
});
|
|
34685
|
+
this._addNotification(CREATE_BREAKEVEN_NOTIFICATION_FN(data));
|
|
34686
|
+
};
|
|
34687
|
+
/**
|
|
34688
|
+
* Handles strategy commit events.
|
|
34689
|
+
* @param data - The strategy commit contract data
|
|
34690
|
+
*/
|
|
34691
|
+
this.handleStrategyCommit = async (data) => {
|
|
34692
|
+
bt.loggerService.info(NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_STRATEGY_COMMIT, {
|
|
34693
|
+
signalId: data.signalId,
|
|
34694
|
+
action: data.action,
|
|
34695
|
+
});
|
|
34696
|
+
const notification = CREATE_STRATEGY_COMMIT_NOTIFICATION_FN(data);
|
|
34697
|
+
if (notification) {
|
|
34698
|
+
this._addNotification(notification);
|
|
34699
|
+
}
|
|
34700
|
+
};
|
|
34701
|
+
/**
|
|
34702
|
+
* Handles risk rejection event.
|
|
34703
|
+
* @param data - The risk contract data
|
|
34704
|
+
*/
|
|
34705
|
+
this.handleRisk = async (data) => {
|
|
34706
|
+
bt.loggerService.info(NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_RISK, {
|
|
34707
|
+
signalId: data.currentSignal.id,
|
|
34708
|
+
rejectionId: data.rejectionId,
|
|
34709
|
+
});
|
|
34710
|
+
this._addNotification(CREATE_RISK_NOTIFICATION_FN(data));
|
|
34711
|
+
};
|
|
34712
|
+
/**
|
|
34713
|
+
* Handles error event.
|
|
34714
|
+
* @param error - The error object
|
|
34715
|
+
*/
|
|
34716
|
+
this.handleError = async (error) => {
|
|
34717
|
+
bt.loggerService.info(NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_ERROR, {
|
|
34718
|
+
message: functoolsKit.getErrorMessage(error),
|
|
34719
|
+
});
|
|
34720
|
+
this._addNotification(CREATE_ERROR_NOTIFICATION_FN(error));
|
|
34721
|
+
};
|
|
34722
|
+
/**
|
|
34723
|
+
* Handles critical error event.
|
|
34724
|
+
* @param error - The error object
|
|
34725
|
+
*/
|
|
34726
|
+
this.handleCriticalError = async (error) => {
|
|
34727
|
+
bt.loggerService.info(NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_CRITICAL_ERROR, {
|
|
34728
|
+
message: functoolsKit.getErrorMessage(error),
|
|
34729
|
+
});
|
|
34730
|
+
this._addNotification(CREATE_CRITICAL_ERROR_NOTIFICATION_FN(error));
|
|
34731
|
+
};
|
|
34732
|
+
/**
|
|
34733
|
+
* Handles validation error event.
|
|
34734
|
+
* @param error - The error object
|
|
34735
|
+
*/
|
|
34736
|
+
this.handleValidationError = async (error) => {
|
|
34737
|
+
bt.loggerService.info(NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_VALIDATION_ERROR, {
|
|
34738
|
+
message: functoolsKit.getErrorMessage(error),
|
|
34739
|
+
});
|
|
34740
|
+
this._addNotification(CREATE_VALIDATION_ERROR_NOTIFICATION_FN(error));
|
|
34741
|
+
};
|
|
34742
|
+
/**
|
|
34743
|
+
* Gets all stored notifications.
|
|
34744
|
+
* @returns Copy of notifications array
|
|
34745
|
+
*/
|
|
34746
|
+
this.getData = async () => {
|
|
34747
|
+
bt.loggerService.info(NOTIFICATION_MEMORY_LIVE_METHOD_NAME_GET_DATA);
|
|
34748
|
+
return [...this._notifications];
|
|
34749
|
+
};
|
|
34750
|
+
/**
|
|
34751
|
+
* Clears all stored notifications.
|
|
34752
|
+
*/
|
|
34753
|
+
this.clear = async () => {
|
|
34754
|
+
bt.loggerService.info(NOTIFICATION_MEMORY_LIVE_METHOD_NAME_CLEAR);
|
|
34755
|
+
this._notifications = [];
|
|
34756
|
+
};
|
|
34757
|
+
}
|
|
34758
|
+
/**
|
|
34759
|
+
* Adds a notification to the beginning of the list.
|
|
34760
|
+
* Removes oldest notification if limit is exceeded.
|
|
34761
|
+
* @param notification - The notification model to add
|
|
34762
|
+
*/
|
|
34763
|
+
_addNotification(notification) {
|
|
34764
|
+
this._notifications.unshift(notification);
|
|
34765
|
+
if (this._notifications.length > MAX_NOTIFICATIONS) {
|
|
34766
|
+
this._notifications.pop();
|
|
34767
|
+
}
|
|
34768
|
+
}
|
|
34769
|
+
}
|
|
34770
|
+
/**
|
|
34771
|
+
* Dummy notification adapter for live trading signals that discards all writes.
|
|
34772
|
+
*
|
|
34773
|
+
* Features:
|
|
34774
|
+
* - No-op implementation for all methods
|
|
34775
|
+
* - getData always returns empty array
|
|
34776
|
+
*
|
|
34777
|
+
* Use this adapter to disable live notification storage completely.
|
|
34778
|
+
*/
|
|
34779
|
+
class NotificationDummyLiveUtils {
|
|
34780
|
+
constructor() {
|
|
34781
|
+
/**
|
|
34782
|
+
* No-op handler for signal events.
|
|
34783
|
+
*/
|
|
34784
|
+
this.handleSignal = async () => {
|
|
34785
|
+
};
|
|
34786
|
+
/**
|
|
34787
|
+
* No-op handler for partial profit event.
|
|
34788
|
+
*/
|
|
34789
|
+
this.handlePartialProfit = async () => {
|
|
34790
|
+
};
|
|
34791
|
+
/**
|
|
34792
|
+
* No-op handler for partial loss event.
|
|
34793
|
+
*/
|
|
34794
|
+
this.handlePartialLoss = async () => {
|
|
34795
|
+
};
|
|
34796
|
+
/**
|
|
34797
|
+
* No-op handler for breakeven event.
|
|
34798
|
+
*/
|
|
34799
|
+
this.handleBreakeven = async () => {
|
|
34800
|
+
};
|
|
34801
|
+
/**
|
|
34802
|
+
* No-op handler for strategy commit event.
|
|
34803
|
+
*/
|
|
34804
|
+
this.handleStrategyCommit = async () => {
|
|
34805
|
+
};
|
|
34806
|
+
/**
|
|
34807
|
+
* No-op handler for risk rejection event.
|
|
34808
|
+
*/
|
|
34809
|
+
this.handleRisk = async () => {
|
|
34810
|
+
};
|
|
34811
|
+
/**
|
|
34812
|
+
* No-op handler for error event.
|
|
34813
|
+
*/
|
|
34814
|
+
this.handleError = async () => {
|
|
34815
|
+
};
|
|
34816
|
+
/**
|
|
34817
|
+
* No-op handler for critical error event.
|
|
34818
|
+
*/
|
|
34819
|
+
this.handleCriticalError = async () => {
|
|
34820
|
+
};
|
|
34821
|
+
/**
|
|
34822
|
+
* No-op handler for validation error event.
|
|
34823
|
+
*/
|
|
34824
|
+
this.handleValidationError = async () => {
|
|
34825
|
+
};
|
|
34826
|
+
/**
|
|
34827
|
+
* Always returns empty array (no storage).
|
|
34828
|
+
* @returns Empty array
|
|
34829
|
+
*/
|
|
34830
|
+
this.getData = async () => {
|
|
34831
|
+
return [];
|
|
34832
|
+
};
|
|
34833
|
+
/**
|
|
34834
|
+
* No-op clear operation.
|
|
34835
|
+
*/
|
|
34836
|
+
this.clear = async () => {
|
|
34837
|
+
};
|
|
34838
|
+
}
|
|
34839
|
+
}
|
|
34840
|
+
/**
|
|
34841
|
+
* Persistent notification adapter for live trading signals.
|
|
34842
|
+
*
|
|
34843
|
+
* Features:
|
|
34844
|
+
* - Persists notifications to disk using PersistNotificationAdapter
|
|
34845
|
+
* - Lazy initialization with singleshot pattern
|
|
34846
|
+
* - Maintains up to MAX_NOTIFICATIONS (250) most recent notifications
|
|
34847
|
+
* - Filters out error notifications when persisting to disk
|
|
34848
|
+
* - Handles all notification types: signals, partial profit/loss, breakeven, risk, errors
|
|
34849
|
+
*
|
|
34850
|
+
* Use this adapter (default) for live notification persistence across sessions.
|
|
34851
|
+
*/
|
|
34852
|
+
class NotificationPersistLiveUtils {
|
|
34853
|
+
constructor() {
|
|
34854
|
+
/**
|
|
34855
|
+
* Singleshot initialization function that loads notifications from disk.
|
|
34856
|
+
* Protected by singleshot to ensure one-time execution.
|
|
34857
|
+
*/
|
|
34858
|
+
this.waitForInit = functoolsKit.singleshot(async () => {
|
|
34859
|
+
bt.loggerService.info(NOTIFICATION_PERSIST_LIVE_METHOD_NAME_WAIT_FOR_INIT);
|
|
34860
|
+
const notificationList = await PersistNotificationAdapter.readNotificationData(false);
|
|
34861
|
+
notificationList.sort((a, b) => {
|
|
34862
|
+
const aTime = 'createdAt' in a ? a.createdAt : 0;
|
|
34863
|
+
const bTime = 'createdAt' in b ? b.createdAt : 0;
|
|
34864
|
+
return aTime - bTime;
|
|
34865
|
+
});
|
|
34866
|
+
this._notifications = new Map(notificationList
|
|
34867
|
+
.slice(-MAX_NOTIFICATIONS)
|
|
34868
|
+
.map((notification) => [notification.id, notification]));
|
|
34869
|
+
});
|
|
34870
|
+
/**
|
|
34871
|
+
* Handles signal events.
|
|
34872
|
+
* Creates and stores notification for opened, closed, scheduled, cancelled signals.
|
|
34873
|
+
* @param data - The strategy tick result data
|
|
34874
|
+
*/
|
|
34875
|
+
this.handleSignal = async (data) => {
|
|
34876
|
+
bt.loggerService.info(NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_SIGNAL, {
|
|
34877
|
+
signalId: data.signal.id,
|
|
34878
|
+
action: data.action,
|
|
34879
|
+
});
|
|
34880
|
+
await this.waitForInit();
|
|
34881
|
+
const notification = CREATE_SIGNAL_NOTIFICATION_FN(data);
|
|
34882
|
+
if (notification) {
|
|
34883
|
+
this._addNotification(notification);
|
|
34884
|
+
await this._updateNotifications();
|
|
34885
|
+
}
|
|
34886
|
+
};
|
|
34887
|
+
/**
|
|
34888
|
+
* Handles partial profit availability event.
|
|
34889
|
+
* @param data - The partial profit contract data
|
|
34890
|
+
*/
|
|
34891
|
+
this.handlePartialProfit = async (data) => {
|
|
34892
|
+
bt.loggerService.info(NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_PARTIAL_PROFIT, {
|
|
34893
|
+
signalId: data.data.id,
|
|
34894
|
+
level: data.level,
|
|
34895
|
+
});
|
|
34896
|
+
await this.waitForInit();
|
|
34897
|
+
this._addNotification(CREATE_PARTIAL_PROFIT_NOTIFICATION_FN(data));
|
|
34898
|
+
await this._updateNotifications();
|
|
34899
|
+
};
|
|
34900
|
+
/**
|
|
34901
|
+
* Handles partial loss availability event.
|
|
34902
|
+
* @param data - The partial loss contract data
|
|
34903
|
+
*/
|
|
34904
|
+
this.handlePartialLoss = async (data) => {
|
|
34905
|
+
bt.loggerService.info(NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_PARTIAL_LOSS, {
|
|
34906
|
+
signalId: data.data.id,
|
|
34907
|
+
level: data.level,
|
|
34908
|
+
});
|
|
34909
|
+
await this.waitForInit();
|
|
34910
|
+
this._addNotification(CREATE_PARTIAL_LOSS_NOTIFICATION_FN(data));
|
|
34911
|
+
await this._updateNotifications();
|
|
34912
|
+
};
|
|
34913
|
+
/**
|
|
34914
|
+
* Handles breakeven availability event.
|
|
34915
|
+
* @param data - The breakeven contract data
|
|
34916
|
+
*/
|
|
34917
|
+
this.handleBreakeven = async (data) => {
|
|
34918
|
+
bt.loggerService.info(NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_BREAKEVEN, {
|
|
34919
|
+
signalId: data.data.id,
|
|
34920
|
+
});
|
|
34921
|
+
await this.waitForInit();
|
|
34922
|
+
this._addNotification(CREATE_BREAKEVEN_NOTIFICATION_FN(data));
|
|
34923
|
+
await this._updateNotifications();
|
|
34924
|
+
};
|
|
34925
|
+
/**
|
|
34926
|
+
* Handles strategy commit events.
|
|
34927
|
+
* @param data - The strategy commit contract data
|
|
34928
|
+
*/
|
|
34929
|
+
this.handleStrategyCommit = async (data) => {
|
|
34930
|
+
bt.loggerService.info(NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_STRATEGY_COMMIT, {
|
|
34931
|
+
signalId: data.signalId,
|
|
34932
|
+
action: data.action,
|
|
34933
|
+
});
|
|
34934
|
+
await this.waitForInit();
|
|
34935
|
+
const notification = CREATE_STRATEGY_COMMIT_NOTIFICATION_FN(data);
|
|
34936
|
+
if (notification) {
|
|
34937
|
+
this._addNotification(notification);
|
|
34938
|
+
await this._updateNotifications();
|
|
34939
|
+
}
|
|
34940
|
+
};
|
|
34941
|
+
/**
|
|
34942
|
+
* Handles risk rejection event.
|
|
34943
|
+
* @param data - The risk contract data
|
|
34944
|
+
*/
|
|
34945
|
+
this.handleRisk = async (data) => {
|
|
34946
|
+
bt.loggerService.info(NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_RISK, {
|
|
34947
|
+
signalId: data.currentSignal.id,
|
|
34948
|
+
rejectionId: data.rejectionId,
|
|
34949
|
+
});
|
|
34950
|
+
await this.waitForInit();
|
|
34951
|
+
this._addNotification(CREATE_RISK_NOTIFICATION_FN(data));
|
|
34952
|
+
await this._updateNotifications();
|
|
34953
|
+
};
|
|
34954
|
+
/**
|
|
34955
|
+
* Handles error event.
|
|
34956
|
+
* Note: Error notifications are not persisted to disk.
|
|
34957
|
+
* @param error - The error object
|
|
34958
|
+
*/
|
|
34959
|
+
this.handleError = async (error) => {
|
|
34960
|
+
bt.loggerService.info(NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_ERROR, {
|
|
34961
|
+
message: functoolsKit.getErrorMessage(error),
|
|
34962
|
+
});
|
|
34963
|
+
await this.waitForInit();
|
|
34964
|
+
this._addNotification(CREATE_ERROR_NOTIFICATION_FN(error));
|
|
34965
|
+
};
|
|
34966
|
+
/**
|
|
34967
|
+
* Handles critical error event.
|
|
34968
|
+
* Note: Error notifications are not persisted to disk.
|
|
34969
|
+
* @param error - The error object
|
|
34970
|
+
*/
|
|
34971
|
+
this.handleCriticalError = async (error) => {
|
|
34972
|
+
bt.loggerService.info(NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_CRITICAL_ERROR, {
|
|
34973
|
+
message: functoolsKit.getErrorMessage(error),
|
|
34974
|
+
});
|
|
34975
|
+
await this.waitForInit();
|
|
34976
|
+
this._addNotification(CREATE_CRITICAL_ERROR_NOTIFICATION_FN(error));
|
|
34977
|
+
};
|
|
34978
|
+
/**
|
|
34979
|
+
* Handles validation error event.
|
|
34980
|
+
* Note: Error notifications are not persisted to disk.
|
|
34981
|
+
* @param error - The error object
|
|
34982
|
+
*/
|
|
34983
|
+
this.handleValidationError = async (error) => {
|
|
34984
|
+
bt.loggerService.info(NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_VALIDATION_ERROR, {
|
|
34985
|
+
message: functoolsKit.getErrorMessage(error),
|
|
34986
|
+
});
|
|
34987
|
+
await this.waitForInit();
|
|
34988
|
+
this._addNotification(CREATE_VALIDATION_ERROR_NOTIFICATION_FN(error));
|
|
34989
|
+
};
|
|
34990
|
+
/**
|
|
34991
|
+
* Gets all stored notifications.
|
|
34992
|
+
* @returns Array of all notification models
|
|
34993
|
+
*/
|
|
34994
|
+
this.getData = async () => {
|
|
34995
|
+
bt.loggerService.info(NOTIFICATION_PERSIST_LIVE_METHOD_NAME_GET_DATA);
|
|
34996
|
+
await this.waitForInit();
|
|
34997
|
+
return Array.from(this._notifications.values());
|
|
34998
|
+
};
|
|
34999
|
+
/**
|
|
35000
|
+
* Clears all stored notifications.
|
|
35001
|
+
*/
|
|
35002
|
+
this.clear = async () => {
|
|
35003
|
+
bt.loggerService.info(NOTIFICATION_PERSIST_LIVE_METHOD_NAME_CLEAR);
|
|
35004
|
+
await this.waitForInit();
|
|
35005
|
+
this._notifications.clear();
|
|
35006
|
+
await this._updateNotifications();
|
|
35007
|
+
};
|
|
35008
|
+
}
|
|
35009
|
+
/**
|
|
35010
|
+
* Persists the current notification map to disk storage.
|
|
35011
|
+
* Filters out error notifications and sorts by createdAt.
|
|
35012
|
+
* Keeps only the most recent MAX_NOTIFICATIONS.
|
|
35013
|
+
* @throws Error if not initialized
|
|
35014
|
+
*/
|
|
35015
|
+
async _updateNotifications() {
|
|
35016
|
+
bt.loggerService.info(NOTIFICATION_PERSIST_LIVE_METHOD_NAME_UPDATE_NOTIFICATIONS);
|
|
35017
|
+
if (!this._notifications) {
|
|
35018
|
+
throw new Error("NotificationPersistLiveUtils not initialized. Call waitForInit first.");
|
|
35019
|
+
}
|
|
35020
|
+
const notificationList = Array.from(this._notifications.values())
|
|
35021
|
+
.filter(({ type }) => !type.startsWith("error."));
|
|
35022
|
+
notificationList.sort((a, b) => {
|
|
35023
|
+
const aTime = 'createdAt' in a ? a.createdAt : 0;
|
|
35024
|
+
const bTime = 'createdAt' in b ? b.createdAt : 0;
|
|
35025
|
+
return aTime - bTime;
|
|
35026
|
+
});
|
|
35027
|
+
await PersistNotificationAdapter.writeNotificationData(notificationList.slice(-MAX_NOTIFICATIONS), false);
|
|
35028
|
+
}
|
|
35029
|
+
/**
|
|
35030
|
+
* Adds a notification to the map.
|
|
35031
|
+
* Removes oldest notification if limit is exceeded.
|
|
35032
|
+
* @param notification - The notification model to add
|
|
35033
|
+
*/
|
|
35034
|
+
_addNotification(notification) {
|
|
35035
|
+
this._notifications.set(notification.id, notification);
|
|
35036
|
+
if (this._notifications.size > MAX_NOTIFICATIONS) {
|
|
35037
|
+
const firstKey = this._notifications.keys().next().value;
|
|
35038
|
+
if (firstKey) {
|
|
35039
|
+
this._notifications.delete(firstKey);
|
|
35040
|
+
}
|
|
35041
|
+
}
|
|
35042
|
+
}
|
|
35043
|
+
}
|
|
35044
|
+
/**
|
|
35045
|
+
* Backtest notification adapter with pluggable notification backend.
|
|
35046
|
+
*
|
|
35047
|
+
* Features:
|
|
35048
|
+
* - Adapter pattern for swappable notification implementations
|
|
35049
|
+
* - Default adapter: NotificationMemoryBacktestUtils (in-memory storage)
|
|
35050
|
+
* - Alternative adapters: NotificationPersistBacktestUtils, NotificationDummyBacktestUtils
|
|
35051
|
+
* - Convenience methods: usePersist(), useMemory(), useDummy()
|
|
35052
|
+
*/
|
|
35053
|
+
class NotificationBacktestAdapter {
|
|
35054
|
+
constructor() {
|
|
35055
|
+
/** Internal notification utils instance */
|
|
35056
|
+
this._notificationBacktestUtils = new NotificationMemoryBacktestUtils();
|
|
35057
|
+
/**
|
|
35058
|
+
* Handles signal events.
|
|
35059
|
+
* Proxies call to the underlying notification adapter.
|
|
35060
|
+
* @param data - The strategy tick result data
|
|
35061
|
+
*/
|
|
35062
|
+
this.handleSignal = async (data) => {
|
|
35063
|
+
return await this._notificationBacktestUtils.handleSignal(data);
|
|
35064
|
+
};
|
|
35065
|
+
/**
|
|
35066
|
+
* Handles partial profit availability event.
|
|
35067
|
+
* Proxies call to the underlying notification adapter.
|
|
35068
|
+
* @param data - The partial profit contract data
|
|
35069
|
+
*/
|
|
35070
|
+
this.handlePartialProfit = async (data) => {
|
|
35071
|
+
return await this._notificationBacktestUtils.handlePartialProfit(data);
|
|
35072
|
+
};
|
|
35073
|
+
/**
|
|
35074
|
+
* Handles partial loss availability event.
|
|
35075
|
+
* Proxies call to the underlying notification adapter.
|
|
35076
|
+
* @param data - The partial loss contract data
|
|
35077
|
+
*/
|
|
35078
|
+
this.handlePartialLoss = async (data) => {
|
|
35079
|
+
return await this._notificationBacktestUtils.handlePartialLoss(data);
|
|
35080
|
+
};
|
|
35081
|
+
/**
|
|
35082
|
+
* Handles breakeven availability event.
|
|
35083
|
+
* Proxies call to the underlying notification adapter.
|
|
35084
|
+
* @param data - The breakeven contract data
|
|
35085
|
+
*/
|
|
35086
|
+
this.handleBreakeven = async (data) => {
|
|
35087
|
+
return await this._notificationBacktestUtils.handleBreakeven(data);
|
|
35088
|
+
};
|
|
35089
|
+
/**
|
|
35090
|
+
* Handles strategy commit events.
|
|
35091
|
+
* Proxies call to the underlying notification adapter.
|
|
35092
|
+
* @param data - The strategy commit contract data
|
|
35093
|
+
*/
|
|
35094
|
+
this.handleStrategyCommit = async (data) => {
|
|
35095
|
+
return await this._notificationBacktestUtils.handleStrategyCommit(data);
|
|
35096
|
+
};
|
|
35097
|
+
/**
|
|
35098
|
+
* Handles risk rejection event.
|
|
35099
|
+
* Proxies call to the underlying notification adapter.
|
|
35100
|
+
* @param data - The risk contract data
|
|
35101
|
+
*/
|
|
35102
|
+
this.handleRisk = async (data) => {
|
|
35103
|
+
return await this._notificationBacktestUtils.handleRisk(data);
|
|
35104
|
+
};
|
|
35105
|
+
/**
|
|
35106
|
+
* Handles error event.
|
|
35107
|
+
* Proxies call to the underlying notification adapter.
|
|
35108
|
+
* @param error - The error object
|
|
35109
|
+
*/
|
|
35110
|
+
this.handleError = async (error) => {
|
|
35111
|
+
return await this._notificationBacktestUtils.handleError(error);
|
|
35112
|
+
};
|
|
35113
|
+
/**
|
|
35114
|
+
* Handles critical error event.
|
|
35115
|
+
* Proxies call to the underlying notification adapter.
|
|
35116
|
+
* @param error - The error object
|
|
35117
|
+
*/
|
|
35118
|
+
this.handleCriticalError = async (error) => {
|
|
35119
|
+
return await this._notificationBacktestUtils.handleCriticalError(error);
|
|
35120
|
+
};
|
|
35121
|
+
/**
|
|
35122
|
+
* Handles validation error event.
|
|
35123
|
+
* Proxies call to the underlying notification adapter.
|
|
35124
|
+
* @param error - The error object
|
|
35125
|
+
*/
|
|
35126
|
+
this.handleValidationError = async (error) => {
|
|
35127
|
+
return await this._notificationBacktestUtils.handleValidationError(error);
|
|
35128
|
+
};
|
|
35129
|
+
/**
|
|
35130
|
+
* Gets all stored notifications.
|
|
35131
|
+
* Proxies call to the underlying notification adapter.
|
|
35132
|
+
* @returns Array of all notification models
|
|
35133
|
+
*/
|
|
35134
|
+
this.getData = async () => {
|
|
35135
|
+
return await this._notificationBacktestUtils.getData();
|
|
35136
|
+
};
|
|
35137
|
+
/**
|
|
35138
|
+
* Clears all stored notifications.
|
|
35139
|
+
* Proxies call to the underlying notification adapter.
|
|
35140
|
+
*/
|
|
35141
|
+
this.clear = async () => {
|
|
35142
|
+
return await this._notificationBacktestUtils.clear();
|
|
35143
|
+
};
|
|
35144
|
+
/**
|
|
35145
|
+
* Sets the notification adapter constructor.
|
|
35146
|
+
* All future notification operations will use this adapter.
|
|
35147
|
+
*
|
|
35148
|
+
* @param Ctor - Constructor for notification adapter
|
|
35149
|
+
*/
|
|
35150
|
+
this.useNotificationAdapter = (Ctor) => {
|
|
35151
|
+
bt.loggerService.info(NOTIFICATION_BACKTEST_ADAPTER_METHOD_NAME_USE_ADAPTER);
|
|
35152
|
+
this._notificationBacktestUtils = Reflect.construct(Ctor, []);
|
|
35153
|
+
};
|
|
35154
|
+
/**
|
|
35155
|
+
* Switches to dummy notification adapter.
|
|
35156
|
+
* All future notification writes will be no-ops.
|
|
35157
|
+
*/
|
|
35158
|
+
this.useDummy = () => {
|
|
35159
|
+
bt.loggerService.info(NOTIFICATION_BACKTEST_ADAPTER_METHOD_NAME_USE_DUMMY);
|
|
35160
|
+
this._notificationBacktestUtils = new NotificationDummyBacktestUtils();
|
|
35161
|
+
};
|
|
35162
|
+
/**
|
|
35163
|
+
* Switches to in-memory notification adapter (default).
|
|
35164
|
+
* Notifications will be stored in memory only.
|
|
35165
|
+
*/
|
|
35166
|
+
this.useMemory = () => {
|
|
35167
|
+
bt.loggerService.info(NOTIFICATION_BACKTEST_ADAPTER_METHOD_NAME_USE_MEMORY);
|
|
35168
|
+
this._notificationBacktestUtils = new NotificationMemoryBacktestUtils();
|
|
35169
|
+
};
|
|
35170
|
+
/**
|
|
35171
|
+
* Switches to persistent notification adapter.
|
|
35172
|
+
* Notifications will be persisted to disk.
|
|
35173
|
+
*/
|
|
35174
|
+
this.usePersist = () => {
|
|
35175
|
+
bt.loggerService.info(NOTIFICATION_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST);
|
|
35176
|
+
this._notificationBacktestUtils = new NotificationPersistBacktestUtils();
|
|
35177
|
+
};
|
|
35178
|
+
}
|
|
35179
|
+
}
|
|
35180
|
+
/**
|
|
35181
|
+
* Live trading notification adapter with pluggable notification backend.
|
|
35182
|
+
*
|
|
35183
|
+
* Features:
|
|
35184
|
+
* - Adapter pattern for swappable notification implementations
|
|
35185
|
+
* - Default adapter: NotificationMemoryLiveUtils (in-memory storage)
|
|
35186
|
+
* - Alternative adapters: NotificationPersistLiveUtils, NotificationDummyLiveUtils
|
|
35187
|
+
* - Convenience methods: usePersist(), useMemory(), useDummy()
|
|
35188
|
+
*/
|
|
35189
|
+
class NotificationLiveAdapter {
|
|
35190
|
+
constructor() {
|
|
35191
|
+
/** Internal notification utils instance */
|
|
35192
|
+
this._notificationLiveUtils = new NotificationMemoryLiveUtils();
|
|
35193
|
+
/**
|
|
35194
|
+
* Handles signal events.
|
|
35195
|
+
* Proxies call to the underlying notification adapter.
|
|
35196
|
+
* @param data - The strategy tick result data
|
|
35197
|
+
*/
|
|
35198
|
+
this.handleSignal = async (data) => {
|
|
35199
|
+
return await this._notificationLiveUtils.handleSignal(data);
|
|
35200
|
+
};
|
|
35201
|
+
/**
|
|
35202
|
+
* Handles partial profit availability event.
|
|
35203
|
+
* Proxies call to the underlying notification adapter.
|
|
35204
|
+
* @param data - The partial profit contract data
|
|
35205
|
+
*/
|
|
35206
|
+
this.handlePartialProfit = async (data) => {
|
|
35207
|
+
return await this._notificationLiveUtils.handlePartialProfit(data);
|
|
35208
|
+
};
|
|
35209
|
+
/**
|
|
35210
|
+
* Handles partial loss availability event.
|
|
35211
|
+
* Proxies call to the underlying notification adapter.
|
|
35212
|
+
* @param data - The partial loss contract data
|
|
35213
|
+
*/
|
|
35214
|
+
this.handlePartialLoss = async (data) => {
|
|
35215
|
+
return await this._notificationLiveUtils.handlePartialLoss(data);
|
|
35216
|
+
};
|
|
35217
|
+
/**
|
|
35218
|
+
* Handles breakeven availability event.
|
|
35219
|
+
* Proxies call to the underlying notification adapter.
|
|
35220
|
+
* @param data - The breakeven contract data
|
|
35221
|
+
*/
|
|
35222
|
+
this.handleBreakeven = async (data) => {
|
|
35223
|
+
return await this._notificationLiveUtils.handleBreakeven(data);
|
|
35224
|
+
};
|
|
35225
|
+
/**
|
|
35226
|
+
* Handles strategy commit events.
|
|
35227
|
+
* Proxies call to the underlying notification adapter.
|
|
35228
|
+
* @param data - The strategy commit contract data
|
|
35229
|
+
*/
|
|
35230
|
+
this.handleStrategyCommit = async (data) => {
|
|
35231
|
+
return await this._notificationLiveUtils.handleStrategyCommit(data);
|
|
35232
|
+
};
|
|
35233
|
+
/**
|
|
35234
|
+
* Handles risk rejection event.
|
|
35235
|
+
* Proxies call to the underlying notification adapter.
|
|
35236
|
+
* @param data - The risk contract data
|
|
35237
|
+
*/
|
|
35238
|
+
this.handleRisk = async (data) => {
|
|
35239
|
+
return await this._notificationLiveUtils.handleRisk(data);
|
|
35240
|
+
};
|
|
35241
|
+
/**
|
|
35242
|
+
* Handles error event.
|
|
35243
|
+
* Proxies call to the underlying notification adapter.
|
|
35244
|
+
* @param error - The error object
|
|
35245
|
+
*/
|
|
35246
|
+
this.handleError = async (error) => {
|
|
35247
|
+
return await this._notificationLiveUtils.handleError(error);
|
|
35248
|
+
};
|
|
35249
|
+
/**
|
|
35250
|
+
* Handles critical error event.
|
|
35251
|
+
* Proxies call to the underlying notification adapter.
|
|
35252
|
+
* @param error - The error object
|
|
35253
|
+
*/
|
|
35254
|
+
this.handleCriticalError = async (error) => {
|
|
35255
|
+
return await this._notificationLiveUtils.handleCriticalError(error);
|
|
35256
|
+
};
|
|
35257
|
+
/**
|
|
35258
|
+
* Handles validation error event.
|
|
35259
|
+
* Proxies call to the underlying notification adapter.
|
|
35260
|
+
* @param error - The error object
|
|
35261
|
+
*/
|
|
35262
|
+
this.handleValidationError = async (error) => {
|
|
35263
|
+
return await this._notificationLiveUtils.handleValidationError(error);
|
|
35264
|
+
};
|
|
35265
|
+
/**
|
|
35266
|
+
* Gets all stored notifications.
|
|
35267
|
+
* Proxies call to the underlying notification adapter.
|
|
35268
|
+
* @returns Array of all notification models
|
|
35269
|
+
*/
|
|
35270
|
+
this.getData = async () => {
|
|
35271
|
+
return await this._notificationLiveUtils.getData();
|
|
35272
|
+
};
|
|
35273
|
+
/**
|
|
35274
|
+
* Clears all stored notifications.
|
|
35275
|
+
* Proxies call to the underlying notification adapter.
|
|
35276
|
+
*/
|
|
35277
|
+
this.clear = async () => {
|
|
35278
|
+
return await this._notificationLiveUtils.clear();
|
|
35279
|
+
};
|
|
35280
|
+
/**
|
|
35281
|
+
* Sets the notification adapter constructor.
|
|
35282
|
+
* All future notification operations will use this adapter.
|
|
35283
|
+
*
|
|
35284
|
+
* @param Ctor - Constructor for notification adapter
|
|
35285
|
+
*/
|
|
35286
|
+
this.useNotificationAdapter = (Ctor) => {
|
|
35287
|
+
bt.loggerService.info(NOTIFICATION_LIVE_ADAPTER_METHOD_NAME_USE_ADAPTER);
|
|
35288
|
+
this._notificationLiveUtils = Reflect.construct(Ctor, []);
|
|
35289
|
+
};
|
|
35290
|
+
/**
|
|
35291
|
+
* Switches to dummy notification adapter.
|
|
35292
|
+
* All future notification writes will be no-ops.
|
|
35293
|
+
*/
|
|
35294
|
+
this.useDummy = () => {
|
|
35295
|
+
bt.loggerService.info(NOTIFICATION_LIVE_ADAPTER_METHOD_NAME_USE_DUMMY);
|
|
35296
|
+
this._notificationLiveUtils = new NotificationDummyLiveUtils();
|
|
35297
|
+
};
|
|
35298
|
+
/**
|
|
35299
|
+
* Switches to in-memory notification adapter (default).
|
|
35300
|
+
* Notifications will be stored in memory only.
|
|
35301
|
+
*/
|
|
35302
|
+
this.useMemory = () => {
|
|
35303
|
+
bt.loggerService.info(NOTIFICATION_LIVE_ADAPTER_METHOD_NAME_USE_MEMORY);
|
|
35304
|
+
this._notificationLiveUtils = new NotificationMemoryLiveUtils();
|
|
35305
|
+
};
|
|
35306
|
+
/**
|
|
35307
|
+
* Switches to persistent notification adapter.
|
|
35308
|
+
* Notifications will be persisted to disk.
|
|
35309
|
+
*/
|
|
35310
|
+
this.usePersist = () => {
|
|
35311
|
+
bt.loggerService.info(NOTIFICATION_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST);
|
|
35312
|
+
this._notificationLiveUtils = new NotificationPersistLiveUtils();
|
|
35313
|
+
};
|
|
35314
|
+
}
|
|
35315
|
+
}
|
|
35316
|
+
/**
|
|
35317
|
+
* Main notification adapter that manages both backtest and live notification storage.
|
|
35318
|
+
*
|
|
35319
|
+
* Features:
|
|
35320
|
+
* - Subscribes to signal emitters for automatic notification updates
|
|
35321
|
+
* - Provides unified access to both backtest and live notifications
|
|
35322
|
+
* - Singleshot enable pattern prevents duplicate subscriptions
|
|
35323
|
+
* - Cleanup function for proper unsubscription
|
|
35324
|
+
*/
|
|
35325
|
+
class NotificationAdapter {
|
|
35326
|
+
constructor() {
|
|
35327
|
+
/**
|
|
35328
|
+
* Enables notification storage by subscribing to signal emitters.
|
|
35329
|
+
* Uses singleshot to ensure one-time subscription.
|
|
35330
|
+
*
|
|
35331
|
+
* @returns Cleanup function that unsubscribes from all emitters
|
|
35332
|
+
*/
|
|
35333
|
+
this.enable = functoolsKit.singleshot(() => {
|
|
35334
|
+
bt.loggerService.info(NOTIFICATION_ADAPTER_METHOD_NAME_ENABLE);
|
|
35335
|
+
let unLive;
|
|
35336
|
+
let unBacktest;
|
|
35337
|
+
{
|
|
35338
|
+
const unBacktestSignal = signalBacktestEmitter.subscribe((data) => NotificationBacktest.handleSignal(data));
|
|
35339
|
+
const unBacktestPartialProfit = partialProfitSubject
|
|
35340
|
+
.filter(({ backtest }) => backtest)
|
|
35341
|
+
.connect((data) => NotificationBacktest.handlePartialProfit(data));
|
|
35342
|
+
const unBacktestPartialLoss = partialLossSubject
|
|
35343
|
+
.filter(({ backtest }) => backtest)
|
|
35344
|
+
.connect((data) => NotificationBacktest.handlePartialLoss(data));
|
|
35345
|
+
const unBacktestBreakeven = breakevenSubject
|
|
35346
|
+
.filter(({ backtest }) => backtest)
|
|
35347
|
+
.connect((data) => NotificationBacktest.handleBreakeven(data));
|
|
35348
|
+
const unBacktestStrategyCommit = strategyCommitSubject
|
|
35349
|
+
.filter(({ backtest }) => backtest)
|
|
35350
|
+
.connect((data) => NotificationBacktest.handleStrategyCommit(data));
|
|
35351
|
+
const unBacktestRisk = riskSubject
|
|
35352
|
+
.filter(({ backtest }) => backtest)
|
|
35353
|
+
.connect((data) => NotificationBacktest.handleRisk(data));
|
|
35354
|
+
const unBacktestError = errorEmitter.subscribe((error) => NotificationBacktest.handleError(error));
|
|
35355
|
+
const unBacktestExit = exitEmitter.subscribe((error) => NotificationBacktest.handleCriticalError(error));
|
|
35356
|
+
const unBacktestValidation = validationSubject.subscribe((error) => NotificationBacktest.handleValidationError(error));
|
|
35357
|
+
unBacktest = functoolsKit.compose(() => unBacktestSignal(), () => unBacktestPartialProfit(), () => unBacktestPartialLoss(), () => unBacktestBreakeven(), () => unBacktestStrategyCommit(), () => unBacktestRisk(), () => unBacktestError(), () => unBacktestExit(), () => unBacktestValidation());
|
|
35358
|
+
}
|
|
35359
|
+
{
|
|
35360
|
+
const unLiveSignal = signalLiveEmitter.subscribe((data) => NotificationLive.handleSignal(data));
|
|
35361
|
+
const unLivePartialProfit = partialProfitSubject
|
|
35362
|
+
.filter(({ backtest }) => !backtest)
|
|
35363
|
+
.connect((data) => NotificationLive.handlePartialProfit(data));
|
|
35364
|
+
const unLivePartialLoss = partialLossSubject
|
|
35365
|
+
.filter(({ backtest }) => !backtest)
|
|
35366
|
+
.connect((data) => NotificationLive.handlePartialLoss(data));
|
|
35367
|
+
const unLiveBreakeven = breakevenSubject
|
|
35368
|
+
.filter(({ backtest }) => !backtest)
|
|
35369
|
+
.connect((data) => NotificationLive.handleBreakeven(data));
|
|
35370
|
+
const unLiveStrategyCommit = strategyCommitSubject
|
|
35371
|
+
.filter(({ backtest }) => !backtest)
|
|
35372
|
+
.connect((data) => NotificationLive.handleStrategyCommit(data));
|
|
35373
|
+
const unLiveRisk = riskSubject
|
|
35374
|
+
.filter(({ backtest }) => !backtest)
|
|
35375
|
+
.connect((data) => NotificationLive.handleRisk(data));
|
|
35376
|
+
const unLiveError = errorEmitter.subscribe((error) => NotificationLive.handleError(error));
|
|
35377
|
+
const unLiveExit = exitEmitter.subscribe((error) => NotificationLive.handleCriticalError(error));
|
|
35378
|
+
const unLiveValidation = validationSubject.subscribe((error) => NotificationLive.handleValidationError(error));
|
|
35379
|
+
unLive = functoolsKit.compose(() => unLiveSignal(), () => unLivePartialProfit(), () => unLivePartialLoss(), () => unLiveBreakeven(), () => unLiveStrategyCommit(), () => unLiveRisk(), () => unLiveError(), () => unLiveExit(), () => unLiveValidation());
|
|
35380
|
+
}
|
|
35381
|
+
return () => {
|
|
35382
|
+
unLive();
|
|
35383
|
+
unBacktest();
|
|
35384
|
+
this.enable.clear();
|
|
35385
|
+
};
|
|
35386
|
+
});
|
|
35387
|
+
/**
|
|
35388
|
+
* Disables notification storage by unsubscribing from all emitters.
|
|
35389
|
+
* Safe to call multiple times.
|
|
35390
|
+
*/
|
|
35391
|
+
this.disable = () => {
|
|
35392
|
+
bt.loggerService.info(NOTIFICATION_ADAPTER_METHOD_NAME_DISABLE);
|
|
35393
|
+
if (this.enable.hasValue()) {
|
|
35394
|
+
const lastSubscription = this.enable();
|
|
35395
|
+
lastSubscription();
|
|
35396
|
+
}
|
|
35397
|
+
};
|
|
35398
|
+
/**
|
|
35399
|
+
* Gets all backtest notifications from storage.
|
|
35400
|
+
*
|
|
35401
|
+
* @returns Array of all backtest notification models
|
|
35402
|
+
* @throws Error if NotificationAdapter is not enabled
|
|
35403
|
+
*/
|
|
35404
|
+
this.getDataBacktest = async () => {
|
|
35405
|
+
bt.loggerService.info(NOTIFICATION_ADAPTER_METHOD_NAME_GET_DATA_BACKTEST);
|
|
35406
|
+
if (!this.enable.hasValue()) {
|
|
35407
|
+
throw new Error("NotificationAdapter is not enabled. Call enable() first.");
|
|
35408
|
+
}
|
|
35409
|
+
return await NotificationBacktest.getData();
|
|
35410
|
+
};
|
|
35411
|
+
/**
|
|
35412
|
+
* Gets all live notifications from storage.
|
|
35413
|
+
*
|
|
35414
|
+
* @returns Array of all live notification models
|
|
35415
|
+
* @throws Error if NotificationAdapter is not enabled
|
|
35416
|
+
*/
|
|
35417
|
+
this.getDataLive = async () => {
|
|
35418
|
+
bt.loggerService.info(NOTIFICATION_ADAPTER_METHOD_NAME_GET_DATA_LIVE);
|
|
35419
|
+
if (!this.enable.hasValue()) {
|
|
35420
|
+
throw new Error("NotificationAdapter is not enabled. Call enable() first.");
|
|
35421
|
+
}
|
|
35422
|
+
return await NotificationLive.getData();
|
|
35423
|
+
};
|
|
35424
|
+
/**
|
|
35425
|
+
* Clears all backtest notifications from storage.
|
|
35426
|
+
*
|
|
35427
|
+
* @throws Error if NotificationAdapter is not enabled
|
|
35428
|
+
*/
|
|
35429
|
+
this.clearBacktest = async () => {
|
|
35430
|
+
bt.loggerService.info(NOTIFICATION_ADAPTER_METHOD_NAME_CLEAR_BACKTEST);
|
|
35431
|
+
if (!this.enable.hasValue()) {
|
|
35432
|
+
throw new Error("NotificationAdapter is not enabled. Call enable() first.");
|
|
35433
|
+
}
|
|
35434
|
+
return await NotificationBacktest.clear();
|
|
35435
|
+
};
|
|
35436
|
+
/**
|
|
35437
|
+
* Clears all live notifications from storage.
|
|
35438
|
+
*
|
|
35439
|
+
* @throws Error if NotificationAdapter is not enabled
|
|
35440
|
+
*/
|
|
35441
|
+
this.clearLive = async () => {
|
|
35442
|
+
bt.loggerService.info(NOTIFICATION_ADAPTER_METHOD_NAME_CLEAR_LIVE);
|
|
35443
|
+
if (!this.enable.hasValue()) {
|
|
35444
|
+
throw new Error("NotificationAdapter is not enabled. Call enable() first.");
|
|
35445
|
+
}
|
|
35446
|
+
return await NotificationLive.clear();
|
|
35447
|
+
};
|
|
35448
|
+
}
|
|
35449
|
+
}
|
|
35450
|
+
/**
|
|
35451
|
+
* Global singleton instance of NotificationAdapter.
|
|
35452
|
+
* Provides unified notification management for backtest and live trading.
|
|
35453
|
+
*/
|
|
35454
|
+
const Notification = new NotificationAdapter();
|
|
35455
|
+
/**
|
|
35456
|
+
* Global singleton instance of NotificationLiveAdapter.
|
|
35457
|
+
* Provides live trading notification storage with pluggable backends.
|
|
35458
|
+
*/
|
|
35459
|
+
const NotificationLive = new NotificationLiveAdapter();
|
|
35460
|
+
/**
|
|
35461
|
+
* Global singleton instance of NotificationBacktestAdapter.
|
|
35462
|
+
* Provides backtest notification storage with pluggable backends.
|
|
35463
|
+
*/
|
|
35464
|
+
const NotificationBacktest = new NotificationBacktestAdapter();
|
|
35465
|
+
|
|
35466
|
+
const EXCHANGE_METHOD_NAME_GET_CANDLES = "ExchangeUtils.getCandles";
|
|
35467
|
+
const EXCHANGE_METHOD_NAME_GET_AVERAGE_PRICE = "ExchangeUtils.getAveragePrice";
|
|
35468
|
+
const EXCHANGE_METHOD_NAME_FORMAT_QUANTITY = "ExchangeUtils.formatQuantity";
|
|
35469
|
+
const EXCHANGE_METHOD_NAME_FORMAT_PRICE = "ExchangeUtils.formatPrice";
|
|
35470
|
+
const EXCHANGE_METHOD_NAME_GET_ORDER_BOOK = "ExchangeUtils.getOrderBook";
|
|
35471
|
+
const EXCHANGE_METHOD_NAME_GET_RAW_CANDLES = "ExchangeUtils.getRawCandles";
|
|
35472
|
+
const MS_PER_MINUTE = 60000;
|
|
35473
|
+
/**
|
|
35474
|
+
* Gets current timestamp from execution context if available.
|
|
35475
|
+
* Returns current Date() if no execution context exists (non-trading GUI).
|
|
35476
|
+
*/
|
|
35477
|
+
const GET_TIMESTAMP_FN = async () => {
|
|
35478
|
+
if (ExecutionContextService.hasContext()) {
|
|
35479
|
+
return new Date(bt.executionContextService.context.when);
|
|
35480
|
+
}
|
|
35481
|
+
return new Date();
|
|
35482
|
+
};
|
|
35483
|
+
/**
|
|
35484
|
+
* Gets backtest mode flag from execution context if available.
|
|
35485
|
+
* Returns false if no execution context exists (live mode).
|
|
35486
|
+
*/
|
|
35487
|
+
const GET_BACKTEST_FN = async () => {
|
|
35488
|
+
if (ExecutionContextService.hasContext()) {
|
|
35489
|
+
return bt.executionContextService.context.backtest;
|
|
35490
|
+
}
|
|
35491
|
+
return false;
|
|
35492
|
+
};
|
|
35493
|
+
/**
|
|
35494
|
+
* Default implementation for getCandles.
|
|
35495
|
+
* Throws an error indicating the method is not implemented.
|
|
35496
|
+
*/
|
|
35497
|
+
const DEFAULT_GET_CANDLES_FN = async (_symbol, _interval, _since, _limit, _backtest) => {
|
|
35498
|
+
throw new Error(`getCandles is not implemented for this exchange`);
|
|
35499
|
+
};
|
|
35500
|
+
/**
|
|
35501
|
+
* Default implementation for formatQuantity.
|
|
35502
|
+
* Returns Bitcoin precision on Binance (8 decimal places).
|
|
35503
|
+
*/
|
|
35504
|
+
const DEFAULT_FORMAT_QUANTITY_FN = async (_symbol, quantity, _backtest) => {
|
|
35505
|
+
return quantity.toFixed(8);
|
|
35506
|
+
};
|
|
35507
|
+
/**
|
|
35508
|
+
* Default implementation for formatPrice.
|
|
35509
|
+
* Returns Bitcoin precision on Binance (2 decimal places).
|
|
35510
|
+
*/
|
|
35511
|
+
const DEFAULT_FORMAT_PRICE_FN = async (_symbol, price, _backtest) => {
|
|
35512
|
+
return price.toFixed(2);
|
|
35513
|
+
};
|
|
35514
|
+
/**
|
|
35515
|
+
* Default implementation for getOrderBook.
|
|
35516
|
+
* Throws an error indicating the method is not implemented.
|
|
35517
|
+
*
|
|
35518
|
+
* @param _symbol - Trading pair symbol (unused)
|
|
35519
|
+
* @param _depth - Maximum depth levels (unused)
|
|
35520
|
+
* @param _from - Start of time range (unused - can be ignored in live implementations)
|
|
35521
|
+
* @param _to - End of time range (unused - can be ignored in live implementations)
|
|
35522
|
+
* @param _backtest - Whether running in backtest mode (unused)
|
|
35523
|
+
*/
|
|
35524
|
+
const DEFAULT_GET_ORDER_BOOK_FN = async (_symbol, _depth, _from, _to, _backtest) => {
|
|
35525
|
+
throw new Error(`getOrderBook is not implemented for this exchange`);
|
|
35526
|
+
};
|
|
35527
|
+
const INTERVAL_MINUTES$1 = {
|
|
35528
|
+
"1m": 1,
|
|
35529
|
+
"3m": 3,
|
|
35530
|
+
"5m": 5,
|
|
35531
|
+
"15m": 15,
|
|
35532
|
+
"30m": 30,
|
|
35533
|
+
"1h": 60,
|
|
35534
|
+
"2h": 120,
|
|
35535
|
+
"4h": 240,
|
|
35536
|
+
"6h": 360,
|
|
35537
|
+
"8h": 480,
|
|
35538
|
+
};
|
|
35539
|
+
/**
|
|
35540
|
+
* Aligns timestamp down to the nearest interval boundary.
|
|
35541
|
+
* For example, for 15m interval: 00:17 -> 00:15, 00:44 -> 00:30
|
|
35542
|
+
*
|
|
35543
|
+
* Candle timestamp convention:
|
|
35544
|
+
* - Candle timestamp = openTime (when candle opens)
|
|
35545
|
+
* - Candle with timestamp 00:00 covers period [00:00, 00:15) for 15m interval
|
|
35546
|
+
*
|
|
35547
|
+
* Adapter contract:
|
|
35548
|
+
* - Adapter must return candles with timestamp = openTime
|
|
35549
|
+
* - First returned candle.timestamp must equal aligned since
|
|
35550
|
+
* - Adapter must return exactly `limit` candles
|
|
35551
|
+
*
|
|
35552
|
+
* @param timestamp - Timestamp in milliseconds
|
|
35553
|
+
* @param intervalMinutes - Interval in minutes
|
|
35554
|
+
* @returns Aligned timestamp rounded down to interval boundary
|
|
35555
|
+
*/
|
|
35556
|
+
const ALIGN_TO_INTERVAL_FN = (timestamp, intervalMinutes) => {
|
|
35557
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE;
|
|
35558
|
+
return Math.floor(timestamp / intervalMs) * intervalMs;
|
|
35559
|
+
};
|
|
35560
|
+
/**
|
|
35561
|
+
* Creates exchange instance with methods resolved once during construction.
|
|
35562
|
+
* Applies default implementations where schema methods are not provided.
|
|
35563
|
+
*
|
|
35564
|
+
* @param schema - Exchange schema from registry
|
|
35565
|
+
* @returns Object with resolved exchange methods
|
|
35566
|
+
*/
|
|
35567
|
+
const CREATE_EXCHANGE_INSTANCE_FN = (schema) => {
|
|
35568
|
+
const getCandles = schema.getCandles ?? DEFAULT_GET_CANDLES_FN;
|
|
35569
|
+
const formatQuantity = schema.formatQuantity ?? DEFAULT_FORMAT_QUANTITY_FN;
|
|
35570
|
+
const formatPrice = schema.formatPrice ?? DEFAULT_FORMAT_PRICE_FN;
|
|
35571
|
+
const getOrderBook = schema.getOrderBook ?? DEFAULT_GET_ORDER_BOOK_FN;
|
|
35572
|
+
return {
|
|
35573
|
+
getCandles,
|
|
35574
|
+
formatQuantity,
|
|
35575
|
+
formatPrice,
|
|
35576
|
+
getOrderBook,
|
|
35577
|
+
};
|
|
35578
|
+
};
|
|
35579
|
+
/**
|
|
35580
|
+
* Attempts to read candles from cache.
|
|
35581
|
+
*
|
|
35582
|
+
* Cache lookup calculates expected timestamps:
|
|
35583
|
+
* sinceTimestamp + i * stepMs for i = 0..limit-1
|
|
35584
|
+
* Returns all candles if found, null if any missing.
|
|
35585
|
+
*
|
|
35586
|
+
* @param dto - Data transfer object containing symbol, interval, and limit
|
|
35587
|
+
* @param sinceTimestamp - Aligned start timestamp (openTime of first candle)
|
|
35588
|
+
* @param untilTimestamp - Unused, kept for API compatibility
|
|
35589
|
+
* @param exchangeName - Exchange name
|
|
35590
|
+
* @returns Cached candles array (exactly limit) or null if cache miss
|
|
35591
|
+
*/
|
|
35592
|
+
const READ_CANDLES_CACHE_FN = functoolsKit.trycatch(async (dto, sinceTimestamp, untilTimestamp, exchangeName) => {
|
|
35593
|
+
// PersistCandleAdapter.readCandlesData calculates expected timestamps:
|
|
35594
|
+
// sinceTimestamp + i * stepMs for i = 0..limit-1
|
|
35595
|
+
// Returns all candles if found, null if any missing
|
|
35596
|
+
const cachedCandles = await PersistCandleAdapter.readCandlesData(dto.symbol, dto.interval, exchangeName, dto.limit, sinceTimestamp, untilTimestamp);
|
|
35597
|
+
// Return cached data only if we have exactly the requested limit
|
|
35598
|
+
if (cachedCandles?.length === dto.limit) {
|
|
35599
|
+
bt.loggerService.debug(`ExchangeInstance READ_CANDLES_CACHE_FN: cache hit for exchangeName=${exchangeName}, symbol=${dto.symbol}, interval=${dto.interval}, limit=${dto.limit}`);
|
|
35600
|
+
return cachedCandles;
|
|
35601
|
+
}
|
|
35602
|
+
bt.loggerService.warn(`ExchangeInstance READ_CANDLES_CACHE_FN: cache inconsistent (count or range mismatch) for exchangeName=${exchangeName}, symbol=${dto.symbol}, interval=${dto.interval}, limit=${dto.limit}`);
|
|
35603
|
+
return null;
|
|
35604
|
+
}, {
|
|
35605
|
+
fallback: async (error) => {
|
|
35606
|
+
const message = `ExchangeInstance READ_CANDLES_CACHE_FN: cache read failed`;
|
|
35607
|
+
const payload = {
|
|
35608
|
+
error: functoolsKit.errorData(error),
|
|
35609
|
+
message: functoolsKit.getErrorMessage(error),
|
|
35610
|
+
};
|
|
35611
|
+
bt.loggerService.warn(message, payload);
|
|
35612
|
+
console.warn(message, payload);
|
|
35613
|
+
errorEmitter.next(error);
|
|
35614
|
+
},
|
|
35615
|
+
defaultValue: null,
|
|
35616
|
+
});
|
|
35617
|
+
/**
|
|
35618
|
+
* Writes candles to cache with error handling.
|
|
35619
|
+
*
|
|
35620
|
+
* The candles passed to this function should be validated:
|
|
35621
|
+
* - First candle.timestamp equals aligned sinceTimestamp (openTime)
|
|
35622
|
+
* - Exact number of candles as requested (limit)
|
|
35623
|
+
* - Sequential timestamps: sinceTimestamp + i * stepMs
|
|
35624
|
+
*
|
|
35625
|
+
* @param candles - Array of validated candle data to cache
|
|
35626
|
+
* @param dto - Data transfer object containing symbol, interval, and limit
|
|
35627
|
+
* @param exchangeName - Exchange name
|
|
35628
|
+
*/
|
|
35629
|
+
const WRITE_CANDLES_CACHE_FN = functoolsKit.trycatch(functoolsKit.queued(async (candles, dto, exchangeName) => {
|
|
35630
|
+
await PersistCandleAdapter.writeCandlesData(candles, dto.symbol, dto.interval, exchangeName);
|
|
35631
|
+
bt.loggerService.debug(`ExchangeInstance WRITE_CANDLES_CACHE_FN: cache updated for exchangeName=${exchangeName}, symbol=${dto.symbol}, interval=${dto.interval}, count=${candles.length}`);
|
|
35632
|
+
}), {
|
|
35633
|
+
fallback: async (error) => {
|
|
35634
|
+
const message = `ExchangeInstance WRITE_CANDLES_CACHE_FN: cache write failed`;
|
|
35635
|
+
const payload = {
|
|
35636
|
+
error: functoolsKit.errorData(error),
|
|
35637
|
+
message: functoolsKit.getErrorMessage(error),
|
|
35638
|
+
};
|
|
35639
|
+
bt.loggerService.warn(message, payload);
|
|
35640
|
+
console.warn(message, payload);
|
|
35641
|
+
errorEmitter.next(error);
|
|
35642
|
+
},
|
|
35643
|
+
defaultValue: null,
|
|
35644
|
+
});
|
|
35645
|
+
/**
|
|
35646
|
+
* Instance class for exchange operations on a specific exchange.
|
|
35647
|
+
*
|
|
35648
|
+
* Provides isolated exchange operations for a single exchange.
|
|
35649
|
+
* Each instance maintains its own context and exposes IExchangeSchema methods.
|
|
35650
|
+
* The schema is retrieved once during construction for better performance.
|
|
35651
|
+
*
|
|
35652
|
+
* @example
|
|
35653
|
+
* ```typescript
|
|
35654
|
+
* const instance = new ExchangeInstance("binance");
|
|
35655
|
+
*
|
|
35656
|
+
* const candles = await instance.getCandles("BTCUSDT", "1m", 100);
|
|
35657
|
+
* const vwap = await instance.getAveragePrice("BTCUSDT");
|
|
35658
|
+
* const formattedQty = await instance.formatQuantity("BTCUSDT", 0.001);
|
|
35659
|
+
* const formattedPrice = await instance.formatPrice("BTCUSDT", 50000.123);
|
|
35660
|
+
* ```
|
|
35661
|
+
*/
|
|
35662
|
+
class ExchangeInstance {
|
|
35663
|
+
/**
|
|
35664
|
+
* Creates a new ExchangeInstance for a specific exchange.
|
|
35665
|
+
*
|
|
35666
|
+
* @param exchangeName - Exchange name (e.g., "binance")
|
|
35667
|
+
*/
|
|
35668
|
+
constructor(exchangeName) {
|
|
35669
|
+
this.exchangeName = exchangeName;
|
|
35670
|
+
/**
|
|
35671
|
+
* Fetch candles from data source (API or database).
|
|
35672
|
+
*
|
|
35673
|
+
* Automatically calculates the start date based on Date.now() and the requested interval/limit.
|
|
35674
|
+
* Uses the same logic as ClientExchange to ensure backwards compatibility.
|
|
35675
|
+
*
|
|
35676
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
35677
|
+
* @param interval - Candle time interval (e.g., "1m", "1h")
|
|
35678
|
+
* @param limit - Maximum number of candles to fetch
|
|
35679
|
+
* @returns Promise resolving to array of OHLCV candle data
|
|
35680
|
+
*
|
|
35681
|
+
* @example
|
|
35682
|
+
* ```typescript
|
|
35683
|
+
* const instance = new ExchangeInstance("binance");
|
|
35684
|
+
* const candles = await instance.getCandles("BTCUSDT", "1m", 100);
|
|
35685
|
+
* ```
|
|
35686
|
+
*/
|
|
35687
|
+
this.getCandles = async (symbol, interval, limit) => {
|
|
35688
|
+
bt.loggerService.info(EXCHANGE_METHOD_NAME_GET_CANDLES, {
|
|
35689
|
+
exchangeName: this.exchangeName,
|
|
35690
|
+
symbol,
|
|
35691
|
+
interval,
|
|
35692
|
+
limit,
|
|
35693
|
+
});
|
|
35694
|
+
const getCandles = this._methods.getCandles;
|
|
35695
|
+
const step = INTERVAL_MINUTES$1[interval];
|
|
35696
|
+
if (!step) {
|
|
35697
|
+
throw new Error(`ExchangeInstance unknown interval=${interval}`);
|
|
35698
|
+
}
|
|
35699
|
+
const stepMs = step * MS_PER_MINUTE;
|
|
35700
|
+
// Align when down to interval boundary
|
|
35701
|
+
const when = await GET_TIMESTAMP_FN();
|
|
35702
|
+
const whenTimestamp = when.getTime();
|
|
35703
|
+
const alignedWhen = ALIGN_TO_INTERVAL_FN(whenTimestamp, step);
|
|
35704
|
+
// Calculate since: go back limit candles from aligned when
|
|
35705
|
+
const sinceTimestamp = alignedWhen - limit * stepMs;
|
|
35706
|
+
const since = new Date(sinceTimestamp);
|
|
35707
|
+
const untilTimestamp = alignedWhen;
|
|
35708
|
+
// Try to read from cache first
|
|
35709
|
+
const cachedCandles = await READ_CANDLES_CACHE_FN({ symbol, interval, limit }, sinceTimestamp, untilTimestamp, this.exchangeName);
|
|
35710
|
+
if (cachedCandles !== null) {
|
|
35711
|
+
return cachedCandles;
|
|
35712
|
+
}
|
|
35713
|
+
let allData = [];
|
|
35714
|
+
// If limit exceeds CC_MAX_CANDLES_PER_REQUEST, fetch data in chunks
|
|
35715
|
+
if (limit > GLOBAL_CONFIG.CC_MAX_CANDLES_PER_REQUEST) {
|
|
35716
|
+
let remaining = limit;
|
|
35717
|
+
let currentSince = new Date(since.getTime());
|
|
35718
|
+
const isBacktest = await GET_BACKTEST_FN();
|
|
35719
|
+
while (remaining > 0) {
|
|
35720
|
+
const chunkLimit = Math.min(remaining, GLOBAL_CONFIG.CC_MAX_CANDLES_PER_REQUEST);
|
|
35721
|
+
const chunkData = await getCandles(symbol, interval, currentSince, chunkLimit, isBacktest);
|
|
35722
|
+
allData.push(...chunkData);
|
|
35723
|
+
remaining -= chunkLimit;
|
|
35724
|
+
if (remaining > 0) {
|
|
35725
|
+
// Move currentSince forward by the number of candles fetched
|
|
35726
|
+
currentSince = new Date(currentSince.getTime() + chunkLimit * stepMs);
|
|
35727
|
+
}
|
|
35728
|
+
}
|
|
35729
|
+
}
|
|
35730
|
+
else {
|
|
35731
|
+
const isBacktest = await GET_BACKTEST_FN();
|
|
35732
|
+
allData = await getCandles(symbol, interval, since, limit, isBacktest);
|
|
35733
|
+
}
|
|
35734
|
+
// Apply distinct by timestamp to remove duplicates
|
|
35735
|
+
const uniqueData = Array.from(new Map(allData.map((candle) => [candle.timestamp, candle])).values());
|
|
35736
|
+
if (allData.length !== uniqueData.length) {
|
|
35737
|
+
bt.loggerService.warn(`ExchangeInstance getCandles: Removed ${allData.length - uniqueData.length} duplicate candles by timestamp`);
|
|
35738
|
+
}
|
|
35739
|
+
// Validate adapter returned data
|
|
35740
|
+
if (uniqueData.length === 0) {
|
|
35741
|
+
throw new Error(`ExchangeInstance getCandles: adapter returned empty array. ` +
|
|
35742
|
+
`Expected ${limit} candles starting from openTime=${sinceTimestamp}.`);
|
|
35743
|
+
}
|
|
35744
|
+
if (uniqueData[0].timestamp !== sinceTimestamp) {
|
|
35745
|
+
throw new Error(`ExchangeInstance getCandles: first candle timestamp mismatch. ` +
|
|
35746
|
+
`Expected openTime=${sinceTimestamp}, got=${uniqueData[0].timestamp}. ` +
|
|
35747
|
+
`Adapter must return candles with timestamp=openTime, starting from aligned since.`);
|
|
35748
|
+
}
|
|
35749
|
+
if (uniqueData.length !== limit) {
|
|
35750
|
+
throw new Error(`ExchangeInstance getCandles: candle count mismatch. ` +
|
|
35751
|
+
`Expected ${limit} candles, got ${uniqueData.length}. ` +
|
|
35752
|
+
`Adapter must return exact number of candles requested.`);
|
|
35753
|
+
}
|
|
35754
|
+
// Write to cache after successful fetch
|
|
35755
|
+
await WRITE_CANDLES_CACHE_FN(uniqueData, { symbol, interval, limit }, this.exchangeName);
|
|
35756
|
+
return uniqueData;
|
|
35757
|
+
};
|
|
35758
|
+
/**
|
|
35759
|
+
* Calculates VWAP (Volume Weighted Average Price) from last N 1m candles.
|
|
35760
|
+
* The number of candles is configurable via GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT.
|
|
35761
|
+
*
|
|
35762
|
+
* Formula:
|
|
35763
|
+
* - Typical Price = (high + low + close) / 3
|
|
35764
|
+
* - VWAP = sum(typical_price * volume) / sum(volume)
|
|
35765
|
+
*
|
|
35766
|
+
* If volume is zero, returns simple average of close prices.
|
|
35767
|
+
*
|
|
35768
|
+
* @param symbol - Trading pair symbol
|
|
35769
|
+
* @returns Promise resolving to VWAP price
|
|
35770
|
+
* @throws Error if no candles available
|
|
35771
|
+
*
|
|
35772
|
+
* @example
|
|
35773
|
+
* ```typescript
|
|
35774
|
+
* const instance = new ExchangeInstance("binance");
|
|
35775
|
+
* const vwap = await instance.getAveragePrice("BTCUSDT");
|
|
35776
|
+
* console.log(vwap); // 50125.43
|
|
35777
|
+
* ```
|
|
35778
|
+
*/
|
|
35779
|
+
this.getAveragePrice = async (symbol) => {
|
|
35780
|
+
bt.loggerService.debug(`ExchangeInstance getAveragePrice`, {
|
|
35781
|
+
exchangeName: this.exchangeName,
|
|
35782
|
+
symbol,
|
|
35783
|
+
});
|
|
35784
|
+
const candles = await this.getCandles(symbol, "1m", GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT);
|
|
33962
35785
|
if (candles.length === 0) {
|
|
33963
35786
|
throw new Error(`ExchangeInstance getAveragePrice: no candles data for symbol=${symbol}`);
|
|
33964
35787
|
}
|
|
@@ -34390,7 +36213,7 @@ const INTERVAL_MINUTES = {
|
|
|
34390
36213
|
* @param backtest - Whether running in backtest mode
|
|
34391
36214
|
* @returns Cache key string
|
|
34392
36215
|
*/
|
|
34393
|
-
const CREATE_KEY_FN
|
|
36216
|
+
const CREATE_KEY_FN = (strategyName, exchangeName, frameName, backtest) => {
|
|
34394
36217
|
const parts = [strategyName, exchangeName];
|
|
34395
36218
|
if (frameName)
|
|
34396
36219
|
parts.push(frameName);
|
|
@@ -34472,7 +36295,7 @@ class CacheInstance {
|
|
|
34472
36295
|
throw new Error(`CacheInstance unknown cache ttl interval=${this.interval}`);
|
|
34473
36296
|
}
|
|
34474
36297
|
}
|
|
34475
|
-
const key = CREATE_KEY_FN
|
|
36298
|
+
const key = CREATE_KEY_FN(bt.methodContextService.context.strategyName, bt.methodContextService.context.exchangeName, bt.methodContextService.context.frameName, bt.executionContextService.context.backtest);
|
|
34476
36299
|
const currentWhen = bt.executionContextService.context.when;
|
|
34477
36300
|
const cached = this._cacheMap.get(key);
|
|
34478
36301
|
if (cached) {
|
|
@@ -34510,7 +36333,7 @@ class CacheInstance {
|
|
|
34510
36333
|
* ```
|
|
34511
36334
|
*/
|
|
34512
36335
|
this.clear = () => {
|
|
34513
|
-
const key = CREATE_KEY_FN
|
|
36336
|
+
const key = CREATE_KEY_FN(bt.methodContextService.context.strategyName, bt.methodContextService.context.exchangeName, bt.methodContextService.context.frameName, bt.executionContextService.context.backtest);
|
|
34514
36337
|
this._cacheMap.delete(key);
|
|
34515
36338
|
};
|
|
34516
36339
|
}
|
|
@@ -34658,663 +36481,6 @@ class CacheUtils {
|
|
|
34658
36481
|
*/
|
|
34659
36482
|
const Cache = new CacheUtils();
|
|
34660
36483
|
|
|
34661
|
-
/** Maximum number of notifications to store in history */
|
|
34662
|
-
const MAX_NOTIFICATIONS = 250;
|
|
34663
|
-
/** Function to create unique notification IDs */
|
|
34664
|
-
const CREATE_KEY_FN = () => functoolsKit.randomString();
|
|
34665
|
-
/**
|
|
34666
|
-
* Instance class for notification history management.
|
|
34667
|
-
*
|
|
34668
|
-
* Contains all business logic for notification collection from emitters/subjects.
|
|
34669
|
-
* Stores notifications in chronological order with automatic limit management.
|
|
34670
|
-
*
|
|
34671
|
-
* @example
|
|
34672
|
-
* ```typescript
|
|
34673
|
-
* const instance = new NotificationInstance();
|
|
34674
|
-
* await instance.waitForInit();
|
|
34675
|
-
*
|
|
34676
|
-
* // Get all notifications
|
|
34677
|
-
* const all = instance.getData();
|
|
34678
|
-
*
|
|
34679
|
-
* // Process notifications with type discrimination
|
|
34680
|
-
* all.forEach(notification => {
|
|
34681
|
-
* switch (notification.type) {
|
|
34682
|
-
* case "signal.closed":
|
|
34683
|
-
* console.log(`Closed: ${notification.pnlPercentage}%`);
|
|
34684
|
-
* break;
|
|
34685
|
-
* case "partial.loss":
|
|
34686
|
-
* if (notification.level >= 30) {
|
|
34687
|
-
* alert("High loss!");
|
|
34688
|
-
* }
|
|
34689
|
-
* break;
|
|
34690
|
-
* case "risk.rejection":
|
|
34691
|
-
* console.warn(notification.rejectionNote);
|
|
34692
|
-
* break;
|
|
34693
|
-
* }
|
|
34694
|
-
* });
|
|
34695
|
-
*
|
|
34696
|
-
* // Clear history
|
|
34697
|
-
* instance.clear();
|
|
34698
|
-
* ```
|
|
34699
|
-
*/
|
|
34700
|
-
class NotificationInstance {
|
|
34701
|
-
constructor() {
|
|
34702
|
-
/** Internal notification history storage (newest first) */
|
|
34703
|
-
this._notifications = [];
|
|
34704
|
-
/**
|
|
34705
|
-
* Processes signal events and creates appropriate notifications.
|
|
34706
|
-
* Sorts signal notifications by createdAt to maintain chronological order.
|
|
34707
|
-
*/
|
|
34708
|
-
this._handleSignal = async (data) => {
|
|
34709
|
-
if (data.action === "opened") {
|
|
34710
|
-
this._addNotification({
|
|
34711
|
-
type: "signal.opened",
|
|
34712
|
-
id: CREATE_KEY_FN(),
|
|
34713
|
-
timestamp: data.signal.pendingAt,
|
|
34714
|
-
backtest: data.backtest,
|
|
34715
|
-
symbol: data.symbol,
|
|
34716
|
-
strategyName: data.strategyName,
|
|
34717
|
-
exchangeName: data.exchangeName,
|
|
34718
|
-
signalId: data.signal.id,
|
|
34719
|
-
position: data.signal.position,
|
|
34720
|
-
priceOpen: data.signal.priceOpen,
|
|
34721
|
-
priceTakeProfit: data.signal.priceTakeProfit,
|
|
34722
|
-
priceStopLoss: data.signal.priceStopLoss,
|
|
34723
|
-
originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
|
|
34724
|
-
originalPriceStopLoss: data.signal.originalPriceStopLoss,
|
|
34725
|
-
note: data.signal.note,
|
|
34726
|
-
scheduledAt: data.signal.scheduledAt,
|
|
34727
|
-
pendingAt: data.signal.pendingAt,
|
|
34728
|
-
createdAt: data.createdAt,
|
|
34729
|
-
});
|
|
34730
|
-
}
|
|
34731
|
-
else if (data.action === "closed") {
|
|
34732
|
-
const durationMs = data.closeTimestamp - data.signal.pendingAt;
|
|
34733
|
-
const durationMin = Math.round(durationMs / 60000);
|
|
34734
|
-
this._addNotification({
|
|
34735
|
-
type: "signal.closed",
|
|
34736
|
-
id: CREATE_KEY_FN(),
|
|
34737
|
-
timestamp: data.closeTimestamp,
|
|
34738
|
-
backtest: data.backtest,
|
|
34739
|
-
symbol: data.symbol,
|
|
34740
|
-
strategyName: data.strategyName,
|
|
34741
|
-
exchangeName: data.exchangeName,
|
|
34742
|
-
signalId: data.signal.id,
|
|
34743
|
-
position: data.signal.position,
|
|
34744
|
-
priceOpen: data.signal.priceOpen,
|
|
34745
|
-
priceClose: data.currentPrice,
|
|
34746
|
-
priceTakeProfit: data.signal.priceTakeProfit,
|
|
34747
|
-
priceStopLoss: data.signal.priceStopLoss,
|
|
34748
|
-
originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
|
|
34749
|
-
originalPriceStopLoss: data.signal.originalPriceStopLoss,
|
|
34750
|
-
pnlPercentage: data.pnl.pnlPercentage,
|
|
34751
|
-
closeReason: data.closeReason,
|
|
34752
|
-
duration: durationMin,
|
|
34753
|
-
note: data.signal.note,
|
|
34754
|
-
scheduledAt: data.signal.scheduledAt,
|
|
34755
|
-
pendingAt: data.signal.pendingAt,
|
|
34756
|
-
createdAt: data.createdAt,
|
|
34757
|
-
});
|
|
34758
|
-
}
|
|
34759
|
-
else if (data.action === "scheduled") {
|
|
34760
|
-
this._addNotification({
|
|
34761
|
-
type: "signal.scheduled",
|
|
34762
|
-
id: CREATE_KEY_FN(),
|
|
34763
|
-
timestamp: data.signal.scheduledAt,
|
|
34764
|
-
backtest: data.backtest,
|
|
34765
|
-
symbol: data.symbol,
|
|
34766
|
-
strategyName: data.strategyName,
|
|
34767
|
-
exchangeName: data.exchangeName,
|
|
34768
|
-
signalId: data.signal.id,
|
|
34769
|
-
position: data.signal.position,
|
|
34770
|
-
priceOpen: data.signal.priceOpen,
|
|
34771
|
-
priceTakeProfit: data.signal.priceTakeProfit,
|
|
34772
|
-
priceStopLoss: data.signal.priceStopLoss,
|
|
34773
|
-
originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
|
|
34774
|
-
originalPriceStopLoss: data.signal.originalPriceStopLoss,
|
|
34775
|
-
scheduledAt: data.signal.scheduledAt,
|
|
34776
|
-
currentPrice: data.currentPrice,
|
|
34777
|
-
createdAt: data.createdAt,
|
|
34778
|
-
});
|
|
34779
|
-
}
|
|
34780
|
-
else if (data.action === "cancelled") {
|
|
34781
|
-
const durationMs = data.closeTimestamp - data.signal.scheduledAt;
|
|
34782
|
-
const durationMin = Math.round(durationMs / 60000);
|
|
34783
|
-
this._addNotification({
|
|
34784
|
-
type: "signal.cancelled",
|
|
34785
|
-
id: CREATE_KEY_FN(),
|
|
34786
|
-
timestamp: data.closeTimestamp,
|
|
34787
|
-
backtest: data.backtest,
|
|
34788
|
-
symbol: data.symbol,
|
|
34789
|
-
strategyName: data.strategyName,
|
|
34790
|
-
exchangeName: data.exchangeName,
|
|
34791
|
-
signalId: data.signal.id,
|
|
34792
|
-
position: data.signal.position,
|
|
34793
|
-
priceOpen: data.signal.priceOpen,
|
|
34794
|
-
priceTakeProfit: data.signal.priceTakeProfit,
|
|
34795
|
-
priceStopLoss: data.signal.priceStopLoss,
|
|
34796
|
-
originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
|
|
34797
|
-
originalPriceStopLoss: data.signal.originalPriceStopLoss,
|
|
34798
|
-
cancelReason: data.reason,
|
|
34799
|
-
cancelId: data.cancelId,
|
|
34800
|
-
duration: durationMin,
|
|
34801
|
-
scheduledAt: data.signal.scheduledAt,
|
|
34802
|
-
pendingAt: data.signal.pendingAt,
|
|
34803
|
-
createdAt: data.createdAt,
|
|
34804
|
-
});
|
|
34805
|
-
}
|
|
34806
|
-
};
|
|
34807
|
-
/**
|
|
34808
|
-
* Processes partial profit events.
|
|
34809
|
-
*/
|
|
34810
|
-
this._handlePartialProfit = async (data) => {
|
|
34811
|
-
this._addNotification({
|
|
34812
|
-
type: "partial_profit.available",
|
|
34813
|
-
id: CREATE_KEY_FN(),
|
|
34814
|
-
timestamp: data.timestamp,
|
|
34815
|
-
backtest: data.backtest,
|
|
34816
|
-
symbol: data.symbol,
|
|
34817
|
-
strategyName: data.strategyName,
|
|
34818
|
-
exchangeName: data.exchangeName,
|
|
34819
|
-
signalId: data.data.id,
|
|
34820
|
-
level: data.level,
|
|
34821
|
-
currentPrice: data.currentPrice,
|
|
34822
|
-
priceOpen: data.data.priceOpen,
|
|
34823
|
-
position: data.data.position,
|
|
34824
|
-
priceTakeProfit: data.data.priceTakeProfit,
|
|
34825
|
-
priceStopLoss: data.data.priceStopLoss,
|
|
34826
|
-
originalPriceTakeProfit: data.data.originalPriceTakeProfit,
|
|
34827
|
-
originalPriceStopLoss: data.data.originalPriceStopLoss,
|
|
34828
|
-
scheduledAt: data.data.scheduledAt,
|
|
34829
|
-
pendingAt: data.data.pendingAt,
|
|
34830
|
-
createdAt: data.timestamp,
|
|
34831
|
-
});
|
|
34832
|
-
};
|
|
34833
|
-
/**
|
|
34834
|
-
* Processes partial loss events.
|
|
34835
|
-
*/
|
|
34836
|
-
this._handlePartialLoss = async (data) => {
|
|
34837
|
-
this._addNotification({
|
|
34838
|
-
type: "partial_loss.available",
|
|
34839
|
-
id: CREATE_KEY_FN(),
|
|
34840
|
-
timestamp: data.timestamp,
|
|
34841
|
-
backtest: data.backtest,
|
|
34842
|
-
symbol: data.symbol,
|
|
34843
|
-
strategyName: data.strategyName,
|
|
34844
|
-
exchangeName: data.exchangeName,
|
|
34845
|
-
signalId: data.data.id,
|
|
34846
|
-
level: data.level,
|
|
34847
|
-
currentPrice: data.currentPrice,
|
|
34848
|
-
priceOpen: data.data.priceOpen,
|
|
34849
|
-
position: data.data.position,
|
|
34850
|
-
priceTakeProfit: data.data.priceTakeProfit,
|
|
34851
|
-
priceStopLoss: data.data.priceStopLoss,
|
|
34852
|
-
originalPriceTakeProfit: data.data.originalPriceTakeProfit,
|
|
34853
|
-
originalPriceStopLoss: data.data.originalPriceStopLoss,
|
|
34854
|
-
scheduledAt: data.data.scheduledAt,
|
|
34855
|
-
pendingAt: data.data.pendingAt,
|
|
34856
|
-
createdAt: data.timestamp,
|
|
34857
|
-
});
|
|
34858
|
-
};
|
|
34859
|
-
/**
|
|
34860
|
-
* Processes breakeven events.
|
|
34861
|
-
*/
|
|
34862
|
-
this._handleBreakeven = async (data) => {
|
|
34863
|
-
this._addNotification({
|
|
34864
|
-
type: "breakeven.available",
|
|
34865
|
-
id: CREATE_KEY_FN(),
|
|
34866
|
-
timestamp: data.timestamp,
|
|
34867
|
-
backtest: data.backtest,
|
|
34868
|
-
symbol: data.symbol,
|
|
34869
|
-
strategyName: data.strategyName,
|
|
34870
|
-
exchangeName: data.exchangeName,
|
|
34871
|
-
signalId: data.data.id,
|
|
34872
|
-
currentPrice: data.currentPrice,
|
|
34873
|
-
priceOpen: data.data.priceOpen,
|
|
34874
|
-
position: data.data.position,
|
|
34875
|
-
priceTakeProfit: data.data.priceTakeProfit,
|
|
34876
|
-
priceStopLoss: data.data.priceStopLoss,
|
|
34877
|
-
originalPriceTakeProfit: data.data.originalPriceTakeProfit,
|
|
34878
|
-
originalPriceStopLoss: data.data.originalPriceStopLoss,
|
|
34879
|
-
scheduledAt: data.data.scheduledAt,
|
|
34880
|
-
pendingAt: data.data.pendingAt,
|
|
34881
|
-
createdAt: data.timestamp,
|
|
34882
|
-
});
|
|
34883
|
-
};
|
|
34884
|
-
/**
|
|
34885
|
-
* Processes strategy commit events.
|
|
34886
|
-
*/
|
|
34887
|
-
this._handleStrategyCommit = async (data) => {
|
|
34888
|
-
if (data.action === "partial-profit") {
|
|
34889
|
-
this._addNotification({
|
|
34890
|
-
type: "partial_profit.commit",
|
|
34891
|
-
id: CREATE_KEY_FN(),
|
|
34892
|
-
timestamp: data.timestamp,
|
|
34893
|
-
backtest: data.backtest,
|
|
34894
|
-
symbol: data.symbol,
|
|
34895
|
-
strategyName: data.strategyName,
|
|
34896
|
-
exchangeName: data.exchangeName,
|
|
34897
|
-
signalId: data.signalId,
|
|
34898
|
-
percentToClose: data.percentToClose,
|
|
34899
|
-
currentPrice: data.currentPrice,
|
|
34900
|
-
position: data.position,
|
|
34901
|
-
priceOpen: data.priceOpen,
|
|
34902
|
-
priceTakeProfit: data.priceTakeProfit,
|
|
34903
|
-
priceStopLoss: data.priceStopLoss,
|
|
34904
|
-
originalPriceTakeProfit: data.originalPriceTakeProfit,
|
|
34905
|
-
originalPriceStopLoss: data.originalPriceStopLoss,
|
|
34906
|
-
scheduledAt: data.scheduledAt,
|
|
34907
|
-
pendingAt: data.pendingAt,
|
|
34908
|
-
createdAt: data.timestamp,
|
|
34909
|
-
});
|
|
34910
|
-
}
|
|
34911
|
-
else if (data.action === "partial-loss") {
|
|
34912
|
-
this._addNotification({
|
|
34913
|
-
type: "partial_loss.commit",
|
|
34914
|
-
id: CREATE_KEY_FN(),
|
|
34915
|
-
timestamp: data.timestamp,
|
|
34916
|
-
backtest: data.backtest,
|
|
34917
|
-
symbol: data.symbol,
|
|
34918
|
-
strategyName: data.strategyName,
|
|
34919
|
-
exchangeName: data.exchangeName,
|
|
34920
|
-
signalId: data.signalId,
|
|
34921
|
-
percentToClose: data.percentToClose,
|
|
34922
|
-
currentPrice: data.currentPrice,
|
|
34923
|
-
position: data.position,
|
|
34924
|
-
priceOpen: data.priceOpen,
|
|
34925
|
-
priceTakeProfit: data.priceTakeProfit,
|
|
34926
|
-
priceStopLoss: data.priceStopLoss,
|
|
34927
|
-
originalPriceTakeProfit: data.originalPriceTakeProfit,
|
|
34928
|
-
originalPriceStopLoss: data.originalPriceStopLoss,
|
|
34929
|
-
scheduledAt: data.scheduledAt,
|
|
34930
|
-
pendingAt: data.pendingAt,
|
|
34931
|
-
createdAt: data.timestamp,
|
|
34932
|
-
});
|
|
34933
|
-
}
|
|
34934
|
-
else if (data.action === "breakeven") {
|
|
34935
|
-
this._addNotification({
|
|
34936
|
-
type: "breakeven.commit",
|
|
34937
|
-
id: CREATE_KEY_FN(),
|
|
34938
|
-
timestamp: data.timestamp,
|
|
34939
|
-
backtest: data.backtest,
|
|
34940
|
-
symbol: data.symbol,
|
|
34941
|
-
strategyName: data.strategyName,
|
|
34942
|
-
exchangeName: data.exchangeName,
|
|
34943
|
-
signalId: data.signalId,
|
|
34944
|
-
currentPrice: data.currentPrice,
|
|
34945
|
-
position: data.position,
|
|
34946
|
-
priceOpen: data.priceOpen,
|
|
34947
|
-
priceTakeProfit: data.priceTakeProfit,
|
|
34948
|
-
priceStopLoss: data.priceStopLoss,
|
|
34949
|
-
originalPriceTakeProfit: data.originalPriceTakeProfit,
|
|
34950
|
-
originalPriceStopLoss: data.originalPriceStopLoss,
|
|
34951
|
-
scheduledAt: data.scheduledAt,
|
|
34952
|
-
pendingAt: data.pendingAt,
|
|
34953
|
-
createdAt: data.timestamp,
|
|
34954
|
-
});
|
|
34955
|
-
}
|
|
34956
|
-
else if (data.action === "trailing-stop") {
|
|
34957
|
-
this._addNotification({
|
|
34958
|
-
type: "trailing_stop.commit",
|
|
34959
|
-
id: CREATE_KEY_FN(),
|
|
34960
|
-
timestamp: data.timestamp,
|
|
34961
|
-
backtest: data.backtest,
|
|
34962
|
-
symbol: data.symbol,
|
|
34963
|
-
strategyName: data.strategyName,
|
|
34964
|
-
exchangeName: data.exchangeName,
|
|
34965
|
-
signalId: data.signalId,
|
|
34966
|
-
percentShift: data.percentShift,
|
|
34967
|
-
currentPrice: data.currentPrice,
|
|
34968
|
-
position: data.position,
|
|
34969
|
-
priceOpen: data.priceOpen,
|
|
34970
|
-
priceTakeProfit: data.priceTakeProfit,
|
|
34971
|
-
priceStopLoss: data.priceStopLoss,
|
|
34972
|
-
originalPriceTakeProfit: data.originalPriceTakeProfit,
|
|
34973
|
-
originalPriceStopLoss: data.originalPriceStopLoss,
|
|
34974
|
-
scheduledAt: data.scheduledAt,
|
|
34975
|
-
pendingAt: data.pendingAt,
|
|
34976
|
-
createdAt: data.timestamp,
|
|
34977
|
-
});
|
|
34978
|
-
}
|
|
34979
|
-
else if (data.action === "trailing-take") {
|
|
34980
|
-
this._addNotification({
|
|
34981
|
-
type: "trailing_take.commit",
|
|
34982
|
-
id: CREATE_KEY_FN(),
|
|
34983
|
-
timestamp: data.timestamp,
|
|
34984
|
-
backtest: data.backtest,
|
|
34985
|
-
symbol: data.symbol,
|
|
34986
|
-
strategyName: data.strategyName,
|
|
34987
|
-
exchangeName: data.exchangeName,
|
|
34988
|
-
signalId: data.signalId,
|
|
34989
|
-
percentShift: data.percentShift,
|
|
34990
|
-
currentPrice: data.currentPrice,
|
|
34991
|
-
position: data.position,
|
|
34992
|
-
priceOpen: data.priceOpen,
|
|
34993
|
-
priceTakeProfit: data.priceTakeProfit,
|
|
34994
|
-
priceStopLoss: data.priceStopLoss,
|
|
34995
|
-
originalPriceTakeProfit: data.originalPriceTakeProfit,
|
|
34996
|
-
originalPriceStopLoss: data.originalPriceStopLoss,
|
|
34997
|
-
scheduledAt: data.scheduledAt,
|
|
34998
|
-
pendingAt: data.pendingAt,
|
|
34999
|
-
createdAt: data.timestamp,
|
|
35000
|
-
});
|
|
35001
|
-
}
|
|
35002
|
-
else if (data.action === "activate-scheduled") {
|
|
35003
|
-
this._addNotification({
|
|
35004
|
-
type: "activate_scheduled.commit",
|
|
35005
|
-
id: CREATE_KEY_FN(),
|
|
35006
|
-
timestamp: data.timestamp,
|
|
35007
|
-
backtest: data.backtest,
|
|
35008
|
-
symbol: data.symbol,
|
|
35009
|
-
strategyName: data.strategyName,
|
|
35010
|
-
exchangeName: data.exchangeName,
|
|
35011
|
-
signalId: data.signalId,
|
|
35012
|
-
activateId: data.activateId,
|
|
35013
|
-
currentPrice: data.currentPrice,
|
|
35014
|
-
position: data.position,
|
|
35015
|
-
priceOpen: data.priceOpen,
|
|
35016
|
-
priceTakeProfit: data.priceTakeProfit,
|
|
35017
|
-
priceStopLoss: data.priceStopLoss,
|
|
35018
|
-
originalPriceTakeProfit: data.originalPriceTakeProfit,
|
|
35019
|
-
originalPriceStopLoss: data.originalPriceStopLoss,
|
|
35020
|
-
scheduledAt: data.scheduledAt,
|
|
35021
|
-
pendingAt: data.pendingAt,
|
|
35022
|
-
createdAt: data.timestamp,
|
|
35023
|
-
});
|
|
35024
|
-
}
|
|
35025
|
-
};
|
|
35026
|
-
/**
|
|
35027
|
-
* Processes risk rejection events.
|
|
35028
|
-
*/
|
|
35029
|
-
this._handleRisk = async (data) => {
|
|
35030
|
-
this._addNotification({
|
|
35031
|
-
type: "risk.rejection",
|
|
35032
|
-
id: CREATE_KEY_FN(),
|
|
35033
|
-
timestamp: data.timestamp,
|
|
35034
|
-
backtest: data.backtest,
|
|
35035
|
-
symbol: data.symbol,
|
|
35036
|
-
strategyName: data.strategyName,
|
|
35037
|
-
exchangeName: data.exchangeName,
|
|
35038
|
-
rejectionNote: data.rejectionNote,
|
|
35039
|
-
rejectionId: data.rejectionId,
|
|
35040
|
-
activePositionCount: data.activePositionCount,
|
|
35041
|
-
currentPrice: data.currentPrice,
|
|
35042
|
-
signalId: data.currentSignal.id,
|
|
35043
|
-
position: data.currentSignal.position,
|
|
35044
|
-
priceOpen: data.currentSignal.priceOpen,
|
|
35045
|
-
priceTakeProfit: data.currentSignal.priceTakeProfit,
|
|
35046
|
-
priceStopLoss: data.currentSignal.priceStopLoss,
|
|
35047
|
-
minuteEstimatedTime: data.currentSignal.minuteEstimatedTime,
|
|
35048
|
-
signalNote: data.currentSignal.note,
|
|
35049
|
-
createdAt: data.timestamp,
|
|
35050
|
-
});
|
|
35051
|
-
};
|
|
35052
|
-
/**
|
|
35053
|
-
* Processes error events.
|
|
35054
|
-
*/
|
|
35055
|
-
this._handleError = async (error) => {
|
|
35056
|
-
this._addNotification({
|
|
35057
|
-
type: "error.info",
|
|
35058
|
-
id: CREATE_KEY_FN(),
|
|
35059
|
-
error: functoolsKit.errorData(error),
|
|
35060
|
-
message: functoolsKit.getErrorMessage(error),
|
|
35061
|
-
backtest: false,
|
|
35062
|
-
});
|
|
35063
|
-
};
|
|
35064
|
-
/**
|
|
35065
|
-
* Processes critical error events.
|
|
35066
|
-
*/
|
|
35067
|
-
this._handleCriticalError = async (error) => {
|
|
35068
|
-
this._addNotification({
|
|
35069
|
-
type: "error.critical",
|
|
35070
|
-
id: CREATE_KEY_FN(),
|
|
35071
|
-
error: functoolsKit.errorData(error),
|
|
35072
|
-
message: functoolsKit.getErrorMessage(error),
|
|
35073
|
-
backtest: false,
|
|
35074
|
-
});
|
|
35075
|
-
};
|
|
35076
|
-
/**
|
|
35077
|
-
* Processes validation error events.
|
|
35078
|
-
*/
|
|
35079
|
-
this._handleValidationError = async (error) => {
|
|
35080
|
-
this._addNotification({
|
|
35081
|
-
type: "error.validation",
|
|
35082
|
-
id: CREATE_KEY_FN(),
|
|
35083
|
-
error: functoolsKit.errorData(error),
|
|
35084
|
-
message: functoolsKit.getErrorMessage(error),
|
|
35085
|
-
backtest: false,
|
|
35086
|
-
});
|
|
35087
|
-
};
|
|
35088
|
-
/**
|
|
35089
|
-
* Subscribes to all notification emitters and returns an unsubscribe function.
|
|
35090
|
-
* Protected against multiple subscriptions using singleshot.
|
|
35091
|
-
*
|
|
35092
|
-
* @returns Unsubscribe function to stop receiving all notification events
|
|
35093
|
-
*
|
|
35094
|
-
* @example
|
|
35095
|
-
* ```typescript
|
|
35096
|
-
* const instance = new NotificationInstance();
|
|
35097
|
-
* const unsubscribe = instance.subscribe();
|
|
35098
|
-
* // ... later
|
|
35099
|
-
* unsubscribe();
|
|
35100
|
-
* ```
|
|
35101
|
-
*/
|
|
35102
|
-
this.enable = functoolsKit.singleshot(() => {
|
|
35103
|
-
const unSignal = signalEmitter.subscribe(this._handleSignal);
|
|
35104
|
-
const unProfit = partialProfitSubject.subscribe(this._handlePartialProfit);
|
|
35105
|
-
const unLoss = partialLossSubject.subscribe(this._handlePartialLoss);
|
|
35106
|
-
const unBreakeven = breakevenSubject.subscribe(this._handleBreakeven);
|
|
35107
|
-
const unStrategyCommit = strategyCommitSubject.subscribe(this._handleStrategyCommit);
|
|
35108
|
-
const unRisk = riskSubject.subscribe(this._handleRisk);
|
|
35109
|
-
const unError = errorEmitter.subscribe(this._handleError);
|
|
35110
|
-
const unExit = exitEmitter.subscribe(this._handleCriticalError);
|
|
35111
|
-
const unValidation = validationSubject.subscribe(this._handleValidationError);
|
|
35112
|
-
const disposeFn = functoolsKit.compose(() => unSignal(), () => unProfit(), () => unLoss(), () => unBreakeven(), () => unStrategyCommit(), () => unRisk(), () => unError(), () => unExit(), () => unValidation());
|
|
35113
|
-
return () => {
|
|
35114
|
-
disposeFn();
|
|
35115
|
-
this.enable.clear();
|
|
35116
|
-
};
|
|
35117
|
-
});
|
|
35118
|
-
}
|
|
35119
|
-
/**
|
|
35120
|
-
* Adds notification to history with automatic limit management.
|
|
35121
|
-
*/
|
|
35122
|
-
_addNotification(notification) {
|
|
35123
|
-
this._notifications.unshift(notification);
|
|
35124
|
-
// Trim history if exceeded MAX_NOTIFICATIONS
|
|
35125
|
-
if (this._notifications.length > MAX_NOTIFICATIONS) {
|
|
35126
|
-
this._notifications.pop();
|
|
35127
|
-
}
|
|
35128
|
-
}
|
|
35129
|
-
/**
|
|
35130
|
-
* Returns all notifications in chronological order (newest first).
|
|
35131
|
-
*
|
|
35132
|
-
* @returns Array of strongly-typed notification objects
|
|
35133
|
-
*
|
|
35134
|
-
* @example
|
|
35135
|
-
* ```typescript
|
|
35136
|
-
* const notifications = instance.getData();
|
|
35137
|
-
*
|
|
35138
|
-
* notifications.forEach(notification => {
|
|
35139
|
-
* switch (notification.type) {
|
|
35140
|
-
* case "signal.closed":
|
|
35141
|
-
* console.log(`${notification.symbol}: ${notification.pnlPercentage}%`);
|
|
35142
|
-
* break;
|
|
35143
|
-
* case "partial.loss":
|
|
35144
|
-
* if (notification.level >= 30) {
|
|
35145
|
-
* console.warn(`High loss: ${notification.symbol}`);
|
|
35146
|
-
* }
|
|
35147
|
-
* break;
|
|
35148
|
-
* }
|
|
35149
|
-
* });
|
|
35150
|
-
* ```
|
|
35151
|
-
*/
|
|
35152
|
-
getData() {
|
|
35153
|
-
return [...this._notifications];
|
|
35154
|
-
}
|
|
35155
|
-
/**
|
|
35156
|
-
* Clears all notification history.
|
|
35157
|
-
*
|
|
35158
|
-
* @example
|
|
35159
|
-
* ```typescript
|
|
35160
|
-
* instance.clear();
|
|
35161
|
-
* ```
|
|
35162
|
-
*/
|
|
35163
|
-
clear() {
|
|
35164
|
-
this._notifications = [];
|
|
35165
|
-
}
|
|
35166
|
-
/**
|
|
35167
|
-
* Unsubscribes from all notification emitters to stop receiving events.
|
|
35168
|
-
* Calls the unsubscribe function returned by subscribe().
|
|
35169
|
-
* If not subscribed, does nothing.
|
|
35170
|
-
*
|
|
35171
|
-
* @example
|
|
35172
|
-
* ```typescript
|
|
35173
|
-
* const instance = new NotificationInstance();
|
|
35174
|
-
* instance.subscribe();
|
|
35175
|
-
* // ... later
|
|
35176
|
-
* instance.unsubscribe();
|
|
35177
|
-
* ```
|
|
35178
|
-
*/
|
|
35179
|
-
disable() {
|
|
35180
|
-
if (this.enable.hasValue()) {
|
|
35181
|
-
const unsubscribeFn = this.enable();
|
|
35182
|
-
unsubscribeFn();
|
|
35183
|
-
}
|
|
35184
|
-
}
|
|
35185
|
-
}
|
|
35186
|
-
/**
|
|
35187
|
-
* Public facade for notification operations.
|
|
35188
|
-
*
|
|
35189
|
-
* Automatically subscribes on first use and provides simplified access to notification instance methods.
|
|
35190
|
-
*
|
|
35191
|
-
* @example
|
|
35192
|
-
* ```typescript
|
|
35193
|
-
* import { Notification } from "./classes/Notification";
|
|
35194
|
-
*
|
|
35195
|
-
* // Get all notifications (auto-subscribes if not subscribed)
|
|
35196
|
-
* const all = await Notification.getData();
|
|
35197
|
-
*
|
|
35198
|
-
* // Process notifications with type discrimination
|
|
35199
|
-
* all.forEach(notification => {
|
|
35200
|
-
* switch (notification.type) {
|
|
35201
|
-
* case "signal.closed":
|
|
35202
|
-
* console.log(`Closed: ${notification.pnlPercentage}%`);
|
|
35203
|
-
* break;
|
|
35204
|
-
* case "partial.loss":
|
|
35205
|
-
* if (notification.level >= 30) {
|
|
35206
|
-
* alert("High loss!");
|
|
35207
|
-
* }
|
|
35208
|
-
* break;
|
|
35209
|
-
* case "risk.rejection":
|
|
35210
|
-
* console.warn(notification.rejectionNote);
|
|
35211
|
-
* break;
|
|
35212
|
-
* }
|
|
35213
|
-
* });
|
|
35214
|
-
*
|
|
35215
|
-
* // Clear history
|
|
35216
|
-
* await Notification.clear();
|
|
35217
|
-
*
|
|
35218
|
-
* // Unsubscribe when done
|
|
35219
|
-
* await Notification.unsubscribe();
|
|
35220
|
-
* ```
|
|
35221
|
-
*/
|
|
35222
|
-
class NotificationUtils {
|
|
35223
|
-
constructor() {
|
|
35224
|
-
/** Internal instance containing business logic */
|
|
35225
|
-
this._instance = new NotificationInstance();
|
|
35226
|
-
}
|
|
35227
|
-
/**
|
|
35228
|
-
* Returns all notifications in chronological order (newest first).
|
|
35229
|
-
* Automatically subscribes to emitters if not already subscribed.
|
|
35230
|
-
*
|
|
35231
|
-
* @returns Array of strongly-typed notification objects
|
|
35232
|
-
*
|
|
35233
|
-
* @example
|
|
35234
|
-
* ```typescript
|
|
35235
|
-
* const notifications = await Notification.getData();
|
|
35236
|
-
*
|
|
35237
|
-
* notifications.forEach(notification => {
|
|
35238
|
-
* switch (notification.type) {
|
|
35239
|
-
* case "signal.closed":
|
|
35240
|
-
* console.log(`${notification.symbol}: ${notification.pnlPercentage}%`);
|
|
35241
|
-
* break;
|
|
35242
|
-
* case "partial.loss":
|
|
35243
|
-
* if (notification.level >= 30) {
|
|
35244
|
-
* console.warn(`High loss: ${notification.symbol}`);
|
|
35245
|
-
* }
|
|
35246
|
-
* break;
|
|
35247
|
-
* }
|
|
35248
|
-
* });
|
|
35249
|
-
* ```
|
|
35250
|
-
*/
|
|
35251
|
-
async getData() {
|
|
35252
|
-
if (!this._instance.enable.hasValue()) {
|
|
35253
|
-
throw new Error("Notification not initialized. Call enable() before getting data.");
|
|
35254
|
-
}
|
|
35255
|
-
return this._instance.getData();
|
|
35256
|
-
}
|
|
35257
|
-
/**
|
|
35258
|
-
* Clears all notification history.
|
|
35259
|
-
* Automatically subscribes to emitters if not already subscribed.
|
|
35260
|
-
*
|
|
35261
|
-
* @example
|
|
35262
|
-
* ```typescript
|
|
35263
|
-
* await Notification.clear();
|
|
35264
|
-
* ```
|
|
35265
|
-
*/
|
|
35266
|
-
async clear() {
|
|
35267
|
-
if (!this._instance.enable.hasValue()) {
|
|
35268
|
-
throw new Error("Notification not initialized. Call enable() before clearing data.");
|
|
35269
|
-
}
|
|
35270
|
-
this._instance.clear();
|
|
35271
|
-
}
|
|
35272
|
-
/**
|
|
35273
|
-
* Unsubscribes from all notification emitters.
|
|
35274
|
-
* Call this when you no longer need to collect notifications.
|
|
35275
|
-
*
|
|
35276
|
-
* @example
|
|
35277
|
-
* ```typescript
|
|
35278
|
-
* await Notification.unsubscribe();
|
|
35279
|
-
* ```
|
|
35280
|
-
*/
|
|
35281
|
-
async enable() {
|
|
35282
|
-
this._instance.enable();
|
|
35283
|
-
}
|
|
35284
|
-
/**
|
|
35285
|
-
* Unsubscribes from all notification emitters.
|
|
35286
|
-
* Call this when you no longer need to collect notifications.
|
|
35287
|
-
* @example
|
|
35288
|
-
* ```typescript
|
|
35289
|
-
* await Notification.unsubscribe();
|
|
35290
|
-
* ```
|
|
35291
|
-
*/
|
|
35292
|
-
async disable() {
|
|
35293
|
-
this._instance.disable();
|
|
35294
|
-
}
|
|
35295
|
-
}
|
|
35296
|
-
/**
|
|
35297
|
-
* Singleton instance of NotificationUtils for convenient notification access.
|
|
35298
|
-
*
|
|
35299
|
-
* @example
|
|
35300
|
-
* ```typescript
|
|
35301
|
-
* import { Notification } from "./classes/Notification";
|
|
35302
|
-
*
|
|
35303
|
-
* // Get all notifications
|
|
35304
|
-
* const all = await Notification.getData();
|
|
35305
|
-
*
|
|
35306
|
-
* // Filter by type using type discrimination
|
|
35307
|
-
* const closedSignals = all.filter(n => n.type === "signal.closed");
|
|
35308
|
-
* const highLosses = all.filter(n =>
|
|
35309
|
-
* n.type === "partial.loss" && n.level >= 30
|
|
35310
|
-
* );
|
|
35311
|
-
*
|
|
35312
|
-
* // Clear history
|
|
35313
|
-
* await Notification.clear();
|
|
35314
|
-
* ```
|
|
35315
|
-
*/
|
|
35316
|
-
const Notification = new NotificationUtils();
|
|
35317
|
-
|
|
35318
36484
|
const BREAKEVEN_METHOD_NAME_GET_DATA = "BreakevenUtils.getData";
|
|
35319
36485
|
const BREAKEVEN_METHOD_NAME_GET_REPORT = "BreakevenUtils.getReport";
|
|
35320
36486
|
const BREAKEVEN_METHOD_NAME_DUMP = "BreakevenUtils.dump";
|
|
@@ -35840,11 +37006,14 @@ exports.MarkdownFileBase = MarkdownFileBase;
|
|
|
35840
37006
|
exports.MarkdownFolderBase = MarkdownFolderBase;
|
|
35841
37007
|
exports.MethodContextService = MethodContextService;
|
|
35842
37008
|
exports.Notification = Notification;
|
|
37009
|
+
exports.NotificationBacktest = NotificationBacktest;
|
|
37010
|
+
exports.NotificationLive = NotificationLive;
|
|
35843
37011
|
exports.Partial = Partial;
|
|
35844
37012
|
exports.Performance = Performance;
|
|
35845
37013
|
exports.PersistBase = PersistBase;
|
|
35846
37014
|
exports.PersistBreakevenAdapter = PersistBreakevenAdapter;
|
|
35847
37015
|
exports.PersistCandleAdapter = PersistCandleAdapter;
|
|
37016
|
+
exports.PersistNotificationAdapter = PersistNotificationAdapter;
|
|
35848
37017
|
exports.PersistPartialAdapter = PersistPartialAdapter;
|
|
35849
37018
|
exports.PersistRiskAdapter = PersistRiskAdapter;
|
|
35850
37019
|
exports.PersistScheduleAdapter = PersistScheduleAdapter;
|