@tradejs/node 3.0.0 → 3.1.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/README.md +2 -0
- package/dist/ai.d.mts +26 -4
- package/dist/ai.d.ts +26 -4
- package/dist/ai.js +161 -165
- package/dist/ai.mjs +3 -1
- package/dist/backtest.js +496 -188
- package/dist/backtest.mjs +3 -3
- package/dist/chunk-3TWULKHV.mjs +595 -0
- package/dist/chunk-FB5NUEOQ.mjs +295 -0
- package/dist/{chunk-VRXU4E4O.mjs → chunk-KOQSLFT2.mjs} +169 -169
- package/dist/chunk-LAJ7NA3Q.mjs +377 -0
- package/dist/{chunk-OGAWBO3Z.mjs → chunk-MKLTJQLY.mjs} +1 -1
- package/dist/cli.js +150 -161
- package/dist/cli.mjs +1 -1
- package/dist/connectors.d.mts +5 -2
- package/dist/connectors.d.ts +5 -2
- package/dist/connectors.js +329 -8
- package/dist/connectors.mjs +5 -1
- package/dist/registry.d.mts +2 -1
- package/dist/registry.d.ts +2 -1
- package/dist/registry.js +157 -161
- package/dist/registry.mjs +4 -2
- package/dist/runtimeDashboard.d.mts +12 -0
- package/dist/runtimeDashboard.d.ts +12 -0
- package/dist/runtimeDashboard.js +8433 -0
- package/dist/runtimeDashboard.mjs +955 -0
- package/dist/runtimeTrades.d.mts +31 -0
- package/dist/runtimeTrades.d.ts +31 -0
- package/dist/runtimeTrades.js +321 -0
- package/dist/runtimeTrades.mjs +11 -0
- package/dist/strategies.d.mts +2 -2
- package/dist/strategies.d.ts +2 -2
- package/dist/strategies.js +168 -165
- package/dist/strategies.mjs +13 -372
- package/package.json +18 -5
- package/dist/chunk-V3YMKE4I.mjs +0 -271
package/dist/backtest.js
CHANGED
|
@@ -44,10 +44,10 @@ __reExport(backtest_exports, require("@tradejs/core/backtest"), module.exports);
|
|
|
44
44
|
|
|
45
45
|
// src/testing.ts
|
|
46
46
|
var import_indicators3 = require("@tradejs/core/indicators");
|
|
47
|
-
var
|
|
47
|
+
var import_constants9 = require("@tradejs/core/constants");
|
|
48
48
|
var import_strategies7 = require("@tradejs/core/strategies");
|
|
49
|
-
var
|
|
50
|
-
var
|
|
49
|
+
var import_time3 = require("@tradejs/core/time");
|
|
50
|
+
var import_logger15 = require("@tradejs/infra/logger");
|
|
51
51
|
|
|
52
52
|
// src/strategyRuntime.ts
|
|
53
53
|
var import_constants5 = require("@tradejs/core/constants");
|
|
@@ -60,11 +60,8 @@ var import_constants3 = require("@tradejs/core/constants");
|
|
|
60
60
|
var import_ml2 = require("@tradejs/infra/ml");
|
|
61
61
|
|
|
62
62
|
// src/ai.ts
|
|
63
|
-
var
|
|
64
|
-
var import_aiEndpoints = require("@tradejs/core/aiEndpoints");
|
|
65
|
-
var import_aiModels = require("@tradejs/core/aiModels");
|
|
63
|
+
var import_aiLanguages2 = require("@tradejs/core/aiLanguages");
|
|
66
64
|
var import_redis = require("@tradejs/infra/redis");
|
|
67
|
-
var import_userSettings = require("@tradejs/infra/userSettings");
|
|
68
65
|
|
|
69
66
|
// src/aiShared.ts
|
|
70
67
|
var MAX_AI_SERIES_POINTS = 5;
|
|
@@ -1273,6 +1270,139 @@ var postProcessLocalAiAnalysisByStrategy = (signal, analysis, payload = buildAiP
|
|
|
1273
1270
|
}) ?? strategyAnalysis;
|
|
1274
1271
|
};
|
|
1275
1272
|
|
|
1273
|
+
// src/aiProvider.ts
|
|
1274
|
+
var import_aiEndpoints = require("@tradejs/core/aiEndpoints");
|
|
1275
|
+
var import_aiModels = require("@tradejs/core/aiModels");
|
|
1276
|
+
var import_aiLanguages = require("@tradejs/core/aiLanguages");
|
|
1277
|
+
var import_userSettings = require("@tradejs/infra/userSettings");
|
|
1278
|
+
var DEFAULT_AI_MODEL = "openai/gpt-5-mini";
|
|
1279
|
+
var userSettingsCache = /* @__PURE__ */ new Map();
|
|
1280
|
+
var aiModelCache = /* @__PURE__ */ new Map();
|
|
1281
|
+
var normalizeResponseContent = (content) => {
|
|
1282
|
+
if (typeof content === "string") return content;
|
|
1283
|
+
if (content && typeof content === "object" && !Array.isArray(content)) {
|
|
1284
|
+
return content;
|
|
1285
|
+
}
|
|
1286
|
+
if (Array.isArray(content)) {
|
|
1287
|
+
return content.map(
|
|
1288
|
+
(part) => typeof part?.text === "string" ? part.text : ""
|
|
1289
|
+
).join("\n").trim();
|
|
1290
|
+
}
|
|
1291
|
+
return String(content ?? "");
|
|
1292
|
+
};
|
|
1293
|
+
var getAiInvocationError = (error) => {
|
|
1294
|
+
const details = error instanceof Error && error.message.trim() ? error.message.trim() : String(error);
|
|
1295
|
+
const isEmptyCompletion = error instanceof TypeError && /Cannot read properties of undefined \(reading ['"]message['"]\)/.test(
|
|
1296
|
+
details
|
|
1297
|
+
);
|
|
1298
|
+
const wrapped = new Error(
|
|
1299
|
+
isEmptyCompletion ? "AI provider returned an empty chat completion" : `AI model invocation failed: ${details}`
|
|
1300
|
+
);
|
|
1301
|
+
wrapped.cause = error;
|
|
1302
|
+
return wrapped;
|
|
1303
|
+
};
|
|
1304
|
+
var isEmptyResponseContent = (content) => typeof content === "string" ? content.trim().length === 0 : Object.keys(content).length === 0;
|
|
1305
|
+
var getAiModelCacheKey = (userName, modelName, temperature) => `${userName}::${modelName}::${temperature}`;
|
|
1306
|
+
var resolveAiModelName = (settings, requestedModelName) => {
|
|
1307
|
+
const explicitModelName = requestedModelName?.trim() ?? "";
|
|
1308
|
+
if (explicitModelName) return explicitModelName;
|
|
1309
|
+
return settings.AI_MODEL?.trim() || DEFAULT_AI_MODEL;
|
|
1310
|
+
};
|
|
1311
|
+
var getOpenRouterModelKwargs = (apiEndpoint) => {
|
|
1312
|
+
const endpoint = String(apiEndpoint ?? "").trim();
|
|
1313
|
+
if (!endpoint) return {};
|
|
1314
|
+
let hostname = "";
|
|
1315
|
+
try {
|
|
1316
|
+
hostname = new URL(endpoint).hostname;
|
|
1317
|
+
} catch {
|
|
1318
|
+
hostname = endpoint;
|
|
1319
|
+
}
|
|
1320
|
+
return hostname.toLowerCase().includes("openrouter") ? { provider: { ignore: ["azure"] } } : {};
|
|
1321
|
+
};
|
|
1322
|
+
var getAiUserSettings = async (userName = "root") => {
|
|
1323
|
+
let settingsPromise = userSettingsCache.get(userName);
|
|
1324
|
+
if (!settingsPromise) {
|
|
1325
|
+
settingsPromise = (0, import_userSettings.getUserSettings)(userName).then((settings2) => {
|
|
1326
|
+
const endpoint = (0, import_aiEndpoints.normalizeAiEndpoint)(settings2.AI_API_ENDPOINT);
|
|
1327
|
+
return {
|
|
1328
|
+
...settings2,
|
|
1329
|
+
AI_API_ENDPOINT: endpoint,
|
|
1330
|
+
AI_MODEL: (0, import_aiModels.normalizeAiModel)(settings2.AI_MODEL, endpoint),
|
|
1331
|
+
AI_RESPONSE_LANGUAGE: (0, import_aiLanguages.normalizeAiResponseLanguage)(
|
|
1332
|
+
settings2.AI_RESPONSE_LANGUAGE
|
|
1333
|
+
)
|
|
1334
|
+
};
|
|
1335
|
+
});
|
|
1336
|
+
settingsPromise.catch(() => userSettingsCache.delete(userName));
|
|
1337
|
+
userSettingsCache.set(userName, settingsPromise);
|
|
1338
|
+
}
|
|
1339
|
+
const settings = await settingsPromise;
|
|
1340
|
+
if (!settings.AI_API_KEY || !settings.AI_API_ENDPOINT) {
|
|
1341
|
+
throw new Error(`AI settings are incomplete for user ${userName}`);
|
|
1342
|
+
}
|
|
1343
|
+
return settings;
|
|
1344
|
+
};
|
|
1345
|
+
var getAiModel = async (userName = "root", requestedModelName, temperature = 0.2) => {
|
|
1346
|
+
const settings = await getAiUserSettings(userName);
|
|
1347
|
+
const modelName = resolveAiModelName(settings, requestedModelName);
|
|
1348
|
+
const cacheKey = getAiModelCacheKey(userName, modelName, temperature);
|
|
1349
|
+
let modelPromise = aiModelCache.get(cacheKey);
|
|
1350
|
+
if (!modelPromise) {
|
|
1351
|
+
modelPromise = import("@langchain/openai").then(({ ChatOpenAI }) => {
|
|
1352
|
+
const modelKwargs = getOpenRouterModelKwargs(settings.AI_API_ENDPOINT);
|
|
1353
|
+
return new ChatOpenAI({
|
|
1354
|
+
temperature,
|
|
1355
|
+
modelName,
|
|
1356
|
+
apiKey: settings.AI_API_KEY,
|
|
1357
|
+
...Object.keys(modelKwargs).length ? { modelKwargs } : {},
|
|
1358
|
+
configuration: {
|
|
1359
|
+
baseURL: settings.AI_API_ENDPOINT,
|
|
1360
|
+
defaultHeaders: {
|
|
1361
|
+
"HTTP-Referer": "https://tradejs.dev",
|
|
1362
|
+
"X-Title": "Inv"
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
});
|
|
1366
|
+
});
|
|
1367
|
+
modelPromise.catch(() => aiModelCache.delete(cacheKey));
|
|
1368
|
+
aiModelCache.set(cacheKey, modelPromise);
|
|
1369
|
+
}
|
|
1370
|
+
try {
|
|
1371
|
+
return await modelPromise;
|
|
1372
|
+
} catch (error) {
|
|
1373
|
+
aiModelCache.delete(cacheKey);
|
|
1374
|
+
userSettingsCache.delete(userName);
|
|
1375
|
+
throw error;
|
|
1376
|
+
}
|
|
1377
|
+
};
|
|
1378
|
+
var invokeAiChatWithUserMessageEncoding = async ({
|
|
1379
|
+
messages,
|
|
1380
|
+
userName = "root",
|
|
1381
|
+
model,
|
|
1382
|
+
temperature = 0.2
|
|
1383
|
+
}, resolveUserMessageEncoding) => {
|
|
1384
|
+
const [{ HumanMessage, SystemMessage }, aiModel] = await Promise.all([
|
|
1385
|
+
import("@langchain/core/messages"),
|
|
1386
|
+
getAiModel(userName, model, temperature)
|
|
1387
|
+
]);
|
|
1388
|
+
const providerMessages = messages.map(
|
|
1389
|
+
(message) => message.role === "system" ? new SystemMessage(message.content) : new HumanMessage(
|
|
1390
|
+
resolveUserMessageEncoding(message) === "text-block" ? { content: [{ type: "text", text: message.content }] } : message.content
|
|
1391
|
+
)
|
|
1392
|
+
);
|
|
1393
|
+
try {
|
|
1394
|
+
const response = await aiModel.invoke(providerMessages);
|
|
1395
|
+
const content = normalizeResponseContent(response?.content);
|
|
1396
|
+
if (isEmptyResponseContent(content)) {
|
|
1397
|
+
throw new Error("AI provider returned an empty chat completion");
|
|
1398
|
+
}
|
|
1399
|
+
return { content };
|
|
1400
|
+
} catch (error) {
|
|
1401
|
+
throw getAiInvocationError(error);
|
|
1402
|
+
}
|
|
1403
|
+
};
|
|
1404
|
+
var invokeAiPromptChat = (options) => invokeAiChatWithUserMessageEncoding(options, () => "text-block");
|
|
1405
|
+
|
|
1276
1406
|
// src/ai.ts
|
|
1277
1407
|
var parseAIResponse = (input) => {
|
|
1278
1408
|
try {
|
|
@@ -1286,18 +1416,6 @@ var parseAIResponse = (input) => {
|
|
|
1286
1416
|
return {};
|
|
1287
1417
|
}
|
|
1288
1418
|
};
|
|
1289
|
-
var normalizeResponseContent = (content) => {
|
|
1290
|
-
if (typeof content === "string" || content && typeof content === "object") {
|
|
1291
|
-
if (typeof content !== "object" || !Array.isArray(content)) {
|
|
1292
|
-
return content;
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
if (Array.isArray(content)) {
|
|
1296
|
-
const text = content.map((part) => typeof part?.text === "string" ? part.text : "").join("\n").trim();
|
|
1297
|
-
return text;
|
|
1298
|
-
}
|
|
1299
|
-
return String(content ?? "");
|
|
1300
|
-
};
|
|
1301
1419
|
var normalizeAnalysis = (raw) => {
|
|
1302
1420
|
const direction = raw?.direction === "LONG" || raw?.direction === "SHORT" ? raw.direction : null;
|
|
1303
1421
|
const qualityNum = typeof raw?.quality === "number" ? Math.max(1, Math.min(5, Math.round(raw.quality))) : void 0;
|
|
@@ -1531,153 +1649,24 @@ Trade payload:
|
|
|
1531
1649
|
${JSON.stringify(payload)}
|
|
1532
1650
|
${buildAiHumanPromptAddonByStrategy(signal, payload)}
|
|
1533
1651
|
`;
|
|
1534
|
-
var getAiInvocationError = (error) => {
|
|
1535
|
-
const details = error instanceof Error && error.message.trim() ? error.message.trim() : String(error);
|
|
1536
|
-
const isEmptyCompletion = error instanceof TypeError && /Cannot read properties of undefined \(reading ['"]message['"]\)/.test(
|
|
1537
|
-
details
|
|
1538
|
-
);
|
|
1539
|
-
const wrapped = new Error(
|
|
1540
|
-
isEmptyCompletion ? "AI provider returned an empty chat completion" : `AI model invocation failed: ${details}`
|
|
1541
|
-
);
|
|
1542
|
-
wrapped.cause = error;
|
|
1543
|
-
return wrapped;
|
|
1544
|
-
};
|
|
1545
|
-
var isEmptyResponseContent = (content) => typeof content === "string" ? content.trim().length === 0 : Object.keys(content).length === 0;
|
|
1546
|
-
var DEFAULT_AI_MODEL = "openai/gpt-5-mini";
|
|
1547
|
-
var userSettingsCache = /* @__PURE__ */ new Map();
|
|
1548
|
-
var aiModelCache = /* @__PURE__ */ new Map();
|
|
1549
|
-
var getAiModelCacheKey = (userName, modelName) => `${userName}::${modelName}`;
|
|
1550
|
-
var resolveAiModelName = (settings, requestedModelName) => {
|
|
1551
|
-
const explicitModelName = typeof requestedModelName === "string" ? requestedModelName.trim() : "";
|
|
1552
|
-
if (explicitModelName) {
|
|
1553
|
-
return explicitModelName;
|
|
1554
|
-
}
|
|
1555
|
-
const settingsModelName = typeof settings.AI_MODEL === "string" ? settings.AI_MODEL.trim() : "";
|
|
1556
|
-
return settingsModelName || DEFAULT_AI_MODEL;
|
|
1557
|
-
};
|
|
1558
|
-
var getOpenRouterModelKwargs = (apiEndpoint) => {
|
|
1559
|
-
const endpoint = String(apiEndpoint ?? "").trim();
|
|
1560
|
-
if (!endpoint) {
|
|
1561
|
-
return {};
|
|
1562
|
-
}
|
|
1563
|
-
let hostname = "";
|
|
1564
|
-
try {
|
|
1565
|
-
hostname = new URL(endpoint).hostname;
|
|
1566
|
-
} catch {
|
|
1567
|
-
hostname = endpoint;
|
|
1568
|
-
}
|
|
1569
|
-
if (!hostname.toLowerCase().includes("openrouter")) {
|
|
1570
|
-
return {};
|
|
1571
|
-
}
|
|
1572
|
-
return {
|
|
1573
|
-
provider: {
|
|
1574
|
-
ignore: ["azure"]
|
|
1575
|
-
}
|
|
1576
|
-
};
|
|
1577
|
-
};
|
|
1578
|
-
var getAiSettings = async (userName = "root") => {
|
|
1579
|
-
let settingsPromise = userSettingsCache.get(userName);
|
|
1580
|
-
if (!settingsPromise) {
|
|
1581
|
-
settingsPromise = (0, import_userSettings.getUserSettings)(userName).then((settings2) => {
|
|
1582
|
-
const endpoint = (0, import_aiEndpoints.normalizeAiEndpoint)(settings2.AI_API_ENDPOINT);
|
|
1583
|
-
return {
|
|
1584
|
-
...settings2,
|
|
1585
|
-
AI_API_ENDPOINT: endpoint,
|
|
1586
|
-
AI_MODEL: (0, import_aiModels.normalizeAiModel)(settings2.AI_MODEL, endpoint),
|
|
1587
|
-
AI_RESPONSE_LANGUAGE: (0, import_aiLanguages.normalizeAiResponseLanguage)(
|
|
1588
|
-
settings2.AI_RESPONSE_LANGUAGE
|
|
1589
|
-
)
|
|
1590
|
-
};
|
|
1591
|
-
});
|
|
1592
|
-
settingsPromise.catch(() => {
|
|
1593
|
-
userSettingsCache.delete(userName);
|
|
1594
|
-
});
|
|
1595
|
-
userSettingsCache.set(userName, settingsPromise);
|
|
1596
|
-
}
|
|
1597
|
-
const settings = await settingsPromise;
|
|
1598
|
-
if (!settings.AI_API_KEY || !settings.AI_API_ENDPOINT) {
|
|
1599
|
-
throw new Error(`AI settings are incomplete for user ${userName}`);
|
|
1600
|
-
}
|
|
1601
|
-
return settings;
|
|
1602
|
-
};
|
|
1603
|
-
var createAiModel = async (userName = "root", requestedModelName) => {
|
|
1604
|
-
const settings = await getAiSettings(userName);
|
|
1605
|
-
const modelName = resolveAiModelName(settings, requestedModelName);
|
|
1606
|
-
const cacheKey = getAiModelCacheKey(userName, modelName);
|
|
1607
|
-
let modelPromise = aiModelCache.get(cacheKey);
|
|
1608
|
-
if (!modelPromise) {
|
|
1609
|
-
modelPromise = (async () => {
|
|
1610
|
-
const { ChatOpenAI } = await import("@langchain/openai");
|
|
1611
|
-
const modelKwargs = getOpenRouterModelKwargs(settings.AI_API_ENDPOINT);
|
|
1612
|
-
return new ChatOpenAI({
|
|
1613
|
-
temperature: 0.2,
|
|
1614
|
-
modelName,
|
|
1615
|
-
apiKey: settings.AI_API_KEY,
|
|
1616
|
-
...Object.keys(modelKwargs).length ? { modelKwargs } : {},
|
|
1617
|
-
configuration: {
|
|
1618
|
-
baseURL: settings.AI_API_ENDPOINT,
|
|
1619
|
-
defaultHeaders: {
|
|
1620
|
-
"HTTP-Referer": "https://tradejs.dev",
|
|
1621
|
-
"X-Title": "Inv"
|
|
1622
|
-
}
|
|
1623
|
-
}
|
|
1624
|
-
});
|
|
1625
|
-
})();
|
|
1626
|
-
modelPromise.catch(() => {
|
|
1627
|
-
aiModelCache.delete(cacheKey);
|
|
1628
|
-
});
|
|
1629
|
-
aiModelCache.set(cacheKey, modelPromise);
|
|
1630
|
-
}
|
|
1631
|
-
return modelPromise;
|
|
1632
|
-
};
|
|
1633
|
-
var getAiModel = async (userName = "root", requestedModelName) => {
|
|
1634
|
-
const settings = await getAiSettings(userName);
|
|
1635
|
-
const resolvedModelName = resolveAiModelName(settings, requestedModelName);
|
|
1636
|
-
try {
|
|
1637
|
-
return await createAiModel(userName, resolvedModelName);
|
|
1638
|
-
} catch (error) {
|
|
1639
|
-
aiModelCache.delete(getAiModelCacheKey(userName, resolvedModelName));
|
|
1640
|
-
userSettingsCache.delete(userName);
|
|
1641
|
-
throw error;
|
|
1642
|
-
}
|
|
1643
|
-
};
|
|
1644
1652
|
var runAiPrompt = async ({ systemPrompt, humanPrompt }, options = {}) => {
|
|
1645
|
-
const
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
getAiSettings(options.userName)
|
|
1649
|
-
]);
|
|
1650
|
-
const messages = [];
|
|
1651
|
-
const responseLanguage = (0, import_aiLanguages.getAiResponseLanguagePromptName)(
|
|
1652
|
-
settings.AI_RESPONSE_LANGUAGE || import_aiLanguages.DEFAULT_AI_RESPONSE_LANGUAGE
|
|
1653
|
-
);
|
|
1654
|
-
messages.push(new SystemMessage(systemPrompt));
|
|
1655
|
-
messages.push(
|
|
1656
|
-
new SystemMessage(
|
|
1657
|
-
`Write all user-visible text fields in ${responseLanguage}. Keep field names and JSON syntax unchanged.`
|
|
1658
|
-
)
|
|
1659
|
-
);
|
|
1660
|
-
messages.push(
|
|
1661
|
-
new HumanMessage({
|
|
1662
|
-
content: [
|
|
1663
|
-
{
|
|
1664
|
-
type: "text",
|
|
1665
|
-
text: humanPrompt
|
|
1666
|
-
}
|
|
1667
|
-
]
|
|
1668
|
-
})
|
|
1653
|
+
const settings = await getAiUserSettings(options.userName);
|
|
1654
|
+
const responseLanguage = (0, import_aiLanguages2.getAiResponseLanguagePromptName)(
|
|
1655
|
+
settings.AI_RESPONSE_LANGUAGE || import_aiLanguages2.DEFAULT_AI_RESPONSE_LANGUAGE
|
|
1669
1656
|
);
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1657
|
+
const response = await invokeAiPromptChat({
|
|
1658
|
+
userName: options.userName,
|
|
1659
|
+
model: options.model,
|
|
1660
|
+
messages: [
|
|
1661
|
+
{ role: "system", content: systemPrompt },
|
|
1662
|
+
{
|
|
1663
|
+
role: "system",
|
|
1664
|
+
content: `Write all user-visible text fields in ${responseLanguage}. Keep field names and JSON syntax unchanged.`
|
|
1665
|
+
},
|
|
1666
|
+
{ role: "user", content: humanPrompt }
|
|
1667
|
+
]
|
|
1668
|
+
});
|
|
1669
|
+
const parsed = parseAIResponse(response.content);
|
|
1681
1670
|
const normalized = normalizeAnalysis(parsed);
|
|
1682
1671
|
if (!options.signal) {
|
|
1683
1672
|
return normalized;
|
|
@@ -6241,7 +6230,323 @@ var createStrategyRuntime = ({
|
|
|
6241
6230
|
setStrategyRuntimeFactory(createStrategyRuntime);
|
|
6242
6231
|
|
|
6243
6232
|
// src/connectorsRegistry.ts
|
|
6233
|
+
var import_logger14 = require("@tradejs/infra/logger");
|
|
6234
|
+
|
|
6235
|
+
// src/connectorRuntime.ts
|
|
6236
|
+
var import_logger13 = require("@tradejs/infra/logger");
|
|
6237
|
+
var import_tradingAccounts = require("@tradejs/infra/tradingAccounts");
|
|
6238
|
+
|
|
6239
|
+
// src/timescaleCachedKline.ts
|
|
6240
|
+
var import_constants6 = require("@tradejs/core/constants");
|
|
6241
|
+
var import_data4 = require("@tradejs/core/data");
|
|
6242
|
+
var import_time2 = require("@tradejs/core/time");
|
|
6243
|
+
var import_async = require("@tradejs/core/async");
|
|
6244
6244
|
var import_logger12 = require("@tradejs/infra/logger");
|
|
6245
|
+
var import_candles = require("@tradejs/infra/timescale/candles");
|
|
6246
|
+
var DEFAULT_LIMIT = 1e3;
|
|
6247
|
+
var DEFAULT_CACHE_FALLBACK_WINDOW = 1e3;
|
|
6248
|
+
var DEFAULT_TIMESCALE_RETRIES = 2;
|
|
6249
|
+
var DEFAULT_TIMESCALE_RETRY_DELAY_MS = 1e3;
|
|
6250
|
+
var resolveNonNegativeInt = (value, fallback) => {
|
|
6251
|
+
const parsed = Number(value);
|
|
6252
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
6253
|
+
return fallback;
|
|
6254
|
+
}
|
|
6255
|
+
return Math.floor(parsed);
|
|
6256
|
+
};
|
|
6257
|
+
var getTimescaleRetryCount = () => resolveNonNegativeInt(
|
|
6258
|
+
process.env.TIMESCALE_KLINE_RETRIES,
|
|
6259
|
+
DEFAULT_TIMESCALE_RETRIES
|
|
6260
|
+
);
|
|
6261
|
+
var getTimescaleRetryDelayMs = () => resolveNonNegativeInt(
|
|
6262
|
+
process.env.TIMESCALE_KLINE_RETRY_DELAY_MS,
|
|
6263
|
+
DEFAULT_TIMESCALE_RETRY_DELAY_MS
|
|
6264
|
+
);
|
|
6265
|
+
var intervalMsOf = (interval) => interval * 6e4;
|
|
6266
|
+
var clampToClosedCandle = (value, intervalMs) => Math.floor(value / intervalMs) * intervalMs;
|
|
6267
|
+
var normalizeRangeToClosed = (intervalMs, start, end) => {
|
|
6268
|
+
const lastClosed = Math.floor(Date.now() / intervalMs) * intervalMs;
|
|
6269
|
+
const normStart = start !== void 0 ? clampToClosedCandle(start, intervalMs) : 0;
|
|
6270
|
+
const cappedEnd = Math.min(end ?? Date.now(), lastClosed);
|
|
6271
|
+
const normEnd = clampToClosedCandle(cappedEnd, intervalMs);
|
|
6272
|
+
return { normStart, normEnd };
|
|
6273
|
+
};
|
|
6274
|
+
var rowsToKline = (rows) => rows.map(({ ts, ...data }) => ({
|
|
6275
|
+
timestamp: new Date(ts).getTime(),
|
|
6276
|
+
...data
|
|
6277
|
+
}));
|
|
6278
|
+
var createTimescaleCachedKline = ({
|
|
6279
|
+
provider,
|
|
6280
|
+
request,
|
|
6281
|
+
intervalToMinutes,
|
|
6282
|
+
limit = DEFAULT_LIMIT,
|
|
6283
|
+
cacheFallbackWindow = DEFAULT_CACHE_FALLBACK_WINDOW
|
|
6284
|
+
}) => {
|
|
6285
|
+
let isTimescaleFallbackMode = false;
|
|
6286
|
+
const runTimescaleOperation = async (operation) => {
|
|
6287
|
+
const retries = getTimescaleRetryCount();
|
|
6288
|
+
const retryDelayMs = getTimescaleRetryDelayMs();
|
|
6289
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
6290
|
+
try {
|
|
6291
|
+
return await operation();
|
|
6292
|
+
} catch (error) {
|
|
6293
|
+
if (attempt >= retries) {
|
|
6294
|
+
throw error;
|
|
6295
|
+
}
|
|
6296
|
+
const waitMs = retryDelayMs * 2 ** attempt;
|
|
6297
|
+
if (waitMs > 0) {
|
|
6298
|
+
await (0, import_async.delay)(waitMs);
|
|
6299
|
+
}
|
|
6300
|
+
}
|
|
6301
|
+
}
|
|
6302
|
+
};
|
|
6303
|
+
const loadData = async (direction, pointer, limitBoundary, requestParams, intervalMs, options = {}) => {
|
|
6304
|
+
if (pointer === void 0) return { data: [], loaded: false };
|
|
6305
|
+
let accumulated = [];
|
|
6306
|
+
let fulfilled = false;
|
|
6307
|
+
let loaded = false;
|
|
6308
|
+
const shouldAccumulate = options.accumulate ?? true;
|
|
6309
|
+
while (!fulfilled) {
|
|
6310
|
+
const currentPointer = pointer;
|
|
6311
|
+
const params = {
|
|
6312
|
+
symbol: requestParams.symbol,
|
|
6313
|
+
interval: requestParams.interval,
|
|
6314
|
+
silent: requestParams.silent,
|
|
6315
|
+
end: requestParams.end
|
|
6316
|
+
};
|
|
6317
|
+
if (direction === "older") {
|
|
6318
|
+
params.end = pointer;
|
|
6319
|
+
if (limitBoundary !== void 0) params.start = limitBoundary;
|
|
6320
|
+
} else {
|
|
6321
|
+
params.start = pointer;
|
|
6322
|
+
if (limitBoundary !== void 0) params.end = limitBoundary;
|
|
6323
|
+
}
|
|
6324
|
+
const partData = await request(params);
|
|
6325
|
+
if (!partData.length) {
|
|
6326
|
+
fulfilled = true;
|
|
6327
|
+
break;
|
|
6328
|
+
}
|
|
6329
|
+
loaded = true;
|
|
6330
|
+
if (options.onPartData) {
|
|
6331
|
+
await options.onPartData(partData);
|
|
6332
|
+
}
|
|
6333
|
+
if (shouldAccumulate) {
|
|
6334
|
+
accumulated = direction === "older" ? (0, import_data4.mergeData)(partData, accumulated) : (0, import_data4.mergeData)(accumulated, partData);
|
|
6335
|
+
}
|
|
6336
|
+
const boundaryReached = limitBoundary !== void 0 && (direction === "older" && currentPointer <= limitBoundary || direction === "newer" && currentPointer >= limitBoundary);
|
|
6337
|
+
if (partData.length < limit || boundaryReached) {
|
|
6338
|
+
fulfilled = true;
|
|
6339
|
+
break;
|
|
6340
|
+
}
|
|
6341
|
+
const nextPointer = direction === "older" ? (0, import_time2.getItemTimestamp)(partData[0]) - intervalMs : (0, import_time2.getItemTimestamp)(partData[partData.length - 1]) + intervalMs;
|
|
6342
|
+
if (!Number.isFinite(nextPointer) || nextPointer === currentPointer) {
|
|
6343
|
+
fulfilled = true;
|
|
6344
|
+
break;
|
|
6345
|
+
}
|
|
6346
|
+
pointer = nextPointer;
|
|
6347
|
+
}
|
|
6348
|
+
return { data: accumulated, loaded };
|
|
6349
|
+
};
|
|
6350
|
+
const refreshTail = async ({
|
|
6351
|
+
symbol,
|
|
6352
|
+
interval,
|
|
6353
|
+
silent,
|
|
6354
|
+
tailCount = 2
|
|
6355
|
+
}) => {
|
|
6356
|
+
const intMinutes = intervalToMinutes(interval);
|
|
6357
|
+
if (!intMinutes) {
|
|
6358
|
+
import_logger12.logger.log("error", "refreshTail: invalid interval %s", interval);
|
|
6359
|
+
return;
|
|
6360
|
+
}
|
|
6361
|
+
const intervalMs = intervalMsOf(intMinutes);
|
|
6362
|
+
const lastClosed = Math.floor(Date.now() / intervalMs) * intervalMs;
|
|
6363
|
+
const tailEnd = lastClosed + intervalMs;
|
|
6364
|
+
const tailStart = tailEnd - tailCount * intervalMs;
|
|
6365
|
+
const part = await request({
|
|
6366
|
+
symbol,
|
|
6367
|
+
interval,
|
|
6368
|
+
start: tailStart,
|
|
6369
|
+
end: tailEnd,
|
|
6370
|
+
silent
|
|
6371
|
+
});
|
|
6372
|
+
if (part.length) {
|
|
6373
|
+
await runTimescaleOperation(
|
|
6374
|
+
() => (0, import_candles.upsertCandles)((0, import_candles.toRows)(provider, symbol, intMinutes, part))
|
|
6375
|
+
);
|
|
6376
|
+
}
|
|
6377
|
+
};
|
|
6378
|
+
return async ({
|
|
6379
|
+
symbol,
|
|
6380
|
+
interval,
|
|
6381
|
+
start: defaultStart,
|
|
6382
|
+
end: defaultEnd,
|
|
6383
|
+
silent = false,
|
|
6384
|
+
cacheOnly = false,
|
|
6385
|
+
warmOnly = false
|
|
6386
|
+
}) => {
|
|
6387
|
+
const intMinutes = intervalToMinutes(interval);
|
|
6388
|
+
if (!intMinutes) {
|
|
6389
|
+
import_logger12.logger.log("error", "kline: invalid interval %s", interval);
|
|
6390
|
+
return [];
|
|
6391
|
+
}
|
|
6392
|
+
if (defaultStart !== void 0 && defaultEnd !== void 0 && defaultEnd <= defaultStart) {
|
|
6393
|
+
return [];
|
|
6394
|
+
}
|
|
6395
|
+
const intervalMs = intervalMsOf(intMinutes);
|
|
6396
|
+
try {
|
|
6397
|
+
const edges = await runTimescaleOperation(
|
|
6398
|
+
() => (0, import_candles.getDataEdges)(provider, symbol, intMinutes)
|
|
6399
|
+
);
|
|
6400
|
+
let dataStart = edges.min;
|
|
6401
|
+
let dataEnd = edges.max;
|
|
6402
|
+
const { normStart, normEnd } = normalizeRangeToClosed(
|
|
6403
|
+
intervalMs,
|
|
6404
|
+
defaultStart,
|
|
6405
|
+
defaultEnd
|
|
6406
|
+
);
|
|
6407
|
+
if (cacheOnly) {
|
|
6408
|
+
const base = edges.max ?? Date.now();
|
|
6409
|
+
const s = Math.max(
|
|
6410
|
+
defaultStart ?? base - cacheFallbackWindow * intervalMs,
|
|
6411
|
+
0
|
|
6412
|
+
);
|
|
6413
|
+
const e = defaultEnd ?? base;
|
|
6414
|
+
const dbData2 = await runTimescaleOperation(
|
|
6415
|
+
() => (0, import_candles.getCandlesRange)(provider, symbol, intMinutes, s, e)
|
|
6416
|
+
);
|
|
6417
|
+
return rowsToKline(dbData2);
|
|
6418
|
+
}
|
|
6419
|
+
const needOlderData = defaultStart !== void 0 && (dataStart === void 0 || normStart < dataStart);
|
|
6420
|
+
const persistPartData = warmOnly ? (partData) => runTimescaleOperation(
|
|
6421
|
+
() => (0, import_candles.upsertCandles)((0, import_candles.toRows)(provider, symbol, intMinutes, partData))
|
|
6422
|
+
) : void 0;
|
|
6423
|
+
if (needOlderData) {
|
|
6424
|
+
const pointerForOlder = dataStart ?? normEnd ?? Date.now();
|
|
6425
|
+
const olderResult = await loadData(
|
|
6426
|
+
"older",
|
|
6427
|
+
pointerForOlder,
|
|
6428
|
+
normStart,
|
|
6429
|
+
{
|
|
6430
|
+
symbol,
|
|
6431
|
+
interval,
|
|
6432
|
+
silent,
|
|
6433
|
+
start: normStart,
|
|
6434
|
+
end: pointerForOlder
|
|
6435
|
+
},
|
|
6436
|
+
intervalMs,
|
|
6437
|
+
{
|
|
6438
|
+
accumulate: !warmOnly,
|
|
6439
|
+
onPartData: persistPartData
|
|
6440
|
+
}
|
|
6441
|
+
);
|
|
6442
|
+
if (warmOnly && olderResult.loaded) {
|
|
6443
|
+
dataStart = normStart;
|
|
6444
|
+
if (dataEnd === void 0) {
|
|
6445
|
+
dataEnd = normEnd;
|
|
6446
|
+
}
|
|
6447
|
+
} else if (olderResult.data.length) {
|
|
6448
|
+
await runTimescaleOperation(
|
|
6449
|
+
() => (0, import_candles.upsertCandles)(
|
|
6450
|
+
(0, import_candles.toRows)(provider, symbol, intMinutes, olderResult.data)
|
|
6451
|
+
)
|
|
6452
|
+
);
|
|
6453
|
+
dataStart = normStart;
|
|
6454
|
+
if (dataEnd === void 0) {
|
|
6455
|
+
dataEnd = normEnd;
|
|
6456
|
+
}
|
|
6457
|
+
}
|
|
6458
|
+
}
|
|
6459
|
+
const needNewerData = defaultEnd !== void 0 && (dataEnd === void 0 || normEnd > dataEnd);
|
|
6460
|
+
if (needNewerData) {
|
|
6461
|
+
const fallbackStart = (0, import_time2.getTimestamp)(import_constants6.PRELOAD_FALLBACK_DAYS);
|
|
6462
|
+
const pointerForNewer = dataEnd ?? (defaultStart !== void 0 ? normStart : fallbackStart) ?? 0;
|
|
6463
|
+
const newerResult = await loadData(
|
|
6464
|
+
"newer",
|
|
6465
|
+
pointerForNewer,
|
|
6466
|
+
normEnd,
|
|
6467
|
+
{
|
|
6468
|
+
symbol,
|
|
6469
|
+
interval,
|
|
6470
|
+
silent,
|
|
6471
|
+
start: pointerForNewer,
|
|
6472
|
+
end: normEnd
|
|
6473
|
+
},
|
|
6474
|
+
intervalMs,
|
|
6475
|
+
{
|
|
6476
|
+
accumulate: !warmOnly,
|
|
6477
|
+
onPartData: persistPartData
|
|
6478
|
+
}
|
|
6479
|
+
);
|
|
6480
|
+
if (warmOnly && newerResult.loaded) {
|
|
6481
|
+
dataEnd = normEnd;
|
|
6482
|
+
} else if (newerResult.data.length) {
|
|
6483
|
+
await runTimescaleOperation(
|
|
6484
|
+
() => (0, import_candles.upsertCandles)(
|
|
6485
|
+
(0, import_candles.toRows)(provider, symbol, intMinutes, newerResult.data)
|
|
6486
|
+
)
|
|
6487
|
+
);
|
|
6488
|
+
dataEnd = normEnd;
|
|
6489
|
+
}
|
|
6490
|
+
}
|
|
6491
|
+
const isRightEdgeQuery = defaultEnd === void 0 || defaultEnd && defaultEnd >= Date.now() - intervalMs;
|
|
6492
|
+
if (!cacheOnly && isRightEdgeQuery) {
|
|
6493
|
+
await refreshTail({ symbol, interval, silent });
|
|
6494
|
+
}
|
|
6495
|
+
if (warmOnly) {
|
|
6496
|
+
if (isTimescaleFallbackMode) {
|
|
6497
|
+
isTimescaleFallbackMode = false;
|
|
6498
|
+
import_logger12.logger.log("info", "TimescaleDB connection restored for kline cache");
|
|
6499
|
+
}
|
|
6500
|
+
return [];
|
|
6501
|
+
}
|
|
6502
|
+
const rangeStart = defaultStart ?? dataStart ?? 0;
|
|
6503
|
+
const rangeEnd = defaultEnd ?? dataEnd ?? Date.now();
|
|
6504
|
+
const { normStart: finalStart, normEnd: finalEnd } = normalizeRangeToClosed(intervalMs, rangeStart, rangeEnd);
|
|
6505
|
+
const dbData = await runTimescaleOperation(
|
|
6506
|
+
() => (0, import_candles.getCandlesRange)(provider, symbol, intMinutes, finalStart, finalEnd)
|
|
6507
|
+
);
|
|
6508
|
+
if (isTimescaleFallbackMode) {
|
|
6509
|
+
isTimescaleFallbackMode = false;
|
|
6510
|
+
import_logger12.logger.log("info", "TimescaleDB connection restored for kline cache");
|
|
6511
|
+
}
|
|
6512
|
+
return rowsToKline(dbData);
|
|
6513
|
+
} catch (error) {
|
|
6514
|
+
if (!isTimescaleFallbackMode) {
|
|
6515
|
+
isTimescaleFallbackMode = true;
|
|
6516
|
+
import_logger12.logger.log(
|
|
6517
|
+
"warn",
|
|
6518
|
+
"TimescaleDB unavailable for %s %s: %s. Falling back to exchange API.",
|
|
6519
|
+
symbol,
|
|
6520
|
+
interval,
|
|
6521
|
+
String(error)
|
|
6522
|
+
);
|
|
6523
|
+
}
|
|
6524
|
+
if (cacheOnly || warmOnly) {
|
|
6525
|
+
return [];
|
|
6526
|
+
}
|
|
6527
|
+
return request({
|
|
6528
|
+
symbol,
|
|
6529
|
+
interval,
|
|
6530
|
+
start: defaultStart,
|
|
6531
|
+
end: defaultEnd,
|
|
6532
|
+
silent
|
|
6533
|
+
});
|
|
6534
|
+
}
|
|
6535
|
+
};
|
|
6536
|
+
};
|
|
6537
|
+
|
|
6538
|
+
// src/connectorRuntime.ts
|
|
6539
|
+
var connectorRuntime = {
|
|
6540
|
+
logger: import_logger13.logger,
|
|
6541
|
+
resolveTradingAccount: import_tradingAccounts.resolveTradingAccount,
|
|
6542
|
+
createCachedKline: createTimescaleCachedKline
|
|
6543
|
+
};
|
|
6544
|
+
var bindConnectorRuntime = (creator, runtime = connectorRuntime) => {
|
|
6545
|
+
const boundCreator = (config) => creator(config, runtime);
|
|
6546
|
+
return boundCreator;
|
|
6547
|
+
};
|
|
6548
|
+
|
|
6549
|
+
// src/connectorsRegistry.ts
|
|
6245
6550
|
var createConnectorRegistryState = () => ({
|
|
6246
6551
|
connectorCreators: /* @__PURE__ */ new Map(),
|
|
6247
6552
|
providerToConnectorName: /* @__PURE__ */ new Map(),
|
|
@@ -6293,7 +6598,7 @@ var normalizeProviders = (providers, connectorName) => {
|
|
|
6293
6598
|
var registerProvider = (provider, connectorName, source, providerToConnectorName) => {
|
|
6294
6599
|
const existing = providerToConnectorName.get(provider);
|
|
6295
6600
|
if (existing && existing !== connectorName) {
|
|
6296
|
-
|
|
6601
|
+
import_logger14.logger.warn(
|
|
6297
6602
|
'Skip duplicate connector provider "%s" from %s: already mapped to %s',
|
|
6298
6603
|
provider,
|
|
6299
6604
|
source,
|
|
@@ -6306,11 +6611,11 @@ var registerProvider = (provider, connectorName, source, providerToConnectorName
|
|
|
6306
6611
|
var registerEntry = (entry, source, state) => {
|
|
6307
6612
|
const connectorName = String(entry?.name ?? "").trim();
|
|
6308
6613
|
if (!connectorName) {
|
|
6309
|
-
|
|
6614
|
+
import_logger14.logger.warn("Skip connector entry without name from %s", source);
|
|
6310
6615
|
return;
|
|
6311
6616
|
}
|
|
6312
6617
|
if (typeof entry.creator !== "function") {
|
|
6313
|
-
|
|
6618
|
+
import_logger14.logger.warn(
|
|
6314
6619
|
'Skip connector entry "%s" from %s: creator must be a function',
|
|
6315
6620
|
connectorName,
|
|
6316
6621
|
source
|
|
@@ -6322,7 +6627,7 @@ var registerEntry = (entry, source, state) => {
|
|
|
6322
6627
|
state.connectorCreators
|
|
6323
6628
|
);
|
|
6324
6629
|
if (existingByName) {
|
|
6325
|
-
|
|
6630
|
+
import_logger14.logger.warn(
|
|
6326
6631
|
'Skip duplicate connector "%s" from %s: already registered as %s',
|
|
6327
6632
|
connectorName,
|
|
6328
6633
|
source,
|
|
@@ -6330,7 +6635,10 @@ var registerEntry = (entry, source, state) => {
|
|
|
6330
6635
|
);
|
|
6331
6636
|
return;
|
|
6332
6637
|
}
|
|
6333
|
-
state.connectorCreators.set(
|
|
6638
|
+
state.connectorCreators.set(
|
|
6639
|
+
connectorName,
|
|
6640
|
+
bindConnectorRuntime(entry.creator)
|
|
6641
|
+
);
|
|
6334
6642
|
const providers = normalizeProviders(entry.providers, connectorName);
|
|
6335
6643
|
for (const provider of providers) {
|
|
6336
6644
|
registerProvider(
|
|
@@ -6394,7 +6702,7 @@ var ensureConnectorPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
|
|
|
6394
6702
|
);
|
|
6395
6703
|
const pluginDefinition = extractConnectorPluginDefinition(moduleExport);
|
|
6396
6704
|
if (!pluginDefinition) {
|
|
6397
|
-
|
|
6705
|
+
import_logger14.logger.warn(
|
|
6398
6706
|
'Skip connector plugin "%s": export { connectorEntries } is missing',
|
|
6399
6707
|
moduleName
|
|
6400
6708
|
);
|
|
@@ -6402,7 +6710,7 @@ var ensureConnectorPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
|
|
|
6402
6710
|
}
|
|
6403
6711
|
registerEntries2(pluginDefinition.connectorEntries, moduleName, state);
|
|
6404
6712
|
} catch (error) {
|
|
6405
|
-
|
|
6713
|
+
import_logger14.logger.warn(
|
|
6406
6714
|
'Failed to load connector plugin "%s": %s',
|
|
6407
6715
|
moduleName,
|
|
6408
6716
|
String(error)
|
|
@@ -6547,7 +6855,7 @@ var import_coreResearch = require("@tradejs/infra/coreResearch");
|
|
|
6547
6855
|
var import_ml3 = require("@tradejs/infra/ml");
|
|
6548
6856
|
|
|
6549
6857
|
// src/executionCosts.ts
|
|
6550
|
-
var
|
|
6858
|
+
var import_constants7 = require("@tradejs/core/constants");
|
|
6551
6859
|
var finiteOr = (value, fallback) => {
|
|
6552
6860
|
const parsed = Number(value);
|
|
6553
6861
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
@@ -6587,8 +6895,8 @@ var resolveExecutionCosts = async (params) => {
|
|
|
6587
6895
|
const cacheOnly = config.EXECUTION_COSTS_CACHE_ONLY === true;
|
|
6588
6896
|
const hasConfiguredFees = Number.isFinite(Number(config.MAKER_FEE_RATE)) && Number.isFinite(Number(config.TAKER_FEE_RATE));
|
|
6589
6897
|
const exchangeFees = !cacheOnly && !hasConfiguredFees && connector.getTradingFeeRate ? await loadTradingFee(connector, symbol).catch(() => null) : null;
|
|
6590
|
-
const makerRate = hasConfiguredFees ? Number(config.MAKER_FEE_RATE) : exchangeFees?.makerRate ??
|
|
6591
|
-
const takerRate = hasConfiguredFees ? Number(config.TAKER_FEE_RATE) : exchangeFees?.takerRate ??
|
|
6898
|
+
const makerRate = hasConfiguredFees ? Number(config.MAKER_FEE_RATE) : exchangeFees?.makerRate ?? import_constants7.FEE_PERCENT;
|
|
6899
|
+
const takerRate = hasConfiguredFees ? Number(config.TAKER_FEE_RATE) : exchangeFees?.takerRate ?? import_constants7.FEE_PERCENT;
|
|
6592
6900
|
const fundingEnabled = config.FUNDING_ENABLED !== false && !cacheOnly && typeof connector.getFundingRateHistory === "function";
|
|
6593
6901
|
const fundingRates = fundingEnabled ? await loadFundingRates(connector, symbol, startTime, endTime) : [];
|
|
6594
6902
|
const requestedLeverage = Math.max(1, finiteOr(config.LEVERAGE, 10));
|
|
@@ -6609,18 +6917,18 @@ var resolveExecutionCosts = async (params) => {
|
|
|
6609
6917
|
toTimestamp: fundingRates.at(-1)?.timestamp ?? null
|
|
6610
6918
|
},
|
|
6611
6919
|
slippage: {
|
|
6612
|
-
baseBps: finiteOr(config.SLIPPAGE_BASE_BPS,
|
|
6920
|
+
baseBps: finiteOr(config.SLIPPAGE_BASE_BPS, import_constants7.BACKTEST_BASE_SLIPPAGE_BPS),
|
|
6613
6921
|
spreadMultiplier: finiteOr(
|
|
6614
6922
|
config.SLIPPAGE_SPREAD_MULTIPLIER,
|
|
6615
|
-
|
|
6923
|
+
import_constants7.BACKTEST_SPREAD_SLIPPAGE_MULTIPLIER
|
|
6616
6924
|
),
|
|
6617
6925
|
marketImpactBps: finiteOr(
|
|
6618
6926
|
config.SLIPPAGE_MARKET_IMPACT_BPS,
|
|
6619
|
-
|
|
6927
|
+
import_constants7.BACKTEST_MARKET_IMPACT_BPS
|
|
6620
6928
|
),
|
|
6621
6929
|
delayRiskMultiplier: finiteOr(
|
|
6622
6930
|
config.SLIPPAGE_DELAY_RISK_MULTIPLIER,
|
|
6623
|
-
|
|
6931
|
+
import_constants7.BACKTEST_DELAY_RISK_MULTIPLIER
|
|
6624
6932
|
),
|
|
6625
6933
|
source: config.SLIPPAGE_BASE_BPS != null || config.SLIPPAGE_SPREAD_MULTIPLIER != null || config.SLIPPAGE_MARKET_IMPACT_BPS != null ? "config" : "fallback"
|
|
6626
6934
|
},
|
|
@@ -6638,7 +6946,7 @@ var resolveExecutionCosts = async (params) => {
|
|
|
6638
6946
|
|
|
6639
6947
|
// src/testConnector.ts
|
|
6640
6948
|
var import_node_crypto4 = require("crypto");
|
|
6641
|
-
var
|
|
6949
|
+
var import_constants8 = require("@tradejs/core/constants");
|
|
6642
6950
|
var import_backtest = require("@tradejs/core/backtest");
|
|
6643
6951
|
var import_trade2 = require("@tradejs/core/trade");
|
|
6644
6952
|
var import_math = require("@tradejs/core/math");
|
|
@@ -6665,14 +6973,14 @@ var createTestConnector = (connector, context) => {
|
|
|
6665
6973
|
const positionLog = [];
|
|
6666
6974
|
const fastMode = Boolean(context?.fastMode);
|
|
6667
6975
|
const executionCostModel = context?.executionCostModel;
|
|
6668
|
-
const makerFeeRate = executionCostModel?.fees.makerRate ??
|
|
6669
|
-
const takerFeeRate = executionCostModel?.fees.takerRate ??
|
|
6976
|
+
const makerFeeRate = executionCostModel?.fees.makerRate ?? import_constants8.FEE_PERCENT;
|
|
6977
|
+
const takerFeeRate = executionCostModel?.fees.takerRate ?? import_constants8.FEE_PERCENT;
|
|
6670
6978
|
const fundingRates = [...context?.fundingRates ?? []].sort(
|
|
6671
6979
|
(left, right) => left.timestamp - right.timestamp
|
|
6672
6980
|
);
|
|
6673
6981
|
const processedFundingTimestamps = /* @__PURE__ */ new Set();
|
|
6674
6982
|
let currentPosition = null;
|
|
6675
|
-
let amount =
|
|
6983
|
+
let amount = import_constants8.INITIAL_BACKTEST_AMOUNT;
|
|
6676
6984
|
let originalQty = 0;
|
|
6677
6985
|
let currentPositionProfit = 0;
|
|
6678
6986
|
let currentSignalId = null;
|
|
@@ -7115,7 +7423,7 @@ var createTestConnector = (connector, context) => {
|
|
|
7115
7423
|
profit: fullStat.netProfit
|
|
7116
7424
|
} : {
|
|
7117
7425
|
amount: (0, import_math.round)(amount),
|
|
7118
|
-
profit: (0, import_math.round)(amount -
|
|
7426
|
+
profit: (0, import_math.round)(amount - import_constants8.INITIAL_BACKTEST_AMOUNT),
|
|
7119
7427
|
orders: positionLog.length
|
|
7120
7428
|
},
|
|
7121
7429
|
orderLogId,
|
|
@@ -8143,7 +8451,7 @@ var BACKTEST_INTERVAL = "15";
|
|
|
8143
8451
|
var resolveBacktestExecutionInterval = (interval) => {
|
|
8144
8452
|
const normalized = String(interval);
|
|
8145
8453
|
if (normalized === "15") {
|
|
8146
|
-
return
|
|
8454
|
+
return import_constants9.BACKTEST_EXECUTION_INTERVAL;
|
|
8147
8455
|
}
|
|
8148
8456
|
if (normalized === "60") {
|
|
8149
8457
|
return "15";
|
|
@@ -8190,7 +8498,7 @@ var filterClosedAlignedCandles = (data, btcData, interval) => {
|
|
|
8190
8498
|
};
|
|
8191
8499
|
};
|
|
8192
8500
|
var shouldLoadBacktestExecutionCandles = (interval) => {
|
|
8193
|
-
if (!
|
|
8501
|
+
if (!import_constants9.BACKTEST_LOWER_TIMEFRAME_EXECUTION_ENABLED) {
|
|
8194
8502
|
return false;
|
|
8195
8503
|
}
|
|
8196
8504
|
const executionInterval = resolveBacktestExecutionInterval(interval);
|
|
@@ -8300,7 +8608,7 @@ var prepareTestingData = async (params) => {
|
|
|
8300
8608
|
throw new Error(`Unknown connector: ${connectorName}`);
|
|
8301
8609
|
}
|
|
8302
8610
|
if (!binanceConnector || !coinbaseConnector) {
|
|
8303
|
-
|
|
8611
|
+
import_logger15.logger.warn(
|
|
8304
8612
|
"Binance/Coinbase connectors are unavailable. Reusing %s for BTC references.",
|
|
8305
8613
|
connectorName
|
|
8306
8614
|
);
|
|
@@ -8583,7 +8891,7 @@ var testing = async (test) => {
|
|
|
8583
8891
|
if (!start) {
|
|
8584
8892
|
throw new Error("no start");
|
|
8585
8893
|
}
|
|
8586
|
-
const preloadStart = (0,
|
|
8894
|
+
const preloadStart = (0, import_time3.getBacktestPreloadStart)(start);
|
|
8587
8895
|
const progress = createBacktestProgress({
|
|
8588
8896
|
testName: name,
|
|
8589
8897
|
symbol,
|
|
@@ -8683,7 +8991,7 @@ var testingGroupInSharedCandleLoop = async (tests) => {
|
|
|
8683
8991
|
if (!start) {
|
|
8684
8992
|
throw new Error("no start");
|
|
8685
8993
|
}
|
|
8686
|
-
const preloadStart = (0,
|
|
8994
|
+
const preloadStart = (0, import_time3.getBacktestPreloadStart)(start);
|
|
8687
8995
|
const progress = createBacktestProgress({
|
|
8688
8996
|
testName: first.name,
|
|
8689
8997
|
symbol,
|