@tradejs/node 1.0.6 → 1.0.9
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/dist/ai.js +274 -126
- package/dist/ai.mjs +3 -3
- package/dist/backtest.js +345 -42
- package/dist/backtest.mjs +98 -16
- package/dist/chunk-2JKX3DM7.mjs +619 -0
- package/dist/{chunk-XW5L327F.mjs → chunk-JMDYEKIO.mjs} +1 -1
- package/dist/chunk-JRRG3YQG.mjs +154 -0
- package/dist/{chunk-CGJ2UU6H.mjs → chunk-JU77QVJ3.mjs} +8 -1
- package/dist/{chunk-SCMBUEGK.mjs → chunk-KMJQQ53K.mjs} +1 -1
- package/dist/{chunk-GKDBAF3A.mjs → chunk-KZDHZ56N.mjs} +25 -7
- package/dist/{chunk-EOZSJKUM.mjs → chunk-WGOYR6AB.mjs} +1 -1
- package/dist/cli.d.mts +20 -2
- package/dist/cli.d.ts +20 -2
- package/dist/cli.js +456 -183
- package/dist/cli.mjs +155 -49
- package/dist/connectors.js +6 -1
- package/dist/connectors.mjs +2 -2
- package/dist/constants.js +1 -1
- package/dist/constants.mjs +1 -1
- package/dist/registry.js +6 -1
- package/dist/registry.mjs +2 -2
- package/dist/strategies.d.mts +24 -9
- package/dist/strategies.d.ts +24 -9
- package/dist/strategies.js +7465 -6467
- package/dist/strategies.mjs +865 -140
- package/package.json +6 -6
- package/dist/chunk-72FKXJ2I.mjs +0 -473
- package/dist/chunk-PQH5PAFC.mjs +0 -49
package/dist/ai.mjs
CHANGED
|
@@ -13,9 +13,9 @@ import {
|
|
|
13
13
|
runAiPrompt,
|
|
14
14
|
runAiPromptLocal,
|
|
15
15
|
trimSeriesDeep
|
|
16
|
-
} from "./chunk-
|
|
17
|
-
import "./chunk-
|
|
18
|
-
import "./chunk-
|
|
16
|
+
} from "./chunk-2JKX3DM7.mjs";
|
|
17
|
+
import "./chunk-WGOYR6AB.mjs";
|
|
18
|
+
import "./chunk-JU77QVJ3.mjs";
|
|
19
19
|
import "./chunk-6DZX6EAA.mjs";
|
|
20
20
|
export {
|
|
21
21
|
DEFAULT_AI_MODEL,
|
package/dist/backtest.js
CHANGED
|
@@ -39,14 +39,14 @@ module.exports = __toCommonJS(backtest_exports);
|
|
|
39
39
|
__reExport(backtest_exports, require("@tradejs/core/backtest"), module.exports);
|
|
40
40
|
|
|
41
41
|
// src/testing.ts
|
|
42
|
-
var
|
|
43
|
-
var import_constants = require("@tradejs/core/constants");
|
|
42
|
+
var import_indicators3 = require("@tradejs/core/indicators");
|
|
44
43
|
var import_time = require("@tradejs/core/time");
|
|
45
44
|
var import_ai2 = require("@tradejs/infra/ai");
|
|
46
45
|
var import_ml2 = require("@tradejs/infra/ml");
|
|
47
|
-
var
|
|
46
|
+
var import_logger5 = require("@tradejs/infra/logger");
|
|
48
47
|
|
|
49
48
|
// src/ai.ts
|
|
49
|
+
var import_aiLanguages = require("@tradejs/infra/aiLanguages");
|
|
50
50
|
var import_redis = require("@tradejs/infra/redis");
|
|
51
51
|
var import_userSettings = require("@tradejs/infra/userSettings");
|
|
52
52
|
|
|
@@ -74,6 +74,109 @@ var trimSeriesDeep = (value) => {
|
|
|
74
74
|
return value;
|
|
75
75
|
};
|
|
76
76
|
|
|
77
|
+
// src/aiMarketContext.ts
|
|
78
|
+
var SESSION_WINDOWS = [
|
|
79
|
+
{ name: "asia", startMinuteUtc: 0, endMinuteUtc: 8 * 60 },
|
|
80
|
+
{ name: "europe", startMinuteUtc: 7 * 60, endMinuteUtc: 16 * 60 },
|
|
81
|
+
{ name: "us", startMinuteUtc: 13 * 60, endMinuteUtc: 22 * 60 }
|
|
82
|
+
];
|
|
83
|
+
var toRecord = (value) => {
|
|
84
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
return value;
|
|
88
|
+
};
|
|
89
|
+
var toFiniteNumber = (value) => {
|
|
90
|
+
const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
|
|
91
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
92
|
+
};
|
|
93
|
+
var getLastFiniteNumber = (value) => {
|
|
94
|
+
const numeric = toFiniteNumber(value);
|
|
95
|
+
if (numeric != null) {
|
|
96
|
+
return numeric;
|
|
97
|
+
}
|
|
98
|
+
if (!Array.isArray(value)) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
for (let i = value.length - 1; i >= 0; i -= 1) {
|
|
102
|
+
const nested = getLastFiniteNumber(value[i]);
|
|
103
|
+
if (nested != null) {
|
|
104
|
+
return nested;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
};
|
|
109
|
+
var roundTo = (value, decimals) => {
|
|
110
|
+
const factor = 10 ** decimals;
|
|
111
|
+
return Math.round(value * factor) / factor;
|
|
112
|
+
};
|
|
113
|
+
var isInsideSession = (minuteUtc, startMinuteUtc, endMinuteUtc) => startMinuteUtc <= endMinuteUtc ? minuteUtc >= startMinuteUtc && minuteUtc < endMinuteUtc : minuteUtc >= startMinuteUtc || minuteUtc < endMinuteUtc;
|
|
114
|
+
var buildTradingSessionContext = (timestamp) => {
|
|
115
|
+
const date = new Date(timestamp);
|
|
116
|
+
const utcHour = date.getUTCHours();
|
|
117
|
+
const utcMinute = date.getUTCMinutes();
|
|
118
|
+
const minuteUtc = utcHour * 60 + utcMinute;
|
|
119
|
+
const activeSessions = SESSION_WINDOWS.filter(
|
|
120
|
+
(session) => isInsideSession(minuteUtc, session.startMinuteUtc, session.endMinuteUtc)
|
|
121
|
+
).map((session) => session.name);
|
|
122
|
+
const primarySession = activeSessions.includes("us") ? "us" : activeSessions.includes("europe") ? "europe" : activeSessions.includes("asia") ? "asia" : "off_hours";
|
|
123
|
+
return {
|
|
124
|
+
timezone: "UTC",
|
|
125
|
+
utcHour,
|
|
126
|
+
utcMinute,
|
|
127
|
+
primarySession,
|
|
128
|
+
activeSessions,
|
|
129
|
+
isOverlap: activeSessions.length > 1,
|
|
130
|
+
overlap: activeSessions.length > 1 ? `${activeSessions.join("_")}_overlap` : null
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
var buildMissingSpreadContext = () => ({
|
|
134
|
+
source: "binance_coinbase_btc",
|
|
135
|
+
indicatorKey: "payload.indicators.spread",
|
|
136
|
+
available: false,
|
|
137
|
+
value: null,
|
|
138
|
+
bps: null,
|
|
139
|
+
absBps: null,
|
|
140
|
+
bias: null,
|
|
141
|
+
severity: null
|
|
142
|
+
});
|
|
143
|
+
var buildSpreadContextFromValue = (spread) => {
|
|
144
|
+
const value = roundTo(spread, 8);
|
|
145
|
+
const bps = roundTo(value * 1e4, 2);
|
|
146
|
+
const absBps = Math.abs(bps);
|
|
147
|
+
const bias = Math.abs(bps) < 1 ? "flat" : bps > 0 ? "coinbase_premium" : "binance_premium";
|
|
148
|
+
const severity = absBps >= 20 ? "wide" : absBps >= 5 ? "elevated" : "normal";
|
|
149
|
+
return {
|
|
150
|
+
source: "binance_coinbase_btc",
|
|
151
|
+
indicatorKey: "payload.indicators.spread",
|
|
152
|
+
available: true,
|
|
153
|
+
value,
|
|
154
|
+
bps,
|
|
155
|
+
absBps,
|
|
156
|
+
bias,
|
|
157
|
+
severity
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
var readSpreadFromSignal = (signal) => {
|
|
161
|
+
const indicatorSpread = getLastFiniteNumber(signal.indicators?.spread);
|
|
162
|
+
if (indicatorSpread != null) {
|
|
163
|
+
return indicatorSpread;
|
|
164
|
+
}
|
|
165
|
+
return getLastFiniteNumber(signal.additionalIndicators?.spread);
|
|
166
|
+
};
|
|
167
|
+
var buildAiMarketContext = (signal) => {
|
|
168
|
+
const existingMarketContext = toRecord(
|
|
169
|
+
signal.additionalIndicators?.marketContext
|
|
170
|
+
);
|
|
171
|
+
const existingSpread = toRecord(existingMarketContext?.binanceCoinbaseSpread);
|
|
172
|
+
const spread = readSpreadFromSignal(signal);
|
|
173
|
+
return {
|
|
174
|
+
...existingMarketContext ?? {},
|
|
175
|
+
tradingSession: buildTradingSessionContext(signal.timestamp),
|
|
176
|
+
binanceCoinbaseSpread: spread != null ? buildSpreadContextFromValue(spread) : existingSpread ?? buildMissingSpreadContext()
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
|
|
77
180
|
// src/strategy/manifests.ts
|
|
78
181
|
var import_indicators = require("@tradejs/core/indicators");
|
|
79
182
|
var import_logger2 = require("@tradejs/infra/logger");
|
|
@@ -83,6 +186,7 @@ var import_fs = __toESM(require("fs"));
|
|
|
83
186
|
var import_path = __toESM(require("path"));
|
|
84
187
|
var import_module = require("module");
|
|
85
188
|
var import_url = require("url");
|
|
189
|
+
var import_config = require("@tradejs/core/config");
|
|
86
190
|
var import_logger = require("@tradejs/infra/logger");
|
|
87
191
|
var CONFIG_FILE_NAMES = [
|
|
88
192
|
"tradejs.config.ts",
|
|
@@ -115,10 +219,14 @@ var normalizeConfig = (rawConfig) => {
|
|
|
115
219
|
const strategies2 = Array.isArray(config.strategies) ? config.strategies.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
116
220
|
const indicators = Array.isArray(config.indicators) ? config.indicators.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
117
221
|
const connectors = Array.isArray(config.connectors) ? config.connectors.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
222
|
+
const hooks = (0, import_config.normalizeTradejsConfigHooks)(
|
|
223
|
+
config.hooks
|
|
224
|
+
);
|
|
118
225
|
return {
|
|
119
226
|
strategies: strategies2,
|
|
120
227
|
indicators,
|
|
121
|
-
connectors
|
|
228
|
+
connectors,
|
|
229
|
+
...hooks ? { hooks } : {}
|
|
122
230
|
};
|
|
123
231
|
};
|
|
124
232
|
var getRequireFn = (cwd = getTradejsProjectCwd()) => (0, import_module.createRequire)(import_path.default.join(import_path.default.resolve(cwd), "__tradejs_loader__.js"));
|
|
@@ -493,24 +601,36 @@ var strategies = new Proxy(
|
|
|
493
601
|
);
|
|
494
602
|
|
|
495
603
|
// src/strategyAdapters/ai.ts
|
|
496
|
-
var
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
604
|
+
var toRecord2 = (value) => {
|
|
605
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
606
|
+
return {};
|
|
607
|
+
}
|
|
608
|
+
return value;
|
|
609
|
+
};
|
|
610
|
+
var buildBaseAiPayload = (signal) => {
|
|
611
|
+
const additionalIndicators = {
|
|
612
|
+
...toRecord2(signal.additionalIndicators),
|
|
613
|
+
marketContext: buildAiMarketContext(signal)
|
|
614
|
+
};
|
|
615
|
+
return {
|
|
616
|
+
signal: {
|
|
617
|
+
symbol: signal.symbol,
|
|
618
|
+
signalId: signal.signalId,
|
|
619
|
+
interval: signal.interval,
|
|
620
|
+
direction: signal.direction,
|
|
621
|
+
timestamp: signal.timestamp,
|
|
622
|
+
strategy: signal.strategy,
|
|
623
|
+
prices: {
|
|
624
|
+
currentPrice: signal.prices.currentPrice,
|
|
625
|
+
takeProfitPrice: signal.prices.takeProfitPrice,
|
|
626
|
+
stopLossPrice: signal.prices.stopLossPrice
|
|
627
|
+
}
|
|
628
|
+
},
|
|
629
|
+
figures: trimSeriesDeep(signal.figures ?? {}),
|
|
630
|
+
indicators: trimSeriesDeep(signal.indicators),
|
|
631
|
+
additionalIndicators: trimSeriesDeep(additionalIndicators)
|
|
632
|
+
};
|
|
633
|
+
};
|
|
514
634
|
var defaultAiAdapter = {};
|
|
515
635
|
var getStrategyAiAdapter = (strategy) => getStrategyManifest(strategy)?.aiAdapter ?? defaultAiAdapter;
|
|
516
636
|
var getSignalAiAdapter = (signal) => getStrategyAiAdapter(signal.strategy);
|
|
@@ -523,6 +643,107 @@ var buildAiPayloadByStrategy = (signal) => {
|
|
|
523
643
|
// src/ai.ts
|
|
524
644
|
var buildAiPayload = (signal) => buildAiPayloadByStrategy(signal);
|
|
525
645
|
|
|
646
|
+
// src/strategyHelpers/derivativesContext.ts
|
|
647
|
+
var import_indicators2 = require("@tradejs/core/indicators");
|
|
648
|
+
var import_constants = require("@tradejs/core/constants");
|
|
649
|
+
var import_timescale = require("@tradejs/infra/timescale");
|
|
650
|
+
var import_logger3 = require("@tradejs/infra/logger");
|
|
651
|
+
var DEFAULT_INTERVALS = ["15m", "1h"];
|
|
652
|
+
var DEFAULT_LOOKBACK_HOURS = 48;
|
|
653
|
+
var derivativesContextUnavailable = false;
|
|
654
|
+
var parseEnabledFlag = (value, env) => {
|
|
655
|
+
const normalized = String(value ?? "").trim().toLowerCase();
|
|
656
|
+
if (!normalized) return false;
|
|
657
|
+
if (["1", "true", "yes", "on"].includes(normalized)) return true;
|
|
658
|
+
if (normalized === "backtest") return env === "BACKTEST";
|
|
659
|
+
if (normalized === "live") return env !== "BACKTEST";
|
|
660
|
+
return false;
|
|
661
|
+
};
|
|
662
|
+
var parseLookbackMs = () => {
|
|
663
|
+
const hours = Number(process.env.DERIVATIVES_CONTEXT_LOOKBACK_HOURS);
|
|
664
|
+
const normalizedHours = Number.isFinite(hours) && hours > 0 ? hours : DEFAULT_LOOKBACK_HOURS;
|
|
665
|
+
return normalizedHours * 60 * 60 * 1e3;
|
|
666
|
+
};
|
|
667
|
+
var parseIntervals = () => {
|
|
668
|
+
const fromEnv = (0, import_indicators2.normalizeDerivativesIntervals)(
|
|
669
|
+
process.env.DERIVATIVES_CONTEXT_INTERVALS
|
|
670
|
+
);
|
|
671
|
+
return fromEnv.length ? fromEnv : DEFAULT_INTERVALS;
|
|
672
|
+
};
|
|
673
|
+
var getDerivativesContextReferenceSymbols = () => [
|
|
674
|
+
...import_constants.DERIVATIVES_CONTEXT_REFERENCE_SYMBOLS
|
|
675
|
+
];
|
|
676
|
+
var normalizeSymbol = (symbol) => String(symbol || "").trim().toUpperCase();
|
|
677
|
+
var resolvePrimaryReferenceSymbol = (signalSymbol) => {
|
|
678
|
+
const symbol = normalizeSymbol(signalSymbol);
|
|
679
|
+
const referenceSymbols = getDerivativesContextReferenceSymbols();
|
|
680
|
+
return referenceSymbols.some((referenceSymbol) => referenceSymbol === symbol) ? symbol : referenceSymbols[0];
|
|
681
|
+
};
|
|
682
|
+
var buildReferenceDerivativesContext = (params) => {
|
|
683
|
+
const { targetSymbol, primaryReferenceSymbol, referenceContexts } = params;
|
|
684
|
+
const primaryContext = referenceContexts[primaryReferenceSymbol] ?? referenceContexts[getDerivativesContextReferenceSymbols()[0]];
|
|
685
|
+
if (!primaryContext) {
|
|
686
|
+
throw new Error("No derivatives reference contexts built");
|
|
687
|
+
}
|
|
688
|
+
return {
|
|
689
|
+
...primaryContext,
|
|
690
|
+
targetSymbol,
|
|
691
|
+
primaryReferenceSymbol: primaryContext.symbol,
|
|
692
|
+
referenceSymbols: getDerivativesContextReferenceSymbols(),
|
|
693
|
+
referenceContexts
|
|
694
|
+
};
|
|
695
|
+
};
|
|
696
|
+
var isDerivativesContextEnabled = (env) => parseEnabledFlag(process.env.DERIVATIVES_CONTEXT_ENABLED, env);
|
|
697
|
+
var enrichSignalWithDerivativesContext = async (params) => {
|
|
698
|
+
const { signal, env, enabled = isDerivativesContextEnabled(env) } = params;
|
|
699
|
+
if (!enabled || derivativesContextUnavailable) {
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
try {
|
|
703
|
+
const intervals = parseIntervals();
|
|
704
|
+
const referenceSymbols = getDerivativesContextReferenceSymbols();
|
|
705
|
+
const lookbackMs = parseLookbackMs();
|
|
706
|
+
const contexts = await Promise.all(
|
|
707
|
+
referenceSymbols.map(async (symbol) => {
|
|
708
|
+
const rowsByInterval = await (0, import_timescale.getDerivativesWindow)({
|
|
709
|
+
symbol,
|
|
710
|
+
intervals,
|
|
711
|
+
endMs: signal.timestamp,
|
|
712
|
+
lookbackMs
|
|
713
|
+
});
|
|
714
|
+
return [
|
|
715
|
+
symbol,
|
|
716
|
+
(0, import_indicators2.buildDerivativesContext)({
|
|
717
|
+
symbol,
|
|
718
|
+
direction: signal.direction,
|
|
719
|
+
timestamp: signal.timestamp,
|
|
720
|
+
rowsByInterval,
|
|
721
|
+
intervals
|
|
722
|
+
})
|
|
723
|
+
];
|
|
724
|
+
})
|
|
725
|
+
);
|
|
726
|
+
const referenceContexts = Object.fromEntries(contexts);
|
|
727
|
+
const derivativesContext = buildReferenceDerivativesContext({
|
|
728
|
+
targetSymbol: signal.symbol,
|
|
729
|
+
primaryReferenceSymbol: resolvePrimaryReferenceSymbol(signal.symbol),
|
|
730
|
+
referenceContexts
|
|
731
|
+
});
|
|
732
|
+
signal.additionalIndicators = {
|
|
733
|
+
...signal.additionalIndicators ?? {},
|
|
734
|
+
derivativesContext
|
|
735
|
+
};
|
|
736
|
+
return true;
|
|
737
|
+
} catch (error) {
|
|
738
|
+
derivativesContextUnavailable = true;
|
|
739
|
+
import_logger3.logger.warn(
|
|
740
|
+
"Derivatives context disabled after Timescale read failure: %s",
|
|
741
|
+
String(error)
|
|
742
|
+
);
|
|
743
|
+
return false;
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
|
|
526
747
|
// src/strategyAdapters/ml.ts
|
|
527
748
|
var defaultMlAdapter = {
|
|
528
749
|
normalizeStrategyConfig: (strategyConfig) => strategyConfig
|
|
@@ -566,7 +787,7 @@ var buildMlPayload = (payload) => {
|
|
|
566
787
|
};
|
|
567
788
|
|
|
568
789
|
// src/connectorsRegistry.ts
|
|
569
|
-
var
|
|
790
|
+
var import_logger4 = require("@tradejs/infra/logger");
|
|
570
791
|
var createConnectorRegistryState = () => ({
|
|
571
792
|
connectorCreators: /* @__PURE__ */ new Map(),
|
|
572
793
|
providerToConnectorName: /* @__PURE__ */ new Map(),
|
|
@@ -618,7 +839,7 @@ var normalizeProviders = (providers, connectorName) => {
|
|
|
618
839
|
var registerProvider = (provider, connectorName, source, providerToConnectorName) => {
|
|
619
840
|
const existing = providerToConnectorName.get(provider);
|
|
620
841
|
if (existing && existing !== connectorName) {
|
|
621
|
-
|
|
842
|
+
import_logger4.logger.warn(
|
|
622
843
|
'Skip duplicate connector provider "%s" from %s: already mapped to %s',
|
|
623
844
|
provider,
|
|
624
845
|
source,
|
|
@@ -631,11 +852,11 @@ var registerProvider = (provider, connectorName, source, providerToConnectorName
|
|
|
631
852
|
var registerEntry = (entry, source, state) => {
|
|
632
853
|
const connectorName = String(entry?.name ?? "").trim();
|
|
633
854
|
if (!connectorName) {
|
|
634
|
-
|
|
855
|
+
import_logger4.logger.warn("Skip connector entry without name from %s", source);
|
|
635
856
|
return;
|
|
636
857
|
}
|
|
637
858
|
if (typeof entry.creator !== "function") {
|
|
638
|
-
|
|
859
|
+
import_logger4.logger.warn(
|
|
639
860
|
'Skip connector entry "%s" from %s: creator must be a function',
|
|
640
861
|
connectorName,
|
|
641
862
|
source
|
|
@@ -647,7 +868,7 @@ var registerEntry = (entry, source, state) => {
|
|
|
647
868
|
state.connectorCreators
|
|
648
869
|
);
|
|
649
870
|
if (existingByName) {
|
|
650
|
-
|
|
871
|
+
import_logger4.logger.warn(
|
|
651
872
|
'Skip duplicate connector "%s" from %s: already registered as %s',
|
|
652
873
|
connectorName,
|
|
653
874
|
source,
|
|
@@ -719,7 +940,7 @@ var ensureConnectorPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
|
|
|
719
940
|
);
|
|
720
941
|
const pluginDefinition = extractConnectorPluginDefinition(moduleExport);
|
|
721
942
|
if (!pluginDefinition) {
|
|
722
|
-
|
|
943
|
+
import_logger4.logger.warn(
|
|
723
944
|
'Skip connector plugin "%s": export { connectorEntries } is missing',
|
|
724
945
|
moduleName
|
|
725
946
|
);
|
|
@@ -727,7 +948,7 @@ var ensureConnectorPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
|
|
|
727
948
|
}
|
|
728
949
|
registerEntries2(pluginDefinition.connectorEntries, moduleName, state);
|
|
729
950
|
} catch (error) {
|
|
730
|
-
|
|
951
|
+
import_logger4.logger.warn(
|
|
731
952
|
'Failed to load connector plugin "%s": %s',
|
|
732
953
|
moduleName,
|
|
733
954
|
String(error)
|
|
@@ -818,6 +1039,7 @@ var createTestConnector = (connector, context) => {
|
|
|
818
1039
|
currentPositionProfit = 0;
|
|
819
1040
|
};
|
|
820
1041
|
return {
|
|
1042
|
+
__tradejsTestConnector: true,
|
|
821
1043
|
getState: async () => state,
|
|
822
1044
|
setState: async (newState) => {
|
|
823
1045
|
state = {
|
|
@@ -840,6 +1062,24 @@ var createTestConnector = (connector, context) => {
|
|
|
840
1062
|
};
|
|
841
1063
|
},
|
|
842
1064
|
getPosition: async () => currentPosition || null,
|
|
1065
|
+
getOpenPositionPnl: async () => {
|
|
1066
|
+
if (typeof connector.getOpenPositionPnl === "function") {
|
|
1067
|
+
return connector.getOpenPositionPnl();
|
|
1068
|
+
}
|
|
1069
|
+
if (!currentPosition) {
|
|
1070
|
+
return [];
|
|
1071
|
+
}
|
|
1072
|
+
return [
|
|
1073
|
+
{
|
|
1074
|
+
symbol: currentPosition.symbol,
|
|
1075
|
+
qty: currentPosition.qty,
|
|
1076
|
+
price: currentPosition.price,
|
|
1077
|
+
currentPrice: currentPosition.price,
|
|
1078
|
+
unrealizedPnl: 0,
|
|
1079
|
+
direction: currentPosition.direction
|
|
1080
|
+
}
|
|
1081
|
+
];
|
|
1082
|
+
},
|
|
843
1083
|
checkTp: async (candle) => {
|
|
844
1084
|
if (!candle || !currentPosition || !currentPosition.qty) {
|
|
845
1085
|
return;
|
|
@@ -957,7 +1197,6 @@ var createTestConnector = (connector, context) => {
|
|
|
957
1197
|
};
|
|
958
1198
|
|
|
959
1199
|
// src/testing.ts
|
|
960
|
-
var preloadStart = (0, import_time.getTimestamp)(import_constants.PRELOAD_DAYS);
|
|
961
1200
|
var createTestingKlineCacheState = () => ({
|
|
962
1201
|
coinKlineCache: /* @__PURE__ */ new Map(),
|
|
963
1202
|
btcKlineCache: /* @__PURE__ */ new Map(),
|
|
@@ -980,7 +1219,15 @@ var getTestingKlineCacheState = (cwd = getTradejsProjectCwd()) => {
|
|
|
980
1219
|
};
|
|
981
1220
|
};
|
|
982
1221
|
var getKlineCacheKey = (params) => {
|
|
983
|
-
const {
|
|
1222
|
+
const {
|
|
1223
|
+
userName,
|
|
1224
|
+
connectorName,
|
|
1225
|
+
symbol,
|
|
1226
|
+
preloadStart,
|
|
1227
|
+
end,
|
|
1228
|
+
interval,
|
|
1229
|
+
cacheOnly
|
|
1230
|
+
} = params;
|
|
984
1231
|
return [
|
|
985
1232
|
userName,
|
|
986
1233
|
connectorName,
|
|
@@ -996,6 +1243,7 @@ var getPreparedDataCacheKey = (params) => {
|
|
|
996
1243
|
userName,
|
|
997
1244
|
connectorName,
|
|
998
1245
|
symbol,
|
|
1246
|
+
preloadStart,
|
|
999
1247
|
start,
|
|
1000
1248
|
end,
|
|
1001
1249
|
interval,
|
|
@@ -1006,6 +1254,7 @@ var getPreparedDataCacheKey = (params) => {
|
|
|
1006
1254
|
userName,
|
|
1007
1255
|
connectorName,
|
|
1008
1256
|
symbol,
|
|
1257
|
+
preloadStart,
|
|
1009
1258
|
start,
|
|
1010
1259
|
end,
|
|
1011
1260
|
interval,
|
|
@@ -1014,7 +1263,7 @@ var getPreparedDataCacheKey = (params) => {
|
|
|
1014
1263
|
].join(":");
|
|
1015
1264
|
};
|
|
1016
1265
|
var getConnectorCacheKey = (params) => [params.userName, params.connectorName].join(":");
|
|
1017
|
-
var splitCandlesForTesting = (candles, start) => {
|
|
1266
|
+
var splitCandlesForTesting = (candles, start, preloadStart) => {
|
|
1018
1267
|
const prevData = [];
|
|
1019
1268
|
const testData = [];
|
|
1020
1269
|
for (const candle of candles) {
|
|
@@ -1075,6 +1324,7 @@ var testing = async ({
|
|
|
1075
1324
|
if (!start) {
|
|
1076
1325
|
throw new Error("no start");
|
|
1077
1326
|
}
|
|
1327
|
+
const preloadStart = (0, import_time.getBacktestPreloadStart)(start);
|
|
1078
1328
|
const startedAt = Date.now();
|
|
1079
1329
|
const formatTimeoutMessage = (stage) => `Test ${name} (${symbol}) timed out after ${timeoutMs}ms during ${stage}`;
|
|
1080
1330
|
const getRemainingTimeoutMs = (stage) => {
|
|
@@ -1150,7 +1400,7 @@ var testing = async ({
|
|
|
1150
1400
|
})
|
|
1151
1401
|
);
|
|
1152
1402
|
if (!binanceConnector || !coinbaseConnector) {
|
|
1153
|
-
|
|
1403
|
+
import_logger5.logger.warn(
|
|
1154
1404
|
"Binance/Coinbase connectors are unavailable. Reusing %s for BTC references.",
|
|
1155
1405
|
connectorName
|
|
1156
1406
|
);
|
|
@@ -1161,6 +1411,7 @@ var testing = async ({
|
|
|
1161
1411
|
userName,
|
|
1162
1412
|
connectorName,
|
|
1163
1413
|
symbol,
|
|
1414
|
+
preloadStart,
|
|
1164
1415
|
end,
|
|
1165
1416
|
interval,
|
|
1166
1417
|
cacheOnly
|
|
@@ -1169,6 +1420,7 @@ var testing = async ({
|
|
|
1169
1420
|
userName,
|
|
1170
1421
|
connectorName,
|
|
1171
1422
|
symbol: "BTCUSDT",
|
|
1423
|
+
preloadStart,
|
|
1172
1424
|
end,
|
|
1173
1425
|
interval,
|
|
1174
1426
|
cacheOnly
|
|
@@ -1181,6 +1433,7 @@ var testing = async ({
|
|
|
1181
1433
|
userName,
|
|
1182
1434
|
connectorName: btcBinanceConnectorName,
|
|
1183
1435
|
symbol: "BTCUSDT",
|
|
1436
|
+
preloadStart,
|
|
1184
1437
|
end,
|
|
1185
1438
|
interval,
|
|
1186
1439
|
cacheOnly
|
|
@@ -1189,6 +1442,7 @@ var testing = async ({
|
|
|
1189
1442
|
userName,
|
|
1190
1443
|
connectorName: btcCoinbaseConnectorName,
|
|
1191
1444
|
symbol: "BTCUSDT",
|
|
1445
|
+
preloadStart,
|
|
1192
1446
|
end,
|
|
1193
1447
|
interval,
|
|
1194
1448
|
cacheOnly
|
|
@@ -1199,6 +1453,7 @@ var testing = async ({
|
|
|
1199
1453
|
userName,
|
|
1200
1454
|
connectorName,
|
|
1201
1455
|
symbol,
|
|
1456
|
+
preloadStart,
|
|
1202
1457
|
start,
|
|
1203
1458
|
end,
|
|
1204
1459
|
interval,
|
|
@@ -1256,20 +1511,22 @@ var testing = async ({
|
|
|
1256
1511
|
if (!cachedBtcCoinbaseData) {
|
|
1257
1512
|
state.btcCoinbaseKlineCache.set(btcCoinbaseCacheKey, btcCoinbaseData);
|
|
1258
1513
|
}
|
|
1259
|
-
const { prevData: prevDataRaw, testData: testDataRaw } = splitCandlesForTesting(data, start);
|
|
1260
|
-
const { prevData: btcPrevDataRaw, testData: btcTestDataRaw } = splitCandlesForTesting(btcData, start);
|
|
1514
|
+
const { prevData: prevDataRaw, testData: testDataRaw } = splitCandlesForTesting(data, start, preloadStart);
|
|
1515
|
+
const { prevData: btcPrevDataRaw, testData: btcTestDataRaw } = splitCandlesForTesting(btcData, start, preloadStart);
|
|
1261
1516
|
const { prevData: btcBinancePrevDataRaw } = splitCandlesForTesting(
|
|
1262
1517
|
btcBinanceData,
|
|
1263
|
-
start
|
|
1518
|
+
start,
|
|
1519
|
+
preloadStart
|
|
1264
1520
|
);
|
|
1265
1521
|
const { prevData: btcCoinbasePrevDataRaw } = splitCandlesForTesting(
|
|
1266
1522
|
btcCoinbaseData,
|
|
1267
|
-
start
|
|
1523
|
+
start,
|
|
1524
|
+
preloadStart
|
|
1268
1525
|
);
|
|
1269
|
-
const { alignedCoinCandles: prevData2, alignedBtcCandles: btcPrevData2 } = (0,
|
|
1270
|
-
const { alignedCoinCandles: testData2, alignedBtcCandles: btcTestData2 } = (0,
|
|
1271
|
-
const { alignedBtcCandles: btcBinancePrevData2 } = (0,
|
|
1272
|
-
const { alignedBtcCandles: btcCoinbasePrevData2 } = (0,
|
|
1526
|
+
const { alignedCoinCandles: prevData2, alignedBtcCandles: btcPrevData2 } = (0, import_indicators3.alignSortedCandlesByTimestamp)(prevDataRaw, btcPrevDataRaw);
|
|
1527
|
+
const { alignedCoinCandles: testData2, alignedBtcCandles: btcTestData2 } = (0, import_indicators3.alignSortedCandlesByTimestamp)(testDataRaw, btcTestDataRaw);
|
|
1528
|
+
const { alignedBtcCandles: btcBinancePrevData2 } = (0, import_indicators3.alignSortedCandlesByTimestamp)(prevDataRaw, btcBinancePrevDataRaw);
|
|
1529
|
+
const { alignedBtcCandles: btcCoinbasePrevData2 } = (0, import_indicators3.alignSortedCandlesByTimestamp)(prevDataRaw, btcCoinbasePrevDataRaw);
|
|
1273
1530
|
preparedData = {
|
|
1274
1531
|
prevData: prevData2,
|
|
1275
1532
|
btcPrevData: btcPrevData2,
|
|
@@ -1311,6 +1568,7 @@ var testing = async ({
|
|
|
1311
1568
|
);
|
|
1312
1569
|
const pendingMlPayloadBySignalId = /* @__PURE__ */ new Map();
|
|
1313
1570
|
const pendingAiRowBySignalId = /* @__PURE__ */ new Map();
|
|
1571
|
+
const replaySignalEvaluations = [];
|
|
1314
1572
|
const flushClosedResultsBatch = async () => {
|
|
1315
1573
|
if (!ml && !ai) return;
|
|
1316
1574
|
const batch = await testConnector.drainMlResultsBatch();
|
|
@@ -1355,6 +1613,47 @@ var testing = async ({
|
|
|
1355
1613
|
"strategy signal",
|
|
1356
1614
|
strategy(candle, btcCandle)
|
|
1357
1615
|
);
|
|
1616
|
+
if (!signal || typeof signal === "string") {
|
|
1617
|
+
replaySignalEvaluations.push({
|
|
1618
|
+
evaluationId: `${testId}:${strategyName}:${symbol}:${candle.timestamp}`,
|
|
1619
|
+
userName,
|
|
1620
|
+
strategy: strategyName,
|
|
1621
|
+
symbol,
|
|
1622
|
+
interval,
|
|
1623
|
+
timestamp: candle.timestamp,
|
|
1624
|
+
evaluatedAt: candle.timestamp,
|
|
1625
|
+
status: "skip",
|
|
1626
|
+
reason: typeof signal === "string" && signal.trim() ? signal : "NO_SIGNAL"
|
|
1627
|
+
});
|
|
1628
|
+
} else {
|
|
1629
|
+
replaySignalEvaluations.push({
|
|
1630
|
+
evaluationId: `${signal.signalId || testId}:${strategyName}:${symbol}:${signal.timestamp || candle.timestamp}`,
|
|
1631
|
+
userName,
|
|
1632
|
+
strategy: signal.strategy || strategyName,
|
|
1633
|
+
symbol: signal.symbol || symbol,
|
|
1634
|
+
interval: signal.interval || interval,
|
|
1635
|
+
timestamp: typeof signal.timestamp === "number" && Number.isFinite(signal.timestamp) ? signal.timestamp : candle.timestamp,
|
|
1636
|
+
evaluatedAt: candle.timestamp,
|
|
1637
|
+
status: "signal",
|
|
1638
|
+
reason: signal.orderSkipReason || signal.orderStatus,
|
|
1639
|
+
signalId: signal.signalId,
|
|
1640
|
+
direction: signal.direction,
|
|
1641
|
+
orderStatus: signal.orderStatus,
|
|
1642
|
+
orderSkipReason: signal.orderSkipReason,
|
|
1643
|
+
aiAnalysis: signal.aiAnalysis ?? null,
|
|
1644
|
+
ml: signal.ml
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
const shouldCapturePayload = signal && typeof signal !== "string" && signal.signalId && (ml || ai);
|
|
1648
|
+
if (shouldCapturePayload) {
|
|
1649
|
+
await withTimeout(
|
|
1650
|
+
"derivatives context",
|
|
1651
|
+
enrichSignalWithDerivativesContext({
|
|
1652
|
+
signal,
|
|
1653
|
+
env: "BACKTEST"
|
|
1654
|
+
})
|
|
1655
|
+
);
|
|
1656
|
+
}
|
|
1358
1657
|
if (ml && signal && typeof signal !== "string" && signal.signalId) {
|
|
1359
1658
|
const payload = buildMlPayload({
|
|
1360
1659
|
signal,
|
|
@@ -1387,7 +1686,11 @@ var testing = async ({
|
|
|
1387
1686
|
}
|
|
1388
1687
|
}
|
|
1389
1688
|
await withTimeout("flush closed results", flushClosedResultsBatch());
|
|
1390
|
-
|
|
1689
|
+
const result = await withTimeout("collect result", testConnector.getResult());
|
|
1690
|
+
return {
|
|
1691
|
+
...result,
|
|
1692
|
+
inlineReplaySignalEvaluations: replaySignalEvaluations
|
|
1693
|
+
};
|
|
1391
1694
|
};
|
|
1392
1695
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1393
1696
|
0 && (module.exports = {
|