@tradejs/node 3.1.29-beta.287 → 3.1.29-beta.289
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/backtest.js +434 -224
- package/dist/backtest.mjs +10 -5
- package/dist/{chunk-KOV3U6ZZ.mjs → chunk-KYGJBMKE.mjs} +407 -202
- package/dist/diagnostics.js +408 -203
- package/dist/diagnostics.mjs +1 -1
- package/dist/mcp.d.mts +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +423 -218
- package/dist/mcp.mjs +1 -1
- package/dist/registry.js +408 -203
- package/dist/registry.mjs +1 -1
- package/dist/runtimeDashboard.js +410 -205
- package/dist/runtimeDashboard.mjs +1 -1
- package/dist/runtimeStrategies.js +408 -203
- package/dist/runtimeStrategies.mjs +1 -1
- package/dist/strategies.d.mts +32 -22
- package/dist/strategies.d.ts +32 -22
- package/dist/strategies.js +416 -211
- package/dist/strategies.mjs +1 -1
- package/package.json +5 -5
package/dist/diagnostics.js
CHANGED
|
@@ -1073,13 +1073,213 @@ var assessSignalWithJev = async ({
|
|
|
1073
1073
|
// src/strategyRuntime.ts
|
|
1074
1074
|
var import_constants5 = require("@tradejs/core/constants");
|
|
1075
1075
|
var import_strategies6 = require("@tradejs/core/strategies");
|
|
1076
|
-
var
|
|
1076
|
+
var import_logger11 = require("@tradejs/infra/logger");
|
|
1077
1077
|
|
|
1078
1078
|
// src/strategyHelpers/runtime.ts
|
|
1079
|
-
var
|
|
1079
|
+
var import_logger9 = require("@tradejs/infra/logger");
|
|
1080
1080
|
var import_constants3 = require("@tradejs/core/constants");
|
|
1081
1081
|
var import_ml2 = require("@tradejs/infra/ml");
|
|
1082
1082
|
|
|
1083
|
+
// src/strategy/entrySubmissionValidation.ts
|
|
1084
|
+
var import_logger = require("@tradejs/infra/logger");
|
|
1085
|
+
|
|
1086
|
+
// src/strategy/entryExecutionValidation.ts
|
|
1087
|
+
var EntrySubmissionValidationError = class extends Error {
|
|
1088
|
+
constructor() {
|
|
1089
|
+
super(...arguments);
|
|
1090
|
+
this.name = "EntrySubmissionValidationError";
|
|
1091
|
+
}
|
|
1092
|
+
};
|
|
1093
|
+
var resolveEntryExecutionLimits = (config, direction) => {
|
|
1094
|
+
const side = config[direction];
|
|
1095
|
+
const minimum = side && typeof side === "object" && "minRiskRatio" in side ? side.minRiskRatio : void 0;
|
|
1096
|
+
const maximum = config.MAX_LOSS_VALUE;
|
|
1097
|
+
if (minimum == null && maximum == null) return void 0;
|
|
1098
|
+
const minimumNetRiskRatio = minimum == null ? void 0 : Number(minimum);
|
|
1099
|
+
const maxLossValue = maximum == null ? void 0 : Number(maximum);
|
|
1100
|
+
const feeRate = Number(config.RISK_FEE_RATE ?? 0);
|
|
1101
|
+
const slippage = Number(config.RISK_SLIPPAGE_BPS ?? 0);
|
|
1102
|
+
const impact = Number(config.RISK_MARKET_IMPACT_BPS ?? 0);
|
|
1103
|
+
const slippageBps = slippage + impact;
|
|
1104
|
+
if (minimumNetRiskRatio != null && (!Number.isFinite(minimumNetRiskRatio) || minimumNetRiskRatio < 0) || maxLossValue != null && (!Number.isFinite(maxLossValue) || maxLossValue <= 0) || !Number.isFinite(feeRate) || feeRate < 0 || feeRate >= 1 || !Number.isFinite(slippage) || slippage < 0 || !Number.isFinite(impact) || impact < 0 || !Number.isFinite(slippageBps) || slippageBps < 0 || slippageBps >= 1e4)
|
|
1105
|
+
throw new Error("INVALID_ENTRY_EXECUTION_LIMITS");
|
|
1106
|
+
return { minimumNetRiskRatio, maxLossValue, feeRate, slippageBps };
|
|
1107
|
+
};
|
|
1108
|
+
var validateEntryExecution = ({
|
|
1109
|
+
direction,
|
|
1110
|
+
price,
|
|
1111
|
+
qty,
|
|
1112
|
+
stopLossPrice,
|
|
1113
|
+
takeProfitPrice,
|
|
1114
|
+
limits
|
|
1115
|
+
}) => {
|
|
1116
|
+
if (!Number.isFinite(price) || price <= 0 || !Number.isFinite(qty) || qty <= 0 || stopLossPrice == null || !Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || !Number.isFinite(takeProfitPrice) || takeProfitPrice <= 0)
|
|
1117
|
+
throw new EntrySubmissionValidationError(
|
|
1118
|
+
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
1119
|
+
);
|
|
1120
|
+
const side = direction === "LONG" ? 1 : -1;
|
|
1121
|
+
if (side * (price - stopLossPrice) <= 0 || side * (takeProfitPrice - price) <= 0)
|
|
1122
|
+
throw new EntrySubmissionValidationError(
|
|
1123
|
+
"INVALID_ENTRY_PROTECTION_AT_SUBMISSION"
|
|
1124
|
+
);
|
|
1125
|
+
const slippage = limits.slippageBps / 1e4;
|
|
1126
|
+
const cost = (value, sign) => value * slippage + value * (1 + sign * slippage) * limits.feeRate;
|
|
1127
|
+
const entryCost = cost(price, side);
|
|
1128
|
+
const lossPerUnit = Math.abs(price - stopLossPrice) + entryCost + cost(stopLossPrice, -side);
|
|
1129
|
+
const rewardPerUnit = Math.max(
|
|
1130
|
+
0,
|
|
1131
|
+
Math.abs(takeProfitPrice - price) - entryCost - cost(takeProfitPrice, -side)
|
|
1132
|
+
);
|
|
1133
|
+
const netRiskRatio = rewardPerUnit / lossPerUnit;
|
|
1134
|
+
const plannedLossValue = qty * lossPerUnit;
|
|
1135
|
+
if (!Number.isFinite(netRiskRatio) || !Number.isFinite(plannedLossValue))
|
|
1136
|
+
throw new EntrySubmissionValidationError(
|
|
1137
|
+
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
1138
|
+
);
|
|
1139
|
+
if (limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio)
|
|
1140
|
+
throw new EntrySubmissionValidationError(
|
|
1141
|
+
"ENTRY_RR_BELOW_MIN_AT_SUBMISSION"
|
|
1142
|
+
);
|
|
1143
|
+
if (limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9))
|
|
1144
|
+
throw new EntrySubmissionValidationError(
|
|
1145
|
+
"ENTRY_RISK_EXCEEDS_MAX_LOSS_AT_SUBMISSION"
|
|
1146
|
+
);
|
|
1147
|
+
return { netRiskRatio, plannedLossValue };
|
|
1148
|
+
};
|
|
1149
|
+
|
|
1150
|
+
// src/strategy/entrySubmissionValidation.ts
|
|
1151
|
+
var MAX_ENTRY_QUOTE_AGE_MS = 5e3;
|
|
1152
|
+
var toFiniteNumberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
1153
|
+
var getOrderArrivalSnapshot = async ({
|
|
1154
|
+
connector,
|
|
1155
|
+
symbol,
|
|
1156
|
+
requireTimestamp = false
|
|
1157
|
+
}) => {
|
|
1158
|
+
if (typeof connector.getTopOfBookTicker !== "function") {
|
|
1159
|
+
return {
|
|
1160
|
+
arrivalSnapshotTime: Date.now(),
|
|
1161
|
+
arrivalSource: "unavailable",
|
|
1162
|
+
bid: null,
|
|
1163
|
+
ask: null,
|
|
1164
|
+
arrivalMid: null,
|
|
1165
|
+
spreadBps: null
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
try {
|
|
1169
|
+
const ticker = await connector.getTopOfBookTicker(symbol);
|
|
1170
|
+
const arrivalSnapshotTime = toFiniteNumberOrNull(ticker?.timestamp);
|
|
1171
|
+
const timestampUnavailable = requireTimestamp && arrivalSnapshotTime == null;
|
|
1172
|
+
const bid = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.bidPrice);
|
|
1173
|
+
const ask = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.askPrice);
|
|
1174
|
+
const arrivalMid = bid != null && ask != null ? (bid + ask) / 2 : null;
|
|
1175
|
+
const spreadBps = bid != null && ask != null && arrivalMid != null && arrivalMid > 0 ? (ask - bid) / arrivalMid * 1e4 : null;
|
|
1176
|
+
return {
|
|
1177
|
+
arrivalSnapshotTime: arrivalSnapshotTime ?? Date.now(),
|
|
1178
|
+
arrivalSource: "top_of_book",
|
|
1179
|
+
bid,
|
|
1180
|
+
ask,
|
|
1181
|
+
arrivalMid,
|
|
1182
|
+
spreadBps
|
|
1183
|
+
};
|
|
1184
|
+
} catch (error) {
|
|
1185
|
+
import_logger.logger.warn(
|
|
1186
|
+
"runtime order arrival snapshot failed: %s %s",
|
|
1187
|
+
symbol,
|
|
1188
|
+
error?.message || String(error)
|
|
1189
|
+
);
|
|
1190
|
+
return {
|
|
1191
|
+
arrivalSnapshotTime: Date.now(),
|
|
1192
|
+
arrivalSource: "top_of_book_error",
|
|
1193
|
+
bid: null,
|
|
1194
|
+
ask: null,
|
|
1195
|
+
arrivalMid: null,
|
|
1196
|
+
spreadBps: null
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
};
|
|
1200
|
+
var validateEntryProtectionAtArrival = ({
|
|
1201
|
+
direction,
|
|
1202
|
+
signalPrice,
|
|
1203
|
+
bid,
|
|
1204
|
+
ask,
|
|
1205
|
+
arrivalMid,
|
|
1206
|
+
takeProfits,
|
|
1207
|
+
stopLossPrice
|
|
1208
|
+
}) => {
|
|
1209
|
+
const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
|
|
1210
|
+
const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
|
|
1211
|
+
if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
|
|
1212
|
+
throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
|
|
1213
|
+
}
|
|
1214
|
+
for (const takeProfit of takeProfits) {
|
|
1215
|
+
if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
|
|
1216
|
+
throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
|
|
1220
|
+
throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
|
|
1221
|
+
}
|
|
1222
|
+
};
|
|
1223
|
+
var createEntrySubmissionValidation = ({
|
|
1224
|
+
connector,
|
|
1225
|
+
symbol,
|
|
1226
|
+
direction,
|
|
1227
|
+
takeProfits,
|
|
1228
|
+
stopLossPrice,
|
|
1229
|
+
limits,
|
|
1230
|
+
onValidated
|
|
1231
|
+
}) => {
|
|
1232
|
+
if (!limits) return void 0;
|
|
1233
|
+
if (!connector.capabilities?.orderSubmissionValidation)
|
|
1234
|
+
throw new EntrySubmissionValidationError(
|
|
1235
|
+
"ENTRY_SUBMISSION_VALIDATION_UNSUPPORTED"
|
|
1236
|
+
);
|
|
1237
|
+
return async ({ qty, executionPrice }) => {
|
|
1238
|
+
const simulated = Reflect.get(connector, "__tradejsTestConnector") === true;
|
|
1239
|
+
const snapshot2 = simulated && executionPrice != null ? {
|
|
1240
|
+
arrivalSnapshotTime: Date.now(),
|
|
1241
|
+
arrivalSource: "simulation",
|
|
1242
|
+
bid: executionPrice,
|
|
1243
|
+
ask: executionPrice,
|
|
1244
|
+
arrivalMid: executionPrice,
|
|
1245
|
+
spreadBps: 0
|
|
1246
|
+
} : await getOrderArrivalSnapshot({
|
|
1247
|
+
connector,
|
|
1248
|
+
symbol,
|
|
1249
|
+
requireTimestamp: true
|
|
1250
|
+
});
|
|
1251
|
+
const age = Date.now() - snapshot2.arrivalSnapshotTime;
|
|
1252
|
+
if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
|
|
1253
|
+
throw new EntrySubmissionValidationError(
|
|
1254
|
+
"ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
|
|
1255
|
+
);
|
|
1256
|
+
if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
|
|
1257
|
+
throw new EntrySubmissionValidationError(
|
|
1258
|
+
"ENTRY_QUOTE_STALE_AT_SUBMISSION"
|
|
1259
|
+
);
|
|
1260
|
+
const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
|
|
1261
|
+
validateEntryProtectionAtArrival({
|
|
1262
|
+
direction,
|
|
1263
|
+
signalPrice: price,
|
|
1264
|
+
bid: snapshot2.bid,
|
|
1265
|
+
ask: snapshot2.ask,
|
|
1266
|
+
arrivalMid: snapshot2.arrivalMid,
|
|
1267
|
+
takeProfits,
|
|
1268
|
+
stopLossPrice
|
|
1269
|
+
});
|
|
1270
|
+
const prices = takeProfits.map((tp) => tp.price);
|
|
1271
|
+
const economics2 = validateEntryExecution({
|
|
1272
|
+
direction,
|
|
1273
|
+
price,
|
|
1274
|
+
qty,
|
|
1275
|
+
stopLossPrice,
|
|
1276
|
+
takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
|
|
1277
|
+
limits
|
|
1278
|
+
});
|
|
1279
|
+
onValidated?.(snapshot2, economics2);
|
|
1280
|
+
};
|
|
1281
|
+
};
|
|
1282
|
+
|
|
1083
1283
|
// src/ai.ts
|
|
1084
1284
|
var import_aiLanguages2 = require("@tradejs/core/aiLanguages");
|
|
1085
1285
|
var import_redis = require("@tradejs/infra/redis");
|
|
@@ -1664,7 +1864,7 @@ var import_fs = __toESM(require("fs"));
|
|
|
1664
1864
|
var import_path = __toESM(require("path"));
|
|
1665
1865
|
var import_url = require("url");
|
|
1666
1866
|
var import_config = require("@tradejs/core/config");
|
|
1667
|
-
var
|
|
1867
|
+
var import_logger2 = require("@tradejs/infra/logger");
|
|
1668
1868
|
var CONFIG_FILE_NAMES = [
|
|
1669
1869
|
"tradejs.config.ts",
|
|
1670
1870
|
"tradejs.config.mts",
|
|
@@ -1931,11 +2131,11 @@ var loadTradejsConfig = async (cwd = getTradejsProjectCwd()) => {
|
|
|
1931
2131
|
cachedByCwd.set(cwd, config);
|
|
1932
2132
|
if (!announcedConfigFile.has(configFilePath)) {
|
|
1933
2133
|
announcedConfigFile.add(configFilePath);
|
|
1934
|
-
|
|
2134
|
+
import_logger2.logger.log("debug", "Loaded TradeJS config: %s", configFilePath);
|
|
1935
2135
|
}
|
|
1936
2136
|
return config;
|
|
1937
2137
|
} catch (error) {
|
|
1938
|
-
|
|
2138
|
+
import_logger2.logger.log(
|
|
1939
2139
|
"warn",
|
|
1940
2140
|
"Failed to load TradeJS config from %s: %s",
|
|
1941
2141
|
configFilePath,
|
|
@@ -2901,7 +3101,7 @@ var import_node_crypto = require("crypto");
|
|
|
2901
3101
|
var import_constants = require("@tradejs/core/constants");
|
|
2902
3102
|
var import_time = require("@tradejs/core/time");
|
|
2903
3103
|
var import_trade = require("@tradejs/core/trade");
|
|
2904
|
-
var
|
|
3104
|
+
var import_logger3 = require("@tradejs/infra/logger");
|
|
2905
3105
|
var import_redis2 = require("@tradejs/infra/redis");
|
|
2906
3106
|
var now = () => Date.now();
|
|
2907
3107
|
var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
|
|
@@ -2960,7 +3160,7 @@ var recordRuntimeTradeOpen = async (params) => {
|
|
|
2960
3160
|
)
|
|
2961
3161
|
]);
|
|
2962
3162
|
} catch (error) {
|
|
2963
|
-
|
|
3163
|
+
import_logger3.logger.error(
|
|
2964
3164
|
"runtime trade open journal failed: %s %s",
|
|
2965
3165
|
record2.symbol,
|
|
2966
3166
|
error?.message || String(error)
|
|
@@ -3028,7 +3228,7 @@ var recordRuntimeTradeIncrease = async (params) => {
|
|
|
3028
3228
|
)
|
|
3029
3229
|
]);
|
|
3030
3230
|
} catch (error) {
|
|
3031
|
-
|
|
3231
|
+
import_logger3.logger.error(
|
|
3032
3232
|
"runtime trade increase journal failed: %s %s",
|
|
3033
3233
|
symbol,
|
|
3034
3234
|
error?.message || String(error)
|
|
@@ -3135,7 +3335,7 @@ var markRuntimeTradeClosed = async (params) => {
|
|
|
3135
3335
|
)
|
|
3136
3336
|
]);
|
|
3137
3337
|
} catch (error) {
|
|
3138
|
-
|
|
3338
|
+
import_logger3.logger.error(
|
|
3139
3339
|
"runtime trade close journal failed: %s %s",
|
|
3140
3340
|
symbol,
|
|
3141
3341
|
error?.message || String(error)
|
|
@@ -3145,11 +3345,11 @@ var markRuntimeTradeClosed = async (params) => {
|
|
|
3145
3345
|
};
|
|
3146
3346
|
|
|
3147
3347
|
// src/strategyHelpers/marketContextStages.ts
|
|
3148
|
-
var
|
|
3348
|
+
var import_logger8 = require("@tradejs/infra/logger");
|
|
3149
3349
|
|
|
3150
3350
|
// src/strategyHelpers/binanceMarketContext.ts
|
|
3151
3351
|
var import_marketContext = require("@tradejs/infra/timescale/marketContext");
|
|
3152
|
-
var
|
|
3352
|
+
var import_logger4 = require("@tradejs/infra/logger");
|
|
3153
3353
|
var import_strategies = require("@tradejs/core/strategies");
|
|
3154
3354
|
|
|
3155
3355
|
// src/binanceBreadthUniverses.ts
|
|
@@ -3513,7 +3713,7 @@ var parseEnabledFlag = (value, env) => {
|
|
|
3513
3713
|
if (normalized === "live") return env !== "BACKTEST";
|
|
3514
3714
|
return false;
|
|
3515
3715
|
};
|
|
3516
|
-
var
|
|
3716
|
+
var toFiniteNumberOrNull2 = (value) => {
|
|
3517
3717
|
const numeric = typeof value === "number" ? value : Number(value);
|
|
3518
3718
|
return Number.isFinite(numeric) ? numeric : null;
|
|
3519
3719
|
};
|
|
@@ -3638,14 +3838,14 @@ var toTradeFlowContext = (row, interval) => row ? {
|
|
|
3638
3838
|
asOfTs: row.ts.getTime(),
|
|
3639
3839
|
ageMs: row.ageMs,
|
|
3640
3840
|
stale: row.stale,
|
|
3641
|
-
trades:
|
|
3642
|
-
buyPressurePct:
|
|
3643
|
-
buyBaseVolume:
|
|
3644
|
-
sellBaseVolume:
|
|
3645
|
-
buyQuoteVolume:
|
|
3646
|
-
sellQuoteVolume:
|
|
3647
|
-
netBaseDelta:
|
|
3648
|
-
netQuoteDelta:
|
|
3841
|
+
trades: toFiniteNumberOrNull2(row.trades),
|
|
3842
|
+
buyPressurePct: toFiniteNumberOrNull2(row.buyPressurePct),
|
|
3843
|
+
buyBaseVolume: toFiniteNumberOrNull2(row.buyBaseVolume),
|
|
3844
|
+
sellBaseVolume: toFiniteNumberOrNull2(row.sellBaseVolume),
|
|
3845
|
+
buyQuoteVolume: toFiniteNumberOrNull2(row.buyQuoteVolume),
|
|
3846
|
+
sellQuoteVolume: toFiniteNumberOrNull2(row.sellQuoteVolume),
|
|
3847
|
+
netBaseDelta: toFiniteNumberOrNull2(row.netBaseDelta),
|
|
3848
|
+
netQuoteDelta: toFiniteNumberOrNull2(row.netQuoteDelta)
|
|
3649
3849
|
} : null;
|
|
3650
3850
|
var getCachedReferenceRows = ({
|
|
3651
3851
|
referenceSymbols,
|
|
@@ -3734,16 +3934,16 @@ var toMarketBreadthContext = (breadth, interval) => ({
|
|
|
3734
3934
|
asOfTs: breadth.ts.getTime(),
|
|
3735
3935
|
ageMs: breadth.ageMs,
|
|
3736
3936
|
stale: breadth.stale,
|
|
3737
|
-
symbolsCount:
|
|
3738
|
-
advancers:
|
|
3739
|
-
decliners:
|
|
3740
|
-
unchanged:
|
|
3741
|
-
advanceDeclineRatio:
|
|
3742
|
-
pctAboveMa20:
|
|
3743
|
-
pctAboveMa50:
|
|
3744
|
-
equalWeightedReturn:
|
|
3745
|
-
volumeWeightedReturn:
|
|
3746
|
-
dispersion:
|
|
3937
|
+
symbolsCount: toFiniteNumberOrNull2(breadth.symbolsCount),
|
|
3938
|
+
advancers: toFiniteNumberOrNull2(breadth.advancers),
|
|
3939
|
+
decliners: toFiniteNumberOrNull2(breadth.decliners),
|
|
3940
|
+
unchanged: toFiniteNumberOrNull2(breadth.unchanged),
|
|
3941
|
+
advanceDeclineRatio: toFiniteNumberOrNull2(breadth.advanceDeclineRatio),
|
|
3942
|
+
pctAboveMa20: toFiniteNumberOrNull2(breadth.pctAboveMa20),
|
|
3943
|
+
pctAboveMa50: toFiniteNumberOrNull2(breadth.pctAboveMa50),
|
|
3944
|
+
equalWeightedReturn: toFiniteNumberOrNull2(breadth.equalWeightedReturn),
|
|
3945
|
+
volumeWeightedReturn: toFiniteNumberOrNull2(breadth.volumeWeightedReturn),
|
|
3946
|
+
dispersion: toFiniteNumberOrNull2(breadth.dispersion)
|
|
3747
3947
|
});
|
|
3748
3948
|
var enrichSignalWithBinanceMarketContext = async (params) => {
|
|
3749
3949
|
const {
|
|
@@ -3844,46 +4044,46 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
|
|
|
3844
4044
|
asOfTs: primaryBreadth.ts.getTime(),
|
|
3845
4045
|
ageMs: primaryBreadth.ageMs,
|
|
3846
4046
|
stale: primaryBreadth.stale,
|
|
3847
|
-
btcReturn1h:
|
|
4047
|
+
btcReturn1h: toFiniteNumberOrNull2(
|
|
3848
4048
|
primaryBreadth.btcReturn1h
|
|
3849
4049
|
),
|
|
3850
|
-
btcReturn4h:
|
|
4050
|
+
btcReturn4h: toFiniteNumberOrNull2(
|
|
3851
4051
|
primaryBreadth.btcReturn4h
|
|
3852
4052
|
),
|
|
3853
|
-
btcReturn24h:
|
|
4053
|
+
btcReturn24h: toFiniteNumberOrNull2(
|
|
3854
4054
|
primaryBreadth.btcReturn24h
|
|
3855
4055
|
),
|
|
3856
|
-
altBasketReturn1h:
|
|
4056
|
+
altBasketReturn1h: toFiniteNumberOrNull2(
|
|
3857
4057
|
primaryBreadth.altBasketReturn1h
|
|
3858
4058
|
),
|
|
3859
|
-
altBasketReturn4h:
|
|
4059
|
+
altBasketReturn4h: toFiniteNumberOrNull2(
|
|
3860
4060
|
primaryBreadth.altBasketReturn4h
|
|
3861
4061
|
),
|
|
3862
|
-
altBasketReturn24h:
|
|
4062
|
+
altBasketReturn24h: toFiniteNumberOrNull2(
|
|
3863
4063
|
primaryBreadth.altBasketReturn24h
|
|
3864
4064
|
),
|
|
3865
|
-
btcVsAltReturn1h:
|
|
4065
|
+
btcVsAltReturn1h: toFiniteNumberOrNull2(
|
|
3866
4066
|
primaryBreadth.btcVsAltReturn1h
|
|
3867
4067
|
),
|
|
3868
|
-
btcVsAltReturn4h:
|
|
4068
|
+
btcVsAltReturn4h: toFiniteNumberOrNull2(
|
|
3869
4069
|
primaryBreadth.btcVsAltReturn4h
|
|
3870
4070
|
),
|
|
3871
|
-
btcVsAltReturn24h:
|
|
4071
|
+
btcVsAltReturn24h: toFiniteNumberOrNull2(
|
|
3872
4072
|
primaryBreadth.btcVsAltReturn24h
|
|
3873
4073
|
),
|
|
3874
|
-
btcTurnoverShare1h:
|
|
4074
|
+
btcTurnoverShare1h: toFiniteNumberOrNull2(
|
|
3875
4075
|
primaryBreadth.btcTurnoverShare1h
|
|
3876
4076
|
),
|
|
3877
|
-
btcTurnoverShare24h:
|
|
4077
|
+
btcTurnoverShare24h: toFiniteNumberOrNull2(
|
|
3878
4078
|
primaryBreadth.btcTurnoverShare24h
|
|
3879
4079
|
),
|
|
3880
|
-
btcTurnoverShareChange24h:
|
|
4080
|
+
btcTurnoverShareChange24h: toFiniteNumberOrNull2(
|
|
3881
4081
|
primaryBreadth.btcTurnoverShareChange24h
|
|
3882
4082
|
),
|
|
3883
|
-
altVolToBtcVol24h:
|
|
4083
|
+
altVolToBtcVol24h: toFiniteNumberOrNull2(
|
|
3884
4084
|
primaryBreadth.altVolToBtcVol24h
|
|
3885
4085
|
),
|
|
3886
|
-
altDispersion24h:
|
|
4086
|
+
altDispersion24h: toFiniteNumberOrNull2(
|
|
3887
4087
|
primaryBreadth.altDispersion24h
|
|
3888
4088
|
),
|
|
3889
4089
|
regime: primaryBreadth.btcAltRegime ?? "unknown"
|
|
@@ -3900,7 +4100,7 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
|
|
|
3900
4100
|
throw error;
|
|
3901
4101
|
}
|
|
3902
4102
|
binanceMarketContextUnavailable = true;
|
|
3903
|
-
|
|
4103
|
+
import_logger4.logger.warn(
|
|
3904
4104
|
"Binance market context disabled after Timescale read failure: %s",
|
|
3905
4105
|
String(error)
|
|
3906
4106
|
);
|
|
@@ -3910,7 +4110,7 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
|
|
|
3910
4110
|
|
|
3911
4111
|
// src/strategyHelpers/coinMarketCapContext.ts
|
|
3912
4112
|
var import_strategies2 = require("@tradejs/core/strategies");
|
|
3913
|
-
var
|
|
4113
|
+
var import_logger5 = require("@tradejs/infra/logger");
|
|
3914
4114
|
var import_marketContext2 = require("@tradejs/infra/timescale/marketContext");
|
|
3915
4115
|
var DEFAULT_MAX_AGE_MS = 48 * 60 * 6e4;
|
|
3916
4116
|
var SOURCE_GLOBAL_DAILY = "coinmarketcap_global";
|
|
@@ -3948,7 +4148,7 @@ var asInt = (value, fallback) => {
|
|
|
3948
4148
|
const parsed = Number.parseInt(String(value ?? ""), 10);
|
|
3949
4149
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
3950
4150
|
};
|
|
3951
|
-
var
|
|
4151
|
+
var toFiniteNumberOrNull3 = (value) => {
|
|
3952
4152
|
const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
|
|
3953
4153
|
return Number.isFinite(numeric) ? numeric : null;
|
|
3954
4154
|
};
|
|
@@ -4182,12 +4382,12 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
4182
4382
|
const ethRow = references.get("ETHUSDT") ?? null;
|
|
4183
4383
|
const previousBtcRow = previousReferences.get("BTCUSDT") ?? null;
|
|
4184
4384
|
const previousEthRow = previousReferences.get("ETHUSDT") ?? null;
|
|
4185
|
-
const btcMarketCapUsd =
|
|
4186
|
-
const ethMarketCapUsd =
|
|
4187
|
-
const previousBtcMarketCapUsd =
|
|
4385
|
+
const btcMarketCapUsd = toFiniteNumberOrNull3(btcRow?.marketCapUsd);
|
|
4386
|
+
const ethMarketCapUsd = toFiniteNumberOrNull3(ethRow?.marketCapUsd);
|
|
4387
|
+
const previousBtcMarketCapUsd = toFiniteNumberOrNull3(
|
|
4188
4388
|
previousBtcRow?.marketCapUsd
|
|
4189
4389
|
);
|
|
4190
|
-
const previousEthMarketCapUsd =
|
|
4390
|
+
const previousEthMarketCapUsd = toFiniteNumberOrNull3(
|
|
4191
4391
|
previousEthRow?.marketCapUsd
|
|
4192
4392
|
);
|
|
4193
4393
|
const ethBtcMarketCapRatio = safeDivide(ethMarketCapUsd, btcMarketCapUsd);
|
|
@@ -4196,16 +4396,16 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
4196
4396
|
previousBtcMarketCapUsd
|
|
4197
4397
|
);
|
|
4198
4398
|
const ethBtcMarketCapRatioChange24hPct = ethBtcMarketCapRatio != null && previousEthBtcMarketCapRatio != null && previousEthBtcMarketCapRatio > 0 ? (ethBtcMarketCapRatio - previousEthBtcMarketCapRatio) / previousEthBtcMarketCapRatio : null;
|
|
4199
|
-
const btcVolumeUsd =
|
|
4200
|
-
const ethVolumeUsd =
|
|
4399
|
+
const btcVolumeUsd = toFiniteNumberOrNull3(btcRow?.volumeUsd);
|
|
4400
|
+
const ethVolumeUsd = toFiniteNumberOrNull3(ethRow?.volumeUsd);
|
|
4201
4401
|
const referenceStale = btcRow?.stale === true || ethRow?.stale === true || !btcRow || !ethRow;
|
|
4202
|
-
const btcDominanceChange24hPct =
|
|
4402
|
+
const btcDominanceChange24hPct = toFiniteNumberOrNull3(
|
|
4203
4403
|
globalRow?.btcDominanceChange24hPct
|
|
4204
4404
|
);
|
|
4205
|
-
const altMarketCapChange24hPct =
|
|
4405
|
+
const altMarketCapChange24hPct = toFiniteNumberOrNull3(
|
|
4206
4406
|
globalRow?.altMarketCapChange24hPct
|
|
4207
4407
|
);
|
|
4208
|
-
const altVolumeChange24hPct =
|
|
4408
|
+
const altVolumeChange24hPct = toFiniteNumberOrNull3(
|
|
4209
4409
|
globalRow?.altVolumeChange24hPct
|
|
4210
4410
|
);
|
|
4211
4411
|
const altLiquidityRegime = globalRow ? toAltLiquidityRegime({
|
|
@@ -4216,19 +4416,19 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
4216
4416
|
}) : "unknown";
|
|
4217
4417
|
const exchangeLiquidityRegime = exchangeLiquidityRow ? toExchangeLiquidityRegime({
|
|
4218
4418
|
stale: exchangeLiquidityRow.stale,
|
|
4219
|
-
totalVolumeChange24hPct:
|
|
4419
|
+
totalVolumeChange24hPct: toFiniteNumberOrNull3(
|
|
4220
4420
|
exchangeLiquidityRow.totalVolumeChange24hPct
|
|
4221
4421
|
),
|
|
4222
4422
|
fallback: exchangeLiquidityRow.liquidityRegime ?? "unknown"
|
|
4223
4423
|
}) : "unknown";
|
|
4224
4424
|
const cmc100Row = indexRows.get("cmc100") ?? null;
|
|
4225
4425
|
const cmc20Row = indexRows.get("cmc20") ?? null;
|
|
4226
|
-
const cmc100Value =
|
|
4227
|
-
const cmc20Value =
|
|
4228
|
-
const cmc100Change24hPct =
|
|
4426
|
+
const cmc100Value = toFiniteNumberOrNull3(cmc100Row?.value);
|
|
4427
|
+
const cmc20Value = toFiniteNumberOrNull3(cmc20Row?.value);
|
|
4428
|
+
const cmc100Change24hPct = toFiniteNumberOrNull3(
|
|
4229
4429
|
cmc100Row?.valueChange24hPct
|
|
4230
4430
|
);
|
|
4231
|
-
const cmc20Change24hPct =
|
|
4431
|
+
const cmc20Change24hPct = toFiniteNumberOrNull3(cmc20Row?.valueChange24hPct);
|
|
4232
4432
|
const cmc20ToCmc100Ratio = safeDivide(cmc20Value, cmc100Value);
|
|
4233
4433
|
const cmc20ToCmc100RatioChange24hPct = cmc20Change24hPct != null && cmc100Change24hPct != null ? (1 + cmc20Change24hPct) / (1 + cmc100Change24hPct) - 1 : null;
|
|
4234
4434
|
const indexStale = cmc100Row?.stale === true || cmc20Row?.stale === true || !cmc100Row || !cmc20Row;
|
|
@@ -4252,41 +4452,41 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
4252
4452
|
asOfTs: globalRow.ts.getTime(),
|
|
4253
4453
|
ageMs: globalRow.ageMs,
|
|
4254
4454
|
stale: globalRow.stale,
|
|
4255
|
-
totalMarketCapUsd:
|
|
4455
|
+
totalMarketCapUsd: toFiniteNumberOrNull3(
|
|
4256
4456
|
globalRow.totalMarketCapUsd
|
|
4257
4457
|
),
|
|
4258
|
-
totalVolumeUsd:
|
|
4458
|
+
totalVolumeUsd: toFiniteNumberOrNull3(
|
|
4259
4459
|
globalRow.totalVolumeUsd
|
|
4260
4460
|
),
|
|
4261
|
-
totalVolumeReportedUsd:
|
|
4461
|
+
totalVolumeReportedUsd: toFiniteNumberOrNull3(
|
|
4262
4462
|
globalRow.totalVolumeReportedUsd
|
|
4263
4463
|
),
|
|
4264
|
-
altMarketCapUsd:
|
|
4464
|
+
altMarketCapUsd: toFiniteNumberOrNull3(
|
|
4265
4465
|
globalRow.altMarketCapUsd
|
|
4266
4466
|
),
|
|
4267
|
-
altVolumeUsd:
|
|
4268
|
-
altVolumeReportedUsd:
|
|
4467
|
+
altVolumeUsd: toFiniteNumberOrNull3(globalRow.altVolumeUsd),
|
|
4468
|
+
altVolumeReportedUsd: toFiniteNumberOrNull3(
|
|
4269
4469
|
globalRow.altVolumeReportedUsd
|
|
4270
4470
|
),
|
|
4271
|
-
btcDominancePct:
|
|
4471
|
+
btcDominancePct: toFiniteNumberOrNull3(
|
|
4272
4472
|
globalRow.btcDominancePct
|
|
4273
4473
|
),
|
|
4274
|
-
ethDominancePct:
|
|
4474
|
+
ethDominancePct: toFiniteNumberOrNull3(
|
|
4275
4475
|
globalRow.ethDominancePct
|
|
4276
4476
|
),
|
|
4277
4477
|
btcDominanceChange24hPct,
|
|
4278
|
-
ethDominanceChange24hPct:
|
|
4478
|
+
ethDominanceChange24hPct: toFiniteNumberOrNull3(
|
|
4279
4479
|
globalRow.ethDominanceChange24hPct
|
|
4280
4480
|
),
|
|
4281
4481
|
altMarketCapChange24hPct,
|
|
4282
4482
|
altVolumeChange24hPct,
|
|
4283
|
-
activeCryptocurrencies:
|
|
4483
|
+
activeCryptocurrencies: toFiniteNumberOrNull3(
|
|
4284
4484
|
globalRow.activeCryptocurrencies
|
|
4285
4485
|
),
|
|
4286
|
-
activeExchanges:
|
|
4486
|
+
activeExchanges: toFiniteNumberOrNull3(
|
|
4287
4487
|
globalRow.activeExchanges
|
|
4288
4488
|
),
|
|
4289
|
-
activeMarketPairs:
|
|
4489
|
+
activeMarketPairs: toFiniteNumberOrNull3(
|
|
4290
4490
|
globalRow.activeMarketPairs
|
|
4291
4491
|
),
|
|
4292
4492
|
altLiquidityRegime
|
|
@@ -4331,22 +4531,22 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
4331
4531
|
asOfTs: exchangeLiquidityRow.ts.getTime(),
|
|
4332
4532
|
ageMs: exchangeLiquidityRow.ageMs,
|
|
4333
4533
|
stale: exchangeLiquidityRow.stale,
|
|
4334
|
-
exchangesCount:
|
|
4534
|
+
exchangesCount: toFiniteNumberOrNull3(
|
|
4335
4535
|
exchangeLiquidityRow.exchangesCount
|
|
4336
4536
|
),
|
|
4337
|
-
totalVolumeUsd:
|
|
4537
|
+
totalVolumeUsd: toFiniteNumberOrNull3(
|
|
4338
4538
|
exchangeLiquidityRow.totalVolumeUsd
|
|
4339
4539
|
),
|
|
4340
|
-
totalVolumeChange24hPct:
|
|
4540
|
+
totalVolumeChange24hPct: toFiniteNumberOrNull3(
|
|
4341
4541
|
exchangeLiquidityRow.totalVolumeChange24hPct
|
|
4342
4542
|
),
|
|
4343
|
-
binanceVolumeUsd:
|
|
4543
|
+
binanceVolumeUsd: toFiniteNumberOrNull3(
|
|
4344
4544
|
exchangeLiquidityRow.binanceVolumeUsd
|
|
4345
4545
|
),
|
|
4346
|
-
binanceVolumeShare:
|
|
4546
|
+
binanceVolumeShare: toFiniteNumberOrNull3(
|
|
4347
4547
|
exchangeLiquidityRow.binanceVolumeShare
|
|
4348
4548
|
),
|
|
4349
|
-
topExchangeVolumeShare:
|
|
4549
|
+
topExchangeVolumeShare: toFiniteNumberOrNull3(
|
|
4350
4550
|
exchangeLiquidityRow.topExchangeVolumeShare
|
|
4351
4551
|
),
|
|
4352
4552
|
liquidityRegime: exchangeLiquidityRegime
|
|
@@ -4359,11 +4559,11 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
4359
4559
|
asOfTs: fearGreedRow.ts.getTime(),
|
|
4360
4560
|
ageMs: fearGreedRow.ageMs,
|
|
4361
4561
|
stale: fearGreedRow.stale,
|
|
4362
|
-
value:
|
|
4363
|
-
valueChange24h:
|
|
4562
|
+
value: toFiniteNumberOrNull3(fearGreedRow.value),
|
|
4563
|
+
valueChange24h: toFiniteNumberOrNull3(
|
|
4364
4564
|
fearGreedRow.valueChange24h
|
|
4365
4565
|
),
|
|
4366
|
-
valueChange7d:
|
|
4566
|
+
valueChange7d: toFiniteNumberOrNull3(
|
|
4367
4567
|
fearGreedRow.valueChange7d
|
|
4368
4568
|
),
|
|
4369
4569
|
classification: fearGreedRow.classification ?? "Unknown",
|
|
@@ -4383,13 +4583,13 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
4383
4583
|
cmc100Value,
|
|
4384
4584
|
cmc100Change24hPct,
|
|
4385
4585
|
cmc100TopConstituentSymbol: cmc100Row?.topConstituentSymbol ?? null,
|
|
4386
|
-
cmc100TopConstituentWeightPct:
|
|
4586
|
+
cmc100TopConstituentWeightPct: toFiniteNumberOrNull3(
|
|
4387
4587
|
cmc100Row?.topConstituentWeightPct
|
|
4388
4588
|
),
|
|
4389
4589
|
cmc20Value,
|
|
4390
4590
|
cmc20Change24hPct,
|
|
4391
4591
|
cmc20TopConstituentSymbol: cmc20Row?.topConstituentSymbol ?? null,
|
|
4392
|
-
cmc20TopConstituentWeightPct:
|
|
4592
|
+
cmc20TopConstituentWeightPct: toFiniteNumberOrNull3(
|
|
4393
4593
|
cmc20Row?.topConstituentWeightPct
|
|
4394
4594
|
),
|
|
4395
4595
|
cmc20ToCmc100Ratio,
|
|
@@ -4407,7 +4607,7 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
4407
4607
|
throw error;
|
|
4408
4608
|
}
|
|
4409
4609
|
coinMarketCapContextUnavailable = true;
|
|
4410
|
-
|
|
4610
|
+
import_logger5.logger.warn(
|
|
4411
4611
|
"CoinMarketCap context disabled after Timescale read failure: %s",
|
|
4412
4612
|
String(error)
|
|
4413
4613
|
);
|
|
@@ -4421,7 +4621,7 @@ var import_data2 = require("@tradejs/core/data");
|
|
|
4421
4621
|
var import_strategies3 = require("@tradejs/core/strategies");
|
|
4422
4622
|
var import_constants2 = require("@tradejs/core/constants");
|
|
4423
4623
|
var import_derivatives = require("@tradejs/infra/timescale/derivatives");
|
|
4424
|
-
var
|
|
4624
|
+
var import_logger6 = require("@tradejs/infra/logger");
|
|
4425
4625
|
var STORED_INTERVALS = ["15m", "1h"];
|
|
4426
4626
|
var CONTEXT_INTERVALS = ["15m", "1h"];
|
|
4427
4627
|
var DEFAULT_LOOKBACK_HOURS = 48;
|
|
@@ -4476,7 +4676,7 @@ var resolvePrimaryReferenceSymbol2 = () => PRIMARY_DERIVATIVES_REFERENCE_SYMBOL;
|
|
|
4476
4676
|
var resolveSecondaryReferenceSymbol = () => SECONDARY_DERIVATIVES_REFERENCE_SYMBOL;
|
|
4477
4677
|
var getPrimaryIntervalContext = (context) => context?.intervals["15m"] ?? context?.intervals["1h"] ?? null;
|
|
4478
4678
|
var hasDerivativesSymbolData = (context) => Object.keys(context.intervals).length > 0 && !context.summary.riskFlags.includes("missing_derivatives");
|
|
4479
|
-
var
|
|
4679
|
+
var toFiniteNumberOrNull4 = (value) => {
|
|
4480
4680
|
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
4481
4681
|
if (typeof value === "string" && value.trim()) {
|
|
4482
4682
|
const parsed = Number(value);
|
|
@@ -4490,8 +4690,8 @@ var roundNullable = (value, digits = 4) => {
|
|
|
4490
4690
|
return Math.round(value * multiplier) / multiplier;
|
|
4491
4691
|
};
|
|
4492
4692
|
var deltaNullable = (targetValue, referenceValue) => {
|
|
4493
|
-
const target =
|
|
4494
|
-
const reference =
|
|
4693
|
+
const target = toFiniteNumberOrNull4(targetValue);
|
|
4694
|
+
const reference = toFiniteNumberOrNull4(referenceValue);
|
|
4495
4695
|
return target == null || reference == null ? null : roundNullable(target - reference);
|
|
4496
4696
|
};
|
|
4497
4697
|
var buildTargetDerivedContext = (params) => {
|
|
@@ -4653,7 +4853,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
4653
4853
|
throw error;
|
|
4654
4854
|
}
|
|
4655
4855
|
derivativesContextUnavailable = true;
|
|
4656
|
-
|
|
4856
|
+
import_logger6.logger.warn(
|
|
4657
4857
|
"Derivatives context disabled after Timescale read failure: %s",
|
|
4658
4858
|
String(error)
|
|
4659
4859
|
);
|
|
@@ -4665,7 +4865,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
4665
4865
|
var import_data3 = require("@tradejs/core/data");
|
|
4666
4866
|
var import_strategies4 = require("@tradejs/core/strategies");
|
|
4667
4867
|
var import_hyperliquidWhales2 = require("@tradejs/infra/timescale/hyperliquidWhales");
|
|
4668
|
-
var
|
|
4868
|
+
var import_logger7 = require("@tradejs/infra/logger");
|
|
4669
4869
|
|
|
4670
4870
|
// src/hyperliquidWhaleUniverse.ts
|
|
4671
4871
|
var import_node_crypto3 = require("crypto");
|
|
@@ -5185,7 +5385,7 @@ var loadHyperliquidWhaleFlowContext = async (params) => {
|
|
|
5185
5385
|
throw error;
|
|
5186
5386
|
}
|
|
5187
5387
|
hyperliquidWhaleContextUnavailable = true;
|
|
5188
|
-
|
|
5388
|
+
import_logger7.logger.warn(
|
|
5189
5389
|
"Hyperliquid whale context disabled after Timescale read failure: %s",
|
|
5190
5390
|
String(error)
|
|
5191
5391
|
);
|
|
@@ -5268,7 +5468,7 @@ var runMarketContextStage = async ({
|
|
|
5268
5468
|
elapsedMs: Date.now() - startedAt
|
|
5269
5469
|
};
|
|
5270
5470
|
if (status === "timed_out") {
|
|
5271
|
-
|
|
5471
|
+
import_logger8.logger.warn(
|
|
5272
5472
|
"Market context stage timed out: %s after %sms",
|
|
5273
5473
|
stage,
|
|
5274
5474
|
result.elapsedMs
|
|
@@ -5458,79 +5658,10 @@ var enrichSignalWithAi = async ({
|
|
|
5458
5658
|
signal.aiAnalysis = analysis;
|
|
5459
5659
|
return resolveAiQuality(analysis, direction);
|
|
5460
5660
|
} catch (err) {
|
|
5461
|
-
|
|
5661
|
+
import_logger9.logger.error("AI analysis error: %s %s", symbol, formatAiError(err));
|
|
5462
5662
|
}
|
|
5463
5663
|
return void 0;
|
|
5464
5664
|
};
|
|
5465
|
-
var toFiniteNumberOrNull4 = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
5466
|
-
var getOrderArrivalSnapshot = async ({
|
|
5467
|
-
connector,
|
|
5468
|
-
symbol
|
|
5469
|
-
}) => {
|
|
5470
|
-
if (typeof connector.getTopOfBookTicker !== "function") {
|
|
5471
|
-
return {
|
|
5472
|
-
arrivalSnapshotTime: Date.now(),
|
|
5473
|
-
arrivalSource: "unavailable",
|
|
5474
|
-
bid: null,
|
|
5475
|
-
ask: null,
|
|
5476
|
-
arrivalMid: null,
|
|
5477
|
-
spreadBps: null
|
|
5478
|
-
};
|
|
5479
|
-
}
|
|
5480
|
-
try {
|
|
5481
|
-
const ticker = await connector.getTopOfBookTicker(symbol);
|
|
5482
|
-
const arrivalSnapshotTime = toFiniteNumberOrNull4(ticker?.timestamp);
|
|
5483
|
-
const bid = toFiniteNumberOrNull4(ticker?.bidPrice);
|
|
5484
|
-
const ask = toFiniteNumberOrNull4(ticker?.askPrice);
|
|
5485
|
-
const arrivalMid = bid != null && ask != null ? (bid + ask) / 2 : null;
|
|
5486
|
-
const spreadBps = bid != null && ask != null && arrivalMid != null && arrivalMid > 0 ? (ask - bid) / arrivalMid * 1e4 : null;
|
|
5487
|
-
return {
|
|
5488
|
-
arrivalSnapshotTime: arrivalSnapshotTime ?? Date.now(),
|
|
5489
|
-
arrivalSource: "top_of_book",
|
|
5490
|
-
bid,
|
|
5491
|
-
ask,
|
|
5492
|
-
arrivalMid,
|
|
5493
|
-
spreadBps
|
|
5494
|
-
};
|
|
5495
|
-
} catch (error) {
|
|
5496
|
-
import_logger8.logger.warn(
|
|
5497
|
-
"runtime order arrival snapshot failed: %s %s",
|
|
5498
|
-
symbol,
|
|
5499
|
-
error?.message || String(error)
|
|
5500
|
-
);
|
|
5501
|
-
return {
|
|
5502
|
-
arrivalSnapshotTime: Date.now(),
|
|
5503
|
-
arrivalSource: "top_of_book_error",
|
|
5504
|
-
bid: null,
|
|
5505
|
-
ask: null,
|
|
5506
|
-
arrivalMid: null,
|
|
5507
|
-
spreadBps: null
|
|
5508
|
-
};
|
|
5509
|
-
}
|
|
5510
|
-
};
|
|
5511
|
-
var validateEntryProtectionAtArrival = ({
|
|
5512
|
-
direction,
|
|
5513
|
-
signalPrice,
|
|
5514
|
-
bid,
|
|
5515
|
-
ask,
|
|
5516
|
-
arrivalMid,
|
|
5517
|
-
takeProfits,
|
|
5518
|
-
stopLossPrice
|
|
5519
|
-
}) => {
|
|
5520
|
-
const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
|
|
5521
|
-
const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
|
|
5522
|
-
if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
|
|
5523
|
-
throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
|
|
5524
|
-
}
|
|
5525
|
-
for (const takeProfit of takeProfits) {
|
|
5526
|
-
if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
|
|
5527
|
-
throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
|
|
5528
|
-
}
|
|
5529
|
-
}
|
|
5530
|
-
if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
|
|
5531
|
-
throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
|
|
5532
|
-
}
|
|
5533
|
-
};
|
|
5534
5665
|
var resolveRuntimeTelemetryQuality = ({
|
|
5535
5666
|
signalClosePrice,
|
|
5536
5667
|
arrivalMid,
|
|
@@ -5607,7 +5738,8 @@ var executeEntryOrder = async ({
|
|
|
5607
5738
|
signal,
|
|
5608
5739
|
beforePlaceOrder,
|
|
5609
5740
|
recordRuntimeTrade = true,
|
|
5610
|
-
leverage
|
|
5741
|
+
leverage,
|
|
5742
|
+
executionLimits
|
|
5611
5743
|
}) => {
|
|
5612
5744
|
await beforePlaceOrder?.();
|
|
5613
5745
|
const previousPosition = positionIntent === "increase" ? await connector.getPosition(symbol) : null;
|
|
@@ -5618,23 +5750,57 @@ var executeEntryOrder = async ({
|
|
|
5618
5750
|
signal.orderQty = qty;
|
|
5619
5751
|
signal.orderValue = qty * currentPrice;
|
|
5620
5752
|
signal.orderFailureReason = void 0;
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5753
|
+
let arrivalSnapshot = {
|
|
5754
|
+
arrivalSnapshotTime: Date.now(),
|
|
5755
|
+
arrivalSource: "unavailable",
|
|
5756
|
+
bid: null,
|
|
5757
|
+
ask: null,
|
|
5758
|
+
arrivalMid: null,
|
|
5759
|
+
spreadBps: null
|
|
5760
|
+
};
|
|
5761
|
+
let orderSubmitTime = Date.now();
|
|
5762
|
+
const beforeSubmit = createEntrySubmissionValidation({
|
|
5763
|
+
connector,
|
|
5764
|
+
symbol,
|
|
5765
|
+
direction,
|
|
5766
|
+
takeProfits,
|
|
5767
|
+
stopLossPrice,
|
|
5768
|
+
limits: executionLimits,
|
|
5769
|
+
onValidated: (snapshot2, economics2) => {
|
|
5770
|
+
arrivalSnapshot = snapshot2;
|
|
5771
|
+
orderSubmitTime = Date.now();
|
|
5772
|
+
if (executionLimits?.maxLossValue != null) {
|
|
5773
|
+
signal.riskDecision = {
|
|
5774
|
+
stage: "order_plan",
|
|
5775
|
+
status: "approved",
|
|
5776
|
+
reason: "PLANNED_LOSS_WITHIN_MAX_LOSS_VALUE",
|
|
5777
|
+
plannedLossValue: economics2.plannedLossValue,
|
|
5778
|
+
maxLossValue: executionLimits.maxLossValue,
|
|
5779
|
+
limitUtilization: economics2.plannedLossValue / executionLimits.maxLossValue,
|
|
5780
|
+
enforced: true
|
|
5781
|
+
};
|
|
5782
|
+
}
|
|
5783
|
+
}
|
|
5784
|
+
});
|
|
5785
|
+
if (!beforeSubmit) {
|
|
5786
|
+
arrivalSnapshot = await getOrderArrivalSnapshot({ connector, symbol });
|
|
5787
|
+
try {
|
|
5788
|
+
validateEntryProtectionAtArrival({
|
|
5789
|
+
direction,
|
|
5790
|
+
signalPrice: currentPrice,
|
|
5791
|
+
bid: arrivalSnapshot.bid,
|
|
5792
|
+
ask: arrivalSnapshot.ask,
|
|
5793
|
+
arrivalMid: arrivalSnapshot.arrivalMid,
|
|
5794
|
+
takeProfits,
|
|
5795
|
+
stopLossPrice
|
|
5796
|
+
});
|
|
5797
|
+
} catch (error) {
|
|
5798
|
+
signal.orderStatus = "failed";
|
|
5799
|
+
signal.orderFailureReason = error.message;
|
|
5800
|
+
throw error;
|
|
5801
|
+
}
|
|
5802
|
+
orderSubmitTime = Date.now();
|
|
5636
5803
|
}
|
|
5637
|
-
const orderSubmitTime = Date.now();
|
|
5638
5804
|
const orderPlaced = await connector.placeOrder({
|
|
5639
5805
|
symbol,
|
|
5640
5806
|
qty,
|
|
@@ -5645,7 +5811,8 @@ var executeEntryOrder = async ({
|
|
|
5645
5811
|
direction,
|
|
5646
5812
|
...typeof leverage === "number" && Number.isFinite(leverage) ? { leverage } : {},
|
|
5647
5813
|
orderId,
|
|
5648
|
-
signal
|
|
5814
|
+
signal,
|
|
5815
|
+
...beforeSubmit ? { beforeSubmit } : {}
|
|
5649
5816
|
});
|
|
5650
5817
|
const orderAckTime = Date.now();
|
|
5651
5818
|
const placedQty = typeof signal.orderQty === "number" && Number.isFinite(signal.orderQty) && signal.orderQty > 0 ? signal.orderQty : qty;
|
|
@@ -6474,7 +6641,7 @@ var canUseSharedReplayState = ({
|
|
|
6474
6641
|
}) => (env === "BACKTEST" || env === "PARITY") && Boolean(sharedReplayKey);
|
|
6475
6642
|
|
|
6476
6643
|
// src/strategy/runtimeExecution.ts
|
|
6477
|
-
var
|
|
6644
|
+
var import_logger10 = require("@tradejs/infra/logger");
|
|
6478
6645
|
var import_types = require("@tradejs/types");
|
|
6479
6646
|
var buildExitOrderSignal = ({
|
|
6480
6647
|
strategyName,
|
|
@@ -6526,7 +6693,7 @@ var handleExitDecision = async ({
|
|
|
6526
6693
|
deploymentId: connector.deploymentId
|
|
6527
6694
|
});
|
|
6528
6695
|
if (!activeTrade) {
|
|
6529
|
-
|
|
6696
|
+
import_logger10.logger.warn(
|
|
6530
6697
|
"[%s] blocked closePosition for untracked runtime position: %s",
|
|
6531
6698
|
strategyName ?? "unknown",
|
|
6532
6699
|
symbol
|
|
@@ -6534,7 +6701,7 @@ var handleExitDecision = async ({
|
|
|
6534
6701
|
return "CLOSE_BLOCKED_BY_UNTRACKED_POSITION";
|
|
6535
6702
|
}
|
|
6536
6703
|
if (!strategyName || activeTrade.strategy !== strategyName) {
|
|
6537
|
-
|
|
6704
|
+
import_logger10.logger.warn(
|
|
6538
6705
|
"[%s] blocked closePosition for foreign runtime position: %s ownedBy=%s",
|
|
6539
6706
|
strategyName ?? "unknown",
|
|
6540
6707
|
symbol,
|
|
@@ -6587,7 +6754,7 @@ var handleExitDecision = async ({
|
|
|
6587
6754
|
exitType: closedTrade?.exitType ?? "exit"
|
|
6588
6755
|
});
|
|
6589
6756
|
} catch (notificationError) {
|
|
6590
|
-
|
|
6757
|
+
import_logger10.logger.error(
|
|
6591
6758
|
"runtime close notification error: %s %s",
|
|
6592
6759
|
symbol,
|
|
6593
6760
|
notificationError
|
|
@@ -6601,7 +6768,7 @@ var handleExitDecision = async ({
|
|
|
6601
6768
|
decision,
|
|
6602
6769
|
market
|
|
6603
6770
|
});
|
|
6604
|
-
|
|
6771
|
+
import_logger10.logger.error("close order error: %s %s", symbol, err);
|
|
6605
6772
|
return "ORDER_ERROR";
|
|
6606
6773
|
}
|
|
6607
6774
|
return decision.code;
|
|
@@ -6628,7 +6795,7 @@ var handleProtectDecision = async ({
|
|
|
6628
6795
|
decision,
|
|
6629
6796
|
market
|
|
6630
6797
|
});
|
|
6631
|
-
|
|
6798
|
+
import_logger10.logger.error("protect position error: %s %s", symbol, err);
|
|
6632
6799
|
return "ORDER_ERROR";
|
|
6633
6800
|
}
|
|
6634
6801
|
return decision.code;
|
|
@@ -6694,7 +6861,11 @@ var executeEntryDecision = async ({
|
|
|
6694
6861
|
...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {},
|
|
6695
6862
|
signal,
|
|
6696
6863
|
beforePlaceOrder,
|
|
6697
|
-
recordRuntimeTrade: recordRuntimeJournal
|
|
6864
|
+
recordRuntimeTrade: recordRuntimeJournal,
|
|
6865
|
+
executionLimits: resolveEntryExecutionLimits(
|
|
6866
|
+
hookCtx.strategyConfig,
|
|
6867
|
+
decision.entryContext.direction
|
|
6868
|
+
)
|
|
6698
6869
|
});
|
|
6699
6870
|
await invokeStageHooks(
|
|
6700
6871
|
"afterPlaceOrder",
|
|
@@ -6716,19 +6887,32 @@ var executeEntryDecision = async ({
|
|
|
6716
6887
|
return signal;
|
|
6717
6888
|
}
|
|
6718
6889
|
await beforePlaceOrder();
|
|
6719
|
-
const
|
|
6890
|
+
const beforeSubmit = createEntrySubmissionValidation({
|
|
6720
6891
|
connector,
|
|
6721
|
-
symbol
|
|
6722
|
-
});
|
|
6723
|
-
validateEntryProtectionAtArrival({
|
|
6892
|
+
symbol,
|
|
6724
6893
|
direction: decision.entryContext.direction,
|
|
6725
|
-
signalPrice: decision.entryContext.prices.currentPrice,
|
|
6726
|
-
bid: arrivalSnapshot.bid,
|
|
6727
|
-
ask: arrivalSnapshot.ask,
|
|
6728
|
-
arrivalMid: arrivalSnapshot.arrivalMid,
|
|
6729
6894
|
takeProfits: decision.orderPlan.takeProfits,
|
|
6730
|
-
stopLossPrice: decision.orderPlan.stopLossPrice
|
|
6895
|
+
stopLossPrice: decision.orderPlan.stopLossPrice,
|
|
6896
|
+
limits: resolveEntryExecutionLimits(
|
|
6897
|
+
hookCtx.strategyConfig,
|
|
6898
|
+
decision.entryContext.direction
|
|
6899
|
+
)
|
|
6731
6900
|
});
|
|
6901
|
+
if (!beforeSubmit) {
|
|
6902
|
+
const arrivalSnapshot = await getOrderArrivalSnapshot({
|
|
6903
|
+
connector,
|
|
6904
|
+
symbol
|
|
6905
|
+
});
|
|
6906
|
+
validateEntryProtectionAtArrival({
|
|
6907
|
+
direction: decision.entryContext.direction,
|
|
6908
|
+
signalPrice: decision.entryContext.prices.currentPrice,
|
|
6909
|
+
bid: arrivalSnapshot.bid,
|
|
6910
|
+
ask: arrivalSnapshot.ask,
|
|
6911
|
+
arrivalMid: arrivalSnapshot.arrivalMid,
|
|
6912
|
+
takeProfits: decision.orderPlan.takeProfits,
|
|
6913
|
+
stopLossPrice: decision.orderPlan.stopLossPrice
|
|
6914
|
+
});
|
|
6915
|
+
}
|
|
6732
6916
|
const orderPlaced = await connector.placeOrder({
|
|
6733
6917
|
symbol,
|
|
6734
6918
|
qty: decision.orderPlan.qty,
|
|
@@ -6736,6 +6920,7 @@ var executeEntryDecision = async ({
|
|
|
6736
6920
|
timestamp: decision.entryContext.timestamp,
|
|
6737
6921
|
direction: decision.entryContext.direction,
|
|
6738
6922
|
positionIntent: decision.orderPlan.positionIntent,
|
|
6923
|
+
...beforeSubmit ? { beforeSubmit } : {},
|
|
6739
6924
|
...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {}
|
|
6740
6925
|
});
|
|
6741
6926
|
if (!orderPlaced) {
|
|
@@ -6777,6 +6962,23 @@ var executeEntryDecision = async ({
|
|
|
6777
6962
|
{ decision, entry, market }
|
|
6778
6963
|
);
|
|
6779
6964
|
} catch (err) {
|
|
6965
|
+
if (err instanceof EntrySubmissionValidationError) {
|
|
6966
|
+
if (signal) {
|
|
6967
|
+
signal.orderStatus = "skipped";
|
|
6968
|
+
signal.orderSkipReason = err.message;
|
|
6969
|
+
signal.allocatorDecision = {
|
|
6970
|
+
stage: "runtime_admission",
|
|
6971
|
+
status: "rejected",
|
|
6972
|
+
reason: err.message
|
|
6973
|
+
};
|
|
6974
|
+
}
|
|
6975
|
+
import_logger10.logger.warn(
|
|
6976
|
+
"entry rejected before submission: %s %s",
|
|
6977
|
+
symbol,
|
|
6978
|
+
err.message
|
|
6979
|
+
);
|
|
6980
|
+
return signal ?? err.message;
|
|
6981
|
+
}
|
|
6780
6982
|
if (signal) {
|
|
6781
6983
|
signal.orderStatus = "failed";
|
|
6782
6984
|
if (typeof signal.orderFailureReason !== "string" || !signal.orderFailureReason.trim()) {
|
|
@@ -6791,9 +6993,9 @@ var executeEntryDecision = async ({
|
|
|
6791
6993
|
market
|
|
6792
6994
|
});
|
|
6793
6995
|
if (err?.message === import_types.BACKTEST_WARNING_CODES.TAKE_PROFIT_CROSSED_BEFORE_ENTRY) {
|
|
6794
|
-
|
|
6996
|
+
import_logger10.logger.warn("order warning: %s %s", symbol, err);
|
|
6795
6997
|
} else {
|
|
6796
|
-
|
|
6998
|
+
import_logger10.logger.error("order error: %s %s", symbol, err);
|
|
6797
6999
|
}
|
|
6798
7000
|
return signal ?? "ORDER_ERROR";
|
|
6799
7001
|
}
|
|
@@ -6958,7 +7160,7 @@ var createStrategyRuntime = ({
|
|
|
6958
7160
|
try {
|
|
6959
7161
|
await projectHook(errorParams);
|
|
6960
7162
|
} catch (hookError) {
|
|
6961
|
-
|
|
7163
|
+
import_logger11.logger.error(
|
|
6962
7164
|
"project hook onRuntimeError failed: %s %s",
|
|
6963
7165
|
strategyName,
|
|
6964
7166
|
hookError
|
|
@@ -6972,7 +7174,7 @@ var createStrategyRuntime = ({
|
|
|
6972
7174
|
try {
|
|
6973
7175
|
await onRuntimeError(errorParams);
|
|
6974
7176
|
} catch (hookError) {
|
|
6975
|
-
|
|
7177
|
+
import_logger11.logger.error(
|
|
6976
7178
|
"runtime hook onRuntimeError failed: %s %s",
|
|
6977
7179
|
strategyName,
|
|
6978
7180
|
hookError
|
|
@@ -6986,7 +7188,7 @@ var createStrategyRuntime = ({
|
|
|
6986
7188
|
try {
|
|
6987
7189
|
return await hook(params);
|
|
6988
7190
|
} catch (error) {
|
|
6989
|
-
|
|
7191
|
+
import_logger11.logger.error(
|
|
6990
7192
|
'strategy hook "%s" failed for %s: %s',
|
|
6991
7193
|
stage,
|
|
6992
7194
|
strategyName,
|
|
@@ -7185,8 +7387,11 @@ var createStrategyRuntime = ({
|
|
|
7185
7387
|
return enrichedBaseContext;
|
|
7186
7388
|
}
|
|
7187
7389
|
});
|
|
7390
|
+
const coreConfig = { ...config };
|
|
7391
|
+
delete coreConfig.RECORD_RUNTIME_TRADES;
|
|
7392
|
+
delete coreConfig.SIMULATE_ORDERS;
|
|
7188
7393
|
const core = await createCore({
|
|
7189
|
-
config,
|
|
7394
|
+
config: coreConfig,
|
|
7190
7395
|
data,
|
|
7191
7396
|
strategyApi,
|
|
7192
7397
|
indicatorsState
|