@tradejs/node 3.0.1 → 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.mjs
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BUILTIN_CONNECTOR_NAMES,
|
|
3
3
|
getConnectorCreatorByName
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-3TWULKHV.mjs";
|
|
5
5
|
import {
|
|
6
6
|
buildMlPayload,
|
|
7
7
|
enrichSignalWithMarketContextStages
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-MKLTJQLY.mjs";
|
|
9
9
|
import {
|
|
10
10
|
buildAiPayload,
|
|
11
11
|
getStrategyCreator
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-KOQSLFT2.mjs";
|
|
13
13
|
import {
|
|
14
14
|
getTradejsProjectCwd
|
|
15
15
|
} from "./chunk-WS5DYEVZ.mjs";
|
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getTradejsProjectCwd,
|
|
3
|
+
importTradejsModule,
|
|
4
|
+
loadTradejsConfig,
|
|
5
|
+
resolvePluginModuleSpecifier
|
|
6
|
+
} from "./chunk-WS5DYEVZ.mjs";
|
|
7
|
+
|
|
8
|
+
// src/connectorRuntime.ts
|
|
9
|
+
import { logger as logger2 } from "@tradejs/infra/logger";
|
|
10
|
+
import { resolveTradingAccount } from "@tradejs/infra/tradingAccounts";
|
|
11
|
+
|
|
12
|
+
// src/timescaleCachedKline.ts
|
|
13
|
+
import { PRELOAD_FALLBACK_DAYS } from "@tradejs/core/constants";
|
|
14
|
+
import { mergeData } from "@tradejs/core/data";
|
|
15
|
+
import { getItemTimestamp, getTimestamp } from "@tradejs/core/time";
|
|
16
|
+
import { delay } from "@tradejs/core/async";
|
|
17
|
+
import { logger } from "@tradejs/infra/logger";
|
|
18
|
+
import {
|
|
19
|
+
getCandlesRange,
|
|
20
|
+
getDataEdges,
|
|
21
|
+
toRows,
|
|
22
|
+
upsertCandles
|
|
23
|
+
} from "@tradejs/infra/timescale/candles";
|
|
24
|
+
var DEFAULT_LIMIT = 1e3;
|
|
25
|
+
var DEFAULT_CACHE_FALLBACK_WINDOW = 1e3;
|
|
26
|
+
var DEFAULT_TIMESCALE_RETRIES = 2;
|
|
27
|
+
var DEFAULT_TIMESCALE_RETRY_DELAY_MS = 1e3;
|
|
28
|
+
var resolveNonNegativeInt = (value, fallback) => {
|
|
29
|
+
const parsed = Number(value);
|
|
30
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
31
|
+
return fallback;
|
|
32
|
+
}
|
|
33
|
+
return Math.floor(parsed);
|
|
34
|
+
};
|
|
35
|
+
var getTimescaleRetryCount = () => resolveNonNegativeInt(
|
|
36
|
+
process.env.TIMESCALE_KLINE_RETRIES,
|
|
37
|
+
DEFAULT_TIMESCALE_RETRIES
|
|
38
|
+
);
|
|
39
|
+
var getTimescaleRetryDelayMs = () => resolveNonNegativeInt(
|
|
40
|
+
process.env.TIMESCALE_KLINE_RETRY_DELAY_MS,
|
|
41
|
+
DEFAULT_TIMESCALE_RETRY_DELAY_MS
|
|
42
|
+
);
|
|
43
|
+
var intervalMsOf = (interval) => interval * 6e4;
|
|
44
|
+
var clampToClosedCandle = (value, intervalMs) => Math.floor(value / intervalMs) * intervalMs;
|
|
45
|
+
var normalizeRangeToClosed = (intervalMs, start, end) => {
|
|
46
|
+
const lastClosed = Math.floor(Date.now() / intervalMs) * intervalMs;
|
|
47
|
+
const normStart = start !== void 0 ? clampToClosedCandle(start, intervalMs) : 0;
|
|
48
|
+
const cappedEnd = Math.min(end ?? Date.now(), lastClosed);
|
|
49
|
+
const normEnd = clampToClosedCandle(cappedEnd, intervalMs);
|
|
50
|
+
return { normStart, normEnd };
|
|
51
|
+
};
|
|
52
|
+
var rowsToKline = (rows) => rows.map(({ ts, ...data }) => ({
|
|
53
|
+
timestamp: new Date(ts).getTime(),
|
|
54
|
+
...data
|
|
55
|
+
}));
|
|
56
|
+
var createTimescaleCachedKline = ({
|
|
57
|
+
provider,
|
|
58
|
+
request,
|
|
59
|
+
intervalToMinutes,
|
|
60
|
+
limit = DEFAULT_LIMIT,
|
|
61
|
+
cacheFallbackWindow = DEFAULT_CACHE_FALLBACK_WINDOW
|
|
62
|
+
}) => {
|
|
63
|
+
let isTimescaleFallbackMode = false;
|
|
64
|
+
const runTimescaleOperation = async (operation) => {
|
|
65
|
+
const retries = getTimescaleRetryCount();
|
|
66
|
+
const retryDelayMs = getTimescaleRetryDelayMs();
|
|
67
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
68
|
+
try {
|
|
69
|
+
return await operation();
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (attempt >= retries) {
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
const waitMs = retryDelayMs * 2 ** attempt;
|
|
75
|
+
if (waitMs > 0) {
|
|
76
|
+
await delay(waitMs);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const loadData = async (direction, pointer, limitBoundary, requestParams, intervalMs, options = {}) => {
|
|
82
|
+
if (pointer === void 0) return { data: [], loaded: false };
|
|
83
|
+
let accumulated = [];
|
|
84
|
+
let fulfilled = false;
|
|
85
|
+
let loaded = false;
|
|
86
|
+
const shouldAccumulate = options.accumulate ?? true;
|
|
87
|
+
while (!fulfilled) {
|
|
88
|
+
const currentPointer = pointer;
|
|
89
|
+
const params = {
|
|
90
|
+
symbol: requestParams.symbol,
|
|
91
|
+
interval: requestParams.interval,
|
|
92
|
+
silent: requestParams.silent,
|
|
93
|
+
end: requestParams.end
|
|
94
|
+
};
|
|
95
|
+
if (direction === "older") {
|
|
96
|
+
params.end = pointer;
|
|
97
|
+
if (limitBoundary !== void 0) params.start = limitBoundary;
|
|
98
|
+
} else {
|
|
99
|
+
params.start = pointer;
|
|
100
|
+
if (limitBoundary !== void 0) params.end = limitBoundary;
|
|
101
|
+
}
|
|
102
|
+
const partData = await request(params);
|
|
103
|
+
if (!partData.length) {
|
|
104
|
+
fulfilled = true;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
loaded = true;
|
|
108
|
+
if (options.onPartData) {
|
|
109
|
+
await options.onPartData(partData);
|
|
110
|
+
}
|
|
111
|
+
if (shouldAccumulate) {
|
|
112
|
+
accumulated = direction === "older" ? mergeData(partData, accumulated) : mergeData(accumulated, partData);
|
|
113
|
+
}
|
|
114
|
+
const boundaryReached = limitBoundary !== void 0 && (direction === "older" && currentPointer <= limitBoundary || direction === "newer" && currentPointer >= limitBoundary);
|
|
115
|
+
if (partData.length < limit || boundaryReached) {
|
|
116
|
+
fulfilled = true;
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
const nextPointer = direction === "older" ? getItemTimestamp(partData[0]) - intervalMs : getItemTimestamp(partData[partData.length - 1]) + intervalMs;
|
|
120
|
+
if (!Number.isFinite(nextPointer) || nextPointer === currentPointer) {
|
|
121
|
+
fulfilled = true;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
pointer = nextPointer;
|
|
125
|
+
}
|
|
126
|
+
return { data: accumulated, loaded };
|
|
127
|
+
};
|
|
128
|
+
const refreshTail = async ({
|
|
129
|
+
symbol,
|
|
130
|
+
interval,
|
|
131
|
+
silent,
|
|
132
|
+
tailCount = 2
|
|
133
|
+
}) => {
|
|
134
|
+
const intMinutes = intervalToMinutes(interval);
|
|
135
|
+
if (!intMinutes) {
|
|
136
|
+
logger.log("error", "refreshTail: invalid interval %s", interval);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const intervalMs = intervalMsOf(intMinutes);
|
|
140
|
+
const lastClosed = Math.floor(Date.now() / intervalMs) * intervalMs;
|
|
141
|
+
const tailEnd = lastClosed + intervalMs;
|
|
142
|
+
const tailStart = tailEnd - tailCount * intervalMs;
|
|
143
|
+
const part = await request({
|
|
144
|
+
symbol,
|
|
145
|
+
interval,
|
|
146
|
+
start: tailStart,
|
|
147
|
+
end: tailEnd,
|
|
148
|
+
silent
|
|
149
|
+
});
|
|
150
|
+
if (part.length) {
|
|
151
|
+
await runTimescaleOperation(
|
|
152
|
+
() => upsertCandles(toRows(provider, symbol, intMinutes, part))
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
return async ({
|
|
157
|
+
symbol,
|
|
158
|
+
interval,
|
|
159
|
+
start: defaultStart,
|
|
160
|
+
end: defaultEnd,
|
|
161
|
+
silent = false,
|
|
162
|
+
cacheOnly = false,
|
|
163
|
+
warmOnly = false
|
|
164
|
+
}) => {
|
|
165
|
+
const intMinutes = intervalToMinutes(interval);
|
|
166
|
+
if (!intMinutes) {
|
|
167
|
+
logger.log("error", "kline: invalid interval %s", interval);
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
if (defaultStart !== void 0 && defaultEnd !== void 0 && defaultEnd <= defaultStart) {
|
|
171
|
+
return [];
|
|
172
|
+
}
|
|
173
|
+
const intervalMs = intervalMsOf(intMinutes);
|
|
174
|
+
try {
|
|
175
|
+
const edges = await runTimescaleOperation(
|
|
176
|
+
() => getDataEdges(provider, symbol, intMinutes)
|
|
177
|
+
);
|
|
178
|
+
let dataStart = edges.min;
|
|
179
|
+
let dataEnd = edges.max;
|
|
180
|
+
const { normStart, normEnd } = normalizeRangeToClosed(
|
|
181
|
+
intervalMs,
|
|
182
|
+
defaultStart,
|
|
183
|
+
defaultEnd
|
|
184
|
+
);
|
|
185
|
+
if (cacheOnly) {
|
|
186
|
+
const base = edges.max ?? Date.now();
|
|
187
|
+
const s = Math.max(
|
|
188
|
+
defaultStart ?? base - cacheFallbackWindow * intervalMs,
|
|
189
|
+
0
|
|
190
|
+
);
|
|
191
|
+
const e = defaultEnd ?? base;
|
|
192
|
+
const dbData2 = await runTimescaleOperation(
|
|
193
|
+
() => getCandlesRange(provider, symbol, intMinutes, s, e)
|
|
194
|
+
);
|
|
195
|
+
return rowsToKline(dbData2);
|
|
196
|
+
}
|
|
197
|
+
const needOlderData = defaultStart !== void 0 && (dataStart === void 0 || normStart < dataStart);
|
|
198
|
+
const persistPartData = warmOnly ? (partData) => runTimescaleOperation(
|
|
199
|
+
() => upsertCandles(toRows(provider, symbol, intMinutes, partData))
|
|
200
|
+
) : void 0;
|
|
201
|
+
if (needOlderData) {
|
|
202
|
+
const pointerForOlder = dataStart ?? normEnd ?? Date.now();
|
|
203
|
+
const olderResult = await loadData(
|
|
204
|
+
"older",
|
|
205
|
+
pointerForOlder,
|
|
206
|
+
normStart,
|
|
207
|
+
{
|
|
208
|
+
symbol,
|
|
209
|
+
interval,
|
|
210
|
+
silent,
|
|
211
|
+
start: normStart,
|
|
212
|
+
end: pointerForOlder
|
|
213
|
+
},
|
|
214
|
+
intervalMs,
|
|
215
|
+
{
|
|
216
|
+
accumulate: !warmOnly,
|
|
217
|
+
onPartData: persistPartData
|
|
218
|
+
}
|
|
219
|
+
);
|
|
220
|
+
if (warmOnly && olderResult.loaded) {
|
|
221
|
+
dataStart = normStart;
|
|
222
|
+
if (dataEnd === void 0) {
|
|
223
|
+
dataEnd = normEnd;
|
|
224
|
+
}
|
|
225
|
+
} else if (olderResult.data.length) {
|
|
226
|
+
await runTimescaleOperation(
|
|
227
|
+
() => upsertCandles(
|
|
228
|
+
toRows(provider, symbol, intMinutes, olderResult.data)
|
|
229
|
+
)
|
|
230
|
+
);
|
|
231
|
+
dataStart = normStart;
|
|
232
|
+
if (dataEnd === void 0) {
|
|
233
|
+
dataEnd = normEnd;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const needNewerData = defaultEnd !== void 0 && (dataEnd === void 0 || normEnd > dataEnd);
|
|
238
|
+
if (needNewerData) {
|
|
239
|
+
const fallbackStart = getTimestamp(PRELOAD_FALLBACK_DAYS);
|
|
240
|
+
const pointerForNewer = dataEnd ?? (defaultStart !== void 0 ? normStart : fallbackStart) ?? 0;
|
|
241
|
+
const newerResult = await loadData(
|
|
242
|
+
"newer",
|
|
243
|
+
pointerForNewer,
|
|
244
|
+
normEnd,
|
|
245
|
+
{
|
|
246
|
+
symbol,
|
|
247
|
+
interval,
|
|
248
|
+
silent,
|
|
249
|
+
start: pointerForNewer,
|
|
250
|
+
end: normEnd
|
|
251
|
+
},
|
|
252
|
+
intervalMs,
|
|
253
|
+
{
|
|
254
|
+
accumulate: !warmOnly,
|
|
255
|
+
onPartData: persistPartData
|
|
256
|
+
}
|
|
257
|
+
);
|
|
258
|
+
if (warmOnly && newerResult.loaded) {
|
|
259
|
+
dataEnd = normEnd;
|
|
260
|
+
} else if (newerResult.data.length) {
|
|
261
|
+
await runTimescaleOperation(
|
|
262
|
+
() => upsertCandles(
|
|
263
|
+
toRows(provider, symbol, intMinutes, newerResult.data)
|
|
264
|
+
)
|
|
265
|
+
);
|
|
266
|
+
dataEnd = normEnd;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const isRightEdgeQuery = defaultEnd === void 0 || defaultEnd && defaultEnd >= Date.now() - intervalMs;
|
|
270
|
+
if (!cacheOnly && isRightEdgeQuery) {
|
|
271
|
+
await refreshTail({ symbol, interval, silent });
|
|
272
|
+
}
|
|
273
|
+
if (warmOnly) {
|
|
274
|
+
if (isTimescaleFallbackMode) {
|
|
275
|
+
isTimescaleFallbackMode = false;
|
|
276
|
+
logger.log("info", "TimescaleDB connection restored for kline cache");
|
|
277
|
+
}
|
|
278
|
+
return [];
|
|
279
|
+
}
|
|
280
|
+
const rangeStart = defaultStart ?? dataStart ?? 0;
|
|
281
|
+
const rangeEnd = defaultEnd ?? dataEnd ?? Date.now();
|
|
282
|
+
const { normStart: finalStart, normEnd: finalEnd } = normalizeRangeToClosed(intervalMs, rangeStart, rangeEnd);
|
|
283
|
+
const dbData = await runTimescaleOperation(
|
|
284
|
+
() => getCandlesRange(provider, symbol, intMinutes, finalStart, finalEnd)
|
|
285
|
+
);
|
|
286
|
+
if (isTimescaleFallbackMode) {
|
|
287
|
+
isTimescaleFallbackMode = false;
|
|
288
|
+
logger.log("info", "TimescaleDB connection restored for kline cache");
|
|
289
|
+
}
|
|
290
|
+
return rowsToKline(dbData);
|
|
291
|
+
} catch (error) {
|
|
292
|
+
if (!isTimescaleFallbackMode) {
|
|
293
|
+
isTimescaleFallbackMode = true;
|
|
294
|
+
logger.log(
|
|
295
|
+
"warn",
|
|
296
|
+
"TimescaleDB unavailable for %s %s: %s. Falling back to exchange API.",
|
|
297
|
+
symbol,
|
|
298
|
+
interval,
|
|
299
|
+
String(error)
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
if (cacheOnly || warmOnly) {
|
|
303
|
+
return [];
|
|
304
|
+
}
|
|
305
|
+
return request({
|
|
306
|
+
symbol,
|
|
307
|
+
interval,
|
|
308
|
+
start: defaultStart,
|
|
309
|
+
end: defaultEnd,
|
|
310
|
+
silent
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
// src/connectorRuntime.ts
|
|
317
|
+
var connectorRuntime = {
|
|
318
|
+
logger: logger2,
|
|
319
|
+
resolveTradingAccount,
|
|
320
|
+
createCachedKline: createTimescaleCachedKline
|
|
321
|
+
};
|
|
322
|
+
var bindConnectorRuntime = (creator, runtime = connectorRuntime) => {
|
|
323
|
+
const boundCreator = (config) => creator(config, runtime);
|
|
324
|
+
return boundCreator;
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// src/connectorsRegistry.ts
|
|
328
|
+
import { logger as logger3 } from "@tradejs/infra/logger";
|
|
329
|
+
var createConnectorRegistryState = () => ({
|
|
330
|
+
connectorCreators: /* @__PURE__ */ new Map(),
|
|
331
|
+
providerToConnectorName: /* @__PURE__ */ new Map(),
|
|
332
|
+
pluginsLoadPromise: null
|
|
333
|
+
});
|
|
334
|
+
var registryStateByProjectRoot = /* @__PURE__ */ new Map();
|
|
335
|
+
var getConnectorRegistryState = (cwd = getTradejsProjectCwd()) => {
|
|
336
|
+
const projectRoot = getTradejsProjectCwd(cwd);
|
|
337
|
+
let state = registryStateByProjectRoot.get(projectRoot);
|
|
338
|
+
if (!state) {
|
|
339
|
+
state = createConnectorRegistryState();
|
|
340
|
+
registryStateByProjectRoot.set(projectRoot, state);
|
|
341
|
+
}
|
|
342
|
+
return {
|
|
343
|
+
projectRoot,
|
|
344
|
+
state
|
|
345
|
+
};
|
|
346
|
+
};
|
|
347
|
+
var BUILTIN_CONNECTOR_NAMES = {
|
|
348
|
+
ByBit: "ByBit",
|
|
349
|
+
Binance: "Binance",
|
|
350
|
+
Coinbase: "Coinbase",
|
|
351
|
+
Test: "Test"
|
|
352
|
+
};
|
|
353
|
+
var normalizeProvider = (value) => String(value ?? "").trim().toLowerCase();
|
|
354
|
+
var toUniqueModules = (modules = []) => [
|
|
355
|
+
...new Set(modules.map((moduleName) => moduleName.trim()).filter(Boolean))
|
|
356
|
+
];
|
|
357
|
+
var findConnectorNameInsensitive = (name, connectorCreators) => {
|
|
358
|
+
const normalized = name.trim().toLowerCase();
|
|
359
|
+
if (!normalized) {
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
for (const existingName of connectorCreators.keys()) {
|
|
363
|
+
if (existingName.toLowerCase() === normalized) {
|
|
364
|
+
return existingName;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return null;
|
|
368
|
+
};
|
|
369
|
+
var normalizeProviders = (providers, connectorName) => {
|
|
370
|
+
const list = Array.isArray(providers) ? providers.map((item) => normalizeProvider(item)).filter(Boolean) : [];
|
|
371
|
+
const deduped = [...new Set(list)];
|
|
372
|
+
if (deduped.length) {
|
|
373
|
+
return deduped;
|
|
374
|
+
}
|
|
375
|
+
return [normalizeProvider(connectorName)];
|
|
376
|
+
};
|
|
377
|
+
var registerProvider = (provider, connectorName, source, providerToConnectorName) => {
|
|
378
|
+
const existing = providerToConnectorName.get(provider);
|
|
379
|
+
if (existing && existing !== connectorName) {
|
|
380
|
+
logger3.warn(
|
|
381
|
+
'Skip duplicate connector provider "%s" from %s: already mapped to %s',
|
|
382
|
+
provider,
|
|
383
|
+
source,
|
|
384
|
+
existing
|
|
385
|
+
);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
providerToConnectorName.set(provider, connectorName);
|
|
389
|
+
};
|
|
390
|
+
var registerEntry = (entry, source, state) => {
|
|
391
|
+
const connectorName = String(entry?.name ?? "").trim();
|
|
392
|
+
if (!connectorName) {
|
|
393
|
+
logger3.warn("Skip connector entry without name from %s", source);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
if (typeof entry.creator !== "function") {
|
|
397
|
+
logger3.warn(
|
|
398
|
+
'Skip connector entry "%s" from %s: creator must be a function',
|
|
399
|
+
connectorName,
|
|
400
|
+
source
|
|
401
|
+
);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const existingByName = findConnectorNameInsensitive(
|
|
405
|
+
connectorName,
|
|
406
|
+
state.connectorCreators
|
|
407
|
+
);
|
|
408
|
+
if (existingByName) {
|
|
409
|
+
logger3.warn(
|
|
410
|
+
'Skip duplicate connector "%s" from %s: already registered as %s',
|
|
411
|
+
connectorName,
|
|
412
|
+
source,
|
|
413
|
+
existingByName
|
|
414
|
+
);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
state.connectorCreators.set(
|
|
418
|
+
connectorName,
|
|
419
|
+
bindConnectorRuntime(entry.creator)
|
|
420
|
+
);
|
|
421
|
+
const providers = normalizeProviders(entry.providers, connectorName);
|
|
422
|
+
for (const provider of providers) {
|
|
423
|
+
registerProvider(
|
|
424
|
+
provider,
|
|
425
|
+
connectorName,
|
|
426
|
+
source,
|
|
427
|
+
state.providerToConnectorName
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
var registerEntries = (entries, source, state) => {
|
|
432
|
+
for (const entry of entries) {
|
|
433
|
+
registerEntry(entry, source, state);
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
var extractConnectorPluginDefinition = (moduleExport) => {
|
|
437
|
+
if (!moduleExport || typeof moduleExport !== "object") {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
const candidate = moduleExport;
|
|
441
|
+
if (Array.isArray(candidate.connectorEntries)) {
|
|
442
|
+
return {
|
|
443
|
+
connectorEntries: candidate.connectorEntries
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
const defaultExport = candidate.default;
|
|
447
|
+
if (defaultExport && Array.isArray(defaultExport.connectorEntries)) {
|
|
448
|
+
return {
|
|
449
|
+
connectorEntries: defaultExport.connectorEntries
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
return null;
|
|
453
|
+
};
|
|
454
|
+
var importConnectorPluginModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
455
|
+
if (typeof importTradejsModule === "function") {
|
|
456
|
+
return importTradejsModule(moduleName, cwd);
|
|
457
|
+
}
|
|
458
|
+
return import(
|
|
459
|
+
/* webpackIgnore: true */
|
|
460
|
+
moduleName
|
|
461
|
+
);
|
|
462
|
+
};
|
|
463
|
+
var ensureConnectorPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
|
|
464
|
+
const { projectRoot, state } = getConnectorRegistryState(cwd);
|
|
465
|
+
if (!state.pluginsLoadPromise) {
|
|
466
|
+
state.pluginsLoadPromise = (async () => {
|
|
467
|
+
const config = await loadTradejsConfig(projectRoot);
|
|
468
|
+
const connectorModules = toUniqueModules(config.connectors);
|
|
469
|
+
if (!connectorModules.length) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
for (const moduleName of connectorModules) {
|
|
473
|
+
try {
|
|
474
|
+
const resolvedModuleName = resolvePluginModuleSpecifier(
|
|
475
|
+
moduleName,
|
|
476
|
+
projectRoot
|
|
477
|
+
);
|
|
478
|
+
const moduleExport = await importConnectorPluginModule(
|
|
479
|
+
resolvedModuleName,
|
|
480
|
+
projectRoot
|
|
481
|
+
);
|
|
482
|
+
const pluginDefinition = extractConnectorPluginDefinition(moduleExport);
|
|
483
|
+
if (!pluginDefinition) {
|
|
484
|
+
logger3.warn(
|
|
485
|
+
'Skip connector plugin "%s": export { connectorEntries } is missing',
|
|
486
|
+
moduleName
|
|
487
|
+
);
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
registerEntries(pluginDefinition.connectorEntries, moduleName, state);
|
|
491
|
+
} catch (error) {
|
|
492
|
+
logger3.warn(
|
|
493
|
+
'Failed to load connector plugin "%s": %s',
|
|
494
|
+
moduleName,
|
|
495
|
+
String(error)
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
})();
|
|
500
|
+
}
|
|
501
|
+
await state.pluginsLoadPromise;
|
|
502
|
+
};
|
|
503
|
+
var getConnectorCreatorByName = async (connectorName, cwd = getTradejsProjectCwd()) => {
|
|
504
|
+
await ensureConnectorPluginsLoaded(cwd);
|
|
505
|
+
const { state } = getConnectorRegistryState(cwd);
|
|
506
|
+
const raw = String(connectorName ?? "").trim();
|
|
507
|
+
if (!raw) {
|
|
508
|
+
return void 0;
|
|
509
|
+
}
|
|
510
|
+
const direct = state.connectorCreators.get(raw);
|
|
511
|
+
if (direct) {
|
|
512
|
+
return direct;
|
|
513
|
+
}
|
|
514
|
+
const existing = findConnectorNameInsensitive(raw, state.connectorCreators);
|
|
515
|
+
if (!existing) {
|
|
516
|
+
return void 0;
|
|
517
|
+
}
|
|
518
|
+
return state.connectorCreators.get(existing);
|
|
519
|
+
};
|
|
520
|
+
var getConnectorNameByProvider = async (provider, cwd = getTradejsProjectCwd()) => {
|
|
521
|
+
await ensureConnectorPluginsLoaded(cwd);
|
|
522
|
+
const { state } = getConnectorRegistryState(cwd);
|
|
523
|
+
const normalized = normalizeProvider(provider);
|
|
524
|
+
if (!normalized) {
|
|
525
|
+
return void 0;
|
|
526
|
+
}
|
|
527
|
+
return state.providerToConnectorName.get(normalized);
|
|
528
|
+
};
|
|
529
|
+
var getConnectorCreatorByProvider = async (provider, cwd = getTradejsProjectCwd()) => {
|
|
530
|
+
await ensureConnectorPluginsLoaded(cwd);
|
|
531
|
+
const { state } = getConnectorRegistryState(cwd);
|
|
532
|
+
const normalized = normalizeProvider(provider);
|
|
533
|
+
if (!normalized) {
|
|
534
|
+
return void 0;
|
|
535
|
+
}
|
|
536
|
+
const connectorName = state.providerToConnectorName.get(normalized);
|
|
537
|
+
if (!connectorName) {
|
|
538
|
+
return void 0;
|
|
539
|
+
}
|
|
540
|
+
return state.connectorCreators.get(connectorName);
|
|
541
|
+
};
|
|
542
|
+
var resolveConnectorName = async (providerOrName, cwd = getTradejsProjectCwd()) => {
|
|
543
|
+
await ensureConnectorPluginsLoaded(cwd);
|
|
544
|
+
const { state } = getConnectorRegistryState(cwd);
|
|
545
|
+
const raw = String(providerOrName ?? "").trim();
|
|
546
|
+
if (!raw) {
|
|
547
|
+
return void 0;
|
|
548
|
+
}
|
|
549
|
+
const byProvider = state.providerToConnectorName.get(normalizeProvider(raw));
|
|
550
|
+
if (byProvider) {
|
|
551
|
+
return byProvider;
|
|
552
|
+
}
|
|
553
|
+
return state.connectorCreators.get(raw) && raw ? raw : findConnectorNameInsensitive(raw, state.connectorCreators) ?? void 0;
|
|
554
|
+
};
|
|
555
|
+
var getAvailableConnectorNames = async (cwd = getTradejsProjectCwd()) => {
|
|
556
|
+
await ensureConnectorPluginsLoaded(cwd);
|
|
557
|
+
const { state } = getConnectorRegistryState(cwd);
|
|
558
|
+
return [...state.connectorCreators.keys()].sort((a, b) => a.localeCompare(b));
|
|
559
|
+
};
|
|
560
|
+
var getAvailableConnectorProviders = async (cwd = getTradejsProjectCwd()) => {
|
|
561
|
+
await ensureConnectorPluginsLoaded(cwd);
|
|
562
|
+
const { state } = getConnectorRegistryState(cwd);
|
|
563
|
+
return [...state.providerToConnectorName.keys()].sort(
|
|
564
|
+
(a, b) => a.localeCompare(b)
|
|
565
|
+
);
|
|
566
|
+
};
|
|
567
|
+
var registerConnectorEntries = (entries, cwd = getTradejsProjectCwd()) => {
|
|
568
|
+
const { state } = getConnectorRegistryState(cwd);
|
|
569
|
+
registerEntries(entries, "runtime", state);
|
|
570
|
+
};
|
|
571
|
+
var resetConnectorRegistryCache = (cwd) => {
|
|
572
|
+
const normalizedCwd = String(cwd ?? "").trim();
|
|
573
|
+
if (!normalizedCwd) {
|
|
574
|
+
registryStateByProjectRoot.clear();
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
registryStateByProjectRoot.delete(getTradejsProjectCwd(normalizedCwd));
|
|
578
|
+
};
|
|
579
|
+
var DEFAULT_CONNECTOR_NAME = BUILTIN_CONNECTOR_NAMES.ByBit;
|
|
580
|
+
|
|
581
|
+
export {
|
|
582
|
+
connectorRuntime,
|
|
583
|
+
bindConnectorRuntime,
|
|
584
|
+
BUILTIN_CONNECTOR_NAMES,
|
|
585
|
+
ensureConnectorPluginsLoaded,
|
|
586
|
+
getConnectorCreatorByName,
|
|
587
|
+
getConnectorNameByProvider,
|
|
588
|
+
getConnectorCreatorByProvider,
|
|
589
|
+
resolveConnectorName,
|
|
590
|
+
getAvailableConnectorNames,
|
|
591
|
+
getAvailableConnectorProviders,
|
|
592
|
+
registerConnectorEntries,
|
|
593
|
+
resetConnectorRegistryCache,
|
|
594
|
+
DEFAULT_CONNECTOR_NAME
|
|
595
|
+
};
|