backtest-kit 9.0.2 → 9.0.4
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 +215 -108
- package/build/index.mjs +215 -109
- package/package.json +1 -1
- package/types.d.ts +166 -113
package/build/index.cjs
CHANGED
|
@@ -1458,7 +1458,7 @@ class PersistRiskInstance {
|
|
|
1458
1458
|
*
|
|
1459
1459
|
* @returns Promise resolving to positions (empty array if none persisted)
|
|
1460
1460
|
*/
|
|
1461
|
-
async readPositionData() {
|
|
1461
|
+
async readPositionData(_when) {
|
|
1462
1462
|
if (await this._storage.hasValue(PersistRiskInstance.STORAGE_KEY)) {
|
|
1463
1463
|
return await this._storage.readValue(PersistRiskInstance.STORAGE_KEY);
|
|
1464
1464
|
}
|
|
@@ -1468,9 +1468,10 @@ class PersistRiskInstance {
|
|
|
1468
1468
|
* Writes the positions array using the fixed STORAGE_KEY.
|
|
1469
1469
|
*
|
|
1470
1470
|
* @param riskRow - Position entries to persist
|
|
1471
|
+
* @param when - Logical timestamp (reserved for API consistency; not used)
|
|
1471
1472
|
* @returns Promise that resolves when write is complete
|
|
1472
1473
|
*/
|
|
1473
|
-
async writePositionData(riskRow) {
|
|
1474
|
+
async writePositionData(riskRow, _when) {
|
|
1474
1475
|
await this._storage.writeValue(PersistRiskInstance.STORAGE_KEY, riskRow);
|
|
1475
1476
|
}
|
|
1476
1477
|
}
|
|
@@ -1495,12 +1496,12 @@ class PersistRiskDummyInstance {
|
|
|
1495
1496
|
* Always returns empty positions array.
|
|
1496
1497
|
* @returns Promise resolving to []
|
|
1497
1498
|
*/
|
|
1498
|
-
async readPositionData() { return []; }
|
|
1499
|
+
async readPositionData(_when) { return []; }
|
|
1499
1500
|
/**
|
|
1500
1501
|
* No-op write (discards positions).
|
|
1501
1502
|
* @returns Promise that resolves immediately
|
|
1502
1503
|
*/
|
|
1503
|
-
async writePositionData(_riskRow) { }
|
|
1504
|
+
async writePositionData(_riskRow, _when) { }
|
|
1504
1505
|
}
|
|
1505
1506
|
/**
|
|
1506
1507
|
* Utility class for managing risk active positions persistence.
|
|
@@ -1530,15 +1531,16 @@ class PersistRiskUtils {
|
|
|
1530
1531
|
*
|
|
1531
1532
|
* @param riskName - Risk profile identifier
|
|
1532
1533
|
* @param exchangeName - Exchange identifier
|
|
1534
|
+
* @param when - Logical timestamp at which the read is happening (reserved for API consistency)
|
|
1533
1535
|
* @returns Promise resolving to position entries (empty array if none)
|
|
1534
1536
|
*/
|
|
1535
|
-
this.readPositionData = async (riskName, exchangeName) => {
|
|
1537
|
+
this.readPositionData = async (riskName, exchangeName, when) => {
|
|
1536
1538
|
LOGGER_SERVICE$7.info(PERSIST_RISK_UTILS_METHOD_NAME_READ_DATA);
|
|
1537
1539
|
const key = `${riskName}:${exchangeName}`;
|
|
1538
1540
|
const isInitial = !this.getRiskStorage.has(key);
|
|
1539
1541
|
const instance = this.getRiskStorage(riskName, exchangeName);
|
|
1540
1542
|
await instance.waitForInit(isInitial);
|
|
1541
|
-
return instance.readPositionData();
|
|
1543
|
+
return instance.readPositionData(when);
|
|
1542
1544
|
};
|
|
1543
1545
|
/**
|
|
1544
1546
|
* Writes active positions for the given risk context.
|
|
@@ -1547,15 +1549,16 @@ class PersistRiskUtils {
|
|
|
1547
1549
|
* @param riskRow - Position entries to persist
|
|
1548
1550
|
* @param riskName - Risk profile identifier
|
|
1549
1551
|
* @param exchangeName - Exchange identifier
|
|
1552
|
+
* @param when - Logical timestamp this write belongs to (reserved for API consistency)
|
|
1550
1553
|
* @returns Promise that resolves when write is complete
|
|
1551
1554
|
*/
|
|
1552
|
-
this.writePositionData = async (riskRow, riskName, exchangeName) => {
|
|
1555
|
+
this.writePositionData = async (riskRow, riskName, exchangeName, when) => {
|
|
1553
1556
|
LOGGER_SERVICE$7.info(PERSIST_RISK_UTILS_METHOD_NAME_WRITE_DATA);
|
|
1554
1557
|
const key = `${riskName}:${exchangeName}`;
|
|
1555
1558
|
const isInitial = !this.getRiskStorage.has(key);
|
|
1556
1559
|
const instance = this.getRiskStorage(riskName, exchangeName);
|
|
1557
1560
|
await instance.waitForInit(isInitial);
|
|
1558
|
-
return instance.writePositionData(riskRow);
|
|
1561
|
+
return instance.writePositionData(riskRow, when);
|
|
1559
1562
|
};
|
|
1560
1563
|
}
|
|
1561
1564
|
/**
|
|
@@ -1849,7 +1852,7 @@ class PersistPartialInstance {
|
|
|
1849
1852
|
* @param signalId - Signal identifier
|
|
1850
1853
|
* @returns Promise resolving to partial data record (empty object if not found)
|
|
1851
1854
|
*/
|
|
1852
|
-
async readPartialData(signalId) {
|
|
1855
|
+
async readPartialData(signalId, _when) {
|
|
1853
1856
|
if (await this._storage.hasValue(signalId)) {
|
|
1854
1857
|
return await this._storage.readValue(signalId);
|
|
1855
1858
|
}
|
|
@@ -1860,9 +1863,10 @@ class PersistPartialInstance {
|
|
|
1860
1863
|
*
|
|
1861
1864
|
* @param data - Partial data record to persist
|
|
1862
1865
|
* @param signalId - Signal identifier
|
|
1866
|
+
* @param when - Logical timestamp (reserved for API consistency; not used)
|
|
1863
1867
|
* @returns Promise that resolves when write is complete
|
|
1864
1868
|
*/
|
|
1865
|
-
async writePartialData(data, signalId) {
|
|
1869
|
+
async writePartialData(data, signalId, _when) {
|
|
1866
1870
|
await this._storage.writeValue(signalId, data);
|
|
1867
1871
|
}
|
|
1868
1872
|
}
|
|
@@ -1885,12 +1889,12 @@ class PersistPartialDummyInstance {
|
|
|
1885
1889
|
* Always returns empty partial data record.
|
|
1886
1890
|
* @returns Promise resolving to {}
|
|
1887
1891
|
*/
|
|
1888
|
-
async readPartialData(_signalId) { return {}; }
|
|
1892
|
+
async readPartialData(_signalId, _when) { return {}; }
|
|
1889
1893
|
/**
|
|
1890
1894
|
* No-op write (discards partial data).
|
|
1891
1895
|
* @returns Promise that resolves immediately
|
|
1892
1896
|
*/
|
|
1893
|
-
async writePartialData(_data, _signalId) { }
|
|
1897
|
+
async writePartialData(_data, _signalId, _when) { }
|
|
1894
1898
|
}
|
|
1895
1899
|
/**
|
|
1896
1900
|
* Utility class for managing partial profit/loss levels persistence.
|
|
@@ -1923,15 +1927,16 @@ class PersistPartialUtils {
|
|
|
1923
1927
|
* @param strategyName - Strategy identifier
|
|
1924
1928
|
* @param signalId - Signal identifier
|
|
1925
1929
|
* @param exchangeName - Exchange identifier
|
|
1930
|
+
* @param when - Logical timestamp at which the read is happening (reserved for API consistency)
|
|
1926
1931
|
* @returns Promise resolving to partial data record (empty object if none)
|
|
1927
1932
|
*/
|
|
1928
|
-
this.readPartialData = async (symbol, strategyName, signalId, exchangeName) => {
|
|
1933
|
+
this.readPartialData = async (symbol, strategyName, signalId, exchangeName, when) => {
|
|
1929
1934
|
LOGGER_SERVICE$7.info(PERSIST_PARTIAL_UTILS_METHOD_NAME_READ_DATA);
|
|
1930
1935
|
const key = `${symbol}:${strategyName}:${exchangeName}`;
|
|
1931
1936
|
const isInitial = !this.getPartialStorage.has(key);
|
|
1932
1937
|
const instance = this.getPartialStorage(symbol, strategyName, exchangeName);
|
|
1933
1938
|
await instance.waitForInit(isInitial);
|
|
1934
|
-
return instance.readPartialData(signalId);
|
|
1939
|
+
return instance.readPartialData(signalId, when);
|
|
1935
1940
|
};
|
|
1936
1941
|
/**
|
|
1937
1942
|
* Writes partial data for the given context and signalId.
|
|
@@ -1942,15 +1947,16 @@ class PersistPartialUtils {
|
|
|
1942
1947
|
* @param strategyName - Strategy identifier
|
|
1943
1948
|
* @param signalId - Signal identifier
|
|
1944
1949
|
* @param exchangeName - Exchange identifier
|
|
1950
|
+
* @param when - Logical timestamp this write belongs to (reserved for API consistency)
|
|
1945
1951
|
* @returns Promise that resolves when write is complete
|
|
1946
1952
|
*/
|
|
1947
|
-
this.writePartialData = async (partialData, symbol, strategyName, signalId, exchangeName) => {
|
|
1953
|
+
this.writePartialData = async (partialData, symbol, strategyName, signalId, exchangeName, when) => {
|
|
1948
1954
|
LOGGER_SERVICE$7.info(PERSIST_PARTIAL_UTILS_METHOD_NAME_WRITE_DATA);
|
|
1949
1955
|
const key = `${symbol}:${strategyName}:${exchangeName}`;
|
|
1950
1956
|
const isInitial = !this.getPartialStorage.has(key);
|
|
1951
1957
|
const instance = this.getPartialStorage(symbol, strategyName, exchangeName);
|
|
1952
1958
|
await instance.waitForInit(isInitial);
|
|
1953
|
-
return instance.writePartialData(partialData, signalId);
|
|
1959
|
+
return instance.writePartialData(partialData, signalId, when);
|
|
1954
1960
|
};
|
|
1955
1961
|
}
|
|
1956
1962
|
/**
|
|
@@ -2049,7 +2055,7 @@ class PersistBreakevenInstance {
|
|
|
2049
2055
|
* @param signalId - Signal identifier
|
|
2050
2056
|
* @returns Promise resolving to breakeven data record (empty object if not found)
|
|
2051
2057
|
*/
|
|
2052
|
-
async readBreakevenData(signalId) {
|
|
2058
|
+
async readBreakevenData(signalId, _when) {
|
|
2053
2059
|
if (await this._storage.hasValue(signalId)) {
|
|
2054
2060
|
return await this._storage.readValue(signalId);
|
|
2055
2061
|
}
|
|
@@ -2060,9 +2066,10 @@ class PersistBreakevenInstance {
|
|
|
2060
2066
|
*
|
|
2061
2067
|
* @param data - Breakeven data record to persist
|
|
2062
2068
|
* @param signalId - Signal identifier
|
|
2069
|
+
* @param when - Logical timestamp (reserved for API consistency; not used)
|
|
2063
2070
|
* @returns Promise that resolves when write is complete
|
|
2064
2071
|
*/
|
|
2065
|
-
async writeBreakevenData(data, signalId) {
|
|
2072
|
+
async writeBreakevenData(data, signalId, _when) {
|
|
2066
2073
|
await this._storage.writeValue(signalId, data);
|
|
2067
2074
|
}
|
|
2068
2075
|
}
|
|
@@ -2085,12 +2092,12 @@ class PersistBreakevenDummyInstance {
|
|
|
2085
2092
|
* Always returns empty breakeven data record.
|
|
2086
2093
|
* @returns Promise resolving to {}
|
|
2087
2094
|
*/
|
|
2088
|
-
async readBreakevenData(_signalId) { return {}; }
|
|
2095
|
+
async readBreakevenData(_signalId, _when) { return {}; }
|
|
2089
2096
|
/**
|
|
2090
2097
|
* No-op write (discards breakeven data).
|
|
2091
2098
|
* @returns Promise that resolves immediately
|
|
2092
2099
|
*/
|
|
2093
|
-
async writeBreakevenData(_data, _signalId) { }
|
|
2100
|
+
async writeBreakevenData(_data, _signalId, _when) { }
|
|
2094
2101
|
}
|
|
2095
2102
|
/**
|
|
2096
2103
|
* Persistence utility class for breakeven state management.
|
|
@@ -2143,15 +2150,16 @@ class PersistBreakevenUtils {
|
|
|
2143
2150
|
* @param strategyName - Strategy identifier
|
|
2144
2151
|
* @param signalId - Signal identifier
|
|
2145
2152
|
* @param exchangeName - Exchange identifier
|
|
2153
|
+
* @param when - Logical timestamp at which the read is happening (reserved for API consistency)
|
|
2146
2154
|
* @returns Promise resolving to breakeven data record (empty object if none)
|
|
2147
2155
|
*/
|
|
2148
|
-
this.readBreakevenData = async (symbol, strategyName, signalId, exchangeName) => {
|
|
2156
|
+
this.readBreakevenData = async (symbol, strategyName, signalId, exchangeName, when) => {
|
|
2149
2157
|
LOGGER_SERVICE$7.info(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_READ_DATA);
|
|
2150
2158
|
const key = `${symbol}:${strategyName}:${exchangeName}`;
|
|
2151
2159
|
const isInitial = !this.getBreakevenStorage.has(key);
|
|
2152
2160
|
const instance = this.getBreakevenStorage(symbol, strategyName, exchangeName);
|
|
2153
2161
|
await instance.waitForInit(isInitial);
|
|
2154
|
-
return instance.readBreakevenData(signalId);
|
|
2162
|
+
return instance.readBreakevenData(signalId, when);
|
|
2155
2163
|
};
|
|
2156
2164
|
/**
|
|
2157
2165
|
* Writes breakeven data for the given context and signalId.
|
|
@@ -2162,15 +2170,16 @@ class PersistBreakevenUtils {
|
|
|
2162
2170
|
* @param strategyName - Strategy identifier
|
|
2163
2171
|
* @param signalId - Signal identifier
|
|
2164
2172
|
* @param exchangeName - Exchange identifier
|
|
2173
|
+
* @param when - Logical timestamp this write belongs to (reserved for API consistency)
|
|
2165
2174
|
* @returns Promise that resolves when write is complete
|
|
2166
2175
|
*/
|
|
2167
|
-
this.writeBreakevenData = async (breakevenData, symbol, strategyName, signalId, exchangeName) => {
|
|
2176
|
+
this.writeBreakevenData = async (breakevenData, symbol, strategyName, signalId, exchangeName, when) => {
|
|
2168
2177
|
LOGGER_SERVICE$7.info(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_WRITE_DATA);
|
|
2169
2178
|
const key = `${symbol}:${strategyName}:${exchangeName}`;
|
|
2170
2179
|
const isInitial = !this.getBreakevenStorage.has(key);
|
|
2171
2180
|
const instance = this.getBreakevenStorage(symbol, strategyName, exchangeName);
|
|
2172
2181
|
await instance.waitForInit(isInitial);
|
|
2173
|
-
return instance.writeBreakevenData(breakevenData, signalId);
|
|
2182
|
+
return instance.writeBreakevenData(breakevenData, signalId, when);
|
|
2174
2183
|
};
|
|
2175
2184
|
}
|
|
2176
2185
|
/**
|
|
@@ -14156,57 +14165,10 @@ const get = (object, path) => {
|
|
|
14156
14165
|
return pathArrayFlat.reduce((obj, key) => obj && obj[key], object);
|
|
14157
14166
|
};
|
|
14158
14167
|
|
|
14159
|
-
const MS_PER_MINUTE$6 = 60000;
|
|
14160
|
-
const INTERVAL_MINUTES$6 = {
|
|
14161
|
-
"1m": 1,
|
|
14162
|
-
"3m": 3,
|
|
14163
|
-
"5m": 5,
|
|
14164
|
-
"15m": 15,
|
|
14165
|
-
"30m": 30,
|
|
14166
|
-
"1h": 60,
|
|
14167
|
-
"2h": 120,
|
|
14168
|
-
"4h": 240,
|
|
14169
|
-
"6h": 360,
|
|
14170
|
-
"8h": 480,
|
|
14171
|
-
"1d": 1440,
|
|
14172
|
-
};
|
|
14173
|
-
/**
|
|
14174
|
-
* Aligns timestamp down to the nearest interval boundary.
|
|
14175
|
-
* For example, for 15m interval: 00:17 -> 00:15, 00:44 -> 00:30
|
|
14176
|
-
*
|
|
14177
|
-
* Candle timestamp convention:
|
|
14178
|
-
* - Candle timestamp = openTime (when candle opens)
|
|
14179
|
-
* - Candle with timestamp 00:00 covers period [00:00, 00:15) for 15m interval
|
|
14180
|
-
*
|
|
14181
|
-
* Adapter contract:
|
|
14182
|
-
* - Adapter must return candles with timestamp = openTime
|
|
14183
|
-
* - First returned candle.timestamp must equal aligned since
|
|
14184
|
-
* - Adapter must return exactly `limit` candles
|
|
14185
|
-
*
|
|
14186
|
-
* @param date - Date to align
|
|
14187
|
-
* @param interval - Candle interval (e.g., "1m", "15m", "1h")
|
|
14188
|
-
* @returns New Date aligned down to interval boundary
|
|
14189
|
-
*/
|
|
14190
|
-
const alignToInterval = (date, interval) => {
|
|
14191
|
-
const minutes = INTERVAL_MINUTES$6[interval];
|
|
14192
|
-
if (minutes === undefined) {
|
|
14193
|
-
throw new Error(`alignToInterval: unknown interval=${interval}`);
|
|
14194
|
-
}
|
|
14195
|
-
const intervalMs = minutes * MS_PER_MINUTE$6;
|
|
14196
|
-
return new Date(Math.floor(date.getTime() / intervalMs) * intervalMs);
|
|
14197
|
-
};
|
|
14198
|
-
|
|
14199
14168
|
/** Used to prevent race confition between concurent strategies */
|
|
14200
14169
|
const RISK_LOCK = new Lock();
|
|
14201
14170
|
/** Symbol indicating that positions need to be fetched from persistence */
|
|
14202
14171
|
const POSITION_NEED_FETCH = Symbol("risk-need-fetch");
|
|
14203
|
-
/** Get timestamp from execution context or fallback to aligned current time */
|
|
14204
|
-
const GET_CONTEXT_TIMESTAMP_FN = (self) => {
|
|
14205
|
-
if (ExecutionContextService.hasContext()) {
|
|
14206
|
-
return self.params.execution.context.when.getTime();
|
|
14207
|
-
}
|
|
14208
|
-
return alignToInterval(new Date(), "1m").getTime();
|
|
14209
|
-
};
|
|
14210
14172
|
/** Zero PNL constant for scheduled signals (which don't have priceOpen or PNL yet) */
|
|
14211
14173
|
const ZERO_PNL = { pnlPercentage: 0, priceOpen: 0, priceClose: 0, pnlCost: 0, pnlEntries: 0 };
|
|
14212
14174
|
/**
|
|
@@ -14332,7 +14294,7 @@ const CALL_ALLOWED_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, par
|
|
|
14332
14294
|
*
|
|
14333
14295
|
* In backtest mode, initializes with empty Map. In live mode, reads from persist storage.
|
|
14334
14296
|
*/
|
|
14335
|
-
const WAIT_FOR_INIT_FN$3 = async (self) => {
|
|
14297
|
+
const WAIT_FOR_INIT_FN$3 = async (when, self) => {
|
|
14336
14298
|
self.params.logger.debug("ClientRisk waitForInit", {
|
|
14337
14299
|
backtest: self.params.backtest,
|
|
14338
14300
|
});
|
|
@@ -14340,7 +14302,7 @@ const WAIT_FOR_INIT_FN$3 = async (self) => {
|
|
|
14340
14302
|
self._activePositions = new Map();
|
|
14341
14303
|
return;
|
|
14342
14304
|
}
|
|
14343
|
-
const persistedPositions = await PersistRiskAdapter.readPositionData(self.params.riskName, self.params.exchangeName);
|
|
14305
|
+
const persistedPositions = await PersistRiskAdapter.readPositionData(self.params.riskName, self.params.exchangeName, when);
|
|
14344
14306
|
self._activePositions = new Map(persistedPositions);
|
|
14345
14307
|
};
|
|
14346
14308
|
/**
|
|
@@ -14369,7 +14331,7 @@ class ClientRisk {
|
|
|
14369
14331
|
* Uses singleshot pattern to ensure initialization happens exactly once.
|
|
14370
14332
|
* Skips persistence in backtest mode.
|
|
14371
14333
|
*/
|
|
14372
|
-
this.waitForInit = functoolsKit.singleshot(async () => await WAIT_FOR_INIT_FN$3(this));
|
|
14334
|
+
this.waitForInit = functoolsKit.singleshot(async (when) => await WAIT_FOR_INIT_FN$3(when, this));
|
|
14373
14335
|
/**
|
|
14374
14336
|
* Checks if a signal should be allowed based on risk limits.
|
|
14375
14337
|
*
|
|
@@ -14391,11 +14353,15 @@ class ClientRisk {
|
|
|
14391
14353
|
});
|
|
14392
14354
|
await RISK_LOCK.acquireLock();
|
|
14393
14355
|
try {
|
|
14356
|
+
const timestamp = await this.params.time.getTimestamp(params.symbol, {
|
|
14357
|
+
strategyName: params.strategyName,
|
|
14358
|
+
exchangeName: params.exchangeName,
|
|
14359
|
+
frameName: params.frameName,
|
|
14360
|
+
}, this.params.backtest);
|
|
14394
14361
|
if (this._activePositions === POSITION_NEED_FETCH) {
|
|
14395
|
-
await this.waitForInit();
|
|
14362
|
+
await this.waitForInit(new Date(timestamp));
|
|
14396
14363
|
}
|
|
14397
14364
|
const riskMap = this._activePositions;
|
|
14398
|
-
const timestamp = GET_CONTEXT_TIMESTAMP_FN(this);
|
|
14399
14365
|
const payload = {
|
|
14400
14366
|
...params,
|
|
14401
14367
|
currentSignal: TO_RISK_SIGNAL(params.currentSignal, params.currentPrice, timestamp),
|
|
@@ -14498,14 +14464,14 @@ class ClientRisk {
|
|
|
14498
14464
|
* Persists current active positions to disk.
|
|
14499
14465
|
* Skips in backtest mode.
|
|
14500
14466
|
*/
|
|
14501
|
-
async _updatePositions() {
|
|
14467
|
+
async _updatePositions(when) {
|
|
14502
14468
|
if (this.params.backtest) {
|
|
14503
14469
|
return;
|
|
14504
14470
|
}
|
|
14505
14471
|
if (this._activePositions === POSITION_NEED_FETCH) {
|
|
14506
|
-
await this.waitForInit();
|
|
14472
|
+
await this.waitForInit(when);
|
|
14507
14473
|
}
|
|
14508
|
-
await PersistRiskAdapter.writePositionData(Array.from(this._activePositions), this.params.riskName, this.params.exchangeName);
|
|
14474
|
+
await PersistRiskAdapter.writePositionData(Array.from(this._activePositions), this.params.riskName, this.params.exchangeName, when);
|
|
14509
14475
|
}
|
|
14510
14476
|
/**
|
|
14511
14477
|
* Registers a new opened signal.
|
|
@@ -14520,8 +14486,9 @@ class ClientRisk {
|
|
|
14520
14486
|
});
|
|
14521
14487
|
await RISK_LOCK.acquireLock();
|
|
14522
14488
|
try {
|
|
14489
|
+
const timestamp = await this.params.time.getTimestamp(symbol, context, this.params.backtest);
|
|
14523
14490
|
if (this._activePositions === POSITION_NEED_FETCH) {
|
|
14524
|
-
await this.waitForInit();
|
|
14491
|
+
await this.waitForInit(new Date(timestamp));
|
|
14525
14492
|
}
|
|
14526
14493
|
const key = CREATE_NAME_FN(context.strategyName, context.exchangeName, symbol);
|
|
14527
14494
|
const riskMap = this._activePositions;
|
|
@@ -14537,7 +14504,7 @@ class ClientRisk {
|
|
|
14537
14504
|
minuteEstimatedTime: positionData.minuteEstimatedTime,
|
|
14538
14505
|
openTimestamp: positionData.openTimestamp,
|
|
14539
14506
|
});
|
|
14540
|
-
await this._updatePositions();
|
|
14507
|
+
await this._updatePositions(new Date(timestamp));
|
|
14541
14508
|
}
|
|
14542
14509
|
finally {
|
|
14543
14510
|
await RISK_LOCK.releaseLock();
|
|
@@ -14555,13 +14522,14 @@ class ClientRisk {
|
|
|
14555
14522
|
});
|
|
14556
14523
|
await RISK_LOCK.acquireLock();
|
|
14557
14524
|
try {
|
|
14525
|
+
const timestamp = await this.params.time.getTimestamp(symbol, context, this.params.backtest);
|
|
14558
14526
|
if (this._activePositions === POSITION_NEED_FETCH) {
|
|
14559
|
-
await this.waitForInit();
|
|
14527
|
+
await this.waitForInit(new Date(timestamp));
|
|
14560
14528
|
}
|
|
14561
14529
|
const key = CREATE_NAME_FN(context.strategyName, context.exchangeName, symbol);
|
|
14562
14530
|
const riskMap = this._activePositions;
|
|
14563
14531
|
riskMap.delete(key);
|
|
14564
|
-
await this._updatePositions();
|
|
14532
|
+
await this._updatePositions(new Date(timestamp));
|
|
14565
14533
|
}
|
|
14566
14534
|
finally {
|
|
14567
14535
|
await RISK_LOCK.releaseLock();
|
|
@@ -14661,7 +14629,7 @@ class RiskConnectionService {
|
|
|
14661
14629
|
constructor() {
|
|
14662
14630
|
this.loggerService = inject(TYPES.loggerService);
|
|
14663
14631
|
this.riskSchemaService = inject(TYPES.riskSchemaService);
|
|
14664
|
-
this.
|
|
14632
|
+
this.timeMetaService = inject(TYPES.timeMetaService);
|
|
14665
14633
|
/**
|
|
14666
14634
|
* Action core service injected from DI container.
|
|
14667
14635
|
*/
|
|
@@ -14683,7 +14651,7 @@ class RiskConnectionService {
|
|
|
14683
14651
|
return new ClientRisk({
|
|
14684
14652
|
...schema,
|
|
14685
14653
|
logger: this.loggerService,
|
|
14686
|
-
|
|
14654
|
+
time: this.timeMetaService,
|
|
14687
14655
|
backtest,
|
|
14688
14656
|
exchangeName,
|
|
14689
14657
|
onRejected: CREATE_COMMIT_REJECTION_FN(this, exchangeName, frameName),
|
|
@@ -19674,8 +19642,8 @@ class BacktestLogicPrivateService {
|
|
|
19674
19642
|
}
|
|
19675
19643
|
|
|
19676
19644
|
const EMITTER_CHECK_INTERVAL = 5000;
|
|
19677
|
-
const MS_PER_MINUTE$
|
|
19678
|
-
const INTERVAL_MINUTES$
|
|
19645
|
+
const MS_PER_MINUTE$6 = 60000;
|
|
19646
|
+
const INTERVAL_MINUTES$6 = {
|
|
19679
19647
|
"1m": 1,
|
|
19680
19648
|
"3m": 3,
|
|
19681
19649
|
"5m": 5,
|
|
@@ -19690,7 +19658,7 @@ const INTERVAL_MINUTES$5 = {
|
|
|
19690
19658
|
};
|
|
19691
19659
|
const createEmitter = functoolsKit.memoize(([interval]) => `${interval}`, (interval) => {
|
|
19692
19660
|
const tickSubject = new functoolsKit.Subject();
|
|
19693
|
-
const intervalMs = INTERVAL_MINUTES$
|
|
19661
|
+
const intervalMs = INTERVAL_MINUTES$6[interval] * MS_PER_MINUTE$6;
|
|
19694
19662
|
{
|
|
19695
19663
|
let lastAligned = Math.floor(Date.now() / intervalMs) * intervalMs;
|
|
19696
19664
|
functoolsKit.Source.fromInterval(EMITTER_CHECK_INTERVAL)
|
|
@@ -19717,6 +19685,46 @@ const waitForCandle = async (interval) => {
|
|
|
19717
19685
|
return emitter.toPromise();
|
|
19718
19686
|
};
|
|
19719
19687
|
|
|
19688
|
+
const MS_PER_MINUTE$5 = 60000;
|
|
19689
|
+
const INTERVAL_MINUTES$5 = {
|
|
19690
|
+
"1m": 1,
|
|
19691
|
+
"3m": 3,
|
|
19692
|
+
"5m": 5,
|
|
19693
|
+
"15m": 15,
|
|
19694
|
+
"30m": 30,
|
|
19695
|
+
"1h": 60,
|
|
19696
|
+
"2h": 120,
|
|
19697
|
+
"4h": 240,
|
|
19698
|
+
"6h": 360,
|
|
19699
|
+
"8h": 480,
|
|
19700
|
+
"1d": 1440,
|
|
19701
|
+
};
|
|
19702
|
+
/**
|
|
19703
|
+
* Aligns timestamp down to the nearest interval boundary.
|
|
19704
|
+
* For example, for 15m interval: 00:17 -> 00:15, 00:44 -> 00:30
|
|
19705
|
+
*
|
|
19706
|
+
* Candle timestamp convention:
|
|
19707
|
+
* - Candle timestamp = openTime (when candle opens)
|
|
19708
|
+
* - Candle with timestamp 00:00 covers period [00:00, 00:15) for 15m interval
|
|
19709
|
+
*
|
|
19710
|
+
* Adapter contract:
|
|
19711
|
+
* - Adapter must return candles with timestamp = openTime
|
|
19712
|
+
* - First returned candle.timestamp must equal aligned since
|
|
19713
|
+
* - Adapter must return exactly `limit` candles
|
|
19714
|
+
*
|
|
19715
|
+
* @param date - Date to align
|
|
19716
|
+
* @param interval - Candle interval (e.g., "1m", "15m", "1h")
|
|
19717
|
+
* @returns New Date aligned down to interval boundary
|
|
19718
|
+
*/
|
|
19719
|
+
const alignToInterval = (date, interval) => {
|
|
19720
|
+
const minutes = INTERVAL_MINUTES$5[interval];
|
|
19721
|
+
if (minutes === undefined) {
|
|
19722
|
+
throw new Error(`alignToInterval: unknown interval=${interval}`);
|
|
19723
|
+
}
|
|
19724
|
+
const intervalMs = minutes * MS_PER_MINUTE$5;
|
|
19725
|
+
return new Date(Math.floor(date.getTime() / intervalMs) * intervalMs);
|
|
19726
|
+
};
|
|
19727
|
+
|
|
19720
19728
|
/**
|
|
19721
19729
|
* Private service for live trading orchestration using async generators.
|
|
19722
19730
|
*
|
|
@@ -27215,7 +27223,7 @@ const HANDLE_PROFIT_FN = async (symbol, data, currentPrice, revenuePercent, back
|
|
|
27215
27223
|
}
|
|
27216
27224
|
}
|
|
27217
27225
|
if (shouldPersist) {
|
|
27218
|
-
await self._persistState(symbol, data.strategyName, data.exchangeName, self.params.signalId);
|
|
27226
|
+
await self._persistState(symbol, data.strategyName, data.exchangeName, data.frameName, self.params.signalId);
|
|
27219
27227
|
}
|
|
27220
27228
|
};
|
|
27221
27229
|
/**
|
|
@@ -27265,7 +27273,7 @@ const HANDLE_LOSS_FN = async (symbol, data, currentPrice, lossPercent, backtest,
|
|
|
27265
27273
|
}
|
|
27266
27274
|
}
|
|
27267
27275
|
if (shouldPersist) {
|
|
27268
|
-
await self._persistState(symbol, data.strategyName, data.exchangeName, self.params.signalId);
|
|
27276
|
+
await self._persistState(symbol, data.strategyName, data.exchangeName, data.frameName, self.params.signalId);
|
|
27269
27277
|
}
|
|
27270
27278
|
};
|
|
27271
27279
|
/**
|
|
@@ -27282,7 +27290,7 @@ const HANDLE_LOSS_FN = async (symbol, data, currentPrice, lossPercent, backtest,
|
|
|
27282
27290
|
* @param backtest - True if backtest mode, false if live mode
|
|
27283
27291
|
* @param self - ClientPartial instance reference
|
|
27284
27292
|
*/
|
|
27285
|
-
const WAIT_FOR_INIT_FN$1 = async (symbol, strategyName, exchangeName, backtest, self) => {
|
|
27293
|
+
const WAIT_FOR_INIT_FN$1 = async (symbol, strategyName, exchangeName, frameName, backtest, self) => {
|
|
27286
27294
|
self.params.logger.debug("ClientPartial waitForInit", {
|
|
27287
27295
|
symbol,
|
|
27288
27296
|
backtest,
|
|
@@ -27298,7 +27306,12 @@ const WAIT_FOR_INIT_FN$1 = async (symbol, strategyName, exchangeName, backtest,
|
|
|
27298
27306
|
self.params.logger.debug("ClientPartial waitForInit: skipping persist read in backtest mode");
|
|
27299
27307
|
return;
|
|
27300
27308
|
}
|
|
27301
|
-
const
|
|
27309
|
+
const timestamp = await self.params.time.getTimestamp(symbol, {
|
|
27310
|
+
strategyName,
|
|
27311
|
+
exchangeName,
|
|
27312
|
+
frameName,
|
|
27313
|
+
}, self.params.backtest);
|
|
27314
|
+
const partialData = await PersistPartialAdapter.readPartialData(symbol, strategyName, self.params.signalId, exchangeName, new Date(timestamp));
|
|
27302
27315
|
for (const [signalId, data] of Object.entries(partialData)) {
|
|
27303
27316
|
const state = {
|
|
27304
27317
|
profitLevels: new Set(data.profitLevels),
|
|
@@ -27406,7 +27419,7 @@ class ClientPartial {
|
|
|
27406
27419
|
* // Now profit()/loss() can be called
|
|
27407
27420
|
* ```
|
|
27408
27421
|
*/
|
|
27409
|
-
this.waitForInit = functoolsKit.singleshot(async (symbol, strategyName, exchangeName, backtest) => await WAIT_FOR_INIT_FN$1(symbol, strategyName, exchangeName, backtest, this));
|
|
27422
|
+
this.waitForInit = functoolsKit.singleshot(async (symbol, strategyName, exchangeName, frameName, backtest) => await WAIT_FOR_INIT_FN$1(symbol, strategyName, exchangeName, frameName, backtest, this));
|
|
27410
27423
|
}
|
|
27411
27424
|
/**
|
|
27412
27425
|
* Persists current partial state to disk.
|
|
@@ -27424,7 +27437,7 @@ class ClientPartial {
|
|
|
27424
27437
|
* @param signalId - Signal identifier
|
|
27425
27438
|
* @returns Promise that resolves when persistence is complete
|
|
27426
27439
|
*/
|
|
27427
|
-
async _persistState(symbol, strategyName, exchangeName, signalId) {
|
|
27440
|
+
async _persistState(symbol, strategyName, exchangeName, frameName, signalId) {
|
|
27428
27441
|
if (this.params.backtest) {
|
|
27429
27442
|
return;
|
|
27430
27443
|
}
|
|
@@ -27439,7 +27452,12 @@ class ClientPartial {
|
|
|
27439
27452
|
lossLevels: Array.from(state.lossLevels),
|
|
27440
27453
|
};
|
|
27441
27454
|
}
|
|
27442
|
-
await
|
|
27455
|
+
const timestamp = await this.params.time.getTimestamp(symbol, {
|
|
27456
|
+
strategyName,
|
|
27457
|
+
exchangeName,
|
|
27458
|
+
frameName,
|
|
27459
|
+
}, this.params.backtest);
|
|
27460
|
+
await PersistPartialAdapter.writePartialData(partialData, symbol, strategyName, signalId, exchangeName, new Date(timestamp));
|
|
27443
27461
|
}
|
|
27444
27462
|
/**
|
|
27445
27463
|
* Processes profit state and emits events for newly reached profit levels.
|
|
@@ -27564,7 +27582,7 @@ class ClientPartial {
|
|
|
27564
27582
|
throw new Error(`Signal ID mismatch: expected ${this.params.signalId}, got ${data.id}`);
|
|
27565
27583
|
}
|
|
27566
27584
|
this._states.delete(data.id);
|
|
27567
|
-
await this._persistState(symbol, data.strategyName, data.exchangeName, this.params.signalId);
|
|
27585
|
+
await this._persistState(symbol, data.strategyName, data.exchangeName, data.frameName, this.params.signalId);
|
|
27568
27586
|
}
|
|
27569
27587
|
}
|
|
27570
27588
|
|
|
@@ -27689,6 +27707,7 @@ class PartialConnectionService {
|
|
|
27689
27707
|
* Action core service injected from DI container.
|
|
27690
27708
|
*/
|
|
27691
27709
|
this.actionCoreService = inject(TYPES.actionCoreService);
|
|
27710
|
+
this.timeMetaService = inject(TYPES.timeMetaService);
|
|
27692
27711
|
/**
|
|
27693
27712
|
* Memoized factory function for ClientPartial instances.
|
|
27694
27713
|
*
|
|
@@ -27702,6 +27721,7 @@ class PartialConnectionService {
|
|
|
27702
27721
|
return new ClientPartial({
|
|
27703
27722
|
signalId,
|
|
27704
27723
|
logger: this.loggerService,
|
|
27724
|
+
time: this.timeMetaService,
|
|
27705
27725
|
backtest,
|
|
27706
27726
|
onProfit: CREATE_COMMIT_PROFIT_FN(this),
|
|
27707
27727
|
onLoss: CREATE_COMMIT_LOSS_FN(this),
|
|
@@ -27731,7 +27751,7 @@ class PartialConnectionService {
|
|
|
27731
27751
|
when,
|
|
27732
27752
|
});
|
|
27733
27753
|
const partial = this.getPartial(data.id, backtest);
|
|
27734
|
-
await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
27754
|
+
await partial.waitForInit(symbol, data.strategyName, data.exchangeName, data.frameName, backtest);
|
|
27735
27755
|
return await partial.profit(symbol, data, currentPrice, revenuePercent, backtest, when);
|
|
27736
27756
|
};
|
|
27737
27757
|
/**
|
|
@@ -27758,7 +27778,7 @@ class PartialConnectionService {
|
|
|
27758
27778
|
when,
|
|
27759
27779
|
});
|
|
27760
27780
|
const partial = this.getPartial(data.id, backtest);
|
|
27761
|
-
await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
27781
|
+
await partial.waitForInit(symbol, data.strategyName, data.exchangeName, data.frameName, backtest);
|
|
27762
27782
|
return await partial.loss(symbol, data, currentPrice, lossPercent, backtest, when);
|
|
27763
27783
|
};
|
|
27764
27784
|
/**
|
|
@@ -27786,7 +27806,7 @@ class PartialConnectionService {
|
|
|
27786
27806
|
backtest,
|
|
27787
27807
|
});
|
|
27788
27808
|
const partial = this.getPartial(data.id, backtest);
|
|
27789
|
-
await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
27809
|
+
await partial.waitForInit(symbol, data.strategyName, data.exchangeName, data.frameName, backtest);
|
|
27790
27810
|
await partial.clear(symbol, data, priceClose, backtest);
|
|
27791
27811
|
const key = CREATE_KEY_FN$l(data.id, backtest);
|
|
27792
27812
|
this.getPartial.clear(key);
|
|
@@ -28512,7 +28532,7 @@ const HANDLE_BREAKEVEN_FN = async (symbol, data, currentPrice, backtest, when, s
|
|
|
28512
28532
|
// Emit event
|
|
28513
28533
|
await self.params.onBreakeven(symbol, data.strategyName, data.exchangeName, data.frameName, data, currentPrice, backtest, when.getTime());
|
|
28514
28534
|
// Persist state
|
|
28515
|
-
await self._persistState(symbol, data.strategyName, data.exchangeName, self.params.signalId);
|
|
28535
|
+
await self._persistState(symbol, data.strategyName, data.exchangeName, data.frameName, self.params.signalId);
|
|
28516
28536
|
return true;
|
|
28517
28537
|
};
|
|
28518
28538
|
/**
|
|
@@ -28527,7 +28547,7 @@ const HANDLE_BREAKEVEN_FN = async (symbol, data, currentPrice, backtest, when, s
|
|
|
28527
28547
|
* @param exchangeName - Exchange identifier
|
|
28528
28548
|
* @param self - ClientBreakeven instance reference
|
|
28529
28549
|
*/
|
|
28530
|
-
const WAIT_FOR_INIT_FN = async (symbol, strategyName, exchangeName, backtest, self) => {
|
|
28550
|
+
const WAIT_FOR_INIT_FN = async (symbol, strategyName, exchangeName, frameName, backtest, self) => {
|
|
28531
28551
|
self.params.logger.debug("ClientBreakeven waitForInit", {
|
|
28532
28552
|
symbol,
|
|
28533
28553
|
strategyName,
|
|
@@ -28543,7 +28563,12 @@ const WAIT_FOR_INIT_FN = async (symbol, strategyName, exchangeName, backtest, se
|
|
|
28543
28563
|
self.params.logger.debug("ClientBreakeven waitForInit: skipping persist read in backtest mode");
|
|
28544
28564
|
return;
|
|
28545
28565
|
}
|
|
28546
|
-
const
|
|
28566
|
+
const timestamp = await self.params.time.getTimestamp(symbol, {
|
|
28567
|
+
strategyName,
|
|
28568
|
+
exchangeName,
|
|
28569
|
+
frameName,
|
|
28570
|
+
}, self.params.backtest);
|
|
28571
|
+
const breakevenData = await PersistBreakevenAdapter.readBreakevenData(symbol, strategyName, self.params.signalId, exchangeName, new Date(timestamp));
|
|
28547
28572
|
for (const [signalId, data] of Object.entries(breakevenData)) {
|
|
28548
28573
|
const state = {
|
|
28549
28574
|
reached: data.reached,
|
|
@@ -28648,7 +28673,7 @@ class ClientBreakeven {
|
|
|
28648
28673
|
* // Now check() can be called
|
|
28649
28674
|
* ```
|
|
28650
28675
|
*/
|
|
28651
|
-
this.waitForInit = functoolsKit.singleshot(async (symbol, strategyName, exchangeName, backtest) => await WAIT_FOR_INIT_FN(symbol, strategyName, exchangeName, backtest, this));
|
|
28676
|
+
this.waitForInit = functoolsKit.singleshot(async (symbol, strategyName, exchangeName, frameName, backtest) => await WAIT_FOR_INIT_FN(symbol, strategyName, exchangeName, frameName, backtest, this));
|
|
28652
28677
|
}
|
|
28653
28678
|
/**
|
|
28654
28679
|
* Persists current breakeven state to disk.
|
|
@@ -28665,7 +28690,7 @@ class ClientBreakeven {
|
|
|
28665
28690
|
* @param signalId - Signal identifier
|
|
28666
28691
|
* @returns Promise that resolves when persistence is complete
|
|
28667
28692
|
*/
|
|
28668
|
-
async _persistState(symbol, strategyName, exchangeName, signalId) {
|
|
28693
|
+
async _persistState(symbol, strategyName, exchangeName, frameName, signalId) {
|
|
28669
28694
|
if (this.params.backtest) {
|
|
28670
28695
|
return;
|
|
28671
28696
|
}
|
|
@@ -28679,7 +28704,12 @@ class ClientBreakeven {
|
|
|
28679
28704
|
reached: state.reached,
|
|
28680
28705
|
};
|
|
28681
28706
|
}
|
|
28682
|
-
await
|
|
28707
|
+
const timestamp = await this.params.time.getTimestamp(symbol, {
|
|
28708
|
+
strategyName,
|
|
28709
|
+
exchangeName,
|
|
28710
|
+
frameName,
|
|
28711
|
+
}, this.params.backtest);
|
|
28712
|
+
await PersistBreakevenAdapter.writeBreakevenData(breakevenData, symbol, strategyName, signalId, exchangeName, new Date(timestamp));
|
|
28683
28713
|
}
|
|
28684
28714
|
/**
|
|
28685
28715
|
* Checks if breakeven should be triggered and emits event if conditions met.
|
|
@@ -28770,7 +28800,7 @@ class ClientBreakeven {
|
|
|
28770
28800
|
throw new Error(`Signal ID mismatch: expected ${this.params.signalId}, got ${data.id}`);
|
|
28771
28801
|
}
|
|
28772
28802
|
this._states.delete(data.id);
|
|
28773
|
-
await this._persistState(symbol, data.strategyName, data.exchangeName, this.params.signalId);
|
|
28803
|
+
await this._persistState(symbol, data.strategyName, data.exchangeName, data.frameName, this.params.signalId);
|
|
28774
28804
|
}
|
|
28775
28805
|
}
|
|
28776
28806
|
|
|
@@ -28859,6 +28889,7 @@ class BreakevenConnectionService {
|
|
|
28859
28889
|
* Action core service injected from DI container.
|
|
28860
28890
|
*/
|
|
28861
28891
|
this.actionCoreService = inject(TYPES.actionCoreService);
|
|
28892
|
+
this.timeMetaService = inject(TYPES.timeMetaService);
|
|
28862
28893
|
/**
|
|
28863
28894
|
* Memoized factory function for ClientBreakeven instances.
|
|
28864
28895
|
*
|
|
@@ -28872,6 +28903,7 @@ class BreakevenConnectionService {
|
|
|
28872
28903
|
return new ClientBreakeven({
|
|
28873
28904
|
signalId,
|
|
28874
28905
|
logger: this.loggerService,
|
|
28906
|
+
time: this.timeMetaService,
|
|
28875
28907
|
backtest,
|
|
28876
28908
|
onBreakeven: CREATE_COMMIT_BREAKEVEN_FN(this),
|
|
28877
28909
|
});
|
|
@@ -28898,7 +28930,7 @@ class BreakevenConnectionService {
|
|
|
28898
28930
|
when,
|
|
28899
28931
|
});
|
|
28900
28932
|
const breakeven = this.getBreakeven(data.id, backtest);
|
|
28901
|
-
await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
28933
|
+
await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, data.frameName, backtest);
|
|
28902
28934
|
return await breakeven.check(symbol, data, currentPrice, backtest, when);
|
|
28903
28935
|
};
|
|
28904
28936
|
/**
|
|
@@ -28927,7 +28959,7 @@ class BreakevenConnectionService {
|
|
|
28927
28959
|
backtest,
|
|
28928
28960
|
});
|
|
28929
28961
|
const breakeven = this.getBreakeven(data.id, backtest);
|
|
28930
|
-
await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
28962
|
+
await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, data.frameName, backtest);
|
|
28931
28963
|
await breakeven.clear(symbol, data, priceClose, backtest);
|
|
28932
28964
|
const key = CREATE_KEY_FN$i(data.id, backtest);
|
|
28933
28965
|
this.getBreakeven.clear(key);
|
|
@@ -36540,6 +36572,80 @@ function getActionSchema(actionName) {
|
|
|
36540
36572
|
return backtest.actionSchemaService.get(actionName);
|
|
36541
36573
|
}
|
|
36542
36574
|
|
|
36575
|
+
const WAIT_FOR_READY_METHOD_NAME = "init.waitForReady";
|
|
36576
|
+
const MAX_WAIT_SECONDS = 10;
|
|
36577
|
+
const SECOND_DELAY = 1000;
|
|
36578
|
+
/**
|
|
36579
|
+
* Blocks until the schema registries needed to start trading are populated.
|
|
36580
|
+
*
|
|
36581
|
+
* Polls `exchangeValidationService`, `frameValidationService` and
|
|
36582
|
+
* `strategyValidationService` once per second for up to `MAX_WAIT_SECONDS`
|
|
36583
|
+
* seconds. The loop exits as soon as the required registries are non-empty
|
|
36584
|
+
* for the given mode:
|
|
36585
|
+
*
|
|
36586
|
+
* - Backtest mode (`isBacktest = true`): exchange, frame and strategy schemas
|
|
36587
|
+
* must all be registered (frames define the historical window).
|
|
36588
|
+
* - Live mode (`isBacktest = false`): only exchange and strategy schemas are
|
|
36589
|
+
* required — frames are unused.
|
|
36590
|
+
*
|
|
36591
|
+
* Useful at startup when schemas are registered asynchronously (lazy imports,
|
|
36592
|
+
* remote config, plugin loading) and the caller wants to delay `Backtest`/
|
|
36593
|
+
* `Live` invocation until everything is ready. If the timeout elapses without
|
|
36594
|
+
* the registries filling in, the function returns silently — the caller is
|
|
36595
|
+
* expected to surface a clearer error from the subsequent `Backtest`/`Live`
|
|
36596
|
+
* call (e.g. "no strategy registered").
|
|
36597
|
+
*
|
|
36598
|
+
* @param isBacktest - Whether to additionally require a registered frame schema. Defaults to `true`.
|
|
36599
|
+
* @returns Promise that resolves when the registries are ready or the timeout elapses.
|
|
36600
|
+
*
|
|
36601
|
+
* @example
|
|
36602
|
+
* ```typescript
|
|
36603
|
+
* import { waitForReady, Backtest } from "backtest-kit";
|
|
36604
|
+
*
|
|
36605
|
+
* import "./schemas/exchange";
|
|
36606
|
+
* import "./schemas/strategy";
|
|
36607
|
+
* import "./schemas/frame";
|
|
36608
|
+
*
|
|
36609
|
+
* await waitForReady();
|
|
36610
|
+
* Backtest.background("BTCUSDT", { strategyName, exchangeName, frameName });
|
|
36611
|
+
* ```
|
|
36612
|
+
*/
|
|
36613
|
+
async function waitForReady(isBacktest = true) {
|
|
36614
|
+
backtest.loggerService.info(WAIT_FOR_READY_METHOD_NAME, { isBacktest });
|
|
36615
|
+
for (let i = 0; i !== MAX_WAIT_SECONDS; i++) {
|
|
36616
|
+
const [exchangeList, frameList, strategyList] = await Promise.all([
|
|
36617
|
+
backtest.exchangeValidationService.list(),
|
|
36618
|
+
backtest.frameValidationService.list(),
|
|
36619
|
+
backtest.strategyValidationService.list(),
|
|
36620
|
+
]);
|
|
36621
|
+
if (isBacktest && !frameList.length) {
|
|
36622
|
+
backtest.loggerService.debug(WAIT_FOR_READY_METHOD_NAME, {
|
|
36623
|
+
reason: "no frames registered",
|
|
36624
|
+
attempt: i + 1,
|
|
36625
|
+
});
|
|
36626
|
+
await functoolsKit.sleep(SECOND_DELAY);
|
|
36627
|
+
continue;
|
|
36628
|
+
}
|
|
36629
|
+
if (!exchangeList.length) {
|
|
36630
|
+
backtest.loggerService.debug(WAIT_FOR_READY_METHOD_NAME, {
|
|
36631
|
+
reason: "no exchanges registered",
|
|
36632
|
+
attempt: i + 1,
|
|
36633
|
+
});
|
|
36634
|
+
await functoolsKit.sleep(SECOND_DELAY);
|
|
36635
|
+
continue;
|
|
36636
|
+
}
|
|
36637
|
+
if (!strategyList.length) {
|
|
36638
|
+
backtest.loggerService.debug(WAIT_FOR_READY_METHOD_NAME, {
|
|
36639
|
+
reason: "no strategies registered",
|
|
36640
|
+
attempt: i + 1,
|
|
36641
|
+
});
|
|
36642
|
+
await functoolsKit.sleep(SECOND_DELAY);
|
|
36643
|
+
continue;
|
|
36644
|
+
}
|
|
36645
|
+
break;
|
|
36646
|
+
}
|
|
36647
|
+
}
|
|
36648
|
+
|
|
36543
36649
|
const GET_CANDLES_METHOD_NAME = "exchange.getCandles";
|
|
36544
36650
|
const GET_AVERAGE_PRICE_METHOD_NAME = "exchange.getAveragePrice";
|
|
36545
36651
|
const GET_CLOSE_PRICE_METHOD_NAME = "exchange.getClosePrice";
|
|
@@ -63825,5 +63931,6 @@ exports.validatePendingSignal = validatePendingSignal;
|
|
|
63825
63931
|
exports.validateScheduledSignal = validateScheduledSignal;
|
|
63826
63932
|
exports.validateSignal = validateSignal;
|
|
63827
63933
|
exports.waitForCandle = waitForCandle;
|
|
63934
|
+
exports.waitForReady = waitForReady;
|
|
63828
63935
|
exports.warmCandles = warmCandles;
|
|
63829
63936
|
exports.writeMemory = writeMemory;
|