backtest-kit 9.0.2 → 9.0.3
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 +140 -108
- package/build/index.mjs +140 -108
- package/package.json +1 -1
- package/types.d.ts +128 -112
package/build/index.mjs
CHANGED
|
@@ -1438,7 +1438,7 @@ class PersistRiskInstance {
|
|
|
1438
1438
|
*
|
|
1439
1439
|
* @returns Promise resolving to positions (empty array if none persisted)
|
|
1440
1440
|
*/
|
|
1441
|
-
async readPositionData() {
|
|
1441
|
+
async readPositionData(_when) {
|
|
1442
1442
|
if (await this._storage.hasValue(PersistRiskInstance.STORAGE_KEY)) {
|
|
1443
1443
|
return await this._storage.readValue(PersistRiskInstance.STORAGE_KEY);
|
|
1444
1444
|
}
|
|
@@ -1448,9 +1448,10 @@ class PersistRiskInstance {
|
|
|
1448
1448
|
* Writes the positions array using the fixed STORAGE_KEY.
|
|
1449
1449
|
*
|
|
1450
1450
|
* @param riskRow - Position entries to persist
|
|
1451
|
+
* @param when - Logical timestamp (reserved for API consistency; not used)
|
|
1451
1452
|
* @returns Promise that resolves when write is complete
|
|
1452
1453
|
*/
|
|
1453
|
-
async writePositionData(riskRow) {
|
|
1454
|
+
async writePositionData(riskRow, _when) {
|
|
1454
1455
|
await this._storage.writeValue(PersistRiskInstance.STORAGE_KEY, riskRow);
|
|
1455
1456
|
}
|
|
1456
1457
|
}
|
|
@@ -1475,12 +1476,12 @@ class PersistRiskDummyInstance {
|
|
|
1475
1476
|
* Always returns empty positions array.
|
|
1476
1477
|
* @returns Promise resolving to []
|
|
1477
1478
|
*/
|
|
1478
|
-
async readPositionData() { return []; }
|
|
1479
|
+
async readPositionData(_when) { return []; }
|
|
1479
1480
|
/**
|
|
1480
1481
|
* No-op write (discards positions).
|
|
1481
1482
|
* @returns Promise that resolves immediately
|
|
1482
1483
|
*/
|
|
1483
|
-
async writePositionData(_riskRow) { }
|
|
1484
|
+
async writePositionData(_riskRow, _when) { }
|
|
1484
1485
|
}
|
|
1485
1486
|
/**
|
|
1486
1487
|
* Utility class for managing risk active positions persistence.
|
|
@@ -1510,15 +1511,16 @@ class PersistRiskUtils {
|
|
|
1510
1511
|
*
|
|
1511
1512
|
* @param riskName - Risk profile identifier
|
|
1512
1513
|
* @param exchangeName - Exchange identifier
|
|
1514
|
+
* @param when - Logical timestamp at which the read is happening (reserved for API consistency)
|
|
1513
1515
|
* @returns Promise resolving to position entries (empty array if none)
|
|
1514
1516
|
*/
|
|
1515
|
-
this.readPositionData = async (riskName, exchangeName) => {
|
|
1517
|
+
this.readPositionData = async (riskName, exchangeName, when) => {
|
|
1516
1518
|
LOGGER_SERVICE$7.info(PERSIST_RISK_UTILS_METHOD_NAME_READ_DATA);
|
|
1517
1519
|
const key = `${riskName}:${exchangeName}`;
|
|
1518
1520
|
const isInitial = !this.getRiskStorage.has(key);
|
|
1519
1521
|
const instance = this.getRiskStorage(riskName, exchangeName);
|
|
1520
1522
|
await instance.waitForInit(isInitial);
|
|
1521
|
-
return instance.readPositionData();
|
|
1523
|
+
return instance.readPositionData(when);
|
|
1522
1524
|
};
|
|
1523
1525
|
/**
|
|
1524
1526
|
* Writes active positions for the given risk context.
|
|
@@ -1527,15 +1529,16 @@ class PersistRiskUtils {
|
|
|
1527
1529
|
* @param riskRow - Position entries to persist
|
|
1528
1530
|
* @param riskName - Risk profile identifier
|
|
1529
1531
|
* @param exchangeName - Exchange identifier
|
|
1532
|
+
* @param when - Logical timestamp this write belongs to (reserved for API consistency)
|
|
1530
1533
|
* @returns Promise that resolves when write is complete
|
|
1531
1534
|
*/
|
|
1532
|
-
this.writePositionData = async (riskRow, riskName, exchangeName) => {
|
|
1535
|
+
this.writePositionData = async (riskRow, riskName, exchangeName, when) => {
|
|
1533
1536
|
LOGGER_SERVICE$7.info(PERSIST_RISK_UTILS_METHOD_NAME_WRITE_DATA);
|
|
1534
1537
|
const key = `${riskName}:${exchangeName}`;
|
|
1535
1538
|
const isInitial = !this.getRiskStorage.has(key);
|
|
1536
1539
|
const instance = this.getRiskStorage(riskName, exchangeName);
|
|
1537
1540
|
await instance.waitForInit(isInitial);
|
|
1538
|
-
return instance.writePositionData(riskRow);
|
|
1541
|
+
return instance.writePositionData(riskRow, when);
|
|
1539
1542
|
};
|
|
1540
1543
|
}
|
|
1541
1544
|
/**
|
|
@@ -1829,7 +1832,7 @@ class PersistPartialInstance {
|
|
|
1829
1832
|
* @param signalId - Signal identifier
|
|
1830
1833
|
* @returns Promise resolving to partial data record (empty object if not found)
|
|
1831
1834
|
*/
|
|
1832
|
-
async readPartialData(signalId) {
|
|
1835
|
+
async readPartialData(signalId, _when) {
|
|
1833
1836
|
if (await this._storage.hasValue(signalId)) {
|
|
1834
1837
|
return await this._storage.readValue(signalId);
|
|
1835
1838
|
}
|
|
@@ -1840,9 +1843,10 @@ class PersistPartialInstance {
|
|
|
1840
1843
|
*
|
|
1841
1844
|
* @param data - Partial data record to persist
|
|
1842
1845
|
* @param signalId - Signal identifier
|
|
1846
|
+
* @param when - Logical timestamp (reserved for API consistency; not used)
|
|
1843
1847
|
* @returns Promise that resolves when write is complete
|
|
1844
1848
|
*/
|
|
1845
|
-
async writePartialData(data, signalId) {
|
|
1849
|
+
async writePartialData(data, signalId, _when) {
|
|
1846
1850
|
await this._storage.writeValue(signalId, data);
|
|
1847
1851
|
}
|
|
1848
1852
|
}
|
|
@@ -1865,12 +1869,12 @@ class PersistPartialDummyInstance {
|
|
|
1865
1869
|
* Always returns empty partial data record.
|
|
1866
1870
|
* @returns Promise resolving to {}
|
|
1867
1871
|
*/
|
|
1868
|
-
async readPartialData(_signalId) { return {}; }
|
|
1872
|
+
async readPartialData(_signalId, _when) { return {}; }
|
|
1869
1873
|
/**
|
|
1870
1874
|
* No-op write (discards partial data).
|
|
1871
1875
|
* @returns Promise that resolves immediately
|
|
1872
1876
|
*/
|
|
1873
|
-
async writePartialData(_data, _signalId) { }
|
|
1877
|
+
async writePartialData(_data, _signalId, _when) { }
|
|
1874
1878
|
}
|
|
1875
1879
|
/**
|
|
1876
1880
|
* Utility class for managing partial profit/loss levels persistence.
|
|
@@ -1903,15 +1907,16 @@ class PersistPartialUtils {
|
|
|
1903
1907
|
* @param strategyName - Strategy identifier
|
|
1904
1908
|
* @param signalId - Signal identifier
|
|
1905
1909
|
* @param exchangeName - Exchange identifier
|
|
1910
|
+
* @param when - Logical timestamp at which the read is happening (reserved for API consistency)
|
|
1906
1911
|
* @returns Promise resolving to partial data record (empty object if none)
|
|
1907
1912
|
*/
|
|
1908
|
-
this.readPartialData = async (symbol, strategyName, signalId, exchangeName) => {
|
|
1913
|
+
this.readPartialData = async (symbol, strategyName, signalId, exchangeName, when) => {
|
|
1909
1914
|
LOGGER_SERVICE$7.info(PERSIST_PARTIAL_UTILS_METHOD_NAME_READ_DATA);
|
|
1910
1915
|
const key = `${symbol}:${strategyName}:${exchangeName}`;
|
|
1911
1916
|
const isInitial = !this.getPartialStorage.has(key);
|
|
1912
1917
|
const instance = this.getPartialStorage(symbol, strategyName, exchangeName);
|
|
1913
1918
|
await instance.waitForInit(isInitial);
|
|
1914
|
-
return instance.readPartialData(signalId);
|
|
1919
|
+
return instance.readPartialData(signalId, when);
|
|
1915
1920
|
};
|
|
1916
1921
|
/**
|
|
1917
1922
|
* Writes partial data for the given context and signalId.
|
|
@@ -1922,15 +1927,16 @@ class PersistPartialUtils {
|
|
|
1922
1927
|
* @param strategyName - Strategy identifier
|
|
1923
1928
|
* @param signalId - Signal identifier
|
|
1924
1929
|
* @param exchangeName - Exchange identifier
|
|
1930
|
+
* @param when - Logical timestamp this write belongs to (reserved for API consistency)
|
|
1925
1931
|
* @returns Promise that resolves when write is complete
|
|
1926
1932
|
*/
|
|
1927
|
-
this.writePartialData = async (partialData, symbol, strategyName, signalId, exchangeName) => {
|
|
1933
|
+
this.writePartialData = async (partialData, symbol, strategyName, signalId, exchangeName, when) => {
|
|
1928
1934
|
LOGGER_SERVICE$7.info(PERSIST_PARTIAL_UTILS_METHOD_NAME_WRITE_DATA);
|
|
1929
1935
|
const key = `${symbol}:${strategyName}:${exchangeName}`;
|
|
1930
1936
|
const isInitial = !this.getPartialStorage.has(key);
|
|
1931
1937
|
const instance = this.getPartialStorage(symbol, strategyName, exchangeName);
|
|
1932
1938
|
await instance.waitForInit(isInitial);
|
|
1933
|
-
return instance.writePartialData(partialData, signalId);
|
|
1939
|
+
return instance.writePartialData(partialData, signalId, when);
|
|
1934
1940
|
};
|
|
1935
1941
|
}
|
|
1936
1942
|
/**
|
|
@@ -2029,7 +2035,7 @@ class PersistBreakevenInstance {
|
|
|
2029
2035
|
* @param signalId - Signal identifier
|
|
2030
2036
|
* @returns Promise resolving to breakeven data record (empty object if not found)
|
|
2031
2037
|
*/
|
|
2032
|
-
async readBreakevenData(signalId) {
|
|
2038
|
+
async readBreakevenData(signalId, _when) {
|
|
2033
2039
|
if (await this._storage.hasValue(signalId)) {
|
|
2034
2040
|
return await this._storage.readValue(signalId);
|
|
2035
2041
|
}
|
|
@@ -2040,9 +2046,10 @@ class PersistBreakevenInstance {
|
|
|
2040
2046
|
*
|
|
2041
2047
|
* @param data - Breakeven data record to persist
|
|
2042
2048
|
* @param signalId - Signal identifier
|
|
2049
|
+
* @param when - Logical timestamp (reserved for API consistency; not used)
|
|
2043
2050
|
* @returns Promise that resolves when write is complete
|
|
2044
2051
|
*/
|
|
2045
|
-
async writeBreakevenData(data, signalId) {
|
|
2052
|
+
async writeBreakevenData(data, signalId, _when) {
|
|
2046
2053
|
await this._storage.writeValue(signalId, data);
|
|
2047
2054
|
}
|
|
2048
2055
|
}
|
|
@@ -2065,12 +2072,12 @@ class PersistBreakevenDummyInstance {
|
|
|
2065
2072
|
* Always returns empty breakeven data record.
|
|
2066
2073
|
* @returns Promise resolving to {}
|
|
2067
2074
|
*/
|
|
2068
|
-
async readBreakevenData(_signalId) { return {}; }
|
|
2075
|
+
async readBreakevenData(_signalId, _when) { return {}; }
|
|
2069
2076
|
/**
|
|
2070
2077
|
* No-op write (discards breakeven data).
|
|
2071
2078
|
* @returns Promise that resolves immediately
|
|
2072
2079
|
*/
|
|
2073
|
-
async writeBreakevenData(_data, _signalId) { }
|
|
2080
|
+
async writeBreakevenData(_data, _signalId, _when) { }
|
|
2074
2081
|
}
|
|
2075
2082
|
/**
|
|
2076
2083
|
* Persistence utility class for breakeven state management.
|
|
@@ -2123,15 +2130,16 @@ class PersistBreakevenUtils {
|
|
|
2123
2130
|
* @param strategyName - Strategy identifier
|
|
2124
2131
|
* @param signalId - Signal identifier
|
|
2125
2132
|
* @param exchangeName - Exchange identifier
|
|
2133
|
+
* @param when - Logical timestamp at which the read is happening (reserved for API consistency)
|
|
2126
2134
|
* @returns Promise resolving to breakeven data record (empty object if none)
|
|
2127
2135
|
*/
|
|
2128
|
-
this.readBreakevenData = async (symbol, strategyName, signalId, exchangeName) => {
|
|
2136
|
+
this.readBreakevenData = async (symbol, strategyName, signalId, exchangeName, when) => {
|
|
2129
2137
|
LOGGER_SERVICE$7.info(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_READ_DATA);
|
|
2130
2138
|
const key = `${symbol}:${strategyName}:${exchangeName}`;
|
|
2131
2139
|
const isInitial = !this.getBreakevenStorage.has(key);
|
|
2132
2140
|
const instance = this.getBreakevenStorage(symbol, strategyName, exchangeName);
|
|
2133
2141
|
await instance.waitForInit(isInitial);
|
|
2134
|
-
return instance.readBreakevenData(signalId);
|
|
2142
|
+
return instance.readBreakevenData(signalId, when);
|
|
2135
2143
|
};
|
|
2136
2144
|
/**
|
|
2137
2145
|
* Writes breakeven data for the given context and signalId.
|
|
@@ -2142,15 +2150,16 @@ class PersistBreakevenUtils {
|
|
|
2142
2150
|
* @param strategyName - Strategy identifier
|
|
2143
2151
|
* @param signalId - Signal identifier
|
|
2144
2152
|
* @param exchangeName - Exchange identifier
|
|
2153
|
+
* @param when - Logical timestamp this write belongs to (reserved for API consistency)
|
|
2145
2154
|
* @returns Promise that resolves when write is complete
|
|
2146
2155
|
*/
|
|
2147
|
-
this.writeBreakevenData = async (breakevenData, symbol, strategyName, signalId, exchangeName) => {
|
|
2156
|
+
this.writeBreakevenData = async (breakevenData, symbol, strategyName, signalId, exchangeName, when) => {
|
|
2148
2157
|
LOGGER_SERVICE$7.info(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_WRITE_DATA);
|
|
2149
2158
|
const key = `${symbol}:${strategyName}:${exchangeName}`;
|
|
2150
2159
|
const isInitial = !this.getBreakevenStorage.has(key);
|
|
2151
2160
|
const instance = this.getBreakevenStorage(symbol, strategyName, exchangeName);
|
|
2152
2161
|
await instance.waitForInit(isInitial);
|
|
2153
|
-
return instance.writeBreakevenData(breakevenData, signalId);
|
|
2162
|
+
return instance.writeBreakevenData(breakevenData, signalId, when);
|
|
2154
2163
|
};
|
|
2155
2164
|
}
|
|
2156
2165
|
/**
|
|
@@ -14136,57 +14145,10 @@ const get = (object, path) => {
|
|
|
14136
14145
|
return pathArrayFlat.reduce((obj, key) => obj && obj[key], object);
|
|
14137
14146
|
};
|
|
14138
14147
|
|
|
14139
|
-
const MS_PER_MINUTE$6 = 60000;
|
|
14140
|
-
const INTERVAL_MINUTES$6 = {
|
|
14141
|
-
"1m": 1,
|
|
14142
|
-
"3m": 3,
|
|
14143
|
-
"5m": 5,
|
|
14144
|
-
"15m": 15,
|
|
14145
|
-
"30m": 30,
|
|
14146
|
-
"1h": 60,
|
|
14147
|
-
"2h": 120,
|
|
14148
|
-
"4h": 240,
|
|
14149
|
-
"6h": 360,
|
|
14150
|
-
"8h": 480,
|
|
14151
|
-
"1d": 1440,
|
|
14152
|
-
};
|
|
14153
|
-
/**
|
|
14154
|
-
* Aligns timestamp down to the nearest interval boundary.
|
|
14155
|
-
* For example, for 15m interval: 00:17 -> 00:15, 00:44 -> 00:30
|
|
14156
|
-
*
|
|
14157
|
-
* Candle timestamp convention:
|
|
14158
|
-
* - Candle timestamp = openTime (when candle opens)
|
|
14159
|
-
* - Candle with timestamp 00:00 covers period [00:00, 00:15) for 15m interval
|
|
14160
|
-
*
|
|
14161
|
-
* Adapter contract:
|
|
14162
|
-
* - Adapter must return candles with timestamp = openTime
|
|
14163
|
-
* - First returned candle.timestamp must equal aligned since
|
|
14164
|
-
* - Adapter must return exactly `limit` candles
|
|
14165
|
-
*
|
|
14166
|
-
* @param date - Date to align
|
|
14167
|
-
* @param interval - Candle interval (e.g., "1m", "15m", "1h")
|
|
14168
|
-
* @returns New Date aligned down to interval boundary
|
|
14169
|
-
*/
|
|
14170
|
-
const alignToInterval = (date, interval) => {
|
|
14171
|
-
const minutes = INTERVAL_MINUTES$6[interval];
|
|
14172
|
-
if (minutes === undefined) {
|
|
14173
|
-
throw new Error(`alignToInterval: unknown interval=${interval}`);
|
|
14174
|
-
}
|
|
14175
|
-
const intervalMs = minutes * MS_PER_MINUTE$6;
|
|
14176
|
-
return new Date(Math.floor(date.getTime() / intervalMs) * intervalMs);
|
|
14177
|
-
};
|
|
14178
|
-
|
|
14179
14148
|
/** Used to prevent race confition between concurent strategies */
|
|
14180
14149
|
const RISK_LOCK = new Lock();
|
|
14181
14150
|
/** Symbol indicating that positions need to be fetched from persistence */
|
|
14182
14151
|
const POSITION_NEED_FETCH = Symbol("risk-need-fetch");
|
|
14183
|
-
/** Get timestamp from execution context or fallback to aligned current time */
|
|
14184
|
-
const GET_CONTEXT_TIMESTAMP_FN = (self) => {
|
|
14185
|
-
if (ExecutionContextService.hasContext()) {
|
|
14186
|
-
return self.params.execution.context.when.getTime();
|
|
14187
|
-
}
|
|
14188
|
-
return alignToInterval(new Date(), "1m").getTime();
|
|
14189
|
-
};
|
|
14190
14152
|
/** Zero PNL constant for scheduled signals (which don't have priceOpen or PNL yet) */
|
|
14191
14153
|
const ZERO_PNL = { pnlPercentage: 0, priceOpen: 0, priceClose: 0, pnlCost: 0, pnlEntries: 0 };
|
|
14192
14154
|
/**
|
|
@@ -14312,7 +14274,7 @@ const CALL_ALLOWED_CALLBACKS_FN = trycatch(async (self, symbol, params) => {
|
|
|
14312
14274
|
*
|
|
14313
14275
|
* In backtest mode, initializes with empty Map. In live mode, reads from persist storage.
|
|
14314
14276
|
*/
|
|
14315
|
-
const WAIT_FOR_INIT_FN$3 = async (self) => {
|
|
14277
|
+
const WAIT_FOR_INIT_FN$3 = async (when, self) => {
|
|
14316
14278
|
self.params.logger.debug("ClientRisk waitForInit", {
|
|
14317
14279
|
backtest: self.params.backtest,
|
|
14318
14280
|
});
|
|
@@ -14320,7 +14282,7 @@ const WAIT_FOR_INIT_FN$3 = async (self) => {
|
|
|
14320
14282
|
self._activePositions = new Map();
|
|
14321
14283
|
return;
|
|
14322
14284
|
}
|
|
14323
|
-
const persistedPositions = await PersistRiskAdapter.readPositionData(self.params.riskName, self.params.exchangeName);
|
|
14285
|
+
const persistedPositions = await PersistRiskAdapter.readPositionData(self.params.riskName, self.params.exchangeName, when);
|
|
14324
14286
|
self._activePositions = new Map(persistedPositions);
|
|
14325
14287
|
};
|
|
14326
14288
|
/**
|
|
@@ -14349,7 +14311,7 @@ class ClientRisk {
|
|
|
14349
14311
|
* Uses singleshot pattern to ensure initialization happens exactly once.
|
|
14350
14312
|
* Skips persistence in backtest mode.
|
|
14351
14313
|
*/
|
|
14352
|
-
this.waitForInit = singleshot(async () => await WAIT_FOR_INIT_FN$3(this));
|
|
14314
|
+
this.waitForInit = singleshot(async (when) => await WAIT_FOR_INIT_FN$3(when, this));
|
|
14353
14315
|
/**
|
|
14354
14316
|
* Checks if a signal should be allowed based on risk limits.
|
|
14355
14317
|
*
|
|
@@ -14371,11 +14333,15 @@ class ClientRisk {
|
|
|
14371
14333
|
});
|
|
14372
14334
|
await RISK_LOCK.acquireLock();
|
|
14373
14335
|
try {
|
|
14336
|
+
const timestamp = await this.params.time.getTimestamp(params.symbol, {
|
|
14337
|
+
strategyName: params.strategyName,
|
|
14338
|
+
exchangeName: params.exchangeName,
|
|
14339
|
+
frameName: params.frameName,
|
|
14340
|
+
}, this.params.backtest);
|
|
14374
14341
|
if (this._activePositions === POSITION_NEED_FETCH) {
|
|
14375
|
-
await this.waitForInit();
|
|
14342
|
+
await this.waitForInit(new Date(timestamp));
|
|
14376
14343
|
}
|
|
14377
14344
|
const riskMap = this._activePositions;
|
|
14378
|
-
const timestamp = GET_CONTEXT_TIMESTAMP_FN(this);
|
|
14379
14345
|
const payload = {
|
|
14380
14346
|
...params,
|
|
14381
14347
|
currentSignal: TO_RISK_SIGNAL(params.currentSignal, params.currentPrice, timestamp),
|
|
@@ -14478,14 +14444,14 @@ class ClientRisk {
|
|
|
14478
14444
|
* Persists current active positions to disk.
|
|
14479
14445
|
* Skips in backtest mode.
|
|
14480
14446
|
*/
|
|
14481
|
-
async _updatePositions() {
|
|
14447
|
+
async _updatePositions(when) {
|
|
14482
14448
|
if (this.params.backtest) {
|
|
14483
14449
|
return;
|
|
14484
14450
|
}
|
|
14485
14451
|
if (this._activePositions === POSITION_NEED_FETCH) {
|
|
14486
|
-
await this.waitForInit();
|
|
14452
|
+
await this.waitForInit(when);
|
|
14487
14453
|
}
|
|
14488
|
-
await PersistRiskAdapter.writePositionData(Array.from(this._activePositions), this.params.riskName, this.params.exchangeName);
|
|
14454
|
+
await PersistRiskAdapter.writePositionData(Array.from(this._activePositions), this.params.riskName, this.params.exchangeName, when);
|
|
14489
14455
|
}
|
|
14490
14456
|
/**
|
|
14491
14457
|
* Registers a new opened signal.
|
|
@@ -14500,8 +14466,9 @@ class ClientRisk {
|
|
|
14500
14466
|
});
|
|
14501
14467
|
await RISK_LOCK.acquireLock();
|
|
14502
14468
|
try {
|
|
14469
|
+
const timestamp = await this.params.time.getTimestamp(symbol, context, this.params.backtest);
|
|
14503
14470
|
if (this._activePositions === POSITION_NEED_FETCH) {
|
|
14504
|
-
await this.waitForInit();
|
|
14471
|
+
await this.waitForInit(new Date(timestamp));
|
|
14505
14472
|
}
|
|
14506
14473
|
const key = CREATE_NAME_FN(context.strategyName, context.exchangeName, symbol);
|
|
14507
14474
|
const riskMap = this._activePositions;
|
|
@@ -14517,7 +14484,7 @@ class ClientRisk {
|
|
|
14517
14484
|
minuteEstimatedTime: positionData.minuteEstimatedTime,
|
|
14518
14485
|
openTimestamp: positionData.openTimestamp,
|
|
14519
14486
|
});
|
|
14520
|
-
await this._updatePositions();
|
|
14487
|
+
await this._updatePositions(new Date(timestamp));
|
|
14521
14488
|
}
|
|
14522
14489
|
finally {
|
|
14523
14490
|
await RISK_LOCK.releaseLock();
|
|
@@ -14535,13 +14502,14 @@ class ClientRisk {
|
|
|
14535
14502
|
});
|
|
14536
14503
|
await RISK_LOCK.acquireLock();
|
|
14537
14504
|
try {
|
|
14505
|
+
const timestamp = await this.params.time.getTimestamp(symbol, context, this.params.backtest);
|
|
14538
14506
|
if (this._activePositions === POSITION_NEED_FETCH) {
|
|
14539
|
-
await this.waitForInit();
|
|
14507
|
+
await this.waitForInit(new Date(timestamp));
|
|
14540
14508
|
}
|
|
14541
14509
|
const key = CREATE_NAME_FN(context.strategyName, context.exchangeName, symbol);
|
|
14542
14510
|
const riskMap = this._activePositions;
|
|
14543
14511
|
riskMap.delete(key);
|
|
14544
|
-
await this._updatePositions();
|
|
14512
|
+
await this._updatePositions(new Date(timestamp));
|
|
14545
14513
|
}
|
|
14546
14514
|
finally {
|
|
14547
14515
|
await RISK_LOCK.releaseLock();
|
|
@@ -14641,7 +14609,7 @@ class RiskConnectionService {
|
|
|
14641
14609
|
constructor() {
|
|
14642
14610
|
this.loggerService = inject(TYPES.loggerService);
|
|
14643
14611
|
this.riskSchemaService = inject(TYPES.riskSchemaService);
|
|
14644
|
-
this.
|
|
14612
|
+
this.timeMetaService = inject(TYPES.timeMetaService);
|
|
14645
14613
|
/**
|
|
14646
14614
|
* Action core service injected from DI container.
|
|
14647
14615
|
*/
|
|
@@ -14663,7 +14631,7 @@ class RiskConnectionService {
|
|
|
14663
14631
|
return new ClientRisk({
|
|
14664
14632
|
...schema,
|
|
14665
14633
|
logger: this.loggerService,
|
|
14666
|
-
|
|
14634
|
+
time: this.timeMetaService,
|
|
14667
14635
|
backtest,
|
|
14668
14636
|
exchangeName,
|
|
14669
14637
|
onRejected: CREATE_COMMIT_REJECTION_FN(this, exchangeName, frameName),
|
|
@@ -19654,8 +19622,8 @@ class BacktestLogicPrivateService {
|
|
|
19654
19622
|
}
|
|
19655
19623
|
|
|
19656
19624
|
const EMITTER_CHECK_INTERVAL = 5000;
|
|
19657
|
-
const MS_PER_MINUTE$
|
|
19658
|
-
const INTERVAL_MINUTES$
|
|
19625
|
+
const MS_PER_MINUTE$6 = 60000;
|
|
19626
|
+
const INTERVAL_MINUTES$6 = {
|
|
19659
19627
|
"1m": 1,
|
|
19660
19628
|
"3m": 3,
|
|
19661
19629
|
"5m": 5,
|
|
@@ -19670,7 +19638,7 @@ const INTERVAL_MINUTES$5 = {
|
|
|
19670
19638
|
};
|
|
19671
19639
|
const createEmitter = memoize(([interval]) => `${interval}`, (interval) => {
|
|
19672
19640
|
const tickSubject = new Subject();
|
|
19673
|
-
const intervalMs = INTERVAL_MINUTES$
|
|
19641
|
+
const intervalMs = INTERVAL_MINUTES$6[interval] * MS_PER_MINUTE$6;
|
|
19674
19642
|
{
|
|
19675
19643
|
let lastAligned = Math.floor(Date.now() / intervalMs) * intervalMs;
|
|
19676
19644
|
Source.fromInterval(EMITTER_CHECK_INTERVAL)
|
|
@@ -19697,6 +19665,46 @@ const waitForCandle = async (interval) => {
|
|
|
19697
19665
|
return emitter.toPromise();
|
|
19698
19666
|
};
|
|
19699
19667
|
|
|
19668
|
+
const MS_PER_MINUTE$5 = 60000;
|
|
19669
|
+
const INTERVAL_MINUTES$5 = {
|
|
19670
|
+
"1m": 1,
|
|
19671
|
+
"3m": 3,
|
|
19672
|
+
"5m": 5,
|
|
19673
|
+
"15m": 15,
|
|
19674
|
+
"30m": 30,
|
|
19675
|
+
"1h": 60,
|
|
19676
|
+
"2h": 120,
|
|
19677
|
+
"4h": 240,
|
|
19678
|
+
"6h": 360,
|
|
19679
|
+
"8h": 480,
|
|
19680
|
+
"1d": 1440,
|
|
19681
|
+
};
|
|
19682
|
+
/**
|
|
19683
|
+
* Aligns timestamp down to the nearest interval boundary.
|
|
19684
|
+
* For example, for 15m interval: 00:17 -> 00:15, 00:44 -> 00:30
|
|
19685
|
+
*
|
|
19686
|
+
* Candle timestamp convention:
|
|
19687
|
+
* - Candle timestamp = openTime (when candle opens)
|
|
19688
|
+
* - Candle with timestamp 00:00 covers period [00:00, 00:15) for 15m interval
|
|
19689
|
+
*
|
|
19690
|
+
* Adapter contract:
|
|
19691
|
+
* - Adapter must return candles with timestamp = openTime
|
|
19692
|
+
* - First returned candle.timestamp must equal aligned since
|
|
19693
|
+
* - Adapter must return exactly `limit` candles
|
|
19694
|
+
*
|
|
19695
|
+
* @param date - Date to align
|
|
19696
|
+
* @param interval - Candle interval (e.g., "1m", "15m", "1h")
|
|
19697
|
+
* @returns New Date aligned down to interval boundary
|
|
19698
|
+
*/
|
|
19699
|
+
const alignToInterval = (date, interval) => {
|
|
19700
|
+
const minutes = INTERVAL_MINUTES$5[interval];
|
|
19701
|
+
if (minutes === undefined) {
|
|
19702
|
+
throw new Error(`alignToInterval: unknown interval=${interval}`);
|
|
19703
|
+
}
|
|
19704
|
+
const intervalMs = minutes * MS_PER_MINUTE$5;
|
|
19705
|
+
return new Date(Math.floor(date.getTime() / intervalMs) * intervalMs);
|
|
19706
|
+
};
|
|
19707
|
+
|
|
19700
19708
|
/**
|
|
19701
19709
|
* Private service for live trading orchestration using async generators.
|
|
19702
19710
|
*
|
|
@@ -27195,7 +27203,7 @@ const HANDLE_PROFIT_FN = async (symbol, data, currentPrice, revenuePercent, back
|
|
|
27195
27203
|
}
|
|
27196
27204
|
}
|
|
27197
27205
|
if (shouldPersist) {
|
|
27198
|
-
await self._persistState(symbol, data.strategyName, data.exchangeName, self.params.signalId);
|
|
27206
|
+
await self._persistState(symbol, data.strategyName, data.exchangeName, data.frameName, self.params.signalId);
|
|
27199
27207
|
}
|
|
27200
27208
|
};
|
|
27201
27209
|
/**
|
|
@@ -27245,7 +27253,7 @@ const HANDLE_LOSS_FN = async (symbol, data, currentPrice, lossPercent, backtest,
|
|
|
27245
27253
|
}
|
|
27246
27254
|
}
|
|
27247
27255
|
if (shouldPersist) {
|
|
27248
|
-
await self._persistState(symbol, data.strategyName, data.exchangeName, self.params.signalId);
|
|
27256
|
+
await self._persistState(symbol, data.strategyName, data.exchangeName, data.frameName, self.params.signalId);
|
|
27249
27257
|
}
|
|
27250
27258
|
};
|
|
27251
27259
|
/**
|
|
@@ -27262,7 +27270,7 @@ const HANDLE_LOSS_FN = async (symbol, data, currentPrice, lossPercent, backtest,
|
|
|
27262
27270
|
* @param backtest - True if backtest mode, false if live mode
|
|
27263
27271
|
* @param self - ClientPartial instance reference
|
|
27264
27272
|
*/
|
|
27265
|
-
const WAIT_FOR_INIT_FN$1 = async (symbol, strategyName, exchangeName, backtest, self) => {
|
|
27273
|
+
const WAIT_FOR_INIT_FN$1 = async (symbol, strategyName, exchangeName, frameName, backtest, self) => {
|
|
27266
27274
|
self.params.logger.debug("ClientPartial waitForInit", {
|
|
27267
27275
|
symbol,
|
|
27268
27276
|
backtest,
|
|
@@ -27278,7 +27286,12 @@ const WAIT_FOR_INIT_FN$1 = async (symbol, strategyName, exchangeName, backtest,
|
|
|
27278
27286
|
self.params.logger.debug("ClientPartial waitForInit: skipping persist read in backtest mode");
|
|
27279
27287
|
return;
|
|
27280
27288
|
}
|
|
27281
|
-
const
|
|
27289
|
+
const timestamp = await self.params.time.getTimestamp(symbol, {
|
|
27290
|
+
strategyName,
|
|
27291
|
+
exchangeName,
|
|
27292
|
+
frameName,
|
|
27293
|
+
}, self.params.backtest);
|
|
27294
|
+
const partialData = await PersistPartialAdapter.readPartialData(symbol, strategyName, self.params.signalId, exchangeName, new Date(timestamp));
|
|
27282
27295
|
for (const [signalId, data] of Object.entries(partialData)) {
|
|
27283
27296
|
const state = {
|
|
27284
27297
|
profitLevels: new Set(data.profitLevels),
|
|
@@ -27386,7 +27399,7 @@ class ClientPartial {
|
|
|
27386
27399
|
* // Now profit()/loss() can be called
|
|
27387
27400
|
* ```
|
|
27388
27401
|
*/
|
|
27389
|
-
this.waitForInit = singleshot(async (symbol, strategyName, exchangeName, backtest) => await WAIT_FOR_INIT_FN$1(symbol, strategyName, exchangeName, backtest, this));
|
|
27402
|
+
this.waitForInit = singleshot(async (symbol, strategyName, exchangeName, frameName, backtest) => await WAIT_FOR_INIT_FN$1(symbol, strategyName, exchangeName, frameName, backtest, this));
|
|
27390
27403
|
}
|
|
27391
27404
|
/**
|
|
27392
27405
|
* Persists current partial state to disk.
|
|
@@ -27404,7 +27417,7 @@ class ClientPartial {
|
|
|
27404
27417
|
* @param signalId - Signal identifier
|
|
27405
27418
|
* @returns Promise that resolves when persistence is complete
|
|
27406
27419
|
*/
|
|
27407
|
-
async _persistState(symbol, strategyName, exchangeName, signalId) {
|
|
27420
|
+
async _persistState(symbol, strategyName, exchangeName, frameName, signalId) {
|
|
27408
27421
|
if (this.params.backtest) {
|
|
27409
27422
|
return;
|
|
27410
27423
|
}
|
|
@@ -27419,7 +27432,12 @@ class ClientPartial {
|
|
|
27419
27432
|
lossLevels: Array.from(state.lossLevels),
|
|
27420
27433
|
};
|
|
27421
27434
|
}
|
|
27422
|
-
await
|
|
27435
|
+
const timestamp = await this.params.time.getTimestamp(symbol, {
|
|
27436
|
+
strategyName,
|
|
27437
|
+
exchangeName,
|
|
27438
|
+
frameName,
|
|
27439
|
+
}, this.params.backtest);
|
|
27440
|
+
await PersistPartialAdapter.writePartialData(partialData, symbol, strategyName, signalId, exchangeName, new Date(timestamp));
|
|
27423
27441
|
}
|
|
27424
27442
|
/**
|
|
27425
27443
|
* Processes profit state and emits events for newly reached profit levels.
|
|
@@ -27544,7 +27562,7 @@ class ClientPartial {
|
|
|
27544
27562
|
throw new Error(`Signal ID mismatch: expected ${this.params.signalId}, got ${data.id}`);
|
|
27545
27563
|
}
|
|
27546
27564
|
this._states.delete(data.id);
|
|
27547
|
-
await this._persistState(symbol, data.strategyName, data.exchangeName, this.params.signalId);
|
|
27565
|
+
await this._persistState(symbol, data.strategyName, data.exchangeName, data.frameName, this.params.signalId);
|
|
27548
27566
|
}
|
|
27549
27567
|
}
|
|
27550
27568
|
|
|
@@ -27669,6 +27687,7 @@ class PartialConnectionService {
|
|
|
27669
27687
|
* Action core service injected from DI container.
|
|
27670
27688
|
*/
|
|
27671
27689
|
this.actionCoreService = inject(TYPES.actionCoreService);
|
|
27690
|
+
this.timeMetaService = inject(TYPES.timeMetaService);
|
|
27672
27691
|
/**
|
|
27673
27692
|
* Memoized factory function for ClientPartial instances.
|
|
27674
27693
|
*
|
|
@@ -27682,6 +27701,7 @@ class PartialConnectionService {
|
|
|
27682
27701
|
return new ClientPartial({
|
|
27683
27702
|
signalId,
|
|
27684
27703
|
logger: this.loggerService,
|
|
27704
|
+
time: this.timeMetaService,
|
|
27685
27705
|
backtest,
|
|
27686
27706
|
onProfit: CREATE_COMMIT_PROFIT_FN(this),
|
|
27687
27707
|
onLoss: CREATE_COMMIT_LOSS_FN(this),
|
|
@@ -27711,7 +27731,7 @@ class PartialConnectionService {
|
|
|
27711
27731
|
when,
|
|
27712
27732
|
});
|
|
27713
27733
|
const partial = this.getPartial(data.id, backtest);
|
|
27714
|
-
await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
27734
|
+
await partial.waitForInit(symbol, data.strategyName, data.exchangeName, data.frameName, backtest);
|
|
27715
27735
|
return await partial.profit(symbol, data, currentPrice, revenuePercent, backtest, when);
|
|
27716
27736
|
};
|
|
27717
27737
|
/**
|
|
@@ -27738,7 +27758,7 @@ class PartialConnectionService {
|
|
|
27738
27758
|
when,
|
|
27739
27759
|
});
|
|
27740
27760
|
const partial = this.getPartial(data.id, backtest);
|
|
27741
|
-
await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
27761
|
+
await partial.waitForInit(symbol, data.strategyName, data.exchangeName, data.frameName, backtest);
|
|
27742
27762
|
return await partial.loss(symbol, data, currentPrice, lossPercent, backtest, when);
|
|
27743
27763
|
};
|
|
27744
27764
|
/**
|
|
@@ -27766,7 +27786,7 @@ class PartialConnectionService {
|
|
|
27766
27786
|
backtest,
|
|
27767
27787
|
});
|
|
27768
27788
|
const partial = this.getPartial(data.id, backtest);
|
|
27769
|
-
await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
27789
|
+
await partial.waitForInit(symbol, data.strategyName, data.exchangeName, data.frameName, backtest);
|
|
27770
27790
|
await partial.clear(symbol, data, priceClose, backtest);
|
|
27771
27791
|
const key = CREATE_KEY_FN$l(data.id, backtest);
|
|
27772
27792
|
this.getPartial.clear(key);
|
|
@@ -28492,7 +28512,7 @@ const HANDLE_BREAKEVEN_FN = async (symbol, data, currentPrice, backtest, when, s
|
|
|
28492
28512
|
// Emit event
|
|
28493
28513
|
await self.params.onBreakeven(symbol, data.strategyName, data.exchangeName, data.frameName, data, currentPrice, backtest, when.getTime());
|
|
28494
28514
|
// Persist state
|
|
28495
|
-
await self._persistState(symbol, data.strategyName, data.exchangeName, self.params.signalId);
|
|
28515
|
+
await self._persistState(symbol, data.strategyName, data.exchangeName, data.frameName, self.params.signalId);
|
|
28496
28516
|
return true;
|
|
28497
28517
|
};
|
|
28498
28518
|
/**
|
|
@@ -28507,7 +28527,7 @@ const HANDLE_BREAKEVEN_FN = async (symbol, data, currentPrice, backtest, when, s
|
|
|
28507
28527
|
* @param exchangeName - Exchange identifier
|
|
28508
28528
|
* @param self - ClientBreakeven instance reference
|
|
28509
28529
|
*/
|
|
28510
|
-
const WAIT_FOR_INIT_FN = async (symbol, strategyName, exchangeName, backtest, self) => {
|
|
28530
|
+
const WAIT_FOR_INIT_FN = async (symbol, strategyName, exchangeName, frameName, backtest, self) => {
|
|
28511
28531
|
self.params.logger.debug("ClientBreakeven waitForInit", {
|
|
28512
28532
|
symbol,
|
|
28513
28533
|
strategyName,
|
|
@@ -28523,7 +28543,12 @@ const WAIT_FOR_INIT_FN = async (symbol, strategyName, exchangeName, backtest, se
|
|
|
28523
28543
|
self.params.logger.debug("ClientBreakeven waitForInit: skipping persist read in backtest mode");
|
|
28524
28544
|
return;
|
|
28525
28545
|
}
|
|
28526
|
-
const
|
|
28546
|
+
const timestamp = await self.params.time.getTimestamp(symbol, {
|
|
28547
|
+
strategyName,
|
|
28548
|
+
exchangeName,
|
|
28549
|
+
frameName,
|
|
28550
|
+
}, self.params.backtest);
|
|
28551
|
+
const breakevenData = await PersistBreakevenAdapter.readBreakevenData(symbol, strategyName, self.params.signalId, exchangeName, new Date(timestamp));
|
|
28527
28552
|
for (const [signalId, data] of Object.entries(breakevenData)) {
|
|
28528
28553
|
const state = {
|
|
28529
28554
|
reached: data.reached,
|
|
@@ -28628,7 +28653,7 @@ class ClientBreakeven {
|
|
|
28628
28653
|
* // Now check() can be called
|
|
28629
28654
|
* ```
|
|
28630
28655
|
*/
|
|
28631
|
-
this.waitForInit = singleshot(async (symbol, strategyName, exchangeName, backtest) => await WAIT_FOR_INIT_FN(symbol, strategyName, exchangeName, backtest, this));
|
|
28656
|
+
this.waitForInit = singleshot(async (symbol, strategyName, exchangeName, frameName, backtest) => await WAIT_FOR_INIT_FN(symbol, strategyName, exchangeName, frameName, backtest, this));
|
|
28632
28657
|
}
|
|
28633
28658
|
/**
|
|
28634
28659
|
* Persists current breakeven state to disk.
|
|
@@ -28645,7 +28670,7 @@ class ClientBreakeven {
|
|
|
28645
28670
|
* @param signalId - Signal identifier
|
|
28646
28671
|
* @returns Promise that resolves when persistence is complete
|
|
28647
28672
|
*/
|
|
28648
|
-
async _persistState(symbol, strategyName, exchangeName, signalId) {
|
|
28673
|
+
async _persistState(symbol, strategyName, exchangeName, frameName, signalId) {
|
|
28649
28674
|
if (this.params.backtest) {
|
|
28650
28675
|
return;
|
|
28651
28676
|
}
|
|
@@ -28659,7 +28684,12 @@ class ClientBreakeven {
|
|
|
28659
28684
|
reached: state.reached,
|
|
28660
28685
|
};
|
|
28661
28686
|
}
|
|
28662
|
-
await
|
|
28687
|
+
const timestamp = await this.params.time.getTimestamp(symbol, {
|
|
28688
|
+
strategyName,
|
|
28689
|
+
exchangeName,
|
|
28690
|
+
frameName,
|
|
28691
|
+
}, this.params.backtest);
|
|
28692
|
+
await PersistBreakevenAdapter.writeBreakevenData(breakevenData, symbol, strategyName, signalId, exchangeName, new Date(timestamp));
|
|
28663
28693
|
}
|
|
28664
28694
|
/**
|
|
28665
28695
|
* Checks if breakeven should be triggered and emits event if conditions met.
|
|
@@ -28750,7 +28780,7 @@ class ClientBreakeven {
|
|
|
28750
28780
|
throw new Error(`Signal ID mismatch: expected ${this.params.signalId}, got ${data.id}`);
|
|
28751
28781
|
}
|
|
28752
28782
|
this._states.delete(data.id);
|
|
28753
|
-
await this._persistState(symbol, data.strategyName, data.exchangeName, this.params.signalId);
|
|
28783
|
+
await this._persistState(symbol, data.strategyName, data.exchangeName, data.frameName, this.params.signalId);
|
|
28754
28784
|
}
|
|
28755
28785
|
}
|
|
28756
28786
|
|
|
@@ -28839,6 +28869,7 @@ class BreakevenConnectionService {
|
|
|
28839
28869
|
* Action core service injected from DI container.
|
|
28840
28870
|
*/
|
|
28841
28871
|
this.actionCoreService = inject(TYPES.actionCoreService);
|
|
28872
|
+
this.timeMetaService = inject(TYPES.timeMetaService);
|
|
28842
28873
|
/**
|
|
28843
28874
|
* Memoized factory function for ClientBreakeven instances.
|
|
28844
28875
|
*
|
|
@@ -28852,6 +28883,7 @@ class BreakevenConnectionService {
|
|
|
28852
28883
|
return new ClientBreakeven({
|
|
28853
28884
|
signalId,
|
|
28854
28885
|
logger: this.loggerService,
|
|
28886
|
+
time: this.timeMetaService,
|
|
28855
28887
|
backtest,
|
|
28856
28888
|
onBreakeven: CREATE_COMMIT_BREAKEVEN_FN(this),
|
|
28857
28889
|
});
|
|
@@ -28878,7 +28910,7 @@ class BreakevenConnectionService {
|
|
|
28878
28910
|
when,
|
|
28879
28911
|
});
|
|
28880
28912
|
const breakeven = this.getBreakeven(data.id, backtest);
|
|
28881
|
-
await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
28913
|
+
await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, data.frameName, backtest);
|
|
28882
28914
|
return await breakeven.check(symbol, data, currentPrice, backtest, when);
|
|
28883
28915
|
};
|
|
28884
28916
|
/**
|
|
@@ -28907,7 +28939,7 @@ class BreakevenConnectionService {
|
|
|
28907
28939
|
backtest,
|
|
28908
28940
|
});
|
|
28909
28941
|
const breakeven = this.getBreakeven(data.id, backtest);
|
|
28910
|
-
await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
28942
|
+
await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, data.frameName, backtest);
|
|
28911
28943
|
await breakeven.clear(symbol, data, priceClose, backtest);
|
|
28912
28944
|
const key = CREATE_KEY_FN$i(data.id, backtest);
|
|
28913
28945
|
this.getBreakeven.clear(key);
|