backtest-kit 1.11.6 → 1.11.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.cjs +417 -280
- package/build/index.mjs +417 -280
- package/package.json +1 -1
- package/types.d.ts +153 -80
package/build/index.cjs
CHANGED
|
@@ -414,6 +414,14 @@ const GLOBAL_CONFIG = {
|
|
|
414
414
|
* Default: 0.2% (additional buffer above costs to ensure no loss when moving to breakeven)
|
|
415
415
|
*/
|
|
416
416
|
CC_BREAKEVEN_THRESHOLD: 0.2,
|
|
417
|
+
/**
|
|
418
|
+
* Time offset in minutes for order book fetching.
|
|
419
|
+
* Subtracts this amount from the current time when fetching order book data.
|
|
420
|
+
* This helps get a more stable snapshot of the order book by avoiding real-time volatility.
|
|
421
|
+
*
|
|
422
|
+
* Default: 10 minutes
|
|
423
|
+
*/
|
|
424
|
+
CC_ORDER_BOOK_TIME_OFFSET_MINUTES: 10,
|
|
417
425
|
};
|
|
418
426
|
const DEFAULT_CONFIG = Object.freeze({ ...GLOBAL_CONFIG });
|
|
419
427
|
|
|
@@ -880,8 +888,51 @@ class ClientExchange {
|
|
|
880
888
|
});
|
|
881
889
|
return await this.params.formatPrice(symbol, price);
|
|
882
890
|
}
|
|
891
|
+
/**
|
|
892
|
+
* Fetches order book for a trading pair.
|
|
893
|
+
*
|
|
894
|
+
* @param symbol - Trading pair symbol
|
|
895
|
+
* @returns Promise resolving to order book data
|
|
896
|
+
* @throws Error if getOrderBook is not implemented
|
|
897
|
+
*/
|
|
898
|
+
async getOrderBook(symbol) {
|
|
899
|
+
this.params.logger.debug("ClientExchange getOrderBook", {
|
|
900
|
+
symbol,
|
|
901
|
+
});
|
|
902
|
+
const to = new Date(this.params.execution.context.when.getTime());
|
|
903
|
+
const from = new Date(to.getTime() - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * 60 * 1000);
|
|
904
|
+
return await this.params.getOrderBook(symbol, from, to);
|
|
905
|
+
}
|
|
883
906
|
}
|
|
884
907
|
|
|
908
|
+
/**
|
|
909
|
+
* Default implementation for getCandles.
|
|
910
|
+
* Throws an error indicating the method is not implemented.
|
|
911
|
+
*/
|
|
912
|
+
const DEFAULT_GET_CANDLES_FN$1 = async (_symbol, _interval, _since, _limit) => {
|
|
913
|
+
throw new Error(`getCandles is not implemented for this exchange`);
|
|
914
|
+
};
|
|
915
|
+
/**
|
|
916
|
+
* Default implementation for formatQuantity.
|
|
917
|
+
* Returns Bitcoin precision on Binance (8 decimal places).
|
|
918
|
+
*/
|
|
919
|
+
const DEFAULT_FORMAT_QUANTITY_FN$1 = async (_symbol, quantity) => {
|
|
920
|
+
return quantity.toFixed(8);
|
|
921
|
+
};
|
|
922
|
+
/**
|
|
923
|
+
* Default implementation for formatPrice.
|
|
924
|
+
* Returns Bitcoin precision on Binance (2 decimal places).
|
|
925
|
+
*/
|
|
926
|
+
const DEFAULT_FORMAT_PRICE_FN$1 = async (_symbol, price) => {
|
|
927
|
+
return price.toFixed(2);
|
|
928
|
+
};
|
|
929
|
+
/**
|
|
930
|
+
* Default implementation for getOrderBook.
|
|
931
|
+
* Throws an error indicating the method is not implemented.
|
|
932
|
+
*/
|
|
933
|
+
const DEFAULT_GET_ORDER_BOOK_FN$1 = async (_symbol, _from, _to) => {
|
|
934
|
+
throw new Error(`getOrderBook is not implemented for this exchange`);
|
|
935
|
+
};
|
|
885
936
|
/**
|
|
886
937
|
* Connection service routing exchange operations to correct ClientExchange instance.
|
|
887
938
|
*
|
|
@@ -920,7 +971,7 @@ class ExchangeConnectionService {
|
|
|
920
971
|
* @returns Configured ClientExchange instance
|
|
921
972
|
*/
|
|
922
973
|
this.getExchange = functoolsKit.memoize(([exchangeName]) => `${exchangeName}`, (exchangeName) => {
|
|
923
|
-
const { getCandles, formatPrice, formatQuantity, callbacks } = this.exchangeSchemaService.get(exchangeName);
|
|
974
|
+
const { getCandles = DEFAULT_GET_CANDLES_FN$1, formatPrice = DEFAULT_FORMAT_PRICE_FN$1, formatQuantity = DEFAULT_FORMAT_QUANTITY_FN$1, getOrderBook = DEFAULT_GET_ORDER_BOOK_FN$1, callbacks } = this.exchangeSchemaService.get(exchangeName);
|
|
924
975
|
return new ClientExchange({
|
|
925
976
|
execution: this.executionContextService,
|
|
926
977
|
logger: this.loggerService,
|
|
@@ -928,6 +979,7 @@ class ExchangeConnectionService {
|
|
|
928
979
|
getCandles,
|
|
929
980
|
formatPrice,
|
|
930
981
|
formatQuantity,
|
|
982
|
+
getOrderBook,
|
|
931
983
|
callbacks,
|
|
932
984
|
});
|
|
933
985
|
});
|
|
@@ -1015,6 +1067,20 @@ class ExchangeConnectionService {
|
|
|
1015
1067
|
});
|
|
1016
1068
|
return await this.getExchange(this.methodContextService.context.exchangeName).formatQuantity(symbol, quantity);
|
|
1017
1069
|
};
|
|
1070
|
+
/**
|
|
1071
|
+
* Fetches order book for a trading pair using configured exchange.
|
|
1072
|
+
*
|
|
1073
|
+
* Routes to exchange determined by methodContextService.context.exchangeName.
|
|
1074
|
+
*
|
|
1075
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
1076
|
+
* @returns Promise resolving to order book data
|
|
1077
|
+
*/
|
|
1078
|
+
this.getOrderBook = async (symbol) => {
|
|
1079
|
+
this.loggerService.log("exchangeConnectionService getOrderBook", {
|
|
1080
|
+
symbol,
|
|
1081
|
+
});
|
|
1082
|
+
return await this.getExchange(this.methodContextService.context.exchangeName).getOrderBook(symbol);
|
|
1083
|
+
};
|
|
1018
1084
|
}
|
|
1019
1085
|
}
|
|
1020
1086
|
|
|
@@ -1284,7 +1350,7 @@ async function writeFileAtomic(file, data, options = {}) {
|
|
|
1284
1350
|
}
|
|
1285
1351
|
}
|
|
1286
1352
|
|
|
1287
|
-
var _a$2;
|
|
1353
|
+
var _a$2, _b$2;
|
|
1288
1354
|
const BASE_WAIT_FOR_INIT_SYMBOL = Symbol("wait-for-init");
|
|
1289
1355
|
const PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_PERSIST_SIGNAL_ADAPTER = "PersistSignalUtils.usePersistSignalAdapter";
|
|
1290
1356
|
const PERSIST_SIGNAL_UTILS_METHOD_NAME_READ_DATA = "PersistSignalUtils.readSignalData";
|
|
@@ -1316,9 +1382,6 @@ const PERSIST_BASE_METHOD_NAME_WAIT_FOR_INIT = "PersistBase.waitForInit";
|
|
|
1316
1382
|
const PERSIST_BASE_METHOD_NAME_READ_VALUE = "PersistBase.readValue";
|
|
1317
1383
|
const PERSIST_BASE_METHOD_NAME_WRITE_VALUE = "PersistBase.writeValue";
|
|
1318
1384
|
const PERSIST_BASE_METHOD_NAME_HAS_VALUE = "PersistBase.hasValue";
|
|
1319
|
-
const PERSIST_BASE_METHOD_NAME_REMOVE_VALUE = "PersistBase.removeValue";
|
|
1320
|
-
const PERSIST_BASE_METHOD_NAME_REMOVE_ALL = "PersistBase.removeAll";
|
|
1321
|
-
const PERSIST_BASE_METHOD_NAME_VALUES = "PersistBase.values";
|
|
1322
1385
|
const PERSIST_BASE_METHOD_NAME_KEYS = "PersistBase.keys";
|
|
1323
1386
|
const BASE_WAIT_FOR_INIT_FN_METHOD_NAME = "PersistBase.waitForInitFn";
|
|
1324
1387
|
const BASE_UNLINK_RETRY_COUNT = 5;
|
|
@@ -1371,254 +1434,119 @@ const BASE_WAIT_FOR_INIT_UNLINK_FN = async (filePath) => functoolsKit.trycatch(f
|
|
|
1371
1434
|
* const value = await persist.readValue("key1");
|
|
1372
1435
|
* ```
|
|
1373
1436
|
*/
|
|
1374
|
-
const PersistBase = functoolsKit.makeExtendable(class {
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
}
|
|
1391
|
-
/**
|
|
1392
|
-
* Computes file path for entity ID.
|
|
1393
|
-
*
|
|
1394
|
-
* @param entityId - Entity identifier
|
|
1395
|
-
* @returns Full file path to entity JSON file
|
|
1396
|
-
*/
|
|
1397
|
-
_getFilePath(entityId) {
|
|
1398
|
-
return path.join(this.baseDir, this.entityName, `${entityId}.json`);
|
|
1399
|
-
}
|
|
1400
|
-
async waitForInit(initial) {
|
|
1401
|
-
bt.loggerService.debug(PERSIST_BASE_METHOD_NAME_WAIT_FOR_INIT, {
|
|
1402
|
-
entityName: this.entityName,
|
|
1403
|
-
initial,
|
|
1404
|
-
});
|
|
1405
|
-
await this[BASE_WAIT_FOR_INIT_SYMBOL]();
|
|
1406
|
-
}
|
|
1407
|
-
/**
|
|
1408
|
-
* Returns count of persisted entities.
|
|
1409
|
-
*
|
|
1410
|
-
* @returns Promise resolving to number of .json files in directory
|
|
1411
|
-
*/
|
|
1412
|
-
async getCount() {
|
|
1413
|
-
const files = await fs.readdir(this._directory);
|
|
1414
|
-
const { length } = files.filter((file) => file.endsWith(".json"));
|
|
1415
|
-
return length;
|
|
1416
|
-
}
|
|
1417
|
-
async readValue(entityId) {
|
|
1418
|
-
bt.loggerService.debug(PERSIST_BASE_METHOD_NAME_READ_VALUE, {
|
|
1419
|
-
entityName: this.entityName,
|
|
1420
|
-
entityId,
|
|
1421
|
-
});
|
|
1422
|
-
try {
|
|
1423
|
-
const filePath = this._getFilePath(entityId);
|
|
1424
|
-
const fileContent = await fs.readFile(filePath, "utf-8");
|
|
1425
|
-
return JSON.parse(fileContent);
|
|
1437
|
+
const PersistBase = functoolsKit.makeExtendable((_b$2 = class {
|
|
1438
|
+
/**
|
|
1439
|
+
* Creates new persistence instance.
|
|
1440
|
+
*
|
|
1441
|
+
* @param entityName - Unique entity type identifier
|
|
1442
|
+
* @param baseDir - Base directory for all entities (default: ./dump/data)
|
|
1443
|
+
*/
|
|
1444
|
+
constructor(entityName, baseDir = path.join(process.cwd(), "logs/data")) {
|
|
1445
|
+
this.entityName = entityName;
|
|
1446
|
+
this.baseDir = baseDir;
|
|
1447
|
+
this[_a$2] = functoolsKit.singleshot(async () => await BASE_WAIT_FOR_INIT_FN(this));
|
|
1448
|
+
bt.loggerService.debug(PERSIST_BASE_METHOD_NAME_CTOR, {
|
|
1449
|
+
entityName: this.entityName,
|
|
1450
|
+
baseDir,
|
|
1451
|
+
});
|
|
1452
|
+
this._directory = path.join(this.baseDir, this.entityName);
|
|
1426
1453
|
}
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1454
|
+
/**
|
|
1455
|
+
* Computes file path for entity ID.
|
|
1456
|
+
*
|
|
1457
|
+
* @param entityId - Entity identifier
|
|
1458
|
+
* @returns Full file path to entity JSON file
|
|
1459
|
+
*/
|
|
1460
|
+
_getFilePath(entityId) {
|
|
1461
|
+
return path.join(this.baseDir, this.entityName, `${entityId}.json`);
|
|
1432
1462
|
}
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
try {
|
|
1440
|
-
const filePath = this._getFilePath(entityId);
|
|
1441
|
-
await fs.access(filePath);
|
|
1442
|
-
return true;
|
|
1463
|
+
async waitForInit(initial) {
|
|
1464
|
+
bt.loggerService.debug(PERSIST_BASE_METHOD_NAME_WAIT_FOR_INIT, {
|
|
1465
|
+
entityName: this.entityName,
|
|
1466
|
+
initial,
|
|
1467
|
+
});
|
|
1468
|
+
await this[BASE_WAIT_FOR_INIT_SYMBOL]();
|
|
1443
1469
|
}
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1470
|
+
async readValue(entityId) {
|
|
1471
|
+
bt.loggerService.debug(PERSIST_BASE_METHOD_NAME_READ_VALUE, {
|
|
1472
|
+
entityName: this.entityName,
|
|
1473
|
+
entityId,
|
|
1474
|
+
});
|
|
1475
|
+
try {
|
|
1476
|
+
const filePath = this._getFilePath(entityId);
|
|
1477
|
+
const fileContent = await fs.readFile(filePath, "utf-8");
|
|
1478
|
+
return JSON.parse(fileContent);
|
|
1447
1479
|
}
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
entityName: this.entityName,
|
|
1454
|
-
entityId,
|
|
1455
|
-
});
|
|
1456
|
-
try {
|
|
1457
|
-
const filePath = this._getFilePath(entityId);
|
|
1458
|
-
const serializedData = JSON.stringify(entity);
|
|
1459
|
-
await writeFileAtomic(filePath, serializedData, "utf-8");
|
|
1460
|
-
}
|
|
1461
|
-
catch (error) {
|
|
1462
|
-
throw new Error(`Failed to write entity ${this.entityName}:${entityId}: ${functoolsKit.getErrorMessage(error)}`);
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
/**
|
|
1466
|
-
* Removes entity from storage.
|
|
1467
|
-
*
|
|
1468
|
-
* @param entityId - Entity identifier to remove
|
|
1469
|
-
* @returns Promise that resolves when entity is deleted
|
|
1470
|
-
* @throws Error if entity not found or deletion fails
|
|
1471
|
-
*/
|
|
1472
|
-
async removeValue(entityId) {
|
|
1473
|
-
bt.loggerService.debug(PERSIST_BASE_METHOD_NAME_REMOVE_VALUE, {
|
|
1474
|
-
entityName: this.entityName,
|
|
1475
|
-
entityId,
|
|
1476
|
-
});
|
|
1477
|
-
try {
|
|
1478
|
-
const filePath = this._getFilePath(entityId);
|
|
1479
|
-
await fs.unlink(filePath);
|
|
1480
|
-
}
|
|
1481
|
-
catch (error) {
|
|
1482
|
-
if (error?.code === "ENOENT") {
|
|
1483
|
-
throw new Error(`Entity ${this.entityName}:${entityId} not found for deletion`);
|
|
1480
|
+
catch (error) {
|
|
1481
|
+
if (error?.code === "ENOENT") {
|
|
1482
|
+
throw new Error(`Entity ${this.entityName}:${entityId} not found`);
|
|
1483
|
+
}
|
|
1484
|
+
throw new Error(`Failed to read entity ${this.entityName}:${entityId}: ${functoolsKit.getErrorMessage(error)}`);
|
|
1484
1485
|
}
|
|
1485
|
-
throw new Error(`Failed to remove entity ${this.entityName}:${entityId}: ${functoolsKit.getErrorMessage(error)}`);
|
|
1486
1486
|
}
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
entityName: this.entityName,
|
|
1497
|
-
});
|
|
1498
|
-
try {
|
|
1499
|
-
const files = await fs.readdir(this._directory);
|
|
1500
|
-
const entityFiles = files.filter((file) => file.endsWith(".json"));
|
|
1501
|
-
for (const file of entityFiles) {
|
|
1502
|
-
await fs.unlink(path.join(this._directory, file));
|
|
1487
|
+
async hasValue(entityId) {
|
|
1488
|
+
bt.loggerService.debug(PERSIST_BASE_METHOD_NAME_HAS_VALUE, {
|
|
1489
|
+
entityName: this.entityName,
|
|
1490
|
+
entityId,
|
|
1491
|
+
});
|
|
1492
|
+
try {
|
|
1493
|
+
const filePath = this._getFilePath(entityId);
|
|
1494
|
+
await fs.access(filePath);
|
|
1495
|
+
return true;
|
|
1503
1496
|
}
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
/**
|
|
1510
|
-
* Async generator yielding all entity values.
|
|
1511
|
-
* Sorted alphanumerically by entity ID.
|
|
1512
|
-
*
|
|
1513
|
-
* @returns AsyncGenerator yielding entities
|
|
1514
|
-
* @throws Error if reading fails
|
|
1515
|
-
*/
|
|
1516
|
-
async *values() {
|
|
1517
|
-
bt.loggerService.debug(PERSIST_BASE_METHOD_NAME_VALUES, {
|
|
1518
|
-
entityName: this.entityName,
|
|
1519
|
-
});
|
|
1520
|
-
try {
|
|
1521
|
-
const files = await fs.readdir(this._directory);
|
|
1522
|
-
const entityIds = files
|
|
1523
|
-
.filter((file) => file.endsWith(".json"))
|
|
1524
|
-
.map((file) => file.slice(0, -5))
|
|
1525
|
-
.sort((a, b) => a.localeCompare(b, undefined, {
|
|
1526
|
-
numeric: true,
|
|
1527
|
-
sensitivity: "base",
|
|
1528
|
-
}));
|
|
1529
|
-
for (const entityId of entityIds) {
|
|
1530
|
-
const entity = await this.readValue(entityId);
|
|
1531
|
-
yield entity;
|
|
1497
|
+
catch (error) {
|
|
1498
|
+
if (error?.code === "ENOENT") {
|
|
1499
|
+
return false;
|
|
1500
|
+
}
|
|
1501
|
+
throw new Error(`Failed to check existence of entity ${this.entityName}:${entityId}: ${functoolsKit.getErrorMessage(error)}`);
|
|
1532
1502
|
}
|
|
1533
1503
|
}
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
* @throws Error if reading fails
|
|
1544
|
-
*/
|
|
1545
|
-
async *keys() {
|
|
1546
|
-
bt.loggerService.debug(PERSIST_BASE_METHOD_NAME_KEYS, {
|
|
1547
|
-
entityName: this.entityName,
|
|
1548
|
-
});
|
|
1549
|
-
try {
|
|
1550
|
-
const files = await fs.readdir(this._directory);
|
|
1551
|
-
const entityIds = files
|
|
1552
|
-
.filter((file) => file.endsWith(".json"))
|
|
1553
|
-
.map((file) => file.slice(0, -5))
|
|
1554
|
-
.sort((a, b) => a.localeCompare(b, undefined, {
|
|
1555
|
-
numeric: true,
|
|
1556
|
-
sensitivity: "base",
|
|
1557
|
-
}));
|
|
1558
|
-
for (const entityId of entityIds) {
|
|
1559
|
-
yield entityId;
|
|
1504
|
+
async writeValue(entityId, entity) {
|
|
1505
|
+
bt.loggerService.debug(PERSIST_BASE_METHOD_NAME_WRITE_VALUE, {
|
|
1506
|
+
entityName: this.entityName,
|
|
1507
|
+
entityId,
|
|
1508
|
+
});
|
|
1509
|
+
try {
|
|
1510
|
+
const filePath = this._getFilePath(entityId);
|
|
1511
|
+
const serializedData = JSON.stringify(entity);
|
|
1512
|
+
await writeFileAtomic(filePath, serializedData, "utf-8");
|
|
1560
1513
|
}
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
throw new Error(`Failed to read keys for ${this.entityName}: ${functoolsKit.getErrorMessage(error)}`);
|
|
1564
|
-
}
|
|
1565
|
-
}
|
|
1566
|
-
/**
|
|
1567
|
-
* Async iterator implementation.
|
|
1568
|
-
* Delegates to values() generator.
|
|
1569
|
-
*
|
|
1570
|
-
* @returns AsyncIterableIterator yielding entities
|
|
1571
|
-
*/
|
|
1572
|
-
async *[(_a$2 = BASE_WAIT_FOR_INIT_SYMBOL, Symbol.asyncIterator)]() {
|
|
1573
|
-
for await (const entity of this.values()) {
|
|
1574
|
-
yield entity;
|
|
1575
|
-
}
|
|
1576
|
-
}
|
|
1577
|
-
/**
|
|
1578
|
-
* Filters entities by predicate function.
|
|
1579
|
-
*
|
|
1580
|
-
* @param predicate - Filter function
|
|
1581
|
-
* @returns AsyncGenerator yielding filtered entities
|
|
1582
|
-
*/
|
|
1583
|
-
async *filter(predicate) {
|
|
1584
|
-
for await (const entity of this.values()) {
|
|
1585
|
-
if (predicate(entity)) {
|
|
1586
|
-
yield entity;
|
|
1514
|
+
catch (error) {
|
|
1515
|
+
throw new Error(`Failed to write entity ${this.entityName}:${entityId}: ${functoolsKit.getErrorMessage(error)}`);
|
|
1587
1516
|
}
|
|
1588
1517
|
}
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1518
|
+
/**
|
|
1519
|
+
* Async generator yielding all entity IDs.
|
|
1520
|
+
* Sorted alphanumerically.
|
|
1521
|
+
* Used internally by waitForInit for validation.
|
|
1522
|
+
*
|
|
1523
|
+
* @returns AsyncGenerator yielding entity IDs
|
|
1524
|
+
* @throws Error if reading fails
|
|
1525
|
+
*/
|
|
1526
|
+
async *keys() {
|
|
1527
|
+
bt.loggerService.debug(PERSIST_BASE_METHOD_NAME_KEYS, {
|
|
1528
|
+
entityName: this.entityName,
|
|
1529
|
+
});
|
|
1530
|
+
try {
|
|
1531
|
+
const files = await fs.readdir(this._directory);
|
|
1532
|
+
const entityIds = files
|
|
1533
|
+
.filter((file) => file.endsWith(".json"))
|
|
1534
|
+
.map((file) => file.slice(0, -5))
|
|
1535
|
+
.sort((a, b) => a.localeCompare(b, undefined, {
|
|
1536
|
+
numeric: true,
|
|
1537
|
+
sensitivity: "base",
|
|
1538
|
+
}));
|
|
1539
|
+
for (const entityId of entityIds) {
|
|
1540
|
+
yield entityId;
|
|
1608
1541
|
}
|
|
1609
1542
|
}
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
for await (const entity of this.values()) {
|
|
1613
|
-
count += 1;
|
|
1614
|
-
yield entity;
|
|
1615
|
-
if (count >= total) {
|
|
1616
|
-
break;
|
|
1617
|
-
}
|
|
1543
|
+
catch (error) {
|
|
1544
|
+
throw new Error(`Failed to read keys for ${this.entityName}: ${functoolsKit.getErrorMessage(error)}`);
|
|
1618
1545
|
}
|
|
1619
1546
|
}
|
|
1620
|
-
}
|
|
1621
|
-
|
|
1547
|
+
},
|
|
1548
|
+
_a$2 = BASE_WAIT_FOR_INIT_SYMBOL,
|
|
1549
|
+
_b$2));
|
|
1622
1550
|
/**
|
|
1623
1551
|
* Dummy persist adapter that discards all writes.
|
|
1624
1552
|
* Used for disabling persistence.
|
|
@@ -1650,12 +1578,6 @@ class PersistDummy {
|
|
|
1650
1578
|
*/
|
|
1651
1579
|
async writeValue() {
|
|
1652
1580
|
}
|
|
1653
|
-
/**
|
|
1654
|
-
* No-op keys generator.
|
|
1655
|
-
* @returns Empty async generator
|
|
1656
|
-
*/
|
|
1657
|
-
async *keys() {
|
|
1658
|
-
}
|
|
1659
1581
|
}
|
|
1660
1582
|
/**
|
|
1661
1583
|
* Utility class for managing signal persistence.
|
|
@@ -7541,6 +7463,32 @@ class ExchangeCoreService {
|
|
|
7541
7463
|
backtest,
|
|
7542
7464
|
});
|
|
7543
7465
|
};
|
|
7466
|
+
/**
|
|
7467
|
+
* Fetches order book with execution context.
|
|
7468
|
+
*
|
|
7469
|
+
* @param symbol - Trading pair symbol
|
|
7470
|
+
* @param when - Timestamp for context
|
|
7471
|
+
* @param backtest - Whether running in backtest mode
|
|
7472
|
+
* @returns Promise resolving to order book data
|
|
7473
|
+
*/
|
|
7474
|
+
this.getOrderBook = async (symbol, when, backtest) => {
|
|
7475
|
+
this.loggerService.log("exchangeCoreService getOrderBook", {
|
|
7476
|
+
symbol,
|
|
7477
|
+
when,
|
|
7478
|
+
backtest,
|
|
7479
|
+
});
|
|
7480
|
+
if (!MethodContextService.hasContext()) {
|
|
7481
|
+
throw new Error("exchangeCoreService getOrderBook requires a method context");
|
|
7482
|
+
}
|
|
7483
|
+
await this.validate(this.methodContextService.context.exchangeName);
|
|
7484
|
+
return await ExecutionContextService.runInContext(async () => {
|
|
7485
|
+
return await this.exchangeConnectionService.getOrderBook(symbol);
|
|
7486
|
+
}, {
|
|
7487
|
+
symbol,
|
|
7488
|
+
when,
|
|
7489
|
+
backtest,
|
|
7490
|
+
});
|
|
7491
|
+
};
|
|
7544
7492
|
}
|
|
7545
7493
|
}
|
|
7546
7494
|
|
|
@@ -8187,12 +8135,6 @@ class ExchangeSchemaService {
|
|
|
8187
8135
|
if (typeof exchangeSchema.getCandles !== "function") {
|
|
8188
8136
|
throw new Error(`exchange schema validation failed: missing getCandles for exchangeName=${exchangeSchema.exchangeName}`);
|
|
8189
8137
|
}
|
|
8190
|
-
if (typeof exchangeSchema.formatPrice !== "function") {
|
|
8191
|
-
throw new Error(`exchange schema validation failed: missing formatPrice for exchangeName=${exchangeSchema.exchangeName}`);
|
|
8192
|
-
}
|
|
8193
|
-
if (typeof exchangeSchema.formatQuantity !== "function") {
|
|
8194
|
-
throw new Error(`exchange schema validation failed: missing formatQuantity for exchangeName=${exchangeSchema.exchangeName}`);
|
|
8195
|
-
}
|
|
8196
8138
|
};
|
|
8197
8139
|
/**
|
|
8198
8140
|
* Overrides an existing exchange schema with partial updates.
|
|
@@ -10062,21 +10004,21 @@ const live_columns = [
|
|
|
10062
10004
|
{
|
|
10063
10005
|
key: "openPrice",
|
|
10064
10006
|
label: "Open Price",
|
|
10065
|
-
format: (data) => data.
|
|
10007
|
+
format: (data) => data.priceOpen !== undefined ? `${data.priceOpen.toFixed(8)} USD` : "N/A",
|
|
10066
10008
|
isVisible: () => true,
|
|
10067
10009
|
},
|
|
10068
10010
|
{
|
|
10069
10011
|
key: "takeProfit",
|
|
10070
10012
|
label: "Take Profit",
|
|
10071
|
-
format: (data) => data.
|
|
10072
|
-
? `${data.
|
|
10013
|
+
format: (data) => data.priceTakeProfit !== undefined
|
|
10014
|
+
? `${data.priceTakeProfit.toFixed(8)} USD`
|
|
10073
10015
|
: "N/A",
|
|
10074
10016
|
isVisible: () => true,
|
|
10075
10017
|
},
|
|
10076
10018
|
{
|
|
10077
10019
|
key: "stopLoss",
|
|
10078
10020
|
label: "Stop Loss",
|
|
10079
|
-
format: (data) => data.
|
|
10021
|
+
format: (data) => data.priceStopLoss !== undefined ? `${data.priceStopLoss.toFixed(8)} USD` : "N/A",
|
|
10080
10022
|
isVisible: () => true,
|
|
10081
10023
|
},
|
|
10082
10024
|
{
|
|
@@ -10217,6 +10159,48 @@ const partial_columns = [
|
|
|
10217
10159
|
format: (data) => `${data.currentPrice.toFixed(8)} USD`,
|
|
10218
10160
|
isVisible: () => true,
|
|
10219
10161
|
},
|
|
10162
|
+
{
|
|
10163
|
+
key: "priceOpen",
|
|
10164
|
+
label: "Entry Price",
|
|
10165
|
+
format: (data) => (data.priceOpen ? `${data.priceOpen.toFixed(8)} USD` : "N/A"),
|
|
10166
|
+
isVisible: () => true,
|
|
10167
|
+
},
|
|
10168
|
+
{
|
|
10169
|
+
key: "priceTakeProfit",
|
|
10170
|
+
label: "Take Profit",
|
|
10171
|
+
format: (data) => (data.priceTakeProfit ? `${data.priceTakeProfit.toFixed(8)} USD` : "N/A"),
|
|
10172
|
+
isVisible: () => true,
|
|
10173
|
+
},
|
|
10174
|
+
{
|
|
10175
|
+
key: "priceStopLoss",
|
|
10176
|
+
label: "Stop Loss",
|
|
10177
|
+
format: (data) => (data.priceStopLoss ? `${data.priceStopLoss.toFixed(8)} USD` : "N/A"),
|
|
10178
|
+
isVisible: () => true,
|
|
10179
|
+
},
|
|
10180
|
+
{
|
|
10181
|
+
key: "originalPriceTakeProfit",
|
|
10182
|
+
label: "Original TP",
|
|
10183
|
+
format: (data) => (data.originalPriceTakeProfit ? `${data.originalPriceTakeProfit.toFixed(8)} USD` : "N/A"),
|
|
10184
|
+
isVisible: () => true,
|
|
10185
|
+
},
|
|
10186
|
+
{
|
|
10187
|
+
key: "originalPriceStopLoss",
|
|
10188
|
+
label: "Original SL",
|
|
10189
|
+
format: (data) => (data.originalPriceStopLoss ? `${data.originalPriceStopLoss.toFixed(8)} USD` : "N/A"),
|
|
10190
|
+
isVisible: () => true,
|
|
10191
|
+
},
|
|
10192
|
+
{
|
|
10193
|
+
key: "totalExecuted",
|
|
10194
|
+
label: "Total Executed %",
|
|
10195
|
+
format: (data) => (data.totalExecuted !== undefined ? `${data.totalExecuted.toFixed(2)}%` : "N/A"),
|
|
10196
|
+
isVisible: () => true,
|
|
10197
|
+
},
|
|
10198
|
+
{
|
|
10199
|
+
key: "note",
|
|
10200
|
+
label: "Note",
|
|
10201
|
+
format: (data) => data.note || "",
|
|
10202
|
+
isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
|
|
10203
|
+
},
|
|
10220
10204
|
{
|
|
10221
10205
|
key: "timestamp",
|
|
10222
10206
|
label: "Timestamp",
|
|
@@ -10303,6 +10287,42 @@ const breakeven_columns = [
|
|
|
10303
10287
|
format: (data) => `${data.currentPrice.toFixed(8)} USD`,
|
|
10304
10288
|
isVisible: () => true,
|
|
10305
10289
|
},
|
|
10290
|
+
{
|
|
10291
|
+
key: "priceTakeProfit",
|
|
10292
|
+
label: "Take Profit",
|
|
10293
|
+
format: (data) => (data.priceTakeProfit ? `${data.priceTakeProfit.toFixed(8)} USD` : "N/A"),
|
|
10294
|
+
isVisible: () => true,
|
|
10295
|
+
},
|
|
10296
|
+
{
|
|
10297
|
+
key: "priceStopLoss",
|
|
10298
|
+
label: "Stop Loss",
|
|
10299
|
+
format: (data) => (data.priceStopLoss ? `${data.priceStopLoss.toFixed(8)} USD` : "N/A"),
|
|
10300
|
+
isVisible: () => true,
|
|
10301
|
+
},
|
|
10302
|
+
{
|
|
10303
|
+
key: "originalPriceTakeProfit",
|
|
10304
|
+
label: "Original TP",
|
|
10305
|
+
format: (data) => (data.originalPriceTakeProfit ? `${data.originalPriceTakeProfit.toFixed(8)} USD` : "N/A"),
|
|
10306
|
+
isVisible: () => true,
|
|
10307
|
+
},
|
|
10308
|
+
{
|
|
10309
|
+
key: "originalPriceStopLoss",
|
|
10310
|
+
label: "Original SL",
|
|
10311
|
+
format: (data) => (data.originalPriceStopLoss ? `${data.originalPriceStopLoss.toFixed(8)} USD` : "N/A"),
|
|
10312
|
+
isVisible: () => true,
|
|
10313
|
+
},
|
|
10314
|
+
{
|
|
10315
|
+
key: "totalExecuted",
|
|
10316
|
+
label: "Total Executed %",
|
|
10317
|
+
format: (data) => (data.totalExecuted !== undefined ? `${data.totalExecuted.toFixed(2)}%` : "N/A"),
|
|
10318
|
+
isVisible: () => true,
|
|
10319
|
+
},
|
|
10320
|
+
{
|
|
10321
|
+
key: "note",
|
|
10322
|
+
label: "Note",
|
|
10323
|
+
format: (data) => data.note || "",
|
|
10324
|
+
isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
|
|
10325
|
+
},
|
|
10306
10326
|
{
|
|
10307
10327
|
key: "timestamp",
|
|
10308
10328
|
label: "Timestamp",
|
|
@@ -10682,13 +10702,13 @@ const schedule_columns = [
|
|
|
10682
10702
|
{
|
|
10683
10703
|
key: "takeProfit",
|
|
10684
10704
|
label: "Take Profit",
|
|
10685
|
-
format: (data) => `${data.
|
|
10705
|
+
format: (data) => `${data.priceTakeProfit.toFixed(8)} USD`,
|
|
10686
10706
|
isVisible: () => true,
|
|
10687
10707
|
},
|
|
10688
10708
|
{
|
|
10689
10709
|
key: "stopLoss",
|
|
10690
10710
|
label: "Stop Loss",
|
|
10691
|
-
format: (data) => `${data.
|
|
10711
|
+
format: (data) => `${data.priceStopLoss.toFixed(8)} USD`,
|
|
10692
10712
|
isVisible: () => true,
|
|
10693
10713
|
},
|
|
10694
10714
|
{
|
|
@@ -11987,9 +12007,9 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
11987
12007
|
position: data.signal.position,
|
|
11988
12008
|
note: data.signal.note,
|
|
11989
12009
|
currentPrice: data.signal.priceOpen,
|
|
11990
|
-
|
|
11991
|
-
|
|
11992
|
-
|
|
12010
|
+
priceOpen: data.signal.priceOpen,
|
|
12011
|
+
priceTakeProfit: data.signal.priceTakeProfit,
|
|
12012
|
+
priceStopLoss: data.signal.priceStopLoss,
|
|
11993
12013
|
originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
|
|
11994
12014
|
originalPriceStopLoss: data.signal.originalPriceStopLoss,
|
|
11995
12015
|
totalExecuted: data.signal.totalExecuted,
|
|
@@ -12014,9 +12034,9 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
12014
12034
|
position: data.signal.position,
|
|
12015
12035
|
note: data.signal.note,
|
|
12016
12036
|
currentPrice: data.currentPrice,
|
|
12017
|
-
|
|
12018
|
-
|
|
12019
|
-
|
|
12037
|
+
priceOpen: data.signal.priceOpen,
|
|
12038
|
+
priceTakeProfit: data.signal.priceTakeProfit,
|
|
12039
|
+
priceStopLoss: data.signal.priceStopLoss,
|
|
12020
12040
|
originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
|
|
12021
12041
|
originalPriceStopLoss: data.signal.originalPriceStopLoss,
|
|
12022
12042
|
totalExecuted: data.signal.totalExecuted,
|
|
@@ -12054,9 +12074,9 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
12054
12074
|
position: data.signal.position,
|
|
12055
12075
|
note: data.signal.note,
|
|
12056
12076
|
currentPrice: data.currentPrice,
|
|
12057
|
-
|
|
12058
|
-
|
|
12059
|
-
|
|
12077
|
+
priceOpen: data.signal.priceOpen,
|
|
12078
|
+
priceTakeProfit: data.signal.priceTakeProfit,
|
|
12079
|
+
priceStopLoss: data.signal.priceStopLoss,
|
|
12060
12080
|
originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
|
|
12061
12081
|
originalPriceStopLoss: data.signal.originalPriceStopLoss,
|
|
12062
12082
|
totalExecuted: data.signal.totalExecuted,
|
|
@@ -12541,8 +12561,8 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
12541
12561
|
note: data.signal.note,
|
|
12542
12562
|
currentPrice: data.currentPrice,
|
|
12543
12563
|
priceOpen: data.signal.priceOpen,
|
|
12544
|
-
|
|
12545
|
-
|
|
12564
|
+
priceTakeProfit: data.signal.priceTakeProfit,
|
|
12565
|
+
priceStopLoss: data.signal.priceStopLoss,
|
|
12546
12566
|
originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
|
|
12547
12567
|
originalPriceStopLoss: data.signal.originalPriceStopLoss,
|
|
12548
12568
|
totalExecuted: data.signal.totalExecuted,
|
|
@@ -12569,8 +12589,8 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
12569
12589
|
note: data.signal.note,
|
|
12570
12590
|
currentPrice: data.currentPrice,
|
|
12571
12591
|
priceOpen: data.signal.priceOpen,
|
|
12572
|
-
|
|
12573
|
-
|
|
12592
|
+
priceTakeProfit: data.signal.priceTakeProfit,
|
|
12593
|
+
priceStopLoss: data.signal.priceStopLoss,
|
|
12574
12594
|
originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
|
|
12575
12595
|
originalPriceStopLoss: data.signal.originalPriceStopLoss,
|
|
12576
12596
|
totalExecuted: data.signal.totalExecuted,
|
|
@@ -12599,8 +12619,8 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
12599
12619
|
note: data.signal.note,
|
|
12600
12620
|
currentPrice: data.currentPrice,
|
|
12601
12621
|
priceOpen: data.signal.priceOpen,
|
|
12602
|
-
|
|
12603
|
-
|
|
12622
|
+
priceTakeProfit: data.signal.priceTakeProfit,
|
|
12623
|
+
priceStopLoss: data.signal.priceStopLoss,
|
|
12604
12624
|
originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
|
|
12605
12625
|
originalPriceStopLoss: data.signal.originalPriceStopLoss,
|
|
12606
12626
|
totalExecuted: data.signal.totalExecuted,
|
|
@@ -13638,11 +13658,11 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
13638
13658
|
await Markdown.writeData("walker", markdown, {
|
|
13639
13659
|
path,
|
|
13640
13660
|
file: filename,
|
|
13641
|
-
symbol:
|
|
13661
|
+
symbol: symbol,
|
|
13642
13662
|
signalId: "",
|
|
13643
13663
|
strategyName: "",
|
|
13644
|
-
exchangeName:
|
|
13645
|
-
frameName:
|
|
13664
|
+
exchangeName: context.exchangeName,
|
|
13665
|
+
frameName: context.frameName
|
|
13646
13666
|
});
|
|
13647
13667
|
}
|
|
13648
13668
|
};
|
|
@@ -16902,6 +16922,13 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
16902
16922
|
position: data.position,
|
|
16903
16923
|
currentPrice,
|
|
16904
16924
|
level,
|
|
16925
|
+
priceOpen: data.priceOpen,
|
|
16926
|
+
priceTakeProfit: data.priceTakeProfit,
|
|
16927
|
+
priceStopLoss: data.priceStopLoss,
|
|
16928
|
+
originalPriceTakeProfit: data.originalPriceTakeProfit,
|
|
16929
|
+
originalPriceStopLoss: data.originalPriceStopLoss,
|
|
16930
|
+
totalExecuted: data.totalExecuted,
|
|
16931
|
+
note: data.note,
|
|
16905
16932
|
backtest,
|
|
16906
16933
|
});
|
|
16907
16934
|
// Trim queue if exceeded MAX_EVENTS
|
|
@@ -16927,6 +16954,13 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
16927
16954
|
position: data.position,
|
|
16928
16955
|
currentPrice,
|
|
16929
16956
|
level,
|
|
16957
|
+
priceOpen: data.priceOpen,
|
|
16958
|
+
priceTakeProfit: data.priceTakeProfit,
|
|
16959
|
+
priceStopLoss: data.priceStopLoss,
|
|
16960
|
+
originalPriceTakeProfit: data.originalPriceTakeProfit,
|
|
16961
|
+
originalPriceStopLoss: data.originalPriceStopLoss,
|
|
16962
|
+
totalExecuted: data.totalExecuted,
|
|
16963
|
+
note: data.note,
|
|
16930
16964
|
backtest,
|
|
16931
16965
|
});
|
|
16932
16966
|
// Trim queue if exceeded MAX_EVENTS
|
|
@@ -17979,7 +18013,7 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
17979
18013
|
/**
|
|
17980
18014
|
* Adds a breakeven event to the storage.
|
|
17981
18015
|
*
|
|
17982
|
-
* @param data - Signal row data
|
|
18016
|
+
* @param data - Signal row data with original prices
|
|
17983
18017
|
* @param currentPrice - Current market price when breakeven was reached
|
|
17984
18018
|
* @param backtest - True if backtest mode
|
|
17985
18019
|
* @param timestamp - Event timestamp in milliseconds
|
|
@@ -17993,6 +18027,12 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
17993
18027
|
position: data.position,
|
|
17994
18028
|
currentPrice,
|
|
17995
18029
|
priceOpen: data.priceOpen,
|
|
18030
|
+
priceTakeProfit: data.priceTakeProfit,
|
|
18031
|
+
priceStopLoss: data.priceStopLoss,
|
|
18032
|
+
originalPriceTakeProfit: data.originalPriceTakeProfit,
|
|
18033
|
+
originalPriceStopLoss: data.originalPriceStopLoss,
|
|
18034
|
+
totalExecuted: data.totalExecuted,
|
|
18035
|
+
note: data.note,
|
|
17996
18036
|
backtest,
|
|
17997
18037
|
});
|
|
17998
18038
|
// Trim queue if exceeded MAX_EVENTS
|
|
@@ -18717,6 +18757,9 @@ class ConfigValidationService {
|
|
|
18717
18757
|
if (!Number.isInteger(GLOBAL_CONFIG.CC_MAX_CANDLES_PER_REQUEST) || GLOBAL_CONFIG.CC_MAX_CANDLES_PER_REQUEST <= 0) {
|
|
18718
18758
|
errors.push(`CC_MAX_CANDLES_PER_REQUEST must be a positive integer, got ${GLOBAL_CONFIG.CC_MAX_CANDLES_PER_REQUEST}`);
|
|
18719
18759
|
}
|
|
18760
|
+
if (!Number.isInteger(GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES) || GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES < 0) {
|
|
18761
|
+
errors.push(`CC_ORDER_BOOK_TIME_OFFSET_MINUTES must be a non-negative integer, got ${GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES}`);
|
|
18762
|
+
}
|
|
18720
18763
|
// Throw aggregated errors if any
|
|
18721
18764
|
if (errors.length > 0) {
|
|
18722
18765
|
const errorMessage = `GLOBAL_CONFIG validation failed:\n${errors.map((e, i) => ` ${i + 1}. ${e}`).join('\n')}`;
|
|
@@ -20575,6 +20618,9 @@ class PartialReportService {
|
|
|
20575
20618
|
priceOpen: data.data.priceOpen,
|
|
20576
20619
|
priceTakeProfit: data.data.priceTakeProfit,
|
|
20577
20620
|
priceStopLoss: data.data.priceStopLoss,
|
|
20621
|
+
originalPriceTakeProfit: data.data.originalPriceTakeProfit,
|
|
20622
|
+
originalPriceStopLoss: data.data.originalPriceStopLoss,
|
|
20623
|
+
totalExecuted: data.data.totalExecuted,
|
|
20578
20624
|
_partial: data.data._partial,
|
|
20579
20625
|
note: data.data.note,
|
|
20580
20626
|
pendingAt: data.data.pendingAt,
|
|
@@ -20613,6 +20659,9 @@ class PartialReportService {
|
|
|
20613
20659
|
priceOpen: data.data.priceOpen,
|
|
20614
20660
|
priceTakeProfit: data.data.priceTakeProfit,
|
|
20615
20661
|
priceStopLoss: data.data.priceStopLoss,
|
|
20662
|
+
originalPriceTakeProfit: data.data.originalPriceTakeProfit,
|
|
20663
|
+
originalPriceStopLoss: data.data.originalPriceStopLoss,
|
|
20664
|
+
totalExecuted: data.data.totalExecuted,
|
|
20616
20665
|
_partial: data.data._partial,
|
|
20617
20666
|
note: data.data.note,
|
|
20618
20667
|
pendingAt: data.data.pendingAt,
|
|
@@ -20732,6 +20781,9 @@ class BreakevenReportService {
|
|
|
20732
20781
|
priceOpen: data.data.priceOpen,
|
|
20733
20782
|
priceTakeProfit: data.data.priceTakeProfit,
|
|
20734
20783
|
priceStopLoss: data.data.priceStopLoss,
|
|
20784
|
+
originalPriceTakeProfit: data.data.originalPriceTakeProfit,
|
|
20785
|
+
originalPriceStopLoss: data.data.originalPriceStopLoss,
|
|
20786
|
+
totalExecuted: data.data.totalExecuted,
|
|
20735
20787
|
_partial: data.data._partial,
|
|
20736
20788
|
note: data.data.note,
|
|
20737
20789
|
pendingAt: data.data.pendingAt,
|
|
@@ -27054,6 +27106,35 @@ const EXCHANGE_METHOD_NAME_GET_CANDLES = "ExchangeUtils.getCandles";
|
|
|
27054
27106
|
const EXCHANGE_METHOD_NAME_GET_AVERAGE_PRICE = "ExchangeUtils.getAveragePrice";
|
|
27055
27107
|
const EXCHANGE_METHOD_NAME_FORMAT_QUANTITY = "ExchangeUtils.formatQuantity";
|
|
27056
27108
|
const EXCHANGE_METHOD_NAME_FORMAT_PRICE = "ExchangeUtils.formatPrice";
|
|
27109
|
+
const EXCHANGE_METHOD_NAME_GET_ORDER_BOOK = "ExchangeUtils.getOrderBook";
|
|
27110
|
+
/**
|
|
27111
|
+
* Default implementation for getCandles.
|
|
27112
|
+
* Throws an error indicating the method is not implemented.
|
|
27113
|
+
*/
|
|
27114
|
+
const DEFAULT_GET_CANDLES_FN = async (_symbol, _interval, _since, _limit) => {
|
|
27115
|
+
throw new Error(`getCandles is not implemented for this exchange`);
|
|
27116
|
+
};
|
|
27117
|
+
/**
|
|
27118
|
+
* Default implementation for formatQuantity.
|
|
27119
|
+
* Returns Bitcoin precision on Binance (8 decimal places).
|
|
27120
|
+
*/
|
|
27121
|
+
const DEFAULT_FORMAT_QUANTITY_FN = async (_symbol, quantity) => {
|
|
27122
|
+
return quantity.toFixed(8);
|
|
27123
|
+
};
|
|
27124
|
+
/**
|
|
27125
|
+
* Default implementation for formatPrice.
|
|
27126
|
+
* Returns Bitcoin precision on Binance (2 decimal places).
|
|
27127
|
+
*/
|
|
27128
|
+
const DEFAULT_FORMAT_PRICE_FN = async (_symbol, price) => {
|
|
27129
|
+
return price.toFixed(2);
|
|
27130
|
+
};
|
|
27131
|
+
/**
|
|
27132
|
+
* Default implementation for getOrderBook.
|
|
27133
|
+
* Throws an error indicating the method is not implemented.
|
|
27134
|
+
*/
|
|
27135
|
+
const DEFAULT_GET_ORDER_BOOK_FN = async (_symbol, _from, _to) => {
|
|
27136
|
+
throw new Error(`getOrderBook is not implemented for this exchange`);
|
|
27137
|
+
};
|
|
27057
27138
|
const INTERVAL_MINUTES$1 = {
|
|
27058
27139
|
"1m": 1,
|
|
27059
27140
|
"3m": 3,
|
|
@@ -27066,6 +27147,25 @@ const INTERVAL_MINUTES$1 = {
|
|
|
27066
27147
|
"6h": 360,
|
|
27067
27148
|
"8h": 480,
|
|
27068
27149
|
};
|
|
27150
|
+
/**
|
|
27151
|
+
* Creates exchange instance with methods resolved once during construction.
|
|
27152
|
+
* Applies default implementations where schema methods are not provided.
|
|
27153
|
+
*
|
|
27154
|
+
* @param schema - Exchange schema from registry
|
|
27155
|
+
* @returns Object with resolved exchange methods
|
|
27156
|
+
*/
|
|
27157
|
+
const CREATE_EXCHANGE_INSTANCE_FN = (schema) => {
|
|
27158
|
+
const getCandles = schema.getCandles ?? DEFAULT_GET_CANDLES_FN;
|
|
27159
|
+
const formatQuantity = schema.formatQuantity ?? DEFAULT_FORMAT_QUANTITY_FN;
|
|
27160
|
+
const formatPrice = schema.formatPrice ?? DEFAULT_FORMAT_PRICE_FN;
|
|
27161
|
+
const getOrderBook = schema.getOrderBook ?? DEFAULT_GET_ORDER_BOOK_FN;
|
|
27162
|
+
return {
|
|
27163
|
+
getCandles,
|
|
27164
|
+
formatQuantity,
|
|
27165
|
+
formatPrice,
|
|
27166
|
+
getOrderBook,
|
|
27167
|
+
};
|
|
27168
|
+
};
|
|
27069
27169
|
/**
|
|
27070
27170
|
* Instance class for exchange operations on a specific exchange.
|
|
27071
27171
|
*
|
|
@@ -27115,6 +27215,7 @@ class ExchangeInstance {
|
|
|
27115
27215
|
interval,
|
|
27116
27216
|
limit,
|
|
27117
27217
|
});
|
|
27218
|
+
const getCandles = this._methods.getCandles;
|
|
27118
27219
|
const step = INTERVAL_MINUTES$1[interval];
|
|
27119
27220
|
const adjust = step * limit - step;
|
|
27120
27221
|
if (!adjust) {
|
|
@@ -27129,7 +27230,7 @@ class ExchangeInstance {
|
|
|
27129
27230
|
let currentSince = new Date(since.getTime());
|
|
27130
27231
|
while (remaining > 0) {
|
|
27131
27232
|
const chunkLimit = Math.min(remaining, GLOBAL_CONFIG.CC_MAX_CANDLES_PER_REQUEST);
|
|
27132
|
-
const chunkData = await
|
|
27233
|
+
const chunkData = await getCandles(symbol, interval, currentSince, chunkLimit);
|
|
27133
27234
|
allData.push(...chunkData);
|
|
27134
27235
|
remaining -= chunkLimit;
|
|
27135
27236
|
if (remaining > 0) {
|
|
@@ -27139,7 +27240,7 @@ class ExchangeInstance {
|
|
|
27139
27240
|
}
|
|
27140
27241
|
}
|
|
27141
27242
|
else {
|
|
27142
|
-
allData = await
|
|
27243
|
+
allData = await getCandles(symbol, interval, since, limit);
|
|
27143
27244
|
}
|
|
27144
27245
|
// Filter candles to strictly match the requested range
|
|
27145
27246
|
const whenTimestamp = when.getTime();
|
|
@@ -27206,7 +27307,7 @@ class ExchangeInstance {
|
|
|
27206
27307
|
* ```typescript
|
|
27207
27308
|
* const instance = new ExchangeInstance("binance");
|
|
27208
27309
|
* const formatted = await instance.formatQuantity("BTCUSDT", 0.001);
|
|
27209
|
-
* console.log(formatted); // "0.
|
|
27310
|
+
* console.log(formatted); // "0.00100000"
|
|
27210
27311
|
* ```
|
|
27211
27312
|
*/
|
|
27212
27313
|
this.formatQuantity = async (symbol, quantity) => {
|
|
@@ -27215,7 +27316,7 @@ class ExchangeInstance {
|
|
|
27215
27316
|
symbol,
|
|
27216
27317
|
quantity,
|
|
27217
27318
|
});
|
|
27218
|
-
return await this.
|
|
27319
|
+
return await this._methods.formatQuantity(symbol, quantity);
|
|
27219
27320
|
};
|
|
27220
27321
|
/**
|
|
27221
27322
|
* Format price according to exchange precision rules.
|
|
@@ -27237,9 +27338,33 @@ class ExchangeInstance {
|
|
|
27237
27338
|
symbol,
|
|
27238
27339
|
price,
|
|
27239
27340
|
});
|
|
27240
|
-
return await this.
|
|
27341
|
+
return await this._methods.formatPrice(symbol, price);
|
|
27241
27342
|
};
|
|
27242
|
-
|
|
27343
|
+
/**
|
|
27344
|
+
* Fetch order book for a trading pair.
|
|
27345
|
+
*
|
|
27346
|
+
* @param symbol - Trading pair symbol
|
|
27347
|
+
* @returns Promise resolving to order book data
|
|
27348
|
+
* @throws Error if getOrderBook is not implemented
|
|
27349
|
+
*
|
|
27350
|
+
* @example
|
|
27351
|
+
* ```typescript
|
|
27352
|
+
* const instance = new ExchangeInstance("binance");
|
|
27353
|
+
* const orderBook = await instance.getOrderBook("BTCUSDT");
|
|
27354
|
+
* console.log(orderBook.bids); // [{ price: "50000.00", quantity: "0.5" }, ...]
|
|
27355
|
+
* ```
|
|
27356
|
+
*/
|
|
27357
|
+
this.getOrderBook = async (symbol) => {
|
|
27358
|
+
bt.loggerService.info(EXCHANGE_METHOD_NAME_GET_ORDER_BOOK, {
|
|
27359
|
+
exchangeName: this.exchangeName,
|
|
27360
|
+
symbol,
|
|
27361
|
+
});
|
|
27362
|
+
const to = new Date(Date.now());
|
|
27363
|
+
const from = new Date(to.getTime() - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * 60 * 1000);
|
|
27364
|
+
return await this._methods.getOrderBook(symbol, from, to);
|
|
27365
|
+
};
|
|
27366
|
+
const schema = bt.exchangeSchemaService.get(this.exchangeName);
|
|
27367
|
+
this._methods = CREATE_EXCHANGE_INSTANCE_FN(schema);
|
|
27243
27368
|
}
|
|
27244
27369
|
}
|
|
27245
27370
|
/**
|
|
@@ -27325,6 +27450,18 @@ class ExchangeUtils {
|
|
|
27325
27450
|
const instance = this._getInstance(context.exchangeName);
|
|
27326
27451
|
return await instance.formatPrice(symbol, price);
|
|
27327
27452
|
};
|
|
27453
|
+
/**
|
|
27454
|
+
* Fetch order book for a trading pair.
|
|
27455
|
+
*
|
|
27456
|
+
* @param symbol - Trading pair symbol
|
|
27457
|
+
* @param context - Execution context with exchange name
|
|
27458
|
+
* @returns Promise resolving to order book data
|
|
27459
|
+
*/
|
|
27460
|
+
this.getOrderBook = async (symbol, context) => {
|
|
27461
|
+
bt.exchangeValidationService.validate(context.exchangeName, EXCHANGE_METHOD_NAME_GET_ORDER_BOOK);
|
|
27462
|
+
const instance = this._getInstance(context.exchangeName);
|
|
27463
|
+
return await instance.getOrderBook(symbol);
|
|
27464
|
+
};
|
|
27328
27465
|
}
|
|
27329
27466
|
}
|
|
27330
27467
|
/**
|