backtest-kit 8.5.0 → 9.0.0
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/LICENSE +21 -21
- package/README.md +1825 -1825
- package/build/index.cjs +2319 -929
- package/build/index.mjs +2305 -930
- package/package.json +86 -86
- package/types.d.ts +1972 -402
package/build/index.mjs
CHANGED
|
@@ -1201,43 +1201,91 @@ _a$3 = BASE_WAIT_FOR_INIT_SYMBOL;
|
|
|
1201
1201
|
// @ts-ignore
|
|
1202
1202
|
PersistBase = makeExtendable(PersistBase);
|
|
1203
1203
|
/**
|
|
1204
|
-
*
|
|
1205
|
-
*
|
|
1204
|
+
* Default file-based implementation of IPersistSignalInstance.
|
|
1205
|
+
*
|
|
1206
|
+
* Features:
|
|
1207
|
+
* - Wraps PersistBase for atomic JSON writes
|
|
1208
|
+
* - Uses symbol as entity ID within a per-context PersistBase
|
|
1209
|
+
* - Crash-safe via atomic writes
|
|
1210
|
+
*
|
|
1211
|
+
* @example
|
|
1212
|
+
* ```typescript
|
|
1213
|
+
* const instance = new PersistSignalInstance("BTCUSDT", "my-strategy", "binance");
|
|
1214
|
+
* await instance.waitForInit(true);
|
|
1215
|
+
* await instance.writeSignalData(signalRow);
|
|
1216
|
+
* const restored = await instance.readSignalData();
|
|
1217
|
+
* ```
|
|
1206
1218
|
*/
|
|
1207
|
-
class
|
|
1219
|
+
class PersistSignalInstance {
|
|
1208
1220
|
/**
|
|
1209
|
-
*
|
|
1210
|
-
*
|
|
1221
|
+
* Creates new signal persistence instance.
|
|
1222
|
+
*
|
|
1223
|
+
* @param symbol - Trading pair symbol
|
|
1224
|
+
* @param strategyName - Strategy identifier
|
|
1225
|
+
* @param exchangeName - Exchange identifier
|
|
1211
1226
|
*/
|
|
1212
|
-
|
|
1227
|
+
constructor(symbol, strategyName, exchangeName) {
|
|
1228
|
+
this.symbol = symbol;
|
|
1229
|
+
this.strategyName = strategyName;
|
|
1230
|
+
this.exchangeName = exchangeName;
|
|
1231
|
+
this._storage = new PersistBase(`${symbol}_${strategyName}_${exchangeName}`, `./dump/data/signal/`);
|
|
1213
1232
|
}
|
|
1214
1233
|
/**
|
|
1215
|
-
*
|
|
1216
|
-
*
|
|
1234
|
+
* Initializes the underlying PersistBase storage.
|
|
1235
|
+
* Delegates to PersistBase.waitForInit which uses singleshot.
|
|
1236
|
+
*
|
|
1237
|
+
* @param initial - Whether this is the first initialization
|
|
1238
|
+
* @returns Promise that resolves when initialization is complete
|
|
1217
1239
|
*/
|
|
1218
|
-
async
|
|
1219
|
-
|
|
1240
|
+
async waitForInit(initial) {
|
|
1241
|
+
await this._storage.waitForInit(initial);
|
|
1220
1242
|
}
|
|
1221
1243
|
/**
|
|
1222
|
-
*
|
|
1223
|
-
*
|
|
1244
|
+
* Reads the persisted signal using `symbol` as the entity key.
|
|
1245
|
+
*
|
|
1246
|
+
* @returns Promise resolving to the signal or null if not found
|
|
1224
1247
|
*/
|
|
1225
|
-
async
|
|
1226
|
-
|
|
1248
|
+
async readSignalData() {
|
|
1249
|
+
if (await this._storage.hasValue(this.symbol)) {
|
|
1250
|
+
return await this._storage.readValue(this.symbol);
|
|
1251
|
+
}
|
|
1252
|
+
return null;
|
|
1227
1253
|
}
|
|
1228
1254
|
/**
|
|
1229
|
-
*
|
|
1230
|
-
*
|
|
1255
|
+
* Writes the signal (or null to clear) using `symbol` as the entity key.
|
|
1256
|
+
*
|
|
1257
|
+
* @param signalRow - Signal data to persist, or null to clear
|
|
1258
|
+
* @returns Promise that resolves when write is complete
|
|
1231
1259
|
*/
|
|
1232
|
-
async
|
|
1260
|
+
async writeSignalData(signalRow) {
|
|
1261
|
+
await this._storage.writeValue(this.symbol, signalRow);
|
|
1233
1262
|
}
|
|
1263
|
+
}
|
|
1264
|
+
/**
|
|
1265
|
+
* No-op IPersistSignalInstance implementation used by PersistSignalUtils.useDummy().
|
|
1266
|
+
* All reads return null, all writes are discarded.
|
|
1267
|
+
*/
|
|
1268
|
+
class PersistSignalDummyInstance {
|
|
1234
1269
|
/**
|
|
1235
|
-
* No-op
|
|
1236
|
-
*
|
|
1270
|
+
* No-op constructor.
|
|
1271
|
+
* Context arguments are accepted to satisfy TPersistSignalInstanceCtor.
|
|
1237
1272
|
*/
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1273
|
+
constructor(_symbol, _strategyName, _exchangeName) { }
|
|
1274
|
+
/**
|
|
1275
|
+
* No-op initialization.
|
|
1276
|
+
* @returns Promise that resolves immediately
|
|
1277
|
+
*/
|
|
1278
|
+
async waitForInit(_initial) { }
|
|
1279
|
+
/**
|
|
1280
|
+
* Always returns null (no persisted signal).
|
|
1281
|
+
* @returns Promise resolving to null
|
|
1282
|
+
*/
|
|
1283
|
+
async readSignalData() { return null; }
|
|
1284
|
+
/**
|
|
1285
|
+
* No-op write (discards data).
|
|
1286
|
+
* @returns Promise that resolves immediately
|
|
1287
|
+
*/
|
|
1288
|
+
async writeSignalData(_signalRow) { }
|
|
1241
1289
|
}
|
|
1242
1290
|
/**
|
|
1243
1291
|
* Utility class for managing signal persistence.
|
|
@@ -1252,40 +1300,37 @@ class PersistDummy {
|
|
|
1252
1300
|
*/
|
|
1253
1301
|
class PersistSignalUtils {
|
|
1254
1302
|
constructor() {
|
|
1255
|
-
this.PersistSignalFactory = PersistBase;
|
|
1256
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName]) => `${symbol}:${strategyName}:${exchangeName}`, (symbol, strategyName, exchangeName) => Reflect.construct(this.PersistSignalFactory, [
|
|
1257
|
-
`${symbol}_${strategyName}_${exchangeName}`,
|
|
1258
|
-
`./dump/data/signal/`,
|
|
1259
|
-
]));
|
|
1260
1303
|
/**
|
|
1261
|
-
*
|
|
1262
|
-
*
|
|
1263
|
-
|
|
1264
|
-
|
|
1304
|
+
* Constructor used to create per-context signal instances.
|
|
1305
|
+
* Replaceable via usePersistSignalAdapter() / useJson() / useDummy().
|
|
1306
|
+
*/
|
|
1307
|
+
this.PersistSignalInstanceCtor = PersistSignalInstance;
|
|
1308
|
+
/**
|
|
1309
|
+
* Memoized factory creating one IPersistSignalInstance per (symbol, strategy, exchange) triple.
|
|
1310
|
+
*/
|
|
1311
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName]) => `${symbol}:${strategyName}:${exchangeName}`, (symbol, strategyName, exchangeName) => Reflect.construct(this.PersistSignalInstanceCtor, [symbol, strategyName, exchangeName]));
|
|
1312
|
+
/**
|
|
1313
|
+
* Reads persisted signal for the given context.
|
|
1314
|
+
* Lazily initializes the instance on first access.
|
|
1265
1315
|
*
|
|
1266
1316
|
* @param symbol - Trading pair symbol
|
|
1267
1317
|
* @param strategyName - Strategy identifier
|
|
1268
1318
|
* @param exchangeName - Exchange identifier
|
|
1269
|
-
* @returns Promise resolving to signal or null
|
|
1319
|
+
* @returns Promise resolving to signal or null if none persisted
|
|
1270
1320
|
*/
|
|
1271
1321
|
this.readSignalData = async (symbol, strategyName, exchangeName) => {
|
|
1272
1322
|
LOGGER_SERVICE$7.info(PERSIST_SIGNAL_UTILS_METHOD_NAME_READ_DATA);
|
|
1273
1323
|
const key = `${symbol}:${strategyName}:${exchangeName}`;
|
|
1274
1324
|
const isInitial = !this.getStorage.has(key);
|
|
1275
|
-
const
|
|
1276
|
-
await
|
|
1277
|
-
|
|
1278
|
-
return await stateStorage.readValue(symbol);
|
|
1279
|
-
}
|
|
1280
|
-
return null;
|
|
1325
|
+
const instance = this.getStorage(symbol, strategyName, exchangeName);
|
|
1326
|
+
await instance.waitForInit(isInitial);
|
|
1327
|
+
return instance.readSignalData();
|
|
1281
1328
|
};
|
|
1282
1329
|
/**
|
|
1283
|
-
* Writes signal data to
|
|
1330
|
+
* Writes signal data (or null to clear) for the given context.
|
|
1331
|
+
* Lazily initializes the instance on first access.
|
|
1284
1332
|
*
|
|
1285
|
-
*
|
|
1286
|
-
* Uses atomic writes to prevent corruption on crashes.
|
|
1287
|
-
*
|
|
1288
|
-
* @param signalRow - Signal data (null to clear)
|
|
1333
|
+
* @param signalRow - Signal data to persist, or null to clear
|
|
1289
1334
|
* @param symbol - Trading pair symbol
|
|
1290
1335
|
* @param strategyName - Strategy identifier
|
|
1291
1336
|
* @param exchangeName - Exchange identifier
|
|
@@ -1295,53 +1340,43 @@ class PersistSignalUtils {
|
|
|
1295
1340
|
LOGGER_SERVICE$7.info(PERSIST_SIGNAL_UTILS_METHOD_NAME_WRITE_DATA);
|
|
1296
1341
|
const key = `${symbol}:${strategyName}:${exchangeName}`;
|
|
1297
1342
|
const isInitial = !this.getStorage.has(key);
|
|
1298
|
-
const
|
|
1299
|
-
await
|
|
1300
|
-
|
|
1343
|
+
const instance = this.getStorage(symbol, strategyName, exchangeName);
|
|
1344
|
+
await instance.waitForInit(isInitial);
|
|
1345
|
+
return instance.writeSignalData(signalRow);
|
|
1301
1346
|
};
|
|
1302
1347
|
}
|
|
1303
1348
|
/**
|
|
1304
|
-
* Registers a custom
|
|
1349
|
+
* Registers a custom IPersistSignalInstance constructor.
|
|
1350
|
+
* Clears the memoization cache so subsequent calls use the new adapter.
|
|
1305
1351
|
*
|
|
1306
|
-
* @param Ctor - Custom
|
|
1307
|
-
*
|
|
1308
|
-
* @example
|
|
1309
|
-
* ```typescript
|
|
1310
|
-
* class RedisPersist extends PersistBase {
|
|
1311
|
-
* async readValue(id) { return JSON.parse(await redis.get(id)); }
|
|
1312
|
-
* async writeValue(id, entity) { await redis.set(id, JSON.stringify(entity)); }
|
|
1313
|
-
* }
|
|
1314
|
-
* PersistSignalAdapter.usePersistSignalAdapter(RedisPersist);
|
|
1315
|
-
* ```
|
|
1352
|
+
* @param Ctor - Custom IPersistSignalInstance constructor
|
|
1316
1353
|
*/
|
|
1317
1354
|
usePersistSignalAdapter(Ctor) {
|
|
1318
1355
|
LOGGER_SERVICE$7.info(PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_PERSIST_SIGNAL_ADAPTER);
|
|
1319
|
-
this.
|
|
1356
|
+
this.PersistSignalInstanceCtor = Ctor;
|
|
1357
|
+
this.getStorage.clear();
|
|
1320
1358
|
}
|
|
1321
1359
|
/**
|
|
1322
|
-
* Clears the memoized
|
|
1323
|
-
* Call
|
|
1324
|
-
* so new storage instances are created with the updated base path.
|
|
1360
|
+
* Clears the memoized instance cache.
|
|
1361
|
+
* Call when process.cwd() changes between strategy iterations.
|
|
1325
1362
|
*/
|
|
1326
1363
|
clear() {
|
|
1327
1364
|
LOGGER_SERVICE$7.log(PERSIST_SIGNAL_UTILS_METHOD_NAME_CLEAR);
|
|
1328
1365
|
this.getStorage.clear();
|
|
1329
1366
|
}
|
|
1330
1367
|
/**
|
|
1331
|
-
* Switches to the default
|
|
1332
|
-
* All future persistence writes will use JSON storage.
|
|
1368
|
+
* Switches to the default file-based PersistSignalInstance.
|
|
1333
1369
|
*/
|
|
1334
1370
|
useJson() {
|
|
1335
1371
|
LOGGER_SERVICE$7.log(PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_JSON);
|
|
1336
|
-
this.usePersistSignalAdapter(
|
|
1372
|
+
this.usePersistSignalAdapter(PersistSignalInstance);
|
|
1337
1373
|
}
|
|
1338
1374
|
/**
|
|
1339
|
-
* Switches to
|
|
1340
|
-
* All future persistence writes will be no-ops.
|
|
1375
|
+
* Switches to PersistSignalDummyInstance (all operations are no-ops).
|
|
1341
1376
|
*/
|
|
1342
1377
|
useDummy() {
|
|
1343
1378
|
LOGGER_SERVICE$7.log(PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_DUMMY);
|
|
1344
|
-
this.usePersistSignalAdapter(
|
|
1379
|
+
this.usePersistSignalAdapter(PersistSignalDummyInstance);
|
|
1345
1380
|
}
|
|
1346
1381
|
}
|
|
1347
1382
|
/**
|
|
@@ -1361,6 +1396,92 @@ class PersistSignalUtils {
|
|
|
1361
1396
|
* ```
|
|
1362
1397
|
*/
|
|
1363
1398
|
const PersistSignalAdapter = new PersistSignalUtils();
|
|
1399
|
+
/**
|
|
1400
|
+
* Default file-based implementation of IPersistRiskInstance.
|
|
1401
|
+
*
|
|
1402
|
+
* Features:
|
|
1403
|
+
* - Wraps PersistBase for atomic JSON writes
|
|
1404
|
+
* - Uses fixed entity ID "positions" within a per-context PersistBase
|
|
1405
|
+
* - Crash-safe via atomic writes
|
|
1406
|
+
*
|
|
1407
|
+
* @example
|
|
1408
|
+
* ```typescript
|
|
1409
|
+
* const instance = new PersistRiskInstance("my-risk", "binance");
|
|
1410
|
+
* await instance.waitForInit(true);
|
|
1411
|
+
* await instance.writePositionData([["strategy:BTCUSDT", positionData]]);
|
|
1412
|
+
* const positions = await instance.readPositionData();
|
|
1413
|
+
* ```
|
|
1414
|
+
*/
|
|
1415
|
+
class PersistRiskInstance {
|
|
1416
|
+
/**
|
|
1417
|
+
* Creates new risk positions persistence instance.
|
|
1418
|
+
*
|
|
1419
|
+
* @param riskName - Risk profile identifier
|
|
1420
|
+
* @param exchangeName - Exchange identifier
|
|
1421
|
+
*/
|
|
1422
|
+
constructor(riskName, exchangeName) {
|
|
1423
|
+
this.riskName = riskName;
|
|
1424
|
+
this.exchangeName = exchangeName;
|
|
1425
|
+
this._storage = new PersistBase(`${riskName}_${exchangeName}`, `./dump/data/risk/`);
|
|
1426
|
+
}
|
|
1427
|
+
/**
|
|
1428
|
+
* Initializes the underlying PersistBase storage.
|
|
1429
|
+
*
|
|
1430
|
+
* @param initial - Whether this is the first initialization
|
|
1431
|
+
* @returns Promise that resolves when initialization is complete
|
|
1432
|
+
*/
|
|
1433
|
+
async waitForInit(initial) {
|
|
1434
|
+
await this._storage.waitForInit(initial);
|
|
1435
|
+
}
|
|
1436
|
+
/**
|
|
1437
|
+
* Reads the persisted positions array using the fixed STORAGE_KEY.
|
|
1438
|
+
*
|
|
1439
|
+
* @returns Promise resolving to positions (empty array if none persisted)
|
|
1440
|
+
*/
|
|
1441
|
+
async readPositionData() {
|
|
1442
|
+
if (await this._storage.hasValue(PersistRiskInstance.STORAGE_KEY)) {
|
|
1443
|
+
return await this._storage.readValue(PersistRiskInstance.STORAGE_KEY);
|
|
1444
|
+
}
|
|
1445
|
+
return [];
|
|
1446
|
+
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Writes the positions array using the fixed STORAGE_KEY.
|
|
1449
|
+
*
|
|
1450
|
+
* @param riskRow - Position entries to persist
|
|
1451
|
+
* @returns Promise that resolves when write is complete
|
|
1452
|
+
*/
|
|
1453
|
+
async writePositionData(riskRow) {
|
|
1454
|
+
await this._storage.writeValue(PersistRiskInstance.STORAGE_KEY, riskRow);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
/** Fixed entity key for storing the positions array */
|
|
1458
|
+
PersistRiskInstance.STORAGE_KEY = "positions";
|
|
1459
|
+
/**
|
|
1460
|
+
* No-op IPersistRiskInstance implementation used by PersistRiskUtils.useDummy().
|
|
1461
|
+
* All reads return empty array, all writes are discarded.
|
|
1462
|
+
*/
|
|
1463
|
+
class PersistRiskDummyInstance {
|
|
1464
|
+
/**
|
|
1465
|
+
* No-op constructor.
|
|
1466
|
+
* Context arguments are accepted to satisfy TPersistRiskInstanceCtor.
|
|
1467
|
+
*/
|
|
1468
|
+
constructor(_riskName, _exchangeName) { }
|
|
1469
|
+
/**
|
|
1470
|
+
* No-op initialization.
|
|
1471
|
+
* @returns Promise that resolves immediately
|
|
1472
|
+
*/
|
|
1473
|
+
async waitForInit(_initial) { }
|
|
1474
|
+
/**
|
|
1475
|
+
* Always returns empty positions array.
|
|
1476
|
+
* @returns Promise resolving to []
|
|
1477
|
+
*/
|
|
1478
|
+
async readPositionData() { return []; }
|
|
1479
|
+
/**
|
|
1480
|
+
* No-op write (discards positions).
|
|
1481
|
+
* @returns Promise that resolves immediately
|
|
1482
|
+
*/
|
|
1483
|
+
async writePositionData(_riskRow) { }
|
|
1484
|
+
}
|
|
1364
1485
|
/**
|
|
1365
1486
|
* Utility class for managing risk active positions persistence.
|
|
1366
1487
|
*
|
|
@@ -1374,40 +1495,36 @@ const PersistSignalAdapter = new PersistSignalUtils();
|
|
|
1374
1495
|
*/
|
|
1375
1496
|
class PersistRiskUtils {
|
|
1376
1497
|
constructor() {
|
|
1377
|
-
this.PersistRiskFactory = PersistBase;
|
|
1378
|
-
this.getRiskStorage = memoize(([riskName, exchangeName]) => `${riskName}:${exchangeName}`, (riskName, exchangeName) => Reflect.construct(this.PersistRiskFactory, [
|
|
1379
|
-
`${riskName}_${exchangeName}`,
|
|
1380
|
-
`./dump/data/risk/`,
|
|
1381
|
-
]));
|
|
1382
1498
|
/**
|
|
1383
|
-
*
|
|
1384
|
-
*
|
|
1385
|
-
|
|
1386
|
-
|
|
1499
|
+
* Constructor used to create per-context risk instances.
|
|
1500
|
+
* Replaceable via usePersistRiskAdapter() / useJson() / useDummy().
|
|
1501
|
+
*/
|
|
1502
|
+
this.PersistRiskInstanceCtor = PersistRiskInstance;
|
|
1503
|
+
/**
|
|
1504
|
+
* Memoized factory creating one IPersistRiskInstance per (riskName, exchange) pair.
|
|
1505
|
+
*/
|
|
1506
|
+
this.getRiskStorage = memoize(([riskName, exchangeName]) => `${riskName}:${exchangeName}`, (riskName, exchangeName) => Reflect.construct(this.PersistRiskInstanceCtor, [riskName, exchangeName]));
|
|
1507
|
+
/**
|
|
1508
|
+
* Reads persisted active positions for the given risk context.
|
|
1509
|
+
* Lazily initializes the instance on first access.
|
|
1387
1510
|
*
|
|
1388
1511
|
* @param riskName - Risk profile identifier
|
|
1389
1512
|
* @param exchangeName - Exchange identifier
|
|
1390
|
-
* @returns Promise resolving to
|
|
1513
|
+
* @returns Promise resolving to position entries (empty array if none)
|
|
1391
1514
|
*/
|
|
1392
1515
|
this.readPositionData = async (riskName, exchangeName) => {
|
|
1393
1516
|
LOGGER_SERVICE$7.info(PERSIST_RISK_UTILS_METHOD_NAME_READ_DATA);
|
|
1394
1517
|
const key = `${riskName}:${exchangeName}`;
|
|
1395
1518
|
const isInitial = !this.getRiskStorage.has(key);
|
|
1396
|
-
const
|
|
1397
|
-
await
|
|
1398
|
-
|
|
1399
|
-
if (await stateStorage.hasValue(RISK_STORAGE_KEY)) {
|
|
1400
|
-
return await stateStorage.readValue(RISK_STORAGE_KEY);
|
|
1401
|
-
}
|
|
1402
|
-
return [];
|
|
1519
|
+
const instance = this.getRiskStorage(riskName, exchangeName);
|
|
1520
|
+
await instance.waitForInit(isInitial);
|
|
1521
|
+
return instance.readPositionData();
|
|
1403
1522
|
};
|
|
1404
1523
|
/**
|
|
1405
|
-
* Writes active positions
|
|
1524
|
+
* Writes active positions for the given risk context.
|
|
1525
|
+
* Lazily initializes the instance on first access.
|
|
1406
1526
|
*
|
|
1407
|
-
*
|
|
1408
|
-
* Uses atomic writes to prevent corruption on crashes.
|
|
1409
|
-
*
|
|
1410
|
-
* @param positions - Map of active positions
|
|
1527
|
+
* @param riskRow - Position entries to persist
|
|
1411
1528
|
* @param riskName - Risk profile identifier
|
|
1412
1529
|
* @param exchangeName - Exchange identifier
|
|
1413
1530
|
* @returns Promise that resolves when write is complete
|
|
@@ -1416,54 +1533,43 @@ class PersistRiskUtils {
|
|
|
1416
1533
|
LOGGER_SERVICE$7.info(PERSIST_RISK_UTILS_METHOD_NAME_WRITE_DATA);
|
|
1417
1534
|
const key = `${riskName}:${exchangeName}`;
|
|
1418
1535
|
const isInitial = !this.getRiskStorage.has(key);
|
|
1419
|
-
const
|
|
1420
|
-
await
|
|
1421
|
-
|
|
1422
|
-
await stateStorage.writeValue(RISK_STORAGE_KEY, riskRow);
|
|
1536
|
+
const instance = this.getRiskStorage(riskName, exchangeName);
|
|
1537
|
+
await instance.waitForInit(isInitial);
|
|
1538
|
+
return instance.writePositionData(riskRow);
|
|
1423
1539
|
};
|
|
1424
1540
|
}
|
|
1425
1541
|
/**
|
|
1426
|
-
* Registers a custom
|
|
1542
|
+
* Registers a custom IPersistRiskInstance constructor.
|
|
1543
|
+
* Clears the memoization cache so subsequent calls use the new adapter.
|
|
1427
1544
|
*
|
|
1428
|
-
* @param Ctor - Custom
|
|
1429
|
-
*
|
|
1430
|
-
* @example
|
|
1431
|
-
* ```typescript
|
|
1432
|
-
* class RedisPersist extends PersistBase {
|
|
1433
|
-
* async readValue(id) { return JSON.parse(await redis.get(id)); }
|
|
1434
|
-
* async writeValue(id, entity) { await redis.set(id, JSON.stringify(entity)); }
|
|
1435
|
-
* }
|
|
1436
|
-
* PersistRiskAdapter.usePersistRiskAdapter(RedisPersist);
|
|
1437
|
-
* ```
|
|
1545
|
+
* @param Ctor - Custom IPersistRiskInstance constructor
|
|
1438
1546
|
*/
|
|
1439
1547
|
usePersistRiskAdapter(Ctor) {
|
|
1440
1548
|
LOGGER_SERVICE$7.info(PERSIST_RISK_UTILS_METHOD_NAME_USE_PERSIST_RISK_ADAPTER);
|
|
1441
|
-
this.
|
|
1549
|
+
this.PersistRiskInstanceCtor = Ctor;
|
|
1550
|
+
this.getRiskStorage.clear();
|
|
1442
1551
|
}
|
|
1443
1552
|
/**
|
|
1444
|
-
* Clears the memoized
|
|
1445
|
-
* Call
|
|
1446
|
-
* so new storage instances are created with the updated base path.
|
|
1553
|
+
* Clears the memoized instance cache.
|
|
1554
|
+
* Call when process.cwd() changes between strategy iterations.
|
|
1447
1555
|
*/
|
|
1448
1556
|
clear() {
|
|
1449
1557
|
LOGGER_SERVICE$7.log(PERSIST_RISK_UTILS_METHOD_NAME_CLEAR);
|
|
1450
1558
|
this.getRiskStorage.clear();
|
|
1451
1559
|
}
|
|
1452
1560
|
/**
|
|
1453
|
-
* Switches to the default
|
|
1454
|
-
* All future persistence writes will use JSON storage.
|
|
1561
|
+
* Switches to the default file-based PersistRiskInstance.
|
|
1455
1562
|
*/
|
|
1456
1563
|
useJson() {
|
|
1457
1564
|
LOGGER_SERVICE$7.log(PERSIST_RISK_UTILS_METHOD_NAME_USE_JSON);
|
|
1458
|
-
this.usePersistRiskAdapter(
|
|
1565
|
+
this.usePersistRiskAdapter(PersistRiskInstance);
|
|
1459
1566
|
}
|
|
1460
1567
|
/**
|
|
1461
|
-
* Switches to
|
|
1462
|
-
* All future persistence writes will be no-ops.
|
|
1568
|
+
* Switches to PersistRiskDummyInstance (all operations are no-ops).
|
|
1463
1569
|
*/
|
|
1464
1570
|
useDummy() {
|
|
1465
1571
|
LOGGER_SERVICE$7.log(PERSIST_RISK_UTILS_METHOD_NAME_USE_DUMMY);
|
|
1466
|
-
this.usePersistRiskAdapter(
|
|
1572
|
+
this.usePersistRiskAdapter(PersistRiskDummyInstance);
|
|
1467
1573
|
}
|
|
1468
1574
|
}
|
|
1469
1575
|
/**
|
|
@@ -1483,6 +1589,92 @@ class PersistRiskUtils {
|
|
|
1483
1589
|
* ```
|
|
1484
1590
|
*/
|
|
1485
1591
|
const PersistRiskAdapter = new PersistRiskUtils();
|
|
1592
|
+
/**
|
|
1593
|
+
* Default file-based implementation of IPersistScheduleInstance.
|
|
1594
|
+
*
|
|
1595
|
+
* Features:
|
|
1596
|
+
* - Wraps PersistBase for atomic JSON writes
|
|
1597
|
+
* - Uses symbol as entity ID within a per-context PersistBase
|
|
1598
|
+
* - Crash-safe via atomic writes
|
|
1599
|
+
*
|
|
1600
|
+
* @example
|
|
1601
|
+
* ```typescript
|
|
1602
|
+
* const instance = new PersistScheduleInstance("BTCUSDT", "my-strategy", "binance");
|
|
1603
|
+
* await instance.waitForInit(true);
|
|
1604
|
+
* await instance.writeScheduleData(scheduledRow);
|
|
1605
|
+
* const restored = await instance.readScheduleData();
|
|
1606
|
+
* ```
|
|
1607
|
+
*/
|
|
1608
|
+
class PersistScheduleInstance {
|
|
1609
|
+
/**
|
|
1610
|
+
* Creates new scheduled signal persistence instance.
|
|
1611
|
+
*
|
|
1612
|
+
* @param symbol - Trading pair symbol
|
|
1613
|
+
* @param strategyName - Strategy identifier
|
|
1614
|
+
* @param exchangeName - Exchange identifier
|
|
1615
|
+
*/
|
|
1616
|
+
constructor(symbol, strategyName, exchangeName) {
|
|
1617
|
+
this.symbol = symbol;
|
|
1618
|
+
this.strategyName = strategyName;
|
|
1619
|
+
this.exchangeName = exchangeName;
|
|
1620
|
+
this._storage = new PersistBase(`${symbol}_${strategyName}_${exchangeName}`, `./dump/data/schedule/`);
|
|
1621
|
+
}
|
|
1622
|
+
/**
|
|
1623
|
+
* Initializes the underlying PersistBase storage.
|
|
1624
|
+
*
|
|
1625
|
+
* @param initial - Whether this is the first initialization
|
|
1626
|
+
* @returns Promise that resolves when initialization is complete
|
|
1627
|
+
*/
|
|
1628
|
+
async waitForInit(initial) {
|
|
1629
|
+
await this._storage.waitForInit(initial);
|
|
1630
|
+
}
|
|
1631
|
+
/**
|
|
1632
|
+
* Reads the persisted scheduled signal using `symbol` as the entity key.
|
|
1633
|
+
*
|
|
1634
|
+
* @returns Promise resolving to scheduled signal or null if not found
|
|
1635
|
+
*/
|
|
1636
|
+
async readScheduleData() {
|
|
1637
|
+
if (await this._storage.hasValue(this.symbol)) {
|
|
1638
|
+
return await this._storage.readValue(this.symbol);
|
|
1639
|
+
}
|
|
1640
|
+
return null;
|
|
1641
|
+
}
|
|
1642
|
+
/**
|
|
1643
|
+
* Writes the scheduled signal (or null to clear) using `symbol` as the entity key.
|
|
1644
|
+
*
|
|
1645
|
+
* @param row - Scheduled signal data to persist, or null to clear
|
|
1646
|
+
* @returns Promise that resolves when write is complete
|
|
1647
|
+
*/
|
|
1648
|
+
async writeScheduleData(row) {
|
|
1649
|
+
await this._storage.writeValue(this.symbol, row);
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
/**
|
|
1653
|
+
* No-op IPersistScheduleInstance implementation used by PersistScheduleUtils.useDummy().
|
|
1654
|
+
* All reads return null, all writes are discarded.
|
|
1655
|
+
*/
|
|
1656
|
+
class PersistScheduleDummyInstance {
|
|
1657
|
+
/**
|
|
1658
|
+
* No-op constructor.
|
|
1659
|
+
* Context arguments are accepted to satisfy TPersistScheduleInstanceCtor.
|
|
1660
|
+
*/
|
|
1661
|
+
constructor(_symbol, _strategyName, _exchangeName) { }
|
|
1662
|
+
/**
|
|
1663
|
+
* No-op initialization.
|
|
1664
|
+
* @returns Promise that resolves immediately
|
|
1665
|
+
*/
|
|
1666
|
+
async waitForInit(_initial) { }
|
|
1667
|
+
/**
|
|
1668
|
+
* Always returns null (no persisted scheduled signal).
|
|
1669
|
+
* @returns Promise resolving to null
|
|
1670
|
+
*/
|
|
1671
|
+
async readScheduleData() { return null; }
|
|
1672
|
+
/**
|
|
1673
|
+
* No-op write (discards scheduled signal).
|
|
1674
|
+
* @returns Promise that resolves immediately
|
|
1675
|
+
*/
|
|
1676
|
+
async writeScheduleData(_row) { }
|
|
1677
|
+
}
|
|
1486
1678
|
/**
|
|
1487
1679
|
* Utility class for managing scheduled signal persistence.
|
|
1488
1680
|
*
|
|
@@ -1496,40 +1688,37 @@ const PersistRiskAdapter = new PersistRiskUtils();
|
|
|
1496
1688
|
*/
|
|
1497
1689
|
class PersistScheduleUtils {
|
|
1498
1690
|
constructor() {
|
|
1499
|
-
this.PersistScheduleFactory = PersistBase;
|
|
1500
|
-
this.getScheduleStorage = memoize(([symbol, strategyName, exchangeName]) => `${symbol}:${strategyName}:${exchangeName}`, (symbol, strategyName, exchangeName) => Reflect.construct(this.PersistScheduleFactory, [
|
|
1501
|
-
`${symbol}_${strategyName}_${exchangeName}`,
|
|
1502
|
-
`./dump/data/schedule/`,
|
|
1503
|
-
]));
|
|
1504
1691
|
/**
|
|
1505
|
-
*
|
|
1506
|
-
*
|
|
1507
|
-
|
|
1508
|
-
|
|
1692
|
+
* Constructor used to create per-context scheduled signal instances.
|
|
1693
|
+
* Replaceable via usePersistScheduleAdapter() / useJson() / useDummy().
|
|
1694
|
+
*/
|
|
1695
|
+
this.PersistScheduleInstanceCtor = PersistScheduleInstance;
|
|
1696
|
+
/**
|
|
1697
|
+
* Memoized factory creating one IPersistScheduleInstance per (symbol, strategy, exchange) triple.
|
|
1698
|
+
*/
|
|
1699
|
+
this.getScheduleStorage = memoize(([symbol, strategyName, exchangeName]) => `${symbol}:${strategyName}:${exchangeName}`, (symbol, strategyName, exchangeName) => Reflect.construct(this.PersistScheduleInstanceCtor, [symbol, strategyName, exchangeName]));
|
|
1700
|
+
/**
|
|
1701
|
+
* Reads persisted scheduled signal for the given context.
|
|
1702
|
+
* Lazily initializes the instance on first access.
|
|
1509
1703
|
*
|
|
1510
1704
|
* @param symbol - Trading pair symbol
|
|
1511
1705
|
* @param strategyName - Strategy identifier
|
|
1512
1706
|
* @param exchangeName - Exchange identifier
|
|
1513
|
-
* @returns Promise resolving to scheduled signal or null
|
|
1707
|
+
* @returns Promise resolving to scheduled signal or null if none persisted
|
|
1514
1708
|
*/
|
|
1515
1709
|
this.readScheduleData = async (symbol, strategyName, exchangeName) => {
|
|
1516
1710
|
LOGGER_SERVICE$7.info(PERSIST_SCHEDULE_UTILS_METHOD_NAME_READ_DATA);
|
|
1517
1711
|
const key = `${symbol}:${strategyName}:${exchangeName}`;
|
|
1518
1712
|
const isInitial = !this.getScheduleStorage.has(key);
|
|
1519
|
-
const
|
|
1520
|
-
await
|
|
1521
|
-
|
|
1522
|
-
return await stateStorage.readValue(symbol);
|
|
1523
|
-
}
|
|
1524
|
-
return null;
|
|
1713
|
+
const instance = this.getScheduleStorage(symbol, strategyName, exchangeName);
|
|
1714
|
+
await instance.waitForInit(isInitial);
|
|
1715
|
+
return instance.readScheduleData();
|
|
1525
1716
|
};
|
|
1526
1717
|
/**
|
|
1527
|
-
* Writes scheduled signal
|
|
1528
|
-
*
|
|
1529
|
-
* Called by ClientStrategy.setScheduledSignal() to persist state.
|
|
1530
|
-
* Uses atomic writes to prevent corruption on crashes.
|
|
1718
|
+
* Writes scheduled signal (or null to clear) for the given context.
|
|
1719
|
+
* Lazily initializes the instance on first access.
|
|
1531
1720
|
*
|
|
1532
|
-
* @param scheduledSignalRow - Scheduled signal data
|
|
1721
|
+
* @param scheduledSignalRow - Scheduled signal data to persist, or null to clear
|
|
1533
1722
|
* @param symbol - Trading pair symbol
|
|
1534
1723
|
* @param strategyName - Strategy identifier
|
|
1535
1724
|
* @param exchangeName - Exchange identifier
|
|
@@ -1539,53 +1728,43 @@ class PersistScheduleUtils {
|
|
|
1539
1728
|
LOGGER_SERVICE$7.info(PERSIST_SCHEDULE_UTILS_METHOD_NAME_WRITE_DATA);
|
|
1540
1729
|
const key = `${symbol}:${strategyName}:${exchangeName}`;
|
|
1541
1730
|
const isInitial = !this.getScheduleStorage.has(key);
|
|
1542
|
-
const
|
|
1543
|
-
await
|
|
1544
|
-
|
|
1731
|
+
const instance = this.getScheduleStorage(symbol, strategyName, exchangeName);
|
|
1732
|
+
await instance.waitForInit(isInitial);
|
|
1733
|
+
return instance.writeScheduleData(scheduledSignalRow);
|
|
1545
1734
|
};
|
|
1546
1735
|
}
|
|
1547
1736
|
/**
|
|
1548
|
-
* Registers a custom
|
|
1549
|
-
*
|
|
1550
|
-
* @param Ctor - Custom PersistBase constructor
|
|
1737
|
+
* Registers a custom IPersistScheduleInstance constructor.
|
|
1738
|
+
* Clears the memoization cache so subsequent calls use the new adapter.
|
|
1551
1739
|
*
|
|
1552
|
-
* @
|
|
1553
|
-
* ```typescript
|
|
1554
|
-
* class RedisPersist extends PersistBase {
|
|
1555
|
-
* async readValue(id) { return JSON.parse(await redis.get(id)); }
|
|
1556
|
-
* async writeValue(id, entity) { await redis.set(id, JSON.stringify(entity)); }
|
|
1557
|
-
* }
|
|
1558
|
-
* PersistScheduleAdapter.usePersistScheduleAdapter(RedisPersist);
|
|
1559
|
-
* ```
|
|
1740
|
+
* @param Ctor - Custom IPersistScheduleInstance constructor
|
|
1560
1741
|
*/
|
|
1561
1742
|
usePersistScheduleAdapter(Ctor) {
|
|
1562
1743
|
LOGGER_SERVICE$7.info(PERSIST_SCHEDULE_UTILS_METHOD_NAME_USE_PERSIST_SCHEDULE_ADAPTER);
|
|
1563
|
-
this.
|
|
1744
|
+
this.PersistScheduleInstanceCtor = Ctor;
|
|
1745
|
+
this.getScheduleStorage.clear();
|
|
1564
1746
|
}
|
|
1565
1747
|
/**
|
|
1566
|
-
* Clears the memoized
|
|
1567
|
-
* Call
|
|
1568
|
-
* so new storage instances are created with the updated base path.
|
|
1748
|
+
* Clears the memoized instance cache.
|
|
1749
|
+
* Call when process.cwd() changes between strategy iterations.
|
|
1569
1750
|
*/
|
|
1570
1751
|
clear() {
|
|
1571
1752
|
LOGGER_SERVICE$7.log(PERSIST_SCHEDULE_UTILS_METHOD_NAME_CLEAR);
|
|
1572
1753
|
this.getScheduleStorage.clear();
|
|
1573
1754
|
}
|
|
1574
1755
|
/**
|
|
1575
|
-
* Switches to the default
|
|
1576
|
-
* All future persistence writes will use JSON storage.
|
|
1756
|
+
* Switches to the default file-based PersistScheduleInstance.
|
|
1577
1757
|
*/
|
|
1578
1758
|
useJson() {
|
|
1579
1759
|
LOGGER_SERVICE$7.log(PERSIST_SCHEDULE_UTILS_METHOD_NAME_USE_JSON);
|
|
1580
|
-
this.usePersistScheduleAdapter(
|
|
1760
|
+
this.usePersistScheduleAdapter(PersistScheduleInstance);
|
|
1581
1761
|
}
|
|
1582
1762
|
/**
|
|
1583
|
-
* Switches to
|
|
1584
|
-
* All future persistence writes will be no-ops.
|
|
1763
|
+
* Switches to PersistScheduleDummyInstance (all operations are no-ops).
|
|
1585
1764
|
*/
|
|
1586
1765
|
useDummy() {
|
|
1587
1766
|
LOGGER_SERVICE$7.log(PERSIST_SCHEDULE_UTILS_METHOD_NAME_USE_DUMMY);
|
|
1588
|
-
this.usePersistScheduleAdapter(
|
|
1767
|
+
this.usePersistScheduleAdapter(PersistScheduleDummyInstance);
|
|
1589
1768
|
}
|
|
1590
1769
|
}
|
|
1591
1770
|
/**
|
|
@@ -1605,6 +1784,94 @@ class PersistScheduleUtils {
|
|
|
1605
1784
|
* ```
|
|
1606
1785
|
*/
|
|
1607
1786
|
const PersistScheduleAdapter = new PersistScheduleUtils();
|
|
1787
|
+
/**
|
|
1788
|
+
* Default file-based implementation of IPersistPartialInstance.
|
|
1789
|
+
*
|
|
1790
|
+
* Features:
|
|
1791
|
+
* - Wraps PersistBase for atomic JSON writes
|
|
1792
|
+
* - Uses signalId as entity ID within a per-context PersistBase
|
|
1793
|
+
* - Crash-safe via atomic writes
|
|
1794
|
+
*
|
|
1795
|
+
* @example
|
|
1796
|
+
* ```typescript
|
|
1797
|
+
* const instance = new PersistPartialInstance("BTCUSDT", "my-strategy", "binance");
|
|
1798
|
+
* await instance.waitForInit(true);
|
|
1799
|
+
* await instance.writePartialData(partialData, "signal-id-1");
|
|
1800
|
+
* const restored = await instance.readPartialData("signal-id-1");
|
|
1801
|
+
* ```
|
|
1802
|
+
*/
|
|
1803
|
+
class PersistPartialInstance {
|
|
1804
|
+
/**
|
|
1805
|
+
* Creates new partial data persistence instance.
|
|
1806
|
+
*
|
|
1807
|
+
* @param symbol - Trading pair symbol
|
|
1808
|
+
* @param strategyName - Strategy identifier
|
|
1809
|
+
* @param exchangeName - Exchange identifier
|
|
1810
|
+
*/
|
|
1811
|
+
constructor(symbol, strategyName, exchangeName) {
|
|
1812
|
+
this.symbol = symbol;
|
|
1813
|
+
this.strategyName = strategyName;
|
|
1814
|
+
this.exchangeName = exchangeName;
|
|
1815
|
+
this._storage = new PersistBase(`${symbol}_${strategyName}_${exchangeName}`, `./dump/data/partial/`);
|
|
1816
|
+
}
|
|
1817
|
+
/**
|
|
1818
|
+
* Initializes the underlying PersistBase storage.
|
|
1819
|
+
*
|
|
1820
|
+
* @param initial - Whether this is the first initialization
|
|
1821
|
+
* @returns Promise that resolves when initialization is complete
|
|
1822
|
+
*/
|
|
1823
|
+
async waitForInit(initial) {
|
|
1824
|
+
await this._storage.waitForInit(initial);
|
|
1825
|
+
}
|
|
1826
|
+
/**
|
|
1827
|
+
* Reads the partial data for the given signal using `signalId` as the entity key.
|
|
1828
|
+
*
|
|
1829
|
+
* @param signalId - Signal identifier
|
|
1830
|
+
* @returns Promise resolving to partial data record (empty object if not found)
|
|
1831
|
+
*/
|
|
1832
|
+
async readPartialData(signalId) {
|
|
1833
|
+
if (await this._storage.hasValue(signalId)) {
|
|
1834
|
+
return await this._storage.readValue(signalId);
|
|
1835
|
+
}
|
|
1836
|
+
return {};
|
|
1837
|
+
}
|
|
1838
|
+
/**
|
|
1839
|
+
* Writes the partial data for the given signal using `signalId` as the entity key.
|
|
1840
|
+
*
|
|
1841
|
+
* @param data - Partial data record to persist
|
|
1842
|
+
* @param signalId - Signal identifier
|
|
1843
|
+
* @returns Promise that resolves when write is complete
|
|
1844
|
+
*/
|
|
1845
|
+
async writePartialData(data, signalId) {
|
|
1846
|
+
await this._storage.writeValue(signalId, data);
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
/**
|
|
1850
|
+
* No-op IPersistPartialInstance implementation used by PersistPartialUtils.useDummy().
|
|
1851
|
+
* All reads return empty object, all writes are discarded.
|
|
1852
|
+
*/
|
|
1853
|
+
class PersistPartialDummyInstance {
|
|
1854
|
+
/**
|
|
1855
|
+
* No-op constructor.
|
|
1856
|
+
* Context arguments are accepted to satisfy TPersistPartialInstanceCtor.
|
|
1857
|
+
*/
|
|
1858
|
+
constructor(_symbol, _strategyName, _exchangeName) { }
|
|
1859
|
+
/**
|
|
1860
|
+
* No-op initialization.
|
|
1861
|
+
* @returns Promise that resolves immediately
|
|
1862
|
+
*/
|
|
1863
|
+
async waitForInit(_initial) { }
|
|
1864
|
+
/**
|
|
1865
|
+
* Always returns empty partial data record.
|
|
1866
|
+
* @returns Promise resolving to {}
|
|
1867
|
+
*/
|
|
1868
|
+
async readPartialData(_signalId) { return {}; }
|
|
1869
|
+
/**
|
|
1870
|
+
* No-op write (discards partial data).
|
|
1871
|
+
* @returns Promise that resolves immediately
|
|
1872
|
+
*/
|
|
1873
|
+
async writePartialData(_data, _signalId) { }
|
|
1874
|
+
}
|
|
1608
1875
|
/**
|
|
1609
1876
|
* Utility class for managing partial profit/loss levels persistence.
|
|
1610
1877
|
*
|
|
@@ -1618,41 +1885,39 @@ const PersistScheduleAdapter = new PersistScheduleUtils();
|
|
|
1618
1885
|
*/
|
|
1619
1886
|
class PersistPartialUtils {
|
|
1620
1887
|
constructor() {
|
|
1621
|
-
this.PersistPartialFactory = PersistBase;
|
|
1622
|
-
this.getPartialStorage = memoize(([symbol, strategyName, exchangeName]) => `${symbol}:${strategyName}:${exchangeName}`, (symbol, strategyName, exchangeName) => Reflect.construct(this.PersistPartialFactory, [
|
|
1623
|
-
`${symbol}_${strategyName}_${exchangeName}`,
|
|
1624
|
-
`./dump/data/partial/`,
|
|
1625
|
-
]));
|
|
1626
1888
|
/**
|
|
1627
|
-
*
|
|
1628
|
-
*
|
|
1629
|
-
|
|
1630
|
-
|
|
1889
|
+
* Constructor used to create per-context partial data instances.
|
|
1890
|
+
* Replaceable via usePersistPartialAdapter() / useJson() / useDummy().
|
|
1891
|
+
*/
|
|
1892
|
+
this.PersistPartialInstanceCtor = PersistPartialInstance;
|
|
1893
|
+
/**
|
|
1894
|
+
* Memoized factory creating one IPersistPartialInstance per (symbol, strategy, exchange) triple.
|
|
1895
|
+
* Each signal's partial data is stored under its own signalId within the instance.
|
|
1896
|
+
*/
|
|
1897
|
+
this.getPartialStorage = memoize(([symbol, strategyName, exchangeName]) => `${symbol}:${strategyName}:${exchangeName}`, (symbol, strategyName, exchangeName) => Reflect.construct(this.PersistPartialInstanceCtor, [symbol, strategyName, exchangeName]));
|
|
1898
|
+
/**
|
|
1899
|
+
* Reads partial data for the given context and signalId.
|
|
1900
|
+
* Lazily initializes the instance on first access.
|
|
1631
1901
|
*
|
|
1632
1902
|
* @param symbol - Trading pair symbol
|
|
1633
1903
|
* @param strategyName - Strategy identifier
|
|
1634
1904
|
* @param signalId - Signal identifier
|
|
1635
1905
|
* @param exchangeName - Exchange identifier
|
|
1636
|
-
* @returns Promise resolving to partial data record
|
|
1906
|
+
* @returns Promise resolving to partial data record (empty object if none)
|
|
1637
1907
|
*/
|
|
1638
1908
|
this.readPartialData = async (symbol, strategyName, signalId, exchangeName) => {
|
|
1639
1909
|
LOGGER_SERVICE$7.info(PERSIST_PARTIAL_UTILS_METHOD_NAME_READ_DATA);
|
|
1640
1910
|
const key = `${symbol}:${strategyName}:${exchangeName}`;
|
|
1641
1911
|
const isInitial = !this.getPartialStorage.has(key);
|
|
1642
|
-
const
|
|
1643
|
-
await
|
|
1644
|
-
|
|
1645
|
-
return await stateStorage.readValue(signalId);
|
|
1646
|
-
}
|
|
1647
|
-
return {};
|
|
1912
|
+
const instance = this.getPartialStorage(symbol, strategyName, exchangeName);
|
|
1913
|
+
await instance.waitForInit(isInitial);
|
|
1914
|
+
return instance.readPartialData(signalId);
|
|
1648
1915
|
};
|
|
1649
1916
|
/**
|
|
1650
|
-
* Writes partial data
|
|
1917
|
+
* Writes partial data for the given context and signalId.
|
|
1918
|
+
* Lazily initializes the instance on first access.
|
|
1651
1919
|
*
|
|
1652
|
-
*
|
|
1653
|
-
* Uses atomic writes to prevent corruption on crashes.
|
|
1654
|
-
*
|
|
1655
|
-
* @param partialData - Record of signal IDs to partial data
|
|
1920
|
+
* @param partialData - Partial data record to persist
|
|
1656
1921
|
* @param symbol - Trading pair symbol
|
|
1657
1922
|
* @param strategyName - Strategy identifier
|
|
1658
1923
|
* @param signalId - Signal identifier
|
|
@@ -1663,53 +1928,43 @@ class PersistPartialUtils {
|
|
|
1663
1928
|
LOGGER_SERVICE$7.info(PERSIST_PARTIAL_UTILS_METHOD_NAME_WRITE_DATA);
|
|
1664
1929
|
const key = `${symbol}:${strategyName}:${exchangeName}`;
|
|
1665
1930
|
const isInitial = !this.getPartialStorage.has(key);
|
|
1666
|
-
const
|
|
1667
|
-
await
|
|
1668
|
-
|
|
1931
|
+
const instance = this.getPartialStorage(symbol, strategyName, exchangeName);
|
|
1932
|
+
await instance.waitForInit(isInitial);
|
|
1933
|
+
return instance.writePartialData(partialData, signalId);
|
|
1669
1934
|
};
|
|
1670
1935
|
}
|
|
1671
1936
|
/**
|
|
1672
|
-
* Registers a custom
|
|
1937
|
+
* Registers a custom IPersistPartialInstance constructor.
|
|
1938
|
+
* Clears the memoization cache so subsequent calls use the new adapter.
|
|
1673
1939
|
*
|
|
1674
|
-
* @param Ctor - Custom
|
|
1675
|
-
*
|
|
1676
|
-
* @example
|
|
1677
|
-
* ```typescript
|
|
1678
|
-
* class RedisPersist extends PersistBase {
|
|
1679
|
-
* async readValue(id) { return JSON.parse(await redis.get(id)); }
|
|
1680
|
-
* async writeValue(id, entity) { await redis.set(id, JSON.stringify(entity)); }
|
|
1681
|
-
* }
|
|
1682
|
-
* PersistPartialAdapter.usePersistPartialAdapter(RedisPersist);
|
|
1683
|
-
* ```
|
|
1940
|
+
* @param Ctor - Custom IPersistPartialInstance constructor
|
|
1684
1941
|
*/
|
|
1685
1942
|
usePersistPartialAdapter(Ctor) {
|
|
1686
1943
|
LOGGER_SERVICE$7.info(PERSIST_PARTIAL_UTILS_METHOD_NAME_USE_PERSIST_PARTIAL_ADAPTER);
|
|
1687
|
-
this.
|
|
1944
|
+
this.PersistPartialInstanceCtor = Ctor;
|
|
1945
|
+
this.getPartialStorage.clear();
|
|
1688
1946
|
}
|
|
1689
1947
|
/**
|
|
1690
|
-
* Clears the memoized
|
|
1691
|
-
* Call
|
|
1692
|
-
* so new storage instances are created with the updated base path.
|
|
1948
|
+
* Clears the memoized instance cache.
|
|
1949
|
+
* Call when process.cwd() changes between strategy iterations.
|
|
1693
1950
|
*/
|
|
1694
1951
|
clear() {
|
|
1695
1952
|
LOGGER_SERVICE$7.log(PERSIST_PARTIAL_UTILS_METHOD_NAME_CLEAR);
|
|
1696
1953
|
this.getPartialStorage.clear();
|
|
1697
1954
|
}
|
|
1698
1955
|
/**
|
|
1699
|
-
* Switches to the default
|
|
1700
|
-
* All future persistence writes will use JSON storage.
|
|
1956
|
+
* Switches to the default file-based PersistPartialInstance.
|
|
1701
1957
|
*/
|
|
1702
1958
|
useJson() {
|
|
1703
1959
|
LOGGER_SERVICE$7.log(PERSIST_PARTIAL_UTILS_METHOD_NAME_USE_JSON);
|
|
1704
|
-
this.usePersistPartialAdapter(
|
|
1960
|
+
this.usePersistPartialAdapter(PersistPartialInstance);
|
|
1705
1961
|
}
|
|
1706
1962
|
/**
|
|
1707
|
-
* Switches to
|
|
1708
|
-
* All future persistence writes will be no-ops.
|
|
1963
|
+
* Switches to PersistPartialDummyInstance (all operations are no-ops).
|
|
1709
1964
|
*/
|
|
1710
1965
|
useDummy() {
|
|
1711
1966
|
LOGGER_SERVICE$7.log(PERSIST_PARTIAL_UTILS_METHOD_NAME_USE_DUMMY);
|
|
1712
|
-
this.usePersistPartialAdapter(
|
|
1967
|
+
this.usePersistPartialAdapter(PersistPartialDummyInstance);
|
|
1713
1968
|
}
|
|
1714
1969
|
}
|
|
1715
1970
|
/**
|
|
@@ -1729,11 +1984,99 @@ class PersistPartialUtils {
|
|
|
1729
1984
|
* ```
|
|
1730
1985
|
*/
|
|
1731
1986
|
const PersistPartialAdapter = new PersistPartialUtils();
|
|
1987
|
+
/**
|
|
1988
|
+
* Default file-based implementation of IPersistBreakevenInstance.
|
|
1989
|
+
*
|
|
1990
|
+
* Features:
|
|
1991
|
+
* - Wraps PersistBase for atomic JSON writes
|
|
1992
|
+
* - Uses signalId as entity ID within a per-context PersistBase
|
|
1993
|
+
* - Crash-safe via atomic writes
|
|
1994
|
+
*
|
|
1995
|
+
* @example
|
|
1996
|
+
* ```typescript
|
|
1997
|
+
* const instance = new PersistBreakevenInstance("BTCUSDT", "my-strategy", "binance");
|
|
1998
|
+
* await instance.waitForInit(true);
|
|
1999
|
+
* await instance.writeBreakevenData(breakevenData, "signal-id-1");
|
|
2000
|
+
* const restored = await instance.readBreakevenData("signal-id-1");
|
|
2001
|
+
* ```
|
|
2002
|
+
*/
|
|
2003
|
+
class PersistBreakevenInstance {
|
|
2004
|
+
/**
|
|
2005
|
+
* Creates new breakeven persistence instance.
|
|
2006
|
+
*
|
|
2007
|
+
* @param symbol - Trading pair symbol
|
|
2008
|
+
* @param strategyName - Strategy identifier
|
|
2009
|
+
* @param exchangeName - Exchange identifier
|
|
2010
|
+
*/
|
|
2011
|
+
constructor(symbol, strategyName, exchangeName) {
|
|
2012
|
+
this.symbol = symbol;
|
|
2013
|
+
this.strategyName = strategyName;
|
|
2014
|
+
this.exchangeName = exchangeName;
|
|
2015
|
+
this._storage = new PersistBase(`${symbol}_${strategyName}_${exchangeName}`, `./dump/data/breakeven/`);
|
|
2016
|
+
}
|
|
2017
|
+
/**
|
|
2018
|
+
* Initializes the underlying PersistBase storage.
|
|
2019
|
+
*
|
|
2020
|
+
* @param initial - Whether this is the first initialization
|
|
2021
|
+
* @returns Promise that resolves when initialization is complete
|
|
2022
|
+
*/
|
|
2023
|
+
async waitForInit(initial) {
|
|
2024
|
+
await this._storage.waitForInit(initial);
|
|
2025
|
+
}
|
|
2026
|
+
/**
|
|
2027
|
+
* Reads the breakeven data for the given signal using `signalId` as the entity key.
|
|
2028
|
+
*
|
|
2029
|
+
* @param signalId - Signal identifier
|
|
2030
|
+
* @returns Promise resolving to breakeven data record (empty object if not found)
|
|
2031
|
+
*/
|
|
2032
|
+
async readBreakevenData(signalId) {
|
|
2033
|
+
if (await this._storage.hasValue(signalId)) {
|
|
2034
|
+
return await this._storage.readValue(signalId);
|
|
2035
|
+
}
|
|
2036
|
+
return {};
|
|
2037
|
+
}
|
|
2038
|
+
/**
|
|
2039
|
+
* Writes the breakeven data for the given signal using `signalId` as the entity key.
|
|
2040
|
+
*
|
|
2041
|
+
* @param data - Breakeven data record to persist
|
|
2042
|
+
* @param signalId - Signal identifier
|
|
2043
|
+
* @returns Promise that resolves when write is complete
|
|
2044
|
+
*/
|
|
2045
|
+
async writeBreakevenData(data, signalId) {
|
|
2046
|
+
await this._storage.writeValue(signalId, data);
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
/**
|
|
2050
|
+
* No-op IPersistBreakevenInstance implementation used by PersistBreakevenUtils.useDummy().
|
|
2051
|
+
* All reads return empty object, all writes are discarded.
|
|
2052
|
+
*/
|
|
2053
|
+
class PersistBreakevenDummyInstance {
|
|
2054
|
+
/**
|
|
2055
|
+
* No-op constructor.
|
|
2056
|
+
* Context arguments are accepted to satisfy TPersistBreakevenInstanceCtor.
|
|
2057
|
+
*/
|
|
2058
|
+
constructor(_symbol, _strategyName, _exchangeName) { }
|
|
2059
|
+
/**
|
|
2060
|
+
* No-op initialization.
|
|
2061
|
+
* @returns Promise that resolves immediately
|
|
2062
|
+
*/
|
|
2063
|
+
async waitForInit(_initial) { }
|
|
2064
|
+
/**
|
|
2065
|
+
* Always returns empty breakeven data record.
|
|
2066
|
+
* @returns Promise resolving to {}
|
|
2067
|
+
*/
|
|
2068
|
+
async readBreakevenData(_signalId) { return {}; }
|
|
2069
|
+
/**
|
|
2070
|
+
* No-op write (discards breakeven data).
|
|
2071
|
+
* @returns Promise that resolves immediately
|
|
2072
|
+
*/
|
|
2073
|
+
async writeBreakevenData(_data, _signalId) { }
|
|
2074
|
+
}
|
|
1732
2075
|
/**
|
|
1733
2076
|
* Persistence utility class for breakeven state management.
|
|
1734
2077
|
*
|
|
1735
2078
|
* Handles reading and writing breakeven state to disk.
|
|
1736
|
-
* Uses memoized
|
|
2079
|
+
* Uses memoized PersistBreakevenInstance instances per symbol-strategy pair.
|
|
1737
2080
|
*
|
|
1738
2081
|
* Features:
|
|
1739
2082
|
* - Atomic file writes via PersistBase.writeValue()
|
|
@@ -1763,53 +2106,36 @@ const PersistPartialAdapter = new PersistPartialUtils();
|
|
|
1763
2106
|
class PersistBreakevenUtils {
|
|
1764
2107
|
constructor() {
|
|
1765
2108
|
/**
|
|
1766
|
-
*
|
|
1767
|
-
*
|
|
2109
|
+
* Constructor used to create per-context breakeven instances.
|
|
2110
|
+
* Replaceable via usePersistBreakevenAdapter() / useJson() / useDummy().
|
|
1768
2111
|
*/
|
|
1769
|
-
this.
|
|
2112
|
+
this.PersistBreakevenInstanceCtor = PersistBreakevenInstance;
|
|
1770
2113
|
/**
|
|
1771
|
-
* Memoized
|
|
1772
|
-
*
|
|
1773
|
-
* Key format: "symbol:strategyName:exchangeName"
|
|
1774
|
-
*
|
|
1775
|
-
* @param symbol - Trading pair symbol
|
|
1776
|
-
* @param strategyName - Strategy identifier
|
|
1777
|
-
* @param exchangeName - Exchange identifier
|
|
1778
|
-
* @returns PersistBase instance for this symbol-strategy-exchange combination
|
|
2114
|
+
* Memoized factory creating one IPersistBreakevenInstance per (symbol, strategy, exchange) triple.
|
|
2115
|
+
* Each signal's breakeven data is stored under its own signalId within the instance.
|
|
1779
2116
|
*/
|
|
1780
|
-
this.getBreakevenStorage = memoize(([symbol, strategyName, exchangeName]) => `${symbol}:${strategyName}:${exchangeName}`, (symbol, strategyName, exchangeName) => Reflect.construct(this.
|
|
1781
|
-
`${symbol}_${strategyName}_${exchangeName}`,
|
|
1782
|
-
`./dump/data/breakeven/`,
|
|
1783
|
-
]));
|
|
2117
|
+
this.getBreakevenStorage = memoize(([symbol, strategyName, exchangeName]) => `${symbol}:${strategyName}:${exchangeName}`, (symbol, strategyName, exchangeName) => Reflect.construct(this.PersistBreakevenInstanceCtor, [symbol, strategyName, exchangeName]));
|
|
1784
2118
|
/**
|
|
1785
|
-
* Reads
|
|
1786
|
-
*
|
|
1787
|
-
* Called by ClientBreakeven.waitForInit() to restore state.
|
|
1788
|
-
* Returns empty object if no breakeven data exists.
|
|
2119
|
+
* Reads breakeven data for the given context and signalId.
|
|
2120
|
+
* Lazily initializes the instance on first access.
|
|
1789
2121
|
*
|
|
1790
2122
|
* @param symbol - Trading pair symbol
|
|
1791
2123
|
* @param strategyName - Strategy identifier
|
|
1792
2124
|
* @param signalId - Signal identifier
|
|
1793
2125
|
* @param exchangeName - Exchange identifier
|
|
1794
|
-
* @returns Promise resolving to breakeven data record
|
|
2126
|
+
* @returns Promise resolving to breakeven data record (empty object if none)
|
|
1795
2127
|
*/
|
|
1796
2128
|
this.readBreakevenData = async (symbol, strategyName, signalId, exchangeName) => {
|
|
1797
2129
|
LOGGER_SERVICE$7.info(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_READ_DATA);
|
|
1798
2130
|
const key = `${symbol}:${strategyName}:${exchangeName}`;
|
|
1799
2131
|
const isInitial = !this.getBreakevenStorage.has(key);
|
|
1800
|
-
const
|
|
1801
|
-
await
|
|
1802
|
-
|
|
1803
|
-
return await stateStorage.readValue(signalId);
|
|
1804
|
-
}
|
|
1805
|
-
return {};
|
|
2132
|
+
const instance = this.getBreakevenStorage(symbol, strategyName, exchangeName);
|
|
2133
|
+
await instance.waitForInit(isInitial);
|
|
2134
|
+
return instance.readBreakevenData(signalId);
|
|
1806
2135
|
};
|
|
1807
2136
|
/**
|
|
1808
|
-
* Writes breakeven data
|
|
1809
|
-
*
|
|
1810
|
-
* Called by ClientBreakeven._persistState() after state changes.
|
|
1811
|
-
* Creates directory and file if they don't exist.
|
|
1812
|
-
* Uses atomic writes to prevent data corruption.
|
|
2137
|
+
* Writes breakeven data for the given context and signalId.
|
|
2138
|
+
* Lazily initializes the instance on first access.
|
|
1813
2139
|
*
|
|
1814
2140
|
* @param breakevenData - Breakeven data record to persist
|
|
1815
2141
|
* @param symbol - Trading pair symbol
|
|
@@ -1822,53 +2148,43 @@ class PersistBreakevenUtils {
|
|
|
1822
2148
|
LOGGER_SERVICE$7.info(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_WRITE_DATA);
|
|
1823
2149
|
const key = `${symbol}:${strategyName}:${exchangeName}`;
|
|
1824
2150
|
const isInitial = !this.getBreakevenStorage.has(key);
|
|
1825
|
-
const
|
|
1826
|
-
await
|
|
1827
|
-
|
|
2151
|
+
const instance = this.getBreakevenStorage(symbol, strategyName, exchangeName);
|
|
2152
|
+
await instance.waitForInit(isInitial);
|
|
2153
|
+
return instance.writeBreakevenData(breakevenData, signalId);
|
|
1828
2154
|
};
|
|
1829
2155
|
}
|
|
1830
2156
|
/**
|
|
1831
|
-
* Registers a custom
|
|
1832
|
-
*
|
|
1833
|
-
* @param Ctor - Custom PersistBase constructor
|
|
2157
|
+
* Registers a custom IPersistBreakevenInstance constructor.
|
|
2158
|
+
* Clears the memoization cache so subsequent calls use the new adapter.
|
|
1834
2159
|
*
|
|
1835
|
-
* @
|
|
1836
|
-
* ```typescript
|
|
1837
|
-
* class RedisPersist extends PersistBase {
|
|
1838
|
-
* async readValue(id) { return JSON.parse(await redis.get(id)); }
|
|
1839
|
-
* async writeValue(id, entity) { await redis.set(id, JSON.stringify(entity)); }
|
|
1840
|
-
* }
|
|
1841
|
-
* PersistBreakevenAdapter.usePersistBreakevenAdapter(RedisPersist);
|
|
1842
|
-
* ```
|
|
2160
|
+
* @param Ctor - Custom IPersistBreakevenInstance constructor
|
|
1843
2161
|
*/
|
|
1844
2162
|
usePersistBreakevenAdapter(Ctor) {
|
|
1845
2163
|
LOGGER_SERVICE$7.info(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_USE_PERSIST_BREAKEVEN_ADAPTER);
|
|
1846
|
-
this.
|
|
2164
|
+
this.PersistBreakevenInstanceCtor = Ctor;
|
|
2165
|
+
this.getBreakevenStorage.clear();
|
|
1847
2166
|
}
|
|
1848
2167
|
/**
|
|
1849
|
-
* Clears the memoized
|
|
1850
|
-
* Call
|
|
1851
|
-
* so new storage instances are created with the updated base path.
|
|
2168
|
+
* Clears the memoized instance cache.
|
|
2169
|
+
* Call when process.cwd() changes between strategy iterations.
|
|
1852
2170
|
*/
|
|
1853
2171
|
clear() {
|
|
1854
2172
|
LOGGER_SERVICE$7.log(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_CLEAR);
|
|
1855
2173
|
this.getBreakevenStorage.clear();
|
|
1856
2174
|
}
|
|
1857
2175
|
/**
|
|
1858
|
-
* Switches to the default
|
|
1859
|
-
* All future persistence writes will use JSON storage.
|
|
2176
|
+
* Switches to the default file-based PersistBreakevenInstance.
|
|
1860
2177
|
*/
|
|
1861
2178
|
useJson() {
|
|
1862
2179
|
LOGGER_SERVICE$7.log(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_USE_JSON);
|
|
1863
|
-
this.usePersistBreakevenAdapter(
|
|
2180
|
+
this.usePersistBreakevenAdapter(PersistBreakevenInstance);
|
|
1864
2181
|
}
|
|
1865
2182
|
/**
|
|
1866
|
-
* Switches to
|
|
1867
|
-
* All future persistence writes will be no-ops.
|
|
2183
|
+
* Switches to PersistBreakevenDummyInstance (all operations are no-ops).
|
|
1868
2184
|
*/
|
|
1869
2185
|
useDummy() {
|
|
1870
2186
|
LOGGER_SERVICE$7.log(PERSIST_BREAKEVEN_UTILS_METHOD_NAME_USE_DUMMY);
|
|
1871
|
-
this.usePersistBreakevenAdapter(
|
|
2187
|
+
this.usePersistBreakevenAdapter(PersistBreakevenDummyInstance);
|
|
1872
2188
|
}
|
|
1873
2189
|
}
|
|
1874
2190
|
/**
|
|
@@ -1888,6 +2204,140 @@ class PersistBreakevenUtils {
|
|
|
1888
2204
|
* ```
|
|
1889
2205
|
*/
|
|
1890
2206
|
const PersistBreakevenAdapter = new PersistBreakevenUtils();
|
|
2207
|
+
/**
|
|
2208
|
+
* Default file-based implementation of IPersistCandleInstance.
|
|
2209
|
+
*
|
|
2210
|
+
* Features:
|
|
2211
|
+
* - Each candle stored as a separate JSON file keyed by its timestamp
|
|
2212
|
+
* - Read returns null on any missing timestamp (cache miss → refetch)
|
|
2213
|
+
* - Write skips incomplete candles (closeTime > now) and existing keys
|
|
2214
|
+
* - Invalid cached candles emit warnings via errorEmitter and treated as miss
|
|
2215
|
+
*
|
|
2216
|
+
* @example
|
|
2217
|
+
* ```typescript
|
|
2218
|
+
* const instance = new PersistCandleInstance("BTCUSDT", "1m", "binance");
|
|
2219
|
+
* await instance.waitForInit(true);
|
|
2220
|
+
* await instance.writeCandlesData(candles);
|
|
2221
|
+
* const cached = await instance.readCandlesData(100, since, until);
|
|
2222
|
+
* ```
|
|
2223
|
+
*/
|
|
2224
|
+
class PersistCandleInstance {
|
|
2225
|
+
/**
|
|
2226
|
+
* Creates new candle cache persistence instance.
|
|
2227
|
+
*
|
|
2228
|
+
* @param symbol - Trading pair symbol
|
|
2229
|
+
* @param interval - Candle interval (1m, 5m, 1h, etc.)
|
|
2230
|
+
* @param exchangeName - Exchange identifier
|
|
2231
|
+
*/
|
|
2232
|
+
constructor(symbol, interval, exchangeName) {
|
|
2233
|
+
this.symbol = symbol;
|
|
2234
|
+
this.interval = interval;
|
|
2235
|
+
this.exchangeName = exchangeName;
|
|
2236
|
+
this._storage = new PersistBase(`${exchangeName}/${symbol}/${interval}`, `./dump/data/candle/`);
|
|
2237
|
+
}
|
|
2238
|
+
/**
|
|
2239
|
+
* Initializes the underlying PersistBase storage.
|
|
2240
|
+
*
|
|
2241
|
+
* @param initial - Whether this is the first initialization
|
|
2242
|
+
* @returns Promise that resolves when initialization is complete
|
|
2243
|
+
*/
|
|
2244
|
+
async waitForInit(initial) {
|
|
2245
|
+
await this._storage.waitForInit(initial);
|
|
2246
|
+
}
|
|
2247
|
+
/**
|
|
2248
|
+
* Reads cached candles for the requested window.
|
|
2249
|
+
* Computes expected timestamps (sinceTimestamp + i * stepMs) and reads each
|
|
2250
|
+
* by timestamp key. Returns null on ANY missing timestamp (cache miss).
|
|
2251
|
+
* Invalid cached candles emit a warning via errorEmitter and are treated as miss.
|
|
2252
|
+
*
|
|
2253
|
+
* @param limit - Number of candles requested
|
|
2254
|
+
* @param sinceTimestamp - Aligned start timestamp (openTime of first candle)
|
|
2255
|
+
* @param _untilTimestamp - Reserved for API compatibility, unused
|
|
2256
|
+
* @returns Promise resolving to candles in order, or null on cache miss
|
|
2257
|
+
*/
|
|
2258
|
+
async readCandlesData(limit, sinceTimestamp, _untilTimestamp) {
|
|
2259
|
+
const stepMs = INTERVAL_MINUTES$9[this.interval] * MS_PER_MINUTE$7;
|
|
2260
|
+
const cachedCandles = [];
|
|
2261
|
+
for (let i = 0; i < limit; i++) {
|
|
2262
|
+
const expectedTimestamp = sinceTimestamp + i * stepMs;
|
|
2263
|
+
const timestampKey = String(expectedTimestamp);
|
|
2264
|
+
if (await not(this._storage.hasValue(timestampKey))) {
|
|
2265
|
+
return null;
|
|
2266
|
+
}
|
|
2267
|
+
try {
|
|
2268
|
+
const candle = await this._storage.readValue(timestampKey);
|
|
2269
|
+
cachedCandles.push(candle);
|
|
2270
|
+
}
|
|
2271
|
+
catch (error) {
|
|
2272
|
+
const message = `PersistCandleInstance.readCandlesData found invalid candle symbol=${this.symbol} interval=${this.interval} timestamp=${expectedTimestamp}`;
|
|
2273
|
+
const payload = {
|
|
2274
|
+
error: errorData(error),
|
|
2275
|
+
message: getErrorMessage(error),
|
|
2276
|
+
};
|
|
2277
|
+
LOGGER_SERVICE$7.warn(message, payload);
|
|
2278
|
+
console.warn(message, payload);
|
|
2279
|
+
errorEmitter.next(error);
|
|
2280
|
+
return null;
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
return cachedCandles;
|
|
2284
|
+
}
|
|
2285
|
+
/**
|
|
2286
|
+
* Writes candles to cache.
|
|
2287
|
+
* Skips incomplete candles (closeTime > now) and existing keys to keep
|
|
2288
|
+
* the cache append-only for fully closed candles.
|
|
2289
|
+
*
|
|
2290
|
+
* @param candles - Array of candle data to cache
|
|
2291
|
+
* @returns Promise that resolves when all writes are complete
|
|
2292
|
+
*/
|
|
2293
|
+
async writeCandlesData(candles) {
|
|
2294
|
+
const stepMs = INTERVAL_MINUTES$9[this.interval] * MS_PER_MINUTE$7;
|
|
2295
|
+
const now = Date.now();
|
|
2296
|
+
for (const candle of candles) {
|
|
2297
|
+
const candleCloseTime = candle.timestamp + stepMs;
|
|
2298
|
+
if (candleCloseTime > now) {
|
|
2299
|
+
LOGGER_SERVICE$7.debug("PersistCandleInstance.writeCandlesData: skipping incomplete candle", {
|
|
2300
|
+
symbol: this.symbol,
|
|
2301
|
+
interval: this.interval,
|
|
2302
|
+
exchangeName: this.exchangeName,
|
|
2303
|
+
timestamp: candle.timestamp,
|
|
2304
|
+
closeTime: candleCloseTime,
|
|
2305
|
+
now,
|
|
2306
|
+
});
|
|
2307
|
+
continue;
|
|
2308
|
+
}
|
|
2309
|
+
if (await not(this._storage.hasValue(String(candle.timestamp)))) {
|
|
2310
|
+
await this._storage.writeValue(String(candle.timestamp), candle);
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
/**
|
|
2316
|
+
* No-op IPersistCandleInstance implementation used by PersistCandleUtils.useDummy().
|
|
2317
|
+
* Always returns null on read (forces refetch), discards writes.
|
|
2318
|
+
*/
|
|
2319
|
+
class PersistCandleDummyInstance {
|
|
2320
|
+
/**
|
|
2321
|
+
* No-op constructor.
|
|
2322
|
+
* Context arguments are accepted to satisfy TPersistCandleInstanceCtor.
|
|
2323
|
+
*/
|
|
2324
|
+
constructor(_symbol, _interval, _exchangeName) { }
|
|
2325
|
+
/**
|
|
2326
|
+
* No-op initialization.
|
|
2327
|
+
* @returns Promise that resolves immediately
|
|
2328
|
+
*/
|
|
2329
|
+
async waitForInit(_initial) { }
|
|
2330
|
+
/**
|
|
2331
|
+
* Always returns null (forces refetch via cache miss).
|
|
2332
|
+
* @returns Promise resolving to null
|
|
2333
|
+
*/
|
|
2334
|
+
async readCandlesData(_limit, _since, _until) { return null; }
|
|
2335
|
+
/**
|
|
2336
|
+
* No-op write (discards candles).
|
|
2337
|
+
* @returns Promise that resolves immediately
|
|
2338
|
+
*/
|
|
2339
|
+
async writeCandlesData(_candles) { }
|
|
2340
|
+
}
|
|
1891
2341
|
/**
|
|
1892
2342
|
* Utility class for managing candles cache persistence.
|
|
1893
2343
|
*
|
|
@@ -1901,30 +2351,28 @@ const PersistBreakevenAdapter = new PersistBreakevenUtils();
|
|
|
1901
2351
|
*/
|
|
1902
2352
|
class PersistCandleUtils {
|
|
1903
2353
|
constructor() {
|
|
1904
|
-
this.PersistCandlesFactory = PersistBase;
|
|
1905
|
-
this.getCandlesStorage = memoize(([symbol, interval, exchangeName]) => `${symbol}:${interval}:${exchangeName}`, (symbol, interval, exchangeName) => Reflect.construct(this.PersistCandlesFactory, [
|
|
1906
|
-
`${exchangeName}/${symbol}/${interval}`,
|
|
1907
|
-
`./dump/data/candle/`,
|
|
1908
|
-
]));
|
|
1909
2354
|
/**
|
|
1910
|
-
*
|
|
1911
|
-
*
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
*
|
|
1916
|
-
|
|
1917
|
-
|
|
2355
|
+
* Constructor used to create per-context candle cache instances.
|
|
2356
|
+
* Replaceable via usePersistCandleAdapter() / useJson() / useDummy().
|
|
2357
|
+
*/
|
|
2358
|
+
this.PersistCandleInstanceCtor = PersistCandleInstance;
|
|
2359
|
+
/**
|
|
2360
|
+
* Memoized factory creating one IPersistCandleInstance per (symbol, interval, exchange) triple.
|
|
2361
|
+
*/
|
|
2362
|
+
this.getCandlesStorage = memoize(([symbol, interval, exchangeName]) => `${symbol}:${interval}:${exchangeName}`, (symbol, interval, exchangeName) => Reflect.construct(this.PersistCandleInstanceCtor, [symbol, interval, exchangeName]));
|
|
2363
|
+
/**
|
|
2364
|
+
* Reads cached candles for the given context and time window.
|
|
2365
|
+
* Lazily initializes the instance on first access.
|
|
1918
2366
|
*
|
|
1919
2367
|
* @param symbol - Trading pair symbol
|
|
1920
2368
|
* @param interval - Candle interval
|
|
1921
2369
|
* @param exchangeName - Exchange identifier
|
|
1922
2370
|
* @param limit - Number of candles requested
|
|
1923
2371
|
* @param sinceTimestamp - Aligned start timestamp (openTime of first candle)
|
|
1924
|
-
* @param
|
|
1925
|
-
* @returns Promise resolving to
|
|
2372
|
+
* @param untilTimestamp - Reserved for API compatibility
|
|
2373
|
+
* @returns Promise resolving to candles in order, or null on cache miss
|
|
1926
2374
|
*/
|
|
1927
|
-
this.readCandlesData = async (symbol, interval, exchangeName, limit, sinceTimestamp,
|
|
2375
|
+
this.readCandlesData = async (symbol, interval, exchangeName, limit, sinceTimestamp, untilTimestamp) => {
|
|
1928
2376
|
LOGGER_SERVICE$7.info("PersistCandleUtils.readCandlesData", {
|
|
1929
2377
|
symbol,
|
|
1930
2378
|
interval,
|
|
@@ -1934,47 +2382,15 @@ class PersistCandleUtils {
|
|
|
1934
2382
|
});
|
|
1935
2383
|
const key = `${symbol}:${interval}:${exchangeName}`;
|
|
1936
2384
|
const isInitial = !this.getCandlesStorage.has(key);
|
|
1937
|
-
const
|
|
1938
|
-
await
|
|
1939
|
-
|
|
1940
|
-
// Calculate expected timestamps and fetch each candle directly
|
|
1941
|
-
const cachedCandles = [];
|
|
1942
|
-
for (let i = 0; i < limit; i++) {
|
|
1943
|
-
const expectedTimestamp = sinceTimestamp + i * stepMs;
|
|
1944
|
-
const timestampKey = String(expectedTimestamp);
|
|
1945
|
-
if (await not(stateStorage.hasValue(timestampKey))) {
|
|
1946
|
-
// Cache miss - candle not found
|
|
1947
|
-
return null;
|
|
1948
|
-
}
|
|
1949
|
-
try {
|
|
1950
|
-
const candle = await stateStorage.readValue(timestampKey);
|
|
1951
|
-
cachedCandles.push(candle);
|
|
1952
|
-
}
|
|
1953
|
-
catch (error) {
|
|
1954
|
-
// Invalid candle in cache - treat as cache miss
|
|
1955
|
-
const message = `PersistCandleUtils.readCandlesData found invalid candle symbol=${symbol} interval=${interval} timestamp=${expectedTimestamp}`;
|
|
1956
|
-
const payload = {
|
|
1957
|
-
error: errorData(error),
|
|
1958
|
-
message: getErrorMessage(error),
|
|
1959
|
-
};
|
|
1960
|
-
LOGGER_SERVICE$7.warn(message, payload);
|
|
1961
|
-
console.warn(message, payload);
|
|
1962
|
-
errorEmitter.next(error);
|
|
1963
|
-
return null;
|
|
1964
|
-
}
|
|
1965
|
-
}
|
|
1966
|
-
return cachedCandles;
|
|
2385
|
+
const instance = this.getCandlesStorage(symbol, interval, exchangeName);
|
|
2386
|
+
await instance.waitForInit(isInitial);
|
|
2387
|
+
return instance.readCandlesData(limit, sinceTimestamp, untilTimestamp);
|
|
1967
2388
|
};
|
|
1968
2389
|
/**
|
|
1969
|
-
* Writes candles to cache
|
|
1970
|
-
*
|
|
2390
|
+
* Writes candles to cache for the given context.
|
|
2391
|
+
* Lazily initializes the instance on first access.
|
|
1971
2392
|
*
|
|
1972
|
-
*
|
|
1973
|
-
* - First candle.timestamp equals aligned sinceTimestamp (openTime)
|
|
1974
|
-
* - Exact number of candles as requested
|
|
1975
|
-
* - All candles are fully closed (timestamp + stepMs < untilTimestamp)
|
|
1976
|
-
*
|
|
1977
|
-
* @param candles - Array of candle data to cache (validated by the caller)
|
|
2393
|
+
* @param candles - Array of candle data to cache
|
|
1978
2394
|
* @param symbol - Trading pair symbol
|
|
1979
2395
|
* @param interval - Candle interval
|
|
1980
2396
|
* @param exchangeName - Exchange identifier
|
|
@@ -1989,66 +2405,43 @@ class PersistCandleUtils {
|
|
|
1989
2405
|
});
|
|
1990
2406
|
const key = `${symbol}:${interval}:${exchangeName}`;
|
|
1991
2407
|
const isInitial = !this.getCandlesStorage.has(key);
|
|
1992
|
-
const
|
|
1993
|
-
await
|
|
1994
|
-
|
|
1995
|
-
const stepMs = INTERVAL_MINUTES$9[interval] * MS_PER_MINUTE$7;
|
|
1996
|
-
const now = Date.now();
|
|
1997
|
-
// Write each candle as a separate file, skipping incomplete candles
|
|
1998
|
-
for (const candle of candles) {
|
|
1999
|
-
// Skip incomplete candles: candle is complete when closeTime <= now
|
|
2000
|
-
// closeTime = timestamp + stepMs
|
|
2001
|
-
const candleCloseTime = candle.timestamp + stepMs;
|
|
2002
|
-
if (candleCloseTime > now) {
|
|
2003
|
-
LOGGER_SERVICE$7.debug("PersistCandleUtils.writeCandlesData: skipping incomplete candle", {
|
|
2004
|
-
symbol,
|
|
2005
|
-
interval,
|
|
2006
|
-
exchangeName,
|
|
2007
|
-
timestamp: candle.timestamp,
|
|
2008
|
-
closeTime: candleCloseTime,
|
|
2009
|
-
now,
|
|
2010
|
-
});
|
|
2011
|
-
continue;
|
|
2012
|
-
}
|
|
2013
|
-
if (await not(stateStorage.hasValue(String(candle.timestamp)))) {
|
|
2014
|
-
await stateStorage.writeValue(String(candle.timestamp), candle);
|
|
2015
|
-
}
|
|
2016
|
-
}
|
|
2408
|
+
const instance = this.getCandlesStorage(symbol, interval, exchangeName);
|
|
2409
|
+
await instance.waitForInit(isInitial);
|
|
2410
|
+
return instance.writeCandlesData(candles);
|
|
2017
2411
|
};
|
|
2018
2412
|
}
|
|
2019
2413
|
/**
|
|
2020
|
-
* Registers a custom
|
|
2414
|
+
* Registers a custom IPersistCandleInstance constructor.
|
|
2415
|
+
* Clears the memoization cache so subsequent calls use the new adapter.
|
|
2021
2416
|
*
|
|
2022
|
-
* @param Ctor - Custom
|
|
2417
|
+
* @param Ctor - Custom IPersistCandleInstance constructor
|
|
2023
2418
|
*/
|
|
2024
2419
|
usePersistCandleAdapter(Ctor) {
|
|
2025
2420
|
LOGGER_SERVICE$7.info("PersistCandleUtils.usePersistCandleAdapter");
|
|
2026
|
-
this.
|
|
2421
|
+
this.PersistCandleInstanceCtor = Ctor;
|
|
2422
|
+
this.getCandlesStorage.clear();
|
|
2027
2423
|
}
|
|
2028
2424
|
/**
|
|
2029
|
-
* Clears the memoized
|
|
2030
|
-
* Call
|
|
2031
|
-
* so new storage instances are created with the updated base path.
|
|
2425
|
+
* Clears the memoized instance cache.
|
|
2426
|
+
* Call when process.cwd() changes between strategy iterations.
|
|
2032
2427
|
*/
|
|
2033
2428
|
clear() {
|
|
2034
2429
|
LOGGER_SERVICE$7.log(PERSIST_CANDLE_UTILS_METHOD_NAME_CLEAR);
|
|
2035
2430
|
this.getCandlesStorage.clear();
|
|
2036
2431
|
}
|
|
2037
2432
|
/**
|
|
2038
|
-
* Switches to the default
|
|
2039
|
-
* All future persistence writes will use JSON storage.
|
|
2433
|
+
* Switches to the default file-based PersistCandleInstance.
|
|
2040
2434
|
*/
|
|
2041
2435
|
useJson() {
|
|
2042
2436
|
LOGGER_SERVICE$7.log("PersistCandleUtils.useJson");
|
|
2043
|
-
this.usePersistCandleAdapter(
|
|
2437
|
+
this.usePersistCandleAdapter(PersistCandleInstance);
|
|
2044
2438
|
}
|
|
2045
2439
|
/**
|
|
2046
|
-
* Switches to
|
|
2047
|
-
* All future persistence writes will be no-ops.
|
|
2440
|
+
* Switches to PersistCandleDummyInstance (always returns null on read, discards writes).
|
|
2048
2441
|
*/
|
|
2049
2442
|
useDummy() {
|
|
2050
2443
|
LOGGER_SERVICE$7.log("PersistCandleUtils.useDummy");
|
|
2051
|
-
this.usePersistCandleAdapter(
|
|
2444
|
+
this.usePersistCandleAdapter(PersistCandleDummyInstance);
|
|
2052
2445
|
}
|
|
2053
2446
|
}
|
|
2054
2447
|
/**
|
|
@@ -2067,6 +2460,92 @@ class PersistCandleUtils {
|
|
|
2067
2460
|
* ```
|
|
2068
2461
|
*/
|
|
2069
2462
|
const PersistCandleAdapter = new PersistCandleUtils();
|
|
2463
|
+
/**
|
|
2464
|
+
* Default file-based implementation of IPersistStorageInstance.
|
|
2465
|
+
*
|
|
2466
|
+
* Features:
|
|
2467
|
+
* - Each signal stored as separate JSON file keyed by signal.id
|
|
2468
|
+
* - Read iterates all keys via PersistBase.keys()
|
|
2469
|
+
* - Crash-safe via atomic writes
|
|
2470
|
+
*
|
|
2471
|
+
* @example
|
|
2472
|
+
* ```typescript
|
|
2473
|
+
* const instance = new PersistStorageInstance(false);
|
|
2474
|
+
* await instance.waitForInit(true);
|
|
2475
|
+
* await instance.writeStorageData(signals);
|
|
2476
|
+
* const all = await instance.readStorageData();
|
|
2477
|
+
* ```
|
|
2478
|
+
*/
|
|
2479
|
+
class PersistStorageInstance {
|
|
2480
|
+
/**
|
|
2481
|
+
* Creates new signal storage persistence instance.
|
|
2482
|
+
*
|
|
2483
|
+
* @param backtest - True for backtest mode storage, false for live mode
|
|
2484
|
+
*/
|
|
2485
|
+
constructor(backtest) {
|
|
2486
|
+
this.backtest = backtest;
|
|
2487
|
+
this._storage = new PersistBase(backtest ? `backtest` : `live`, `./dump/data/storage/`);
|
|
2488
|
+
}
|
|
2489
|
+
/**
|
|
2490
|
+
* Initializes the underlying PersistBase storage.
|
|
2491
|
+
*
|
|
2492
|
+
* @param initial - Whether this is the first initialization
|
|
2493
|
+
* @returns Promise that resolves when initialization is complete
|
|
2494
|
+
*/
|
|
2495
|
+
async waitForInit(initial) {
|
|
2496
|
+
await this._storage.waitForInit(initial);
|
|
2497
|
+
}
|
|
2498
|
+
/**
|
|
2499
|
+
* Reads all persisted signals by iterating storage keys.
|
|
2500
|
+
*
|
|
2501
|
+
* @returns Promise resolving to array of signal entries
|
|
2502
|
+
*/
|
|
2503
|
+
async readStorageData() {
|
|
2504
|
+
const signals = [];
|
|
2505
|
+
for await (const signalId of this._storage.keys()) {
|
|
2506
|
+
const signal = await this._storage.readValue(signalId);
|
|
2507
|
+
signals.push(signal);
|
|
2508
|
+
}
|
|
2509
|
+
return signals;
|
|
2510
|
+
}
|
|
2511
|
+
/**
|
|
2512
|
+
* Writes each signal as a separate entity keyed by `signal.id`.
|
|
2513
|
+
*
|
|
2514
|
+
* @param signals - Signal entries to persist
|
|
2515
|
+
* @returns Promise that resolves when all writes are complete
|
|
2516
|
+
*/
|
|
2517
|
+
async writeStorageData(signals) {
|
|
2518
|
+
for (const signal of signals) {
|
|
2519
|
+
await this._storage.writeValue(signal.id, signal);
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
/**
|
|
2524
|
+
* No-op IPersistStorageInstance implementation used by PersistStorageUtils.useDummy().
|
|
2525
|
+
* All reads return empty array, all writes are discarded.
|
|
2526
|
+
*/
|
|
2527
|
+
class PersistStorageDummyInstance {
|
|
2528
|
+
/**
|
|
2529
|
+
* No-op constructor.
|
|
2530
|
+
* Context arguments are accepted to satisfy TPersistStorageInstanceCtor.
|
|
2531
|
+
*/
|
|
2532
|
+
constructor(_backtest) { }
|
|
2533
|
+
/**
|
|
2534
|
+
* No-op initialization.
|
|
2535
|
+
* @returns Promise that resolves immediately
|
|
2536
|
+
*/
|
|
2537
|
+
async waitForInit(_initial) { }
|
|
2538
|
+
/**
|
|
2539
|
+
* Always returns empty signals array.
|
|
2540
|
+
* @returns Promise resolving to []
|
|
2541
|
+
*/
|
|
2542
|
+
async readStorageData() { return []; }
|
|
2543
|
+
/**
|
|
2544
|
+
* No-op write (discards signals).
|
|
2545
|
+
* @returns Promise that resolves immediately
|
|
2546
|
+
*/
|
|
2547
|
+
async writeStorageData(_signals) { }
|
|
2548
|
+
}
|
|
2070
2549
|
/**
|
|
2071
2550
|
* Utility class for managing signal storage persistence.
|
|
2072
2551
|
*
|
|
@@ -2081,89 +2560,80 @@ const PersistCandleAdapter = new PersistCandleUtils();
|
|
|
2081
2560
|
*/
|
|
2082
2561
|
class PersistStorageUtils {
|
|
2083
2562
|
constructor() {
|
|
2084
|
-
this.PersistStorageFactory = PersistBase;
|
|
2085
|
-
this.getStorage = memoize(([backtest]) => backtest ? `backtest` : `live`, (backtest) => Reflect.construct(this.PersistStorageFactory, [
|
|
2086
|
-
backtest ? `backtest` : `live`,
|
|
2087
|
-
`./dump/data/storage/`,
|
|
2088
|
-
]));
|
|
2089
2563
|
/**
|
|
2090
|
-
*
|
|
2091
|
-
*
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2564
|
+
* Constructor used to create per-mode signal storage instances.
|
|
2565
|
+
* Replaceable via usePersistStorageAdapter() / useJson() / useDummy().
|
|
2566
|
+
*/
|
|
2567
|
+
this.PersistStorageInstanceCtor = PersistStorageInstance;
|
|
2568
|
+
/**
|
|
2569
|
+
* Memoized factory creating one IPersistStorageInstance per mode (backtest/live).
|
|
2570
|
+
* Key: "backtest" or "live".
|
|
2571
|
+
*/
|
|
2572
|
+
this.getStorage = memoize(([backtest]) => backtest ? `backtest` : `live`, (backtest) => Reflect.construct(this.PersistStorageInstanceCtor, [backtest]));
|
|
2573
|
+
/**
|
|
2574
|
+
* Reads all persisted signals for the given mode.
|
|
2575
|
+
* Lazily initializes the instance on first access.
|
|
2095
2576
|
*
|
|
2096
|
-
* @param backtest -
|
|
2577
|
+
* @param backtest - True for backtest mode storage, false for live mode
|
|
2097
2578
|
* @returns Promise resolving to array of signal entries
|
|
2098
2579
|
*/
|
|
2099
2580
|
this.readStorageData = async (backtest) => {
|
|
2100
2581
|
LOGGER_SERVICE$7.info(PERSIST_STORAGE_UTILS_METHOD_NAME_READ_DATA);
|
|
2101
2582
|
const key = backtest ? `backtest` : `live`;
|
|
2102
2583
|
const isInitial = !this.getStorage.has(key);
|
|
2103
|
-
const
|
|
2104
|
-
await
|
|
2105
|
-
|
|
2106
|
-
for await (const signalId of stateStorage.keys()) {
|
|
2107
|
-
const signal = await stateStorage.readValue(signalId);
|
|
2108
|
-
signals.push(signal);
|
|
2109
|
-
}
|
|
2110
|
-
return signals;
|
|
2584
|
+
const instance = this.getStorage(backtest);
|
|
2585
|
+
await instance.waitForInit(isInitial);
|
|
2586
|
+
return instance.readStorageData();
|
|
2111
2587
|
};
|
|
2112
2588
|
/**
|
|
2113
|
-
* Writes
|
|
2114
|
-
*
|
|
2115
|
-
* Called by StorageLiveUtils/StorageBacktestUtils after signal changes to persist state.
|
|
2116
|
-
* Uses signal.id as the storage key for individual file storage.
|
|
2117
|
-
* Uses atomic writes to prevent corruption on crashes.
|
|
2589
|
+
* Writes signals for the given mode.
|
|
2590
|
+
* Lazily initializes the instance on first access.
|
|
2118
2591
|
*
|
|
2119
2592
|
* @param signalData - Signal entries to persist
|
|
2120
|
-
* @param backtest -
|
|
2593
|
+
* @param backtest - True for backtest mode storage, false for live mode
|
|
2121
2594
|
* @returns Promise that resolves when write is complete
|
|
2122
2595
|
*/
|
|
2123
2596
|
this.writeStorageData = async (signalData, backtest) => {
|
|
2124
2597
|
LOGGER_SERVICE$7.info(PERSIST_STORAGE_UTILS_METHOD_NAME_WRITE_DATA);
|
|
2125
2598
|
const key = backtest ? `backtest` : `live`;
|
|
2126
2599
|
const isInitial = !this.getStorage.has(key);
|
|
2127
|
-
const
|
|
2128
|
-
await
|
|
2129
|
-
|
|
2130
|
-
await stateStorage.writeValue(signal.id, signal);
|
|
2131
|
-
}
|
|
2600
|
+
const instance = this.getStorage(backtest);
|
|
2601
|
+
await instance.waitForInit(isInitial);
|
|
2602
|
+
return instance.writeStorageData(signalData);
|
|
2132
2603
|
};
|
|
2133
2604
|
}
|
|
2134
2605
|
/**
|
|
2135
|
-
* Registers a custom
|
|
2606
|
+
* Registers a custom IPersistStorageInstance constructor.
|
|
2607
|
+
* Clears the memoization cache so subsequent calls use the new adapter.
|
|
2136
2608
|
*
|
|
2137
|
-
* @param Ctor - Custom
|
|
2609
|
+
* @param Ctor - Custom IPersistStorageInstance constructor
|
|
2138
2610
|
*/
|
|
2139
2611
|
usePersistStorageAdapter(Ctor) {
|
|
2140
2612
|
LOGGER_SERVICE$7.info(PERSIST_STORAGE_UTILS_METHOD_NAME_USE_PERSIST_STORAGE_ADAPTER);
|
|
2141
|
-
this.
|
|
2613
|
+
this.PersistStorageInstanceCtor = Ctor;
|
|
2614
|
+
this.getStorage.clear();
|
|
2142
2615
|
}
|
|
2143
2616
|
/**
|
|
2144
|
-
* Clears the memoized
|
|
2145
|
-
* Call
|
|
2146
|
-
* so new storage instances are created with the updated base path.
|
|
2617
|
+
* Clears the memoized instance cache.
|
|
2618
|
+
* Call when process.cwd() changes between strategy iterations.
|
|
2147
2619
|
*/
|
|
2148
2620
|
clear() {
|
|
2149
2621
|
LOGGER_SERVICE$7.log(PERSIST_STORAGE_UTILS_METHOD_NAME_CLEAR);
|
|
2150
2622
|
this.getStorage.clear();
|
|
2151
2623
|
}
|
|
2152
2624
|
/**
|
|
2153
|
-
* Switches to the default
|
|
2154
|
-
* All future persistence writes will use JSON storage.
|
|
2625
|
+
* Switches to the default file-based PersistStorageInstance.
|
|
2155
2626
|
*/
|
|
2156
2627
|
useJson() {
|
|
2157
2628
|
LOGGER_SERVICE$7.log(PERSIST_STORAGE_UTILS_METHOD_NAME_USE_JSON);
|
|
2158
|
-
this.usePersistStorageAdapter(
|
|
2629
|
+
this.usePersistStorageAdapter(PersistStorageInstance);
|
|
2159
2630
|
}
|
|
2160
2631
|
/**
|
|
2161
|
-
* Switches to
|
|
2162
|
-
* All future persistence writes will be no-ops.
|
|
2632
|
+
* Switches to PersistStorageDummyInstance (all operations are no-ops).
|
|
2163
2633
|
*/
|
|
2164
2634
|
useDummy() {
|
|
2165
2635
|
LOGGER_SERVICE$7.log(PERSIST_STORAGE_UTILS_METHOD_NAME_USE_DUMMY);
|
|
2166
|
-
this.usePersistStorageAdapter(
|
|
2636
|
+
this.usePersistStorageAdapter(PersistStorageDummyInstance);
|
|
2167
2637
|
}
|
|
2168
2638
|
}
|
|
2169
2639
|
/**
|
|
@@ -2171,6 +2641,92 @@ class PersistStorageUtils {
|
|
|
2171
2641
|
* Used by SignalLiveUtils for signal storage persistence.
|
|
2172
2642
|
*/
|
|
2173
2643
|
const PersistStorageAdapter = new PersistStorageUtils();
|
|
2644
|
+
/**
|
|
2645
|
+
* Default file-based implementation of IPersistNotificationInstance.
|
|
2646
|
+
*
|
|
2647
|
+
* Features:
|
|
2648
|
+
* - Each notification stored as separate JSON file keyed by id
|
|
2649
|
+
* - Read iterates all keys via PersistBase.keys()
|
|
2650
|
+
* - Crash-safe via atomic writes
|
|
2651
|
+
*
|
|
2652
|
+
* @example
|
|
2653
|
+
* ```typescript
|
|
2654
|
+
* const instance = new PersistNotificationInstance(false);
|
|
2655
|
+
* await instance.waitForInit(true);
|
|
2656
|
+
* await instance.writeNotificationData(notifications);
|
|
2657
|
+
* const all = await instance.readNotificationData();
|
|
2658
|
+
* ```
|
|
2659
|
+
*/
|
|
2660
|
+
class PersistNotificationInstance {
|
|
2661
|
+
/**
|
|
2662
|
+
* Creates new notification persistence instance.
|
|
2663
|
+
*
|
|
2664
|
+
* @param backtest - True for backtest mode storage, false for live mode
|
|
2665
|
+
*/
|
|
2666
|
+
constructor(backtest) {
|
|
2667
|
+
this.backtest = backtest;
|
|
2668
|
+
this._storage = new PersistBase(backtest ? `backtest` : `live`, `./dump/data/notification/`);
|
|
2669
|
+
}
|
|
2670
|
+
/**
|
|
2671
|
+
* Initializes the underlying PersistBase storage.
|
|
2672
|
+
*
|
|
2673
|
+
* @param initial - Whether this is the first initialization
|
|
2674
|
+
* @returns Promise that resolves when initialization is complete
|
|
2675
|
+
*/
|
|
2676
|
+
async waitForInit(initial) {
|
|
2677
|
+
await this._storage.waitForInit(initial);
|
|
2678
|
+
}
|
|
2679
|
+
/**
|
|
2680
|
+
* Reads all persisted notifications by iterating storage keys.
|
|
2681
|
+
*
|
|
2682
|
+
* @returns Promise resolving to array of notification entries
|
|
2683
|
+
*/
|
|
2684
|
+
async readNotificationData() {
|
|
2685
|
+
const notifications = [];
|
|
2686
|
+
for await (const notificationId of this._storage.keys()) {
|
|
2687
|
+
const notification = await this._storage.readValue(notificationId);
|
|
2688
|
+
notifications.push(notification);
|
|
2689
|
+
}
|
|
2690
|
+
return notifications;
|
|
2691
|
+
}
|
|
2692
|
+
/**
|
|
2693
|
+
* Writes each notification as a separate entity keyed by `notification.id`.
|
|
2694
|
+
*
|
|
2695
|
+
* @param notifications - Notification entries to persist
|
|
2696
|
+
* @returns Promise that resolves when all writes are complete
|
|
2697
|
+
*/
|
|
2698
|
+
async writeNotificationData(notifications) {
|
|
2699
|
+
for (const notification of notifications) {
|
|
2700
|
+
await this._storage.writeValue(notification.id, notification);
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
/**
|
|
2705
|
+
* No-op IPersistNotificationInstance implementation used by PersistNotificationUtils.useDummy().
|
|
2706
|
+
* All reads return empty array, all writes are discarded.
|
|
2707
|
+
*/
|
|
2708
|
+
class PersistNotificationDummyInstance {
|
|
2709
|
+
/**
|
|
2710
|
+
* No-op constructor.
|
|
2711
|
+
* Context arguments are accepted to satisfy TPersistNotificationInstanceCtor.
|
|
2712
|
+
*/
|
|
2713
|
+
constructor(_backtest) { }
|
|
2714
|
+
/**
|
|
2715
|
+
* No-op initialization.
|
|
2716
|
+
* @returns Promise that resolves immediately
|
|
2717
|
+
*/
|
|
2718
|
+
async waitForInit(_initial) { }
|
|
2719
|
+
/**
|
|
2720
|
+
* Always returns empty notifications array.
|
|
2721
|
+
* @returns Promise resolving to []
|
|
2722
|
+
*/
|
|
2723
|
+
async readNotificationData() { return []; }
|
|
2724
|
+
/**
|
|
2725
|
+
* No-op write (discards notifications).
|
|
2726
|
+
* @returns Promise that resolves immediately
|
|
2727
|
+
*/
|
|
2728
|
+
async writeNotificationData(_notifications) { }
|
|
2729
|
+
}
|
|
2174
2730
|
/**
|
|
2175
2731
|
* Utility class for managing notification persistence.
|
|
2176
2732
|
*
|
|
@@ -2185,89 +2741,81 @@ const PersistStorageAdapter = new PersistStorageUtils();
|
|
|
2185
2741
|
*/
|
|
2186
2742
|
class PersistNotificationUtils {
|
|
2187
2743
|
constructor() {
|
|
2188
|
-
this.PersistNotificationFactory = PersistBase;
|
|
2189
|
-
this.getNotificationStorage = memoize(([backtest]) => backtest ? `backtest` : `live`, (backtest) => Reflect.construct(this.PersistNotificationFactory, [
|
|
2190
|
-
backtest ? `backtest` : `live`,
|
|
2191
|
-
`./dump/data/notification/`,
|
|
2192
|
-
]));
|
|
2193
2744
|
/**
|
|
2194
|
-
*
|
|
2195
|
-
*
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2745
|
+
* Constructor used to create per-mode notification instances.
|
|
2746
|
+
* Replaceable via usePersistNotificationAdapter() / useJson() / useDummy().
|
|
2747
|
+
*/
|
|
2748
|
+
this.PersistNotificationInstanceCtor = PersistNotificationInstance;
|
|
2749
|
+
/**
|
|
2750
|
+
* Memoized factory creating one IPersistNotificationInstance per mode (backtest/live).
|
|
2751
|
+
* Key: "backtest" or "live".
|
|
2752
|
+
*/
|
|
2753
|
+
this.getNotificationStorage = memoize(([backtest]) => backtest ? `backtest` : `live`, (backtest) => Reflect.construct(this.PersistNotificationInstanceCtor, [backtest]));
|
|
2754
|
+
/**
|
|
2755
|
+
* Reads persisted notifications for the given mode.
|
|
2756
|
+
* Lazily initializes the instance on first access.
|
|
2199
2757
|
*
|
|
2200
|
-
* @param backtest -
|
|
2758
|
+
* @param backtest - True for backtest mode storage, false for live mode
|
|
2201
2759
|
* @returns Promise resolving to array of notification entries
|
|
2202
2760
|
*/
|
|
2203
2761
|
this.readNotificationData = async (backtest) => {
|
|
2204
2762
|
LOGGER_SERVICE$7.info(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_READ_DATA);
|
|
2205
2763
|
const key = backtest ? `backtest` : `live`;
|
|
2206
2764
|
const isInitial = !this.getNotificationStorage.has(key);
|
|
2207
|
-
const
|
|
2208
|
-
await
|
|
2209
|
-
|
|
2210
|
-
for await (const notificationId of stateStorage.keys()) {
|
|
2211
|
-
const notification = await stateStorage.readValue(notificationId);
|
|
2212
|
-
notifications.push(notification);
|
|
2213
|
-
}
|
|
2214
|
-
return notifications;
|
|
2765
|
+
const instance = this.getNotificationStorage(backtest);
|
|
2766
|
+
await instance.waitForInit(isInitial);
|
|
2767
|
+
return instance.readNotificationData();
|
|
2215
2768
|
};
|
|
2216
2769
|
/**
|
|
2217
|
-
* Writes
|
|
2218
|
-
*
|
|
2219
|
-
* Called by NotificationPersistLiveUtils/NotificationPersistBacktestUtils after notification changes to persist state.
|
|
2220
|
-
* Uses notification.id as the storage key for individual file storage.
|
|
2221
|
-
* Uses atomic writes to prevent corruption on crashes.
|
|
2770
|
+
* Writes notifications for the given mode.
|
|
2771
|
+
* Lazily initializes the instance on first access.
|
|
2222
2772
|
*
|
|
2223
2773
|
* @param notificationData - Notification entries to persist
|
|
2224
|
-
* @param backtest -
|
|
2774
|
+
* @param backtest - True for backtest mode storage, false for live mode
|
|
2225
2775
|
* @returns Promise that resolves when write is complete
|
|
2226
2776
|
*/
|
|
2227
2777
|
this.writeNotificationData = async (notificationData, backtest) => {
|
|
2228
2778
|
LOGGER_SERVICE$7.info(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_WRITE_DATA);
|
|
2229
2779
|
const key = backtest ? `backtest` : `live`;
|
|
2230
2780
|
const isInitial = !this.getNotificationStorage.has(key);
|
|
2231
|
-
const
|
|
2232
|
-
await
|
|
2233
|
-
|
|
2234
|
-
await stateStorage.writeValue(notification.id, notification);
|
|
2235
|
-
}
|
|
2781
|
+
const instance = this.getNotificationStorage(backtest);
|
|
2782
|
+
await instance.waitForInit(isInitial);
|
|
2783
|
+
return instance.writeNotificationData(notificationData);
|
|
2236
2784
|
};
|
|
2237
2785
|
}
|
|
2238
2786
|
/**
|
|
2239
|
-
* Registers a custom
|
|
2787
|
+
* Registers a custom IPersistNotificationInstance constructor.
|
|
2788
|
+
* Clears the memoization cache so subsequent calls use the new adapter.
|
|
2240
2789
|
*
|
|
2241
|
-
* @param Ctor - Custom
|
|
2790
|
+
* @param Ctor - Custom IPersistNotificationInstance constructor
|
|
2242
2791
|
*/
|
|
2243
2792
|
usePersistNotificationAdapter(Ctor) {
|
|
2244
2793
|
LOGGER_SERVICE$7.info(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_USE_PERSIST_NOTIFICATION_ADAPTER);
|
|
2245
|
-
this.
|
|
2794
|
+
this.PersistNotificationInstanceCtor = Ctor;
|
|
2795
|
+
this.getNotificationStorage.clear();
|
|
2246
2796
|
}
|
|
2247
2797
|
/**
|
|
2248
|
-
* Clears the memoized
|
|
2249
|
-
* Call
|
|
2250
|
-
*
|
|
2798
|
+
* Clears the memoized instance cache.
|
|
2799
|
+
* Call when process.cwd() changes between strategy iterations so new
|
|
2800
|
+
* instances are created with the updated base path.
|
|
2251
2801
|
*/
|
|
2252
2802
|
clear() {
|
|
2253
2803
|
LOGGER_SERVICE$7.log(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_CLEAR);
|
|
2254
2804
|
this.getNotificationStorage.clear();
|
|
2255
2805
|
}
|
|
2256
2806
|
/**
|
|
2257
|
-
* Switches to the default
|
|
2258
|
-
* All future persistence writes will use JSON storage.
|
|
2807
|
+
* Switches to the default file-based PersistNotificationInstance.
|
|
2259
2808
|
*/
|
|
2260
2809
|
useJson() {
|
|
2261
2810
|
LOGGER_SERVICE$7.log(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_USE_JSON);
|
|
2262
|
-
this.usePersistNotificationAdapter(
|
|
2811
|
+
this.usePersistNotificationAdapter(PersistNotificationInstance);
|
|
2263
2812
|
}
|
|
2264
2813
|
/**
|
|
2265
|
-
* Switches to
|
|
2266
|
-
* All future persistence writes will be no-ops.
|
|
2814
|
+
* Switches to PersistNotificationDummyInstance (all operations are no-ops).
|
|
2267
2815
|
*/
|
|
2268
2816
|
useDummy() {
|
|
2269
2817
|
LOGGER_SERVICE$7.log(PERSIST_NOTIFICATION_UTILS_METHOD_NAME_USE_DUMMY);
|
|
2270
|
-
this.usePersistNotificationAdapter(
|
|
2818
|
+
this.usePersistNotificationAdapter(PersistNotificationDummyInstance);
|
|
2271
2819
|
}
|
|
2272
2820
|
}
|
|
2273
2821
|
/**
|
|
@@ -2275,11 +2823,95 @@ class PersistNotificationUtils {
|
|
|
2275
2823
|
* Used by NotificationPersistLiveUtils/NotificationPersistBacktestUtils for notification persistence.
|
|
2276
2824
|
*/
|
|
2277
2825
|
const PersistNotificationAdapter = new PersistNotificationUtils();
|
|
2826
|
+
/**
|
|
2827
|
+
* Default file-based implementation of IPersistLogInstance.
|
|
2828
|
+
*
|
|
2829
|
+
* Features:
|
|
2830
|
+
* - Each log entry stored as separate JSON file keyed by entry.id
|
|
2831
|
+
* - Read iterates all keys via PersistBase.keys()
|
|
2832
|
+
* - Append-only: existing keys are skipped on write
|
|
2833
|
+
* - Crash-safe via atomic writes
|
|
2834
|
+
*
|
|
2835
|
+
* @example
|
|
2836
|
+
* ```typescript
|
|
2837
|
+
* const instance = new PersistLogInstance();
|
|
2838
|
+
* await instance.waitForInit(true);
|
|
2839
|
+
* await instance.writeLogData(entries);
|
|
2840
|
+
* const all = await instance.readLogData();
|
|
2841
|
+
* ```
|
|
2842
|
+
*/
|
|
2843
|
+
class PersistLogInstance {
|
|
2844
|
+
/**
|
|
2845
|
+
* Creates new log persistence instance.
|
|
2846
|
+
* No context parameters — there is a single global log storage.
|
|
2847
|
+
*/
|
|
2848
|
+
constructor() {
|
|
2849
|
+
this._storage = new PersistBase(`log`, `./dump/data/log/`);
|
|
2850
|
+
}
|
|
2851
|
+
/**
|
|
2852
|
+
* Initializes the underlying PersistBase storage.
|
|
2853
|
+
*
|
|
2854
|
+
* @param initial - Whether this is the first initialization
|
|
2855
|
+
* @returns Promise that resolves when initialization is complete
|
|
2856
|
+
*/
|
|
2857
|
+
async waitForInit(initial) {
|
|
2858
|
+
await this._storage.waitForInit(initial);
|
|
2859
|
+
}
|
|
2860
|
+
/**
|
|
2861
|
+
* Reads all persisted log entries by iterating storage keys.
|
|
2862
|
+
*
|
|
2863
|
+
* @returns Promise resolving to array of log entries
|
|
2864
|
+
*/
|
|
2865
|
+
async readLogData() {
|
|
2866
|
+
const entries = [];
|
|
2867
|
+
for await (const entryId of this._storage.keys()) {
|
|
2868
|
+
const entry = await this._storage.readValue(entryId);
|
|
2869
|
+
entries.push(entry);
|
|
2870
|
+
}
|
|
2871
|
+
return entries;
|
|
2872
|
+
}
|
|
2873
|
+
/**
|
|
2874
|
+
* Writes log entries append-only — skips entries whose id already exists
|
|
2875
|
+
* so the log file is never overwritten.
|
|
2876
|
+
*
|
|
2877
|
+
* @param logData - Log entries to persist
|
|
2878
|
+
* @returns Promise that resolves when all writes are complete
|
|
2879
|
+
*/
|
|
2880
|
+
async writeLogData(logData) {
|
|
2881
|
+
for (const entry of logData) {
|
|
2882
|
+
if (await this._storage.hasValue(entry.id)) {
|
|
2883
|
+
continue;
|
|
2884
|
+
}
|
|
2885
|
+
await this._storage.writeValue(entry.id, entry);
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
/**
|
|
2890
|
+
* No-op IPersistLogInstance implementation used by PersistLogUtils.useDummy().
|
|
2891
|
+
* All reads return empty array, all writes are discarded.
|
|
2892
|
+
*/
|
|
2893
|
+
class PersistLogDummyInstance {
|
|
2894
|
+
/**
|
|
2895
|
+
* No-op initialization.
|
|
2896
|
+
* @returns Promise that resolves immediately
|
|
2897
|
+
*/
|
|
2898
|
+
async waitForInit(_initial) { }
|
|
2899
|
+
/**
|
|
2900
|
+
* Always returns empty log entries array.
|
|
2901
|
+
* @returns Promise resolving to []
|
|
2902
|
+
*/
|
|
2903
|
+
async readLogData() { return []; }
|
|
2904
|
+
/**
|
|
2905
|
+
* No-op write (discards log entries).
|
|
2906
|
+
* @returns Promise that resolves immediately
|
|
2907
|
+
*/
|
|
2908
|
+
async writeLogData(_entries) { }
|
|
2909
|
+
}
|
|
2278
2910
|
/**
|
|
2279
2911
|
* Utility class for managing log entry persistence.
|
|
2280
2912
|
*
|
|
2281
2913
|
* Features:
|
|
2282
|
-
* -
|
|
2914
|
+
* - Cached storage instance
|
|
2283
2915
|
* - Custom adapter support
|
|
2284
2916
|
* - Atomic read/write operations for LogData
|
|
2285
2917
|
* - Each log entry stored as separate file keyed by id
|
|
@@ -2289,94 +2921,87 @@ const PersistNotificationAdapter = new PersistNotificationUtils();
|
|
|
2289
2921
|
*/
|
|
2290
2922
|
class PersistLogUtils {
|
|
2291
2923
|
constructor() {
|
|
2292
|
-
this.PersistLogFactory = PersistBase;
|
|
2293
|
-
this._logStorage = null;
|
|
2294
2924
|
/**
|
|
2295
|
-
*
|
|
2296
|
-
*
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2925
|
+
* Constructor used to create the global log instance.
|
|
2926
|
+
* Replaceable via usePersistLogAdapter() / useJson() / useDummy().
|
|
2927
|
+
*/
|
|
2928
|
+
this.PersistLogInstanceCtor = PersistLogInstance;
|
|
2929
|
+
/**
|
|
2930
|
+
* Cached singleton log instance. Lazily created on first access.
|
|
2931
|
+
* Reset to null by clear() and usePersistLogAdapter().
|
|
2932
|
+
*/
|
|
2933
|
+
this._logInstance = null;
|
|
2934
|
+
/**
|
|
2935
|
+
* Reads all persisted log entries.
|
|
2936
|
+
* Lazily initializes the instance on first access.
|
|
2300
2937
|
*
|
|
2301
2938
|
* @returns Promise resolving to array of log entries
|
|
2302
2939
|
*/
|
|
2303
2940
|
this.readLogData = async () => {
|
|
2304
2941
|
LOGGER_SERVICE$7.info(PERSIST_LOG_UTILS_METHOD_NAME_READ_DATA);
|
|
2305
|
-
const isInitial = !this.
|
|
2306
|
-
const
|
|
2307
|
-
await
|
|
2308
|
-
|
|
2309
|
-
for await (const entryId of stateStorage.keys()) {
|
|
2310
|
-
const entry = await stateStorage.readValue(entryId);
|
|
2311
|
-
entries.push(entry);
|
|
2312
|
-
}
|
|
2313
|
-
return entries;
|
|
2942
|
+
const isInitial = !this._logInstance;
|
|
2943
|
+
const instance = this.getLogInstance();
|
|
2944
|
+
await instance.waitForInit(isInitial);
|
|
2945
|
+
return instance.readLogData();
|
|
2314
2946
|
};
|
|
2315
2947
|
/**
|
|
2316
|
-
* Writes log entries
|
|
2317
|
-
*
|
|
2318
|
-
* Called by LogPersistUtils after each log call to persist state.
|
|
2319
|
-
* Uses entry.id as the storage key for individual file storage.
|
|
2320
|
-
* Uses atomic writes to prevent corruption on crashes.
|
|
2948
|
+
* Writes log entries (append-only — duplicates by id are skipped).
|
|
2949
|
+
* Lazily initializes the instance on first access.
|
|
2321
2950
|
*
|
|
2322
2951
|
* @param logData - Log entries to persist
|
|
2323
2952
|
* @returns Promise that resolves when write is complete
|
|
2324
2953
|
*/
|
|
2325
2954
|
this.writeLogData = async (logData) => {
|
|
2326
2955
|
LOGGER_SERVICE$7.info(PERSIST_LOG_UTILS_METHOD_NAME_WRITE_DATA);
|
|
2327
|
-
const isInitial = !this.
|
|
2328
|
-
const
|
|
2329
|
-
await
|
|
2330
|
-
|
|
2331
|
-
if (await stateStorage.hasValue(entry.id)) {
|
|
2332
|
-
continue;
|
|
2333
|
-
}
|
|
2334
|
-
await stateStorage.writeValue(entry.id, entry);
|
|
2335
|
-
}
|
|
2956
|
+
const isInitial = !this._logInstance;
|
|
2957
|
+
const instance = this.getLogInstance();
|
|
2958
|
+
await instance.waitForInit(isInitial);
|
|
2959
|
+
return instance.writeLogData(logData);
|
|
2336
2960
|
};
|
|
2337
2961
|
}
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2962
|
+
/**
|
|
2963
|
+
* Returns the cached log instance, creating it on first access.
|
|
2964
|
+
*
|
|
2965
|
+
* @returns The IPersistLogInstance singleton
|
|
2966
|
+
*/
|
|
2967
|
+
getLogInstance() {
|
|
2968
|
+
if (!this._logInstance) {
|
|
2969
|
+
this._logInstance = Reflect.construct(this.PersistLogInstanceCtor, []);
|
|
2344
2970
|
}
|
|
2345
|
-
return this.
|
|
2971
|
+
return this._logInstance;
|
|
2346
2972
|
}
|
|
2347
2973
|
/**
|
|
2348
|
-
* Registers a custom
|
|
2974
|
+
* Registers a custom IPersistLogInstance constructor.
|
|
2975
|
+
* Drops the cached instance so the next access uses the new adapter.
|
|
2349
2976
|
*
|
|
2350
|
-
* @param Ctor - Custom
|
|
2977
|
+
* @param Ctor - Custom IPersistLogInstance constructor
|
|
2351
2978
|
*/
|
|
2352
2979
|
usePersistLogAdapter(Ctor) {
|
|
2353
2980
|
LOGGER_SERVICE$7.info(PERSIST_LOG_UTILS_METHOD_NAME_USE_PERSIST_LOG_ADAPTER);
|
|
2354
|
-
this.
|
|
2981
|
+
this.PersistLogInstanceCtor = Ctor;
|
|
2982
|
+
this._logInstance = null;
|
|
2355
2983
|
}
|
|
2356
2984
|
/**
|
|
2357
|
-
*
|
|
2358
|
-
* Call
|
|
2359
|
-
* so a new storage instance is created with the updated base path.
|
|
2985
|
+
* Drops the cached log instance.
|
|
2986
|
+
* Call when process.cwd() changes between strategy iterations.
|
|
2360
2987
|
*/
|
|
2361
2988
|
clear() {
|
|
2362
2989
|
LOGGER_SERVICE$7.log(PERSIST_LOG_UTILS_METHOD_NAME_CLEAR);
|
|
2363
|
-
this.
|
|
2990
|
+
this._logInstance = null;
|
|
2364
2991
|
}
|
|
2365
2992
|
/**
|
|
2366
|
-
* Switches to the default
|
|
2367
|
-
* All future persistence writes will use JSON storage.
|
|
2993
|
+
* Switches to the default file-based PersistLogInstance.
|
|
2368
2994
|
*/
|
|
2369
2995
|
useJson() {
|
|
2370
2996
|
LOGGER_SERVICE$7.log(PERSIST_LOG_UTILS_METHOD_NAME_USE_JSON);
|
|
2371
|
-
this.usePersistLogAdapter(
|
|
2997
|
+
this.usePersistLogAdapter(PersistLogInstance);
|
|
2372
2998
|
}
|
|
2373
2999
|
/**
|
|
2374
|
-
* Switches to
|
|
2375
|
-
* All future persistence writes will be no-ops.
|
|
3000
|
+
* Switches to PersistLogDummyInstance (all operations are no-ops).
|
|
2376
3001
|
*/
|
|
2377
3002
|
useDummy() {
|
|
2378
3003
|
LOGGER_SERVICE$7.log(PERSIST_LOG_UTILS_METHOD_NAME_USE_DUMMY);
|
|
2379
|
-
this.usePersistLogAdapter(
|
|
3004
|
+
this.usePersistLogAdapter(PersistLogDummyInstance);
|
|
2380
3005
|
}
|
|
2381
3006
|
}
|
|
2382
3007
|
/**
|
|
@@ -2384,6 +3009,128 @@ class PersistLogUtils {
|
|
|
2384
3009
|
* Used by LogPersistUtils for log entry persistence.
|
|
2385
3010
|
*/
|
|
2386
3011
|
const PersistLogAdapter = new PersistLogUtils();
|
|
3012
|
+
/**
|
|
3013
|
+
* Default file-based implementation of IPersistMeasureInstance.
|
|
3014
|
+
*
|
|
3015
|
+
* Features:
|
|
3016
|
+
* - Wraps PersistBase for atomic JSON writes
|
|
3017
|
+
* - Soft delete via `removed: true` flag
|
|
3018
|
+
* - listMeasureData filters out removed entries
|
|
3019
|
+
*
|
|
3020
|
+
* @example
|
|
3021
|
+
* ```typescript
|
|
3022
|
+
* const instance = new PersistMeasureInstance("my-bucket");
|
|
3023
|
+
* await instance.waitForInit(true);
|
|
3024
|
+
* await instance.writeMeasureData({ id: "x", data: {}, removed: false }, "key1");
|
|
3025
|
+
* const data = await instance.readMeasureData("key1");
|
|
3026
|
+
* await instance.removeMeasureData("key1");
|
|
3027
|
+
* ```
|
|
3028
|
+
*/
|
|
3029
|
+
class PersistMeasureInstance {
|
|
3030
|
+
/**
|
|
3031
|
+
* Creates new measure cache persistence instance.
|
|
3032
|
+
*
|
|
3033
|
+
* @param bucket - Cache bucket identifier
|
|
3034
|
+
*/
|
|
3035
|
+
constructor(bucket) {
|
|
3036
|
+
this.bucket = bucket;
|
|
3037
|
+
this._storage = new PersistBase(bucket, `./dump/data/measure/`);
|
|
3038
|
+
}
|
|
3039
|
+
/**
|
|
3040
|
+
* Initializes the underlying PersistBase storage.
|
|
3041
|
+
*
|
|
3042
|
+
* @param initial - Whether this is the first initialization
|
|
3043
|
+
* @returns Promise that resolves when initialization is complete
|
|
3044
|
+
*/
|
|
3045
|
+
async waitForInit(initial) {
|
|
3046
|
+
await this._storage.waitForInit(initial);
|
|
3047
|
+
}
|
|
3048
|
+
/**
|
|
3049
|
+
* Reads a measure entry by key. Returns null if entry is missing or soft-deleted.
|
|
3050
|
+
*
|
|
3051
|
+
* @param key - Cache key within the bucket
|
|
3052
|
+
* @returns Promise resolving to entry data, or null
|
|
3053
|
+
*/
|
|
3054
|
+
async readMeasureData(key) {
|
|
3055
|
+
if (await this._storage.hasValue(key)) {
|
|
3056
|
+
const data = await this._storage.readValue(key);
|
|
3057
|
+
return data.removed ? null : data;
|
|
3058
|
+
}
|
|
3059
|
+
return null;
|
|
3060
|
+
}
|
|
3061
|
+
/**
|
|
3062
|
+
* Writes a measure entry under the given key.
|
|
3063
|
+
*
|
|
3064
|
+
* @param data - Data to cache
|
|
3065
|
+
* @param key - Cache key within the bucket
|
|
3066
|
+
* @returns Promise that resolves when write is complete
|
|
3067
|
+
*/
|
|
3068
|
+
async writeMeasureData(data, key) {
|
|
3069
|
+
await this._storage.writeValue(key, data);
|
|
3070
|
+
}
|
|
3071
|
+
/**
|
|
3072
|
+
* Soft-deletes an entry by writing `removed: true` flag while preserving the file.
|
|
3073
|
+
*
|
|
3074
|
+
* @param key - Cache key within the bucket
|
|
3075
|
+
* @returns Promise that resolves when removal is complete
|
|
3076
|
+
*/
|
|
3077
|
+
async removeMeasureData(key) {
|
|
3078
|
+
const data = await this._storage.readValue(key);
|
|
3079
|
+
if (data) {
|
|
3080
|
+
await this._storage.writeValue(key, Object.assign({}, data, { removed: true }));
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
/**
|
|
3084
|
+
* Iterates all entries in the bucket, yielding keys of non-removed entries only.
|
|
3085
|
+
*
|
|
3086
|
+
* @returns AsyncGenerator yielding entry keys
|
|
3087
|
+
*/
|
|
3088
|
+
async *listMeasureData() {
|
|
3089
|
+
for await (const key of this._storage.keys()) {
|
|
3090
|
+
const data = await this._storage.readValue(String(key));
|
|
3091
|
+
if (data === null || data.removed) {
|
|
3092
|
+
continue;
|
|
3093
|
+
}
|
|
3094
|
+
yield String(key);
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
/**
|
|
3099
|
+
* No-op IPersistMeasureInstance implementation used by PersistMeasureUtils.useDummy().
|
|
3100
|
+
* All reads return null, all writes/removes are discarded, list yields nothing.
|
|
3101
|
+
*/
|
|
3102
|
+
class PersistMeasureDummyInstance {
|
|
3103
|
+
/**
|
|
3104
|
+
* No-op constructor.
|
|
3105
|
+
* Context arguments are accepted to satisfy TPersistMeasureInstanceCtor.
|
|
3106
|
+
*/
|
|
3107
|
+
constructor(_bucket) { }
|
|
3108
|
+
/**
|
|
3109
|
+
* No-op initialization.
|
|
3110
|
+
* @returns Promise that resolves immediately
|
|
3111
|
+
*/
|
|
3112
|
+
async waitForInit(_initial) { }
|
|
3113
|
+
/**
|
|
3114
|
+
* Always returns null (no cached entries).
|
|
3115
|
+
* @returns Promise resolving to null
|
|
3116
|
+
*/
|
|
3117
|
+
async readMeasureData(_key) { return null; }
|
|
3118
|
+
/**
|
|
3119
|
+
* No-op write (discards entry).
|
|
3120
|
+
* @returns Promise that resolves immediately
|
|
3121
|
+
*/
|
|
3122
|
+
async writeMeasureData(_data, _key) { }
|
|
3123
|
+
/**
|
|
3124
|
+
* No-op remove.
|
|
3125
|
+
* @returns Promise that resolves immediately
|
|
3126
|
+
*/
|
|
3127
|
+
async removeMeasureData(_key) { }
|
|
3128
|
+
/**
|
|
3129
|
+
* Empty generator — yields no entries.
|
|
3130
|
+
* @returns AsyncGenerator that immediately completes
|
|
3131
|
+
*/
|
|
3132
|
+
async *listMeasureData() { }
|
|
3133
|
+
}
|
|
2387
3134
|
/**
|
|
2388
3135
|
* Utility class for managing external API response cache persistence.
|
|
2389
3136
|
*
|
|
@@ -2397,124 +3144,108 @@ const PersistLogAdapter = new PersistLogUtils();
|
|
|
2397
3144
|
*/
|
|
2398
3145
|
class PersistMeasureUtils {
|
|
2399
3146
|
constructor() {
|
|
2400
|
-
this.PersistMeasureFactory = PersistBase;
|
|
2401
|
-
this.getMeasureStorage = memoize(([bucket]) => bucket, (bucket) => Reflect.construct(this.PersistMeasureFactory, [
|
|
2402
|
-
bucket,
|
|
2403
|
-
`./dump/data/measure/`,
|
|
2404
|
-
]));
|
|
2405
3147
|
/**
|
|
2406
|
-
*
|
|
3148
|
+
* Constructor used to create per-bucket measure cache instances.
|
|
3149
|
+
* Replaceable via usePersistMeasureAdapter() / useJson() / useDummy().
|
|
3150
|
+
*/
|
|
3151
|
+
this.PersistMeasureInstanceCtor = PersistMeasureInstance;
|
|
3152
|
+
/**
|
|
3153
|
+
* Memoized factory creating one IPersistMeasureInstance per bucket.
|
|
3154
|
+
*/
|
|
3155
|
+
this.getMeasureStorage = memoize(([bucket]) => bucket, (bucket) => Reflect.construct(this.PersistMeasureInstanceCtor, [bucket]));
|
|
3156
|
+
/**
|
|
3157
|
+
* Reads a measure entry from the given bucket by key.
|
|
3158
|
+
* Lazily initializes the bucket instance on first access.
|
|
2407
3159
|
*
|
|
2408
|
-
* @param bucket - Storage bucket
|
|
2409
|
-
* @param key -
|
|
2410
|
-
* @returns Promise resolving to cached value or null if not found
|
|
3160
|
+
* @param bucket - Storage bucket identifier
|
|
3161
|
+
* @param key - Cache key within the bucket
|
|
3162
|
+
* @returns Promise resolving to cached value, or null if not found / soft-deleted
|
|
2411
3163
|
*/
|
|
2412
3164
|
this.readMeasureData = async (bucket, key) => {
|
|
2413
|
-
LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_READ_DATA, {
|
|
2414
|
-
bucket,
|
|
2415
|
-
key,
|
|
2416
|
-
});
|
|
3165
|
+
LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_READ_DATA, { bucket, key });
|
|
2417
3166
|
const isInitial = !this.getMeasureStorage.has(bucket);
|
|
2418
|
-
const
|
|
2419
|
-
await
|
|
2420
|
-
|
|
2421
|
-
const data = await stateStorage.readValue(key);
|
|
2422
|
-
return data.removed ? null : data;
|
|
2423
|
-
}
|
|
2424
|
-
return null;
|
|
3167
|
+
const instance = this.getMeasureStorage(bucket);
|
|
3168
|
+
await instance.waitForInit(isInitial);
|
|
3169
|
+
return instance.readMeasureData(key);
|
|
2425
3170
|
};
|
|
2426
3171
|
/**
|
|
2427
|
-
* Writes measure
|
|
3172
|
+
* Writes a measure entry to the given bucket under the given key.
|
|
3173
|
+
* Lazily initializes the bucket instance on first access.
|
|
2428
3174
|
*
|
|
2429
3175
|
* @param data - Data to cache
|
|
2430
|
-
* @param bucket - Storage bucket
|
|
2431
|
-
* @param key -
|
|
3176
|
+
* @param bucket - Storage bucket identifier
|
|
3177
|
+
* @param key - Cache key within the bucket
|
|
2432
3178
|
* @returns Promise that resolves when write is complete
|
|
2433
3179
|
*/
|
|
2434
3180
|
this.writeMeasureData = async (data, bucket, key) => {
|
|
2435
|
-
LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_WRITE_DATA, {
|
|
2436
|
-
bucket,
|
|
2437
|
-
key,
|
|
2438
|
-
});
|
|
3181
|
+
LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_WRITE_DATA, { bucket, key });
|
|
2439
3182
|
const isInitial = !this.getMeasureStorage.has(bucket);
|
|
2440
|
-
const
|
|
2441
|
-
await
|
|
2442
|
-
|
|
3183
|
+
const instance = this.getMeasureStorage(bucket);
|
|
3184
|
+
await instance.waitForInit(isInitial);
|
|
3185
|
+
return instance.writeMeasureData(data, key);
|
|
2443
3186
|
};
|
|
2444
3187
|
/**
|
|
2445
|
-
*
|
|
2446
|
-
*
|
|
3188
|
+
* Soft-deletes a measure entry in the given bucket by setting `removed: true`.
|
|
3189
|
+
* Lazily initializes the bucket instance on first access.
|
|
2447
3190
|
*
|
|
2448
|
-
* @param bucket - Storage bucket
|
|
2449
|
-
* @param key -
|
|
3191
|
+
* @param bucket - Storage bucket identifier
|
|
3192
|
+
* @param key - Cache key within the bucket
|
|
2450
3193
|
* @returns Promise that resolves when removal is complete
|
|
2451
3194
|
*/
|
|
2452
3195
|
this.removeMeasureData = async (bucket, key) => {
|
|
2453
|
-
LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_REMOVE_DATA, {
|
|
2454
|
-
bucket,
|
|
2455
|
-
key,
|
|
2456
|
-
});
|
|
3196
|
+
LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_REMOVE_DATA, { bucket, key });
|
|
2457
3197
|
const isInitial = !this.getMeasureStorage.has(bucket);
|
|
2458
|
-
const
|
|
2459
|
-
await
|
|
2460
|
-
|
|
2461
|
-
if (data) {
|
|
2462
|
-
await stateStorage.writeValue(key, Object.assign({}, data, { removed: true }));
|
|
2463
|
-
}
|
|
3198
|
+
const instance = this.getMeasureStorage(bucket);
|
|
3199
|
+
await instance.waitForInit(isInitial);
|
|
3200
|
+
return instance.removeMeasureData(key);
|
|
2464
3201
|
};
|
|
2465
3202
|
}
|
|
2466
3203
|
/**
|
|
2467
|
-
* Registers a custom
|
|
3204
|
+
* Registers a custom IPersistMeasureInstance constructor.
|
|
3205
|
+
* Clears the memoization cache so subsequent calls use the new adapter.
|
|
2468
3206
|
*
|
|
2469
|
-
* @param Ctor - Custom
|
|
3207
|
+
* @param Ctor - Custom IPersistMeasureInstance constructor
|
|
2470
3208
|
*/
|
|
2471
3209
|
usePersistMeasureAdapter(Ctor) {
|
|
2472
3210
|
LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_PERSIST_MEASURE_ADAPTER);
|
|
2473
|
-
this.
|
|
3211
|
+
this.PersistMeasureInstanceCtor = Ctor;
|
|
3212
|
+
this.getMeasureStorage.clear();
|
|
2474
3213
|
}
|
|
2475
3214
|
/**
|
|
2476
|
-
*
|
|
2477
|
-
*
|
|
3215
|
+
* Iterates all non-removed measure entries for the given bucket.
|
|
3216
|
+
* Lazily initializes the bucket instance on first access.
|
|
2478
3217
|
*
|
|
2479
|
-
* @param bucket - Storage bucket
|
|
2480
|
-
* @returns AsyncGenerator yielding
|
|
3218
|
+
* @param bucket - Storage bucket identifier
|
|
3219
|
+
* @returns AsyncGenerator yielding entry keys
|
|
2481
3220
|
*/
|
|
2482
3221
|
async *listMeasureData(bucket) {
|
|
2483
3222
|
LOGGER_SERVICE$7.info(PERSIST_MEASURE_UTILS_METHOD_NAME_LIST_DATA, { bucket });
|
|
2484
3223
|
const isInitial = !this.getMeasureStorage.has(bucket);
|
|
2485
|
-
const
|
|
2486
|
-
await
|
|
2487
|
-
|
|
2488
|
-
const data = await stateStorage.readValue(String(key));
|
|
2489
|
-
if (data === null || data.removed) {
|
|
2490
|
-
continue;
|
|
2491
|
-
}
|
|
2492
|
-
yield String(key);
|
|
2493
|
-
}
|
|
3224
|
+
const instance = this.getMeasureStorage(bucket);
|
|
3225
|
+
await instance.waitForInit(isInitial);
|
|
3226
|
+
yield* instance.listMeasureData();
|
|
2494
3227
|
}
|
|
2495
|
-
;
|
|
2496
3228
|
/**
|
|
2497
|
-
* Clears the memoized
|
|
2498
|
-
* Call
|
|
2499
|
-
* so new storage instances are created with the updated base path.
|
|
3229
|
+
* Clears the memoized bucket instance cache.
|
|
3230
|
+
* Call when process.cwd() changes between strategy iterations.
|
|
2500
3231
|
*/
|
|
2501
3232
|
clear() {
|
|
2502
3233
|
LOGGER_SERVICE$7.log(PERSIST_MEASURE_UTILS_METHOD_NAME_CLEAR);
|
|
2503
3234
|
this.getMeasureStorage.clear();
|
|
2504
3235
|
}
|
|
2505
3236
|
/**
|
|
2506
|
-
* Switches to the default
|
|
3237
|
+
* Switches to the default file-based PersistMeasureInstance.
|
|
2507
3238
|
*/
|
|
2508
3239
|
useJson() {
|
|
2509
3240
|
LOGGER_SERVICE$7.log(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_JSON);
|
|
2510
|
-
this.usePersistMeasureAdapter(
|
|
3241
|
+
this.usePersistMeasureAdapter(PersistMeasureInstance);
|
|
2511
3242
|
}
|
|
2512
3243
|
/**
|
|
2513
|
-
* Switches to
|
|
3244
|
+
* Switches to PersistMeasureDummyInstance (all operations are no-ops).
|
|
2514
3245
|
*/
|
|
2515
3246
|
useDummy() {
|
|
2516
3247
|
LOGGER_SERVICE$7.log(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_DUMMY);
|
|
2517
|
-
this.usePersistMeasureAdapter(
|
|
3248
|
+
this.usePersistMeasureAdapter(PersistMeasureDummyInstance);
|
|
2518
3249
|
}
|
|
2519
3250
|
}
|
|
2520
3251
|
/**
|
|
@@ -2522,6 +3253,129 @@ class PersistMeasureUtils {
|
|
|
2522
3253
|
* Used by Cache.file for persistent caching of external API responses.
|
|
2523
3254
|
*/
|
|
2524
3255
|
const PersistMeasureAdapter = new PersistMeasureUtils();
|
|
3256
|
+
/**
|
|
3257
|
+
* Default file-based implementation of IPersistIntervalInstance.
|
|
3258
|
+
*
|
|
3259
|
+
* Features:
|
|
3260
|
+
* - Wraps PersistBase for atomic JSON writes
|
|
3261
|
+
* - Soft delete via `removed: true` flag
|
|
3262
|
+
* - listIntervalData filters out removed markers
|
|
3263
|
+
*
|
|
3264
|
+
* @example
|
|
3265
|
+
* ```typescript
|
|
3266
|
+
* const instance = new PersistIntervalInstance("my-interval-bucket");
|
|
3267
|
+
* await instance.waitForInit(true);
|
|
3268
|
+
* await instance.writeIntervalData({ id: "x", data: {}, removed: false }, "key1");
|
|
3269
|
+
* const marker = await instance.readIntervalData("key1");
|
|
3270
|
+
* await instance.removeIntervalData("key1");
|
|
3271
|
+
* ```
|
|
3272
|
+
*/
|
|
3273
|
+
class PersistIntervalInstance {
|
|
3274
|
+
/**
|
|
3275
|
+
* Creates new interval marker persistence instance.
|
|
3276
|
+
*
|
|
3277
|
+
* @param bucket - Marker bucket identifier
|
|
3278
|
+
*/
|
|
3279
|
+
constructor(bucket) {
|
|
3280
|
+
this.bucket = bucket;
|
|
3281
|
+
this._storage = new PersistBase(bucket, `./dump/data/interval/`);
|
|
3282
|
+
}
|
|
3283
|
+
/**
|
|
3284
|
+
* Initializes the underlying PersistBase storage.
|
|
3285
|
+
*
|
|
3286
|
+
* @param initial - Whether this is the first initialization
|
|
3287
|
+
* @returns Promise that resolves when initialization is complete
|
|
3288
|
+
*/
|
|
3289
|
+
async waitForInit(initial) {
|
|
3290
|
+
await this._storage.waitForInit(initial);
|
|
3291
|
+
}
|
|
3292
|
+
/**
|
|
3293
|
+
* Reads an interval marker by key. Returns null if marker is missing or soft-deleted.
|
|
3294
|
+
*
|
|
3295
|
+
* @param key - Marker key within the bucket
|
|
3296
|
+
* @returns Promise resolving to stored data, or null
|
|
3297
|
+
*/
|
|
3298
|
+
async readIntervalData(key) {
|
|
3299
|
+
if (await this._storage.hasValue(key)) {
|
|
3300
|
+
const data = await this._storage.readValue(key);
|
|
3301
|
+
return data.removed ? null : data;
|
|
3302
|
+
}
|
|
3303
|
+
return null;
|
|
3304
|
+
}
|
|
3305
|
+
/**
|
|
3306
|
+
* Writes an interval marker under the given key.
|
|
3307
|
+
*
|
|
3308
|
+
* @param data - Data to store
|
|
3309
|
+
* @param key - Marker key within the bucket
|
|
3310
|
+
* @returns Promise that resolves when write is complete
|
|
3311
|
+
*/
|
|
3312
|
+
async writeIntervalData(data, key) {
|
|
3313
|
+
await this._storage.writeValue(key, data);
|
|
3314
|
+
}
|
|
3315
|
+
/**
|
|
3316
|
+
* Soft-deletes a marker by writing `removed: true` flag while preserving the file.
|
|
3317
|
+
* Subsequent reads will return null, allowing the interval to fire again.
|
|
3318
|
+
*
|
|
3319
|
+
* @param key - Marker key within the bucket
|
|
3320
|
+
* @returns Promise that resolves when removal is complete
|
|
3321
|
+
*/
|
|
3322
|
+
async removeIntervalData(key) {
|
|
3323
|
+
const data = await this._storage.readValue(key);
|
|
3324
|
+
if (data) {
|
|
3325
|
+
await this._storage.writeValue(key, Object.assign({}, data, { removed: true }));
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
/**
|
|
3329
|
+
* Iterates all markers in the bucket, yielding keys of non-removed markers only.
|
|
3330
|
+
*
|
|
3331
|
+
* @returns AsyncGenerator yielding marker keys
|
|
3332
|
+
*/
|
|
3333
|
+
async *listIntervalData() {
|
|
3334
|
+
for await (const key of this._storage.keys()) {
|
|
3335
|
+
const data = await this._storage.readValue(String(key));
|
|
3336
|
+
if (data === null || data.removed) {
|
|
3337
|
+
continue;
|
|
3338
|
+
}
|
|
3339
|
+
yield String(key);
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
/**
|
|
3344
|
+
* No-op IPersistIntervalInstance implementation used by PersistIntervalUtils.useDummy().
|
|
3345
|
+
* All reads return null, all writes/removes are discarded, list yields nothing.
|
|
3346
|
+
*/
|
|
3347
|
+
class PersistIntervalDummyInstance {
|
|
3348
|
+
/**
|
|
3349
|
+
* No-op constructor.
|
|
3350
|
+
* Context arguments are accepted to satisfy TPersistIntervalInstanceCtor.
|
|
3351
|
+
*/
|
|
3352
|
+
constructor(_bucket) { }
|
|
3353
|
+
/**
|
|
3354
|
+
* No-op initialization.
|
|
3355
|
+
* @returns Promise that resolves immediately
|
|
3356
|
+
*/
|
|
3357
|
+
async waitForInit(_initial) { }
|
|
3358
|
+
/**
|
|
3359
|
+
* Always returns null (no interval markers).
|
|
3360
|
+
* @returns Promise resolving to null
|
|
3361
|
+
*/
|
|
3362
|
+
async readIntervalData(_key) { return null; }
|
|
3363
|
+
/**
|
|
3364
|
+
* No-op write (discards marker).
|
|
3365
|
+
* @returns Promise that resolves immediately
|
|
3366
|
+
*/
|
|
3367
|
+
async writeIntervalData(_data, _key) { }
|
|
3368
|
+
/**
|
|
3369
|
+
* No-op remove.
|
|
3370
|
+
* @returns Promise that resolves immediately
|
|
3371
|
+
*/
|
|
3372
|
+
async removeIntervalData(_key) { }
|
|
3373
|
+
/**
|
|
3374
|
+
* Empty generator — yields no markers.
|
|
3375
|
+
* @returns AsyncGenerator that immediately completes
|
|
3376
|
+
*/
|
|
3377
|
+
async *listIntervalData() { }
|
|
3378
|
+
}
|
|
2525
3379
|
/**
|
|
2526
3380
|
* Persistence layer for Interval.file once-per-interval signal firing.
|
|
2527
3381
|
*
|
|
@@ -2531,124 +3385,108 @@ const PersistMeasureAdapter = new PersistMeasureUtils();
|
|
|
2531
3385
|
*/
|
|
2532
3386
|
class PersistIntervalUtils {
|
|
2533
3387
|
constructor() {
|
|
2534
|
-
this.PersistIntervalFactory = PersistBase;
|
|
2535
|
-
this.getIntervalStorage = memoize(([bucket]) => bucket, (bucket) => Reflect.construct(this.PersistIntervalFactory, [
|
|
2536
|
-
bucket,
|
|
2537
|
-
`./dump/data/interval/`,
|
|
2538
|
-
]));
|
|
2539
3388
|
/**
|
|
2540
|
-
*
|
|
3389
|
+
* Constructor used to create per-bucket interval marker instances.
|
|
3390
|
+
* Replaceable via usePersistIntervalAdapter() / useJson() / useDummy().
|
|
3391
|
+
*/
|
|
3392
|
+
this.PersistIntervalInstanceCtor = PersistIntervalInstance;
|
|
3393
|
+
/**
|
|
3394
|
+
* Memoized factory creating one IPersistIntervalInstance per bucket.
|
|
3395
|
+
*/
|
|
3396
|
+
this.getIntervalStorage = memoize(([bucket]) => bucket, (bucket) => Reflect.construct(this.PersistIntervalInstanceCtor, [bucket]));
|
|
3397
|
+
/**
|
|
3398
|
+
* Reads an interval marker from the given bucket by key.
|
|
3399
|
+
* Lazily initializes the bucket instance on first access.
|
|
2541
3400
|
*
|
|
2542
|
-
* @param bucket - Storage bucket
|
|
2543
|
-
* @param key -
|
|
2544
|
-
* @returns Promise resolving to
|
|
3401
|
+
* @param bucket - Storage bucket identifier
|
|
3402
|
+
* @param key - Marker key within the bucket
|
|
3403
|
+
* @returns Promise resolving to marker data, or null if not found / soft-deleted
|
|
2545
3404
|
*/
|
|
2546
3405
|
this.readIntervalData = async (bucket, key) => {
|
|
2547
|
-
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_READ_DATA, {
|
|
2548
|
-
bucket,
|
|
2549
|
-
key,
|
|
2550
|
-
});
|
|
3406
|
+
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_READ_DATA, { bucket, key });
|
|
2551
3407
|
const isInitial = !this.getIntervalStorage.has(bucket);
|
|
2552
|
-
const
|
|
2553
|
-
await
|
|
2554
|
-
|
|
2555
|
-
const data = await stateStorage.readValue(key);
|
|
2556
|
-
return data.removed ? null : data;
|
|
2557
|
-
}
|
|
2558
|
-
return null;
|
|
3408
|
+
const instance = this.getIntervalStorage(bucket);
|
|
3409
|
+
await instance.waitForInit(isInitial);
|
|
3410
|
+
return instance.readIntervalData(key);
|
|
2559
3411
|
};
|
|
2560
3412
|
/**
|
|
2561
|
-
* Writes interval
|
|
3413
|
+
* Writes an interval marker to the given bucket under the given key.
|
|
3414
|
+
* Lazily initializes the bucket instance on first access.
|
|
2562
3415
|
*
|
|
2563
3416
|
* @param data - Data to store
|
|
2564
|
-
* @param bucket - Storage bucket
|
|
2565
|
-
* @param key -
|
|
3417
|
+
* @param bucket - Storage bucket identifier
|
|
3418
|
+
* @param key - Marker key within the bucket
|
|
2566
3419
|
* @returns Promise that resolves when write is complete
|
|
2567
3420
|
*/
|
|
2568
3421
|
this.writeIntervalData = async (data, bucket, key) => {
|
|
2569
|
-
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_WRITE_DATA, {
|
|
2570
|
-
bucket,
|
|
2571
|
-
key,
|
|
2572
|
-
});
|
|
3422
|
+
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_WRITE_DATA, { bucket, key });
|
|
2573
3423
|
const isInitial = !this.getIntervalStorage.has(bucket);
|
|
2574
|
-
const
|
|
2575
|
-
await
|
|
2576
|
-
|
|
3424
|
+
const instance = this.getIntervalStorage(bucket);
|
|
3425
|
+
await instance.waitForInit(isInitial);
|
|
3426
|
+
return instance.writeIntervalData(data, key);
|
|
2577
3427
|
};
|
|
2578
3428
|
/**
|
|
2579
|
-
*
|
|
2580
|
-
*
|
|
2581
|
-
* so the function will fire again on the next `IntervalFileInstance.run` call.
|
|
3429
|
+
* Soft-deletes a marker in the given bucket by setting `removed: true`.
|
|
3430
|
+
* Lazily initializes the bucket instance on first access.
|
|
2582
3431
|
*
|
|
2583
|
-
* @param bucket - Storage bucket
|
|
2584
|
-
* @param key -
|
|
3432
|
+
* @param bucket - Storage bucket identifier
|
|
3433
|
+
* @param key - Marker key within the bucket
|
|
2585
3434
|
* @returns Promise that resolves when removal is complete
|
|
2586
3435
|
*/
|
|
2587
3436
|
this.removeIntervalData = async (bucket, key) => {
|
|
2588
|
-
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_REMOVE_DATA, {
|
|
2589
|
-
bucket,
|
|
2590
|
-
key,
|
|
2591
|
-
});
|
|
3437
|
+
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_REMOVE_DATA, { bucket, key });
|
|
2592
3438
|
const isInitial = !this.getIntervalStorage.has(bucket);
|
|
2593
|
-
const
|
|
2594
|
-
await
|
|
2595
|
-
|
|
2596
|
-
if (data) {
|
|
2597
|
-
await stateStorage.writeValue(key, Object.assign({}, data, { removed: true }));
|
|
2598
|
-
}
|
|
3439
|
+
const instance = this.getIntervalStorage(bucket);
|
|
3440
|
+
await instance.waitForInit(isInitial);
|
|
3441
|
+
return instance.removeIntervalData(key);
|
|
2599
3442
|
};
|
|
2600
3443
|
}
|
|
2601
3444
|
/**
|
|
2602
|
-
* Registers a custom
|
|
3445
|
+
* Registers a custom IPersistIntervalInstance constructor.
|
|
3446
|
+
* Clears the memoization cache so subsequent calls use the new adapter.
|
|
2603
3447
|
*
|
|
2604
|
-
* @param Ctor - Custom
|
|
3448
|
+
* @param Ctor - Custom IPersistIntervalInstance constructor
|
|
2605
3449
|
*/
|
|
2606
3450
|
usePersistIntervalAdapter(Ctor) {
|
|
2607
3451
|
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_PERSIST_INTERVAL_ADAPTER);
|
|
2608
|
-
this.
|
|
3452
|
+
this.PersistIntervalInstanceCtor = Ctor;
|
|
3453
|
+
this.getIntervalStorage.clear();
|
|
2609
3454
|
}
|
|
2610
3455
|
/**
|
|
2611
|
-
*
|
|
2612
|
-
*
|
|
3456
|
+
* Iterates all non-removed markers for the given bucket.
|
|
3457
|
+
* Lazily initializes the bucket instance on first access.
|
|
2613
3458
|
*
|
|
2614
|
-
* @param bucket - Storage bucket
|
|
2615
|
-
* @returns AsyncGenerator yielding
|
|
3459
|
+
* @param bucket - Storage bucket identifier
|
|
3460
|
+
* @returns AsyncGenerator yielding marker keys
|
|
2616
3461
|
*/
|
|
2617
3462
|
async *listIntervalData(bucket) {
|
|
2618
3463
|
LOGGER_SERVICE$7.info(PERSIST_INTERVAL_UTILS_METHOD_NAME_LIST_DATA, { bucket });
|
|
2619
3464
|
const isInitial = !this.getIntervalStorage.has(bucket);
|
|
2620
|
-
const
|
|
2621
|
-
await
|
|
2622
|
-
|
|
2623
|
-
const data = await stateStorage.readValue(String(key));
|
|
2624
|
-
if (data === null || data.removed) {
|
|
2625
|
-
continue;
|
|
2626
|
-
}
|
|
2627
|
-
yield String(key);
|
|
2628
|
-
}
|
|
3465
|
+
const instance = this.getIntervalStorage(bucket);
|
|
3466
|
+
await instance.waitForInit(isInitial);
|
|
3467
|
+
yield* instance.listIntervalData();
|
|
2629
3468
|
}
|
|
2630
|
-
;
|
|
2631
3469
|
/**
|
|
2632
|
-
* Clears the memoized
|
|
2633
|
-
* Call
|
|
3470
|
+
* Clears the memoized bucket instance cache.
|
|
3471
|
+
* Call when process.cwd() changes between strategy iterations.
|
|
2634
3472
|
*/
|
|
2635
3473
|
clear() {
|
|
2636
3474
|
LOGGER_SERVICE$7.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_CLEAR);
|
|
2637
3475
|
this.getIntervalStorage.clear();
|
|
2638
3476
|
}
|
|
2639
3477
|
/**
|
|
2640
|
-
* Switches to the default
|
|
3478
|
+
* Switches to the default file-based PersistIntervalInstance.
|
|
2641
3479
|
*/
|
|
2642
3480
|
useJson() {
|
|
2643
3481
|
LOGGER_SERVICE$7.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_JSON);
|
|
2644
|
-
this.usePersistIntervalAdapter(
|
|
3482
|
+
this.usePersistIntervalAdapter(PersistIntervalInstance);
|
|
2645
3483
|
}
|
|
2646
3484
|
/**
|
|
2647
|
-
* Switches to
|
|
3485
|
+
* Switches to PersistIntervalDummyInstance (all operations are no-ops).
|
|
2648
3486
|
*/
|
|
2649
3487
|
useDummy() {
|
|
2650
3488
|
LOGGER_SERVICE$7.log(PERSIST_INTERVAL_UTILS_METHOD_NAME_USE_DUMMY);
|
|
2651
|
-
this.usePersistIntervalAdapter(
|
|
3489
|
+
this.usePersistIntervalAdapter(PersistIntervalDummyInstance);
|
|
2652
3490
|
}
|
|
2653
3491
|
}
|
|
2654
3492
|
/**
|
|
@@ -2656,6 +3494,154 @@ class PersistIntervalUtils {
|
|
|
2656
3494
|
* Used by Interval.file for persistent once-per-interval signal firing.
|
|
2657
3495
|
*/
|
|
2658
3496
|
const PersistIntervalAdapter = new PersistIntervalUtils();
|
|
3497
|
+
/**
|
|
3498
|
+
* Default file-based implementation of IPersistMemoryInstance.
|
|
3499
|
+
*
|
|
3500
|
+
* Features:
|
|
3501
|
+
* - Wraps PersistBase for atomic JSON writes
|
|
3502
|
+
* - Soft delete via `removed: true` flag
|
|
3503
|
+
* - listMemoryData filters out removed entries
|
|
3504
|
+
* - dispose is a no-op (memo cache is managed by PersistMemoryUtils)
|
|
3505
|
+
*
|
|
3506
|
+
* @example
|
|
3507
|
+
* ```typescript
|
|
3508
|
+
* const instance = new PersistMemoryInstance("signal-1", "context-bucket");
|
|
3509
|
+
* await instance.waitForInit(true);
|
|
3510
|
+
* await instance.writeMemoryData(entryData, "memory-id-1");
|
|
3511
|
+
* const data = await instance.readMemoryData("memory-id-1");
|
|
3512
|
+
* ```
|
|
3513
|
+
*/
|
|
3514
|
+
class PersistMemoryInstance {
|
|
3515
|
+
/**
|
|
3516
|
+
* Creates new memory persistence instance.
|
|
3517
|
+
*
|
|
3518
|
+
* @param signalId - Signal identifier (entity folder name)
|
|
3519
|
+
* @param bucketName - Bucket name (subfolder under memory/)
|
|
3520
|
+
*/
|
|
3521
|
+
constructor(signalId, bucketName) {
|
|
3522
|
+
this.signalId = signalId;
|
|
3523
|
+
this.bucketName = bucketName;
|
|
3524
|
+
this._storage = new PersistBase(bucketName, `./dump/memory/${signalId}/`);
|
|
3525
|
+
}
|
|
3526
|
+
/**
|
|
3527
|
+
* Initializes the underlying PersistBase storage.
|
|
3528
|
+
*
|
|
3529
|
+
* @param initial - Whether this is the first initialization
|
|
3530
|
+
* @returns Promise that resolves when initialization is complete
|
|
3531
|
+
*/
|
|
3532
|
+
async waitForInit(initial) {
|
|
3533
|
+
await this._storage.waitForInit(initial);
|
|
3534
|
+
}
|
|
3535
|
+
/**
|
|
3536
|
+
* Reads a memory entry by id. Returns null if entry is missing or soft-deleted.
|
|
3537
|
+
*
|
|
3538
|
+
* @param memoryId - Memory entry identifier
|
|
3539
|
+
* @returns Promise resolving to entry data, or null
|
|
3540
|
+
*/
|
|
3541
|
+
async readMemoryData(memoryId) {
|
|
3542
|
+
if (await this._storage.hasValue(memoryId)) {
|
|
3543
|
+
const data = await this._storage.readValue(memoryId);
|
|
3544
|
+
return data.removed ? null : data;
|
|
3545
|
+
}
|
|
3546
|
+
return null;
|
|
3547
|
+
}
|
|
3548
|
+
/**
|
|
3549
|
+
* Checks whether a memory entry exists on disk (regardless of removed flag).
|
|
3550
|
+
*
|
|
3551
|
+
* @param memoryId - Memory entry identifier
|
|
3552
|
+
* @returns Promise resolving to true if entry file exists
|
|
3553
|
+
*/
|
|
3554
|
+
async hasMemoryData(memoryId) {
|
|
3555
|
+
return await this._storage.hasValue(memoryId);
|
|
3556
|
+
}
|
|
3557
|
+
/**
|
|
3558
|
+
* Writes a memory entry under the given id.
|
|
3559
|
+
*
|
|
3560
|
+
* @param data - Entry data to persist
|
|
3561
|
+
* @param memoryId - Memory entry identifier
|
|
3562
|
+
* @returns Promise that resolves when write is complete
|
|
3563
|
+
*/
|
|
3564
|
+
async writeMemoryData(data, memoryId) {
|
|
3565
|
+
await this._storage.writeValue(memoryId, data);
|
|
3566
|
+
}
|
|
3567
|
+
/**
|
|
3568
|
+
* Soft-deletes a memory entry by writing `removed: true` flag.
|
|
3569
|
+
*
|
|
3570
|
+
* @param memoryId - Memory entry identifier
|
|
3571
|
+
* @returns Promise that resolves when removal is complete
|
|
3572
|
+
*/
|
|
3573
|
+
async removeMemoryData(memoryId) {
|
|
3574
|
+
const data = await this._storage.readValue(memoryId);
|
|
3575
|
+
if (data) {
|
|
3576
|
+
await this._storage.writeValue(memoryId, Object.assign({}, data, { removed: true }));
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
/**
|
|
3580
|
+
* Iterates all memory entries in the bucket, yielding id + data tuples
|
|
3581
|
+
* for non-removed entries only.
|
|
3582
|
+
*
|
|
3583
|
+
* @returns AsyncGenerator yielding `{ memoryId, data }` tuples
|
|
3584
|
+
*/
|
|
3585
|
+
async *listMemoryData() {
|
|
3586
|
+
for await (const memoryId of this._storage.keys()) {
|
|
3587
|
+
const data = await this._storage.readValue(String(memoryId));
|
|
3588
|
+
if (data === null || data.removed) {
|
|
3589
|
+
continue;
|
|
3590
|
+
}
|
|
3591
|
+
yield { memoryId: String(memoryId), data };
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
/**
|
|
3595
|
+
* No-op for the default file-based implementation.
|
|
3596
|
+
* Resource cleanup (memo cache invalidation) is handled by PersistMemoryUtils.dispose().
|
|
3597
|
+
*/
|
|
3598
|
+
dispose() { }
|
|
3599
|
+
}
|
|
3600
|
+
/**
|
|
3601
|
+
* No-op IPersistMemoryInstance implementation used by PersistMemoryUtils.useDummy().
|
|
3602
|
+
* All reads return null/false, all writes/removes are discarded, list yields nothing.
|
|
3603
|
+
*/
|
|
3604
|
+
class PersistMemoryDummyInstance {
|
|
3605
|
+
/**
|
|
3606
|
+
* No-op constructor.
|
|
3607
|
+
* Context arguments are accepted to satisfy TPersistMemoryInstanceCtor.
|
|
3608
|
+
*/
|
|
3609
|
+
constructor(_signalId, _bucketName) { }
|
|
3610
|
+
/**
|
|
3611
|
+
* No-op initialization.
|
|
3612
|
+
* @returns Promise that resolves immediately
|
|
3613
|
+
*/
|
|
3614
|
+
async waitForInit(_initial) { }
|
|
3615
|
+
/**
|
|
3616
|
+
* Always returns null (no memory entries).
|
|
3617
|
+
* @returns Promise resolving to null
|
|
3618
|
+
*/
|
|
3619
|
+
async readMemoryData(_memoryId) { return null; }
|
|
3620
|
+
/**
|
|
3621
|
+
* Always returns false (no memory entries exist).
|
|
3622
|
+
* @returns Promise resolving to false
|
|
3623
|
+
*/
|
|
3624
|
+
async hasMemoryData(_memoryId) { return false; }
|
|
3625
|
+
/**
|
|
3626
|
+
* No-op write (discards entry).
|
|
3627
|
+
* @returns Promise that resolves immediately
|
|
3628
|
+
*/
|
|
3629
|
+
async writeMemoryData(_data, _memoryId) { }
|
|
3630
|
+
/**
|
|
3631
|
+
* No-op remove.
|
|
3632
|
+
* @returns Promise that resolves immediately
|
|
3633
|
+
*/
|
|
3634
|
+
async removeMemoryData(_memoryId) { }
|
|
3635
|
+
/**
|
|
3636
|
+
* Empty generator — yields no entries.
|
|
3637
|
+
* @returns AsyncGenerator that immediately completes
|
|
3638
|
+
*/
|
|
3639
|
+
async *listMemoryData() { }
|
|
3640
|
+
/**
|
|
3641
|
+
* No-op dispose.
|
|
3642
|
+
*/
|
|
3643
|
+
dispose() { }
|
|
3644
|
+
}
|
|
2659
3645
|
/**
|
|
2660
3646
|
* Utility class for managing memory entry persistence.
|
|
2661
3647
|
*
|
|
@@ -2671,51 +3657,50 @@ const PersistIntervalAdapter = new PersistIntervalUtils();
|
|
|
2671
3657
|
*/
|
|
2672
3658
|
class PersistMemoryUtils {
|
|
2673
3659
|
constructor() {
|
|
2674
|
-
this.PersistMemoryFactory = PersistBase;
|
|
2675
|
-
this.getMemoryStorage = memoize(([signalId, bucketName]) => `${signalId}:${bucketName}`, (signalId, bucketName) => Reflect.construct(this.PersistMemoryFactory, [
|
|
2676
|
-
bucketName,
|
|
2677
|
-
`./dump/memory/${signalId}/`,
|
|
2678
|
-
]));
|
|
2679
3660
|
/**
|
|
2680
|
-
*
|
|
3661
|
+
* Constructor used to create per-context memory instances.
|
|
3662
|
+
* Replaceable via usePersistMemoryAdapter() / useJson() / useDummy().
|
|
3663
|
+
*/
|
|
3664
|
+
this.PersistMemoryInstanceCtor = PersistMemoryInstance;
|
|
3665
|
+
/**
|
|
3666
|
+
* Memoized factory creating one IPersistMemoryInstance per (signalId, bucketName) pair.
|
|
3667
|
+
*/
|
|
3668
|
+
this.getMemoryStorage = memoize(([signalId, bucketName]) => `${signalId}:${bucketName}`, (signalId, bucketName) => Reflect.construct(this.PersistMemoryInstanceCtor, [signalId, bucketName]));
|
|
3669
|
+
/**
|
|
3670
|
+
* Initializes the memory storage for the given context.
|
|
3671
|
+
* Skips initialization when `initial` is false (used to gate first-time setup).
|
|
2681
3672
|
*
|
|
2682
|
-
* @param signalId - Signal identifier
|
|
2683
|
-
* @param bucketName - Bucket name
|
|
3673
|
+
* @param signalId - Signal identifier
|
|
3674
|
+
* @param bucketName - Bucket name
|
|
2684
3675
|
* @param initial - Whether this is the first initialization
|
|
2685
3676
|
* @returns Promise that resolves when initialization is complete
|
|
2686
3677
|
*/
|
|
2687
3678
|
this.waitForInit = async (signalId, bucketName, initial) => {
|
|
2688
3679
|
const key = `${signalId}:${bucketName}`;
|
|
2689
3680
|
const isInitial = initial && !this.getMemoryStorage.has(key);
|
|
2690
|
-
const
|
|
2691
|
-
await
|
|
3681
|
+
const instance = this.getMemoryStorage(signalId, bucketName);
|
|
3682
|
+
await instance.waitForInit(isInitial);
|
|
2692
3683
|
};
|
|
2693
3684
|
/**
|
|
2694
|
-
* Reads a memory entry
|
|
3685
|
+
* Reads a memory entry for the given context and id.
|
|
3686
|
+
* Lazily initializes the instance on first access.
|
|
2695
3687
|
*
|
|
2696
3688
|
* @param signalId - Signal identifier
|
|
2697
3689
|
* @param bucketName - Bucket name
|
|
2698
3690
|
* @param memoryId - Memory entry identifier
|
|
2699
|
-
* @returns Promise resolving to entry data or null if not found
|
|
3691
|
+
* @returns Promise resolving to entry data, or null if not found / soft-deleted
|
|
2700
3692
|
*/
|
|
2701
3693
|
this.readMemoryData = async (signalId, bucketName, memoryId) => {
|
|
2702
|
-
LOGGER_SERVICE$7.info(PERSIST_MEMORY_UTILS_METHOD_NAME_READ_DATA, {
|
|
2703
|
-
signalId,
|
|
2704
|
-
bucketName,
|
|
2705
|
-
memoryId,
|
|
2706
|
-
});
|
|
3694
|
+
LOGGER_SERVICE$7.info(PERSIST_MEMORY_UTILS_METHOD_NAME_READ_DATA, { signalId, bucketName, memoryId });
|
|
2707
3695
|
const key = `${signalId}:${bucketName}`;
|
|
2708
3696
|
const isInitial = !this.getMemoryStorage.has(key);
|
|
2709
|
-
const
|
|
2710
|
-
await
|
|
2711
|
-
|
|
2712
|
-
const data = await stateStorage.readValue(memoryId);
|
|
2713
|
-
return data.removed ? null : data;
|
|
2714
|
-
}
|
|
2715
|
-
return null;
|
|
3697
|
+
const instance = this.getMemoryStorage(signalId, bucketName);
|
|
3698
|
+
await instance.waitForInit(isInitial);
|
|
3699
|
+
return instance.readMemoryData(memoryId);
|
|
2716
3700
|
};
|
|
2717
3701
|
/**
|
|
2718
|
-
* Checks
|
|
3702
|
+
* Checks whether a memory entry exists on disk for the given context.
|
|
3703
|
+
* Lazily initializes the instance on first access.
|
|
2719
3704
|
*
|
|
2720
3705
|
* @param signalId - Signal identifier
|
|
2721
3706
|
* @param bucketName - Bucket name
|
|
@@ -2723,19 +3708,16 @@ class PersistMemoryUtils {
|
|
|
2723
3708
|
* @returns Promise resolving to true if entry exists
|
|
2724
3709
|
*/
|
|
2725
3710
|
this.hasMemoryData = async (signalId, bucketName, memoryId) => {
|
|
2726
|
-
LOGGER_SERVICE$7.info(PERSIST_MEMORY_UTILS_METHOD_NAME_HAS_DATA, {
|
|
2727
|
-
signalId,
|
|
2728
|
-
bucketName,
|
|
2729
|
-
memoryId,
|
|
2730
|
-
});
|
|
3711
|
+
LOGGER_SERVICE$7.info(PERSIST_MEMORY_UTILS_METHOD_NAME_HAS_DATA, { signalId, bucketName, memoryId });
|
|
2731
3712
|
const key = `${signalId}:${bucketName}`;
|
|
2732
3713
|
const isInitial = !this.getMemoryStorage.has(key);
|
|
2733
|
-
const
|
|
2734
|
-
await
|
|
2735
|
-
return
|
|
3714
|
+
const instance = this.getMemoryStorage(signalId, bucketName);
|
|
3715
|
+
await instance.waitForInit(isInitial);
|
|
3716
|
+
return instance.hasMemoryData(memoryId);
|
|
2736
3717
|
};
|
|
2737
3718
|
/**
|
|
2738
|
-
* Writes a memory entry
|
|
3719
|
+
* Writes a memory entry for the given context.
|
|
3720
|
+
* Lazily initializes the instance on first access.
|
|
2739
3721
|
*
|
|
2740
3722
|
* @param data - Entry data to persist
|
|
2741
3723
|
* @param signalId - Signal identifier
|
|
@@ -2744,19 +3726,16 @@ class PersistMemoryUtils {
|
|
|
2744
3726
|
* @returns Promise that resolves when write is complete
|
|
2745
3727
|
*/
|
|
2746
3728
|
this.writeMemoryData = async (data, signalId, bucketName, memoryId) => {
|
|
2747
|
-
LOGGER_SERVICE$7.info(PERSIST_MEMORY_UTILS_METHOD_NAME_WRITE_DATA, {
|
|
2748
|
-
signalId,
|
|
2749
|
-
bucketName,
|
|
2750
|
-
memoryId,
|
|
2751
|
-
});
|
|
3729
|
+
LOGGER_SERVICE$7.info(PERSIST_MEMORY_UTILS_METHOD_NAME_WRITE_DATA, { signalId, bucketName, memoryId });
|
|
2752
3730
|
const key = `${signalId}:${bucketName}`;
|
|
2753
3731
|
const isInitial = !this.getMemoryStorage.has(key);
|
|
2754
|
-
const
|
|
2755
|
-
await
|
|
2756
|
-
|
|
3732
|
+
const instance = this.getMemoryStorage(signalId, bucketName);
|
|
3733
|
+
await instance.waitForInit(isInitial);
|
|
3734
|
+
return instance.writeMemoryData(data, memoryId);
|
|
2757
3735
|
};
|
|
2758
3736
|
/**
|
|
2759
|
-
*
|
|
3737
|
+
* Soft-deletes a memory entry for the given context.
|
|
3738
|
+
* Lazily initializes the instance on first access.
|
|
2760
3739
|
*
|
|
2761
3740
|
* @param signalId - Signal identifier
|
|
2762
3741
|
* @param bucketName - Bucket name
|
|
@@ -2764,36 +3743,27 @@ class PersistMemoryUtils {
|
|
|
2764
3743
|
* @returns Promise that resolves when removal is complete
|
|
2765
3744
|
*/
|
|
2766
3745
|
this.removeMemoryData = async (signalId, bucketName, memoryId) => {
|
|
2767
|
-
LOGGER_SERVICE$7.info(PERSIST_MEMORY_UTILS_METHOD_NAME_REMOVE_DATA, {
|
|
2768
|
-
signalId,
|
|
2769
|
-
bucketName,
|
|
2770
|
-
memoryId,
|
|
2771
|
-
});
|
|
3746
|
+
LOGGER_SERVICE$7.info(PERSIST_MEMORY_UTILS_METHOD_NAME_REMOVE_DATA, { signalId, bucketName, memoryId });
|
|
2772
3747
|
const key = `${signalId}:${bucketName}`;
|
|
2773
3748
|
const isInitial = !this.getMemoryStorage.has(key);
|
|
2774
|
-
const
|
|
2775
|
-
await
|
|
2776
|
-
|
|
2777
|
-
if (data) {
|
|
2778
|
-
await stateStorage.writeValue(memoryId, Object.assign({}, data, { removed: true }));
|
|
2779
|
-
}
|
|
3749
|
+
const instance = this.getMemoryStorage(signalId, bucketName);
|
|
3750
|
+
await instance.waitForInit(isInitial);
|
|
3751
|
+
return instance.removeMemoryData(memoryId);
|
|
2780
3752
|
};
|
|
2781
3753
|
/**
|
|
2782
|
-
* Clears the memoized
|
|
2783
|
-
* Call
|
|
2784
|
-
* so new storage instances are created with the updated base path.
|
|
3754
|
+
* Clears the memoized instance cache.
|
|
3755
|
+
* Call when process.cwd() changes between strategy iterations.
|
|
2785
3756
|
*/
|
|
2786
3757
|
this.clear = () => {
|
|
2787
3758
|
LOGGER_SERVICE$7.info(PERSIST_MEMORY_UTILS_METHOD_NAME_CLEAR);
|
|
2788
3759
|
this.getMemoryStorage.clear();
|
|
2789
3760
|
};
|
|
2790
3761
|
/**
|
|
2791
|
-
*
|
|
2792
|
-
* Call
|
|
3762
|
+
* Drops the memoized instance for the given context.
|
|
3763
|
+
* Call when a signal is removed to clean up its associated storage entry.
|
|
2793
3764
|
*
|
|
2794
3765
|
* @param signalId - Signal identifier
|
|
2795
3766
|
* @param bucketName - Bucket name
|
|
2796
|
-
* @returns void
|
|
2797
3767
|
*/
|
|
2798
3768
|
this.dispose = (signalId, bucketName) => {
|
|
2799
3769
|
LOGGER_SERVICE$7.info(PERSIST_MEMORY_UTILS_METHOD_NAME_DISPOSE);
|
|
@@ -2802,64 +3772,46 @@ class PersistMemoryUtils {
|
|
|
2802
3772
|
};
|
|
2803
3773
|
}
|
|
2804
3774
|
/**
|
|
2805
|
-
* Registers a custom
|
|
3775
|
+
* Registers a custom IPersistMemoryInstance constructor.
|
|
3776
|
+
* Clears the memoization cache so subsequent calls use the new adapter.
|
|
2806
3777
|
*
|
|
2807
|
-
* @param Ctor - Custom
|
|
2808
|
-
*
|
|
2809
|
-
* @example
|
|
2810
|
-
* ```typescript
|
|
2811
|
-
* class RedisPersist extends PersistBase {
|
|
2812
|
-
* async readValue(id) { return JSON.parse(await redis.get(id)); }
|
|
2813
|
-
* async writeValue(id, entity) { await redis.set(id, JSON.stringify(entity)); }
|
|
2814
|
-
* }
|
|
2815
|
-
* PersistMemoryAdapter.usePersistMemoryAdapter(RedisPersist);
|
|
2816
|
-
* ```
|
|
3778
|
+
* @param Ctor - Custom IPersistMemoryInstance constructor
|
|
2817
3779
|
*/
|
|
2818
3780
|
usePersistMemoryAdapter(Ctor) {
|
|
2819
3781
|
LOGGER_SERVICE$7.info(PERSIST_MEMORY_UTILS_METHOD_NAME_USE_PERSIST_MEMORY_ADAPTER);
|
|
2820
|
-
this.
|
|
3782
|
+
this.PersistMemoryInstanceCtor = Ctor;
|
|
3783
|
+
this.getMemoryStorage.clear();
|
|
2821
3784
|
}
|
|
2822
3785
|
/**
|
|
2823
|
-
*
|
|
3786
|
+
* Iterates all non-removed memory entries for the given context.
|
|
2824
3787
|
* Used by MemoryPersistInstance to rebuild the BM25 index on init.
|
|
3788
|
+
* Lazily initializes the instance on first access.
|
|
2825
3789
|
*
|
|
2826
3790
|
* @param signalId - Signal identifier
|
|
2827
3791
|
* @param bucketName - Bucket name
|
|
2828
|
-
* @returns AsyncGenerator yielding
|
|
3792
|
+
* @returns AsyncGenerator yielding `{ memoryId, data }` tuples
|
|
2829
3793
|
*/
|
|
2830
3794
|
async *listMemoryData(signalId, bucketName) {
|
|
2831
|
-
LOGGER_SERVICE$7.info(PERSIST_MEMORY_UTILS_METHOD_NAME_LIST_DATA, {
|
|
2832
|
-
signalId,
|
|
2833
|
-
bucketName,
|
|
2834
|
-
});
|
|
3795
|
+
LOGGER_SERVICE$7.info(PERSIST_MEMORY_UTILS_METHOD_NAME_LIST_DATA, { signalId, bucketName });
|
|
2835
3796
|
const key = `${signalId}:${bucketName}`;
|
|
2836
3797
|
const isInitial = !this.getMemoryStorage.has(key);
|
|
2837
|
-
const
|
|
2838
|
-
await
|
|
2839
|
-
|
|
2840
|
-
const data = await stateStorage.readValue(String(memoryId));
|
|
2841
|
-
if (data === null || data.removed) {
|
|
2842
|
-
continue;
|
|
2843
|
-
}
|
|
2844
|
-
yield { memoryId: String(memoryId), data };
|
|
2845
|
-
}
|
|
3798
|
+
const instance = this.getMemoryStorage(signalId, bucketName);
|
|
3799
|
+
await instance.waitForInit(isInitial);
|
|
3800
|
+
yield* instance.listMemoryData();
|
|
2846
3801
|
}
|
|
2847
|
-
;
|
|
2848
3802
|
/**
|
|
2849
|
-
* Switches to the default
|
|
2850
|
-
* All future persistence writes will use JSON storage.
|
|
3803
|
+
* Switches to the default file-based PersistMemoryInstance.
|
|
2851
3804
|
*/
|
|
2852
3805
|
useJson() {
|
|
2853
3806
|
LOGGER_SERVICE$7.log(PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_JSON);
|
|
2854
|
-
this.usePersistMemoryAdapter(
|
|
3807
|
+
this.usePersistMemoryAdapter(PersistMemoryInstance);
|
|
2855
3808
|
}
|
|
2856
3809
|
/**
|
|
2857
|
-
* Switches to
|
|
2858
|
-
* All future persistence writes will be no-ops.
|
|
3810
|
+
* Switches to PersistMemoryDummyInstance (all operations are no-ops).
|
|
2859
3811
|
*/
|
|
2860
3812
|
useDummy() {
|
|
2861
3813
|
LOGGER_SERVICE$7.log(PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_DUMMY);
|
|
2862
|
-
this.usePersistMemoryAdapter(
|
|
3814
|
+
this.usePersistMemoryAdapter(PersistMemoryDummyInstance);
|
|
2863
3815
|
}
|
|
2864
3816
|
}
|
|
2865
3817
|
/**
|
|
@@ -2879,6 +3831,100 @@ class PersistMemoryUtils {
|
|
|
2879
3831
|
* ```
|
|
2880
3832
|
*/
|
|
2881
3833
|
const PersistMemoryAdapter = new PersistMemoryUtils();
|
|
3834
|
+
/**
|
|
3835
|
+
* Default file-based implementation of IPersistRecentInstance.
|
|
3836
|
+
*
|
|
3837
|
+
* Features:
|
|
3838
|
+
* - Wraps PersistBase for atomic JSON writes
|
|
3839
|
+
* - Uses symbol as entity ID within a per-context PersistBase
|
|
3840
|
+
* - Context key includes backtest/live mode and optional frameName
|
|
3841
|
+
*
|
|
3842
|
+
* @example
|
|
3843
|
+
* ```typescript
|
|
3844
|
+
* const instance = new PersistRecentInstance("BTCUSDT", "my-strategy", "binance", "frame-1", false);
|
|
3845
|
+
* await instance.waitForInit(true);
|
|
3846
|
+
* await instance.writeRecentData(publicSignalRow);
|
|
3847
|
+
* const recent = await instance.readRecentData();
|
|
3848
|
+
* ```
|
|
3849
|
+
*/
|
|
3850
|
+
class PersistRecentInstance {
|
|
3851
|
+
/**
|
|
3852
|
+
* Creates new recent signal persistence instance.
|
|
3853
|
+
*
|
|
3854
|
+
* @param symbol - Trading pair symbol
|
|
3855
|
+
* @param strategyName - Strategy identifier
|
|
3856
|
+
* @param exchangeName - Exchange identifier
|
|
3857
|
+
* @param frameName - Frame identifier (may be empty for live mode)
|
|
3858
|
+
* @param backtest - True for backtest mode, false for live mode
|
|
3859
|
+
*/
|
|
3860
|
+
constructor(symbol, strategyName, exchangeName, frameName, backtest) {
|
|
3861
|
+
this.symbol = symbol;
|
|
3862
|
+
this.strategyName = strategyName;
|
|
3863
|
+
this.exchangeName = exchangeName;
|
|
3864
|
+
this.frameName = frameName;
|
|
3865
|
+
this.backtest = backtest;
|
|
3866
|
+
const parts = [symbol, strategyName, exchangeName];
|
|
3867
|
+
if (frameName)
|
|
3868
|
+
parts.push(frameName);
|
|
3869
|
+
parts.push(backtest ? "backtest" : "live");
|
|
3870
|
+
this._storage = new PersistBase(parts.join("_"), `./dump/data/recent/`);
|
|
3871
|
+
}
|
|
3872
|
+
/**
|
|
3873
|
+
* Initializes the underlying PersistBase storage.
|
|
3874
|
+
*
|
|
3875
|
+
* @param initial - Whether this is the first initialization
|
|
3876
|
+
* @returns Promise that resolves when initialization is complete
|
|
3877
|
+
*/
|
|
3878
|
+
async waitForInit(initial) {
|
|
3879
|
+
await this._storage.waitForInit(initial);
|
|
3880
|
+
}
|
|
3881
|
+
/**
|
|
3882
|
+
* Reads the persisted recent signal using `symbol` as the entity key.
|
|
3883
|
+
*
|
|
3884
|
+
* @returns Promise resolving to recent signal or null if not found
|
|
3885
|
+
*/
|
|
3886
|
+
async readRecentData() {
|
|
3887
|
+
if (await this._storage.hasValue(this.symbol)) {
|
|
3888
|
+
return await this._storage.readValue(this.symbol);
|
|
3889
|
+
}
|
|
3890
|
+
return null;
|
|
3891
|
+
}
|
|
3892
|
+
/**
|
|
3893
|
+
* Writes the recent signal using `symbol` as the entity key.
|
|
3894
|
+
*
|
|
3895
|
+
* @param signalRow - Recent signal data to persist
|
|
3896
|
+
* @returns Promise that resolves when write is complete
|
|
3897
|
+
*/
|
|
3898
|
+
async writeRecentData(signalRow) {
|
|
3899
|
+
await this._storage.writeValue(this.symbol, signalRow);
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
/**
|
|
3903
|
+
* No-op IPersistRecentInstance implementation used by PersistRecentUtils.useDummy().
|
|
3904
|
+
* All reads return null, all writes are discarded.
|
|
3905
|
+
*/
|
|
3906
|
+
class PersistRecentDummyInstance {
|
|
3907
|
+
/**
|
|
3908
|
+
* No-op constructor.
|
|
3909
|
+
* Context arguments are accepted to satisfy TPersistRecentInstanceCtor.
|
|
3910
|
+
*/
|
|
3911
|
+
constructor(_symbol, _strategyName, _exchangeName, _frameName, _backtest) { }
|
|
3912
|
+
/**
|
|
3913
|
+
* No-op initialization.
|
|
3914
|
+
* @returns Promise that resolves immediately
|
|
3915
|
+
*/
|
|
3916
|
+
async waitForInit(_initial) { }
|
|
3917
|
+
/**
|
|
3918
|
+
* Always returns null (no recent signal).
|
|
3919
|
+
* @returns Promise resolving to null
|
|
3920
|
+
*/
|
|
3921
|
+
async readRecentData() { return null; }
|
|
3922
|
+
/**
|
|
3923
|
+
* No-op write (discards recent signal).
|
|
3924
|
+
* @returns Promise that resolves immediately
|
|
3925
|
+
*/
|
|
3926
|
+
async writeRecentData(_signalRow) { }
|
|
3927
|
+
}
|
|
2882
3928
|
/**
|
|
2883
3929
|
* Utility class for managing recent signal persistence.
|
|
2884
3930
|
*
|
|
@@ -2892,95 +3938,105 @@ const PersistMemoryAdapter = new PersistMemoryUtils();
|
|
|
2892
3938
|
*/
|
|
2893
3939
|
class PersistRecentUtils {
|
|
2894
3940
|
constructor() {
|
|
2895
|
-
this.PersistRecentFactory = PersistBase;
|
|
2896
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => this.createKeyParts(symbol, strategyName, exchangeName, frameName, backtest).join(":"), (symbol, strategyName, exchangeName, frameName, backtest) => Reflect.construct(this.PersistRecentFactory, [
|
|
2897
|
-
this.createKeyParts(symbol, strategyName, exchangeName, frameName, backtest).join("_"),
|
|
2898
|
-
`./dump/data/recent/`,
|
|
2899
|
-
]));
|
|
2900
3941
|
/**
|
|
2901
|
-
*
|
|
2902
|
-
*
|
|
2903
|
-
|
|
3942
|
+
* Constructor used to create per-context recent signal instances.
|
|
3943
|
+
* Replaceable via usePersistRecentAdapter() / useJson() / useDummy().
|
|
3944
|
+
*/
|
|
3945
|
+
this.PersistRecentInstanceCtor = PersistRecentInstance;
|
|
3946
|
+
/**
|
|
3947
|
+
* Memoized factory creating one IPersistRecentInstance per context tuple.
|
|
3948
|
+
*/
|
|
3949
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => this.createKey(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => Reflect.construct(this.PersistRecentInstanceCtor, [symbol, strategyName, exchangeName, frameName, backtest]));
|
|
3950
|
+
/**
|
|
3951
|
+
* Reads the latest recent signal for the given context.
|
|
3952
|
+
* Lazily initializes the instance on first access.
|
|
2904
3953
|
*
|
|
2905
3954
|
* @param symbol - Trading pair symbol
|
|
2906
3955
|
* @param strategyName - Strategy identifier
|
|
2907
3956
|
* @param exchangeName - Exchange identifier
|
|
2908
|
-
* @param frameName - Frame identifier
|
|
2909
|
-
* @
|
|
3957
|
+
* @param frameName - Frame identifier (may be empty)
|
|
3958
|
+
* @param backtest - True for backtest mode, false for live mode
|
|
3959
|
+
* @returns Promise resolving to recent signal or null if none persisted
|
|
2910
3960
|
*/
|
|
2911
3961
|
this.readRecentData = async (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
2912
3962
|
LOGGER_SERVICE$7.info(PERSIST_RECENT_UTILS_METHOD_NAME_READ_DATA);
|
|
2913
|
-
const key = this.
|
|
3963
|
+
const key = this.createKey(symbol, strategyName, exchangeName, frameName, backtest);
|
|
2914
3964
|
const isInitial = !this.getStorage.has(key);
|
|
2915
|
-
const
|
|
2916
|
-
await
|
|
2917
|
-
|
|
2918
|
-
return await stateStorage.readValue(symbol);
|
|
2919
|
-
}
|
|
2920
|
-
return null;
|
|
3965
|
+
const instance = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
|
|
3966
|
+
await instance.waitForInit(isInitial);
|
|
3967
|
+
return instance.readRecentData();
|
|
2921
3968
|
};
|
|
2922
3969
|
/**
|
|
2923
|
-
* Writes the latest recent signal
|
|
2924
|
-
*
|
|
2925
|
-
* Uses symbol as the entity ID within the per-context storage instance.
|
|
2926
|
-
* Uses atomic writes to prevent corruption on crashes.
|
|
3970
|
+
* Writes the latest recent signal for the given context.
|
|
3971
|
+
* Lazily initializes the instance on first access.
|
|
2927
3972
|
*
|
|
2928
3973
|
* @param signalRow - Recent signal data to persist
|
|
2929
3974
|
* @param symbol - Trading pair symbol
|
|
2930
3975
|
* @param strategyName - Strategy identifier
|
|
2931
3976
|
* @param exchangeName - Exchange identifier
|
|
2932
|
-
* @param frameName - Frame identifier
|
|
3977
|
+
* @param frameName - Frame identifier (may be empty)
|
|
3978
|
+
* @param backtest - True for backtest mode, false for live mode
|
|
2933
3979
|
* @returns Promise that resolves when write is complete
|
|
2934
3980
|
*/
|
|
2935
3981
|
this.writeRecentData = async (signalRow, symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
2936
3982
|
LOGGER_SERVICE$7.info(PERSIST_RECENT_UTILS_METHOD_NAME_WRITE_DATA);
|
|
2937
|
-
const key = this.
|
|
3983
|
+
const key = this.createKey(symbol, strategyName, exchangeName, frameName, backtest);
|
|
2938
3984
|
const isInitial = !this.getStorage.has(key);
|
|
2939
|
-
const
|
|
2940
|
-
await
|
|
2941
|
-
|
|
3985
|
+
const instance = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
|
|
3986
|
+
await instance.waitForInit(isInitial);
|
|
3987
|
+
return instance.writeRecentData(signalRow);
|
|
2942
3988
|
};
|
|
2943
3989
|
}
|
|
2944
|
-
|
|
3990
|
+
/**
|
|
3991
|
+
* Builds the composite memoization key for a recent signal context.
|
|
3992
|
+
* Includes optional frameName and the backtest/live mode flag.
|
|
3993
|
+
*
|
|
3994
|
+
* @param symbol - Trading pair symbol
|
|
3995
|
+
* @param strategyName - Strategy identifier
|
|
3996
|
+
* @param exchangeName - Exchange identifier
|
|
3997
|
+
* @param frameName - Frame identifier (omitted from key if empty)
|
|
3998
|
+
* @param backtest - True for backtest mode, false for live mode
|
|
3999
|
+
* @returns Composite key string
|
|
4000
|
+
*/
|
|
4001
|
+
createKey(symbol, strategyName, exchangeName, frameName, backtest) {
|
|
2945
4002
|
const parts = [symbol, strategyName, exchangeName];
|
|
2946
4003
|
if (frameName)
|
|
2947
4004
|
parts.push(frameName);
|
|
2948
4005
|
parts.push(backtest ? "backtest" : "live");
|
|
2949
|
-
return parts;
|
|
4006
|
+
return parts.join(":");
|
|
2950
4007
|
}
|
|
2951
4008
|
/**
|
|
2952
|
-
* Registers a custom
|
|
4009
|
+
* Registers a custom IPersistRecentInstance constructor.
|
|
4010
|
+
* Clears the memoization cache so subsequent calls use the new adapter.
|
|
2953
4011
|
*
|
|
2954
|
-
* @param Ctor - Custom
|
|
4012
|
+
* @param Ctor - Custom IPersistRecentInstance constructor
|
|
2955
4013
|
*/
|
|
2956
4014
|
usePersistRecentAdapter(Ctor) {
|
|
2957
4015
|
LOGGER_SERVICE$7.info(PERSIST_RECENT_UTILS_METHOD_NAME_USE_PERSIST_RECENT_ADAPTER);
|
|
2958
|
-
this.
|
|
4016
|
+
this.PersistRecentInstanceCtor = Ctor;
|
|
4017
|
+
this.getStorage.clear();
|
|
2959
4018
|
}
|
|
2960
4019
|
/**
|
|
2961
|
-
* Clears the memoized
|
|
2962
|
-
* Call
|
|
2963
|
-
* so new storage instances are created with the updated base path.
|
|
4020
|
+
* Clears the memoized instance cache.
|
|
4021
|
+
* Call when process.cwd() changes between strategy iterations.
|
|
2964
4022
|
*/
|
|
2965
4023
|
clear() {
|
|
2966
4024
|
LOGGER_SERVICE$7.log(PERSIST_RECENT_UTILS_METHOD_NAME_CLEAR);
|
|
2967
4025
|
this.getStorage.clear();
|
|
2968
4026
|
}
|
|
2969
4027
|
/**
|
|
2970
|
-
* Switches to the default
|
|
2971
|
-
* All future persistence writes will use JSON storage.
|
|
4028
|
+
* Switches to the default file-based PersistRecentInstance.
|
|
2972
4029
|
*/
|
|
2973
4030
|
useJson() {
|
|
2974
4031
|
LOGGER_SERVICE$7.log(PERSIST_RECENT_UTILS_METHOD_NAME_USE_JSON);
|
|
2975
|
-
this.usePersistRecentAdapter(
|
|
4032
|
+
this.usePersistRecentAdapter(PersistRecentInstance);
|
|
2976
4033
|
}
|
|
2977
4034
|
/**
|
|
2978
|
-
* Switches to
|
|
2979
|
-
* All future persistence writes will be no-ops.
|
|
4035
|
+
* Switches to PersistRecentDummyInstance (all operations are no-ops).
|
|
2980
4036
|
*/
|
|
2981
4037
|
useDummy() {
|
|
2982
4038
|
LOGGER_SERVICE$7.log(PERSIST_RECENT_UTILS_METHOD_NAME_USE_DUMMY);
|
|
2983
|
-
this.usePersistRecentAdapter(
|
|
4039
|
+
this.usePersistRecentAdapter(PersistRecentDummyInstance);
|
|
2984
4040
|
}
|
|
2985
4041
|
}
|
|
2986
4042
|
/**
|
|
@@ -2988,6 +4044,99 @@ class PersistRecentUtils {
|
|
|
2988
4044
|
* Used by RecentPersistBacktestUtils/RecentPersistLiveUtils for recent signal persistence.
|
|
2989
4045
|
*/
|
|
2990
4046
|
const PersistRecentAdapter = new PersistRecentUtils();
|
|
4047
|
+
/**
|
|
4048
|
+
* Default file-based implementation of IPersistStateInstance.
|
|
4049
|
+
*
|
|
4050
|
+
* Features:
|
|
4051
|
+
* - Wraps PersistBase for atomic JSON writes
|
|
4052
|
+
* - Uses bucketName as entity ID within a per-signal PersistBase
|
|
4053
|
+
* - dispose is a no-op (memo cache is managed by PersistStateUtils)
|
|
4054
|
+
*
|
|
4055
|
+
* @example
|
|
4056
|
+
* ```typescript
|
|
4057
|
+
* const instance = new PersistStateInstance("signal-1", "counter");
|
|
4058
|
+
* await instance.waitForInit(true);
|
|
4059
|
+
* await instance.writeStateData({ id: "counter", data: { count: 1 } });
|
|
4060
|
+
* const state = await instance.readStateData();
|
|
4061
|
+
* ```
|
|
4062
|
+
*/
|
|
4063
|
+
class PersistStateInstance {
|
|
4064
|
+
/**
|
|
4065
|
+
* Creates new state persistence instance.
|
|
4066
|
+
*
|
|
4067
|
+
* @param signalId - Signal identifier (folder name under state/)
|
|
4068
|
+
* @param bucketName - Bucket name (file name)
|
|
4069
|
+
*/
|
|
4070
|
+
constructor(signalId, bucketName) {
|
|
4071
|
+
this.signalId = signalId;
|
|
4072
|
+
this.bucketName = bucketName;
|
|
4073
|
+
this._storage = new PersistBase(bucketName, `./dump/state/${signalId}/`);
|
|
4074
|
+
}
|
|
4075
|
+
/**
|
|
4076
|
+
* Initializes the underlying PersistBase storage.
|
|
4077
|
+
*
|
|
4078
|
+
* @param initial - Whether this is the first initialization
|
|
4079
|
+
* @returns Promise that resolves when initialization is complete
|
|
4080
|
+
*/
|
|
4081
|
+
async waitForInit(initial) {
|
|
4082
|
+
await this._storage.waitForInit(initial);
|
|
4083
|
+
}
|
|
4084
|
+
/**
|
|
4085
|
+
* Reads the persisted state using `bucketName` as the entity key.
|
|
4086
|
+
*
|
|
4087
|
+
* @returns Promise resolving to state data or null if not found
|
|
4088
|
+
*/
|
|
4089
|
+
async readStateData() {
|
|
4090
|
+
if (await this._storage.hasValue(this.bucketName)) {
|
|
4091
|
+
return await this._storage.readValue(this.bucketName);
|
|
4092
|
+
}
|
|
4093
|
+
return null;
|
|
4094
|
+
}
|
|
4095
|
+
/**
|
|
4096
|
+
* Writes the state using `bucketName` as the entity key.
|
|
4097
|
+
*
|
|
4098
|
+
* @param data - State data to persist
|
|
4099
|
+
* @returns Promise that resolves when write is complete
|
|
4100
|
+
*/
|
|
4101
|
+
async writeStateData(data) {
|
|
4102
|
+
await this._storage.writeValue(this.bucketName, data);
|
|
4103
|
+
}
|
|
4104
|
+
/**
|
|
4105
|
+
* No-op for the default file-based implementation.
|
|
4106
|
+
* Resource cleanup (memo cache invalidation) is handled by PersistStateUtils.dispose().
|
|
4107
|
+
*/
|
|
4108
|
+
dispose() { }
|
|
4109
|
+
}
|
|
4110
|
+
/**
|
|
4111
|
+
* No-op IPersistStateInstance implementation used by PersistStateUtils.useDummy().
|
|
4112
|
+
* All reads return null, all writes are discarded.
|
|
4113
|
+
*/
|
|
4114
|
+
class PersistStateDummyInstance {
|
|
4115
|
+
/**
|
|
4116
|
+
* No-op constructor.
|
|
4117
|
+
* Context arguments are accepted to satisfy TPersistStateInstanceCtor.
|
|
4118
|
+
*/
|
|
4119
|
+
constructor(_signalId, _bucketName) { }
|
|
4120
|
+
/**
|
|
4121
|
+
* No-op initialization.
|
|
4122
|
+
* @returns Promise that resolves immediately
|
|
4123
|
+
*/
|
|
4124
|
+
async waitForInit(_initial) { }
|
|
4125
|
+
/**
|
|
4126
|
+
* Always returns null (no persisted state).
|
|
4127
|
+
* @returns Promise resolving to null
|
|
4128
|
+
*/
|
|
4129
|
+
async readStateData() { return null; }
|
|
4130
|
+
/**
|
|
4131
|
+
* No-op write (discards state).
|
|
4132
|
+
* @returns Promise that resolves immediately
|
|
4133
|
+
*/
|
|
4134
|
+
async writeStateData(_data) { }
|
|
4135
|
+
/**
|
|
4136
|
+
* No-op dispose.
|
|
4137
|
+
*/
|
|
4138
|
+
dispose() { }
|
|
4139
|
+
}
|
|
2991
4140
|
/**
|
|
2992
4141
|
* Utility class for managing state persistence.
|
|
2993
4142
|
*
|
|
@@ -3002,96 +4151,89 @@ const PersistRecentAdapter = new PersistRecentUtils();
|
|
|
3002
4151
|
*/
|
|
3003
4152
|
class PersistStateUtils {
|
|
3004
4153
|
constructor() {
|
|
3005
|
-
this.PersistStateFactory = PersistBase;
|
|
3006
|
-
this.getStateStorage = memoize(([signalId, bucketName]) => `${signalId}:${bucketName}`, (signalId, bucketName) => Reflect.construct(this.PersistStateFactory, [
|
|
3007
|
-
bucketName,
|
|
3008
|
-
`./dump/state/${signalId}/`,
|
|
3009
|
-
]));
|
|
3010
4154
|
/**
|
|
3011
|
-
*
|
|
4155
|
+
* Constructor used to create per-context state instances.
|
|
4156
|
+
* Replaceable via usePersistStateAdapter() / useJson() / useDummy().
|
|
4157
|
+
*/
|
|
4158
|
+
this.PersistStateInstanceCtor = PersistStateInstance;
|
|
4159
|
+
/**
|
|
4160
|
+
* Memoized factory creating one IPersistStateInstance per (signalId, bucketName) pair.
|
|
4161
|
+
*/
|
|
4162
|
+
this.getStateStorage = memoize(([signalId, bucketName]) => `${signalId}:${bucketName}`, (signalId, bucketName) => Reflect.construct(this.PersistStateInstanceCtor, [signalId, bucketName]));
|
|
4163
|
+
/**
|
|
4164
|
+
* Initializes the state storage for the given context.
|
|
4165
|
+
* Skips initialization when `initial` is false (used to gate first-time setup).
|
|
3012
4166
|
*
|
|
3013
4167
|
* @param signalId - Signal identifier
|
|
3014
4168
|
* @param bucketName - Bucket name
|
|
3015
4169
|
* @param initial - Whether this is the first initialization
|
|
4170
|
+
* @returns Promise that resolves when initialization is complete
|
|
3016
4171
|
*/
|
|
3017
4172
|
this.waitForInit = async (signalId, bucketName, initial) => {
|
|
3018
|
-
LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_WAIT_FOR_INIT, {
|
|
3019
|
-
signalId,
|
|
3020
|
-
bucketName,
|
|
3021
|
-
initial,
|
|
3022
|
-
});
|
|
4173
|
+
LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_WAIT_FOR_INIT, { signalId, bucketName, initial });
|
|
3023
4174
|
const key = `${signalId}:${bucketName}`;
|
|
3024
4175
|
const isInitial = initial && !this.getStateStorage.has(key);
|
|
3025
|
-
const
|
|
3026
|
-
await
|
|
4176
|
+
const instance = this.getStateStorage(signalId, bucketName);
|
|
4177
|
+
await instance.waitForInit(isInitial);
|
|
3027
4178
|
};
|
|
3028
4179
|
/**
|
|
3029
|
-
* Reads
|
|
4180
|
+
* Reads persisted state for the given context.
|
|
4181
|
+
* Lazily initializes the instance on first access.
|
|
3030
4182
|
*
|
|
3031
4183
|
* @param signalId - Signal identifier
|
|
3032
4184
|
* @param bucketName - Bucket name
|
|
3033
|
-
* @returns Promise resolving to
|
|
4185
|
+
* @returns Promise resolving to state data or null if none persisted
|
|
3034
4186
|
*/
|
|
3035
4187
|
this.readStateData = async (signalId, bucketName) => {
|
|
3036
|
-
LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_READ_DATA, {
|
|
3037
|
-
signalId,
|
|
3038
|
-
bucketName,
|
|
3039
|
-
});
|
|
4188
|
+
LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_READ_DATA, { signalId, bucketName });
|
|
3040
4189
|
const key = `${signalId}:${bucketName}`;
|
|
3041
4190
|
const isInitial = !this.getStateStorage.has(key);
|
|
3042
|
-
const
|
|
3043
|
-
await
|
|
3044
|
-
|
|
3045
|
-
return await stateStorage.readValue(bucketName);
|
|
3046
|
-
}
|
|
3047
|
-
return null;
|
|
4191
|
+
const instance = this.getStateStorage(signalId, bucketName);
|
|
4192
|
+
await instance.waitForInit(isInitial);
|
|
4193
|
+
return instance.readStateData();
|
|
3048
4194
|
};
|
|
3049
4195
|
/**
|
|
3050
|
-
* Writes
|
|
4196
|
+
* Writes state for the given context.
|
|
4197
|
+
* Lazily initializes the instance on first access.
|
|
3051
4198
|
*
|
|
3052
|
-
* @param data -
|
|
4199
|
+
* @param data - State data to persist
|
|
3053
4200
|
* @param signalId - Signal identifier
|
|
3054
4201
|
* @param bucketName - Bucket name
|
|
4202
|
+
* @returns Promise that resolves when write is complete
|
|
3055
4203
|
*/
|
|
3056
4204
|
this.writeStateData = async (data, signalId, bucketName) => {
|
|
3057
|
-
LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_WRITE_DATA, {
|
|
3058
|
-
signalId,
|
|
3059
|
-
bucketName,
|
|
3060
|
-
});
|
|
4205
|
+
LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_WRITE_DATA, { signalId, bucketName });
|
|
3061
4206
|
const key = `${signalId}:${bucketName}`;
|
|
3062
4207
|
const isInitial = !this.getStateStorage.has(key);
|
|
3063
|
-
const
|
|
3064
|
-
await
|
|
3065
|
-
|
|
4208
|
+
const instance = this.getStateStorage(signalId, bucketName);
|
|
4209
|
+
await instance.waitForInit(isInitial);
|
|
4210
|
+
return instance.writeStateData(data);
|
|
3066
4211
|
};
|
|
3067
4212
|
/**
|
|
3068
|
-
* Switches to
|
|
3069
|
-
* All future persistence writes will be no-ops.
|
|
4213
|
+
* Switches to PersistStateDummyInstance (all operations are no-ops).
|
|
3070
4214
|
*/
|
|
3071
4215
|
this.useDummy = () => {
|
|
3072
4216
|
LOGGER_SERVICE$7.log(PERSIST_STATE_UTILS_METHOD_NAME_USE_DUMMY);
|
|
3073
|
-
this.usePersistStateAdapter(
|
|
4217
|
+
this.usePersistStateAdapter(PersistStateDummyInstance);
|
|
3074
4218
|
};
|
|
3075
4219
|
/**
|
|
3076
|
-
* Switches to the default
|
|
3077
|
-
* All future persistence writes will use JSON storage.
|
|
4220
|
+
* Switches to the default file-based PersistStateInstance.
|
|
3078
4221
|
*/
|
|
3079
4222
|
this.useJson = () => {
|
|
3080
4223
|
LOGGER_SERVICE$7.log(PERSIST_STATE_UTILS_METHOD_NAME_USE_JSON);
|
|
3081
|
-
this.usePersistStateAdapter(
|
|
4224
|
+
this.usePersistStateAdapter(PersistStateInstance);
|
|
3082
4225
|
};
|
|
3083
4226
|
/**
|
|
3084
|
-
* Clears the memoized
|
|
3085
|
-
* Call
|
|
3086
|
-
* so new storage instances are created with the updated base path.
|
|
4227
|
+
* Clears the memoized instance cache.
|
|
4228
|
+
* Call when process.cwd() changes between strategy iterations.
|
|
3087
4229
|
*/
|
|
3088
4230
|
this.clear = () => {
|
|
3089
4231
|
LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_CLEAR);
|
|
3090
4232
|
this.getStateStorage.clear();
|
|
3091
4233
|
};
|
|
3092
4234
|
/**
|
|
3093
|
-
*
|
|
3094
|
-
* Call
|
|
4235
|
+
* Drops the memoized instance for the given context.
|
|
4236
|
+
* Call when a signal is removed to clean up its associated storage entry.
|
|
3095
4237
|
*
|
|
3096
4238
|
* @param signalId - Signal identifier
|
|
3097
4239
|
* @param bucketName - Bucket name
|
|
@@ -3103,13 +4245,15 @@ class PersistStateUtils {
|
|
|
3103
4245
|
};
|
|
3104
4246
|
}
|
|
3105
4247
|
/**
|
|
3106
|
-
* Registers a custom
|
|
4248
|
+
* Registers a custom IPersistStateInstance constructor.
|
|
4249
|
+
* Clears the memoization cache so subsequent calls use the new adapter.
|
|
3107
4250
|
*
|
|
3108
|
-
* @param Ctor - Custom
|
|
4251
|
+
* @param Ctor - Custom IPersistStateInstance constructor
|
|
3109
4252
|
*/
|
|
3110
4253
|
usePersistStateAdapter(Ctor) {
|
|
3111
4254
|
LOGGER_SERVICE$7.info(PERSIST_STATE_UTILS_METHOD_NAME_USE_PERSIST_STATE_ADAPTER);
|
|
3112
|
-
this.
|
|
4255
|
+
this.PersistStateInstanceCtor = Ctor;
|
|
4256
|
+
this.getStateStorage.clear();
|
|
3113
4257
|
}
|
|
3114
4258
|
}
|
|
3115
4259
|
/**
|
|
@@ -3117,6 +4261,101 @@ class PersistStateUtils {
|
|
|
3117
4261
|
* Used by StatePersistInstance for crash-safe state persistence.
|
|
3118
4262
|
*/
|
|
3119
4263
|
const PersistStateAdapter = new PersistStateUtils();
|
|
4264
|
+
/**
|
|
4265
|
+
* Default file-based implementation of IPersistSessionInstance.
|
|
4266
|
+
*
|
|
4267
|
+
* Features:
|
|
4268
|
+
* - Wraps PersistBase for atomic JSON writes
|
|
4269
|
+
* - Uses frameName as entity ID within a per-strategy/exchange PersistBase
|
|
4270
|
+
* - dispose is a no-op (memo cache is managed by PersistSessionUtils)
|
|
4271
|
+
*
|
|
4272
|
+
* @example
|
|
4273
|
+
* ```typescript
|
|
4274
|
+
* const instance = new PersistSessionInstance("my-strategy", "binance", "frame-1");
|
|
4275
|
+
* await instance.waitForInit(true);
|
|
4276
|
+
* await instance.writeSessionData({ id: "frame-1", data: { session: "state" } });
|
|
4277
|
+
* const session = await instance.readSessionData();
|
|
4278
|
+
* ```
|
|
4279
|
+
*/
|
|
4280
|
+
class PersistSessionInstance {
|
|
4281
|
+
/**
|
|
4282
|
+
* Creates new session persistence instance.
|
|
4283
|
+
*
|
|
4284
|
+
* @param strategyName - Strategy identifier
|
|
4285
|
+
* @param exchangeName - Exchange identifier
|
|
4286
|
+
* @param frameName - Frame identifier (also used as entity ID)
|
|
4287
|
+
*/
|
|
4288
|
+
constructor(strategyName, exchangeName, frameName) {
|
|
4289
|
+
this.strategyName = strategyName;
|
|
4290
|
+
this.exchangeName = exchangeName;
|
|
4291
|
+
this.frameName = frameName;
|
|
4292
|
+
this._storage = new PersistBase(frameName, `./dump/session/${strategyName}/${exchangeName}/`);
|
|
4293
|
+
}
|
|
4294
|
+
/**
|
|
4295
|
+
* Initializes the underlying PersistBase storage.
|
|
4296
|
+
*
|
|
4297
|
+
* @param initial - Whether this is the first initialization
|
|
4298
|
+
* @returns Promise that resolves when initialization is complete
|
|
4299
|
+
*/
|
|
4300
|
+
async waitForInit(initial) {
|
|
4301
|
+
await this._storage.waitForInit(initial);
|
|
4302
|
+
}
|
|
4303
|
+
/**
|
|
4304
|
+
* Reads the persisted session data using `frameName` as the entity key.
|
|
4305
|
+
*
|
|
4306
|
+
* @returns Promise resolving to session data or null if not found
|
|
4307
|
+
*/
|
|
4308
|
+
async readSessionData() {
|
|
4309
|
+
if (await this._storage.hasValue(this.frameName)) {
|
|
4310
|
+
return await this._storage.readValue(this.frameName);
|
|
4311
|
+
}
|
|
4312
|
+
return null;
|
|
4313
|
+
}
|
|
4314
|
+
/**
|
|
4315
|
+
* Writes the session data using `frameName` as the entity key.
|
|
4316
|
+
*
|
|
4317
|
+
* @param data - Session data to persist
|
|
4318
|
+
* @returns Promise that resolves when write is complete
|
|
4319
|
+
*/
|
|
4320
|
+
async writeSessionData(data) {
|
|
4321
|
+
await this._storage.writeValue(this.frameName, data);
|
|
4322
|
+
}
|
|
4323
|
+
/**
|
|
4324
|
+
* No-op for the default file-based implementation.
|
|
4325
|
+
* Resource cleanup (memo cache invalidation) is handled by PersistSessionUtils.dispose().
|
|
4326
|
+
*/
|
|
4327
|
+
dispose() { }
|
|
4328
|
+
}
|
|
4329
|
+
/**
|
|
4330
|
+
* No-op IPersistSessionInstance implementation used by PersistSessionUtils.useDummy().
|
|
4331
|
+
* All reads return null, all writes are discarded.
|
|
4332
|
+
*/
|
|
4333
|
+
class PersistSessionDummyInstance {
|
|
4334
|
+
/**
|
|
4335
|
+
* No-op constructor.
|
|
4336
|
+
* Context arguments are accepted to satisfy TPersistSessionInstanceCtor.
|
|
4337
|
+
*/
|
|
4338
|
+
constructor(_strategyName, _exchangeName, _frameName) { }
|
|
4339
|
+
/**
|
|
4340
|
+
* No-op initialization.
|
|
4341
|
+
* @returns Promise that resolves immediately
|
|
4342
|
+
*/
|
|
4343
|
+
async waitForInit(_initial) { }
|
|
4344
|
+
/**
|
|
4345
|
+
* Always returns null (no persisted session).
|
|
4346
|
+
* @returns Promise resolving to null
|
|
4347
|
+
*/
|
|
4348
|
+
async readSessionData() { return null; }
|
|
4349
|
+
/**
|
|
4350
|
+
* No-op write (discards session data).
|
|
4351
|
+
* @returns Promise that resolves immediately
|
|
4352
|
+
*/
|
|
4353
|
+
async writeSessionData(_data) { }
|
|
4354
|
+
/**
|
|
4355
|
+
* No-op dispose.
|
|
4356
|
+
*/
|
|
4357
|
+
dispose() { }
|
|
4358
|
+
}
|
|
3120
4359
|
/**
|
|
3121
4360
|
* Utility class for managing session persistence.
|
|
3122
4361
|
*
|
|
@@ -3131,102 +4370,93 @@ const PersistStateAdapter = new PersistStateUtils();
|
|
|
3131
4370
|
*/
|
|
3132
4371
|
class PersistSessionUtils {
|
|
3133
4372
|
constructor() {
|
|
3134
|
-
this.PersistSessionFactory = PersistBase;
|
|
3135
|
-
this.getSessionStorage = memoize(([strategyName, exchangeName, frameName]) => `${strategyName}:${exchangeName}:${frameName}`, (strategyName, exchangeName, frameName) => Reflect.construct(this.PersistSessionFactory, [
|
|
3136
|
-
frameName,
|
|
3137
|
-
`./dump/session/${strategyName}/${exchangeName}/`,
|
|
3138
|
-
]));
|
|
3139
4373
|
/**
|
|
3140
|
-
*
|
|
4374
|
+
* Constructor used to create per-context session instances.
|
|
4375
|
+
* Replaceable via usePersistSessionAdapter() / useJson() / useDummy().
|
|
4376
|
+
*/
|
|
4377
|
+
this.PersistSessionInstanceCtor = PersistSessionInstance;
|
|
4378
|
+
/**
|
|
4379
|
+
* Memoized factory creating one IPersistSessionInstance per
|
|
4380
|
+
* (strategyName, exchangeName, frameName) triple.
|
|
4381
|
+
*/
|
|
4382
|
+
this.getSessionStorage = memoize(([strategyName, exchangeName, frameName]) => `${strategyName}:${exchangeName}:${frameName}`, (strategyName, exchangeName, frameName) => Reflect.construct(this.PersistSessionInstanceCtor, [strategyName, exchangeName, frameName]));
|
|
4383
|
+
/**
|
|
4384
|
+
* Initializes the session storage for the given context.
|
|
4385
|
+
* Skips initialization when `initial` is false (used to gate first-time setup).
|
|
3141
4386
|
*
|
|
3142
4387
|
* @param strategyName - Strategy identifier
|
|
3143
4388
|
* @param exchangeName - Exchange identifier
|
|
3144
4389
|
* @param frameName - Frame identifier
|
|
3145
4390
|
* @param initial - Whether this is the first initialization
|
|
4391
|
+
* @returns Promise that resolves when initialization is complete
|
|
3146
4392
|
*/
|
|
3147
4393
|
this.waitForInit = async (strategyName, exchangeName, frameName, initial) => {
|
|
3148
|
-
LOGGER_SERVICE$7.info(PERSIST_SESSION_UTILS_METHOD_NAME_WAIT_FOR_INIT, {
|
|
3149
|
-
strategyName,
|
|
3150
|
-
exchangeName,
|
|
3151
|
-
frameName,
|
|
3152
|
-
initial,
|
|
3153
|
-
});
|
|
4394
|
+
LOGGER_SERVICE$7.info(PERSIST_SESSION_UTILS_METHOD_NAME_WAIT_FOR_INIT, { strategyName, exchangeName, frameName, initial });
|
|
3154
4395
|
const key = `${strategyName}:${exchangeName}:${frameName}`;
|
|
3155
4396
|
const isInitial = initial && !this.getSessionStorage.has(key);
|
|
3156
|
-
const
|
|
3157
|
-
await
|
|
4397
|
+
const instance = this.getSessionStorage(strategyName, exchangeName, frameName);
|
|
4398
|
+
await instance.waitForInit(isInitial);
|
|
3158
4399
|
};
|
|
3159
4400
|
/**
|
|
3160
|
-
* Reads
|
|
4401
|
+
* Reads persisted session data for the given context.
|
|
4402
|
+
* Lazily initializes the instance on first access.
|
|
3161
4403
|
*
|
|
3162
4404
|
* @param strategyName - Strategy identifier
|
|
3163
4405
|
* @param exchangeName - Exchange identifier
|
|
3164
4406
|
* @param frameName - Frame identifier
|
|
3165
|
-
* @returns Promise resolving to
|
|
4407
|
+
* @returns Promise resolving to session data or null if none persisted
|
|
3166
4408
|
*/
|
|
3167
4409
|
this.readSessionData = async (strategyName, exchangeName, frameName) => {
|
|
3168
|
-
LOGGER_SERVICE$7.info(PERSIST_SESSION_UTILS_METHOD_NAME_READ_DATA, {
|
|
3169
|
-
strategyName,
|
|
3170
|
-
exchangeName,
|
|
3171
|
-
frameName,
|
|
3172
|
-
});
|
|
4410
|
+
LOGGER_SERVICE$7.info(PERSIST_SESSION_UTILS_METHOD_NAME_READ_DATA, { strategyName, exchangeName, frameName });
|
|
3173
4411
|
const key = `${strategyName}:${exchangeName}:${frameName}`;
|
|
3174
4412
|
const isInitial = !this.getSessionStorage.has(key);
|
|
3175
|
-
const
|
|
3176
|
-
await
|
|
3177
|
-
|
|
3178
|
-
return await sessionStorage.readValue(frameName);
|
|
3179
|
-
}
|
|
3180
|
-
return null;
|
|
4413
|
+
const instance = this.getSessionStorage(strategyName, exchangeName, frameName);
|
|
4414
|
+
await instance.waitForInit(isInitial);
|
|
4415
|
+
return instance.readSessionData();
|
|
3181
4416
|
};
|
|
3182
4417
|
/**
|
|
3183
|
-
* Writes
|
|
4418
|
+
* Writes session data for the given context.
|
|
4419
|
+
* Lazily initializes the instance on first access.
|
|
3184
4420
|
*
|
|
3185
|
-
* @param data -
|
|
4421
|
+
* @param data - Session data to persist
|
|
3186
4422
|
* @param strategyName - Strategy identifier
|
|
3187
4423
|
* @param exchangeName - Exchange identifier
|
|
3188
4424
|
* @param frameName - Frame identifier
|
|
4425
|
+
* @returns Promise that resolves when write is complete
|
|
3189
4426
|
*/
|
|
3190
4427
|
this.writeSessionData = async (data, strategyName, exchangeName, frameName) => {
|
|
3191
|
-
LOGGER_SERVICE$7.info(PERSIST_SESSION_UTILS_METHOD_NAME_WRITE_DATA, {
|
|
3192
|
-
strategyName,
|
|
3193
|
-
exchangeName,
|
|
3194
|
-
frameName,
|
|
3195
|
-
});
|
|
4428
|
+
LOGGER_SERVICE$7.info(PERSIST_SESSION_UTILS_METHOD_NAME_WRITE_DATA, { strategyName, exchangeName, frameName });
|
|
3196
4429
|
const key = `${strategyName}:${exchangeName}:${frameName}`;
|
|
3197
4430
|
const isInitial = !this.getSessionStorage.has(key);
|
|
3198
|
-
const
|
|
3199
|
-
await
|
|
3200
|
-
|
|
4431
|
+
const instance = this.getSessionStorage(strategyName, exchangeName, frameName);
|
|
4432
|
+
await instance.waitForInit(isInitial);
|
|
4433
|
+
return instance.writeSessionData(data);
|
|
3201
4434
|
};
|
|
3202
4435
|
/**
|
|
3203
|
-
* Switches to
|
|
3204
|
-
* All future persistence writes will be no-ops.
|
|
4436
|
+
* Switches to PersistSessionDummyInstance (all operations are no-ops).
|
|
3205
4437
|
*/
|
|
3206
4438
|
this.useDummy = () => {
|
|
3207
4439
|
LOGGER_SERVICE$7.log(PERSIST_SESSION_UTILS_METHOD_NAME_USE_DUMMY);
|
|
3208
|
-
this.usePersistSessionAdapter(
|
|
4440
|
+
this.usePersistSessionAdapter(PersistSessionDummyInstance);
|
|
3209
4441
|
};
|
|
3210
4442
|
/**
|
|
3211
|
-
* Switches to the default
|
|
3212
|
-
* All future persistence writes will use JSON storage.
|
|
4443
|
+
* Switches to the default file-based PersistSessionInstance.
|
|
3213
4444
|
*/
|
|
3214
4445
|
this.useJson = () => {
|
|
3215
4446
|
LOGGER_SERVICE$7.log(PERSIST_SESSION_UTILS_METHOD_NAME_USE_JSON);
|
|
3216
|
-
this.usePersistSessionAdapter(
|
|
4447
|
+
this.usePersistSessionAdapter(PersistSessionInstance);
|
|
3217
4448
|
};
|
|
3218
4449
|
/**
|
|
3219
|
-
* Clears the memoized
|
|
3220
|
-
* Call
|
|
3221
|
-
* so new storage instances are created with the updated base path.
|
|
4450
|
+
* Clears the memoized instance cache.
|
|
4451
|
+
* Call when process.cwd() changes between strategy iterations.
|
|
3222
4452
|
*/
|
|
3223
4453
|
this.clear = () => {
|
|
3224
4454
|
LOGGER_SERVICE$7.info(PERSIST_SESSION_UTILS_METHOD_NAME_CLEAR);
|
|
3225
4455
|
this.getSessionStorage.clear();
|
|
3226
4456
|
};
|
|
3227
4457
|
/**
|
|
3228
|
-
*
|
|
3229
|
-
* Call
|
|
4458
|
+
* Drops the memoized instance for the given context.
|
|
4459
|
+
* Call when a session is removed to clean up its associated storage entry.
|
|
3230
4460
|
*
|
|
3231
4461
|
* @param strategyName - Strategy identifier
|
|
3232
4462
|
* @param exchangeName - Exchange identifier
|
|
@@ -3239,13 +4469,15 @@ class PersistSessionUtils {
|
|
|
3239
4469
|
};
|
|
3240
4470
|
}
|
|
3241
4471
|
/**
|
|
3242
|
-
* Registers a custom
|
|
4472
|
+
* Registers a custom IPersistSessionInstance constructor.
|
|
4473
|
+
* Clears the memoization cache so subsequent calls use the new adapter.
|
|
3243
4474
|
*
|
|
3244
|
-
* @param Ctor - Custom
|
|
4475
|
+
* @param Ctor - Custom IPersistSessionInstance constructor
|
|
3245
4476
|
*/
|
|
3246
4477
|
usePersistSessionAdapter(Ctor) {
|
|
3247
4478
|
LOGGER_SERVICE$7.info(PERSIST_SESSION_UTILS_METHOD_NAME_USE_PERSIST_SESSION_ADAPTER);
|
|
3248
|
-
this.
|
|
4479
|
+
this.PersistSessionInstanceCtor = Ctor;
|
|
4480
|
+
this.getSessionStorage.clear();
|
|
3249
4481
|
}
|
|
3250
4482
|
}
|
|
3251
4483
|
/**
|
|
@@ -6677,7 +7909,7 @@ const CALL_PARTIAL_CLEAR_FN = trycatch(beginTime(async (self, symbol, signal, cu
|
|
|
6677
7909
|
});
|
|
6678
7910
|
const CALL_RISK_CHECK_SIGNAL_FN = trycatch(beginTime(async (self, symbol, pendingSignal, currentPrice, timestamp, backtest) => {
|
|
6679
7911
|
return await ExecutionContextService.runInContext(async () => {
|
|
6680
|
-
return await self.params.risk.
|
|
7912
|
+
return await self.params.risk.checkSignalAndReserve({
|
|
6681
7913
|
currentSignal: TO_PUBLIC_SIGNAL("scheduled", pendingSignal, currentPrice),
|
|
6682
7914
|
symbol: symbol,
|
|
6683
7915
|
strategyName: self.params.method.context.strategyName,
|
|
@@ -10558,6 +11790,7 @@ class ClientStrategy {
|
|
|
10558
11790
|
}
|
|
10559
11791
|
|
|
10560
11792
|
const RISK_METHOD_NAME_CHECK_SIGNAL = "MergeRisk.checkSignal";
|
|
11793
|
+
const RISK_METHOD_NAME_CHECK_SIGNAL_AND_RESERVE = "MergeRisk.checkSignalAndReserve";
|
|
10561
11794
|
const RISK_METHOD_NAME_ADD_SIGNAL = "MergeRisk.addSignal";
|
|
10562
11795
|
const RISK_METHOD_NAME_REMOVE_SIGNAL = "MergeRisk.removeSignal";
|
|
10563
11796
|
/** Logger service injected as DI singleton */
|
|
@@ -10616,7 +11849,7 @@ class MergeRisk {
|
|
|
10616
11849
|
* @param params - Risk check parameters (symbol, strategy, position, exchange)
|
|
10617
11850
|
* @returns Promise resolving to true if all risks approve, false if any risk rejects
|
|
10618
11851
|
*/
|
|
10619
|
-
async checkSignal(params) {
|
|
11852
|
+
async checkSignal(params, options = {}) {
|
|
10620
11853
|
LOGGER_SERVICE$5.info(RISK_METHOD_NAME_CHECK_SIGNAL, {
|
|
10621
11854
|
params,
|
|
10622
11855
|
});
|
|
@@ -10624,6 +11857,36 @@ class MergeRisk {
|
|
|
10624
11857
|
if (await not(risk.checkSignal({
|
|
10625
11858
|
...params,
|
|
10626
11859
|
riskName,
|
|
11860
|
+
}, options))) {
|
|
11861
|
+
return false;
|
|
11862
|
+
}
|
|
11863
|
+
}
|
|
11864
|
+
return true;
|
|
11865
|
+
}
|
|
11866
|
+
/**
|
|
11867
|
+
* Concurrency-safe variant of {@link checkSignal} — validates the signal AND
|
|
11868
|
+
* reserves a placeholder in every child risk's active position map atomically.
|
|
11869
|
+
*
|
|
11870
|
+
* Iterates child risks sequentially. On the first rejection it returns false
|
|
11871
|
+
* immediately; child risks checked earlier in the loop are left with their
|
|
11872
|
+
* reservations in place — `removeSignal` on the parent will roll them back
|
|
11873
|
+
* (it propagates to all children unconditionally).
|
|
11874
|
+
*
|
|
11875
|
+
* Use from strategy execution paths where the caller will follow up with
|
|
11876
|
+
* `addSignal` on success. See {@link IRisk.checkSignalAndReserve} for the
|
|
11877
|
+
* full rationale on why reserving inside the lock is necessary.
|
|
11878
|
+
*
|
|
11879
|
+
* @param params - Risk check parameters (symbol, strategy, position, exchange)
|
|
11880
|
+
* @returns Promise resolving to true if all risks approve (and reserved), false if any risk rejects
|
|
11881
|
+
*/
|
|
11882
|
+
async checkSignalAndReserve(params) {
|
|
11883
|
+
LOGGER_SERVICE$5.info(RISK_METHOD_NAME_CHECK_SIGNAL_AND_RESERVE, {
|
|
11884
|
+
params,
|
|
11885
|
+
});
|
|
11886
|
+
for (const [riskName, risk] of Object.entries(this._riskMap)) {
|
|
11887
|
+
if (await not(risk.checkSignalAndReserve({
|
|
11888
|
+
...params,
|
|
11889
|
+
riskName,
|
|
10627
11890
|
}))) {
|
|
10628
11891
|
return false;
|
|
10629
11892
|
}
|
|
@@ -10733,6 +11996,7 @@ const CALL_SIGNAL_EMIT_FN = trycatch(beginTime(async (self, tick, context, isBac
|
|
|
10733
11996
|
*/
|
|
10734
11997
|
const NOOP_RISK = {
|
|
10735
11998
|
checkSignal: () => Promise.resolve(true),
|
|
11999
|
+
checkSignalAndReserve: () => Promise.resolve(true),
|
|
10736
12000
|
addSignal: () => Promise.resolve(),
|
|
10737
12001
|
removeSignal: () => Promise.resolve(),
|
|
10738
12002
|
};
|
|
@@ -12908,6 +14172,8 @@ const alignToInterval = (date, interval) => {
|
|
|
12908
14172
|
return new Date(Math.floor(date.getTime() / intervalMs) * intervalMs);
|
|
12909
14173
|
};
|
|
12910
14174
|
|
|
14175
|
+
/** Used to prevent race confition between concurent strategies */
|
|
14176
|
+
const RISK_LOCK = new Lock();
|
|
12911
14177
|
/** Symbol indicating that positions need to be fetched from persistence */
|
|
12912
14178
|
const POSITION_NEED_FETCH = Symbol("risk-need-fetch");
|
|
12913
14179
|
/** Get timestamp from execution context or fallback to aligned current time */
|
|
@@ -13093,60 +14359,115 @@ class ClientRisk {
|
|
|
13093
14359
|
* @param params - Risk check arguments (passthrough from ClientStrategy)
|
|
13094
14360
|
* @returns Promise resolving to true if allowed, false if rejected
|
|
13095
14361
|
*/
|
|
13096
|
-
this.checkSignal = async (params) => {
|
|
14362
|
+
this.checkSignal = async (params, options = {}) => {
|
|
13097
14363
|
this.params.logger.debug("ClientRisk checkSignal", {
|
|
13098
14364
|
symbol: params.symbol,
|
|
13099
14365
|
strategyName: params.strategyName,
|
|
13100
14366
|
backtest: this.params.backtest,
|
|
13101
14367
|
});
|
|
13102
|
-
|
|
13103
|
-
|
|
13104
|
-
|
|
13105
|
-
|
|
13106
|
-
|
|
13107
|
-
|
|
13108
|
-
|
|
13109
|
-
|
|
13110
|
-
|
|
13111
|
-
|
|
13112
|
-
|
|
13113
|
-
|
|
13114
|
-
|
|
13115
|
-
|
|
13116
|
-
|
|
13117
|
-
|
|
13118
|
-
|
|
13119
|
-
|
|
13120
|
-
|
|
13121
|
-
|
|
13122
|
-
|
|
13123
|
-
|
|
13124
|
-
|
|
13125
|
-
:
|
|
13126
|
-
?
|
|
13127
|
-
: "
|
|
13128
|
-
|
|
13129
|
-
|
|
13130
|
-
|
|
13131
|
-
|
|
13132
|
-
|
|
13133
|
-
|
|
13134
|
-
|
|
13135
|
-
|
|
13136
|
-
|
|
14368
|
+
await RISK_LOCK.acquireLock();
|
|
14369
|
+
try {
|
|
14370
|
+
if (this._activePositions === POSITION_NEED_FETCH) {
|
|
14371
|
+
await this.waitForInit();
|
|
14372
|
+
}
|
|
14373
|
+
const riskMap = this._activePositions;
|
|
14374
|
+
const timestamp = GET_CONTEXT_TIMESTAMP_FN(this);
|
|
14375
|
+
const payload = {
|
|
14376
|
+
...params,
|
|
14377
|
+
currentSignal: TO_RISK_SIGNAL(params.currentSignal, params.currentPrice, timestamp),
|
|
14378
|
+
activePositionCount: riskMap.size,
|
|
14379
|
+
activePositions: Array.from(riskMap.values()),
|
|
14380
|
+
};
|
|
14381
|
+
let rejectionResult = null;
|
|
14382
|
+
if (this.params.validations) {
|
|
14383
|
+
for (const validation of this.params.validations) {
|
|
14384
|
+
const rejection = await DO_VALIDATION_FN(this, typeof validation === "function" ? validation : validation.validate, payload);
|
|
14385
|
+
if (!rejection) {
|
|
14386
|
+
continue;
|
|
14387
|
+
}
|
|
14388
|
+
if (typeof rejection === "string") {
|
|
14389
|
+
rejectionResult = {
|
|
14390
|
+
id: null,
|
|
14391
|
+
note: rejection
|
|
14392
|
+
? rejection
|
|
14393
|
+
: "note" in validation
|
|
14394
|
+
? validation.note
|
|
14395
|
+
: "Validation failed",
|
|
14396
|
+
};
|
|
14397
|
+
break;
|
|
14398
|
+
}
|
|
14399
|
+
if (isObject(rejection)) {
|
|
14400
|
+
rejectionResult = {
|
|
14401
|
+
id: get(rejection, "id") || null,
|
|
14402
|
+
note: get(rejection, "note") || "Validation rejected the signal",
|
|
14403
|
+
};
|
|
14404
|
+
break;
|
|
14405
|
+
}
|
|
13137
14406
|
}
|
|
13138
14407
|
}
|
|
14408
|
+
if (rejectionResult) {
|
|
14409
|
+
// Call params.onRejected for riskSubject emission
|
|
14410
|
+
await this.params.onRejected(params.symbol, params, riskMap.size, rejectionResult, params.timestamp, this.params.backtest);
|
|
14411
|
+
// Call schema callbacks.onRejected if defined
|
|
14412
|
+
await CALL_REJECTED_CALLBACKS_FN(this, params.symbol, params);
|
|
14413
|
+
return false;
|
|
14414
|
+
}
|
|
14415
|
+
// Optional placeholder reservation: when caller plans to addSignal next,
|
|
14416
|
+
// pre-write into riskMap inside the same critical section so concurrent
|
|
14417
|
+
// checkSignal calls observe the incremented size before addSignal lands.
|
|
14418
|
+
// The placeholder shares the same key as the future addSignal — it will
|
|
14419
|
+
// be overwritten with real position data, not duplicated.
|
|
14420
|
+
if (options?.reserve) {
|
|
14421
|
+
const reserveKey = CREATE_NAME_FN(params.strategyName, params.exchangeName, params.symbol);
|
|
14422
|
+
const signal = params.currentSignal;
|
|
14423
|
+
riskMap.set(reserveKey, {
|
|
14424
|
+
strategyName: params.strategyName,
|
|
14425
|
+
exchangeName: params.exchangeName,
|
|
14426
|
+
frameName: params.frameName,
|
|
14427
|
+
symbol: params.symbol,
|
|
14428
|
+
position: signal.position,
|
|
14429
|
+
priceOpen: signal.priceOpen ?? params.currentPrice,
|
|
14430
|
+
priceStopLoss: signal.priceStopLoss,
|
|
14431
|
+
priceTakeProfit: signal.priceTakeProfit,
|
|
14432
|
+
minuteEstimatedTime: signal.minuteEstimatedTime,
|
|
14433
|
+
openTimestamp: timestamp,
|
|
14434
|
+
});
|
|
14435
|
+
}
|
|
14436
|
+
// All checks passed
|
|
14437
|
+
await CALL_ALLOWED_CALLBACKS_FN(this, params.symbol, params);
|
|
14438
|
+
return true;
|
|
13139
14439
|
}
|
|
13140
|
-
|
|
13141
|
-
|
|
13142
|
-
await this.params.onRejected(params.symbol, params, riskMap.size, rejectionResult, params.timestamp, this.params.backtest);
|
|
13143
|
-
// Call schema callbacks.onRejected if defined
|
|
13144
|
-
await CALL_REJECTED_CALLBACKS_FN(this, params.symbol, params);
|
|
13145
|
-
return false;
|
|
14440
|
+
finally {
|
|
14441
|
+
await RISK_LOCK.releaseLock();
|
|
13146
14442
|
}
|
|
13147
|
-
|
|
13148
|
-
|
|
13149
|
-
|
|
14443
|
+
};
|
|
14444
|
+
/**
|
|
14445
|
+
* Concurrency-safe variant of {@link checkSignal}: validates the signal AND
|
|
14446
|
+
* reserves a placeholder slot in the active position map atomically.
|
|
14447
|
+
*
|
|
14448
|
+
* **Why this exists.** `checkSignal` followed later by `addSignal` is not
|
|
14449
|
+
* atomic — between the two calls the caller does signal setup work that
|
|
14450
|
+
* yields to the event loop (sync-open callback, persist writes, etc.). When
|
|
14451
|
+
* several strategies sharing the same risk profile run in parallel, all of
|
|
14452
|
+
* them can pass `checkSignal` while the active position map is still empty,
|
|
14453
|
+
* then each call `addSignal` and blow past the limit. Reserving inside the
|
|
14454
|
+
* lock guarantees the next concurrent caller observes the incremented size
|
|
14455
|
+
* before its own validation runs.
|
|
14456
|
+
*
|
|
14457
|
+
* The reservation uses the same map key as the eventual `addSignal` call
|
|
14458
|
+
* (`strategyName + exchangeName + symbol`), so `addSignal` overwrites the
|
|
14459
|
+
* placeholder rather than appending a duplicate.
|
|
14460
|
+
*
|
|
14461
|
+
* Callers MUST ensure that every successful return is followed by either
|
|
14462
|
+
* `addSignal` (overwrites the placeholder with real data) or `removeSignal`
|
|
14463
|
+
* (clears the placeholder if opening is aborted). Otherwise the riskMap
|
|
14464
|
+
* accumulates stale reservations.
|
|
14465
|
+
*
|
|
14466
|
+
* @param params - Risk check arguments (passthrough from ClientStrategy)
|
|
14467
|
+
* @returns Promise resolving to true if allowed (and reserved), false if rejected (no reservation)
|
|
14468
|
+
*/
|
|
14469
|
+
this.checkSignalAndReserve = async (params) => {
|
|
14470
|
+
return await this.checkSignal(params, { reserve: true });
|
|
13150
14471
|
};
|
|
13151
14472
|
}
|
|
13152
14473
|
/**
|
|
@@ -13173,24 +14494,30 @@ class ClientRisk {
|
|
|
13173
14494
|
positionData,
|
|
13174
14495
|
backtest: this.params.backtest,
|
|
13175
14496
|
});
|
|
13176
|
-
|
|
13177
|
-
|
|
14497
|
+
await RISK_LOCK.acquireLock();
|
|
14498
|
+
try {
|
|
14499
|
+
if (this._activePositions === POSITION_NEED_FETCH) {
|
|
14500
|
+
await this.waitForInit();
|
|
14501
|
+
}
|
|
14502
|
+
const key = CREATE_NAME_FN(context.strategyName, context.exchangeName, symbol);
|
|
14503
|
+
const riskMap = this._activePositions;
|
|
14504
|
+
riskMap.set(key, {
|
|
14505
|
+
strategyName: context.strategyName,
|
|
14506
|
+
exchangeName: context.exchangeName,
|
|
14507
|
+
frameName: context.frameName,
|
|
14508
|
+
symbol,
|
|
14509
|
+
position: positionData.position,
|
|
14510
|
+
priceOpen: positionData.priceOpen,
|
|
14511
|
+
priceStopLoss: positionData.priceStopLoss,
|
|
14512
|
+
priceTakeProfit: positionData.priceTakeProfit,
|
|
14513
|
+
minuteEstimatedTime: positionData.minuteEstimatedTime,
|
|
14514
|
+
openTimestamp: positionData.openTimestamp,
|
|
14515
|
+
});
|
|
14516
|
+
await this._updatePositions();
|
|
14517
|
+
}
|
|
14518
|
+
finally {
|
|
14519
|
+
await RISK_LOCK.releaseLock();
|
|
13178
14520
|
}
|
|
13179
|
-
const key = CREATE_NAME_FN(context.strategyName, context.exchangeName, symbol);
|
|
13180
|
-
const riskMap = this._activePositions;
|
|
13181
|
-
riskMap.set(key, {
|
|
13182
|
-
strategyName: context.strategyName,
|
|
13183
|
-
exchangeName: context.exchangeName,
|
|
13184
|
-
frameName: context.frameName,
|
|
13185
|
-
symbol,
|
|
13186
|
-
position: positionData.position,
|
|
13187
|
-
priceOpen: positionData.priceOpen,
|
|
13188
|
-
priceStopLoss: positionData.priceStopLoss,
|
|
13189
|
-
priceTakeProfit: positionData.priceTakeProfit,
|
|
13190
|
-
minuteEstimatedTime: positionData.minuteEstimatedTime,
|
|
13191
|
-
openTimestamp: positionData.openTimestamp,
|
|
13192
|
-
});
|
|
13193
|
-
await this._updatePositions();
|
|
13194
14521
|
}
|
|
13195
14522
|
/**
|
|
13196
14523
|
* Removes a closed signal.
|
|
@@ -13202,13 +14529,19 @@ class ClientRisk {
|
|
|
13202
14529
|
context,
|
|
13203
14530
|
backtest: this.params.backtest,
|
|
13204
14531
|
});
|
|
13205
|
-
|
|
13206
|
-
|
|
14532
|
+
await RISK_LOCK.acquireLock();
|
|
14533
|
+
try {
|
|
14534
|
+
if (this._activePositions === POSITION_NEED_FETCH) {
|
|
14535
|
+
await this.waitForInit();
|
|
14536
|
+
}
|
|
14537
|
+
const key = CREATE_NAME_FN(context.strategyName, context.exchangeName, symbol);
|
|
14538
|
+
const riskMap = this._activePositions;
|
|
14539
|
+
riskMap.delete(key);
|
|
14540
|
+
await this._updatePositions();
|
|
14541
|
+
}
|
|
14542
|
+
finally {
|
|
14543
|
+
await RISK_LOCK.releaseLock();
|
|
13207
14544
|
}
|
|
13208
|
-
const key = CREATE_NAME_FN(context.strategyName, context.exchangeName, symbol);
|
|
13209
|
-
const riskMap = this._activePositions;
|
|
13210
|
-
riskMap.delete(key);
|
|
13211
|
-
await this._updatePositions();
|
|
13212
14545
|
}
|
|
13213
14546
|
}
|
|
13214
14547
|
|
|
@@ -13343,12 +14676,33 @@ class RiskConnectionService {
|
|
|
13343
14676
|
* @param payload - Execution payload with risk name, exchangeName, frameName and backtest mode
|
|
13344
14677
|
* @returns Promise resolving to risk check result
|
|
13345
14678
|
*/
|
|
13346
|
-
this.checkSignal = async (params, payload) => {
|
|
14679
|
+
this.checkSignal = async (params, payload, options = {}) => {
|
|
13347
14680
|
this.loggerService.log("riskConnectionService checkSignal", {
|
|
13348
14681
|
symbol: params.symbol,
|
|
13349
14682
|
payload,
|
|
13350
14683
|
});
|
|
13351
|
-
return await this.getRisk(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest).checkSignal(params);
|
|
14684
|
+
return await this.getRisk(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest).checkSignal(params, options);
|
|
14685
|
+
};
|
|
14686
|
+
/**
|
|
14687
|
+
* Concurrency-safe variant of {@link checkSignal} — validates the signal AND
|
|
14688
|
+
* reserves a placeholder in the active position map atomically.
|
|
14689
|
+
*
|
|
14690
|
+
* Routes to the same ClientRisk instance as {@link checkSignal} but delegates
|
|
14691
|
+
* to its `checkSignalAndReserve` method. Use from execution paths where the
|
|
14692
|
+
* caller will follow up with `addSignal` on success — guarantees concurrent
|
|
14693
|
+
* callers cannot all pass validation against a stale empty map. See
|
|
14694
|
+
* {@link IRisk.checkSignalAndReserve} for the full rationale.
|
|
14695
|
+
*
|
|
14696
|
+
* @param params - Risk check arguments (portfolio state, position details)
|
|
14697
|
+
* @param payload - Execution payload with risk name, exchangeName, frameName and backtest mode
|
|
14698
|
+
* @returns Promise resolving to true if allowed (and reserved), false if rejected (no reservation)
|
|
14699
|
+
*/
|
|
14700
|
+
this.checkSignalAndReserve = async (params, payload) => {
|
|
14701
|
+
this.loggerService.log("riskConnectionService checkSignalAndReserve", {
|
|
14702
|
+
symbol: params.symbol,
|
|
14703
|
+
payload,
|
|
14704
|
+
});
|
|
14705
|
+
return await this.getRisk(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest).checkSignalAndReserve(params);
|
|
13352
14706
|
};
|
|
13353
14707
|
/**
|
|
13354
14708
|
* Registers an opened signal with the risk management system.
|
|
@@ -16552,13 +17906,34 @@ class RiskGlobalService {
|
|
|
16552
17906
|
* @param payload - Execution payload with risk name, exchangeName, frameName and backtest mode
|
|
16553
17907
|
* @returns Promise resolving to risk check result
|
|
16554
17908
|
*/
|
|
16555
|
-
this.checkSignal = async (params, payload) => {
|
|
17909
|
+
this.checkSignal = async (params, payload, options = {}) => {
|
|
16556
17910
|
this.loggerService.log("riskGlobalService checkSignal", {
|
|
16557
17911
|
symbol: params.symbol,
|
|
16558
17912
|
payload,
|
|
16559
17913
|
});
|
|
16560
17914
|
await this.validate(payload);
|
|
16561
|
-
return await this.riskConnectionService.checkSignal(params, payload);
|
|
17915
|
+
return await this.riskConnectionService.checkSignal(params, payload, options);
|
|
17916
|
+
};
|
|
17917
|
+
/**
|
|
17918
|
+
* Concurrency-safe variant of {@link checkSignal} — validates the signal AND
|
|
17919
|
+
* reserves a placeholder in the active position map atomically.
|
|
17920
|
+
*
|
|
17921
|
+
* Use from strategy execution paths where the caller will follow up with
|
|
17922
|
+
* `addSignal` on success — guarantees concurrent callers cannot all pass
|
|
17923
|
+
* validation against a stale empty map. See {@link IRisk.checkSignalAndReserve}
|
|
17924
|
+
* for the full rationale.
|
|
17925
|
+
*
|
|
17926
|
+
* @param params - Risk check arguments (portfolio state, position details)
|
|
17927
|
+
* @param payload - Execution payload with risk name, exchangeName, frameName and backtest mode
|
|
17928
|
+
* @returns Promise resolving to true if allowed (and reserved), false if rejected (no reservation)
|
|
17929
|
+
*/
|
|
17930
|
+
this.checkSignalAndReserve = async (params, payload) => {
|
|
17931
|
+
this.loggerService.log("riskGlobalService checkSignalAndReserve", {
|
|
17932
|
+
symbol: params.symbol,
|
|
17933
|
+
payload,
|
|
17934
|
+
});
|
|
17935
|
+
await this.validate(payload);
|
|
17936
|
+
return await this.riskConnectionService.checkSignalAndReserve(params, payload);
|
|
16562
17937
|
};
|
|
16563
17938
|
/**
|
|
16564
17939
|
* Registers an opened signal with the risk management system.
|
|
@@ -61924,4 +63299,4 @@ const validateSignal = (signal, currentPrice) => {
|
|
|
61924
63299
|
return !errors.length;
|
|
61925
63300
|
};
|
|
61926
63301
|
|
|
61927
|
-
export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MemoryBacktest, MemoryBacktestAdapter, MemoryLive, MemoryLiveAdapter, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistIntervalAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistMemoryAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRecentAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSessionAdapter, PersistSignalAdapter, PersistStateAdapter, PersistStorageAdapter, Position, PositionSize, Recent, RecentBacktest, RecentLive, Reflect$1 as Reflect, Report, ReportBase, ReportWriter, Risk, Schedule, Session, SessionBacktest, SessionLive, State, StateBacktest, StateBacktestAdapter, StateLive, StateLiveAdapter, Storage, StorageBacktest, StorageLive, Strategy, Sync, System, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitSignalNotify, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, createSignalState, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getClosePrice, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getLatestSignal, getMaxDrawdownDistancePnlCost, getMaxDrawdownDistancePnlPercentage, getMinutesSinceLatestSignalCreated, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionActiveMinutes, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestMaxDrawdownPnlCost, getPositionHighestMaxDrawdownPnlPercentage, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitDistancePnlCost, getPositionHighestProfitDistancePnlPercentage, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getPositionWaitingMinutes, getRawCandles, getRiskSchema, getScheduledSignal, getSessionData, getSignalState, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenIdlePing, listenIdlePingOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalNotify, listenSignalNotifyOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, setSessionData, setSignalState, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };
|
|
63302
|
+
export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MemoryBacktest, MemoryBacktestAdapter, MemoryLive, MemoryLiveAdapter, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistBreakevenInstance, PersistCandleAdapter, PersistCandleInstance, PersistIntervalAdapter, PersistIntervalInstance, PersistLogAdapter, PersistLogInstance, PersistMeasureAdapter, PersistMeasureInstance, PersistMemoryAdapter, PersistMemoryInstance, PersistNotificationAdapter, PersistNotificationInstance, PersistPartialAdapter, PersistPartialInstance, PersistRecentAdapter, PersistRecentInstance, PersistRiskAdapter, PersistRiskInstance, PersistScheduleAdapter, PersistScheduleInstance, PersistSessionAdapter, PersistSessionInstance, PersistSignalAdapter, PersistSignalInstance, PersistStateAdapter, PersistStateInstance, PersistStorageAdapter, PersistStorageInstance, Position, PositionSize, Recent, RecentBacktest, RecentLive, Reflect$1 as Reflect, Report, ReportBase, ReportWriter, Risk, Schedule, Session, SessionBacktest, SessionLive, State, StateBacktest, StateBacktestAdapter, StateLive, StateLiveAdapter, Storage, StorageBacktest, StorageLive, Strategy, Sync, System, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitSignalNotify, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, createSignalState, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getClosePrice, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getLatestSignal, getMaxDrawdownDistancePnlCost, getMaxDrawdownDistancePnlPercentage, getMinutesSinceLatestSignalCreated, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionActiveMinutes, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestMaxDrawdownPnlCost, getPositionHighestMaxDrawdownPnlPercentage, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitDistancePnlCost, getPositionHighestProfitDistancePnlPercentage, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getPositionWaitingMinutes, getRawCandles, getRiskSchema, getScheduledSignal, getSessionData, getSignalState, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenIdlePing, listenIdlePingOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalNotify, listenSignalNotifyOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, setSessionData, setSignalState, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };
|