@tradejs/node 2.0.18 → 2.0.19
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 +4763 -168
- package/dist/ai.mjs +2 -3
- package/dist/backtest.js +4018 -1372
- package/dist/backtest.mjs +537 -872
- package/dist/chunk-4AVFJMQL.mjs +6054 -0
- package/dist/chunk-Y6FXYEAI.mjs +10 -0
- package/dist/cli.js +5537 -6403
- package/dist/cli.mjs +5 -11
- package/dist/connectors.mjs +1 -1
- package/dist/constants.mjs +1 -1
- package/dist/pine.mjs +122 -13
- package/dist/registry.js +5732 -48
- package/dist/registry.mjs +2 -2
- package/dist/strategies.d.mts +2 -2
- package/dist/strategies.d.ts +2 -2
- package/dist/strategies.js +2296 -7843
- package/dist/strategies.mjs +46 -2547
- package/package.json +6 -4
- package/dist/chunk-3BP3ETAD.mjs +0 -2134
- package/dist/chunk-GPR56UYQ.mjs +0 -5518
- package/dist/chunk-IQKMII6L.mjs +0 -137
- package/dist/chunk-QVSMINLG.mjs +0 -246
- package/dist/chunk-UV2HVMKZ.mjs +0 -41
- package/dist/chunk-WK5EUCX5.mjs +0 -1191
package/dist/ai.js
CHANGED
|
@@ -48,7 +48,7 @@ __export(ai_exports, {
|
|
|
48
48
|
});
|
|
49
49
|
module.exports = __toCommonJS(ai_exports);
|
|
50
50
|
var import_aiLanguages = require("@tradejs/infra/aiLanguages");
|
|
51
|
-
var
|
|
51
|
+
var import_redis2 = require("@tradejs/infra/redis");
|
|
52
52
|
var import_userSettings = require("@tradejs/infra/userSettings");
|
|
53
53
|
|
|
54
54
|
// src/aiShared.ts
|
|
@@ -624,8 +624,131 @@ var buildAiMarketContext = (signal) => ({
|
|
|
624
624
|
});
|
|
625
625
|
|
|
626
626
|
// src/strategy/manifests.ts
|
|
627
|
-
var
|
|
628
|
-
var
|
|
627
|
+
var import_indicators2 = require("@tradejs/core/indicators");
|
|
628
|
+
var import_logger11 = require("@tradejs/infra/logger");
|
|
629
|
+
|
|
630
|
+
// src/strategyRuntime.ts
|
|
631
|
+
var import_constants5 = require("@tradejs/core/constants");
|
|
632
|
+
var import_strategies6 = require("@tradejs/core/strategies");
|
|
633
|
+
var import_logger10 = require("@tradejs/infra/logger");
|
|
634
|
+
|
|
635
|
+
// src/strategyHelpers/runtime.ts
|
|
636
|
+
var import_logger8 = require("@tradejs/infra/logger");
|
|
637
|
+
var import_constants3 = require("@tradejs/core/constants");
|
|
638
|
+
var import_ml2 = require("@tradejs/infra/ml");
|
|
639
|
+
|
|
640
|
+
// src/strategy/policyProfiles.ts
|
|
641
|
+
var profileMatches = (profile, universe, assetClass) => {
|
|
642
|
+
const { appliesTo } = profile;
|
|
643
|
+
if (!appliesTo) return true;
|
|
644
|
+
if (appliesTo.universes?.length && (!universe || !appliesTo.universes.includes(universe))) {
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
if (appliesTo.assetClasses?.length && (!assetClass || !appliesTo.assetClasses.includes(assetClass))) {
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
return true;
|
|
651
|
+
};
|
|
652
|
+
var resolveStrategyPolicyProfile = (manifest, params) => {
|
|
653
|
+
const profiles = manifest?.policyProfiles ?? [];
|
|
654
|
+
if (!profiles.length) {
|
|
655
|
+
const inferredId = params.profileId ?? (params.universe === "tradfi" ? "tradfi" : void 0);
|
|
656
|
+
if (!inferredId) return void 0;
|
|
657
|
+
if (inferredId !== "crypto" && inferredId !== "tradfi") {
|
|
658
|
+
throw new Error(
|
|
659
|
+
`Unknown policy profile "${inferredId}" for strategy "${manifest?.name}"`
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
if (params.universe && inferredId !== params.universe) {
|
|
663
|
+
throw new Error(
|
|
664
|
+
`Policy profile "${inferredId}" is not compatible with ${params.universe}`
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
return {
|
|
668
|
+
id: inferredId,
|
|
669
|
+
appliesTo: { universes: [inferredId] },
|
|
670
|
+
marketDataRequirements: inferredId === "crypto" ? ["crypto.btcReference"] : [],
|
|
671
|
+
...manifest?.mlAdapter ? {
|
|
672
|
+
entryRuntimeDefaults: {
|
|
673
|
+
ml: {
|
|
674
|
+
modelKey: inferredId === "crypto" ? manifest.name : `${manifest.name}:tradfi`
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
} : {}
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
if (params.profileId) {
|
|
681
|
+
const profile = profiles.find(({ id }) => id === params.profileId);
|
|
682
|
+
if (!profile) {
|
|
683
|
+
throw new Error(
|
|
684
|
+
`Unknown policy profile "${params.profileId}" for strategy "${manifest?.name}"`
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
if (!profileMatches(profile, params.universe, params.assetClass)) {
|
|
688
|
+
throw new Error(
|
|
689
|
+
`Policy profile "${params.profileId}" is not compatible with ${params.universe ?? "unknown"}:${params.assetClass ?? "unknown"}`
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
return profile;
|
|
693
|
+
}
|
|
694
|
+
const matching = profiles.filter(
|
|
695
|
+
(profile) => profileMatches(profile, params.universe, params.assetClass)
|
|
696
|
+
);
|
|
697
|
+
const defaultProfile = matching.find(
|
|
698
|
+
({ id }) => id === manifest?.defaultPolicyProfileId
|
|
699
|
+
);
|
|
700
|
+
return defaultProfile ?? matching[0];
|
|
701
|
+
};
|
|
702
|
+
var getStrategyProfileAiAdapter = (manifest, profileId) => manifest?.policyProfiles?.find(({ id }) => id === profileId)?.aiAdapter ?? manifest?.aiAdapter;
|
|
703
|
+
var getStrategyProfileMlAdapter = (manifest, profileId) => manifest?.policyProfiles?.find(({ id }) => id === profileId)?.mlAdapter ?? manifest?.mlAdapter;
|
|
704
|
+
|
|
705
|
+
// src/strategyAdapters/ml.ts
|
|
706
|
+
var defaultMlAdapter = {
|
|
707
|
+
normalizeStrategyConfig: (strategyConfig) => strategyConfig
|
|
708
|
+
};
|
|
709
|
+
var getStrategyMlAdapter = (strategy, profileId) => {
|
|
710
|
+
const strategyAdapter = getStrategyProfileMlAdapter(
|
|
711
|
+
getStrategyManifest(strategy),
|
|
712
|
+
profileId
|
|
713
|
+
);
|
|
714
|
+
if (!strategyAdapter) return defaultMlAdapter;
|
|
715
|
+
return {
|
|
716
|
+
...defaultMlAdapter,
|
|
717
|
+
...strategyAdapter
|
|
718
|
+
};
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
// src/mlPayload.ts
|
|
722
|
+
var normalizeStrategyConfig = (strategyConfig, strategyName, profileId) => {
|
|
723
|
+
return getStrategyMlAdapter(
|
|
724
|
+
strategyName,
|
|
725
|
+
profileId
|
|
726
|
+
).normalizeStrategyConfig?.(strategyConfig);
|
|
727
|
+
};
|
|
728
|
+
var buildMlPayload = (payload) => {
|
|
729
|
+
const strategyName = payload.signal?.strategy ?? payload.context?.strategyName;
|
|
730
|
+
const profileId = payload.signal?.policyProfileId;
|
|
731
|
+
const mlAdapter = getStrategyMlAdapter(strategyName, profileId);
|
|
732
|
+
const normalizedSignal = mlAdapter.normalizeSignal?.(payload.signal) ?? payload.signal;
|
|
733
|
+
const nextSignal = {
|
|
734
|
+
...normalizedSignal,
|
|
735
|
+
indicators: {
|
|
736
|
+
...normalizedSignal?.indicators ?? {}
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
const nextContext = payload.context ? {
|
|
740
|
+
...payload.context,
|
|
741
|
+
strategyConfig: normalizeStrategyConfig(
|
|
742
|
+
payload.context.strategyConfig,
|
|
743
|
+
strategyName,
|
|
744
|
+
profileId
|
|
745
|
+
)
|
|
746
|
+
} : void 0;
|
|
747
|
+
return {
|
|
748
|
+
signal: nextSignal,
|
|
749
|
+
context: nextContext
|
|
750
|
+
};
|
|
751
|
+
};
|
|
629
752
|
|
|
630
753
|
// src/tradejsConfig.ts
|
|
631
754
|
var import_fs = __toESM(require("fs"));
|
|
@@ -912,191 +1035,4663 @@ var loadTradejsConfig = async (cwd = getTradejsProjectCwd()) => {
|
|
|
912
1035
|
}
|
|
913
1036
|
};
|
|
914
1037
|
|
|
915
|
-
// src/
|
|
916
|
-
var
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
var
|
|
922
|
-
var
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
1038
|
+
// src/runtimeJournal.ts
|
|
1039
|
+
var import_node_crypto = require("crypto");
|
|
1040
|
+
var import_constants = require("@tradejs/core/constants");
|
|
1041
|
+
var import_time = require("@tradejs/core/time");
|
|
1042
|
+
var import_trade = require("@tradejs/core/trade");
|
|
1043
|
+
var import_logger2 = require("@tradejs/infra/logger");
|
|
1044
|
+
var import_redis = require("@tradejs/infra/redis");
|
|
1045
|
+
var now = () => Date.now();
|
|
1046
|
+
var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
|
|
1047
|
+
var calculateClosedPnl = ({
|
|
1048
|
+
direction,
|
|
1049
|
+
entryPrice,
|
|
1050
|
+
exitPrice,
|
|
1051
|
+
qty
|
|
1052
|
+
}) => {
|
|
1053
|
+
const pnl = direction === "LONG" ? (exitPrice - entryPrice) * qty : (entryPrice - exitPrice) * qty;
|
|
1054
|
+
return Number.isFinite(pnl) ? pnl : null;
|
|
1055
|
+
};
|
|
1056
|
+
var createRuntimeOrderId = (strategy) => {
|
|
1057
|
+
const prefix = (0, import_trade.createRuntimeOrderLinkPrefix)(strategy);
|
|
1058
|
+
if (prefix === "tjs-") {
|
|
1059
|
+
return `tjs-${(0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 24).toLowerCase()}`;
|
|
928
1060
|
}
|
|
929
|
-
return {
|
|
930
|
-
projectRoot,
|
|
931
|
-
state
|
|
932
|
-
};
|
|
1061
|
+
return `${prefix}${toRandomOrderSuffix()}`;
|
|
933
1062
|
};
|
|
934
|
-
var
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
1063
|
+
var recordRuntimeTradeOpen = async (params) => {
|
|
1064
|
+
const { userName } = params;
|
|
1065
|
+
if (!userName) {
|
|
1066
|
+
return null;
|
|
1067
|
+
}
|
|
1068
|
+
const record = {
|
|
1069
|
+
...params,
|
|
1070
|
+
entryCount: 1,
|
|
1071
|
+
lastEntryPrice: params.entryPrice,
|
|
1072
|
+
lastEntryQty: params.qty,
|
|
1073
|
+
lastEntryTimestamp: params.entryTimestamp,
|
|
1074
|
+
status: "active",
|
|
1075
|
+
currentPrice: params.entryPrice,
|
|
1076
|
+
currentPnl: 0,
|
|
1077
|
+
closedPnl: null,
|
|
1078
|
+
exitPrice: null,
|
|
1079
|
+
exitTimestamp: null,
|
|
1080
|
+
lastSyncedAt: now()
|
|
942
1081
|
};
|
|
1082
|
+
const dayKey = (0, import_time.getRuntimeStorageDayKey)(record.entryTimestamp);
|
|
1083
|
+
const runtimeScopeId = record.deploymentId ?? record.accountId;
|
|
1084
|
+
try {
|
|
1085
|
+
await Promise.all([
|
|
1086
|
+
(0, import_redis.setData)(import_redis.redisKeys.runtimeTrade(userName, record.orderId), record, {
|
|
1087
|
+
expire: 0
|
|
1088
|
+
}),
|
|
1089
|
+
(0, import_redis.setHashJsonField)(
|
|
1090
|
+
import_redis.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
1091
|
+
record.orderId,
|
|
1092
|
+
record,
|
|
1093
|
+
{ expire: 0 }
|
|
1094
|
+
),
|
|
1095
|
+
(0, import_redis.setData)(
|
|
1096
|
+
import_redis.redisKeys.runtimeActiveTrade(userName, record.symbol, runtimeScopeId),
|
|
1097
|
+
{ orderId: record.orderId },
|
|
1098
|
+
{ expire: 0 }
|
|
1099
|
+
)
|
|
1100
|
+
]);
|
|
1101
|
+
} catch (error) {
|
|
1102
|
+
import_logger2.logger.error(
|
|
1103
|
+
"runtime trade open journal failed: %s %s",
|
|
1104
|
+
record.symbol,
|
|
1105
|
+
error?.message || String(error)
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
return record;
|
|
943
1109
|
};
|
|
944
|
-
var
|
|
945
|
-
|
|
1110
|
+
var recordRuntimeTradeIncrease = async (params) => {
|
|
1111
|
+
const {
|
|
1112
|
+
userName,
|
|
1113
|
+
strategy,
|
|
1114
|
+
symbol,
|
|
1115
|
+
direction,
|
|
1116
|
+
resultingQty,
|
|
1117
|
+
resultingEntryPrice,
|
|
1118
|
+
addedQty,
|
|
1119
|
+
addedEntryPrice,
|
|
1120
|
+
entryTimestamp,
|
|
1121
|
+
fee,
|
|
1122
|
+
accountId,
|
|
1123
|
+
deploymentId
|
|
1124
|
+
} = params;
|
|
1125
|
+
if (!userName) {
|
|
946
1126
|
return null;
|
|
947
1127
|
}
|
|
948
|
-
const
|
|
949
|
-
|
|
950
|
-
|
|
1128
|
+
const existing = await getActiveRuntimeTrade({
|
|
1129
|
+
userName,
|
|
1130
|
+
symbol,
|
|
1131
|
+
accountId,
|
|
1132
|
+
deploymentId
|
|
1133
|
+
});
|
|
1134
|
+
if (!existing || existing.strategy !== strategy || existing.direction !== direction) {
|
|
1135
|
+
return null;
|
|
951
1136
|
}
|
|
952
|
-
const
|
|
953
|
-
|
|
954
|
-
|
|
1137
|
+
const addedFee = typeof fee === "number" && Number.isFinite(fee) ? fee : 0;
|
|
1138
|
+
const openFee = (existing.openFee ?? existing.fee ?? 0) + addedFee;
|
|
1139
|
+
const totalFee = (existing.totalFee ?? existing.fee ?? 0) + addedFee;
|
|
1140
|
+
const next = {
|
|
1141
|
+
...existing,
|
|
1142
|
+
qty: resultingQty,
|
|
1143
|
+
entryPrice: resultingEntryPrice,
|
|
1144
|
+
entryCount: Math.max(1, existing.entryCount ?? 1) + 1,
|
|
1145
|
+
lastEntryPrice: addedEntryPrice,
|
|
1146
|
+
lastEntryQty: addedQty,
|
|
1147
|
+
lastEntryTimestamp: entryTimestamp,
|
|
1148
|
+
currentPrice: resultingEntryPrice,
|
|
1149
|
+
currentPnl: 0,
|
|
1150
|
+
fee: openFee,
|
|
1151
|
+
openFee,
|
|
1152
|
+
totalFee,
|
|
1153
|
+
lastSyncedAt: now()
|
|
1154
|
+
};
|
|
1155
|
+
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
1156
|
+
try {
|
|
1157
|
+
await Promise.all([
|
|
1158
|
+
(0, import_redis.setData)(import_redis.redisKeys.runtimeTrade(userName, existing.orderId), next, {
|
|
1159
|
+
expire: 0
|
|
1160
|
+
}),
|
|
1161
|
+
(0, import_redis.setHashJsonField)(
|
|
1162
|
+
import_redis.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
1163
|
+
existing.orderId,
|
|
1164
|
+
next,
|
|
1165
|
+
{ expire: 0 }
|
|
1166
|
+
)
|
|
1167
|
+
]);
|
|
1168
|
+
} catch (error) {
|
|
1169
|
+
import_logger2.logger.error(
|
|
1170
|
+
"runtime trade increase journal failed: %s %s",
|
|
1171
|
+
symbol,
|
|
1172
|
+
error?.message || String(error)
|
|
1173
|
+
);
|
|
955
1174
|
}
|
|
956
|
-
return
|
|
1175
|
+
return next;
|
|
957
1176
|
};
|
|
958
|
-
var
|
|
959
|
-
const
|
|
960
|
-
|
|
961
|
-
|
|
1177
|
+
var getActiveRuntimeTrade = async (params) => {
|
|
1178
|
+
const { userName, symbol, accountId, deploymentId } = params;
|
|
1179
|
+
if (!userName) {
|
|
1180
|
+
return null;
|
|
1181
|
+
}
|
|
1182
|
+
const activeRef = await (0, import_redis.getData)(
|
|
1183
|
+
import_redis.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId),
|
|
1184
|
+
null
|
|
962
1185
|
);
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1186
|
+
const orderId = String(activeRef?.orderId || "").trim();
|
|
1187
|
+
if (!orderId) {
|
|
1188
|
+
return null;
|
|
1189
|
+
}
|
|
1190
|
+
const existing = await (0, import_redis.getData)(
|
|
1191
|
+
import_redis.redisKeys.runtimeTrade(userName, orderId),
|
|
1192
|
+
null
|
|
969
1193
|
);
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
if (!strategyName) {
|
|
976
|
-
import_logger2.logger.warn("Skip strategy entry without name from %s", source);
|
|
977
|
-
continue;
|
|
978
|
-
}
|
|
979
|
-
if (state.strategyCreators.has(strategyName)) {
|
|
980
|
-
import_logger2.logger.warn(
|
|
981
|
-
'Skip duplicate strategy "%s" from %s: already registered',
|
|
982
|
-
strategyName,
|
|
983
|
-
source
|
|
984
|
-
);
|
|
985
|
-
continue;
|
|
986
|
-
}
|
|
987
|
-
state.strategyCreators.set(strategyName, entry.creator);
|
|
988
|
-
state.strategyManifestsMap.set(strategyName, entry.manifest);
|
|
1194
|
+
if (!existing || existing.status !== "active") {
|
|
1195
|
+
await (0, import_redis.delKey)(
|
|
1196
|
+
import_redis.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
|
|
1197
|
+
);
|
|
1198
|
+
return null;
|
|
989
1199
|
}
|
|
1200
|
+
return existing;
|
|
990
1201
|
};
|
|
991
|
-
var
|
|
992
|
-
|
|
993
|
-
|
|
1202
|
+
var markRuntimeTradeClosed = async (params) => {
|
|
1203
|
+
const {
|
|
1204
|
+
userName,
|
|
1205
|
+
symbol,
|
|
1206
|
+
strategy,
|
|
1207
|
+
exitPrice,
|
|
1208
|
+
exitTimestamp,
|
|
1209
|
+
closedPnl,
|
|
1210
|
+
exitType,
|
|
1211
|
+
accountId,
|
|
1212
|
+
deploymentId
|
|
1213
|
+
} = params;
|
|
1214
|
+
if (!userName) {
|
|
1215
|
+
return null;
|
|
994
1216
|
}
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
(0, import_indicators.resetIndicatorRegistryCache)(projectRoot);
|
|
1004
|
-
state.pluginsLoadPromise = (async () => {
|
|
1005
|
-
const { strategyModules, indicatorModules } = await getConfiguredPluginModuleNames(projectRoot);
|
|
1006
|
-
const strategySet = new Set(strategyModules);
|
|
1007
|
-
const indicatorSet = new Set(indicatorModules);
|
|
1008
|
-
const pluginModuleNames = [
|
|
1009
|
-
.../* @__PURE__ */ new Set([...strategyModules, ...indicatorModules])
|
|
1010
|
-
];
|
|
1011
|
-
if (!pluginModuleNames.length) {
|
|
1012
|
-
return;
|
|
1013
|
-
}
|
|
1014
|
-
for (const moduleName of pluginModuleNames) {
|
|
1015
|
-
try {
|
|
1016
|
-
const resolvedModuleName = resolvePluginModuleSpecifier(
|
|
1017
|
-
moduleName,
|
|
1018
|
-
projectRoot
|
|
1019
|
-
);
|
|
1020
|
-
const moduleExport = await importStrategyPluginModule(
|
|
1021
|
-
resolvedModuleName,
|
|
1022
|
-
projectRoot
|
|
1023
|
-
);
|
|
1024
|
-
if (strategySet.has(moduleName)) {
|
|
1025
|
-
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
1026
|
-
if (!pluginDefinition) {
|
|
1027
|
-
import_logger2.logger.warn(
|
|
1028
|
-
'Skip strategy plugin "%s": export { strategyEntries } is missing',
|
|
1029
|
-
moduleName
|
|
1030
|
-
);
|
|
1031
|
-
} else {
|
|
1032
|
-
registerEntries(
|
|
1033
|
-
pluginDefinition.strategyEntries,
|
|
1034
|
-
moduleName,
|
|
1035
|
-
state
|
|
1036
|
-
);
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
if (indicatorSet.has(moduleName)) {
|
|
1040
|
-
const indicatorPluginDefinition = extractIndicatorPluginDefinition(moduleExport);
|
|
1041
|
-
if (!indicatorPluginDefinition) {
|
|
1042
|
-
import_logger2.logger.warn(
|
|
1043
|
-
'Skip indicator plugin "%s": export { indicatorEntries } is missing',
|
|
1044
|
-
moduleName
|
|
1045
|
-
);
|
|
1046
|
-
} else {
|
|
1047
|
-
(0, import_indicators.registerIndicatorEntries)(
|
|
1048
|
-
indicatorPluginDefinition.indicatorEntries,
|
|
1049
|
-
moduleName,
|
|
1050
|
-
projectRoot
|
|
1051
|
-
);
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
if (!strategySet.has(moduleName) && !indicatorSet.has(moduleName)) {
|
|
1055
|
-
import_logger2.logger.warn(
|
|
1056
|
-
'Skip plugin "%s": no strategy/indicator sections requested in config',
|
|
1057
|
-
moduleName
|
|
1058
|
-
);
|
|
1059
|
-
}
|
|
1060
|
-
} catch (error) {
|
|
1061
|
-
import_logger2.logger.warn(
|
|
1062
|
-
'Failed to load plugin "%s": %s',
|
|
1063
|
-
moduleName,
|
|
1064
|
-
String(error)
|
|
1065
|
-
);
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
})();
|
|
1217
|
+
const existing = await getActiveRuntimeTrade({
|
|
1218
|
+
userName,
|
|
1219
|
+
symbol,
|
|
1220
|
+
accountId,
|
|
1221
|
+
deploymentId
|
|
1222
|
+
});
|
|
1223
|
+
if (!existing) {
|
|
1224
|
+
return null;
|
|
1069
1225
|
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
if (!name) {
|
|
1074
|
-
return void 0;
|
|
1226
|
+
const orderId = existing.orderId;
|
|
1227
|
+
if (strategy && existing.strategy !== strategy) {
|
|
1228
|
+
return null;
|
|
1075
1229
|
}
|
|
1076
|
-
const
|
|
1077
|
-
|
|
1230
|
+
const resolvedExitPrice = typeof exitPrice === "number" && Number.isFinite(exitPrice) ? exitPrice : existing.currentPrice ?? existing.entryPrice;
|
|
1231
|
+
const resolvedClosedPnl = typeof closedPnl === "number" && Number.isFinite(closedPnl) ? closedPnl : typeof resolvedExitPrice === "number" && Number.isFinite(resolvedExitPrice) ? calculateClosedPnl({
|
|
1232
|
+
direction: existing.direction,
|
|
1233
|
+
entryPrice: existing.entryPrice,
|
|
1234
|
+
exitPrice: resolvedExitPrice,
|
|
1235
|
+
qty: existing.qty
|
|
1236
|
+
}) : existing.closedPnl ?? existing.currentPnl ?? null;
|
|
1237
|
+
const next = {
|
|
1238
|
+
...existing,
|
|
1239
|
+
status: "closed",
|
|
1240
|
+
currentPrice: resolvedExitPrice,
|
|
1241
|
+
currentPnl: resolvedClosedPnl,
|
|
1242
|
+
closedPnl: resolvedClosedPnl,
|
|
1243
|
+
exitPrice: resolvedExitPrice,
|
|
1244
|
+
exitTimestamp: typeof exitTimestamp === "number" && Number.isFinite(exitTimestamp) ? exitTimestamp : now(),
|
|
1245
|
+
exitType: exitType ?? existing.exitType ?? null,
|
|
1246
|
+
lastSyncedAt: now()
|
|
1247
|
+
};
|
|
1248
|
+
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
1249
|
+
const closeDayKey = (0, import_time.getRuntimeStorageDayKey)(next.exitTimestamp);
|
|
1250
|
+
try {
|
|
1251
|
+
await Promise.all([
|
|
1252
|
+
(0, import_redis.setData)(import_redis.redisKeys.runtimeTrade(userName, orderId), next, {
|
|
1253
|
+
expire: import_constants.TTL_1M
|
|
1254
|
+
}),
|
|
1255
|
+
(0, import_redis.setHashJsonField)(
|
|
1256
|
+
import_redis.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
1257
|
+
orderId,
|
|
1258
|
+
next,
|
|
1259
|
+
{ expire: import_constants.TTL_1M }
|
|
1260
|
+
),
|
|
1261
|
+
(0, import_redis.setHashJsonField)(
|
|
1262
|
+
import_redis.redisKeys.runtimeClosedTradeBucket(userName, closeDayKey),
|
|
1263
|
+
orderId,
|
|
1264
|
+
next,
|
|
1265
|
+
{ expire: import_constants.TTL_1M }
|
|
1266
|
+
),
|
|
1267
|
+
(0, import_redis.delKey)(
|
|
1268
|
+
import_redis.redisKeys.runtimeActiveTrade(
|
|
1269
|
+
userName,
|
|
1270
|
+
symbol,
|
|
1271
|
+
deploymentId ?? accountId
|
|
1272
|
+
)
|
|
1273
|
+
)
|
|
1274
|
+
]);
|
|
1275
|
+
} catch (error) {
|
|
1276
|
+
import_logger2.logger.error(
|
|
1277
|
+
"runtime trade close journal failed: %s %s",
|
|
1278
|
+
symbol,
|
|
1279
|
+
error?.message || String(error)
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1282
|
+
return next;
|
|
1078
1283
|
};
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1284
|
+
|
|
1285
|
+
// src/strategyHelpers/marketContextStages.ts
|
|
1286
|
+
var import_logger7 = require("@tradejs/infra/logger");
|
|
1287
|
+
|
|
1288
|
+
// src/strategyHelpers/binanceMarketContext.ts
|
|
1289
|
+
var import_marketContext = require("@tradejs/infra/timescale/marketContext");
|
|
1290
|
+
var import_logger3 = require("@tradejs/infra/logger");
|
|
1291
|
+
var import_strategies = require("@tradejs/core/strategies");
|
|
1292
|
+
|
|
1293
|
+
// src/binanceBreadthUniverses.ts
|
|
1294
|
+
var import_node_crypto2 = require("crypto");
|
|
1295
|
+
|
|
1296
|
+
// src/config/binanceBreadthUniverses.json
|
|
1297
|
+
var binanceBreadthUniverses_default = {
|
|
1298
|
+
schemaVersion: 1,
|
|
1299
|
+
updatedAt: "2026-07-24T15:28:27.234Z",
|
|
1300
|
+
source: "binance_spot_usdt_turnover24h",
|
|
1301
|
+
fingerprint: "43df006e5510bb99",
|
|
1302
|
+
universes: {
|
|
1303
|
+
top5: {
|
|
1304
|
+
size: 5,
|
|
1305
|
+
fingerprint: "ba5b73456b18",
|
|
1306
|
+
symbols: ["ETHUSDT", "USD1USDT", "SOLUSDT", "OPNUSDT", "VANAUSDT"]
|
|
1087
1307
|
},
|
|
1088
|
-
|
|
1089
|
-
|
|
1308
|
+
top10: {
|
|
1309
|
+
size: 10,
|
|
1310
|
+
fingerprint: "370eb5af9778",
|
|
1311
|
+
symbols: [
|
|
1312
|
+
"ETHUSDT",
|
|
1313
|
+
"USD1USDT",
|
|
1314
|
+
"SOLUSDT",
|
|
1315
|
+
"OPNUSDT",
|
|
1316
|
+
"VANAUSDT",
|
|
1317
|
+
"DEXEUSDT",
|
|
1318
|
+
"BANKUSDT",
|
|
1319
|
+
"ZECUSDT",
|
|
1320
|
+
"XRPUSDT",
|
|
1321
|
+
"RLUSDUSDT"
|
|
1322
|
+
]
|
|
1090
1323
|
},
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1324
|
+
top30: {
|
|
1325
|
+
size: 30,
|
|
1326
|
+
fingerprint: "685a7e7f5fce",
|
|
1327
|
+
symbols: [
|
|
1328
|
+
"ETHUSDT",
|
|
1329
|
+
"USD1USDT",
|
|
1330
|
+
"SOLUSDT",
|
|
1331
|
+
"OPNUSDT",
|
|
1332
|
+
"VANAUSDT",
|
|
1333
|
+
"DEXEUSDT",
|
|
1334
|
+
"BANKUSDT",
|
|
1335
|
+
"ZECUSDT",
|
|
1336
|
+
"XRPUSDT",
|
|
1337
|
+
"RLUSDUSDT",
|
|
1338
|
+
"BNBUSDT",
|
|
1339
|
+
"DOGEUSDT",
|
|
1340
|
+
"REUSDT",
|
|
1341
|
+
"RIFUSDT",
|
|
1342
|
+
"ZAMAUSDT",
|
|
1343
|
+
"TRXUSDT",
|
|
1344
|
+
"EURUSDT",
|
|
1345
|
+
"AEROUSDT",
|
|
1346
|
+
"UUSDT",
|
|
1347
|
+
"SUIUSDT",
|
|
1348
|
+
"XAUTUSDT",
|
|
1349
|
+
"NEARUSDT",
|
|
1350
|
+
"LTCUSDT",
|
|
1351
|
+
"SPCXBUSDT",
|
|
1352
|
+
"LAUSDT",
|
|
1353
|
+
"AVAXUSDT",
|
|
1354
|
+
"WLDUSDT",
|
|
1355
|
+
"ONDOUSDT",
|
|
1356
|
+
"XLMUSDT",
|
|
1357
|
+
"UNIUSDT"
|
|
1358
|
+
]
|
|
1359
|
+
},
|
|
1360
|
+
top50: {
|
|
1361
|
+
size: 50,
|
|
1362
|
+
fingerprint: "cb9725e24141",
|
|
1363
|
+
symbols: [
|
|
1364
|
+
"ETHUSDT",
|
|
1365
|
+
"USD1USDT",
|
|
1366
|
+
"SOLUSDT",
|
|
1367
|
+
"OPNUSDT",
|
|
1368
|
+
"VANAUSDT",
|
|
1369
|
+
"DEXEUSDT",
|
|
1370
|
+
"BANKUSDT",
|
|
1371
|
+
"ZECUSDT",
|
|
1372
|
+
"XRPUSDT",
|
|
1373
|
+
"RLUSDUSDT",
|
|
1374
|
+
"BNBUSDT",
|
|
1375
|
+
"DOGEUSDT",
|
|
1376
|
+
"REUSDT",
|
|
1377
|
+
"RIFUSDT",
|
|
1378
|
+
"ZAMAUSDT",
|
|
1379
|
+
"TRXUSDT",
|
|
1380
|
+
"EURUSDT",
|
|
1381
|
+
"AEROUSDT",
|
|
1382
|
+
"UUSDT",
|
|
1383
|
+
"SUIUSDT",
|
|
1384
|
+
"XAUTUSDT",
|
|
1385
|
+
"NEARUSDT",
|
|
1386
|
+
"LTCUSDT",
|
|
1387
|
+
"SPCXBUSDT",
|
|
1388
|
+
"LAUSDT",
|
|
1389
|
+
"AVAXUSDT",
|
|
1390
|
+
"WLDUSDT",
|
|
1391
|
+
"ONDOUSDT",
|
|
1392
|
+
"XLMUSDT",
|
|
1393
|
+
"UNIUSDT",
|
|
1394
|
+
"ADAUSDT",
|
|
1395
|
+
"SNDKBUSDT",
|
|
1396
|
+
"KAITOUSDT",
|
|
1397
|
+
"UTKUSDT",
|
|
1398
|
+
"PEPEUSDT",
|
|
1399
|
+
"WLFIUSDT",
|
|
1400
|
+
"PAXGUSDT",
|
|
1401
|
+
"ENAUSDT",
|
|
1402
|
+
"TAOUSDT",
|
|
1403
|
+
"AAVEUSDT",
|
|
1404
|
+
"CRCLBUSDT",
|
|
1405
|
+
"LINKUSDT",
|
|
1406
|
+
"TONUSDT",
|
|
1407
|
+
"ALLOUSDT",
|
|
1408
|
+
"PUMPUSDT",
|
|
1409
|
+
"INJUSDT",
|
|
1410
|
+
"HOMEUSDT",
|
|
1411
|
+
"MUBUSDT",
|
|
1412
|
+
"TSLABUSDT",
|
|
1413
|
+
"GRAMUSDT"
|
|
1414
|
+
]
|
|
1415
|
+
},
|
|
1416
|
+
top100: {
|
|
1417
|
+
size: 100,
|
|
1418
|
+
fingerprint: "f397883a81cf",
|
|
1419
|
+
symbols: [
|
|
1420
|
+
"ETHUSDT",
|
|
1421
|
+
"USD1USDT",
|
|
1422
|
+
"SOLUSDT",
|
|
1423
|
+
"OPNUSDT",
|
|
1424
|
+
"VANAUSDT",
|
|
1425
|
+
"DEXEUSDT",
|
|
1426
|
+
"BANKUSDT",
|
|
1427
|
+
"ZECUSDT",
|
|
1428
|
+
"XRPUSDT",
|
|
1429
|
+
"RLUSDUSDT",
|
|
1430
|
+
"BNBUSDT",
|
|
1431
|
+
"DOGEUSDT",
|
|
1432
|
+
"REUSDT",
|
|
1433
|
+
"RIFUSDT",
|
|
1434
|
+
"ZAMAUSDT",
|
|
1435
|
+
"TRXUSDT",
|
|
1436
|
+
"EURUSDT",
|
|
1437
|
+
"AEROUSDT",
|
|
1438
|
+
"UUSDT",
|
|
1439
|
+
"SUIUSDT",
|
|
1440
|
+
"XAUTUSDT",
|
|
1441
|
+
"NEARUSDT",
|
|
1442
|
+
"LTCUSDT",
|
|
1443
|
+
"SPCXBUSDT",
|
|
1444
|
+
"LAUSDT",
|
|
1445
|
+
"AVAXUSDT",
|
|
1446
|
+
"WLDUSDT",
|
|
1447
|
+
"ONDOUSDT",
|
|
1448
|
+
"XLMUSDT",
|
|
1449
|
+
"UNIUSDT",
|
|
1450
|
+
"ADAUSDT",
|
|
1451
|
+
"SNDKBUSDT",
|
|
1452
|
+
"KAITOUSDT",
|
|
1453
|
+
"UTKUSDT",
|
|
1454
|
+
"PEPEUSDT",
|
|
1455
|
+
"WLFIUSDT",
|
|
1456
|
+
"PAXGUSDT",
|
|
1457
|
+
"ENAUSDT",
|
|
1458
|
+
"TAOUSDT",
|
|
1459
|
+
"AAVEUSDT",
|
|
1460
|
+
"CRCLBUSDT",
|
|
1461
|
+
"LINKUSDT",
|
|
1462
|
+
"TONUSDT",
|
|
1463
|
+
"ALLOUSDT",
|
|
1464
|
+
"PUMPUSDT",
|
|
1465
|
+
"INJUSDT",
|
|
1466
|
+
"HOMEUSDT",
|
|
1467
|
+
"MUBUSDT",
|
|
1468
|
+
"TSLABUSDT",
|
|
1469
|
+
"GRAMUSDT",
|
|
1470
|
+
"\u5E01\u5B89\u4EBA\u751FUSDT",
|
|
1471
|
+
"SYNUSDT",
|
|
1472
|
+
"TRUMPUSDT",
|
|
1473
|
+
"PROMUSDT",
|
|
1474
|
+
"XPLUSDT",
|
|
1475
|
+
"HBARUSDT",
|
|
1476
|
+
"ERAUSDT",
|
|
1477
|
+
"BCHUSDT",
|
|
1478
|
+
"SOXLBUSDT",
|
|
1479
|
+
"DOTUSDT",
|
|
1480
|
+
"EPICUSDT",
|
|
1481
|
+
"KITEUSDT",
|
|
1482
|
+
"NFPUSDT",
|
|
1483
|
+
"KGSTUSDT",
|
|
1484
|
+
"FILUSDT",
|
|
1485
|
+
"FETUSDT",
|
|
1486
|
+
"KORUBUSDT",
|
|
1487
|
+
"ARBUSDT",
|
|
1488
|
+
"STXUSDT",
|
|
1489
|
+
"HEIUSDT",
|
|
1490
|
+
"BARDUSDT",
|
|
1491
|
+
"DASHUSDT",
|
|
1492
|
+
"ASTERUSDT",
|
|
1493
|
+
"VANRYUSDT",
|
|
1494
|
+
"INTCBUSDT",
|
|
1495
|
+
"ETCUSDT",
|
|
1496
|
+
"EIGENUSDT",
|
|
1497
|
+
"PENGUUSDT",
|
|
1498
|
+
"LRCUSDT",
|
|
1499
|
+
"VIRTUALUSDT",
|
|
1500
|
+
"POLUSDT",
|
|
1501
|
+
"ICPUSDT",
|
|
1502
|
+
"ZBTUSDT",
|
|
1503
|
+
"SKHYBUSDT",
|
|
1504
|
+
"SHIBUSDT",
|
|
1505
|
+
"LDOUSDT",
|
|
1506
|
+
"APTUSDT",
|
|
1507
|
+
"JTOUSDT",
|
|
1508
|
+
"NOMUSDT",
|
|
1509
|
+
"ZROUSDT",
|
|
1510
|
+
"OPUSDT",
|
|
1511
|
+
"BONKUSDT",
|
|
1512
|
+
"SKLUSDT",
|
|
1513
|
+
"HEMIUSDT",
|
|
1514
|
+
"TNSRUSDT",
|
|
1515
|
+
"INTWBUSDT",
|
|
1516
|
+
"BIOUSDT",
|
|
1517
|
+
"TIAUSDT",
|
|
1518
|
+
"ORDIUSDT",
|
|
1519
|
+
"XUSDUSDT"
|
|
1520
|
+
]
|
|
1521
|
+
}
|
|
1095
1522
|
}
|
|
1096
|
-
|
|
1523
|
+
};
|
|
1097
1524
|
|
|
1098
|
-
// src/
|
|
1099
|
-
var
|
|
1525
|
+
// src/binanceBreadthUniverses.ts
|
|
1526
|
+
var BINANCE_BREADTH_UNIVERSE_KEYS = [
|
|
1527
|
+
"top5",
|
|
1528
|
+
"top10",
|
|
1529
|
+
"top30",
|
|
1530
|
+
"top50",
|
|
1531
|
+
"top100"
|
|
1532
|
+
];
|
|
1533
|
+
var fingerprint = (value, length) => (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, length);
|
|
1534
|
+
var normalizeSymbols = (symbols) => symbols.map((symbol) => symbol.trim().toUpperCase()).filter(Boolean);
|
|
1535
|
+
var buildBinanceBreadthUniverseSnapshot = ({
|
|
1536
|
+
rankedSymbols,
|
|
1537
|
+
updatedAt = (/* @__PURE__ */ new Date()).toISOString()
|
|
1538
|
+
}) => {
|
|
1539
|
+
const uniqueSymbols = [...new Set(normalizeSymbols(rankedSymbols))];
|
|
1540
|
+
if (uniqueSymbols.length < 100) {
|
|
1541
|
+
throw new Error(
|
|
1542
|
+
`Binance breadth snapshot requires at least 100 symbols, got ${uniqueSymbols.length}`
|
|
1543
|
+
);
|
|
1544
|
+
}
|
|
1545
|
+
const universes = Object.fromEntries(
|
|
1546
|
+
BINANCE_BREADTH_UNIVERSE_KEYS.map((key) => {
|
|
1547
|
+
const size = Number(key.slice(3));
|
|
1548
|
+
const symbols = uniqueSymbols.slice(0, size);
|
|
1549
|
+
return [
|
|
1550
|
+
key,
|
|
1551
|
+
{
|
|
1552
|
+
size,
|
|
1553
|
+
fingerprint: fingerprint(symbols, 12),
|
|
1554
|
+
symbols
|
|
1555
|
+
}
|
|
1556
|
+
];
|
|
1557
|
+
})
|
|
1558
|
+
);
|
|
1559
|
+
return {
|
|
1560
|
+
schemaVersion: 1,
|
|
1561
|
+
updatedAt,
|
|
1562
|
+
source: "binance_spot_usdt_turnover24h",
|
|
1563
|
+
fingerprint: fingerprint(universes, 16),
|
|
1564
|
+
universes
|
|
1565
|
+
};
|
|
1566
|
+
};
|
|
1567
|
+
var validateSnapshot = (value) => {
|
|
1568
|
+
const rebuilt = buildBinanceBreadthUniverseSnapshot({
|
|
1569
|
+
rankedSymbols: value.universes.top100.symbols,
|
|
1570
|
+
updatedAt: value.updatedAt
|
|
1571
|
+
});
|
|
1572
|
+
if (value.schemaVersion !== 1 || value.source !== rebuilt.source || value.fingerprint !== rebuilt.fingerprint) {
|
|
1573
|
+
throw new Error("Invalid Binance breadth universe snapshot fingerprint");
|
|
1574
|
+
}
|
|
1575
|
+
for (const key of BINANCE_BREADTH_UNIVERSE_KEYS) {
|
|
1576
|
+
const actual = value.universes[key];
|
|
1577
|
+
const expected = rebuilt.universes[key];
|
|
1578
|
+
if (actual.size !== expected.size || actual.fingerprint !== expected.fingerprint || JSON.stringify(actual.symbols) !== JSON.stringify(expected.symbols)) {
|
|
1579
|
+
throw new Error(`Invalid Binance breadth universe snapshot: ${key}`);
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
return value;
|
|
1583
|
+
};
|
|
1584
|
+
var snapshot = validateSnapshot(binanceBreadthUniverses_default);
|
|
1585
|
+
var getBinanceBreadthUniverses = () => BINANCE_BREADTH_UNIVERSE_KEYS.map((key) => {
|
|
1586
|
+
const definition = snapshot.universes[key];
|
|
1587
|
+
return {
|
|
1588
|
+
key,
|
|
1589
|
+
size: definition.size,
|
|
1590
|
+
fingerprint: definition.fingerprint,
|
|
1591
|
+
universe: `binance_${key}_usdt_${definition.fingerprint}`,
|
|
1592
|
+
symbols: [...definition.symbols]
|
|
1593
|
+
};
|
|
1594
|
+
});
|
|
1595
|
+
|
|
1596
|
+
// src/strategyHelpers/marketContextErrors.ts
|
|
1597
|
+
var MARKET_CONTEXT_CANCELLATION_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
1598
|
+
"AbortError",
|
|
1599
|
+
"TimescaleQueryTimeoutError"
|
|
1600
|
+
]);
|
|
1601
|
+
var isMarketContextCancellationError = (error, abortSignal) => abortSignal?.aborted === true || error instanceof Error && MARKET_CONTEXT_CANCELLATION_ERROR_NAMES.has(error.name);
|
|
1602
|
+
|
|
1603
|
+
// src/strategyHelpers/binanceMarketContext.ts
|
|
1604
|
+
var DEFAULT_MAX_AGE_BY_INTERVAL = {
|
|
1605
|
+
"1m": 3 * 6e4,
|
|
1606
|
+
"5m": 10 * 6e4,
|
|
1607
|
+
"15m": 30 * 6e4,
|
|
1608
|
+
"1h": 2 * 60 * 6e4
|
|
1609
|
+
};
|
|
1610
|
+
var binanceMarketContextUnavailable = false;
|
|
1611
|
+
var referenceRowsCache = /* @__PURE__ */ new Map();
|
|
1612
|
+
var breadthCache = /* @__PURE__ */ new Map();
|
|
1613
|
+
var parseEnabledFlag = (value, env) => {
|
|
1614
|
+
const normalized = String(value ?? "").trim().toLowerCase();
|
|
1615
|
+
if (!normalized)
|
|
1616
|
+
return env === "BACKTEST" || env === "CRON" || env === "PARITY";
|
|
1617
|
+
if (["1", "true", "yes", "on"].includes(normalized)) return true;
|
|
1618
|
+
if (["0", "false", "no", "off"].includes(normalized)) return false;
|
|
1619
|
+
if (normalized === "backtest") return env === "BACKTEST";
|
|
1620
|
+
if (normalized === "live") return env !== "BACKTEST";
|
|
1621
|
+
return false;
|
|
1622
|
+
};
|
|
1623
|
+
var toFiniteNumberOrNull = (value) => {
|
|
1624
|
+
const numeric = typeof value === "number" ? value : Number(value);
|
|
1625
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
1626
|
+
};
|
|
1627
|
+
var signalIntervalToMarketInterval = (value) => {
|
|
1628
|
+
const normalized = String(value).trim().toLowerCase();
|
|
1629
|
+
if (normalized === "1" || normalized === "1m") return "1m";
|
|
1630
|
+
if (normalized === "5" || normalized === "5m") return "5m";
|
|
1631
|
+
if (normalized === "60" || normalized === "1h") return "1h";
|
|
1632
|
+
return "15m";
|
|
1633
|
+
};
|
|
1634
|
+
var resolveMarketInterval = (signal, override) => override ?? signalIntervalToMarketInterval(signal.interval);
|
|
1635
|
+
var getReferenceSymbols = () => {
|
|
1636
|
+
const symbols = (process.env.BINANCE_MARKET_CONTEXT_REFERENCE_SYMBOLS || "BTCUSDT,ETHUSDT").split(",").map((item) => item.trim().toUpperCase()).filter(Boolean);
|
|
1637
|
+
return symbols.length ? [...new Set(symbols)] : ["BTCUSDT", "ETHUSDT"];
|
|
1638
|
+
};
|
|
1639
|
+
var resolvePrimaryReferenceSymbol = (signalSymbol) => {
|
|
1640
|
+
const symbol = signalSymbol.trim().toUpperCase();
|
|
1641
|
+
const referenceSymbols = getReferenceSymbols();
|
|
1642
|
+
return referenceSymbols.includes(symbol) ? symbol : referenceSymbols[0];
|
|
1643
|
+
};
|
|
1644
|
+
var hasBaseContext = (signal) => Boolean(
|
|
1645
|
+
signal.additionalIndicators?.baseContext && typeof signal.additionalIndicators.baseContext === "object" && !Array.isArray(signal.additionalIndicators.baseContext)
|
|
1646
|
+
);
|
|
1647
|
+
var isBinanceMarketContextEnabled = (env) => parseEnabledFlag(process.env.BINANCE_MARKET_CONTEXT_ENABLED, env);
|
|
1648
|
+
var toTradeFlowContext = (row, interval) => row ? {
|
|
1649
|
+
source: "binance_agg_trades",
|
|
1650
|
+
interval,
|
|
1651
|
+
asOfTs: row.ts.getTime(),
|
|
1652
|
+
ageMs: row.ageMs,
|
|
1653
|
+
stale: row.stale,
|
|
1654
|
+
trades: toFiniteNumberOrNull(row.trades),
|
|
1655
|
+
buyPressurePct: toFiniteNumberOrNull(row.buyPressurePct),
|
|
1656
|
+
buyBaseVolume: toFiniteNumberOrNull(row.buyBaseVolume),
|
|
1657
|
+
sellBaseVolume: toFiniteNumberOrNull(row.sellBaseVolume),
|
|
1658
|
+
buyQuoteVolume: toFiniteNumberOrNull(row.buyQuoteVolume),
|
|
1659
|
+
sellQuoteVolume: toFiniteNumberOrNull(row.sellQuoteVolume),
|
|
1660
|
+
netBaseDelta: toFiniteNumberOrNull(row.netBaseDelta),
|
|
1661
|
+
netQuoteDelta: toFiniteNumberOrNull(row.netQuoteDelta)
|
|
1662
|
+
} : null;
|
|
1663
|
+
var getCachedReferenceRows = ({
|
|
1664
|
+
referenceSymbols,
|
|
1665
|
+
interval,
|
|
1666
|
+
timestamp,
|
|
1667
|
+
maxAgeMs,
|
|
1668
|
+
abortSignal
|
|
1669
|
+
}) => {
|
|
1670
|
+
const key = `${referenceSymbols.join(",")}:${interval}:${timestamp}:${maxAgeMs}`;
|
|
1671
|
+
const cached = referenceRowsCache.get(key);
|
|
1672
|
+
if (cached) return cached;
|
|
1673
|
+
const promise = Promise.all(
|
|
1674
|
+
referenceSymbols.map(async (symbol) => {
|
|
1675
|
+
const tradeFlow = await (0, import_marketContext.getLatestMarketTradeFlow)({
|
|
1676
|
+
symbol,
|
|
1677
|
+
interval,
|
|
1678
|
+
atMs: timestamp,
|
|
1679
|
+
maxAgeMs,
|
|
1680
|
+
...abortSignal ? { signal: abortSignal } : {}
|
|
1681
|
+
});
|
|
1682
|
+
return {
|
|
1683
|
+
symbol,
|
|
1684
|
+
tradeFlow: toTradeFlowContext(tradeFlow, interval)
|
|
1685
|
+
};
|
|
1686
|
+
})
|
|
1687
|
+
);
|
|
1688
|
+
referenceRowsCache.set(key, promise);
|
|
1689
|
+
void promise.catch(() => referenceRowsCache.delete(key));
|
|
1690
|
+
return promise;
|
|
1691
|
+
};
|
|
1692
|
+
var getCachedBreadth = ({
|
|
1693
|
+
breadthUniverse,
|
|
1694
|
+
interval,
|
|
1695
|
+
timestamp,
|
|
1696
|
+
maxAgeMs,
|
|
1697
|
+
abortSignal
|
|
1698
|
+
}) => {
|
|
1699
|
+
const key = `${breadthUniverse}:${interval}:${timestamp}:${maxAgeMs}`;
|
|
1700
|
+
const cached = breadthCache.get(key);
|
|
1701
|
+
if (cached) return cached;
|
|
1702
|
+
const promise = (0, import_marketContext.getLatestMarketBreadth)({
|
|
1703
|
+
universe: breadthUniverse,
|
|
1704
|
+
interval,
|
|
1705
|
+
atMs: timestamp,
|
|
1706
|
+
maxAgeMs,
|
|
1707
|
+
...abortSignal ? { signal: abortSignal } : {}
|
|
1708
|
+
});
|
|
1709
|
+
breadthCache.set(key, promise);
|
|
1710
|
+
void promise.catch(() => breadthCache.delete(key));
|
|
1711
|
+
return promise;
|
|
1712
|
+
};
|
|
1713
|
+
var toMarketBreadthContext = (breadth, interval) => ({
|
|
1714
|
+
source: "binance_klines",
|
|
1715
|
+
universe: breadth.universe,
|
|
1716
|
+
interval,
|
|
1717
|
+
asOfTs: breadth.ts.getTime(),
|
|
1718
|
+
ageMs: breadth.ageMs,
|
|
1719
|
+
stale: breadth.stale,
|
|
1720
|
+
symbolsCount: toFiniteNumberOrNull(breadth.symbolsCount),
|
|
1721
|
+
advancers: toFiniteNumberOrNull(breadth.advancers),
|
|
1722
|
+
decliners: toFiniteNumberOrNull(breadth.decliners),
|
|
1723
|
+
unchanged: toFiniteNumberOrNull(breadth.unchanged),
|
|
1724
|
+
advanceDeclineRatio: toFiniteNumberOrNull(breadth.advanceDeclineRatio),
|
|
1725
|
+
pctAboveMa20: toFiniteNumberOrNull(breadth.pctAboveMa20),
|
|
1726
|
+
pctAboveMa50: toFiniteNumberOrNull(breadth.pctAboveMa50),
|
|
1727
|
+
equalWeightedReturn: toFiniteNumberOrNull(breadth.equalWeightedReturn),
|
|
1728
|
+
volumeWeightedReturn: toFiniteNumberOrNull(breadth.volumeWeightedReturn),
|
|
1729
|
+
dispersion: toFiniteNumberOrNull(breadth.dispersion)
|
|
1730
|
+
});
|
|
1731
|
+
var enrichSignalWithBinanceMarketContext = async (params) => {
|
|
1732
|
+
const {
|
|
1733
|
+
signal,
|
|
1734
|
+
env,
|
|
1735
|
+
enabled = isBinanceMarketContextEnabled(env),
|
|
1736
|
+
interval = resolveMarketInterval(signal, params.interval),
|
|
1737
|
+
maxAgeMs = DEFAULT_MAX_AGE_BY_INTERVAL[interval]
|
|
1738
|
+
} = params;
|
|
1739
|
+
if (signal.universe === "tradfi" || !enabled || binanceMarketContextUnavailable || !hasBaseContext(signal)) {
|
|
1740
|
+
return false;
|
|
1741
|
+
}
|
|
1742
|
+
try {
|
|
1743
|
+
const referenceSymbols = getReferenceSymbols();
|
|
1744
|
+
const primaryReferenceSymbol = resolvePrimaryReferenceSymbol(signal.symbol);
|
|
1745
|
+
const breadthUniverses = params.breadthUniverse ? [
|
|
1746
|
+
{
|
|
1747
|
+
key: "top30",
|
|
1748
|
+
universe: params.breadthUniverse
|
|
1749
|
+
}
|
|
1750
|
+
] : getBinanceBreadthUniverses();
|
|
1751
|
+
const [referenceRows, breadthRows] = await Promise.all([
|
|
1752
|
+
getCachedReferenceRows({
|
|
1753
|
+
referenceSymbols,
|
|
1754
|
+
interval,
|
|
1755
|
+
timestamp: signal.timestamp,
|
|
1756
|
+
maxAgeMs,
|
|
1757
|
+
abortSignal: params.abortSignal
|
|
1758
|
+
}),
|
|
1759
|
+
Promise.all(
|
|
1760
|
+
breadthUniverses.map(async ({ key, universe }) => ({
|
|
1761
|
+
key,
|
|
1762
|
+
breadth: await getCachedBreadth({
|
|
1763
|
+
breadthUniverse: universe,
|
|
1764
|
+
interval,
|
|
1765
|
+
timestamp: signal.timestamp,
|
|
1766
|
+
maxAgeMs,
|
|
1767
|
+
abortSignal: params.abortSignal
|
|
1768
|
+
})
|
|
1769
|
+
}))
|
|
1770
|
+
)
|
|
1771
|
+
]);
|
|
1772
|
+
const availableBreadths = breadthRows.filter(
|
|
1773
|
+
(row) => row.breadth != null
|
|
1774
|
+
);
|
|
1775
|
+
const marketBreadths = Object.fromEntries(
|
|
1776
|
+
availableBreadths.map(({ key, breadth }) => [
|
|
1777
|
+
key,
|
|
1778
|
+
toMarketBreadthContext(breadth, interval)
|
|
1779
|
+
])
|
|
1780
|
+
);
|
|
1781
|
+
const primaryBreadth = availableBreadths.find(
|
|
1782
|
+
({ key }) => key === "top30"
|
|
1783
|
+
)?.breadth;
|
|
1784
|
+
const tradeFlowBySymbol = Object.fromEntries(
|
|
1785
|
+
referenceRows.filter((row) => row.tradeFlow).map((row) => [row.symbol, row.tradeFlow])
|
|
1786
|
+
);
|
|
1787
|
+
const targetReferenceSymbol = signal.symbol.trim().toUpperCase();
|
|
1788
|
+
const targetTradeFlow = tradeFlowBySymbol[targetReferenceSymbol];
|
|
1789
|
+
if (!Object.keys(tradeFlowBySymbol).length && !availableBreadths.length) {
|
|
1790
|
+
return false;
|
|
1791
|
+
}
|
|
1792
|
+
const baseContext = signal.additionalIndicators.baseContext;
|
|
1793
|
+
signal.additionalIndicators = {
|
|
1794
|
+
...signal.additionalIndicators,
|
|
1795
|
+
baseContext: {
|
|
1796
|
+
...baseContext,
|
|
1797
|
+
participation: {
|
|
1798
|
+
...baseContext.participation,
|
|
1799
|
+
...targetTradeFlow ? {
|
|
1800
|
+
tradeFlow: targetTradeFlow
|
|
1801
|
+
} : {}
|
|
1802
|
+
},
|
|
1803
|
+
relative: {
|
|
1804
|
+
...baseContext.relative,
|
|
1805
|
+
execution: {
|
|
1806
|
+
...baseContext.relative.execution
|
|
1807
|
+
},
|
|
1808
|
+
...Object.keys(tradeFlowBySymbol).length ? {
|
|
1809
|
+
referenceTradeFlow: {
|
|
1810
|
+
source: "binance_reference_market",
|
|
1811
|
+
primaryReferenceSymbol,
|
|
1812
|
+
referenceSymbols,
|
|
1813
|
+
tradeFlowBySymbol
|
|
1814
|
+
}
|
|
1815
|
+
} : {},
|
|
1816
|
+
...availableBreadths.length ? {
|
|
1817
|
+
marketBreadths,
|
|
1818
|
+
...primaryBreadth ? {
|
|
1819
|
+
marketBreadth: toMarketBreadthContext(
|
|
1820
|
+
primaryBreadth,
|
|
1821
|
+
interval
|
|
1822
|
+
),
|
|
1823
|
+
btcAltRegime: {
|
|
1824
|
+
source: "binance_klines",
|
|
1825
|
+
universe: primaryBreadth.universe,
|
|
1826
|
+
interval,
|
|
1827
|
+
asOfTs: primaryBreadth.ts.getTime(),
|
|
1828
|
+
ageMs: primaryBreadth.ageMs,
|
|
1829
|
+
stale: primaryBreadth.stale,
|
|
1830
|
+
btcReturn1h: toFiniteNumberOrNull(
|
|
1831
|
+
primaryBreadth.btcReturn1h
|
|
1832
|
+
),
|
|
1833
|
+
btcReturn4h: toFiniteNumberOrNull(
|
|
1834
|
+
primaryBreadth.btcReturn4h
|
|
1835
|
+
),
|
|
1836
|
+
btcReturn24h: toFiniteNumberOrNull(
|
|
1837
|
+
primaryBreadth.btcReturn24h
|
|
1838
|
+
),
|
|
1839
|
+
altBasketReturn1h: toFiniteNumberOrNull(
|
|
1840
|
+
primaryBreadth.altBasketReturn1h
|
|
1841
|
+
),
|
|
1842
|
+
altBasketReturn4h: toFiniteNumberOrNull(
|
|
1843
|
+
primaryBreadth.altBasketReturn4h
|
|
1844
|
+
),
|
|
1845
|
+
altBasketReturn24h: toFiniteNumberOrNull(
|
|
1846
|
+
primaryBreadth.altBasketReturn24h
|
|
1847
|
+
),
|
|
1848
|
+
btcVsAltReturn1h: toFiniteNumberOrNull(
|
|
1849
|
+
primaryBreadth.btcVsAltReturn1h
|
|
1850
|
+
),
|
|
1851
|
+
btcVsAltReturn4h: toFiniteNumberOrNull(
|
|
1852
|
+
primaryBreadth.btcVsAltReturn4h
|
|
1853
|
+
),
|
|
1854
|
+
btcVsAltReturn24h: toFiniteNumberOrNull(
|
|
1855
|
+
primaryBreadth.btcVsAltReturn24h
|
|
1856
|
+
),
|
|
1857
|
+
btcTurnoverShare1h: toFiniteNumberOrNull(
|
|
1858
|
+
primaryBreadth.btcTurnoverShare1h
|
|
1859
|
+
),
|
|
1860
|
+
btcTurnoverShare24h: toFiniteNumberOrNull(
|
|
1861
|
+
primaryBreadth.btcTurnoverShare24h
|
|
1862
|
+
),
|
|
1863
|
+
btcTurnoverShareChange24h: toFiniteNumberOrNull(
|
|
1864
|
+
primaryBreadth.btcTurnoverShareChange24h
|
|
1865
|
+
),
|
|
1866
|
+
altVolToBtcVol24h: toFiniteNumberOrNull(
|
|
1867
|
+
primaryBreadth.altVolToBtcVol24h
|
|
1868
|
+
),
|
|
1869
|
+
altDispersion24h: toFiniteNumberOrNull(
|
|
1870
|
+
primaryBreadth.altDispersion24h
|
|
1871
|
+
),
|
|
1872
|
+
regime: primaryBreadth.btcAltRegime ?? "unknown"
|
|
1873
|
+
}
|
|
1874
|
+
} : {}
|
|
1875
|
+
} : {}
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
};
|
|
1879
|
+
(0, import_strategies.refreshSignalBaseContextGateFeatures)(signal);
|
|
1880
|
+
return true;
|
|
1881
|
+
} catch (error) {
|
|
1882
|
+
if (isMarketContextCancellationError(error, params.abortSignal)) {
|
|
1883
|
+
throw error;
|
|
1884
|
+
}
|
|
1885
|
+
binanceMarketContextUnavailable = true;
|
|
1886
|
+
import_logger3.logger.warn(
|
|
1887
|
+
"Binance market context disabled after Timescale read failure: %s",
|
|
1888
|
+
String(error)
|
|
1889
|
+
);
|
|
1890
|
+
return false;
|
|
1891
|
+
}
|
|
1892
|
+
};
|
|
1893
|
+
|
|
1894
|
+
// src/strategyHelpers/coinMarketCapContext.ts
|
|
1895
|
+
var import_strategies2 = require("@tradejs/core/strategies");
|
|
1896
|
+
var import_logger4 = require("@tradejs/infra/logger");
|
|
1897
|
+
var import_marketContext2 = require("@tradejs/infra/timescale/marketContext");
|
|
1898
|
+
var DEFAULT_MAX_AGE_MS = 48 * 60 * 6e4;
|
|
1899
|
+
var SOURCE_GLOBAL_DAILY = "coinmarketcap_global";
|
|
1900
|
+
var SOURCE_REFERENCE = "coinmarketcap_reference_asset";
|
|
1901
|
+
var SOURCE_EXCHANGE_LIQUIDITY = "coinmarketcap_exchange_liquidity";
|
|
1902
|
+
var SOURCE_FEAR_GREED = "coinmarketcap_fear_greed";
|
|
1903
|
+
var SOURCE_INDEX = "coinmarketcap_index";
|
|
1904
|
+
var DAY_MS = 864e5;
|
|
1905
|
+
var coinMarketCapContextUnavailable = false;
|
|
1906
|
+
var globalContextCache = /* @__PURE__ */ new Map();
|
|
1907
|
+
var referenceContextCache = /* @__PURE__ */ new Map();
|
|
1908
|
+
var exchangeLiquidityContextCache = /* @__PURE__ */ new Map();
|
|
1909
|
+
var fearGreedContextCache = /* @__PURE__ */ new Map();
|
|
1910
|
+
var indexContextCache = /* @__PURE__ */ new Map();
|
|
1911
|
+
var parseEnabledFlag2 = (value, env) => {
|
|
1912
|
+
const normalized = String(value ?? "").trim().toLowerCase();
|
|
1913
|
+
if (!normalized) {
|
|
1914
|
+
return env === "BACKTEST" || env === "PARITY" || env === "CRON";
|
|
1915
|
+
}
|
|
1916
|
+
if (["1", "true", "yes", "on"].includes(normalized)) return true;
|
|
1917
|
+
if (["0", "false", "no", "off"].includes(normalized)) return false;
|
|
1918
|
+
if (normalized === "backtest") return env === "BACKTEST";
|
|
1919
|
+
if (normalized === "live") return env !== "BACKTEST";
|
|
1920
|
+
return false;
|
|
1921
|
+
};
|
|
1922
|
+
var asInt = (value, fallback) => {
|
|
1923
|
+
const parsed = Number.parseInt(String(value ?? ""), 10);
|
|
1924
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
1925
|
+
};
|
|
1926
|
+
var toFiniteNumberOrNull2 = (value) => {
|
|
1927
|
+
const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
|
|
1928
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
1929
|
+
};
|
|
1930
|
+
var safeDivide = (numerator, denominator) => numerator != null && denominator != null && denominator > 0 ? numerator / denominator : null;
|
|
1931
|
+
var hasBaseContext2 = (signal) => Boolean(
|
|
1932
|
+
signal.additionalIndicators?.baseContext && typeof signal.additionalIndicators.baseContext === "object" && !Array.isArray(signal.additionalIndicators.baseContext)
|
|
1933
|
+
);
|
|
1934
|
+
var resolveMaxAgeMs = () => asInt(process.env.COINMARKETCAP_CONTEXT_MAX_AGE_MS, DEFAULT_MAX_AGE_MS);
|
|
1935
|
+
var toAltLiquidityRegime = ({
|
|
1936
|
+
stale,
|
|
1937
|
+
btcDominanceChange24hPct,
|
|
1938
|
+
altMarketCapChange24hPct,
|
|
1939
|
+
altVolumeChange24hPct
|
|
1940
|
+
}) => {
|
|
1941
|
+
if (stale) return "unknown";
|
|
1942
|
+
if (altMarketCapChange24hPct != null && altMarketCapChange24hPct <= -0.03 || altVolumeChange24hPct != null && altVolumeChange24hPct <= -0.15) {
|
|
1943
|
+
return "risk_off";
|
|
1944
|
+
}
|
|
1945
|
+
if (btcDominanceChange24hPct != null && btcDominanceChange24hPct >= 0.25) {
|
|
1946
|
+
return "btc_favored";
|
|
1947
|
+
}
|
|
1948
|
+
if (btcDominanceChange24hPct != null && btcDominanceChange24hPct <= -0.25 && (altMarketCapChange24hPct == null || altMarketCapChange24hPct >= 0)) {
|
|
1949
|
+
return "alt_friendly";
|
|
1950
|
+
}
|
|
1951
|
+
return "neutral";
|
|
1952
|
+
};
|
|
1953
|
+
var toReferenceLiquidityRegime = ({
|
|
1954
|
+
stale,
|
|
1955
|
+
ethBtcMarketCapRatioChange24hPct,
|
|
1956
|
+
ethVsBtcVolumeRatio
|
|
1957
|
+
}) => {
|
|
1958
|
+
if (stale) return "unknown";
|
|
1959
|
+
if (ethVsBtcVolumeRatio != null && ethVsBtcVolumeRatio < 0.15) return "thin";
|
|
1960
|
+
if (ethBtcMarketCapRatioChange24hPct != null && ethBtcMarketCapRatioChange24hPct >= 0.01) {
|
|
1961
|
+
return "eth_led";
|
|
1962
|
+
}
|
|
1963
|
+
if (ethBtcMarketCapRatioChange24hPct != null && ethBtcMarketCapRatioChange24hPct <= -0.01) {
|
|
1964
|
+
return "btc_led";
|
|
1965
|
+
}
|
|
1966
|
+
return "balanced";
|
|
1967
|
+
};
|
|
1968
|
+
var toExchangeLiquidityRegime = ({
|
|
1969
|
+
stale,
|
|
1970
|
+
totalVolumeChange24hPct,
|
|
1971
|
+
fallback
|
|
1972
|
+
}) => {
|
|
1973
|
+
if (stale) return "unknown";
|
|
1974
|
+
if (totalVolumeChange24hPct != null && totalVolumeChange24hPct >= 0.15) {
|
|
1975
|
+
return "expanding";
|
|
1976
|
+
}
|
|
1977
|
+
if (totalVolumeChange24hPct != null && totalVolumeChange24hPct <= -0.15) {
|
|
1978
|
+
return "contracting";
|
|
1979
|
+
}
|
|
1980
|
+
return fallback;
|
|
1981
|
+
};
|
|
1982
|
+
var toIndexRegime = ({
|
|
1983
|
+
stale,
|
|
1984
|
+
cmc100Change24hPct,
|
|
1985
|
+
cmc20Change24hPct,
|
|
1986
|
+
cmc20ToCmc100RatioChange24hPct
|
|
1987
|
+
}) => {
|
|
1988
|
+
if (stale) return "unknown";
|
|
1989
|
+
if (cmc100Change24hPct == null && cmc20Change24hPct == null) {
|
|
1990
|
+
return "unknown";
|
|
1991
|
+
}
|
|
1992
|
+
if ((cmc100Change24hPct ?? 0) <= -0.02 && (cmc20Change24hPct ?? 0) <= -0.02) {
|
|
1993
|
+
return "risk_off";
|
|
1994
|
+
}
|
|
1995
|
+
if ((cmc20ToCmc100RatioChange24hPct ?? 0) >= 5e-3) {
|
|
1996
|
+
return "top20_led";
|
|
1997
|
+
}
|
|
1998
|
+
if ((cmc20ToCmc100RatioChange24hPct ?? 0) <= -5e-3) {
|
|
1999
|
+
return "large_cap_led";
|
|
2000
|
+
}
|
|
2001
|
+
return "balanced";
|
|
2002
|
+
};
|
|
2003
|
+
var getCachedGlobalContext = ({
|
|
2004
|
+
timestamp,
|
|
2005
|
+
maxAgeMs,
|
|
2006
|
+
abortSignal
|
|
2007
|
+
}) => {
|
|
2008
|
+
const key = `${SOURCE_GLOBAL_DAILY}:${timestamp}:${maxAgeMs}`;
|
|
2009
|
+
const cached = globalContextCache.get(key);
|
|
2010
|
+
if (cached) return cached;
|
|
2011
|
+
const promise = (0, import_marketContext2.getLatestMarketGlobalContext)({
|
|
2012
|
+
source: SOURCE_GLOBAL_DAILY,
|
|
2013
|
+
atMs: timestamp,
|
|
2014
|
+
maxAgeMs,
|
|
2015
|
+
...abortSignal ? { signal: abortSignal } : {}
|
|
2016
|
+
});
|
|
2017
|
+
globalContextCache.set(key, promise);
|
|
2018
|
+
void promise.catch(() => globalContextCache.delete(key));
|
|
2019
|
+
return promise;
|
|
2020
|
+
};
|
|
2021
|
+
var getCachedReferenceContexts = ({
|
|
2022
|
+
timestamp,
|
|
2023
|
+
maxAgeMs,
|
|
2024
|
+
abortSignal
|
|
2025
|
+
}) => {
|
|
2026
|
+
const key = `${SOURCE_REFERENCE}:1d:${timestamp}:${maxAgeMs}`;
|
|
2027
|
+
const cached = referenceContextCache.get(key);
|
|
2028
|
+
if (cached) return cached;
|
|
2029
|
+
const promise = (0, import_marketContext2.getLatestMarketReferenceAssetContexts)({
|
|
2030
|
+
source: SOURCE_REFERENCE,
|
|
2031
|
+
symbols: ["BTCUSDT", "ETHUSDT"],
|
|
2032
|
+
interval: "1d",
|
|
2033
|
+
atMs: timestamp,
|
|
2034
|
+
maxAgeMs,
|
|
2035
|
+
...abortSignal ? { signal: abortSignal } : {}
|
|
2036
|
+
});
|
|
2037
|
+
referenceContextCache.set(key, promise);
|
|
2038
|
+
void promise.catch(() => referenceContextCache.delete(key));
|
|
2039
|
+
return promise;
|
|
2040
|
+
};
|
|
2041
|
+
var getCachedExchangeLiquidityContext = ({
|
|
2042
|
+
timestamp,
|
|
2043
|
+
maxAgeMs,
|
|
2044
|
+
abortSignal
|
|
2045
|
+
}) => {
|
|
2046
|
+
const key = `${SOURCE_EXCHANGE_LIQUIDITY}:1d:${timestamp}:${maxAgeMs}`;
|
|
2047
|
+
const cached = exchangeLiquidityContextCache.get(key);
|
|
2048
|
+
if (cached) return cached;
|
|
2049
|
+
const promise = (0, import_marketContext2.getLatestMarketCmcExchangeLiquidityContext)({
|
|
2050
|
+
source: SOURCE_EXCHANGE_LIQUIDITY,
|
|
2051
|
+
interval: "1d",
|
|
2052
|
+
atMs: timestamp,
|
|
2053
|
+
maxAgeMs,
|
|
2054
|
+
...abortSignal ? { signal: abortSignal } : {}
|
|
2055
|
+
});
|
|
2056
|
+
exchangeLiquidityContextCache.set(key, promise);
|
|
2057
|
+
void promise.catch(() => exchangeLiquidityContextCache.delete(key));
|
|
2058
|
+
return promise;
|
|
2059
|
+
};
|
|
2060
|
+
var getCachedFearGreedContext = ({
|
|
2061
|
+
timestamp,
|
|
2062
|
+
maxAgeMs,
|
|
2063
|
+
abortSignal
|
|
2064
|
+
}) => {
|
|
2065
|
+
const key = `${SOURCE_FEAR_GREED}:1d:${timestamp}:${maxAgeMs}`;
|
|
2066
|
+
const cached = fearGreedContextCache.get(key);
|
|
2067
|
+
if (cached) return cached;
|
|
2068
|
+
const promise = (0, import_marketContext2.getLatestMarketCmcFearGreedContext)({
|
|
2069
|
+
source: SOURCE_FEAR_GREED,
|
|
2070
|
+
interval: "1d",
|
|
2071
|
+
atMs: timestamp,
|
|
2072
|
+
maxAgeMs,
|
|
2073
|
+
...abortSignal ? { signal: abortSignal } : {}
|
|
2074
|
+
});
|
|
2075
|
+
fearGreedContextCache.set(key, promise);
|
|
2076
|
+
void promise.catch(() => fearGreedContextCache.delete(key));
|
|
2077
|
+
return promise;
|
|
2078
|
+
};
|
|
2079
|
+
var getCachedIndexContexts = ({
|
|
2080
|
+
timestamp,
|
|
2081
|
+
maxAgeMs,
|
|
2082
|
+
abortSignal
|
|
2083
|
+
}) => {
|
|
2084
|
+
const key = `${SOURCE_INDEX}:1d:${timestamp}:${maxAgeMs}`;
|
|
2085
|
+
const cached = indexContextCache.get(key);
|
|
2086
|
+
if (cached) return cached;
|
|
2087
|
+
const promise = (0, import_marketContext2.getLatestMarketCmcIndexContexts)({
|
|
2088
|
+
source: SOURCE_INDEX,
|
|
2089
|
+
indexSlugs: ["cmc100", "cmc20"],
|
|
2090
|
+
interval: "1d",
|
|
2091
|
+
atMs: timestamp,
|
|
2092
|
+
maxAgeMs,
|
|
2093
|
+
...abortSignal ? { signal: abortSignal } : {}
|
|
2094
|
+
});
|
|
2095
|
+
indexContextCache.set(key, promise);
|
|
2096
|
+
void promise.catch(() => indexContextCache.delete(key));
|
|
2097
|
+
return promise;
|
|
2098
|
+
};
|
|
2099
|
+
var isCoinMarketCapContextEnabled = (env) => parseEnabledFlag2(process.env.COINMARKETCAP_CONTEXT_ENABLED, env);
|
|
2100
|
+
var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
2101
|
+
const {
|
|
2102
|
+
signal,
|
|
2103
|
+
env,
|
|
2104
|
+
enabled = isCoinMarketCapContextEnabled(env),
|
|
2105
|
+
maxAgeMs = resolveMaxAgeMs()
|
|
2106
|
+
} = params;
|
|
2107
|
+
if (signal.universe === "tradfi" || !enabled || coinMarketCapContextUnavailable || !hasBaseContext2(signal)) {
|
|
2108
|
+
return false;
|
|
2109
|
+
}
|
|
2110
|
+
try {
|
|
2111
|
+
const [
|
|
2112
|
+
globalDailyRow,
|
|
2113
|
+
dailyReferences,
|
|
2114
|
+
previousDailyReferences,
|
|
2115
|
+
exchangeLiquidityRow,
|
|
2116
|
+
fearGreedRow,
|
|
2117
|
+
indexRows
|
|
2118
|
+
] = await Promise.all([
|
|
2119
|
+
getCachedGlobalContext({
|
|
2120
|
+
timestamp: signal.timestamp,
|
|
2121
|
+
maxAgeMs,
|
|
2122
|
+
abortSignal: params.abortSignal
|
|
2123
|
+
}),
|
|
2124
|
+
getCachedReferenceContexts({
|
|
2125
|
+
timestamp: signal.timestamp,
|
|
2126
|
+
maxAgeMs,
|
|
2127
|
+
abortSignal: params.abortSignal
|
|
2128
|
+
}),
|
|
2129
|
+
getCachedReferenceContexts({
|
|
2130
|
+
timestamp: signal.timestamp - DAY_MS,
|
|
2131
|
+
maxAgeMs: maxAgeMs + DAY_MS,
|
|
2132
|
+
abortSignal: params.abortSignal
|
|
2133
|
+
}),
|
|
2134
|
+
getCachedExchangeLiquidityContext({
|
|
2135
|
+
timestamp: signal.timestamp,
|
|
2136
|
+
maxAgeMs,
|
|
2137
|
+
abortSignal: params.abortSignal
|
|
2138
|
+
}),
|
|
2139
|
+
getCachedFearGreedContext({
|
|
2140
|
+
timestamp: signal.timestamp,
|
|
2141
|
+
maxAgeMs,
|
|
2142
|
+
abortSignal: params.abortSignal
|
|
2143
|
+
}),
|
|
2144
|
+
getCachedIndexContexts({
|
|
2145
|
+
timestamp: signal.timestamp,
|
|
2146
|
+
maxAgeMs,
|
|
2147
|
+
abortSignal: params.abortSignal
|
|
2148
|
+
})
|
|
2149
|
+
]);
|
|
2150
|
+
const globalRow = globalDailyRow;
|
|
2151
|
+
const references = dailyReferences;
|
|
2152
|
+
const previousReferences = previousDailyReferences;
|
|
2153
|
+
if (!globalRow && !references.size && !exchangeLiquidityRow && !fearGreedRow && !indexRows.size) {
|
|
2154
|
+
return false;
|
|
2155
|
+
}
|
|
2156
|
+
const btcRow = references.get("BTCUSDT") ?? null;
|
|
2157
|
+
const ethRow = references.get("ETHUSDT") ?? null;
|
|
2158
|
+
const previousBtcRow = previousReferences.get("BTCUSDT") ?? null;
|
|
2159
|
+
const previousEthRow = previousReferences.get("ETHUSDT") ?? null;
|
|
2160
|
+
const btcMarketCapUsd = toFiniteNumberOrNull2(btcRow?.marketCapUsd);
|
|
2161
|
+
const ethMarketCapUsd = toFiniteNumberOrNull2(ethRow?.marketCapUsd);
|
|
2162
|
+
const previousBtcMarketCapUsd = toFiniteNumberOrNull2(
|
|
2163
|
+
previousBtcRow?.marketCapUsd
|
|
2164
|
+
);
|
|
2165
|
+
const previousEthMarketCapUsd = toFiniteNumberOrNull2(
|
|
2166
|
+
previousEthRow?.marketCapUsd
|
|
2167
|
+
);
|
|
2168
|
+
const ethBtcMarketCapRatio = safeDivide(ethMarketCapUsd, btcMarketCapUsd);
|
|
2169
|
+
const previousEthBtcMarketCapRatio = safeDivide(
|
|
2170
|
+
previousEthMarketCapUsd,
|
|
2171
|
+
previousBtcMarketCapUsd
|
|
2172
|
+
);
|
|
2173
|
+
const ethBtcMarketCapRatioChange24hPct = ethBtcMarketCapRatio != null && previousEthBtcMarketCapRatio != null && previousEthBtcMarketCapRatio > 0 ? (ethBtcMarketCapRatio - previousEthBtcMarketCapRatio) / previousEthBtcMarketCapRatio : null;
|
|
2174
|
+
const btcVolumeUsd = toFiniteNumberOrNull2(btcRow?.volumeUsd);
|
|
2175
|
+
const ethVolumeUsd = toFiniteNumberOrNull2(ethRow?.volumeUsd);
|
|
2176
|
+
const referenceStale = btcRow?.stale === true || ethRow?.stale === true || !btcRow || !ethRow;
|
|
2177
|
+
const btcDominanceChange24hPct = toFiniteNumberOrNull2(
|
|
2178
|
+
globalRow?.btcDominanceChange24hPct
|
|
2179
|
+
);
|
|
2180
|
+
const altMarketCapChange24hPct = toFiniteNumberOrNull2(
|
|
2181
|
+
globalRow?.altMarketCapChange24hPct
|
|
2182
|
+
);
|
|
2183
|
+
const altVolumeChange24hPct = toFiniteNumberOrNull2(
|
|
2184
|
+
globalRow?.altVolumeChange24hPct
|
|
2185
|
+
);
|
|
2186
|
+
const altLiquidityRegime = globalRow ? toAltLiquidityRegime({
|
|
2187
|
+
stale: globalRow.stale,
|
|
2188
|
+
btcDominanceChange24hPct,
|
|
2189
|
+
altMarketCapChange24hPct,
|
|
2190
|
+
altVolumeChange24hPct
|
|
2191
|
+
}) : "unknown";
|
|
2192
|
+
const exchangeLiquidityRegime = exchangeLiquidityRow ? toExchangeLiquidityRegime({
|
|
2193
|
+
stale: exchangeLiquidityRow.stale,
|
|
2194
|
+
totalVolumeChange24hPct: toFiniteNumberOrNull2(
|
|
2195
|
+
exchangeLiquidityRow.totalVolumeChange24hPct
|
|
2196
|
+
),
|
|
2197
|
+
fallback: exchangeLiquidityRow.liquidityRegime ?? "unknown"
|
|
2198
|
+
}) : "unknown";
|
|
2199
|
+
const cmc100Row = indexRows.get("cmc100") ?? null;
|
|
2200
|
+
const cmc20Row = indexRows.get("cmc20") ?? null;
|
|
2201
|
+
const cmc100Value = toFiniteNumberOrNull2(cmc100Row?.value);
|
|
2202
|
+
const cmc20Value = toFiniteNumberOrNull2(cmc20Row?.value);
|
|
2203
|
+
const cmc100Change24hPct = toFiniteNumberOrNull2(
|
|
2204
|
+
cmc100Row?.valueChange24hPct
|
|
2205
|
+
);
|
|
2206
|
+
const cmc20Change24hPct = toFiniteNumberOrNull2(cmc20Row?.valueChange24hPct);
|
|
2207
|
+
const cmc20ToCmc100Ratio = safeDivide(cmc20Value, cmc100Value);
|
|
2208
|
+
const cmc20ToCmc100RatioChange24hPct = cmc20Change24hPct != null && cmc100Change24hPct != null ? (1 + cmc20Change24hPct) / (1 + cmc100Change24hPct) - 1 : null;
|
|
2209
|
+
const indexStale = cmc100Row?.stale === true || cmc20Row?.stale === true || !cmc100Row || !cmc20Row;
|
|
2210
|
+
const indexRegime = toIndexRegime({
|
|
2211
|
+
stale: indexStale,
|
|
2212
|
+
cmc100Change24hPct,
|
|
2213
|
+
cmc20Change24hPct,
|
|
2214
|
+
cmc20ToCmc100RatioChange24hPct
|
|
2215
|
+
});
|
|
2216
|
+
const baseContext = signal.additionalIndicators.baseContext;
|
|
2217
|
+
signal.additionalIndicators = {
|
|
2218
|
+
...signal.additionalIndicators,
|
|
2219
|
+
baseContext: {
|
|
2220
|
+
...baseContext,
|
|
2221
|
+
relative: {
|
|
2222
|
+
...baseContext.relative,
|
|
2223
|
+
...globalRow ? {
|
|
2224
|
+
cmcGlobal: {
|
|
2225
|
+
source: globalRow.source,
|
|
2226
|
+
interval: "1d",
|
|
2227
|
+
asOfTs: globalRow.ts.getTime(),
|
|
2228
|
+
ageMs: globalRow.ageMs,
|
|
2229
|
+
stale: globalRow.stale,
|
|
2230
|
+
totalMarketCapUsd: toFiniteNumberOrNull2(
|
|
2231
|
+
globalRow.totalMarketCapUsd
|
|
2232
|
+
),
|
|
2233
|
+
totalVolumeUsd: toFiniteNumberOrNull2(
|
|
2234
|
+
globalRow.totalVolumeUsd
|
|
2235
|
+
),
|
|
2236
|
+
totalVolumeReportedUsd: toFiniteNumberOrNull2(
|
|
2237
|
+
globalRow.totalVolumeReportedUsd
|
|
2238
|
+
),
|
|
2239
|
+
altMarketCapUsd: toFiniteNumberOrNull2(
|
|
2240
|
+
globalRow.altMarketCapUsd
|
|
2241
|
+
),
|
|
2242
|
+
altVolumeUsd: toFiniteNumberOrNull2(globalRow.altVolumeUsd),
|
|
2243
|
+
altVolumeReportedUsd: toFiniteNumberOrNull2(
|
|
2244
|
+
globalRow.altVolumeReportedUsd
|
|
2245
|
+
),
|
|
2246
|
+
btcDominancePct: toFiniteNumberOrNull2(
|
|
2247
|
+
globalRow.btcDominancePct
|
|
2248
|
+
),
|
|
2249
|
+
ethDominancePct: toFiniteNumberOrNull2(
|
|
2250
|
+
globalRow.ethDominancePct
|
|
2251
|
+
),
|
|
2252
|
+
btcDominanceChange24hPct,
|
|
2253
|
+
ethDominanceChange24hPct: toFiniteNumberOrNull2(
|
|
2254
|
+
globalRow.ethDominanceChange24hPct
|
|
2255
|
+
),
|
|
2256
|
+
altMarketCapChange24hPct,
|
|
2257
|
+
altVolumeChange24hPct,
|
|
2258
|
+
activeCryptocurrencies: toFiniteNumberOrNull2(
|
|
2259
|
+
globalRow.activeCryptocurrencies
|
|
2260
|
+
),
|
|
2261
|
+
activeExchanges: toFiniteNumberOrNull2(
|
|
2262
|
+
globalRow.activeExchanges
|
|
2263
|
+
),
|
|
2264
|
+
activeMarketPairs: toFiniteNumberOrNull2(
|
|
2265
|
+
globalRow.activeMarketPairs
|
|
2266
|
+
),
|
|
2267
|
+
altLiquidityRegime
|
|
2268
|
+
}
|
|
2269
|
+
} : {},
|
|
2270
|
+
...btcRow || ethRow ? {
|
|
2271
|
+
cmcReferenceAssets: {
|
|
2272
|
+
source: SOURCE_REFERENCE,
|
|
2273
|
+
interval: "1d",
|
|
2274
|
+
asOfTs: Math.max(
|
|
2275
|
+
btcRow?.ts.getTime() ?? 0,
|
|
2276
|
+
ethRow?.ts.getTime() ?? 0
|
|
2277
|
+
),
|
|
2278
|
+
ageMs: btcRow?.ageMs != null && ethRow?.ageMs != null ? Math.max(btcRow.ageMs, ethRow.ageMs) : btcRow?.ageMs ?? ethRow?.ageMs ?? null,
|
|
2279
|
+
stale: referenceStale,
|
|
2280
|
+
btcMarketCapUsd,
|
|
2281
|
+
ethMarketCapUsd,
|
|
2282
|
+
btcVolumeUsd,
|
|
2283
|
+
ethVolumeUsd,
|
|
2284
|
+
btcVolumeToMarketCap: safeDivide(
|
|
2285
|
+
btcVolumeUsd,
|
|
2286
|
+
btcMarketCapUsd
|
|
2287
|
+
),
|
|
2288
|
+
ethVolumeToMarketCap: safeDivide(
|
|
2289
|
+
ethVolumeUsd,
|
|
2290
|
+
ethMarketCapUsd
|
|
2291
|
+
),
|
|
2292
|
+
ethBtcMarketCapRatio,
|
|
2293
|
+
ethBtcMarketCapRatioChange24hPct,
|
|
2294
|
+
ethVsBtcVolumeRatio: safeDivide(ethVolumeUsd, btcVolumeUsd),
|
|
2295
|
+
referenceLiquidityRegime: toReferenceLiquidityRegime({
|
|
2296
|
+
stale: referenceStale,
|
|
2297
|
+
ethBtcMarketCapRatioChange24hPct,
|
|
2298
|
+
ethVsBtcVolumeRatio: safeDivide(ethVolumeUsd, btcVolumeUsd)
|
|
2299
|
+
})
|
|
2300
|
+
}
|
|
2301
|
+
} : {},
|
|
2302
|
+
...exchangeLiquidityRow ? {
|
|
2303
|
+
cmcExchangeLiquidity: {
|
|
2304
|
+
source: SOURCE_EXCHANGE_LIQUIDITY,
|
|
2305
|
+
interval: exchangeLiquidityRow.interval,
|
|
2306
|
+
asOfTs: exchangeLiquidityRow.ts.getTime(),
|
|
2307
|
+
ageMs: exchangeLiquidityRow.ageMs,
|
|
2308
|
+
stale: exchangeLiquidityRow.stale,
|
|
2309
|
+
exchangesCount: toFiniteNumberOrNull2(
|
|
2310
|
+
exchangeLiquidityRow.exchangesCount
|
|
2311
|
+
),
|
|
2312
|
+
totalVolumeUsd: toFiniteNumberOrNull2(
|
|
2313
|
+
exchangeLiquidityRow.totalVolumeUsd
|
|
2314
|
+
),
|
|
2315
|
+
totalVolumeChange24hPct: toFiniteNumberOrNull2(
|
|
2316
|
+
exchangeLiquidityRow.totalVolumeChange24hPct
|
|
2317
|
+
),
|
|
2318
|
+
binanceVolumeUsd: toFiniteNumberOrNull2(
|
|
2319
|
+
exchangeLiquidityRow.binanceVolumeUsd
|
|
2320
|
+
),
|
|
2321
|
+
binanceVolumeShare: toFiniteNumberOrNull2(
|
|
2322
|
+
exchangeLiquidityRow.binanceVolumeShare
|
|
2323
|
+
),
|
|
2324
|
+
topExchangeVolumeShare: toFiniteNumberOrNull2(
|
|
2325
|
+
exchangeLiquidityRow.topExchangeVolumeShare
|
|
2326
|
+
),
|
|
2327
|
+
liquidityRegime: exchangeLiquidityRegime
|
|
2328
|
+
}
|
|
2329
|
+
} : {},
|
|
2330
|
+
...fearGreedRow ? {
|
|
2331
|
+
cmcFearGreed: {
|
|
2332
|
+
source: SOURCE_FEAR_GREED,
|
|
2333
|
+
interval: "1d",
|
|
2334
|
+
asOfTs: fearGreedRow.ts.getTime(),
|
|
2335
|
+
ageMs: fearGreedRow.ageMs,
|
|
2336
|
+
stale: fearGreedRow.stale,
|
|
2337
|
+
value: toFiniteNumberOrNull2(fearGreedRow.value),
|
|
2338
|
+
valueChange24h: toFiniteNumberOrNull2(
|
|
2339
|
+
fearGreedRow.valueChange24h
|
|
2340
|
+
),
|
|
2341
|
+
valueChange7d: toFiniteNumberOrNull2(
|
|
2342
|
+
fearGreedRow.valueChange7d
|
|
2343
|
+
),
|
|
2344
|
+
classification: fearGreedRow.classification ?? "Unknown",
|
|
2345
|
+
sentimentRegime: fearGreedRow.sentimentRegime ?? "unknown"
|
|
2346
|
+
}
|
|
2347
|
+
} : {},
|
|
2348
|
+
...cmc100Row || cmc20Row ? {
|
|
2349
|
+
cmcIndexes: {
|
|
2350
|
+
source: SOURCE_INDEX,
|
|
2351
|
+
interval: "1d",
|
|
2352
|
+
asOfTs: Math.max(
|
|
2353
|
+
cmc100Row?.ts.getTime() ?? 0,
|
|
2354
|
+
cmc20Row?.ts.getTime() ?? 0
|
|
2355
|
+
),
|
|
2356
|
+
ageMs: cmc100Row?.ageMs != null && cmc20Row?.ageMs != null ? Math.max(cmc100Row.ageMs, cmc20Row.ageMs) : cmc100Row?.ageMs ?? cmc20Row?.ageMs ?? null,
|
|
2357
|
+
stale: indexStale,
|
|
2358
|
+
cmc100Value,
|
|
2359
|
+
cmc100Change24hPct,
|
|
2360
|
+
cmc100TopConstituentSymbol: cmc100Row?.topConstituentSymbol ?? null,
|
|
2361
|
+
cmc100TopConstituentWeightPct: toFiniteNumberOrNull2(
|
|
2362
|
+
cmc100Row?.topConstituentWeightPct
|
|
2363
|
+
),
|
|
2364
|
+
cmc20Value,
|
|
2365
|
+
cmc20Change24hPct,
|
|
2366
|
+
cmc20TopConstituentSymbol: cmc20Row?.topConstituentSymbol ?? null,
|
|
2367
|
+
cmc20TopConstituentWeightPct: toFiniteNumberOrNull2(
|
|
2368
|
+
cmc20Row?.topConstituentWeightPct
|
|
2369
|
+
),
|
|
2370
|
+
cmc20ToCmc100Ratio,
|
|
2371
|
+
cmc20ToCmc100RatioChange24hPct,
|
|
2372
|
+
indexRegime
|
|
2373
|
+
}
|
|
2374
|
+
} : {}
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
};
|
|
2378
|
+
(0, import_strategies2.refreshSignalBaseContextGateFeatures)(signal);
|
|
2379
|
+
return true;
|
|
2380
|
+
} catch (error) {
|
|
2381
|
+
if (isMarketContextCancellationError(error, params.abortSignal)) {
|
|
2382
|
+
throw error;
|
|
2383
|
+
}
|
|
2384
|
+
coinMarketCapContextUnavailable = true;
|
|
2385
|
+
import_logger4.logger.warn(
|
|
2386
|
+
"CoinMarketCap context disabled after Timescale read failure: %s",
|
|
2387
|
+
String(error)
|
|
2388
|
+
);
|
|
2389
|
+
return false;
|
|
2390
|
+
}
|
|
2391
|
+
};
|
|
2392
|
+
|
|
2393
|
+
// src/strategyHelpers/derivativesContext.ts
|
|
2394
|
+
var import_indicators = require("@tradejs/core/indicators");
|
|
2395
|
+
var import_data = require("@tradejs/core/data");
|
|
2396
|
+
var import_strategies3 = require("@tradejs/core/strategies");
|
|
2397
|
+
var import_constants2 = require("@tradejs/core/constants");
|
|
2398
|
+
var import_derivatives = require("@tradejs/infra/timescale/derivatives");
|
|
2399
|
+
var import_logger5 = require("@tradejs/infra/logger");
|
|
2400
|
+
var STORED_INTERVALS = ["15m", "1h"];
|
|
2401
|
+
var CONTEXT_INTERVALS = ["15m", "1h"];
|
|
2402
|
+
var DEFAULT_LOOKBACK_HOURS = 48;
|
|
2403
|
+
var PRIMARY_DERIVATIVES_REFERENCE_SYMBOL = import_constants2.DERIVATIVES_CONTEXT_BASE_REFERENCE_SYMBOLS[0];
|
|
2404
|
+
var SECONDARY_DERIVATIVES_REFERENCE_SYMBOL = import_constants2.DERIVATIVES_CONTEXT_BASE_REFERENCE_SYMBOLS[1];
|
|
2405
|
+
var derivativesContextUnavailable = false;
|
|
2406
|
+
var parseEnabledFlag3 = (value, env) => {
|
|
2407
|
+
const normalized = String(value ?? "").trim().toLowerCase();
|
|
2408
|
+
if (!normalized) return true;
|
|
2409
|
+
if (["1", "true", "yes", "on"].includes(normalized)) return true;
|
|
2410
|
+
if (normalized === "backtest") return env === "BACKTEST";
|
|
2411
|
+
if (normalized === "live") return env !== "BACKTEST";
|
|
2412
|
+
return false;
|
|
2413
|
+
};
|
|
2414
|
+
var parseBooleanFlag = (value, fallback = false) => {
|
|
2415
|
+
const normalized = String(value ?? "").trim().toLowerCase();
|
|
2416
|
+
if (!normalized) return fallback;
|
|
2417
|
+
if (["1", "true", "yes", "on"].includes(normalized)) return true;
|
|
2418
|
+
if (["0", "false", "no", "off"].includes(normalized)) return false;
|
|
2419
|
+
return fallback;
|
|
2420
|
+
};
|
|
2421
|
+
var parseLookbackMs = () => {
|
|
2422
|
+
const hours = Number(process.env.DERIVATIVES_CONTEXT_LOOKBACK_HOURS);
|
|
2423
|
+
const normalizedHours = Number.isFinite(hours) && hours > 0 ? hours : DEFAULT_LOOKBACK_HOURS;
|
|
2424
|
+
return normalizedHours * 60 * 60 * 1e3;
|
|
2425
|
+
};
|
|
2426
|
+
var withHourlyFallbackRows = (rowsByInterval) => ({
|
|
2427
|
+
"15m": rowsByInterval["15m"] ?? [],
|
|
2428
|
+
"1h": (0, import_indicators.buildCoinalyzeHourlyRowsWithFallback)({
|
|
2429
|
+
rows15m: rowsByInterval["15m"],
|
|
2430
|
+
fallbackRows1h: rowsByInterval["1h"]
|
|
2431
|
+
})
|
|
2432
|
+
});
|
|
2433
|
+
var getDerivativesContextReferenceSymbols = () => [
|
|
2434
|
+
...(0, import_constants2.resolveDerivativesContextReferenceSymbols)(
|
|
2435
|
+
process.env.DERIVATIVES_CONTEXT_EXTRA_REFERENCE_SYMBOLS
|
|
2436
|
+
)
|
|
2437
|
+
];
|
|
2438
|
+
var normalizeSymbol = (symbol) => String(symbol || "").trim().toUpperCase();
|
|
2439
|
+
var getSignalPriceChangePct1h = (signal) => {
|
|
2440
|
+
const baseContext = signal.additionalIndicators?.baseContext;
|
|
2441
|
+
if (!baseContext || typeof baseContext !== "object" || Array.isArray(baseContext)) {
|
|
2442
|
+
return null;
|
|
2443
|
+
}
|
|
2444
|
+
const raw = typeof baseContext.raw === "object" && baseContext.raw && !Array.isArray(baseContext.raw) ? baseContext.raw : null;
|
|
2445
|
+
const price = raw && typeof raw.price === "object" && raw.price && !Array.isArray(raw.price) ? raw.price : null;
|
|
2446
|
+
const value = price?.price1hPct;
|
|
2447
|
+
const numeric = typeof value === "number" ? value : Number(value);
|
|
2448
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
2449
|
+
};
|
|
2450
|
+
var resolvePrimaryReferenceSymbol2 = () => PRIMARY_DERIVATIVES_REFERENCE_SYMBOL;
|
|
2451
|
+
var resolveSecondaryReferenceSymbol = () => SECONDARY_DERIVATIVES_REFERENCE_SYMBOL;
|
|
2452
|
+
var getPrimaryIntervalContext = (context) => context?.intervals["15m"] ?? context?.intervals["1h"] ?? null;
|
|
2453
|
+
var hasDerivativesSymbolData = (context) => Object.keys(context.intervals).length > 0 && !context.summary.riskFlags.includes("missing_derivatives");
|
|
2454
|
+
var toFiniteNumberOrNull3 = (value) => {
|
|
2455
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
2456
|
+
if (typeof value === "string" && value.trim()) {
|
|
2457
|
+
const parsed = Number(value);
|
|
2458
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
2459
|
+
}
|
|
2460
|
+
return null;
|
|
2461
|
+
};
|
|
2462
|
+
var roundNullable = (value, digits = 4) => {
|
|
2463
|
+
if (value == null || !Number.isFinite(value)) return null;
|
|
2464
|
+
const multiplier = 10 ** digits;
|
|
2465
|
+
return Math.round(value * multiplier) / multiplier;
|
|
2466
|
+
};
|
|
2467
|
+
var deltaNullable = (targetValue, referenceValue) => {
|
|
2468
|
+
const target = toFiniteNumberOrNull3(targetValue);
|
|
2469
|
+
const reference = toFiniteNumberOrNull3(referenceValue);
|
|
2470
|
+
return target == null || reference == null ? null : roundNullable(target - reference);
|
|
2471
|
+
};
|
|
2472
|
+
var buildTargetDerivedContext = (params) => {
|
|
2473
|
+
const { targetContext, primaryReferenceContext } = params;
|
|
2474
|
+
const targetPrimary = getPrimaryIntervalContext(targetContext);
|
|
2475
|
+
const referencePrimary = getPrimaryIntervalContext(primaryReferenceContext);
|
|
2476
|
+
const targetDirectionAligned = targetContext.summary.directionAligned;
|
|
2477
|
+
const referenceDirectionAligned = primaryReferenceContext?.summary.directionAligned ?? null;
|
|
2478
|
+
return {
|
|
2479
|
+
available: hasDerivativesSymbolData(targetContext),
|
|
2480
|
+
stale: targetContext.summary.riskFlags.includes("stale_derivatives") || targetPrimary?.stale === true ? true : targetPrimary == null ? null : false,
|
|
2481
|
+
sourceSymbol: targetContext.symbol,
|
|
2482
|
+
referenceSymbol: primaryReferenceContext?.symbol ?? null,
|
|
2483
|
+
directionAligned: targetDirectionAligned,
|
|
2484
|
+
referenceDirectionAligned,
|
|
2485
|
+
pressure: targetContext.summary.pressure ?? null,
|
|
2486
|
+
referencePressure: primaryReferenceContext?.summary.pressure ?? null,
|
|
2487
|
+
riskFlags: targetContext.summary.riskFlags,
|
|
2488
|
+
oiChangePct1h: targetPrimary?.oiChangePct1h ?? null,
|
|
2489
|
+
oiAcceleration: targetContext.summary.oiAcceleration ?? null,
|
|
2490
|
+
fundingRate: targetPrimary?.fundingRate ?? null,
|
|
2491
|
+
fundingZScore: targetPrimary?.fundingZScore ?? null,
|
|
2492
|
+
fundingChange1h: targetContext.summary.fundingChange1h ?? null,
|
|
2493
|
+
liqSpikeRatio: targetPrimary?.liqSpikeRatio ?? null,
|
|
2494
|
+
liqImbalance: targetPrimary?.liqImbalance ?? null,
|
|
2495
|
+
targetVsPrimaryOiChangePct1hDelta: deltaNullable(
|
|
2496
|
+
targetPrimary?.oiChangePct1h,
|
|
2497
|
+
referencePrimary?.oiChangePct1h
|
|
2498
|
+
),
|
|
2499
|
+
targetVsPrimaryFundingZScoreDelta: deltaNullable(
|
|
2500
|
+
targetPrimary?.fundingZScore,
|
|
2501
|
+
referencePrimary?.fundingZScore
|
|
2502
|
+
),
|
|
2503
|
+
targetReferenceConflict: targetDirectionAligned == null || referenceDirectionAligned == null ? null : targetDirectionAligned !== referenceDirectionAligned
|
|
2504
|
+
};
|
|
2505
|
+
};
|
|
2506
|
+
var buildReferenceDerivativesContext = (params) => {
|
|
2507
|
+
const {
|
|
2508
|
+
targetSymbol,
|
|
2509
|
+
primaryReferenceSymbol,
|
|
2510
|
+
secondaryReferenceSymbol,
|
|
2511
|
+
referenceSymbols,
|
|
2512
|
+
referenceContexts,
|
|
2513
|
+
targetContext
|
|
2514
|
+
} = params;
|
|
2515
|
+
const primaryContext = referenceContexts[primaryReferenceSymbol] ?? referenceContexts[referenceSymbols[0]];
|
|
2516
|
+
if (!primaryContext) {
|
|
2517
|
+
throw new Error("No derivatives reference contexts built");
|
|
2518
|
+
}
|
|
2519
|
+
const referenceSymbolsMetadata = [
|
|
2520
|
+
.../* @__PURE__ */ new Set([
|
|
2521
|
+
primaryReferenceSymbol,
|
|
2522
|
+
secondaryReferenceSymbol,
|
|
2523
|
+
...referenceSymbols,
|
|
2524
|
+
...Object.keys(referenceContexts)
|
|
2525
|
+
])
|
|
2526
|
+
];
|
|
2527
|
+
const targetDerived = targetContext && hasDerivativesSymbolData(targetContext) ? buildTargetDerivedContext({
|
|
2528
|
+
targetContext,
|
|
2529
|
+
primaryReferenceContext: primaryContext
|
|
2530
|
+
}) : void 0;
|
|
2531
|
+
return {
|
|
2532
|
+
...primaryContext,
|
|
2533
|
+
targetSymbol,
|
|
2534
|
+
primaryReferenceSymbol: primaryContext.symbol,
|
|
2535
|
+
secondaryReferenceSymbol: referenceContexts[secondaryReferenceSymbol]?.symbol ?? secondaryReferenceSymbol,
|
|
2536
|
+
referenceSymbols: referenceSymbolsMetadata,
|
|
2537
|
+
referenceContexts,
|
|
2538
|
+
...targetContext && targetDerived ? {
|
|
2539
|
+
targetContext,
|
|
2540
|
+
targetDerived
|
|
2541
|
+
} : {}
|
|
2542
|
+
};
|
|
2543
|
+
};
|
|
2544
|
+
var isDerivativesContextEnabled = (env) => parseEnabledFlag3(process.env.DERIVATIVES_CONTEXT_ENABLED, env);
|
|
2545
|
+
var isDerivativesTargetContextEnabled = () => parseBooleanFlag(process.env.DERIVATIVES_CONTEXT_TARGET_ENABLED, false);
|
|
2546
|
+
var enrichSignalWithDerivativesContext = async (params) => {
|
|
2547
|
+
const { signal, env, enabled = isDerivativesContextEnabled(env) } = params;
|
|
2548
|
+
if (signal.universe === "tradfi" || !enabled || derivativesContextUnavailable) {
|
|
2549
|
+
return false;
|
|
2550
|
+
}
|
|
2551
|
+
try {
|
|
2552
|
+
const referenceSymbols = getDerivativesContextReferenceSymbols();
|
|
2553
|
+
const targetSymbol = normalizeSymbol(signal.symbol);
|
|
2554
|
+
const lookbackMs = parseLookbackMs();
|
|
2555
|
+
const decisionTimeMs = signal.timestamp + (0, import_data.intervalToMs)(signal.interval);
|
|
2556
|
+
const derivativesEndMs = (0, import_indicators.getLastClosedDerivativesBarStartMs)(
|
|
2557
|
+
decisionTimeMs,
|
|
2558
|
+
"15m"
|
|
2559
|
+
);
|
|
2560
|
+
const contexts = await Promise.all(
|
|
2561
|
+
referenceSymbols.map(async (symbol) => {
|
|
2562
|
+
const rowsByInterval = await (0, import_derivatives.getDerivativesWindow)({
|
|
2563
|
+
symbol,
|
|
2564
|
+
intervals: STORED_INTERVALS,
|
|
2565
|
+
endMs: derivativesEndMs,
|
|
2566
|
+
lookbackMs,
|
|
2567
|
+
...params.abortSignal ? { signal: params.abortSignal } : {}
|
|
2568
|
+
});
|
|
2569
|
+
return [
|
|
2570
|
+
symbol,
|
|
2571
|
+
(0, import_indicators.buildDerivativesContext)({
|
|
2572
|
+
symbol,
|
|
2573
|
+
direction: signal.direction,
|
|
2574
|
+
timestamp: derivativesEndMs,
|
|
2575
|
+
rowsByInterval: withHourlyFallbackRows(rowsByInterval),
|
|
2576
|
+
priceChangePct1h: getSignalPriceChangePct1h(signal),
|
|
2577
|
+
intervals: CONTEXT_INTERVALS
|
|
2578
|
+
})
|
|
2579
|
+
];
|
|
2580
|
+
})
|
|
2581
|
+
);
|
|
2582
|
+
const referenceContexts = Object.fromEntries(contexts);
|
|
2583
|
+
const primaryReferenceSymbol = resolvePrimaryReferenceSymbol2();
|
|
2584
|
+
const secondaryReferenceSymbol = resolveSecondaryReferenceSymbol();
|
|
2585
|
+
const targetContextEnabled = isDerivativesTargetContextEnabled();
|
|
2586
|
+
const referenceTargetContext = targetContextEnabled && targetSymbol !== primaryReferenceSymbol ? referenceContexts[targetSymbol] : void 0;
|
|
2587
|
+
const shouldFetchTargetContext = targetContextEnabled && targetSymbol.length > 0 && !referenceSymbols.some(
|
|
2588
|
+
(referenceSymbol) => referenceSymbol === targetSymbol
|
|
2589
|
+
);
|
|
2590
|
+
const fetchedTargetContext = shouldFetchTargetContext ? await (async () => {
|
|
2591
|
+
const rowsByInterval = await (0, import_derivatives.getDerivativesWindow)({
|
|
2592
|
+
symbol: targetSymbol,
|
|
2593
|
+
intervals: STORED_INTERVALS,
|
|
2594
|
+
endMs: derivativesEndMs,
|
|
2595
|
+
lookbackMs,
|
|
2596
|
+
...params.abortSignal ? { signal: params.abortSignal } : {}
|
|
2597
|
+
});
|
|
2598
|
+
const context = (0, import_indicators.buildDerivativesContext)({
|
|
2599
|
+
symbol: targetSymbol,
|
|
2600
|
+
direction: signal.direction,
|
|
2601
|
+
timestamp: derivativesEndMs,
|
|
2602
|
+
rowsByInterval: withHourlyFallbackRows(rowsByInterval),
|
|
2603
|
+
priceChangePct1h: getSignalPriceChangePct1h(signal),
|
|
2604
|
+
intervals: CONTEXT_INTERVALS
|
|
2605
|
+
});
|
|
2606
|
+
return hasDerivativesSymbolData(context) ? context : void 0;
|
|
2607
|
+
})() : void 0;
|
|
2608
|
+
const targetContext = referenceTargetContext && hasDerivativesSymbolData(referenceTargetContext) ? referenceTargetContext : fetchedTargetContext;
|
|
2609
|
+
const derivativesContext = buildReferenceDerivativesContext({
|
|
2610
|
+
targetSymbol: targetSymbol || signal.symbol,
|
|
2611
|
+
primaryReferenceSymbol,
|
|
2612
|
+
secondaryReferenceSymbol,
|
|
2613
|
+
referenceSymbols,
|
|
2614
|
+
referenceContexts,
|
|
2615
|
+
targetContext
|
|
2616
|
+
});
|
|
2617
|
+
signal.additionalIndicators = {
|
|
2618
|
+
...signal.additionalIndicators ?? {},
|
|
2619
|
+
baseContext: signal.additionalIndicators?.baseContext && typeof signal.additionalIndicators.baseContext === "object" && !Array.isArray(signal.additionalIndicators.baseContext) ? {
|
|
2620
|
+
...signal.additionalIndicators.baseContext,
|
|
2621
|
+
derivatives: derivativesContext
|
|
2622
|
+
} : signal.additionalIndicators?.baseContext
|
|
2623
|
+
};
|
|
2624
|
+
(0, import_strategies3.refreshSignalBaseContextGateFeatures)(signal);
|
|
2625
|
+
return true;
|
|
2626
|
+
} catch (error) {
|
|
2627
|
+
if (isMarketContextCancellationError(error, params.abortSignal)) {
|
|
2628
|
+
throw error;
|
|
2629
|
+
}
|
|
2630
|
+
derivativesContextUnavailable = true;
|
|
2631
|
+
import_logger5.logger.warn(
|
|
2632
|
+
"Derivatives context disabled after Timescale read failure: %s",
|
|
2633
|
+
String(error)
|
|
2634
|
+
);
|
|
2635
|
+
return false;
|
|
2636
|
+
}
|
|
2637
|
+
};
|
|
2638
|
+
|
|
2639
|
+
// src/strategyHelpers/hyperliquidWhaleContext.ts
|
|
2640
|
+
var import_data2 = require("@tradejs/core/data");
|
|
2641
|
+
var import_strategies4 = require("@tradejs/core/strategies");
|
|
2642
|
+
var import_hyperliquidWhales2 = require("@tradejs/infra/timescale/hyperliquidWhales");
|
|
2643
|
+
var import_logger6 = require("@tradejs/infra/logger");
|
|
2644
|
+
|
|
2645
|
+
// src/hyperliquidWhaleUniverse.ts
|
|
2646
|
+
var import_node_crypto3 = require("crypto");
|
|
2647
|
+
|
|
2648
|
+
// src/config/hyperliquidPerpUniverse.json
|
|
2649
|
+
var hyperliquidPerpUniverse_default = {
|
|
2650
|
+
schemaVersion: 1,
|
|
2651
|
+
updatedAt: "2026-08-04T11:46:43.476Z",
|
|
2652
|
+
source: "hyperliquid_main_perp_day_notional_volume",
|
|
2653
|
+
fingerprint: "58f7e024b19158dc",
|
|
2654
|
+
size: 30,
|
|
2655
|
+
symbols: [
|
|
2656
|
+
"BTC",
|
|
2657
|
+
"ETH",
|
|
2658
|
+
"HYPE",
|
|
2659
|
+
"SOL",
|
|
2660
|
+
"ZEC",
|
|
2661
|
+
"PUMP",
|
|
2662
|
+
"XRP",
|
|
2663
|
+
"CASHCAT",
|
|
2664
|
+
"WLD",
|
|
2665
|
+
"GRAM",
|
|
2666
|
+
"UNI",
|
|
2667
|
+
"ADA",
|
|
2668
|
+
"LIT",
|
|
2669
|
+
"ENA",
|
|
2670
|
+
"NEAR",
|
|
2671
|
+
"kPEPE",
|
|
2672
|
+
"FARTCOIN",
|
|
2673
|
+
"KAITO",
|
|
2674
|
+
"AAVE",
|
|
2675
|
+
"SUI",
|
|
2676
|
+
"ONDO",
|
|
2677
|
+
"AVAX",
|
|
2678
|
+
"DOGE",
|
|
2679
|
+
"VVV",
|
|
2680
|
+
"BNB",
|
|
2681
|
+
"ALGO",
|
|
2682
|
+
"XMR",
|
|
2683
|
+
"MEGA",
|
|
2684
|
+
"LINK",
|
|
2685
|
+
"TAO"
|
|
2686
|
+
]
|
|
2687
|
+
};
|
|
2688
|
+
|
|
2689
|
+
// src/config/hyperliquidWhales.json
|
|
2690
|
+
var hyperliquidWhales_default = {
|
|
2691
|
+
schemaVersion: 2,
|
|
2692
|
+
updatedAt: "2026-08-04T15:10:21.316Z",
|
|
2693
|
+
source: "hyperliquid_structural_fills_snapshot",
|
|
2694
|
+
selection: {
|
|
2695
|
+
calibrationFrom: "2026-07-28T00:00:00.000Z",
|
|
2696
|
+
calibrationTo: "2026-08-04T00:00:00.000Z",
|
|
2697
|
+
effectiveFrom: "2026-08-04T00:00:00.000Z",
|
|
2698
|
+
candidateLimit: 400,
|
|
2699
|
+
minimumAccountValueUsd: 5e5,
|
|
2700
|
+
minimumActiveDays: 1,
|
|
2701
|
+
maximumRawFillsPerDay: 20,
|
|
2702
|
+
maximumDirectionalExecutionsPerDay: 10,
|
|
2703
|
+
minimumMedianNotionalUsd: 0,
|
|
2704
|
+
minimumMedianInterExecutionMinutes: 0,
|
|
2705
|
+
minimumTop30NotionalShare: 1e-3,
|
|
2706
|
+
minimumTurnoverToEquity: 0.05,
|
|
2707
|
+
maximumTurnoverToEquity: 5,
|
|
2708
|
+
score: "log1p(accountValue)*log1p(medianExecutionNotional)*log1p(max(1,medianInterExecutionMinutes))*sqrt(activeDays)*top30Share/sqrt(1+rawFillsPerDay)",
|
|
2709
|
+
forbiddenSelectionMetrics: ["pnl", "roi", "winRate", "closedPnl"]
|
|
2710
|
+
},
|
|
2711
|
+
fingerprint: "77a6e9d41222efdb",
|
|
2712
|
+
size: 100,
|
|
2713
|
+
addresses: [
|
|
2714
|
+
"0xacb66d1996501bcc82a0e7819c9e041599f5efbd",
|
|
2715
|
+
"0x90dbb196cacf24bd212b377a0dd62eaad5e8151b",
|
|
2716
|
+
"0xde5b8c8e713f7c29ec72c22a326d61e5ac76e402",
|
|
2717
|
+
"0x22edc38749b59c31c9319eae7fc62143ac2fcc6c",
|
|
2718
|
+
"0x4bc9a771cd2c96cb2a4501f2c049402b4e7e11b3",
|
|
2719
|
+
"0x9a0825ca6c4c577a1202a8fae3b8f044c1d5711d",
|
|
2720
|
+
"0xb6ca5d1ed0ca4bf8444c9dee2d068a9f4c5e2e92",
|
|
2721
|
+
"0xc3610d02548cdb1a30ede87c5dc0d2ffe5b9b99f",
|
|
2722
|
+
"0x48c6aaa647ad5197e85797f8eb95d7b9ce1ec5b6",
|
|
2723
|
+
"0x8c1ab015da7e5319649afca4a551924f89eff883",
|
|
2724
|
+
"0x64bddab5f13a99576fca6747f0aa205422c5f708",
|
|
2725
|
+
"0x98785f2a43fe32f972476b86ed5a5cfaf5240f00",
|
|
2726
|
+
"0x9836e286fbf4f71998827d4bd6ea3b470c26457e",
|
|
2727
|
+
"0x8f8d2d2565bfb10608a7ce64b48e2aea7875a344",
|
|
2728
|
+
"0xe6df4ed7a6ccb8426cbc87bdcdd3cc6d0e6c0690",
|
|
2729
|
+
"0xab04d040dec929a204298d44701476968b238b69",
|
|
2730
|
+
"0x870f718a84843abc3e22412a9b4dc2924466d448",
|
|
2731
|
+
"0x12c206d3e2f0d0059420c54d33fe604ebbecc230",
|
|
2732
|
+
"0x7d9e91a8a47eb553cbfd323e7633c0c213327124",
|
|
2733
|
+
"0x2057d4f2b6ea957b63371980775b0a5505a438f0",
|
|
2734
|
+
"0xead5b7d86c681c036c59cd00a0390541061c69f2",
|
|
2735
|
+
"0x3005fade4c0df5e1cd187d7062da359416f0eb8e",
|
|
2736
|
+
"0xe83b5e3644b50e802a25b34b38b91a061a0d3ca8",
|
|
2737
|
+
"0x525232cb6ed5030d2b052b36bfc0baec96986ee3",
|
|
2738
|
+
"0xa251520154ca342f0b1d702bf5a56f78c982405b",
|
|
2739
|
+
"0xa9184bb57fd75c7c63e4c074d6319eceac6be53b",
|
|
2740
|
+
"0xa20fb0c9e04063eec5be286e9269028d966646fa",
|
|
2741
|
+
"0x169ba96d963e77a87dbe58922ecdcdd0f0cf3315",
|
|
2742
|
+
"0x115849ce84370f25cadcf0d348510d73837e1aa5",
|
|
2743
|
+
"0x37b81ab9e3ab04b9e3738e4891621205aaa31fd5",
|
|
2744
|
+
"0x90f57d90a7dd3114312d4c31803ad5732baf5935",
|
|
2745
|
+
"0xad227f63d34e7251c1d0ab65e64eeea07aee4e44",
|
|
2746
|
+
"0xd38e2b0b3c85ae138532f9fe1a1d5f5455866b82",
|
|
2747
|
+
"0xf5f4c3aa62a0f903a8529905031b1536d1cf16f8",
|
|
2748
|
+
"0x75632aaf83120eae9b0618f8f50773ce347de2ad",
|
|
2749
|
+
"0x3927af597c09774f91ef84bf765ded4109e9f8ea",
|
|
2750
|
+
"0xf1863bd6d60a8f4b5f583ec54009af6a044877bf",
|
|
2751
|
+
"0xe7b34f28c54f85533896f8827edc92edac28b17f",
|
|
2752
|
+
"0x1cda20f9f6a23c209d54940af3ef63b96c9b2c31",
|
|
2753
|
+
"0x9e55b0e43b7f7f3909af841d326b2dadfda9392e",
|
|
2754
|
+
"0x610b8dd6b392d010c2b6ad8d121dfc09524cb613",
|
|
2755
|
+
"0x1d639a688b78f3d7e57166e59d9a8886b1bfb5a5",
|
|
2756
|
+
"0x34d3a17095ef7def39a489d63c7c916e673c9ca2",
|
|
2757
|
+
"0x5d0bde4017530ed43370e332a5d30194941f73c6",
|
|
2758
|
+
"0xaa7afc250041e2c56a06d3b7a5295bd01929e11b",
|
|
2759
|
+
"0x325edd95bb016c36027ce3b9f7595af7094a9564",
|
|
2760
|
+
"0x47f5a6397b79deaaca5c456503e7c1a49357b942",
|
|
2761
|
+
"0xf5e7ffe523d0491709958490b4877e1d9de6c6f7",
|
|
2762
|
+
"0x64702807a1f0f9888ea22b858099d7f481227b55",
|
|
2763
|
+
"0xd08a4557671ca465e7bba41110a93148c90f9931",
|
|
2764
|
+
"0x0423b4b2acb4f219ae4a270abd5dde2360e455bb",
|
|
2765
|
+
"0x7b29f8c0ea709b96b72214b3de5d85c3d9570794",
|
|
2766
|
+
"0xeb8ebad162a9ce4cb7e2325c92b960b9df63365c",
|
|
2767
|
+
"0x939f95036d2e7b6d7419ec072bf9d967352204d2",
|
|
2768
|
+
"0x2490aa2b48022e857d44e738911787c8e2296aed",
|
|
2769
|
+
"0x7622f628135378f1b8d1876952de9bab80245f1c",
|
|
2770
|
+
"0x9d677028e0e592d48fc8a8ddc910693301a4a450",
|
|
2771
|
+
"0x85e410e4c535cb1709dbd36468f132037a5b2087",
|
|
2772
|
+
"0xd6f52e2ab58a678eced4a4d1693757b0677631c8",
|
|
2773
|
+
"0x24d8969e2bd6aabb6c4dfe042ef8b2cd8085217e",
|
|
2774
|
+
"0xda51323fe9800c8365646ad5c7ade0dd17fdc167",
|
|
2775
|
+
"0x53f8f390fd4f70941c5d160a964f6893c8dbceff",
|
|
2776
|
+
"0x72dae3560e7f4c59a305e2e739d6bd6777f738b5",
|
|
2777
|
+
"0x36f5e448c9cf0032551e58919cf4eabefcd742c1",
|
|
2778
|
+
"0x5b43b7162694de766c164202df0f0c716e4292ab",
|
|
2779
|
+
"0xae551d73161bac3315c5ade0e2d499a44ebe2236",
|
|
2780
|
+
"0x4eca8d62e07b3f3e78a8ae8725d4666da324c5a8",
|
|
2781
|
+
"0x40a632026febd007b0e4431a29170507f7aa3896",
|
|
2782
|
+
"0xd453e91f692eb5437cf345e09545827576e3225d",
|
|
2783
|
+
"0xbe2c69eb3d7b8ee35cbe3a28293472be31179d75",
|
|
2784
|
+
"0xfdf891f2b214a4c9374d26595ec6d4080262e381",
|
|
2785
|
+
"0xfd97600ac44b3c4e20ac1a5f23e3b18d10fa5912",
|
|
2786
|
+
"0x98e073b579fd483eac8f10d5bd0b32c8c3bbd7e0",
|
|
2787
|
+
"0xb56f19e917546a60d658110a611cf62a6877b517",
|
|
2788
|
+
"0xbd334a55db91617545e8d1b09941e036ad8e4200",
|
|
2789
|
+
"0x9f1920d0cbb63ed03376a1e09fd2851d601234c8",
|
|
2790
|
+
"0xe9cef9fd872a03cedf9d3e6e90309782b3724ea6",
|
|
2791
|
+
"0xa43682a1f129e4faf0152175cc03c44e4957e8cf",
|
|
2792
|
+
"0x6fbb33de5f39c690d2decb95b9ab46ed0c83e3bc",
|
|
2793
|
+
"0x81d4163289cf61a0eaaac4862cb88ef0f4215c29",
|
|
2794
|
+
"0xd248d2f09bfbe04e67fc7fea08828d6ad6d95b6d",
|
|
2795
|
+
"0x01ffcc7285866e5cee14c8e807cb2e8d5f61079f",
|
|
2796
|
+
"0xc69ae428f6049e78d445f053d2c1df879c59b34c",
|
|
2797
|
+
"0xaab4dfe6d735c4ac46217216fe883a39fbfe8284",
|
|
2798
|
+
"0xe912cac1a6641004a8803687ee7699227fdb0550",
|
|
2799
|
+
"0x5fdbf83c3d95d525704f61401db68cbec1b2a775",
|
|
2800
|
+
"0xebd7c4b3677a4480fb5f69ac2f51c51d66a19990",
|
|
2801
|
+
"0xe4d1fa7e6141f9131345117bdb8b5701bca67662",
|
|
2802
|
+
"0x685feceec46dd4e5c9b5b726f5d7550fd0eda526",
|
|
2803
|
+
"0x3ea8b09da72972e953541500721803a187526da6",
|
|
2804
|
+
"0xdbe0780a5c5a0e3b345101bbc01ac2218196d6d5",
|
|
2805
|
+
"0x18cd4597e06b7fe0a8cd33dda499121b3a145a8b",
|
|
2806
|
+
"0xb8243756956e722bff5c763971b562ac551bfbd2",
|
|
2807
|
+
"0xe0bf8c96795d2df0b61b3b17e7cfd7d3ba13d4be",
|
|
2808
|
+
"0x218a65e21eddeece7a9df38c6bbdd89f692b7da2",
|
|
2809
|
+
"0x53b63a30a688beb53b5dc7bd731c661d678c555c",
|
|
2810
|
+
"0xfce053a5e461683454bf37ad66d20344c0e3f4c0",
|
|
2811
|
+
"0x3e516cf3c9d4f29fae6c1324c2414dc872fc9c09",
|
|
2812
|
+
"0xe5f1486dc84706d2509885fe6bbc46d21ba81286",
|
|
2813
|
+
"0x38042d713af713f0275b00640a83654db91ea543"
|
|
2814
|
+
]
|
|
2815
|
+
};
|
|
2816
|
+
|
|
2817
|
+
// src/hyperliquidWhaleUniverse.ts
|
|
2818
|
+
var fingerprint2 = (value) => (0, import_node_crypto3.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
|
|
2819
|
+
var validateUnique = (values, label) => {
|
|
2820
|
+
if (new Set(values).size !== values.length) {
|
|
2821
|
+
throw new Error(`Hyperliquid ${label} snapshot contains duplicates`);
|
|
2822
|
+
}
|
|
2823
|
+
};
|
|
2824
|
+
var validatePerpSnapshot = (value) => {
|
|
2825
|
+
const symbols = value.symbols.map((symbol) => symbol.trim()).filter(Boolean);
|
|
2826
|
+
validateUnique(symbols, "perp universe");
|
|
2827
|
+
if (value.schemaVersion !== 1 || value.source !== "hyperliquid_main_perp_day_notional_volume" || value.size !== 30 || symbols.length !== 30 || value.fingerprint !== fingerprint2(symbols)) {
|
|
2828
|
+
throw new Error("Invalid Hyperliquid perp universe snapshot");
|
|
2829
|
+
}
|
|
2830
|
+
return value;
|
|
2831
|
+
};
|
|
2832
|
+
var validateWhaleSnapshot = (value) => {
|
|
2833
|
+
const addresses = value.addresses.map((address) => address.toLowerCase());
|
|
2834
|
+
validateUnique(addresses, "whale registry");
|
|
2835
|
+
if (value.schemaVersion !== 2 || value.source !== "hyperliquid_structural_fills_snapshot" || value.size !== 100 || addresses.length !== 100 || addresses.some((address) => !/^0x[0-9a-f]{40}$/.test(address)) || value.fingerprint !== fingerprint2(addresses)) {
|
|
2836
|
+
throw new Error("Invalid Hyperliquid whale registry snapshot");
|
|
2837
|
+
}
|
|
2838
|
+
return value;
|
|
2839
|
+
};
|
|
2840
|
+
var perpSnapshot = validatePerpSnapshot(hyperliquidPerpUniverse_default);
|
|
2841
|
+
var whaleSnapshot = validateWhaleSnapshot(hyperliquidWhales_default);
|
|
2842
|
+
var perpSymbolSet = new Set(perpSnapshot.symbols);
|
|
2843
|
+
var whaleAddressSet = new Set(whaleSnapshot.addresses);
|
|
2844
|
+
var getHyperliquidPerpUniverseSnapshot = () => perpSnapshot;
|
|
2845
|
+
var getHyperliquidWhaleRegistrySnapshot = () => whaleSnapshot;
|
|
2846
|
+
var SIGNAL_TO_HYPERLIQUID_ALIASES = {
|
|
2847
|
+
PEPE: "kPEPE",
|
|
2848
|
+
"1000PEPE": "kPEPE",
|
|
2849
|
+
PUMPFUN: "PUMP",
|
|
2850
|
+
SHIB: "kSHIB",
|
|
2851
|
+
BONK: "kBONK"
|
|
2852
|
+
};
|
|
2853
|
+
var resolveHyperliquidPerpFromSignalSymbol = (symbol) => {
|
|
2854
|
+
const normalized = symbol.trim().toUpperCase().replace(/(?:USDT|USDC)$/, "");
|
|
2855
|
+
const candidate = SIGNAL_TO_HYPERLIQUID_ALIASES[normalized] ?? normalized;
|
|
2856
|
+
return perpSymbolSet.has(candidate) ? candidate : null;
|
|
2857
|
+
};
|
|
2858
|
+
|
|
2859
|
+
// src/strategyHelpers/hyperliquidWhaleContext.ts
|
|
2860
|
+
var MAX_CACHE_ENTRIES = 2048;
|
|
2861
|
+
var MAX_COVERAGE_SERIES_CACHE_ENTRIES = 20;
|
|
2862
|
+
var MAX_FLOW_SERIES_CACHE_ENTRIES = 64;
|
|
2863
|
+
var SERIES_CHUNK_MS = 90 * 24 * 60 * 6e4;
|
|
2864
|
+
var MAX_SERIES_LOOKBACK_MS = 60 * 6e4;
|
|
2865
|
+
var DEFAULT_MIN_COVERAGE_PCT = 0.8;
|
|
2866
|
+
var INTERVAL_MS = {
|
|
2867
|
+
"1m": 6e4,
|
|
2868
|
+
"5m": 5 * 6e4,
|
|
2869
|
+
"15m": 15 * 6e4,
|
|
2870
|
+
"1h": 60 * 6e4
|
|
2871
|
+
};
|
|
2872
|
+
var DEFAULT_MAX_AGE_BY_INTERVAL2 = {
|
|
2873
|
+
"1m": 2 * 6e4,
|
|
2874
|
+
"5m": 10 * 6e4,
|
|
2875
|
+
"15m": 30 * 6e4,
|
|
2876
|
+
"1h": 2 * 60 * 6e4
|
|
2877
|
+
};
|
|
2878
|
+
var hyperliquidWhaleContextUnavailable = false;
|
|
2879
|
+
var contextCache = /* @__PURE__ */ new Map();
|
|
2880
|
+
var coverageSeriesCache = /* @__PURE__ */ new Map();
|
|
2881
|
+
var flowSeriesCache = /* @__PURE__ */ new Map();
|
|
2882
|
+
var parseEnabledFlag4 = (value, env) => {
|
|
2883
|
+
const normalized = String(value ?? "").trim().toLowerCase();
|
|
2884
|
+
if (!normalized) return env !== "TEST";
|
|
2885
|
+
if (["1", "true", "yes", "on"].includes(normalized)) return true;
|
|
2886
|
+
if (["0", "false", "no", "off"].includes(normalized)) return false;
|
|
2887
|
+
if (normalized === "backtest") return env === "BACKTEST";
|
|
2888
|
+
if (normalized === "live") return env !== "BACKTEST";
|
|
2889
|
+
return false;
|
|
2890
|
+
};
|
|
2891
|
+
var getMinimumCoveragePct = () => {
|
|
2892
|
+
const parsed = Number(process.env.HYPERLIQUID_WHALE_MIN_COVERAGE_PCT);
|
|
2893
|
+
return Number.isFinite(parsed) ? Math.max(0, Math.min(1, parsed)) : DEFAULT_MIN_COVERAGE_PCT;
|
|
2894
|
+
};
|
|
2895
|
+
var signalIntervalToMarketInterval2 = (value) => {
|
|
2896
|
+
const normalized = String(value).trim().toLowerCase();
|
|
2897
|
+
if (normalized === "1" || normalized === "1m") return "1m";
|
|
2898
|
+
if (normalized === "5" || normalized === "5m") return "5m";
|
|
2899
|
+
if (normalized === "60" || normalized === "1h") return "1h";
|
|
2900
|
+
return "15m";
|
|
2901
|
+
};
|
|
2902
|
+
var hasBaseContext3 = (signal) => Boolean(
|
|
2903
|
+
signal.additionalIndicators?.baseContext && typeof signal.additionalIndicators.baseContext === "object" && !Array.isArray(signal.additionalIndicators.baseContext)
|
|
2904
|
+
);
|
|
2905
|
+
var setBoundedCache = (key, value) => {
|
|
2906
|
+
if (contextCache.size >= MAX_CACHE_ENTRIES) {
|
|
2907
|
+
const oldestKey = contextCache.keys().next().value;
|
|
2908
|
+
if (oldestKey != null) contextCache.delete(oldestKey);
|
|
2909
|
+
}
|
|
2910
|
+
contextCache.set(key, value);
|
|
2911
|
+
void value.catch(() => contextCache.delete(key));
|
|
2912
|
+
};
|
|
2913
|
+
var setBoundedSeriesCache = (cache, maxEntries, key, value) => {
|
|
2914
|
+
if (cache.size >= maxEntries) {
|
|
2915
|
+
const oldestKey = cache.keys().next().value;
|
|
2916
|
+
if (oldestKey != null) cache.delete(oldestKey);
|
|
2917
|
+
}
|
|
2918
|
+
cache.set(key, value);
|
|
2919
|
+
void value.catch(() => cache.delete(key));
|
|
2920
|
+
};
|
|
2921
|
+
var uniqueAddressCount = (rows, field) => {
|
|
2922
|
+
const addresses = /* @__PURE__ */ new Set();
|
|
2923
|
+
for (const row of rows) {
|
|
2924
|
+
for (const address of row[field]) addresses.add(address);
|
|
2925
|
+
}
|
|
2926
|
+
return addresses.size;
|
|
2927
|
+
};
|
|
2928
|
+
var sumFlowField = (rows, field) => rows.reduce((sum, row) => sum + row[field], 0);
|
|
2929
|
+
var sliceSeriesWindow = (rows, fromMs, toMs) => {
|
|
2930
|
+
const lowerBound = (timestamp) => {
|
|
2931
|
+
let low = 0;
|
|
2932
|
+
let high = rows.length;
|
|
2933
|
+
while (low < high) {
|
|
2934
|
+
const middle = low + Math.floor((high - low) / 2);
|
|
2935
|
+
if (rows[middle].ts.getTime() < timestamp) low = middle + 1;
|
|
2936
|
+
else high = middle;
|
|
2937
|
+
}
|
|
2938
|
+
return low;
|
|
2939
|
+
};
|
|
2940
|
+
return rows.slice(lowerBound(fromMs), lowerBound(toMs));
|
|
2941
|
+
};
|
|
2942
|
+
var aggregateHyperliquidWhaleFlowSeries = (params) => {
|
|
2943
|
+
const intervalMs = INTERVAL_MS[params.interval];
|
|
2944
|
+
const windowStartMs = params.decisionTimeMs - intervalMs;
|
|
2945
|
+
const coverageRows = sliceSeriesWindow(
|
|
2946
|
+
params.coverageRows,
|
|
2947
|
+
windowStartMs,
|
|
2948
|
+
params.decisionTimeMs
|
|
2949
|
+
);
|
|
2950
|
+
const expectedBuckets = Math.ceil(intervalMs / 6e4);
|
|
2951
|
+
if (coverageRows.length !== expectedBuckets || coverageRows.some((row) => row.coveredWhales <= 0)) {
|
|
2952
|
+
return null;
|
|
2953
|
+
}
|
|
2954
|
+
const flowRows = sliceSeriesWindow(
|
|
2955
|
+
params.flowRows,
|
|
2956
|
+
windowStartMs,
|
|
2957
|
+
params.decisionTimeMs
|
|
2958
|
+
);
|
|
2959
|
+
const asOfTs = new Date(
|
|
2960
|
+
Math.max(...coverageRows.map((row) => row.ts.getTime()))
|
|
2961
|
+
);
|
|
2962
|
+
const coveredWhales = Math.min(
|
|
2963
|
+
...coverageRows.map((row) => row.coveredWhales)
|
|
2964
|
+
);
|
|
2965
|
+
const expectedWhales = Math.max(
|
|
2966
|
+
...coverageRows.map((row) => row.expectedWhales)
|
|
2967
|
+
);
|
|
2968
|
+
const coveragePct = Math.min(...coverageRows.map((row) => row.coveragePct));
|
|
2969
|
+
const trades = sumFlowField(flowRows, "trades");
|
|
2970
|
+
const whaleSides = sumFlowField(flowRows, "whaleSides");
|
|
2971
|
+
const buyNotionalUsd = sumFlowField(flowRows, "buyNotionalUsd");
|
|
2972
|
+
const sellNotionalUsd = sumFlowField(flowRows, "sellNotionalUsd");
|
|
2973
|
+
const positionAwareWhaleSides = sumFlowField(
|
|
2974
|
+
flowRows,
|
|
2975
|
+
"positionAwareWhaleSides"
|
|
2976
|
+
);
|
|
2977
|
+
const longEntryNotionalUsd = sumFlowField(flowRows, "longEntryNotionalUsd");
|
|
2978
|
+
const shortEntryNotionalUsd = sumFlowField(flowRows, "shortEntryNotionalUsd");
|
|
2979
|
+
const longExitNotionalUsd = sumFlowField(flowRows, "longExitNotionalUsd");
|
|
2980
|
+
const shortExitNotionalUsd = sumFlowField(flowRows, "shortExitNotionalUsd");
|
|
2981
|
+
const totalNotionalUsd = buyNotionalUsd + sellNotionalUsd;
|
|
2982
|
+
const totalEntryNotionalUsd = longEntryNotionalUsd + shortEntryNotionalUsd;
|
|
2983
|
+
const ageMs = params.decisionTimeMs - (asOfTs.getTime() + 6e4);
|
|
2984
|
+
return {
|
|
2985
|
+
symbol: params.symbol,
|
|
2986
|
+
interval: params.interval,
|
|
2987
|
+
asOfTs,
|
|
2988
|
+
windowEndTs: new Date(params.decisionTimeMs),
|
|
2989
|
+
trades,
|
|
2990
|
+
whaleSides,
|
|
2991
|
+
uniqueWhales: uniqueAddressCount(flowRows, "whaleAddresses"),
|
|
2992
|
+
coveredWhales,
|
|
2993
|
+
expectedWhales,
|
|
2994
|
+
coveragePct,
|
|
2995
|
+
buyNotionalUsd,
|
|
2996
|
+
sellNotionalUsd,
|
|
2997
|
+
netNotionalUsd: buyNotionalUsd - sellNotionalUsd,
|
|
2998
|
+
buySharePct: totalNotionalUsd > 0 ? buyNotionalUsd / totalNotionalUsd : null,
|
|
2999
|
+
positionAwareWhaleSides,
|
|
3000
|
+
positionAwarePct: whaleSides > 0 ? positionAwareWhaleSides / whaleSides : 0,
|
|
3001
|
+
longEntryWhales: uniqueAddressCount(flowRows, "longEntryWhaleAddresses"),
|
|
3002
|
+
shortEntryWhales: uniqueAddressCount(flowRows, "shortEntryWhaleAddresses"),
|
|
3003
|
+
longExitWhales: uniqueAddressCount(flowRows, "longExitWhaleAddresses"),
|
|
3004
|
+
shortExitWhales: uniqueAddressCount(flowRows, "shortExitWhaleAddresses"),
|
|
3005
|
+
longEntryNotionalUsd,
|
|
3006
|
+
shortEntryNotionalUsd,
|
|
3007
|
+
longExitNotionalUsd,
|
|
3008
|
+
shortExitNotionalUsd,
|
|
3009
|
+
entryNetNotionalUsd: longEntryNotionalUsd - shortEntryNotionalUsd,
|
|
3010
|
+
entryLongSharePct: totalEntryNotionalUsd > 0 ? longEntryNotionalUsd / totalEntryNotionalUsd : null,
|
|
3011
|
+
universeFingerprint: params.universeFingerprint,
|
|
3012
|
+
whaleRegistryFingerprint: params.whaleRegistryFingerprint,
|
|
3013
|
+
source: positionAwareWhaleSides > 0 ? "hyperliquid_user_fills" : null,
|
|
3014
|
+
ageMs,
|
|
3015
|
+
stale: ageMs < 0 || (params.maxAgeMs != null && Number.isFinite(params.maxAgeMs) ? ageMs > params.maxAgeMs : false)
|
|
3016
|
+
};
|
|
3017
|
+
};
|
|
3018
|
+
var loadHyperliquidWhaleFlowAggregateFromSeries = async (params) => {
|
|
3019
|
+
const chunkStartMs = Math.floor(params.decisionTimeMs / SERIES_CHUNK_MS) * SERIES_CHUNK_MS;
|
|
3020
|
+
const fromMs = chunkStartMs - MAX_SERIES_LOOKBACK_MS;
|
|
3021
|
+
const toMs = chunkStartMs + SERIES_CHUNK_MS;
|
|
3022
|
+
const sharedKey = [
|
|
3023
|
+
fromMs,
|
|
3024
|
+
toMs,
|
|
3025
|
+
params.universeFingerprint,
|
|
3026
|
+
params.whaleRegistryFingerprint
|
|
3027
|
+
].join(":");
|
|
3028
|
+
let pendingCoverage = coverageSeriesCache.get(sharedKey);
|
|
3029
|
+
if (!pendingCoverage) {
|
|
3030
|
+
pendingCoverage = (0, import_hyperliquidWhales2.getHyperliquidWhaleCoverageSeriesRows)({
|
|
3031
|
+
fromMs,
|
|
3032
|
+
toMs,
|
|
3033
|
+
universeFingerprint: params.universeFingerprint,
|
|
3034
|
+
whaleRegistryFingerprint: params.whaleRegistryFingerprint,
|
|
3035
|
+
...params.abortSignal ? { signal: params.abortSignal } : {}
|
|
3036
|
+
});
|
|
3037
|
+
setBoundedSeriesCache(
|
|
3038
|
+
coverageSeriesCache,
|
|
3039
|
+
MAX_COVERAGE_SERIES_CACHE_ENTRIES,
|
|
3040
|
+
sharedKey,
|
|
3041
|
+
pendingCoverage
|
|
3042
|
+
);
|
|
3043
|
+
}
|
|
3044
|
+
const flowKey = `${params.symbol}:${sharedKey}`;
|
|
3045
|
+
let pendingFlow = flowSeriesCache.get(flowKey);
|
|
3046
|
+
if (!pendingFlow) {
|
|
3047
|
+
pendingFlow = (0, import_hyperliquidWhales2.getHyperliquidWhaleFlowSeriesRows)({
|
|
3048
|
+
symbol: params.symbol,
|
|
3049
|
+
fromMs,
|
|
3050
|
+
toMs,
|
|
3051
|
+
universeFingerprint: params.universeFingerprint,
|
|
3052
|
+
whaleRegistryFingerprint: params.whaleRegistryFingerprint,
|
|
3053
|
+
...params.abortSignal ? { signal: params.abortSignal } : {}
|
|
3054
|
+
});
|
|
3055
|
+
setBoundedSeriesCache(
|
|
3056
|
+
flowSeriesCache,
|
|
3057
|
+
MAX_FLOW_SERIES_CACHE_ENTRIES,
|
|
3058
|
+
flowKey,
|
|
3059
|
+
pendingFlow
|
|
3060
|
+
);
|
|
3061
|
+
}
|
|
3062
|
+
const [coverageRows, flowRows] = await Promise.all([
|
|
3063
|
+
pendingCoverage,
|
|
3064
|
+
pendingFlow
|
|
3065
|
+
]);
|
|
3066
|
+
return aggregateHyperliquidWhaleFlowSeries({
|
|
3067
|
+
...params,
|
|
3068
|
+
coverageRows,
|
|
3069
|
+
flowRows
|
|
3070
|
+
});
|
|
3071
|
+
};
|
|
3072
|
+
var toBaseHyperliquidWhaleFlowContext = (row, minimumCoveragePct) => ({
|
|
3073
|
+
source: row.positionAwareWhaleSides > 0 ? "hyperliquid_user_fills" : "hyperliquid_trades",
|
|
3074
|
+
interval: row.interval,
|
|
3075
|
+
asOfTs: row.asOfTs.getTime(),
|
|
3076
|
+
windowEndTs: row.windowEndTs.getTime(),
|
|
3077
|
+
ageMs: row.ageMs,
|
|
3078
|
+
stale: row.stale,
|
|
3079
|
+
symbol: row.symbol,
|
|
3080
|
+
trades: row.trades,
|
|
3081
|
+
whaleSides: row.whaleSides,
|
|
3082
|
+
uniqueWhales: row.uniqueWhales,
|
|
3083
|
+
coveredWhales: row.coveredWhales,
|
|
3084
|
+
expectedWhales: row.expectedWhales,
|
|
3085
|
+
coveragePct: row.coveragePct,
|
|
3086
|
+
coverageSufficient: row.coveragePct >= minimumCoveragePct,
|
|
3087
|
+
buyNotionalUsd: row.buyNotionalUsd,
|
|
3088
|
+
sellNotionalUsd: row.sellNotionalUsd,
|
|
3089
|
+
netNotionalUsd: row.netNotionalUsd,
|
|
3090
|
+
buySharePct: row.buySharePct,
|
|
3091
|
+
positionAwareWhaleSides: row.positionAwareWhaleSides,
|
|
3092
|
+
positionAwarePct: row.positionAwarePct,
|
|
3093
|
+
longEntryWhales: row.longEntryWhales,
|
|
3094
|
+
shortEntryWhales: row.shortEntryWhales,
|
|
3095
|
+
longExitWhales: row.longExitWhales,
|
|
3096
|
+
shortExitWhales: row.shortExitWhales,
|
|
3097
|
+
longEntryNotionalUsd: row.longEntryNotionalUsd,
|
|
3098
|
+
shortEntryNotionalUsd: row.shortEntryNotionalUsd,
|
|
3099
|
+
longExitNotionalUsd: row.longExitNotionalUsd,
|
|
3100
|
+
shortExitNotionalUsd: row.shortExitNotionalUsd,
|
|
3101
|
+
entryNetNotionalUsd: row.entryNetNotionalUsd,
|
|
3102
|
+
entryLongSharePct: row.entryLongSharePct,
|
|
3103
|
+
universeFingerprint: row.universeFingerprint,
|
|
3104
|
+
whaleRegistryFingerprint: row.whaleRegistryFingerprint
|
|
3105
|
+
});
|
|
3106
|
+
var isHyperliquidWhaleContextEnabled = (env) => parseEnabledFlag4(process.env.HYPERLIQUID_WHALE_CONTEXT_ENABLED, env);
|
|
3107
|
+
var loadHyperliquidWhaleFlowContext = async (params) => {
|
|
3108
|
+
if (!(params.enabled ?? isHyperliquidWhaleContextEnabled(params.env)) || hyperliquidWhaleContextUnavailable || params.interval == null) {
|
|
3109
|
+
return null;
|
|
3110
|
+
}
|
|
3111
|
+
const symbol = resolveHyperliquidPerpFromSignalSymbol(params.symbol);
|
|
3112
|
+
if (!symbol) return null;
|
|
3113
|
+
const interval = params.marketInterval ?? signalIntervalToMarketInterval2(params.interval);
|
|
3114
|
+
const decisionTimeMs = params.timestamp + (0, import_data2.intervalToMs)(params.interval);
|
|
3115
|
+
const maxAgeMs = params.maxAgeMs ?? DEFAULT_MAX_AGE_BY_INTERVAL2[interval];
|
|
3116
|
+
const universe = getHyperliquidPerpUniverseSnapshot();
|
|
3117
|
+
const whales = getHyperliquidWhaleRegistrySnapshot();
|
|
3118
|
+
const minimumCoveragePct = getMinimumCoveragePct();
|
|
3119
|
+
const cacheKey = [
|
|
3120
|
+
symbol,
|
|
3121
|
+
interval,
|
|
3122
|
+
decisionTimeMs,
|
|
3123
|
+
maxAgeMs,
|
|
3124
|
+
universe.fingerprint,
|
|
3125
|
+
whales.fingerprint,
|
|
3126
|
+
minimumCoveragePct
|
|
3127
|
+
].join(":");
|
|
3128
|
+
try {
|
|
3129
|
+
let row;
|
|
3130
|
+
if (params.useSeriesCache) {
|
|
3131
|
+
row = await loadHyperliquidWhaleFlowAggregateFromSeries({
|
|
3132
|
+
symbol,
|
|
3133
|
+
interval,
|
|
3134
|
+
decisionTimeMs,
|
|
3135
|
+
maxAgeMs,
|
|
3136
|
+
universeFingerprint: universe.fingerprint,
|
|
3137
|
+
whaleRegistryFingerprint: whales.fingerprint,
|
|
3138
|
+
abortSignal: params.abortSignal
|
|
3139
|
+
});
|
|
3140
|
+
} else {
|
|
3141
|
+
let pending = contextCache.get(cacheKey);
|
|
3142
|
+
if (!pending) {
|
|
3143
|
+
pending = (0, import_hyperliquidWhales2.getHyperliquidWhaleFlowAggregate)({
|
|
3144
|
+
symbol,
|
|
3145
|
+
interval,
|
|
3146
|
+
decisionTimeMs,
|
|
3147
|
+
maxAgeMs,
|
|
3148
|
+
universeFingerprint: universe.fingerprint,
|
|
3149
|
+
whaleRegistryFingerprint: whales.fingerprint,
|
|
3150
|
+
...params.abortSignal ? { signal: params.abortSignal } : {}
|
|
3151
|
+
});
|
|
3152
|
+
setBoundedCache(cacheKey, pending);
|
|
3153
|
+
}
|
|
3154
|
+
row = await pending;
|
|
3155
|
+
}
|
|
3156
|
+
if (!row) return null;
|
|
3157
|
+
return toBaseHyperliquidWhaleFlowContext(row, minimumCoveragePct);
|
|
3158
|
+
} catch (error) {
|
|
3159
|
+
if (isMarketContextCancellationError(error, params.abortSignal)) {
|
|
3160
|
+
throw error;
|
|
3161
|
+
}
|
|
3162
|
+
hyperliquidWhaleContextUnavailable = true;
|
|
3163
|
+
import_logger6.logger.warn(
|
|
3164
|
+
"Hyperliquid whale context disabled after Timescale read failure: %s",
|
|
3165
|
+
String(error)
|
|
3166
|
+
);
|
|
3167
|
+
return null;
|
|
3168
|
+
}
|
|
3169
|
+
};
|
|
3170
|
+
var enrichSignalWithHyperliquidWhaleContext = async (params) => {
|
|
3171
|
+
const { signal, env } = params;
|
|
3172
|
+
if (signal.universe === "tradfi" || signal.interval == null || !hasBaseContext3(signal)) {
|
|
3173
|
+
return false;
|
|
3174
|
+
}
|
|
3175
|
+
const hyperliquidWhales = await loadHyperliquidWhaleFlowContext({
|
|
3176
|
+
symbol: signal.symbol,
|
|
3177
|
+
interval: signal.interval,
|
|
3178
|
+
timestamp: signal.timestamp,
|
|
3179
|
+
env,
|
|
3180
|
+
enabled: params.enabled,
|
|
3181
|
+
marketInterval: params.interval,
|
|
3182
|
+
maxAgeMs: params.maxAgeMs,
|
|
3183
|
+
abortSignal: params.abortSignal
|
|
3184
|
+
});
|
|
3185
|
+
if (!hyperliquidWhales) return false;
|
|
3186
|
+
const baseContext = signal.additionalIndicators.baseContext;
|
|
3187
|
+
signal.additionalIndicators = {
|
|
3188
|
+
...signal.additionalIndicators,
|
|
3189
|
+
baseContext: {
|
|
3190
|
+
...baseContext,
|
|
3191
|
+
participation: {
|
|
3192
|
+
...baseContext.participation,
|
|
3193
|
+
hyperliquidWhales
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
};
|
|
3197
|
+
(0, import_strategies4.refreshSignalBaseContextGateFeatures)(signal);
|
|
3198
|
+
return true;
|
|
3199
|
+
};
|
|
3200
|
+
|
|
3201
|
+
// src/strategyHelpers/marketContextStages.ts
|
|
3202
|
+
var STAGE_ENV_KEYS = {
|
|
3203
|
+
binance: "BINANCE_MARKET_CONTEXT_STAGE_TIMEOUT_MS",
|
|
3204
|
+
coinmarketcap: "COINMARKETCAP_CONTEXT_STAGE_TIMEOUT_MS",
|
|
3205
|
+
derivatives: "DERIVATIVES_CONTEXT_STAGE_TIMEOUT_MS",
|
|
3206
|
+
hyperliquidWhales: "HYPERLIQUID_WHALE_CONTEXT_STAGE_TIMEOUT_MS"
|
|
3207
|
+
};
|
|
3208
|
+
var parsePositiveInt = (value) => {
|
|
3209
|
+
const parsed = Number.parseInt(String(value ?? ""), 10);
|
|
3210
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
3211
|
+
};
|
|
3212
|
+
var resolveMarketContextStageTimeoutMs = (stage) => parsePositiveInt(process.env[STAGE_ENV_KEYS[stage]]) ?? parsePositiveInt(process.env.MARKET_CONTEXT_STAGE_TIMEOUT_MS) ?? 35e3;
|
|
3213
|
+
var runMarketContextStage = async ({
|
|
3214
|
+
stage,
|
|
3215
|
+
parentSignal,
|
|
3216
|
+
operation,
|
|
3217
|
+
onStart,
|
|
3218
|
+
onComplete
|
|
3219
|
+
}) => {
|
|
3220
|
+
const controller = new AbortController();
|
|
3221
|
+
const timeoutMs = resolveMarketContextStageTimeoutMs(stage);
|
|
3222
|
+
const startedAt = Date.now();
|
|
3223
|
+
const onParentAbort = () => controller.abort(parentSignal?.reason);
|
|
3224
|
+
parentSignal?.addEventListener("abort", onParentAbort, { once: true });
|
|
3225
|
+
if (parentSignal?.aborted) onParentAbort();
|
|
3226
|
+
onStart?.(stage);
|
|
3227
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
3228
|
+
timer.unref?.();
|
|
3229
|
+
try {
|
|
3230
|
+
let available = false;
|
|
3231
|
+
try {
|
|
3232
|
+
available = await operation(controller.signal);
|
|
3233
|
+
} catch (error) {
|
|
3234
|
+
if (isMarketContextCancellationError(error)) {
|
|
3235
|
+
controller.abort(error);
|
|
3236
|
+
}
|
|
3237
|
+
if (!controller.signal.aborted) throw error;
|
|
3238
|
+
}
|
|
3239
|
+
const status = controller.signal.aborted ? "timed_out" : available ? "available" : "absent";
|
|
3240
|
+
const result = {
|
|
3241
|
+
stage,
|
|
3242
|
+
status,
|
|
3243
|
+
elapsedMs: Date.now() - startedAt
|
|
3244
|
+
};
|
|
3245
|
+
if (status === "timed_out") {
|
|
3246
|
+
import_logger7.logger.warn(
|
|
3247
|
+
"Market context stage timed out: %s after %sms",
|
|
3248
|
+
stage,
|
|
3249
|
+
result.elapsedMs
|
|
3250
|
+
);
|
|
3251
|
+
}
|
|
3252
|
+
onComplete?.(result);
|
|
3253
|
+
return result;
|
|
3254
|
+
} finally {
|
|
3255
|
+
clearTimeout(timer);
|
|
3256
|
+
parentSignal?.removeEventListener("abort", onParentAbort);
|
|
3257
|
+
}
|
|
3258
|
+
};
|
|
3259
|
+
var enrichSignalWithMarketContextStages = async ({
|
|
3260
|
+
signal,
|
|
3261
|
+
env,
|
|
3262
|
+
coinMarketCapEnabled,
|
|
3263
|
+
includeHyperliquidWhales = true,
|
|
3264
|
+
abortSignal,
|
|
3265
|
+
onStageStart,
|
|
3266
|
+
onStageComplete
|
|
3267
|
+
}) => {
|
|
3268
|
+
const stages = [
|
|
3269
|
+
{
|
|
3270
|
+
stage: "binance",
|
|
3271
|
+
operation: (stageSignal) => enrichSignalWithBinanceMarketContext({
|
|
3272
|
+
signal,
|
|
3273
|
+
env,
|
|
3274
|
+
abortSignal: stageSignal
|
|
3275
|
+
})
|
|
3276
|
+
},
|
|
3277
|
+
{
|
|
3278
|
+
stage: "coinmarketcap",
|
|
3279
|
+
operation: (stageSignal) => enrichSignalWithCoinMarketCapContext({
|
|
3280
|
+
signal,
|
|
3281
|
+
env,
|
|
3282
|
+
enabled: coinMarketCapEnabled,
|
|
3283
|
+
abortSignal: stageSignal
|
|
3284
|
+
})
|
|
3285
|
+
},
|
|
3286
|
+
{
|
|
3287
|
+
stage: "derivatives",
|
|
3288
|
+
operation: (stageSignal) => enrichSignalWithDerivativesContext({
|
|
3289
|
+
signal,
|
|
3290
|
+
env,
|
|
3291
|
+
abortSignal: stageSignal
|
|
3292
|
+
})
|
|
3293
|
+
}
|
|
3294
|
+
];
|
|
3295
|
+
if (includeHyperliquidWhales) {
|
|
3296
|
+
stages.push({
|
|
3297
|
+
stage: "hyperliquidWhales",
|
|
3298
|
+
operation: (stageSignal) => enrichSignalWithHyperliquidWhaleContext({
|
|
3299
|
+
signal,
|
|
3300
|
+
env,
|
|
3301
|
+
abortSignal: stageSignal
|
|
3302
|
+
})
|
|
3303
|
+
});
|
|
3304
|
+
}
|
|
3305
|
+
const results = [];
|
|
3306
|
+
for (const stage of stages) {
|
|
3307
|
+
if (abortSignal?.aborted) break;
|
|
3308
|
+
results.push(
|
|
3309
|
+
await runMarketContextStage({
|
|
3310
|
+
...stage,
|
|
3311
|
+
parentSignal: abortSignal,
|
|
3312
|
+
onStart: onStageStart,
|
|
3313
|
+
onComplete: onStageComplete
|
|
3314
|
+
})
|
|
3315
|
+
);
|
|
3316
|
+
}
|
|
3317
|
+
return results;
|
|
3318
|
+
};
|
|
3319
|
+
|
|
3320
|
+
// src/strategyHelpers/runtime.ts
|
|
3321
|
+
var formatAiError = (err) => {
|
|
3322
|
+
const error = err;
|
|
3323
|
+
const safeJson = (value) => {
|
|
3324
|
+
try {
|
|
3325
|
+
return JSON.stringify(value);
|
|
3326
|
+
} catch {
|
|
3327
|
+
return String(value);
|
|
3328
|
+
}
|
|
3329
|
+
};
|
|
3330
|
+
const details = {
|
|
3331
|
+
message: String(error?.message ?? "unknown"),
|
|
3332
|
+
status: error?.status ?? null,
|
|
3333
|
+
code: error?.code ?? null,
|
|
3334
|
+
type: error?.type ?? null,
|
|
3335
|
+
providerError: error?.error ?? null
|
|
3336
|
+
};
|
|
3337
|
+
return safeJson(details);
|
|
3338
|
+
};
|
|
3339
|
+
var resolveAiQuality = (analysis, direction) => {
|
|
3340
|
+
if (typeof analysis?.quality !== "number") {
|
|
3341
|
+
return void 0;
|
|
3342
|
+
}
|
|
3343
|
+
const normalizedQuality = Math.round(analysis.quality);
|
|
3344
|
+
const aiApprovedCurrentTrade = analysis.direction === direction;
|
|
3345
|
+
return aiApprovedCurrentTrade ? normalizedQuality : 0;
|
|
3346
|
+
};
|
|
3347
|
+
var findReplayAiAnalysis = ({
|
|
3348
|
+
signal,
|
|
3349
|
+
direction,
|
|
3350
|
+
ai
|
|
3351
|
+
}) => {
|
|
3352
|
+
const snapshots = ai?.replayAnalyses;
|
|
3353
|
+
if (!Array.isArray(snapshots) || !snapshots.length) {
|
|
3354
|
+
return void 0;
|
|
3355
|
+
}
|
|
3356
|
+
let best = null;
|
|
3357
|
+
for (const snapshot2 of snapshots) {
|
|
3358
|
+
if (snapshot2.symbol !== signal.symbol || snapshot2.direction !== direction || snapshot2.strategy && snapshot2.strategy !== signal.strategy) {
|
|
3359
|
+
continue;
|
|
3360
|
+
}
|
|
3361
|
+
const toleranceMs = Math.max(0, Number(snapshot2.toleranceMs ?? 0));
|
|
3362
|
+
const diff = Math.abs(snapshot2.timestamp - signal.timestamp);
|
|
3363
|
+
if (diff > toleranceMs || best && diff >= best.diff) {
|
|
3364
|
+
continue;
|
|
3365
|
+
}
|
|
3366
|
+
best = {
|
|
3367
|
+
diff,
|
|
3368
|
+
analysis: snapshot2.analysis
|
|
3369
|
+
};
|
|
3370
|
+
}
|
|
3371
|
+
return best?.analysis;
|
|
3372
|
+
};
|
|
3373
|
+
var enrichSignalWithMl = async ({
|
|
3374
|
+
signal,
|
|
3375
|
+
env,
|
|
3376
|
+
ml
|
|
3377
|
+
}) => {
|
|
3378
|
+
if (env !== "BACKTEST" && ml && ml.enabled !== false && ml.strategyConfig && typeof ml.mlThreshold === "number") {
|
|
3379
|
+
const strategy = signal.strategy;
|
|
3380
|
+
const fullRow = (0, import_ml2.buildMlTrainingRow)(
|
|
3381
|
+
buildMlPayload({
|
|
3382
|
+
signal,
|
|
3383
|
+
context: {
|
|
3384
|
+
strategyConfig: ml.strategyConfig,
|
|
3385
|
+
strategyName: strategy,
|
|
3386
|
+
symbol: signal.symbol
|
|
3387
|
+
}
|
|
3388
|
+
}),
|
|
3389
|
+
null
|
|
3390
|
+
);
|
|
3391
|
+
const row = (0, import_ml2.trimMlTrainingRowWindows)(fullRow, 5);
|
|
3392
|
+
const features = (0, import_ml2.buildMlFeatures)(row);
|
|
3393
|
+
const mlResult = await (0, import_ml2.fetchMlThreshold)({
|
|
3394
|
+
strategy: ml.modelKey ?? strategy,
|
|
3395
|
+
features,
|
|
3396
|
+
threshold: ml.mlThreshold,
|
|
3397
|
+
projectRoot: getTradejsProjectCwd()
|
|
3398
|
+
});
|
|
3399
|
+
if (mlResult) {
|
|
3400
|
+
signal.ml = mlResult;
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
};
|
|
3404
|
+
var enrichSignalWithAi = async ({
|
|
3405
|
+
signal,
|
|
3406
|
+
symbol,
|
|
3407
|
+
userName,
|
|
3408
|
+
direction,
|
|
3409
|
+
env,
|
|
3410
|
+
ai
|
|
3411
|
+
}) => {
|
|
3412
|
+
if (ai?.enabled === false) {
|
|
3413
|
+
return void 0;
|
|
3414
|
+
}
|
|
3415
|
+
if (env === "PARITY") {
|
|
3416
|
+
const replayAnalysis = findReplayAiAnalysis({ signal, direction, ai });
|
|
3417
|
+
if (replayAnalysis) {
|
|
3418
|
+
signal.aiAnalysis = replayAnalysis;
|
|
3419
|
+
return resolveAiQuality(replayAnalysis, direction);
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
if (env === "BACKTEST") {
|
|
3423
|
+
return void 0;
|
|
3424
|
+
}
|
|
3425
|
+
if (ai?.mode === "gate") {
|
|
3426
|
+
const gateAnalysis = await runAiPromptLocal(signal);
|
|
3427
|
+
const gateQuality = resolveAiQuality(gateAnalysis, direction);
|
|
3428
|
+
signal.aiAnalysis = gateAnalysis;
|
|
3429
|
+
return gateQuality;
|
|
3430
|
+
}
|
|
3431
|
+
try {
|
|
3432
|
+
const analysis = await askAI(signal, { userName });
|
|
3433
|
+
signal.aiAnalysis = analysis;
|
|
3434
|
+
return resolveAiQuality(analysis, direction);
|
|
3435
|
+
} catch (err) {
|
|
3436
|
+
import_logger8.logger.error("AI analysis error: %s %s", symbol, formatAiError(err));
|
|
3437
|
+
}
|
|
3438
|
+
return void 0;
|
|
3439
|
+
};
|
|
3440
|
+
var toFiniteNumberOrNull4 = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
3441
|
+
var getOrderArrivalSnapshot = async ({
|
|
3442
|
+
connector,
|
|
3443
|
+
symbol
|
|
3444
|
+
}) => {
|
|
3445
|
+
if (typeof connector.getTopOfBookTicker !== "function") {
|
|
3446
|
+
return {
|
|
3447
|
+
arrivalSnapshotTime: Date.now(),
|
|
3448
|
+
arrivalSource: "unavailable",
|
|
3449
|
+
bid: null,
|
|
3450
|
+
ask: null,
|
|
3451
|
+
arrivalMid: null,
|
|
3452
|
+
spreadBps: null
|
|
3453
|
+
};
|
|
3454
|
+
}
|
|
3455
|
+
try {
|
|
3456
|
+
const ticker = await connector.getTopOfBookTicker(symbol);
|
|
3457
|
+
const arrivalSnapshotTime = toFiniteNumberOrNull4(ticker?.timestamp);
|
|
3458
|
+
const bid = toFiniteNumberOrNull4(ticker?.bidPrice);
|
|
3459
|
+
const ask = toFiniteNumberOrNull4(ticker?.askPrice);
|
|
3460
|
+
const arrivalMid = bid != null && ask != null ? (bid + ask) / 2 : null;
|
|
3461
|
+
const spreadBps = bid != null && ask != null && arrivalMid != null && arrivalMid > 0 ? (ask - bid) / arrivalMid * 1e4 : null;
|
|
3462
|
+
return {
|
|
3463
|
+
arrivalSnapshotTime: arrivalSnapshotTime ?? Date.now(),
|
|
3464
|
+
arrivalSource: "top_of_book",
|
|
3465
|
+
bid,
|
|
3466
|
+
ask,
|
|
3467
|
+
arrivalMid,
|
|
3468
|
+
spreadBps
|
|
3469
|
+
};
|
|
3470
|
+
} catch (error) {
|
|
3471
|
+
import_logger8.logger.warn(
|
|
3472
|
+
"runtime order arrival snapshot failed: %s %s",
|
|
3473
|
+
symbol,
|
|
3474
|
+
error?.message || String(error)
|
|
3475
|
+
);
|
|
3476
|
+
return {
|
|
3477
|
+
arrivalSnapshotTime: Date.now(),
|
|
3478
|
+
arrivalSource: "top_of_book_error",
|
|
3479
|
+
bid: null,
|
|
3480
|
+
ask: null,
|
|
3481
|
+
arrivalMid: null,
|
|
3482
|
+
spreadBps: null
|
|
3483
|
+
};
|
|
3484
|
+
}
|
|
3485
|
+
};
|
|
3486
|
+
var validateEntryProtectionAtArrival = ({
|
|
3487
|
+
direction,
|
|
3488
|
+
signalPrice,
|
|
3489
|
+
bid,
|
|
3490
|
+
ask,
|
|
3491
|
+
arrivalMid,
|
|
3492
|
+
takeProfits,
|
|
3493
|
+
stopLossPrice
|
|
3494
|
+
}) => {
|
|
3495
|
+
const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
|
|
3496
|
+
const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
|
|
3497
|
+
if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
|
|
3498
|
+
throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
|
|
3499
|
+
}
|
|
3500
|
+
for (const takeProfit of takeProfits) {
|
|
3501
|
+
if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
|
|
3502
|
+
throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
|
|
3506
|
+
throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
|
|
3507
|
+
}
|
|
3508
|
+
};
|
|
3509
|
+
var resolveRuntimeTelemetryQuality = ({
|
|
3510
|
+
signalClosePrice,
|
|
3511
|
+
arrivalMid,
|
|
3512
|
+
orderSubmitTime,
|
|
3513
|
+
orderAckTime,
|
|
3514
|
+
fillAvgPrice,
|
|
3515
|
+
fillTime
|
|
3516
|
+
}) => {
|
|
3517
|
+
if (signalClosePrice != null && arrivalMid != null && orderSubmitTime != null && orderAckTime != null && fillAvgPrice != null && fillTime != null) {
|
|
3518
|
+
return "full";
|
|
3519
|
+
}
|
|
3520
|
+
if (fillAvgPrice != null && (arrivalMid != null || orderSubmitTime != null)) {
|
|
3521
|
+
return "partial";
|
|
3522
|
+
}
|
|
3523
|
+
if (fillAvgPrice != null) {
|
|
3524
|
+
return "price_only";
|
|
3525
|
+
}
|
|
3526
|
+
return "none";
|
|
3527
|
+
};
|
|
3528
|
+
var applyProtectiveOrders = async ({
|
|
3529
|
+
connector,
|
|
3530
|
+
symbol,
|
|
3531
|
+
direction,
|
|
3532
|
+
qty,
|
|
3533
|
+
takeProfits,
|
|
3534
|
+
stopLossPrice
|
|
3535
|
+
}) => {
|
|
3536
|
+
if (Array.isArray(takeProfits) && takeProfits.length > 0) {
|
|
3537
|
+
const tpOk = await connector.setTakeProfits({
|
|
3538
|
+
symbol,
|
|
3539
|
+
direction,
|
|
3540
|
+
qty,
|
|
3541
|
+
takeProfits
|
|
3542
|
+
});
|
|
3543
|
+
if (!tpOk) {
|
|
3544
|
+
throw new Error("SET_TAKE_PROFITS_FAILED");
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
if (typeof stopLossPrice === "number" && Number.isFinite(stopLossPrice)) {
|
|
3548
|
+
const slOk = await connector.setStopLoss({
|
|
3549
|
+
symbol,
|
|
3550
|
+
direction,
|
|
3551
|
+
stopLossPrice
|
|
3552
|
+
});
|
|
3553
|
+
if (!slOk) {
|
|
3554
|
+
throw new Error("SET_STOP_LOSS_FAILED");
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
};
|
|
3558
|
+
var executeEntryOrder = async ({
|
|
3559
|
+
connector,
|
|
3560
|
+
userName,
|
|
3561
|
+
symbol,
|
|
3562
|
+
direction,
|
|
3563
|
+
qty,
|
|
3564
|
+
currentPrice,
|
|
3565
|
+
timestamp,
|
|
3566
|
+
takeProfits,
|
|
3567
|
+
stopLossPrice,
|
|
3568
|
+
positionIntent = "open",
|
|
3569
|
+
signal,
|
|
3570
|
+
beforePlaceOrder,
|
|
3571
|
+
recordRuntimeTrade = true,
|
|
3572
|
+
leverage
|
|
3573
|
+
}) => {
|
|
3574
|
+
await beforePlaceOrder?.();
|
|
3575
|
+
const previousPosition = positionIntent === "increase" ? await connector.getPosition(symbol) : null;
|
|
3576
|
+
const orderId = signal.orderId || createRuntimeOrderId(signal.strategy);
|
|
3577
|
+
const signalTimestamp = signal.timestamp;
|
|
3578
|
+
const signalClosePrice = currentPrice;
|
|
3579
|
+
signal.orderId = orderId;
|
|
3580
|
+
signal.orderQty = qty;
|
|
3581
|
+
signal.orderValue = qty * currentPrice;
|
|
3582
|
+
signal.orderFailureReason = void 0;
|
|
3583
|
+
const arrivalSnapshot = await getOrderArrivalSnapshot({ connector, symbol });
|
|
3584
|
+
try {
|
|
3585
|
+
validateEntryProtectionAtArrival({
|
|
3586
|
+
direction,
|
|
3587
|
+
signalPrice: currentPrice,
|
|
3588
|
+
bid: arrivalSnapshot.bid,
|
|
3589
|
+
ask: arrivalSnapshot.ask,
|
|
3590
|
+
arrivalMid: arrivalSnapshot.arrivalMid,
|
|
3591
|
+
takeProfits,
|
|
3592
|
+
stopLossPrice
|
|
3593
|
+
});
|
|
3594
|
+
} catch (error) {
|
|
3595
|
+
signal.orderStatus = "failed";
|
|
3596
|
+
signal.orderFailureReason = error.message;
|
|
3597
|
+
throw error;
|
|
3598
|
+
}
|
|
3599
|
+
const orderSubmitTime = Date.now();
|
|
3600
|
+
const orderPlaced = await connector.placeOrder({
|
|
3601
|
+
symbol,
|
|
3602
|
+
qty,
|
|
3603
|
+
price: currentPrice,
|
|
3604
|
+
isLimit: false,
|
|
3605
|
+
positionIntent,
|
|
3606
|
+
timestamp,
|
|
3607
|
+
direction,
|
|
3608
|
+
...typeof leverage === "number" && Number.isFinite(leverage) ? { leverage } : {},
|
|
3609
|
+
orderId,
|
|
3610
|
+
signal
|
|
3611
|
+
});
|
|
3612
|
+
const orderAckTime = Date.now();
|
|
3613
|
+
const placedQty = typeof signal.orderQty === "number" && Number.isFinite(signal.orderQty) && signal.orderQty > 0 ? signal.orderQty : qty;
|
|
3614
|
+
const currentPosition = await connector.getPosition(symbol);
|
|
3615
|
+
const fillTime = Date.now();
|
|
3616
|
+
const isPositionIncrease = positionIntent === "increase" && previousPosition?.direction === direction && Number.isFinite(previousPosition.qty) && previousPosition.qty > 0;
|
|
3617
|
+
const hasRefreshedIncreasedPosition = Boolean(
|
|
3618
|
+
isPositionIncrease && previousPosition && currentPosition?.qty && currentPosition.qty > previousPosition.qty
|
|
3619
|
+
);
|
|
3620
|
+
const hasUsableCurrentPosition = Boolean(
|
|
3621
|
+
currentPosition && Number.isFinite(currentPosition.price) && Number.isFinite(currentPosition.qty) && (!isPositionIncrease || hasRefreshedIncreasedPosition)
|
|
3622
|
+
);
|
|
3623
|
+
const resultingEntryPrice = hasUsableCurrentPosition && currentPosition ? currentPosition.price : isPositionIncrease && previousPosition ? (previousPosition.price * previousPosition.qty + currentPrice * placedQty) / (previousPosition.qty + placedQty) : currentPrice;
|
|
3624
|
+
const resultingQty = hasUsableCurrentPosition && currentPosition ? currentPosition.qty : isPositionIncrease && previousPosition ? previousPosition.qty + placedQty : placedQty;
|
|
3625
|
+
const filledQty = isPositionIncrease && previousPosition ? hasRefreshedIncreasedPosition && currentPosition ? currentPosition.qty - previousPosition.qty : placedQty : resultingQty;
|
|
3626
|
+
const fillPrice = isPositionIncrease && previousPosition && hasRefreshedIncreasedPosition && currentPosition ? (resultingEntryPrice * currentPosition.qty - previousPosition.price * previousPosition.qty) / (currentPosition.qty - previousPosition.qty) : isPositionIncrease ? currentPrice : resultingEntryPrice;
|
|
3627
|
+
const fillSource = hasUsableCurrentPosition ? "exchange_position" : orderPlaced ? "requested_price" : "unknown";
|
|
3628
|
+
const estimatedOpenFee = fillPrice * filledQty * import_constants3.FEE_PERCENT;
|
|
3629
|
+
signal.prices.currentPrice = fillPrice;
|
|
3630
|
+
signal.orderQty = filledQty;
|
|
3631
|
+
signal.orderValue = filledQty * fillPrice;
|
|
3632
|
+
if (orderPlaced) {
|
|
3633
|
+
try {
|
|
3634
|
+
await applyProtectiveOrders({
|
|
3635
|
+
connector,
|
|
3636
|
+
symbol,
|
|
3637
|
+
direction,
|
|
3638
|
+
qty: resultingQty,
|
|
3639
|
+
takeProfits,
|
|
3640
|
+
stopLossPrice
|
|
3641
|
+
});
|
|
3642
|
+
} catch (error) {
|
|
3643
|
+
await connector.closePosition({
|
|
3644
|
+
symbol,
|
|
3645
|
+
price: resultingEntryPrice,
|
|
3646
|
+
timestamp,
|
|
3647
|
+
direction,
|
|
3648
|
+
signal
|
|
3649
|
+
});
|
|
3650
|
+
throw error;
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
signal.orderStatus = orderPlaced ? "completed" : "failed";
|
|
3654
|
+
signal.orderSkipReason = void 0;
|
|
3655
|
+
if (orderPlaced) {
|
|
3656
|
+
signal.orderFailureReason = void 0;
|
|
3657
|
+
}
|
|
3658
|
+
if (orderPlaced && recordRuntimeTrade && !isPositionIncrease) {
|
|
3659
|
+
await recordRuntimeTradeOpen({
|
|
3660
|
+
userName,
|
|
3661
|
+
orderId,
|
|
3662
|
+
signalId: signal.signalId,
|
|
3663
|
+
strategy: signal.strategy,
|
|
3664
|
+
symbol,
|
|
3665
|
+
interval: signal.interval,
|
|
3666
|
+
direction,
|
|
3667
|
+
qty: resultingQty,
|
|
3668
|
+
entryPrice: resultingEntryPrice,
|
|
3669
|
+
signalTimestamp,
|
|
3670
|
+
signalClosePrice,
|
|
3671
|
+
arrivalSnapshotTime: arrivalSnapshot.arrivalSnapshotTime,
|
|
3672
|
+
arrivalSource: arrivalSnapshot.arrivalSource,
|
|
3673
|
+
arrivalMid: arrivalSnapshot.arrivalMid,
|
|
3674
|
+
bid: arrivalSnapshot.bid,
|
|
3675
|
+
ask: arrivalSnapshot.ask,
|
|
3676
|
+
spreadBps: arrivalSnapshot.spreadBps,
|
|
3677
|
+
orderSubmitTime,
|
|
3678
|
+
orderAckTime,
|
|
3679
|
+
fillAvgPrice: fillPrice,
|
|
3680
|
+
fillSource,
|
|
3681
|
+
fillTime,
|
|
3682
|
+
telemetryQuality: resolveRuntimeTelemetryQuality({
|
|
3683
|
+
signalClosePrice,
|
|
3684
|
+
arrivalMid: arrivalSnapshot.arrivalMid,
|
|
3685
|
+
orderSubmitTime,
|
|
3686
|
+
orderAckTime,
|
|
3687
|
+
fillAvgPrice: fillPrice,
|
|
3688
|
+
fillTime
|
|
3689
|
+
}),
|
|
3690
|
+
fee: estimatedOpenFee,
|
|
3691
|
+
openFee: estimatedOpenFee,
|
|
3692
|
+
totalFee: estimatedOpenFee,
|
|
3693
|
+
entryTimestamp: timestamp,
|
|
3694
|
+
universe: signal.universe,
|
|
3695
|
+
assetClass: signal.assetClass,
|
|
3696
|
+
accountId: signal.accountId,
|
|
3697
|
+
deploymentId: signal.deploymentId,
|
|
3698
|
+
policyProfileId: signal.policyProfileId,
|
|
3699
|
+
runtimeConfigId: signal.runtimeConfigId,
|
|
3700
|
+
runtimeLineage: signal.runtimeLineage,
|
|
3701
|
+
...signal.aiAnalysis ? { aiAnalysis: signal.aiAnalysis } : {}
|
|
3702
|
+
});
|
|
3703
|
+
}
|
|
3704
|
+
if (orderPlaced && recordRuntimeTrade && isPositionIncrease) {
|
|
3705
|
+
await recordRuntimeTradeIncrease({
|
|
3706
|
+
userName,
|
|
3707
|
+
strategy: signal.strategy,
|
|
3708
|
+
symbol,
|
|
3709
|
+
direction,
|
|
3710
|
+
resultingQty,
|
|
3711
|
+
resultingEntryPrice,
|
|
3712
|
+
addedQty: filledQty,
|
|
3713
|
+
addedEntryPrice: fillPrice,
|
|
3714
|
+
entryTimestamp: timestamp,
|
|
3715
|
+
fee: estimatedOpenFee,
|
|
3716
|
+
accountId: signal.accountId,
|
|
3717
|
+
deploymentId: signal.deploymentId
|
|
3718
|
+
});
|
|
3719
|
+
}
|
|
3720
|
+
if (hasUsableCurrentPosition && currentPosition?.price) {
|
|
3721
|
+
return currentPosition.price;
|
|
3722
|
+
}
|
|
3723
|
+
return resultingEntryPrice;
|
|
3724
|
+
};
|
|
3725
|
+
var updatePositionProtection = async ({
|
|
3726
|
+
connector,
|
|
3727
|
+
symbol,
|
|
3728
|
+
direction,
|
|
3729
|
+
qty,
|
|
3730
|
+
takeProfits,
|
|
3731
|
+
stopLossPrice
|
|
3732
|
+
}) => {
|
|
3733
|
+
await applyProtectiveOrders({
|
|
3734
|
+
connector,
|
|
3735
|
+
symbol,
|
|
3736
|
+
direction,
|
|
3737
|
+
qty,
|
|
3738
|
+
takeProfits: takeProfits ?? [],
|
|
3739
|
+
stopLossPrice: stopLossPrice ?? null
|
|
3740
|
+
});
|
|
3741
|
+
};
|
|
3742
|
+
|
|
3743
|
+
// src/strategyHelpers/config.ts
|
|
3744
|
+
var import_lodash = __toESM(require("lodash"));
|
|
3745
|
+
var import_runtimeStrategyConfigs = require("@tradejs/infra/runtimeStrategyConfigs");
|
|
3746
|
+
var resolveStrategyConfig = async ({
|
|
3747
|
+
strategyName,
|
|
3748
|
+
userName,
|
|
3749
|
+
symbol,
|
|
3750
|
+
baseConfig,
|
|
3751
|
+
defaults,
|
|
3752
|
+
runtimeConfigId,
|
|
3753
|
+
runtimeConfigSnapshot
|
|
3754
|
+
}) => {
|
|
3755
|
+
const mergeIfNotEmpty = (target, patch) => patch && !import_lodash.default.isEmpty(patch) ? {
|
|
3756
|
+
...target,
|
|
3757
|
+
...patch
|
|
3758
|
+
} : target;
|
|
3759
|
+
let config = {
|
|
3760
|
+
...defaults,
|
|
3761
|
+
...baseConfig
|
|
3762
|
+
};
|
|
3763
|
+
let isConfigFromBacktest = false;
|
|
3764
|
+
if (config.ENV !== "BACKTEST") {
|
|
3765
|
+
const userConfig = runtimeConfigSnapshot ? runtimeConfigSnapshot.userConfig : await (0, import_runtimeStrategyConfigs.getRuntimeStrategyConfig)(
|
|
3766
|
+
userName,
|
|
3767
|
+
strategyName,
|
|
3768
|
+
runtimeConfigId
|
|
3769
|
+
) ?? {};
|
|
3770
|
+
config = mergeIfNotEmpty(config, userConfig);
|
|
3771
|
+
if (!runtimeConfigId || runtimeConfigId === "config") {
|
|
3772
|
+
const symbolResultConfig = runtimeConfigSnapshot ? runtimeConfigSnapshot.symbolResultConfig : await (0, import_runtimeStrategyConfigs.getRuntimeStrategyResultConfig)(userName, strategyName, symbol);
|
|
3773
|
+
if (symbolResultConfig && !import_lodash.default.isEmpty(symbolResultConfig)) {
|
|
3774
|
+
config = mergeIfNotEmpty(
|
|
3775
|
+
config,
|
|
3776
|
+
symbolResultConfig
|
|
3777
|
+
);
|
|
3778
|
+
isConfigFromBacktest = true;
|
|
3779
|
+
}
|
|
3780
|
+
}
|
|
3781
|
+
}
|
|
3782
|
+
return { config, isConfigFromBacktest };
|
|
3783
|
+
};
|
|
3784
|
+
|
|
3785
|
+
// src/strategy/runtimeBacktestDelay.ts
|
|
3786
|
+
var import_constants4 = require("@tradejs/core/constants");
|
|
3787
|
+
var import_data3 = require("@tradejs/core/data");
|
|
3788
|
+
var import_strategies5 = require("@tradejs/core/strategies");
|
|
3789
|
+
var resolveBacktestEntryDelayBars = (value) => {
|
|
3790
|
+
if (value == null || value === "") {
|
|
3791
|
+
return 1;
|
|
3792
|
+
}
|
|
3793
|
+
const parsed = parseInt(String(value), 10);
|
|
3794
|
+
return Number.isFinite(parsed) ? Math.max(0, parsed) : 1;
|
|
3795
|
+
};
|
|
3796
|
+
var resolveBacktestExecutionIntervalForPrimary = (interval) => {
|
|
3797
|
+
const normalized = String(interval ?? "15");
|
|
3798
|
+
if (normalized === "15") {
|
|
3799
|
+
return import_constants4.BACKTEST_EXECUTION_INTERVAL;
|
|
3800
|
+
}
|
|
3801
|
+
if (normalized === "60") {
|
|
3802
|
+
return "15";
|
|
3803
|
+
}
|
|
3804
|
+
return null;
|
|
3805
|
+
};
|
|
3806
|
+
var resolveBacktestExecutionDelayMs = (value, fallbackDelayMs) => {
|
|
3807
|
+
if (value == null || value === "") {
|
|
3808
|
+
return fallbackDelayMs;
|
|
3809
|
+
}
|
|
3810
|
+
const parsed = Number(value);
|
|
3811
|
+
return Number.isFinite(parsed) ? Math.max(0, Math.trunc(parsed)) : fallbackDelayMs;
|
|
3812
|
+
};
|
|
3813
|
+
var safeIntervalToMs = (interval) => {
|
|
3814
|
+
try {
|
|
3815
|
+
return (0, import_data3.intervalToMs)(interval);
|
|
3816
|
+
} catch {
|
|
3817
|
+
return null;
|
|
3818
|
+
}
|
|
3819
|
+
};
|
|
3820
|
+
var buildCandleByTimestamp = (candles) => new Map(
|
|
3821
|
+
(candles ?? []).filter((candle) => typeof candle?.timestamp === "number").map((candle) => [candle.timestamp, candle])
|
|
3822
|
+
);
|
|
3823
|
+
var buildBacktestExecutionOnlyCandle = (candle, executionPrice) => ({
|
|
3824
|
+
...candle,
|
|
3825
|
+
open: executionPrice,
|
|
3826
|
+
high: executionPrice,
|
|
3827
|
+
low: executionPrice,
|
|
3828
|
+
close: executionPrice,
|
|
3829
|
+
volume: 0,
|
|
3830
|
+
turnover: 0
|
|
3831
|
+
});
|
|
3832
|
+
var resolveInvalidDelayedEntryReason = ({
|
|
3833
|
+
decision,
|
|
3834
|
+
executionPrice,
|
|
3835
|
+
takeProfitPrice,
|
|
3836
|
+
stopLossPrice,
|
|
3837
|
+
riskRatio
|
|
3838
|
+
}) => {
|
|
3839
|
+
if (!Number.isFinite(executionPrice) || !Number.isFinite(takeProfitPrice) || !Number.isFinite(stopLossPrice)) {
|
|
3840
|
+
return "BACKTEST_DELAYED_ENTRY_INVALID_PRICE";
|
|
3841
|
+
}
|
|
3842
|
+
if (decision.entryContext.direction === "LONG") {
|
|
3843
|
+
if (executionPrice <= stopLossPrice) {
|
|
3844
|
+
return "BACKTEST_DELAYED_ENTRY_BEYOND_STOP";
|
|
3845
|
+
}
|
|
3846
|
+
if (executionPrice >= takeProfitPrice) {
|
|
3847
|
+
return "BACKTEST_DELAYED_ENTRY_BEYOND_TAKE_PROFIT";
|
|
3848
|
+
}
|
|
3849
|
+
return !Number.isFinite(riskRatio) || riskRatio <= 0 ? "BACKTEST_DELAYED_ENTRY_INVALID_PRICE" : null;
|
|
3850
|
+
}
|
|
3851
|
+
if (executionPrice >= stopLossPrice) {
|
|
3852
|
+
return "BACKTEST_DELAYED_ENTRY_BEYOND_STOP";
|
|
3853
|
+
}
|
|
3854
|
+
if (executionPrice <= takeProfitPrice) {
|
|
3855
|
+
return "BACKTEST_DELAYED_ENTRY_BEYOND_TAKE_PROFIT";
|
|
3856
|
+
}
|
|
3857
|
+
return !Number.isFinite(riskRatio) || riskRatio <= 0 ? "BACKTEST_DELAYED_ENTRY_INVALID_PRICE" : null;
|
|
3858
|
+
};
|
|
3859
|
+
var applyBacktestDelayedEntryExecution = ({
|
|
3860
|
+
decision,
|
|
3861
|
+
execution,
|
|
3862
|
+
backtestPriceMode,
|
|
3863
|
+
delayBars
|
|
3864
|
+
}) => {
|
|
3865
|
+
const { candle, btcCandle } = execution;
|
|
3866
|
+
const signalTimestamp = decision.signal?.timestamp ?? decision.entryContext.timestamp;
|
|
3867
|
+
const signalPrice = decision.signal?.prices.currentPrice ?? decision.entryContext.prices.currentPrice;
|
|
3868
|
+
const skipReason = execution.skipReason ?? (!candle || !btcCandle ? "BACKTEST_LOWER_EXECUTION_CANDLE_MISSING" : void 0);
|
|
3869
|
+
if (skipReason || !candle || !btcCandle) {
|
|
3870
|
+
if (decision.signal) {
|
|
3871
|
+
decision.signal.additionalIndicators = {
|
|
3872
|
+
...decision.signal.additionalIndicators ?? {},
|
|
3873
|
+
backtestExecution: {
|
|
3874
|
+
entryDelayBars: delayBars,
|
|
3875
|
+
priceMode: backtestPriceMode ?? "open",
|
|
3876
|
+
signalTimestamp,
|
|
3877
|
+
signalPrice,
|
|
3878
|
+
executionSource: execution.source,
|
|
3879
|
+
...execution.executionInterval ? { executionInterval: execution.executionInterval } : {},
|
|
3880
|
+
...execution.executionDelayMs != null ? { executionDelayMs: execution.executionDelayMs } : {},
|
|
3881
|
+
...execution.primaryExecutionTimestamp != null ? { primaryExecutionTimestamp: execution.primaryExecutionTimestamp } : {},
|
|
3882
|
+
...execution.requestedExecutionTimestamp != null ? {
|
|
3883
|
+
requestedExecutionTimestamp: execution.requestedExecutionTimestamp
|
|
3884
|
+
} : {},
|
|
3885
|
+
skipReason
|
|
3886
|
+
}
|
|
3887
|
+
};
|
|
3888
|
+
decision.signal.orderStatus = "skipped";
|
|
3889
|
+
decision.signal.orderSkipReason = skipReason;
|
|
3890
|
+
}
|
|
3891
|
+
return {
|
|
3892
|
+
skipReason,
|
|
3893
|
+
executionCandle: null,
|
|
3894
|
+
btcExecutionCandle: null
|
|
3895
|
+
};
|
|
3896
|
+
}
|
|
3897
|
+
const executionPrice = (0, import_strategies5.resolveBacktestExecutionPrice)(
|
|
3898
|
+
candle,
|
|
3899
|
+
backtestPriceMode ?? "open"
|
|
3900
|
+
);
|
|
3901
|
+
const executionTimestamp = candle.timestamp;
|
|
3902
|
+
const takeProfitPrice = decision.entryContext.prices.takeProfitPrice;
|
|
3903
|
+
const stopLossPrice = decision.orderPlan.stopLossPrice;
|
|
3904
|
+
const riskRatio = (0, import_strategies5.calculateRiskRatio)({
|
|
3905
|
+
direction: decision.entryContext.direction,
|
|
3906
|
+
currentPrice: executionPrice,
|
|
3907
|
+
takeProfitPrice,
|
|
3908
|
+
stopLossPrice
|
|
3909
|
+
});
|
|
3910
|
+
const invalidSkipReason = resolveInvalidDelayedEntryReason({
|
|
3911
|
+
decision,
|
|
3912
|
+
executionPrice,
|
|
3913
|
+
takeProfitPrice,
|
|
3914
|
+
stopLossPrice,
|
|
3915
|
+
riskRatio
|
|
3916
|
+
});
|
|
3917
|
+
decision.entryContext = {
|
|
3918
|
+
...decision.entryContext,
|
|
3919
|
+
timestamp: executionTimestamp,
|
|
3920
|
+
prices: {
|
|
3921
|
+
...decision.entryContext.prices,
|
|
3922
|
+
currentPrice: executionPrice,
|
|
3923
|
+
stopLossPrice,
|
|
3924
|
+
riskRatio
|
|
3925
|
+
}
|
|
3926
|
+
};
|
|
3927
|
+
const executionResult = {
|
|
3928
|
+
skipReason: invalidSkipReason,
|
|
3929
|
+
executionCandle: buildBacktestExecutionOnlyCandle(candle, executionPrice),
|
|
3930
|
+
btcExecutionCandle: buildBacktestExecutionOnlyCandle(
|
|
3931
|
+
btcCandle,
|
|
3932
|
+
(0, import_strategies5.resolveBacktestExecutionPrice)(btcCandle, backtestPriceMode ?? "open")
|
|
3933
|
+
)
|
|
3934
|
+
};
|
|
3935
|
+
if (!decision.signal) {
|
|
3936
|
+
return executionResult;
|
|
3937
|
+
}
|
|
3938
|
+
decision.signal.prices = {
|
|
3939
|
+
...decision.signal.prices,
|
|
3940
|
+
currentPrice: executionPrice,
|
|
3941
|
+
stopLossPrice,
|
|
3942
|
+
riskRatio
|
|
3943
|
+
};
|
|
3944
|
+
decision.signal.additionalIndicators = {
|
|
3945
|
+
...decision.signal.additionalIndicators ?? {},
|
|
3946
|
+
backtestExecution: {
|
|
3947
|
+
entryDelayBars: delayBars,
|
|
3948
|
+
priceMode: backtestPriceMode ?? "open",
|
|
3949
|
+
signalTimestamp,
|
|
3950
|
+
signalPrice,
|
|
3951
|
+
executionTimestamp,
|
|
3952
|
+
executionPrice,
|
|
3953
|
+
executionSource: execution.source,
|
|
3954
|
+
...execution.executionInterval ? { executionInterval: execution.executionInterval } : {},
|
|
3955
|
+
...execution.executionDelayMs != null ? { executionDelayMs: execution.executionDelayMs } : {},
|
|
3956
|
+
...execution.primaryExecutionTimestamp != null ? { primaryExecutionTimestamp: execution.primaryExecutionTimestamp } : {},
|
|
3957
|
+
...execution.requestedExecutionTimestamp != null ? { requestedExecutionTimestamp: execution.requestedExecutionTimestamp } : {},
|
|
3958
|
+
...invalidSkipReason ? { skipReason: invalidSkipReason } : {}
|
|
3959
|
+
}
|
|
3960
|
+
};
|
|
3961
|
+
if (invalidSkipReason) {
|
|
3962
|
+
decision.signal.orderStatus = "skipped";
|
|
3963
|
+
decision.signal.orderSkipReason = invalidSkipReason;
|
|
3964
|
+
}
|
|
3965
|
+
return executionResult;
|
|
3966
|
+
};
|
|
3967
|
+
|
|
3968
|
+
// src/strategy/runtimeEntryPolicy.ts
|
|
3969
|
+
var resolveEntryRuntimePolicy = ({
|
|
3970
|
+
decision,
|
|
3971
|
+
config,
|
|
3972
|
+
manifest,
|
|
3973
|
+
policyProfile
|
|
3974
|
+
}) => {
|
|
3975
|
+
const baseDefaults = manifest?.entryRuntimeDefaults;
|
|
3976
|
+
const profileDefaults = policyProfile?.entryRuntimeDefaults;
|
|
3977
|
+
const manifestDefaults = baseDefaults || profileDefaults ? {
|
|
3978
|
+
...baseDefaults,
|
|
3979
|
+
...profileDefaults,
|
|
3980
|
+
...baseDefaults?.ml || profileDefaults?.ml ? { ml: { ...baseDefaults?.ml, ...profileDefaults?.ml } } : {},
|
|
3981
|
+
...baseDefaults?.ai || profileDefaults?.ai ? { ai: { ...baseDefaults?.ai, ...profileDefaults?.ai } } : {}
|
|
3982
|
+
} : void 0;
|
|
3983
|
+
const adapterMl = (policyProfile?.mlAdapter ?? manifest?.mlAdapter)?.mapEntryRuntimeFromConfig?.(config);
|
|
3984
|
+
const adapterAi = (policyProfile?.aiAdapter ?? manifest?.aiAdapter)?.mapEntryRuntimeFromConfig?.(config);
|
|
3985
|
+
const ml = manifestDefaults?.ml || adapterMl || decision.runtime?.ml ? {
|
|
3986
|
+
...manifestDefaults?.ml,
|
|
3987
|
+
...adapterMl,
|
|
3988
|
+
...decision.runtime?.ml
|
|
3989
|
+
} : void 0;
|
|
3990
|
+
const ai = manifestDefaults?.ai || adapterAi || decision.runtime?.ai ? {
|
|
3991
|
+
...manifestDefaults?.ai,
|
|
3992
|
+
...adapterAi,
|
|
3993
|
+
...decision.runtime?.ai
|
|
3994
|
+
} : void 0;
|
|
3995
|
+
return {
|
|
3996
|
+
...manifestDefaults,
|
|
3997
|
+
...decision.runtime,
|
|
3998
|
+
ml,
|
|
3999
|
+
ai
|
|
4000
|
+
};
|
|
4001
|
+
};
|
|
4002
|
+
var formatGateNumber = (value) => {
|
|
4003
|
+
if (!Number.isFinite(value)) {
|
|
4004
|
+
return String(value);
|
|
4005
|
+
}
|
|
4006
|
+
const normalized = Number(value.toFixed(6));
|
|
4007
|
+
return Number.isInteger(normalized) ? String(normalized) : String(normalized);
|
|
4008
|
+
};
|
|
4009
|
+
var isMlRuntimeGateEnabled = (params) => {
|
|
4010
|
+
const { env, ml } = params;
|
|
4011
|
+
return env !== "BACKTEST" && ml?.config != null && ml.config.enabled !== false;
|
|
4012
|
+
};
|
|
4013
|
+
var isMlResultUnavailable = (params) => {
|
|
4014
|
+
const { env, ml } = params;
|
|
4015
|
+
return isMlRuntimeGateEnabled({ env, ml }) && ml?.result == null;
|
|
4016
|
+
};
|
|
4017
|
+
var shouldExecuteEntryDecision = ({
|
|
4018
|
+
makeOrdersEnabled,
|
|
4019
|
+
env,
|
|
4020
|
+
signal,
|
|
4021
|
+
ml,
|
|
4022
|
+
aiEnabled,
|
|
4023
|
+
quality,
|
|
4024
|
+
minAiQuality
|
|
4025
|
+
}) => {
|
|
4026
|
+
if (!makeOrdersEnabled) {
|
|
4027
|
+
return false;
|
|
4028
|
+
}
|
|
4029
|
+
if (!signal || env === "BACKTEST") {
|
|
4030
|
+
return true;
|
|
4031
|
+
}
|
|
4032
|
+
if (isMlResultUnavailable({ env, ml })) {
|
|
4033
|
+
return false;
|
|
4034
|
+
}
|
|
4035
|
+
if (isMlRuntimeGateEnabled({ env, ml }) && ml?.result?.passed === false) {
|
|
4036
|
+
return false;
|
|
4037
|
+
}
|
|
4038
|
+
if (!aiEnabled) {
|
|
4039
|
+
return true;
|
|
4040
|
+
}
|
|
4041
|
+
return Number.isFinite(quality) && quality >= minAiQuality;
|
|
4042
|
+
};
|
|
4043
|
+
var getEntrySkipReason = ({
|
|
4044
|
+
makeOrdersEnabled,
|
|
4045
|
+
env,
|
|
4046
|
+
ml,
|
|
4047
|
+
aiEnabled,
|
|
4048
|
+
quality,
|
|
4049
|
+
minAiQuality
|
|
4050
|
+
}) => {
|
|
4051
|
+
if (!makeOrdersEnabled) {
|
|
4052
|
+
return "MAKE_ORDERS_DISABLED";
|
|
4053
|
+
}
|
|
4054
|
+
if (isMlResultUnavailable({ env, ml })) {
|
|
4055
|
+
return "ML_RESULT_UNAVAILABLE";
|
|
4056
|
+
}
|
|
4057
|
+
if (isMlRuntimeGateEnabled({ env, ml }) && ml?.result?.passed === false) {
|
|
4058
|
+
const probability = formatGateNumber(ml.result.probability);
|
|
4059
|
+
const threshold = formatGateNumber(ml.result.threshold);
|
|
4060
|
+
return `ML_THRESHOLD_NOT_MET (${probability} < ${threshold})`;
|
|
4061
|
+
}
|
|
4062
|
+
if (env !== "BACKTEST" && aiEnabled && quality == null) {
|
|
4063
|
+
return "AI_QUALITY_UNAVAILABLE";
|
|
4064
|
+
}
|
|
4065
|
+
if (env !== "BACKTEST" && aiEnabled && quality != null && Number.isFinite(quality) && quality < minAiQuality) {
|
|
4066
|
+
return `AI_QUALITY_BELOW_MIN (${quality} < ${minAiQuality})`;
|
|
4067
|
+
}
|
|
4068
|
+
return "ENTRY_POLICY_BLOCKED";
|
|
4069
|
+
};
|
|
4070
|
+
var buildHookEntry = ({
|
|
4071
|
+
decision,
|
|
4072
|
+
runtime
|
|
4073
|
+
}) => ({
|
|
4074
|
+
context: decision.entryContext,
|
|
4075
|
+
orderPlan: decision.orderPlan,
|
|
4076
|
+
signal: decision.signal,
|
|
4077
|
+
runtime: {
|
|
4078
|
+
raw: decision.runtime,
|
|
4079
|
+
resolved: runtime
|
|
4080
|
+
}
|
|
4081
|
+
});
|
|
4082
|
+
var buildHookPolicy = ({
|
|
4083
|
+
quality,
|
|
4084
|
+
makeOrdersEnabled,
|
|
4085
|
+
minAiQuality
|
|
4086
|
+
}) => ({
|
|
4087
|
+
aiQuality: quality,
|
|
4088
|
+
makeOrdersEnabled,
|
|
4089
|
+
minAiQuality
|
|
4090
|
+
});
|
|
4091
|
+
var buildMlHookContext = ({
|
|
4092
|
+
signal,
|
|
4093
|
+
env,
|
|
4094
|
+
ml
|
|
4095
|
+
}) => {
|
|
4096
|
+
if (env === "BACKTEST") {
|
|
4097
|
+
return {
|
|
4098
|
+
config: ml,
|
|
4099
|
+
attempted: false,
|
|
4100
|
+
applied: false,
|
|
4101
|
+
skippedReason: "BACKTEST"
|
|
4102
|
+
};
|
|
4103
|
+
}
|
|
4104
|
+
if (!ml) {
|
|
4105
|
+
return {
|
|
4106
|
+
attempted: false,
|
|
4107
|
+
applied: false,
|
|
4108
|
+
skippedReason: "NO_RUNTIME"
|
|
4109
|
+
};
|
|
4110
|
+
}
|
|
4111
|
+
if (ml.enabled === false) {
|
|
4112
|
+
return {
|
|
4113
|
+
config: ml,
|
|
4114
|
+
attempted: false,
|
|
4115
|
+
applied: false,
|
|
4116
|
+
skippedReason: "DISABLED"
|
|
4117
|
+
};
|
|
4118
|
+
}
|
|
4119
|
+
if (!ml.strategyConfig) {
|
|
4120
|
+
return {
|
|
4121
|
+
config: ml,
|
|
4122
|
+
attempted: false,
|
|
4123
|
+
applied: false,
|
|
4124
|
+
skippedReason: "NO_STRATEGY_CONFIG"
|
|
4125
|
+
};
|
|
4126
|
+
}
|
|
4127
|
+
if (typeof ml.mlThreshold !== "number") {
|
|
4128
|
+
return {
|
|
4129
|
+
config: ml,
|
|
4130
|
+
attempted: false,
|
|
4131
|
+
applied: false,
|
|
4132
|
+
skippedReason: "NO_THRESHOLD"
|
|
4133
|
+
};
|
|
4134
|
+
}
|
|
4135
|
+
if (signal.ml) {
|
|
4136
|
+
return {
|
|
4137
|
+
config: ml,
|
|
4138
|
+
attempted: true,
|
|
4139
|
+
applied: true,
|
|
4140
|
+
result: signal.ml
|
|
4141
|
+
};
|
|
4142
|
+
}
|
|
4143
|
+
return {
|
|
4144
|
+
config: ml,
|
|
4145
|
+
attempted: true,
|
|
4146
|
+
applied: false,
|
|
4147
|
+
skippedReason: "NO_RESULT"
|
|
4148
|
+
};
|
|
4149
|
+
};
|
|
4150
|
+
var buildAiHookContext = ({
|
|
4151
|
+
env,
|
|
4152
|
+
ai,
|
|
4153
|
+
quality
|
|
4154
|
+
}) => {
|
|
4155
|
+
if (env === "BACKTEST") {
|
|
4156
|
+
return {
|
|
4157
|
+
config: ai,
|
|
4158
|
+
attempted: false,
|
|
4159
|
+
applied: false,
|
|
4160
|
+
skippedReason: "BACKTEST"
|
|
4161
|
+
};
|
|
4162
|
+
}
|
|
4163
|
+
if (!ai) {
|
|
4164
|
+
return {
|
|
4165
|
+
attempted: false,
|
|
4166
|
+
applied: false,
|
|
4167
|
+
skippedReason: "NO_RUNTIME"
|
|
4168
|
+
};
|
|
4169
|
+
}
|
|
4170
|
+
if (ai.enabled === false) {
|
|
4171
|
+
return {
|
|
4172
|
+
config: ai,
|
|
4173
|
+
attempted: false,
|
|
4174
|
+
applied: false,
|
|
4175
|
+
skippedReason: "DISABLED"
|
|
4176
|
+
};
|
|
4177
|
+
}
|
|
4178
|
+
if (typeof quality === "number") {
|
|
4179
|
+
return {
|
|
4180
|
+
config: ai,
|
|
4181
|
+
attempted: true,
|
|
4182
|
+
applied: true,
|
|
4183
|
+
quality
|
|
4184
|
+
};
|
|
4185
|
+
}
|
|
4186
|
+
return {
|
|
4187
|
+
config: ai,
|
|
4188
|
+
attempted: true,
|
|
4189
|
+
applied: false,
|
|
4190
|
+
skippedReason: "NO_QUALITY"
|
|
4191
|
+
};
|
|
4192
|
+
};
|
|
4193
|
+
|
|
4194
|
+
// src/strategy/runtimeHooks.ts
|
|
4195
|
+
var normalizeConfigHookList = (value) => {
|
|
4196
|
+
if (Array.isArray(value)) {
|
|
4197
|
+
return value;
|
|
4198
|
+
}
|
|
4199
|
+
return value ? [value] : [];
|
|
4200
|
+
};
|
|
4201
|
+
var isStrategyDecision = (value) => {
|
|
4202
|
+
if (!value || typeof value !== "object") {
|
|
4203
|
+
return false;
|
|
4204
|
+
}
|
|
4205
|
+
const kind = value.kind;
|
|
4206
|
+
return kind === "skip" || kind === "entry" || kind === "exit" || kind === "protect";
|
|
4207
|
+
};
|
|
4208
|
+
var CONFIG_HOOK_STAGES = [
|
|
4209
|
+
"onInit",
|
|
4210
|
+
"onBar",
|
|
4211
|
+
"afterCoreDecision",
|
|
4212
|
+
"afterBarDecision",
|
|
4213
|
+
"onSkip",
|
|
4214
|
+
"beforeClosePosition",
|
|
4215
|
+
"afterEnrichMl",
|
|
4216
|
+
"afterEnrichAi",
|
|
4217
|
+
"beforeEntryGate",
|
|
4218
|
+
"beforePlaceOrder",
|
|
4219
|
+
"afterPlaceOrder"
|
|
4220
|
+
];
|
|
4221
|
+
var isConfigHookStage = (stage) => CONFIG_HOOK_STAGES.includes(
|
|
4222
|
+
stage
|
|
4223
|
+
);
|
|
4224
|
+
var buildHookCtx = ({
|
|
4225
|
+
connector,
|
|
4226
|
+
strategyName,
|
|
4227
|
+
userName,
|
|
4228
|
+
symbol,
|
|
4229
|
+
universe,
|
|
4230
|
+
assetClass,
|
|
4231
|
+
accountId,
|
|
4232
|
+
deploymentId,
|
|
4233
|
+
policyProfileId,
|
|
4234
|
+
strategyConfig,
|
|
4235
|
+
env,
|
|
4236
|
+
isConfigFromBacktest
|
|
4237
|
+
}) => ({
|
|
4238
|
+
connector,
|
|
4239
|
+
strategyName,
|
|
4240
|
+
userName,
|
|
4241
|
+
symbol,
|
|
4242
|
+
...universe ? { universe } : {},
|
|
4243
|
+
...assetClass ? { assetClass } : {},
|
|
4244
|
+
...accountId ? { accountId } : {},
|
|
4245
|
+
...deploymentId ? { deploymentId } : {},
|
|
4246
|
+
...policyProfileId ? { policyProfileId } : {},
|
|
4247
|
+
strategyConfig,
|
|
4248
|
+
env,
|
|
4249
|
+
isConfigFromBacktest
|
|
4250
|
+
});
|
|
4251
|
+
var shouldRecordRuntimeJournal = ({
|
|
4252
|
+
env,
|
|
4253
|
+
config
|
|
4254
|
+
}) => env !== "BACKTEST" && env !== "PARITY" && config.RECORD_RUNTIME_TRADES !== false;
|
|
4255
|
+
var isTestConnector = (connector) => Boolean(
|
|
4256
|
+
connector.__tradejsTestConnector
|
|
4257
|
+
);
|
|
4258
|
+
var canUseSharedReplayState = ({
|
|
4259
|
+
env,
|
|
4260
|
+
sharedReplayKey
|
|
4261
|
+
}) => (env === "BACKTEST" || env === "PARITY") && Boolean(sharedReplayKey);
|
|
4262
|
+
|
|
4263
|
+
// src/strategy/runtimeExecution.ts
|
|
4264
|
+
var import_logger9 = require("@tradejs/infra/logger");
|
|
4265
|
+
var import_types = require("@tradejs/types");
|
|
4266
|
+
var buildExitOrderSignal = ({
|
|
4267
|
+
strategyName,
|
|
4268
|
+
symbol,
|
|
4269
|
+
decision
|
|
4270
|
+
}) => {
|
|
4271
|
+
if (!strategyName) {
|
|
4272
|
+
return void 0;
|
|
4273
|
+
}
|
|
4274
|
+
return {
|
|
4275
|
+
signalId: `${strategyName}:${symbol}:exit:${decision.closePlan.timestamp}`,
|
|
4276
|
+
strategy: strategyName,
|
|
4277
|
+
symbol,
|
|
4278
|
+
interval: "15",
|
|
4279
|
+
direction: decision.closePlan.direction,
|
|
4280
|
+
timestamp: decision.closePlan.timestamp,
|
|
4281
|
+
figures: {},
|
|
4282
|
+
indicators: {},
|
|
4283
|
+
prices: {
|
|
4284
|
+
currentPrice: decision.closePlan.price,
|
|
4285
|
+
takeProfitPrice: decision.closePlan.price,
|
|
4286
|
+
stopLossPrice: decision.closePlan.price,
|
|
4287
|
+
riskRatio: 0
|
|
4288
|
+
},
|
|
4289
|
+
additionalIndicators: {
|
|
4290
|
+
exit: {
|
|
4291
|
+
code: decision.code
|
|
4292
|
+
}
|
|
4293
|
+
}
|
|
4294
|
+
};
|
|
4295
|
+
};
|
|
4296
|
+
var handleExitDecision = async ({
|
|
4297
|
+
connector,
|
|
4298
|
+
userName,
|
|
4299
|
+
strategyName,
|
|
4300
|
+
symbol,
|
|
4301
|
+
decision,
|
|
4302
|
+
market,
|
|
4303
|
+
onRuntimeClose,
|
|
4304
|
+
onRuntimeError
|
|
4305
|
+
}) => {
|
|
4306
|
+
try {
|
|
4307
|
+
let activeTradeForClose = null;
|
|
4308
|
+
if (userName) {
|
|
4309
|
+
const activeTrade = await getActiveRuntimeTrade({
|
|
4310
|
+
userName,
|
|
4311
|
+
symbol,
|
|
4312
|
+
accountId: connector.accountId,
|
|
4313
|
+
deploymentId: connector.deploymentId
|
|
4314
|
+
});
|
|
4315
|
+
if (!activeTrade) {
|
|
4316
|
+
import_logger9.logger.warn(
|
|
4317
|
+
"[%s] blocked closePosition for untracked runtime position: %s",
|
|
4318
|
+
strategyName ?? "unknown",
|
|
4319
|
+
symbol
|
|
4320
|
+
);
|
|
4321
|
+
return "CLOSE_BLOCKED_BY_UNTRACKED_POSITION";
|
|
4322
|
+
}
|
|
4323
|
+
if (!strategyName || activeTrade.strategy !== strategyName) {
|
|
4324
|
+
import_logger9.logger.warn(
|
|
4325
|
+
"[%s] blocked closePosition for foreign runtime position: %s ownedBy=%s",
|
|
4326
|
+
strategyName ?? "unknown",
|
|
4327
|
+
symbol,
|
|
4328
|
+
activeTrade.strategy
|
|
4329
|
+
);
|
|
4330
|
+
return "CLOSE_BLOCKED_BY_FOREIGN_STRATEGY_POSITION";
|
|
4331
|
+
}
|
|
4332
|
+
activeTradeForClose = activeTrade;
|
|
4333
|
+
}
|
|
4334
|
+
await connector.closePosition({
|
|
4335
|
+
symbol,
|
|
4336
|
+
price: decision.closePlan.price,
|
|
4337
|
+
timestamp: decision.closePlan.timestamp,
|
|
4338
|
+
direction: decision.closePlan.direction,
|
|
4339
|
+
signal: buildExitOrderSignal({
|
|
4340
|
+
strategyName,
|
|
4341
|
+
symbol,
|
|
4342
|
+
decision
|
|
4343
|
+
})
|
|
4344
|
+
});
|
|
4345
|
+
const closedTrade = await markRuntimeTradeClosed({
|
|
4346
|
+
userName,
|
|
4347
|
+
strategy: strategyName,
|
|
4348
|
+
symbol,
|
|
4349
|
+
exitPrice: decision.closePlan.price,
|
|
4350
|
+
exitTimestamp: decision.closePlan.timestamp,
|
|
4351
|
+
exitType: "exit",
|
|
4352
|
+
accountId: connector.accountId,
|
|
4353
|
+
deploymentId: connector.deploymentId
|
|
4354
|
+
});
|
|
4355
|
+
const trade = closedTrade ?? activeTradeForClose;
|
|
4356
|
+
if (trade && strategyName) {
|
|
4357
|
+
try {
|
|
4358
|
+
onRuntimeClose?.({
|
|
4359
|
+
userName,
|
|
4360
|
+
strategy: strategyName,
|
|
4361
|
+
openedByStrategy: trade.strategy,
|
|
4362
|
+
symbol,
|
|
4363
|
+
direction: trade.direction,
|
|
4364
|
+
code: decision.code,
|
|
4365
|
+
orderId: trade.orderId,
|
|
4366
|
+
signalId: trade.signalId,
|
|
4367
|
+
qty: trade.qty,
|
|
4368
|
+
entryPrice: trade.entryPrice,
|
|
4369
|
+
entryTimestamp: trade.entryTimestamp,
|
|
4370
|
+
exitPrice: closedTrade?.exitPrice ?? decision.closePlan.price,
|
|
4371
|
+
exitTimestamp: closedTrade?.exitTimestamp ?? decision.closePlan.timestamp,
|
|
4372
|
+
closedPnl: closedTrade?.closedPnl ?? trade.closedPnl ?? null,
|
|
4373
|
+
exitType: closedTrade?.exitType ?? "exit"
|
|
4374
|
+
});
|
|
4375
|
+
} catch (notificationError) {
|
|
4376
|
+
import_logger9.logger.error(
|
|
4377
|
+
"runtime close notification error: %s %s",
|
|
4378
|
+
symbol,
|
|
4379
|
+
notificationError
|
|
4380
|
+
);
|
|
4381
|
+
}
|
|
4382
|
+
}
|
|
4383
|
+
} catch (err) {
|
|
4384
|
+
await onRuntimeError?.({
|
|
4385
|
+
stage: "closePosition",
|
|
4386
|
+
error: err,
|
|
4387
|
+
decision,
|
|
4388
|
+
market
|
|
4389
|
+
});
|
|
4390
|
+
import_logger9.logger.error("close order error: %s %s", symbol, err);
|
|
4391
|
+
return "ORDER_ERROR";
|
|
4392
|
+
}
|
|
4393
|
+
return decision.code;
|
|
4394
|
+
};
|
|
4395
|
+
var handleProtectDecision = async ({
|
|
4396
|
+
connector,
|
|
4397
|
+
symbol,
|
|
4398
|
+
decision,
|
|
4399
|
+
market,
|
|
4400
|
+
onRuntimeError
|
|
4401
|
+
}) => {
|
|
4402
|
+
try {
|
|
4403
|
+
await updatePositionProtection({
|
|
4404
|
+
connector,
|
|
4405
|
+
symbol,
|
|
4406
|
+
direction: decision.protectPlan.direction,
|
|
4407
|
+
takeProfits: decision.protectPlan.takeProfits ?? [],
|
|
4408
|
+
stopLossPrice: decision.protectPlan.stopLossPrice ?? null
|
|
4409
|
+
});
|
|
4410
|
+
} catch (err) {
|
|
4411
|
+
await onRuntimeError?.({
|
|
4412
|
+
stage: "protectPosition",
|
|
4413
|
+
error: err,
|
|
4414
|
+
decision,
|
|
4415
|
+
market
|
|
4416
|
+
});
|
|
4417
|
+
import_logger9.logger.error("protect position error: %s %s", symbol, err);
|
|
4418
|
+
return "ORDER_ERROR";
|
|
4419
|
+
}
|
|
4420
|
+
return decision.code;
|
|
4421
|
+
};
|
|
4422
|
+
var executeEntryDecision = async ({
|
|
4423
|
+
connector,
|
|
4424
|
+
symbol,
|
|
4425
|
+
decision,
|
|
4426
|
+
runtime,
|
|
4427
|
+
manifest,
|
|
4428
|
+
hookCtx,
|
|
4429
|
+
market,
|
|
4430
|
+
entry,
|
|
4431
|
+
policy,
|
|
4432
|
+
ml,
|
|
4433
|
+
ai,
|
|
4434
|
+
recordRuntimeJournal,
|
|
4435
|
+
invokeStageHooks,
|
|
4436
|
+
notifyRuntimeError
|
|
4437
|
+
}) => {
|
|
4438
|
+
const signal = decision.signal;
|
|
4439
|
+
const beforePlaceOrder = async () => {
|
|
4440
|
+
await invokeStageHooks(
|
|
4441
|
+
"beforePlaceOrder",
|
|
4442
|
+
manifest?.hooks?.beforePlaceOrder,
|
|
4443
|
+
{
|
|
4444
|
+
ctx: hookCtx,
|
|
4445
|
+
market,
|
|
4446
|
+
decision,
|
|
4447
|
+
entry,
|
|
4448
|
+
policy,
|
|
4449
|
+
ml,
|
|
4450
|
+
ai
|
|
4451
|
+
},
|
|
4452
|
+
{ decision, entry, market }
|
|
4453
|
+
);
|
|
4454
|
+
try {
|
|
4455
|
+
await runtime.beforePlaceOrder?.();
|
|
4456
|
+
} catch (error) {
|
|
4457
|
+
await notifyRuntimeError({
|
|
4458
|
+
stage: "runtime.beforePlaceOrder",
|
|
4459
|
+
error,
|
|
4460
|
+
decision,
|
|
4461
|
+
entry,
|
|
4462
|
+
market
|
|
4463
|
+
});
|
|
4464
|
+
throw error;
|
|
4465
|
+
}
|
|
4466
|
+
};
|
|
4467
|
+
try {
|
|
4468
|
+
if (signal) {
|
|
4469
|
+
await executeEntryOrder({
|
|
4470
|
+
connector,
|
|
4471
|
+
userName: hookCtx.userName,
|
|
4472
|
+
symbol,
|
|
4473
|
+
direction: decision.entryContext.direction,
|
|
4474
|
+
qty: decision.orderPlan.qty,
|
|
4475
|
+
currentPrice: decision.entryContext.prices.currentPrice,
|
|
4476
|
+
timestamp: decision.entryContext.timestamp,
|
|
4477
|
+
takeProfits: decision.orderPlan.takeProfits,
|
|
4478
|
+
stopLossPrice: decision.orderPlan.stopLossPrice,
|
|
4479
|
+
positionIntent: decision.orderPlan.positionIntent,
|
|
4480
|
+
...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {},
|
|
4481
|
+
signal,
|
|
4482
|
+
beforePlaceOrder,
|
|
4483
|
+
recordRuntimeTrade: recordRuntimeJournal
|
|
4484
|
+
});
|
|
4485
|
+
await invokeStageHooks(
|
|
4486
|
+
"afterPlaceOrder",
|
|
4487
|
+
manifest?.hooks?.afterPlaceOrder,
|
|
4488
|
+
{
|
|
4489
|
+
ctx: hookCtx,
|
|
4490
|
+
market,
|
|
4491
|
+
decision,
|
|
4492
|
+
entry,
|
|
4493
|
+
policy,
|
|
4494
|
+
ml,
|
|
4495
|
+
ai,
|
|
4496
|
+
order: {
|
|
4497
|
+
result: signal
|
|
4498
|
+
}
|
|
4499
|
+
},
|
|
4500
|
+
{ decision, entry, market }
|
|
4501
|
+
);
|
|
4502
|
+
return signal;
|
|
4503
|
+
}
|
|
4504
|
+
await beforePlaceOrder();
|
|
4505
|
+
const arrivalSnapshot = await getOrderArrivalSnapshot({
|
|
4506
|
+
connector,
|
|
4507
|
+
symbol
|
|
4508
|
+
});
|
|
4509
|
+
validateEntryProtectionAtArrival({
|
|
4510
|
+
direction: decision.entryContext.direction,
|
|
4511
|
+
signalPrice: decision.entryContext.prices.currentPrice,
|
|
4512
|
+
bid: arrivalSnapshot.bid,
|
|
4513
|
+
ask: arrivalSnapshot.ask,
|
|
4514
|
+
arrivalMid: arrivalSnapshot.arrivalMid,
|
|
4515
|
+
takeProfits: decision.orderPlan.takeProfits,
|
|
4516
|
+
stopLossPrice: decision.orderPlan.stopLossPrice
|
|
4517
|
+
});
|
|
4518
|
+
const orderPlaced = await connector.placeOrder({
|
|
4519
|
+
symbol,
|
|
4520
|
+
qty: decision.orderPlan.qty,
|
|
4521
|
+
price: decision.entryContext.prices.currentPrice,
|
|
4522
|
+
timestamp: decision.entryContext.timestamp,
|
|
4523
|
+
direction: decision.entryContext.direction,
|
|
4524
|
+
positionIntent: decision.orderPlan.positionIntent,
|
|
4525
|
+
...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {}
|
|
4526
|
+
});
|
|
4527
|
+
if (!orderPlaced) {
|
|
4528
|
+
throw new Error("PLACE_ORDER_FAILED");
|
|
4529
|
+
}
|
|
4530
|
+
try {
|
|
4531
|
+
await updatePositionProtection({
|
|
4532
|
+
connector,
|
|
4533
|
+
symbol,
|
|
4534
|
+
direction: decision.entryContext.direction,
|
|
4535
|
+
qty: decision.orderPlan.qty,
|
|
4536
|
+
takeProfits: decision.orderPlan.takeProfits,
|
|
4537
|
+
stopLossPrice: decision.orderPlan.stopLossPrice
|
|
4538
|
+
});
|
|
4539
|
+
} catch (error) {
|
|
4540
|
+
await connector.closePosition({
|
|
4541
|
+
symbol,
|
|
4542
|
+
price: decision.entryContext.prices.currentPrice,
|
|
4543
|
+
timestamp: decision.entryContext.timestamp,
|
|
4544
|
+
direction: decision.entryContext.direction
|
|
4545
|
+
});
|
|
4546
|
+
throw error;
|
|
4547
|
+
}
|
|
4548
|
+
await invokeStageHooks(
|
|
4549
|
+
"afterPlaceOrder",
|
|
4550
|
+
manifest?.hooks?.afterPlaceOrder,
|
|
4551
|
+
{
|
|
4552
|
+
ctx: hookCtx,
|
|
4553
|
+
market,
|
|
4554
|
+
decision,
|
|
4555
|
+
entry,
|
|
4556
|
+
policy,
|
|
4557
|
+
ml,
|
|
4558
|
+
ai,
|
|
4559
|
+
order: {
|
|
4560
|
+
result: decision.code
|
|
4561
|
+
}
|
|
4562
|
+
},
|
|
4563
|
+
{ decision, entry, market }
|
|
4564
|
+
);
|
|
4565
|
+
} catch (err) {
|
|
4566
|
+
if (signal) {
|
|
4567
|
+
signal.orderStatus = "failed";
|
|
4568
|
+
if (typeof signal.orderFailureReason !== "string" || !signal.orderFailureReason.trim()) {
|
|
4569
|
+
signal.orderFailureReason = typeof err?.message === "string" && err.message.trim() ? err.message.trim() : void 0;
|
|
4570
|
+
}
|
|
4571
|
+
}
|
|
4572
|
+
await notifyRuntimeError({
|
|
4573
|
+
stage: "placeOrder",
|
|
4574
|
+
error: err,
|
|
4575
|
+
decision,
|
|
4576
|
+
entry,
|
|
4577
|
+
market
|
|
4578
|
+
});
|
|
4579
|
+
if (err?.message === import_types.BACKTEST_WARNING_CODES.TAKE_PROFIT_CROSSED_BEFORE_ENTRY) {
|
|
4580
|
+
import_logger9.logger.warn("order warning: %s %s", symbol, err);
|
|
4581
|
+
} else {
|
|
4582
|
+
import_logger9.logger.error("order error: %s %s", symbol, err);
|
|
4583
|
+
}
|
|
4584
|
+
return signal ?? "ORDER_ERROR";
|
|
4585
|
+
}
|
|
4586
|
+
return signal ?? decision.code;
|
|
4587
|
+
};
|
|
4588
|
+
|
|
4589
|
+
// src/strategyRuntime.ts
|
|
4590
|
+
var cloneWithPropertyDescriptors = (value) => Object.create(
|
|
4591
|
+
Object.getPrototypeOf(value),
|
|
4592
|
+
Object.getOwnPropertyDescriptors(value)
|
|
4593
|
+
);
|
|
4594
|
+
var createStrategyRuntime = ({
|
|
4595
|
+
strategyName,
|
|
4596
|
+
defaults,
|
|
4597
|
+
createCore,
|
|
4598
|
+
manifest: staticManifest,
|
|
4599
|
+
detectorKey,
|
|
4600
|
+
detectorNoSignalSkipReason,
|
|
4601
|
+
resolveRegisteredManifest
|
|
4602
|
+
}) => {
|
|
4603
|
+
const projectRoot = getTradejsProjectCwd();
|
|
4604
|
+
const resolveManifest = (name) => {
|
|
4605
|
+
if (!name) {
|
|
4606
|
+
return void 0;
|
|
4607
|
+
}
|
|
4608
|
+
if (staticManifest?.name === name) {
|
|
4609
|
+
return staticManifest;
|
|
4610
|
+
}
|
|
4611
|
+
return resolveRegisteredManifest?.(name);
|
|
4612
|
+
};
|
|
4613
|
+
const creator = async ({
|
|
4614
|
+
userName,
|
|
4615
|
+
connectorName,
|
|
4616
|
+
config: baseConfig,
|
|
4617
|
+
symbol,
|
|
4618
|
+
universe: requestedUniverse,
|
|
4619
|
+
assetClass,
|
|
4620
|
+
accountId: requestedAccountId,
|
|
4621
|
+
deploymentId: requestedDeploymentId,
|
|
4622
|
+
policyProfileId,
|
|
4623
|
+
runtimeConfigId,
|
|
4624
|
+
runtimeConfigSnapshot,
|
|
4625
|
+
data,
|
|
4626
|
+
btcData,
|
|
4627
|
+
ethData = [],
|
|
4628
|
+
btcBinanceData,
|
|
4629
|
+
btcCoinbaseData,
|
|
4630
|
+
backtestExecutionMarketData,
|
|
4631
|
+
connector,
|
|
4632
|
+
sharedIndicatorsReplayKey,
|
|
4633
|
+
sharedStrategyStateKey,
|
|
4634
|
+
onRuntimeClose
|
|
4635
|
+
}) => {
|
|
4636
|
+
const { config, isConfigFromBacktest } = await resolveStrategyConfig({
|
|
4637
|
+
strategyName,
|
|
4638
|
+
userName,
|
|
4639
|
+
symbol,
|
|
4640
|
+
baseConfig,
|
|
4641
|
+
defaults,
|
|
4642
|
+
runtimeConfigId,
|
|
4643
|
+
runtimeConfigSnapshot
|
|
4644
|
+
});
|
|
4645
|
+
const universe = requestedUniverse ?? connector.universe;
|
|
4646
|
+
const accountId = requestedAccountId ?? connector.accountId;
|
|
4647
|
+
const deploymentId = requestedDeploymentId ?? connector.deploymentId;
|
|
4648
|
+
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
4649
|
+
const projectHooks = projectConfig.hooks;
|
|
4650
|
+
const env = String(config.ENV ?? "BACKTEST");
|
|
4651
|
+
const backtestPriceMode = config.BACKTEST_PRICE_MODE ?? "open";
|
|
4652
|
+
const backtestEntryDelayBars = env === "BACKTEST" ? resolveBacktestEntryDelayBars(config.BACKTEST_ENTRY_DELAY_BARS) : 0;
|
|
4653
|
+
const resolvedBacktestExecutionInterval = config.BACKTEST_EXECUTION_INTERVAL ?? backtestExecutionMarketData?.interval ?? resolveBacktestExecutionIntervalForPrimary(config.INTERVAL ?? "15");
|
|
4654
|
+
const backtestExecutionInterval = resolvedBacktestExecutionInterval == null ? null : String(resolvedBacktestExecutionInterval);
|
|
4655
|
+
const backtestExecutionIntervalLabel = backtestExecutionInterval == null ? void 0 : String(backtestExecutionInterval);
|
|
4656
|
+
const primaryIntervalMs = safeIntervalToMs(config.INTERVAL ?? "15");
|
|
4657
|
+
const backtestExecutionIntervalMs = safeIntervalToMs(
|
|
4658
|
+
backtestExecutionInterval
|
|
4659
|
+
);
|
|
4660
|
+
const backtestExecutionDelayMs = resolveBacktestExecutionDelayMs(
|
|
4661
|
+
config.BACKTEST_EXECUTION_DELAY_MS,
|
|
4662
|
+
backtestExecutionIntervalMs ?? import_constants5.BACKTEST_EXECUTION_DELAY_MS
|
|
4663
|
+
);
|
|
4664
|
+
const backtestExecutionCandleByTimestamp = backtestExecutionMarketData?.dataByTimestamp ?? buildCandleByTimestamp(backtestExecutionMarketData?.data);
|
|
4665
|
+
const backtestExecutionBtcCandleByTimestamp = backtestExecutionMarketData?.btcDataByTimestamp ?? buildCandleByTimestamp(backtestExecutionMarketData?.btcData);
|
|
4666
|
+
const canUseLowerBacktestExecution = env === "BACKTEST" && backtestEntryDelayBars > 0 && backtestExecutionIntervalMs != null && primaryIntervalMs != null && backtestExecutionIntervalMs < primaryIntervalMs;
|
|
4667
|
+
const recordRuntimeJournal = shouldRecordRuntimeJournal({ env, config });
|
|
4668
|
+
const strategyManifest = resolveManifest(strategyName);
|
|
4669
|
+
const requestedPolicyProfileId = policyProfileId ?? (typeof config.POLICY_PROFILE_ID === "string" ? config.POLICY_PROFILE_ID : void 0);
|
|
4670
|
+
const getPolicyProfile = (name = strategyName) => resolveStrategyPolicyProfile(resolveManifest(name), {
|
|
4671
|
+
profileId: requestedPolicyProfileId,
|
|
4672
|
+
universe,
|
|
4673
|
+
assetClass
|
|
4674
|
+
});
|
|
4675
|
+
const strategyPolicyProfile = getPolicyProfile();
|
|
4676
|
+
const indicatorPeriods = (0, import_strategies6.buildDefaultIndicatorPeriods)(config);
|
|
4677
|
+
const hookBase = {
|
|
4678
|
+
connector,
|
|
4679
|
+
strategyName,
|
|
4680
|
+
userName,
|
|
4681
|
+
symbol,
|
|
4682
|
+
universe,
|
|
4683
|
+
assetClass,
|
|
4684
|
+
accountId,
|
|
4685
|
+
deploymentId,
|
|
4686
|
+
policyProfileId: strategyPolicyProfile?.id ?? requestedPolicyProfileId,
|
|
4687
|
+
strategyConfig: config,
|
|
4688
|
+
env,
|
|
4689
|
+
isConfigFromBacktest
|
|
4690
|
+
};
|
|
4691
|
+
const getHookCtx = (name = strategyName) => {
|
|
4692
|
+
const profile = getPolicyProfile(name);
|
|
4693
|
+
return buildHookCtx({
|
|
4694
|
+
...hookBase,
|
|
4695
|
+
strategyName: name,
|
|
4696
|
+
policyProfileId: profile?.id ?? requestedPolicyProfileId
|
|
4697
|
+
});
|
|
4698
|
+
};
|
|
4699
|
+
const getProjectHookList = (stage) => normalizeConfigHookList(projectHooks?.[stage]);
|
|
4700
|
+
const indicatorReplayKey = JSON.stringify({
|
|
4701
|
+
periods: indicatorPeriods,
|
|
4702
|
+
universe
|
|
4703
|
+
});
|
|
4704
|
+
const sharedReplayEnabled = canUseSharedReplayState({
|
|
4705
|
+
env,
|
|
4706
|
+
sharedReplayKey: sharedIndicatorsReplayKey
|
|
4707
|
+
});
|
|
4708
|
+
const indicatorSharedReplayKey = sharedReplayEnabled && sharedIndicatorsReplayKey ? `${sharedIndicatorsReplayKey}:indicators:${indicatorReplayKey}` : void 0;
|
|
4709
|
+
const strategyStateBaseKey = env === "CRON" && sharedStrategyStateKey ? sharedStrategyStateKey : sharedReplayEnabled ? sharedIndicatorsReplayKey : void 0;
|
|
4710
|
+
const strategySharedReplayKey = strategyStateBaseKey ? `${strategyStateBaseKey}:strategy:${strategyName}` : void 0;
|
|
4711
|
+
const notifyRuntimeError = async ({
|
|
4712
|
+
stage,
|
|
4713
|
+
error,
|
|
4714
|
+
decision,
|
|
4715
|
+
entry,
|
|
4716
|
+
market
|
|
4717
|
+
}) => {
|
|
4718
|
+
const errorStrategyName = decision?.kind === "entry" ? decision.entryContext.strategy : strategyName;
|
|
4719
|
+
const errorManifest = resolveManifest(errorStrategyName) ?? strategyManifest;
|
|
4720
|
+
const errorParams = {
|
|
4721
|
+
ctx: getHookCtx(errorStrategyName),
|
|
4722
|
+
market,
|
|
4723
|
+
decision,
|
|
4724
|
+
entry,
|
|
4725
|
+
error: {
|
|
4726
|
+
stage,
|
|
4727
|
+
cause: error
|
|
4728
|
+
}
|
|
4729
|
+
};
|
|
4730
|
+
for (const projectHook of getProjectHookList("onRuntimeError")) {
|
|
4731
|
+
try {
|
|
4732
|
+
await projectHook(errorParams);
|
|
4733
|
+
} catch (hookError) {
|
|
4734
|
+
import_logger10.logger.error(
|
|
4735
|
+
"project hook onRuntimeError failed: %s %s",
|
|
4736
|
+
strategyName,
|
|
4737
|
+
hookError
|
|
4738
|
+
);
|
|
4739
|
+
}
|
|
4740
|
+
}
|
|
4741
|
+
const onRuntimeError = errorManifest?.hooks?.onRuntimeError;
|
|
4742
|
+
if (!onRuntimeError) {
|
|
4743
|
+
return;
|
|
4744
|
+
}
|
|
4745
|
+
try {
|
|
4746
|
+
await onRuntimeError(errorParams);
|
|
4747
|
+
} catch (hookError) {
|
|
4748
|
+
import_logger10.logger.error(
|
|
4749
|
+
"runtime hook onRuntimeError failed: %s %s",
|
|
4750
|
+
strategyName,
|
|
4751
|
+
hookError
|
|
4752
|
+
);
|
|
4753
|
+
}
|
|
4754
|
+
};
|
|
4755
|
+
const invokeHook = async (stage, hook, params, errorContext = {}) => {
|
|
4756
|
+
if (!hook) {
|
|
4757
|
+
return void 0;
|
|
4758
|
+
}
|
|
4759
|
+
try {
|
|
4760
|
+
return await hook(params);
|
|
4761
|
+
} catch (error) {
|
|
4762
|
+
import_logger10.logger.error(
|
|
4763
|
+
'strategy hook "%s" failed for %s: %s',
|
|
4764
|
+
stage,
|
|
4765
|
+
strategyName,
|
|
4766
|
+
error
|
|
4767
|
+
);
|
|
4768
|
+
await notifyRuntimeError({
|
|
4769
|
+
stage,
|
|
4770
|
+
error,
|
|
4771
|
+
decision: errorContext.decision,
|
|
4772
|
+
entry: errorContext.entry,
|
|
4773
|
+
market: errorContext.market
|
|
4774
|
+
});
|
|
4775
|
+
return void 0;
|
|
4776
|
+
}
|
|
4777
|
+
};
|
|
4778
|
+
const invokeProjectHooks = async (stage, params, errorContext = {}) => {
|
|
4779
|
+
const results = [];
|
|
4780
|
+
for (const hook of getProjectHookList(stage)) {
|
|
4781
|
+
const result = await invokeHook(
|
|
4782
|
+
stage,
|
|
4783
|
+
hook,
|
|
4784
|
+
params,
|
|
4785
|
+
errorContext
|
|
4786
|
+
);
|
|
4787
|
+
if (result !== void 0) {
|
|
4788
|
+
results.push(result);
|
|
4789
|
+
}
|
|
4790
|
+
}
|
|
4791
|
+
return results;
|
|
4792
|
+
};
|
|
4793
|
+
const invokeStageHooks = async (stage, hook, params, errorContext = {}) => {
|
|
4794
|
+
if (isConfigHookStage(stage)) {
|
|
4795
|
+
await invokeProjectHooks(stage, params, errorContext);
|
|
4796
|
+
}
|
|
4797
|
+
return invokeHook(stage, hook, params, errorContext);
|
|
4798
|
+
};
|
|
4799
|
+
const invokeGateHooks = async (stage, hook, params, errorContext = {}) => {
|
|
4800
|
+
const projectResults = await invokeProjectHooks(
|
|
4801
|
+
stage,
|
|
4802
|
+
params,
|
|
4803
|
+
errorContext
|
|
4804
|
+
);
|
|
4805
|
+
const projectBlock = projectResults.find(
|
|
4806
|
+
(result) => result?.allow === false
|
|
4807
|
+
);
|
|
4808
|
+
if (projectBlock?.allow === false) {
|
|
4809
|
+
return projectBlock;
|
|
4810
|
+
}
|
|
4811
|
+
return invokeHook(
|
|
4812
|
+
stage,
|
|
4813
|
+
hook,
|
|
4814
|
+
params,
|
|
4815
|
+
errorContext
|
|
4816
|
+
);
|
|
4817
|
+
};
|
|
4818
|
+
const applyProjectAfterCoreDecisionHooks = async ({
|
|
4819
|
+
hookCtx,
|
|
4820
|
+
market,
|
|
4821
|
+
decision
|
|
4822
|
+
}) => {
|
|
4823
|
+
let nextDecision = decision;
|
|
4824
|
+
for (const hook of getProjectHookList(
|
|
4825
|
+
"afterCoreDecision"
|
|
4826
|
+
)) {
|
|
4827
|
+
const result = await invokeHook(
|
|
4828
|
+
"afterCoreDecision",
|
|
4829
|
+
hook,
|
|
4830
|
+
{
|
|
4831
|
+
ctx: hookCtx,
|
|
4832
|
+
market,
|
|
4833
|
+
decision: nextDecision
|
|
4834
|
+
},
|
|
4835
|
+
{
|
|
4836
|
+
decision: nextDecision,
|
|
4837
|
+
market
|
|
4838
|
+
}
|
|
4839
|
+
);
|
|
4840
|
+
if (isStrategyDecision(result)) {
|
|
4841
|
+
nextDecision = result;
|
|
4842
|
+
}
|
|
4843
|
+
}
|
|
4844
|
+
return nextDecision;
|
|
4845
|
+
};
|
|
4846
|
+
const applyProjectAfterBarDecisionHooks = async ({
|
|
4847
|
+
hookCtx,
|
|
4848
|
+
market,
|
|
4849
|
+
decision
|
|
4850
|
+
}) => {
|
|
4851
|
+
let nextDecision = decision;
|
|
4852
|
+
for (const hook of getProjectHookList(
|
|
4853
|
+
"afterBarDecision"
|
|
4854
|
+
)) {
|
|
4855
|
+
const result = await invokeHook(
|
|
4856
|
+
"afterBarDecision",
|
|
4857
|
+
hook,
|
|
4858
|
+
{
|
|
4859
|
+
ctx: hookCtx,
|
|
4860
|
+
market,
|
|
4861
|
+
decision: nextDecision
|
|
4862
|
+
},
|
|
4863
|
+
{
|
|
4864
|
+
decision: nextDecision,
|
|
4865
|
+
market
|
|
4866
|
+
}
|
|
4867
|
+
);
|
|
4868
|
+
if (isStrategyDecision(result)) {
|
|
4869
|
+
nextDecision = result;
|
|
4870
|
+
}
|
|
4871
|
+
}
|
|
4872
|
+
return nextDecision;
|
|
4873
|
+
};
|
|
4874
|
+
const applyProjectOnBarHooks = async ({
|
|
4875
|
+
hookCtx,
|
|
4876
|
+
market
|
|
4877
|
+
}) => {
|
|
4878
|
+
for (const hook of getProjectHookList(
|
|
4879
|
+
"onBar"
|
|
4880
|
+
)) {
|
|
4881
|
+
const result = await invokeHook(
|
|
4882
|
+
"onBar",
|
|
4883
|
+
hook,
|
|
4884
|
+
{
|
|
4885
|
+
ctx: hookCtx,
|
|
4886
|
+
market
|
|
4887
|
+
},
|
|
4888
|
+
{
|
|
4889
|
+
market
|
|
4890
|
+
}
|
|
4891
|
+
);
|
|
4892
|
+
if (isStrategyDecision(result)) {
|
|
4893
|
+
return result;
|
|
4894
|
+
}
|
|
4895
|
+
}
|
|
4896
|
+
return void 0;
|
|
4897
|
+
};
|
|
4898
|
+
const indicatorsState = (0, import_strategies6.createStrategyIndicatorsState)({
|
|
4899
|
+
env,
|
|
4900
|
+
data,
|
|
4901
|
+
btcData,
|
|
4902
|
+
ethData,
|
|
4903
|
+
btcBinanceData,
|
|
4904
|
+
btcCoinbaseData,
|
|
4905
|
+
periods: indicatorPeriods,
|
|
4906
|
+
pluginRegistryScope: projectRoot,
|
|
4907
|
+
sharedReplayKey: indicatorSharedReplayKey,
|
|
4908
|
+
useBtcReference: universe === "crypto"
|
|
4909
|
+
});
|
|
4910
|
+
const coreContextRequirements = new Set(
|
|
4911
|
+
strategyManifest?.contextRequirements?.core ?? []
|
|
4912
|
+
);
|
|
4913
|
+
const strategyApi = (0, import_strategies6.createStrategyAPI)({
|
|
4914
|
+
strategy: strategyName,
|
|
4915
|
+
symbol,
|
|
4916
|
+
interval: config.INTERVAL ?? "15",
|
|
4917
|
+
env,
|
|
4918
|
+
connector,
|
|
4919
|
+
cachedData: data,
|
|
4920
|
+
indicatorsState,
|
|
4921
|
+
isConfigFromBacktest,
|
|
4922
|
+
sharedReplayKey: strategySharedReplayKey,
|
|
4923
|
+
getSharedReplayState: import_strategies6.getSharedStrategyReplayState,
|
|
4924
|
+
loadDecisionBaseContext: async ({
|
|
4925
|
+
baseContext,
|
|
4926
|
+
candle,
|
|
4927
|
+
symbol: decisionSymbol,
|
|
4928
|
+
interval: decisionInterval
|
|
4929
|
+
}) => {
|
|
4930
|
+
if (!baseContext) return void 0;
|
|
4931
|
+
if (!coreContextRequirements.has("hyperliquidWhales")) {
|
|
4932
|
+
return baseContext;
|
|
4933
|
+
}
|
|
4934
|
+
const hyperliquidWhales = await loadHyperliquidWhaleFlowContext({
|
|
4935
|
+
symbol: decisionSymbol,
|
|
4936
|
+
interval: decisionInterval,
|
|
4937
|
+
timestamp: candle.timestamp,
|
|
4938
|
+
env,
|
|
4939
|
+
useSeriesCache: env === "BACKTEST" || env === "PARITY"
|
|
4940
|
+
});
|
|
4941
|
+
if (!hyperliquidWhales) return baseContext;
|
|
4942
|
+
const participation = cloneWithPropertyDescriptors(
|
|
4943
|
+
baseContext.participation
|
|
4944
|
+
);
|
|
4945
|
+
Object.defineProperty(participation, "hyperliquidWhales", {
|
|
4946
|
+
configurable: true,
|
|
4947
|
+
enumerable: true,
|
|
4948
|
+
value: hyperliquidWhales,
|
|
4949
|
+
writable: true
|
|
4950
|
+
});
|
|
4951
|
+
const enrichedBaseContext = cloneWithPropertyDescriptors(baseContext);
|
|
4952
|
+
Object.defineProperty(enrichedBaseContext, "participation", {
|
|
4953
|
+
configurable: true,
|
|
4954
|
+
enumerable: true,
|
|
4955
|
+
value: participation,
|
|
4956
|
+
writable: true
|
|
4957
|
+
});
|
|
4958
|
+
return enrichedBaseContext;
|
|
4959
|
+
}
|
|
4960
|
+
});
|
|
4961
|
+
const core = await createCore({
|
|
4962
|
+
config,
|
|
4963
|
+
data,
|
|
4964
|
+
strategyApi,
|
|
4965
|
+
indicatorsState
|
|
4966
|
+
});
|
|
4967
|
+
await invokeStageHooks("onInit", strategyManifest?.hooks?.onInit, {
|
|
4968
|
+
ctx: getHookCtx(),
|
|
4969
|
+
market: {
|
|
4970
|
+
data,
|
|
4971
|
+
btcData
|
|
4972
|
+
}
|
|
4973
|
+
});
|
|
4974
|
+
const appendCurrentMarketData = (candle, btcCandle, ethCandle) => {
|
|
4975
|
+
if (data[data.length - 1]?.timestamp !== candle.timestamp) {
|
|
4976
|
+
data.push(candle);
|
|
4977
|
+
}
|
|
4978
|
+
if (universe === "crypto" && btcData[btcData.length - 1]?.timestamp !== btcCandle.timestamp) {
|
|
4979
|
+
btcData.push(btcCandle);
|
|
4980
|
+
}
|
|
4981
|
+
if (universe === "crypto" && ethCandle && ethData[ethData.length - 1]?.timestamp !== ethCandle.timestamp) {
|
|
4982
|
+
ethData.push(ethCandle);
|
|
4983
|
+
}
|
|
4984
|
+
};
|
|
4985
|
+
const resolveEthCandle = (candle, ethCandle) => {
|
|
4986
|
+
if (ethCandle?.timestamp === candle.timestamp) {
|
|
4987
|
+
return ethCandle;
|
|
4988
|
+
}
|
|
4989
|
+
const alignedEthCandle = ethData[data.length - 1];
|
|
4990
|
+
if (alignedEthCandle?.timestamp === candle.timestamp) {
|
|
4991
|
+
return alignedEthCandle;
|
|
4992
|
+
}
|
|
4993
|
+
const latestEthCandle = ethData[ethData.length - 1];
|
|
4994
|
+
if (latestEthCandle?.timestamp === candle.timestamp) {
|
|
4995
|
+
return latestEthCandle;
|
|
4996
|
+
}
|
|
4997
|
+
return void 0;
|
|
4998
|
+
};
|
|
4999
|
+
const resolveBacktestExecutionCandle = (candle, btcCandle) => {
|
|
5000
|
+
if (!import_constants5.BACKTEST_LOWER_TIMEFRAME_EXECUTION_ENABLED) {
|
|
5001
|
+
return {
|
|
5002
|
+
candle,
|
|
5003
|
+
btcCandle,
|
|
5004
|
+
source: "primary_timeframe",
|
|
5005
|
+
requestedExecutionTimestamp: candle.timestamp,
|
|
5006
|
+
executionInterval: String(config.INTERVAL ?? "15"),
|
|
5007
|
+
executionDelayMs: 0,
|
|
5008
|
+
primaryExecutionTimestamp: candle.timestamp
|
|
5009
|
+
};
|
|
5010
|
+
}
|
|
5011
|
+
const requestedExecutionTimestamp = candle.timestamp + backtestExecutionDelayMs;
|
|
5012
|
+
const primaryExecutionTimestamp = candle.timestamp;
|
|
5013
|
+
if (!canUseLowerBacktestExecution || primaryIntervalMs == null) {
|
|
5014
|
+
return {
|
|
5015
|
+
source: "lower_timeframe",
|
|
5016
|
+
requestedExecutionTimestamp,
|
|
5017
|
+
executionInterval: backtestExecutionIntervalLabel,
|
|
5018
|
+
executionDelayMs: backtestExecutionDelayMs,
|
|
5019
|
+
primaryExecutionTimestamp,
|
|
5020
|
+
skipReason: "BACKTEST_LOWER_EXECUTION_UNAVAILABLE"
|
|
5021
|
+
};
|
|
5022
|
+
}
|
|
5023
|
+
if (requestedExecutionTimestamp >= candle.timestamp + primaryIntervalMs) {
|
|
5024
|
+
return {
|
|
5025
|
+
source: "lower_timeframe",
|
|
5026
|
+
requestedExecutionTimestamp,
|
|
5027
|
+
executionInterval: backtestExecutionIntervalLabel,
|
|
5028
|
+
executionDelayMs: backtestExecutionDelayMs,
|
|
5029
|
+
primaryExecutionTimestamp,
|
|
5030
|
+
skipReason: "BACKTEST_LOWER_EXECUTION_DELAY_OUT_OF_BAR"
|
|
5031
|
+
};
|
|
5032
|
+
}
|
|
5033
|
+
const lowerCandle = backtestExecutionCandleByTimestamp.get(
|
|
5034
|
+
requestedExecutionTimestamp
|
|
5035
|
+
);
|
|
5036
|
+
const lowerBtcCandle = backtestExecutionBtcCandleByTimestamp.get(
|
|
5037
|
+
requestedExecutionTimestamp
|
|
5038
|
+
);
|
|
5039
|
+
if (lowerCandle && lowerBtcCandle) {
|
|
5040
|
+
return {
|
|
5041
|
+
candle: lowerCandle,
|
|
5042
|
+
btcCandle: lowerBtcCandle,
|
|
5043
|
+
source: "lower_timeframe",
|
|
5044
|
+
requestedExecutionTimestamp,
|
|
5045
|
+
executionInterval: backtestExecutionIntervalLabel,
|
|
5046
|
+
executionDelayMs: backtestExecutionDelayMs,
|
|
5047
|
+
primaryExecutionTimestamp
|
|
5048
|
+
};
|
|
5049
|
+
}
|
|
5050
|
+
return {
|
|
5051
|
+
source: "lower_timeframe",
|
|
5052
|
+
requestedExecutionTimestamp,
|
|
5053
|
+
executionInterval: backtestExecutionIntervalLabel,
|
|
5054
|
+
executionDelayMs: backtestExecutionDelayMs,
|
|
5055
|
+
primaryExecutionTimestamp,
|
|
5056
|
+
skipReason: !lowerCandle ? "BACKTEST_LOWER_EXECUTION_CANDLE_MISSING" : "BACKTEST_LOWER_EXECUTION_BTC_CANDLE_MISSING"
|
|
5057
|
+
};
|
|
5058
|
+
};
|
|
5059
|
+
let pendingBacktestEntry = null;
|
|
5060
|
+
const flushPendingBacktestEntry = async (candle, btcCandle, ethCandle) => {
|
|
5061
|
+
if (!pendingBacktestEntry) {
|
|
5062
|
+
return void 0;
|
|
5063
|
+
}
|
|
5064
|
+
appendCurrentMarketData(candle, btcCandle, ethCandle);
|
|
5065
|
+
const resolvedEthCandle = resolveEthCandle(candle, ethCandle);
|
|
5066
|
+
indicatorsState.setCurrentBar(candle, btcCandle, resolvedEthCandle);
|
|
5067
|
+
pendingBacktestEntry.delayBarsRemaining -= 1;
|
|
5068
|
+
if (pendingBacktestEntry.delayBarsRemaining > 0) {
|
|
5069
|
+
return `BACKTEST_ENTRY_DELAY_PENDING:${pendingBacktestEntry.delayBarsRemaining}`;
|
|
5070
|
+
}
|
|
5071
|
+
const pending = pendingBacktestEntry;
|
|
5072
|
+
pendingBacktestEntry = null;
|
|
5073
|
+
const executionCandleResolution = resolveBacktestExecutionCandle(
|
|
5074
|
+
candle,
|
|
5075
|
+
btcCandle
|
|
5076
|
+
);
|
|
5077
|
+
const execution = applyBacktestDelayedEntryExecution({
|
|
5078
|
+
decision: pending.decision,
|
|
5079
|
+
execution: executionCandleResolution,
|
|
5080
|
+
backtestPriceMode: executionCandleResolution.source === "primary_timeframe" ? "open" : backtestPriceMode,
|
|
5081
|
+
delayBars: pending.delayBars
|
|
5082
|
+
});
|
|
5083
|
+
if (execution.skipReason) {
|
|
5084
|
+
return pending.decision.signal ?? execution.skipReason;
|
|
5085
|
+
}
|
|
5086
|
+
if (!execution.executionCandle || !execution.btcExecutionCandle) {
|
|
5087
|
+
return pending.decision.signal ?? "BACKTEST_LOWER_EXECUTION_CANDLE_MISSING";
|
|
5088
|
+
}
|
|
5089
|
+
const market = {
|
|
5090
|
+
candle: execution.executionCandle,
|
|
5091
|
+
btcCandle: execution.btcExecutionCandle
|
|
5092
|
+
};
|
|
5093
|
+
const entry = buildHookEntry({
|
|
5094
|
+
decision: pending.decision,
|
|
5095
|
+
runtime: pending.runtime
|
|
5096
|
+
});
|
|
5097
|
+
return executeEntryDecision({
|
|
5098
|
+
connector,
|
|
5099
|
+
symbol,
|
|
5100
|
+
decision: pending.decision,
|
|
5101
|
+
runtime: pending.runtime,
|
|
5102
|
+
manifest: pending.manifest,
|
|
5103
|
+
hookCtx: pending.hookCtx,
|
|
5104
|
+
market,
|
|
5105
|
+
entry,
|
|
5106
|
+
policy: pending.policy,
|
|
5107
|
+
ml: pending.ml,
|
|
5108
|
+
ai: pending.ai,
|
|
5109
|
+
recordRuntimeJournal,
|
|
5110
|
+
invokeStageHooks,
|
|
5111
|
+
notifyRuntimeError
|
|
5112
|
+
});
|
|
5113
|
+
};
|
|
5114
|
+
const runWithDecisionOverride = async (candle, btcCandle, options = {}) => {
|
|
5115
|
+
appendCurrentMarketData(candle, btcCandle, options.ethCandle);
|
|
5116
|
+
const ethCandle = resolveEthCandle(candle, options.ethCandle);
|
|
5117
|
+
indicatorsState.setCurrentBar(candle, btcCandle, ethCandle);
|
|
5118
|
+
const delayedEntrySignal = await flushPendingBacktestEntry(
|
|
5119
|
+
candle,
|
|
5120
|
+
btcCandle,
|
|
5121
|
+
ethCandle
|
|
5122
|
+
);
|
|
5123
|
+
if (delayedEntrySignal) {
|
|
5124
|
+
return delayedEntrySignal;
|
|
5125
|
+
}
|
|
5126
|
+
const market = {
|
|
5127
|
+
candle,
|
|
5128
|
+
btcCandle
|
|
5129
|
+
};
|
|
5130
|
+
const onBarHookCtx = getHookCtx();
|
|
5131
|
+
const projectOnBarDecision = await applyProjectOnBarHooks({
|
|
5132
|
+
hookCtx: onBarHookCtx,
|
|
5133
|
+
market
|
|
5134
|
+
});
|
|
5135
|
+
let decision;
|
|
5136
|
+
let shouldInvokeAfterCoreDecisionHook = false;
|
|
5137
|
+
if (projectOnBarDecision) {
|
|
5138
|
+
decision = projectOnBarDecision;
|
|
5139
|
+
} else {
|
|
5140
|
+
const manifestOnBarDecision = await invokeHook(
|
|
5141
|
+
"onBar",
|
|
5142
|
+
strategyManifest?.hooks?.onBar,
|
|
5143
|
+
{
|
|
5144
|
+
ctx: onBarHookCtx,
|
|
5145
|
+
market
|
|
5146
|
+
},
|
|
5147
|
+
{ market }
|
|
5148
|
+
);
|
|
5149
|
+
if (isStrategyDecision(manifestOnBarDecision)) {
|
|
5150
|
+
decision = manifestOnBarDecision;
|
|
5151
|
+
} else {
|
|
5152
|
+
decision = options.coreDecisionOverride ?? await core(candle, btcCandle);
|
|
5153
|
+
shouldInvokeAfterCoreDecisionHook = true;
|
|
5154
|
+
}
|
|
5155
|
+
}
|
|
5156
|
+
if (shouldInvokeAfterCoreDecisionHook) {
|
|
5157
|
+
const initialDecisionStrategyName = decision.kind === "entry" ? decision.entryContext.strategy : strategyName;
|
|
5158
|
+
decision = await applyProjectAfterCoreDecisionHooks({
|
|
5159
|
+
hookCtx: getHookCtx(initialDecisionStrategyName),
|
|
5160
|
+
market,
|
|
5161
|
+
decision
|
|
5162
|
+
});
|
|
5163
|
+
}
|
|
5164
|
+
const initialAfterBarDecisionStrategyName = decision.kind === "entry" ? decision.entryContext.strategy : strategyName;
|
|
5165
|
+
decision = await applyProjectAfterBarDecisionHooks({
|
|
5166
|
+
hookCtx: getHookCtx(initialAfterBarDecisionStrategyName),
|
|
5167
|
+
market,
|
|
5168
|
+
decision
|
|
5169
|
+
});
|
|
5170
|
+
const decisionStrategyName = decision.kind === "entry" ? decision.entryContext.strategy : strategyName;
|
|
5171
|
+
const decisionManifest = resolveManifest(decisionStrategyName) ?? strategyManifest;
|
|
5172
|
+
const decisionHookCtx = getHookCtx(decisionStrategyName);
|
|
5173
|
+
if (shouldInvokeAfterCoreDecisionHook) {
|
|
5174
|
+
await invokeHook(
|
|
5175
|
+
"afterCoreDecision",
|
|
5176
|
+
decisionManifest?.hooks?.afterCoreDecision,
|
|
5177
|
+
{
|
|
5178
|
+
ctx: decisionHookCtx,
|
|
5179
|
+
market,
|
|
5180
|
+
decision
|
|
5181
|
+
},
|
|
5182
|
+
{ decision, market }
|
|
5183
|
+
);
|
|
5184
|
+
}
|
|
5185
|
+
await invokeHook(
|
|
5186
|
+
"afterBarDecision",
|
|
5187
|
+
decisionManifest?.hooks?.afterBarDecision,
|
|
5188
|
+
{
|
|
5189
|
+
ctx: decisionHookCtx,
|
|
5190
|
+
market,
|
|
5191
|
+
decision
|
|
5192
|
+
},
|
|
5193
|
+
{ decision, market }
|
|
5194
|
+
);
|
|
5195
|
+
if (decision.kind === "skip") {
|
|
5196
|
+
await invokeStageHooks(
|
|
5197
|
+
"onSkip",
|
|
5198
|
+
decisionManifest?.hooks?.onSkip,
|
|
5199
|
+
{
|
|
5200
|
+
ctx: decisionHookCtx,
|
|
5201
|
+
market,
|
|
5202
|
+
decision
|
|
5203
|
+
},
|
|
5204
|
+
{ decision, market }
|
|
5205
|
+
);
|
|
5206
|
+
return decision.code;
|
|
5207
|
+
}
|
|
5208
|
+
const rawMakeOrdersEnabled = typeof config.MAKE_ORDERS === "boolean" ? config.MAKE_ORDERS : true;
|
|
5209
|
+
const makeOrdersEnabled = rawMakeOrdersEnabled && (env !== "PARITY" || isTestConnector(connector));
|
|
5210
|
+
if (decision.kind === "exit") {
|
|
5211
|
+
if (!makeOrdersEnabled) {
|
|
5212
|
+
return decision.code;
|
|
5213
|
+
}
|
|
5214
|
+
const closeGate = await invokeGateHooks(
|
|
5215
|
+
"beforeClosePosition",
|
|
5216
|
+
decisionManifest?.hooks?.beforeClosePosition,
|
|
5217
|
+
{
|
|
5218
|
+
ctx: decisionHookCtx,
|
|
5219
|
+
market,
|
|
5220
|
+
decision
|
|
5221
|
+
},
|
|
5222
|
+
{ decision, market }
|
|
5223
|
+
);
|
|
5224
|
+
if (closeGate?.allow === false) {
|
|
5225
|
+
return closeGate.reason ? `CLOSE_BLOCKED_BY_HOOK:${closeGate.reason}` : "CLOSE_BLOCKED_BY_HOOK";
|
|
5226
|
+
}
|
|
5227
|
+
return handleExitDecision({
|
|
5228
|
+
connector,
|
|
5229
|
+
userName: recordRuntimeJournal ? userName : void 0,
|
|
5230
|
+
strategyName,
|
|
5231
|
+
symbol,
|
|
5232
|
+
decision,
|
|
5233
|
+
market,
|
|
5234
|
+
onRuntimeClose,
|
|
5235
|
+
onRuntimeError: async ({
|
|
5236
|
+
stage,
|
|
5237
|
+
error,
|
|
5238
|
+
decision: exitDecision,
|
|
5239
|
+
market: errorMarket
|
|
5240
|
+
}) => {
|
|
5241
|
+
await notifyRuntimeError({
|
|
5242
|
+
stage,
|
|
5243
|
+
error,
|
|
5244
|
+
decision: exitDecision,
|
|
5245
|
+
market: errorMarket
|
|
5246
|
+
});
|
|
5247
|
+
}
|
|
5248
|
+
});
|
|
5249
|
+
}
|
|
5250
|
+
if (decision.kind === "protect") {
|
|
5251
|
+
if (!makeOrdersEnabled) {
|
|
5252
|
+
return decision.code;
|
|
5253
|
+
}
|
|
5254
|
+
return handleProtectDecision({
|
|
5255
|
+
connector,
|
|
5256
|
+
symbol,
|
|
5257
|
+
decision,
|
|
5258
|
+
market,
|
|
5259
|
+
onRuntimeError: async ({
|
|
5260
|
+
stage,
|
|
5261
|
+
error,
|
|
5262
|
+
decision: protectDecision,
|
|
5263
|
+
market: errorMarket
|
|
5264
|
+
}) => {
|
|
5265
|
+
await notifyRuntimeError({
|
|
5266
|
+
stage,
|
|
5267
|
+
error,
|
|
5268
|
+
decision: protectDecision,
|
|
5269
|
+
market: errorMarket
|
|
5270
|
+
});
|
|
5271
|
+
}
|
|
5272
|
+
});
|
|
5273
|
+
}
|
|
5274
|
+
const runtime = resolveEntryRuntimePolicy({
|
|
5275
|
+
decision,
|
|
5276
|
+
config,
|
|
5277
|
+
manifest: decisionManifest,
|
|
5278
|
+
policyProfile: getPolicyProfile(decisionStrategyName)
|
|
5279
|
+
});
|
|
5280
|
+
const signal = decision.signal;
|
|
5281
|
+
if (signal) {
|
|
5282
|
+
if (universe) signal.universe = universe;
|
|
5283
|
+
if (assetClass) signal.assetClass = assetClass;
|
|
5284
|
+
if (accountId) signal.accountId = accountId;
|
|
5285
|
+
if (deploymentId) signal.deploymentId = deploymentId;
|
|
5286
|
+
if (runtimeConfigId) {
|
|
5287
|
+
signal.runtimeConfigId = runtimeConfigId;
|
|
5288
|
+
if (runtimeConfigId !== "config") {
|
|
5289
|
+
signal.signalId = `${signal.signalId}:${runtimeConfigId}`;
|
|
5290
|
+
}
|
|
5291
|
+
}
|
|
5292
|
+
if (decisionHookCtx.policyProfileId) {
|
|
5293
|
+
signal.policyProfileId = decisionHookCtx.policyProfileId;
|
|
5294
|
+
}
|
|
5295
|
+
}
|
|
5296
|
+
const entry = buildHookEntry({
|
|
5297
|
+
decision,
|
|
5298
|
+
runtime
|
|
5299
|
+
});
|
|
5300
|
+
let ml;
|
|
5301
|
+
if (signal) {
|
|
5302
|
+
try {
|
|
5303
|
+
await enrichSignalWithMl({
|
|
5304
|
+
signal,
|
|
5305
|
+
env,
|
|
5306
|
+
ml: runtime.ml
|
|
5307
|
+
});
|
|
5308
|
+
} catch (error) {
|
|
5309
|
+
await notifyRuntimeError({
|
|
5310
|
+
stage: "enrichSignalWithMl",
|
|
5311
|
+
error,
|
|
5312
|
+
decision,
|
|
5313
|
+
entry,
|
|
5314
|
+
market
|
|
5315
|
+
});
|
|
5316
|
+
throw error;
|
|
5317
|
+
}
|
|
5318
|
+
ml = buildMlHookContext({
|
|
5319
|
+
signal,
|
|
5320
|
+
env,
|
|
5321
|
+
ml: runtime.ml
|
|
5322
|
+
});
|
|
5323
|
+
await invokeStageHooks(
|
|
5324
|
+
"afterEnrichMl",
|
|
5325
|
+
decisionManifest?.hooks?.afterEnrichMl,
|
|
5326
|
+
{
|
|
5327
|
+
ctx: decisionHookCtx,
|
|
5328
|
+
market,
|
|
5329
|
+
decision,
|
|
5330
|
+
entry,
|
|
5331
|
+
ml
|
|
5332
|
+
},
|
|
5333
|
+
{ decision, entry, market }
|
|
5334
|
+
);
|
|
5335
|
+
}
|
|
5336
|
+
let quality;
|
|
5337
|
+
let ai;
|
|
5338
|
+
if (signal) {
|
|
5339
|
+
try {
|
|
5340
|
+
await enrichSignalWithMarketContextStages({
|
|
5341
|
+
signal,
|
|
5342
|
+
env,
|
|
5343
|
+
includeHyperliquidWhales: false
|
|
5344
|
+
});
|
|
5345
|
+
quality = await enrichSignalWithAi({
|
|
5346
|
+
signal,
|
|
5347
|
+
userName,
|
|
5348
|
+
symbol,
|
|
5349
|
+
direction: signal.direction,
|
|
5350
|
+
env,
|
|
5351
|
+
ai: runtime.ai
|
|
5352
|
+
});
|
|
5353
|
+
} catch (error) {
|
|
5354
|
+
await notifyRuntimeError({
|
|
5355
|
+
stage: "enrichSignalWithAi",
|
|
5356
|
+
error,
|
|
5357
|
+
decision,
|
|
5358
|
+
entry,
|
|
5359
|
+
market
|
|
5360
|
+
});
|
|
5361
|
+
throw error;
|
|
5362
|
+
}
|
|
5363
|
+
ai = buildAiHookContext({
|
|
5364
|
+
env,
|
|
5365
|
+
ai: runtime.ai,
|
|
5366
|
+
quality
|
|
5367
|
+
});
|
|
5368
|
+
await invokeStageHooks(
|
|
5369
|
+
"afterEnrichAi",
|
|
5370
|
+
decisionManifest?.hooks?.afterEnrichAi,
|
|
5371
|
+
{
|
|
5372
|
+
ctx: decisionHookCtx,
|
|
5373
|
+
market,
|
|
5374
|
+
decision,
|
|
5375
|
+
entry,
|
|
5376
|
+
ml: ml ?? buildMlHookContext({ signal, env, ml: runtime.ml }),
|
|
5377
|
+
ai
|
|
5378
|
+
},
|
|
5379
|
+
{ decision, entry, market }
|
|
5380
|
+
);
|
|
5381
|
+
}
|
|
5382
|
+
const minAiQuality = runtime.ai?.minQuality ?? 4;
|
|
5383
|
+
const aiEnabled = runtime.ai?.enabled !== false && runtime.ai != null;
|
|
5384
|
+
const policy = buildHookPolicy({
|
|
5385
|
+
quality,
|
|
5386
|
+
makeOrdersEnabled,
|
|
5387
|
+
minAiQuality
|
|
5388
|
+
});
|
|
5389
|
+
const shouldMakeOrder = shouldExecuteEntryDecision({
|
|
5390
|
+
makeOrdersEnabled,
|
|
5391
|
+
env,
|
|
5392
|
+
signal,
|
|
5393
|
+
ml,
|
|
5394
|
+
aiEnabled,
|
|
5395
|
+
quality,
|
|
5396
|
+
minAiQuality
|
|
5397
|
+
});
|
|
5398
|
+
if (!shouldMakeOrder) {
|
|
5399
|
+
if (signal) {
|
|
5400
|
+
signal.orderStatus = "skipped";
|
|
5401
|
+
signal.orderSkipReason = getEntrySkipReason({
|
|
5402
|
+
makeOrdersEnabled,
|
|
5403
|
+
env,
|
|
5404
|
+
ml,
|
|
5405
|
+
aiEnabled,
|
|
5406
|
+
quality,
|
|
5407
|
+
minAiQuality
|
|
5408
|
+
});
|
|
5409
|
+
}
|
|
5410
|
+
return signal ?? decision.code;
|
|
5411
|
+
}
|
|
5412
|
+
const entryGate = await invokeGateHooks(
|
|
5413
|
+
"beforeEntryGate",
|
|
5414
|
+
decisionManifest?.hooks?.beforeEntryGate,
|
|
5415
|
+
{
|
|
5416
|
+
ctx: decisionHookCtx,
|
|
5417
|
+
market,
|
|
5418
|
+
decision,
|
|
5419
|
+
entry,
|
|
5420
|
+
policy,
|
|
5421
|
+
ml,
|
|
5422
|
+
ai
|
|
5423
|
+
},
|
|
5424
|
+
{ decision, entry, market }
|
|
5425
|
+
);
|
|
5426
|
+
if (entryGate?.allow === false) {
|
|
5427
|
+
const skipReason = entryGate.reason ? `HOOK_BEFORE_ENTRY_GATE:${entryGate.reason}` : "HOOK_BEFORE_ENTRY_GATE";
|
|
5428
|
+
if (signal) {
|
|
5429
|
+
signal.orderStatus = "skipped";
|
|
5430
|
+
signal.orderSkipReason = skipReason;
|
|
5431
|
+
}
|
|
5432
|
+
return signal ?? skipReason;
|
|
5433
|
+
}
|
|
5434
|
+
if (backtestEntryDelayBars > 0) {
|
|
5435
|
+
pendingBacktestEntry = {
|
|
5436
|
+
delayBars: backtestEntryDelayBars,
|
|
5437
|
+
delayBarsRemaining: backtestEntryDelayBars,
|
|
5438
|
+
decision,
|
|
5439
|
+
runtime,
|
|
5440
|
+
manifest: decisionManifest,
|
|
5441
|
+
hookCtx: decisionHookCtx,
|
|
5442
|
+
policy,
|
|
5443
|
+
ml,
|
|
5444
|
+
ai
|
|
5445
|
+
};
|
|
5446
|
+
return `BACKTEST_ENTRY_DELAY_QUEUED:${backtestEntryDelayBars}`;
|
|
5447
|
+
}
|
|
5448
|
+
return executeEntryDecision({
|
|
5449
|
+
connector,
|
|
5450
|
+
symbol,
|
|
5451
|
+
decision,
|
|
5452
|
+
runtime,
|
|
5453
|
+
manifest: decisionManifest,
|
|
5454
|
+
hookCtx: decisionHookCtx,
|
|
5455
|
+
market,
|
|
5456
|
+
entry,
|
|
5457
|
+
policy,
|
|
5458
|
+
ml,
|
|
5459
|
+
ai,
|
|
5460
|
+
recordRuntimeJournal,
|
|
5461
|
+
invokeStageHooks,
|
|
5462
|
+
notifyRuntimeError
|
|
5463
|
+
});
|
|
5464
|
+
};
|
|
5465
|
+
const strategy = (async (candle, btcCandle, ethCandle) => runWithDecisionOverride(candle, btcCandle, { ethCandle }));
|
|
5466
|
+
strategy.__tradejsUpdateReferenceData = (params) => indicatorsState.updateReferenceData?.(params);
|
|
5467
|
+
strategy.__tradejsFlushBacktestDelayedEntry = flushPendingBacktestEntry;
|
|
5468
|
+
const resolvedDetectorKey = detectorKey?.(config);
|
|
5469
|
+
if (resolvedDetectorKey && detectorNoSignalSkipReason) {
|
|
5470
|
+
const canFastAdvanceDetectorNoSignal = env === "BACKTEST" && getProjectHookList("onBar").length === 0 && getProjectHookList("afterCoreDecision").length === 0 && getProjectHookList("afterBarDecision").length === 0 && getProjectHookList("onSkip").length === 0 && !strategyManifest?.hooks?.onBar && !strategyManifest?.hooks?.afterCoreDecision && !strategyManifest?.hooks?.afterBarDecision && !strategyManifest?.hooks?.onSkip;
|
|
5471
|
+
strategy.detectorFanoutKey = [strategyName, resolvedDetectorKey].join(
|
|
5472
|
+
":"
|
|
5473
|
+
);
|
|
5474
|
+
strategy.detectorNoSignalSkipReason = detectorNoSignalSkipReason;
|
|
5475
|
+
strategy.canFastAdvanceDetectorNoSignal = canFastAdvanceDetectorNoSignal;
|
|
5476
|
+
if (canFastAdvanceDetectorNoSignal) {
|
|
5477
|
+
strategy.advanceDetectorNoSignal = (candle, btcCandle, code) => {
|
|
5478
|
+
appendCurrentMarketData(candle, btcCandle);
|
|
5479
|
+
indicatorsState.setCurrentBar(
|
|
5480
|
+
candle,
|
|
5481
|
+
btcCandle,
|
|
5482
|
+
resolveEthCandle(candle)
|
|
5483
|
+
);
|
|
5484
|
+
return Promise.resolve(code);
|
|
5485
|
+
};
|
|
5486
|
+
}
|
|
5487
|
+
strategy.skipDetectorNoSignal = (candle, btcCandle, code) => runWithDecisionOverride(candle, btcCandle, {
|
|
5488
|
+
coreDecisionOverride: strategyApi.skip(code)
|
|
5489
|
+
});
|
|
5490
|
+
}
|
|
5491
|
+
return strategy;
|
|
5492
|
+
};
|
|
5493
|
+
if (detectorKey) {
|
|
5494
|
+
creator.detectorKey = detectorKey;
|
|
5495
|
+
}
|
|
5496
|
+
if (detectorNoSignalSkipReason) {
|
|
5497
|
+
creator.detectorNoSignalSkipReason = detectorNoSignalSkipReason;
|
|
5498
|
+
}
|
|
5499
|
+
return creator;
|
|
5500
|
+
};
|
|
5501
|
+
|
|
5502
|
+
// src/strategy/manifests.ts
|
|
5503
|
+
var createStrategyRegistryState = () => ({
|
|
5504
|
+
strategyCreators: /* @__PURE__ */ new Map(),
|
|
5505
|
+
strategyManifestsMap: /* @__PURE__ */ new Map(),
|
|
5506
|
+
pluginsLoadPromise: null
|
|
5507
|
+
});
|
|
5508
|
+
var registryStateByProjectRoot = /* @__PURE__ */ new Map();
|
|
5509
|
+
var getStrategyRegistryState = (cwd = getTradejsProjectCwd()) => {
|
|
5510
|
+
const projectRoot = getTradejsProjectCwd(cwd);
|
|
5511
|
+
let state = registryStateByProjectRoot.get(projectRoot);
|
|
5512
|
+
if (!state) {
|
|
5513
|
+
state = createStrategyRegistryState();
|
|
5514
|
+
registryStateByProjectRoot.set(projectRoot, state);
|
|
5515
|
+
}
|
|
5516
|
+
return {
|
|
5517
|
+
projectRoot,
|
|
5518
|
+
state
|
|
5519
|
+
};
|
|
5520
|
+
};
|
|
5521
|
+
var toUniqueModules = (modules = []) => [
|
|
5522
|
+
...new Set(modules.map((moduleName) => moduleName.trim()).filter(Boolean))
|
|
5523
|
+
];
|
|
5524
|
+
var getConfiguredPluginModuleNames = async (cwd = getTradejsProjectCwd()) => {
|
|
5525
|
+
const config = await loadTradejsConfig(cwd);
|
|
5526
|
+
return {
|
|
5527
|
+
strategyModules: toUniqueModules(config.strategies),
|
|
5528
|
+
indicatorModules: toUniqueModules(config.indicators)
|
|
5529
|
+
};
|
|
5530
|
+
};
|
|
5531
|
+
var extractModuleEntries = (moduleExport, key) => {
|
|
5532
|
+
if (!moduleExport || typeof moduleExport !== "object") {
|
|
5533
|
+
return null;
|
|
5534
|
+
}
|
|
5535
|
+
const candidate = moduleExport;
|
|
5536
|
+
if (Array.isArray(candidate[key])) {
|
|
5537
|
+
return candidate[key];
|
|
5538
|
+
}
|
|
5539
|
+
const defaultExport = candidate.default;
|
|
5540
|
+
if (defaultExport && Array.isArray(defaultExport[key])) {
|
|
5541
|
+
return defaultExport[key];
|
|
5542
|
+
}
|
|
5543
|
+
return null;
|
|
5544
|
+
};
|
|
5545
|
+
var extractStrategyPluginDefinition = (moduleExport) => {
|
|
5546
|
+
const strategyEntries = extractModuleEntries(
|
|
5547
|
+
moduleExport,
|
|
5548
|
+
"strategyEntries"
|
|
5549
|
+
);
|
|
5550
|
+
return strategyEntries ? { strategyEntries } : null;
|
|
5551
|
+
};
|
|
5552
|
+
var extractIndicatorPluginDefinition = (moduleExport) => {
|
|
5553
|
+
const indicatorEntries = extractModuleEntries(
|
|
5554
|
+
moduleExport,
|
|
5555
|
+
"indicatorEntries"
|
|
5556
|
+
);
|
|
5557
|
+
return indicatorEntries ? { indicatorEntries } : null;
|
|
5558
|
+
};
|
|
5559
|
+
var registerEntries = (entries, source, state) => {
|
|
5560
|
+
for (const entry of entries) {
|
|
5561
|
+
const strategyName = entry.manifest?.name;
|
|
5562
|
+
if (!strategyName) {
|
|
5563
|
+
import_logger11.logger.warn("Skip strategy entry without name from %s", source);
|
|
5564
|
+
continue;
|
|
5565
|
+
}
|
|
5566
|
+
if (state.strategyCreators.has(strategyName)) {
|
|
5567
|
+
import_logger11.logger.warn(
|
|
5568
|
+
'Skip duplicate strategy "%s" from %s: already registered',
|
|
5569
|
+
strategyName,
|
|
5570
|
+
source
|
|
5571
|
+
);
|
|
5572
|
+
continue;
|
|
5573
|
+
}
|
|
5574
|
+
state.strategyManifestsMap.set(strategyName, entry.manifest);
|
|
5575
|
+
state.strategyCreators.set(
|
|
5576
|
+
strategyName,
|
|
5577
|
+
createStrategyRuntime({
|
|
5578
|
+
strategyName,
|
|
5579
|
+
defaults: entry.defaults,
|
|
5580
|
+
createCore: entry.createCore,
|
|
5581
|
+
manifest: entry.manifest,
|
|
5582
|
+
detectorKey: entry.detectorKey,
|
|
5583
|
+
detectorNoSignalSkipReason: entry.detectorNoSignalSkipReason,
|
|
5584
|
+
resolveRegisteredManifest: (name) => state.strategyManifestsMap.get(name)
|
|
5585
|
+
})
|
|
5586
|
+
);
|
|
5587
|
+
}
|
|
5588
|
+
};
|
|
5589
|
+
var importStrategyPluginModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
5590
|
+
if (typeof importTradejsModule === "function") {
|
|
5591
|
+
return importTradejsModule(moduleName, cwd);
|
|
5592
|
+
}
|
|
5593
|
+
return import(
|
|
5594
|
+
/* webpackIgnore: true */
|
|
5595
|
+
moduleName
|
|
5596
|
+
);
|
|
5597
|
+
};
|
|
5598
|
+
var ensureStrategyPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
|
|
5599
|
+
const { projectRoot, state } = getStrategyRegistryState(cwd);
|
|
5600
|
+
if (!state.pluginsLoadPromise) {
|
|
5601
|
+
(0, import_indicators2.resetIndicatorRegistryCache)(projectRoot);
|
|
5602
|
+
state.pluginsLoadPromise = (async () => {
|
|
5603
|
+
const { strategyModules, indicatorModules } = await getConfiguredPluginModuleNames(projectRoot);
|
|
5604
|
+
const strategySet = new Set(strategyModules);
|
|
5605
|
+
const indicatorSet = new Set(indicatorModules);
|
|
5606
|
+
const pluginModuleNames = [
|
|
5607
|
+
.../* @__PURE__ */ new Set([...strategyModules, ...indicatorModules])
|
|
5608
|
+
];
|
|
5609
|
+
if (!pluginModuleNames.length) {
|
|
5610
|
+
return;
|
|
5611
|
+
}
|
|
5612
|
+
for (const moduleName of pluginModuleNames) {
|
|
5613
|
+
try {
|
|
5614
|
+
const resolvedModuleName = resolvePluginModuleSpecifier(
|
|
5615
|
+
moduleName,
|
|
5616
|
+
projectRoot
|
|
5617
|
+
);
|
|
5618
|
+
const moduleExport = await importStrategyPluginModule(
|
|
5619
|
+
resolvedModuleName,
|
|
5620
|
+
projectRoot
|
|
5621
|
+
);
|
|
5622
|
+
if (strategySet.has(moduleName)) {
|
|
5623
|
+
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
5624
|
+
if (!pluginDefinition) {
|
|
5625
|
+
import_logger11.logger.warn(
|
|
5626
|
+
'Skip strategy plugin "%s": export { strategyEntries } is missing',
|
|
5627
|
+
moduleName
|
|
5628
|
+
);
|
|
5629
|
+
} else {
|
|
5630
|
+
registerEntries(
|
|
5631
|
+
pluginDefinition.strategyEntries,
|
|
5632
|
+
moduleName,
|
|
5633
|
+
state
|
|
5634
|
+
);
|
|
5635
|
+
}
|
|
5636
|
+
}
|
|
5637
|
+
if (indicatorSet.has(moduleName)) {
|
|
5638
|
+
const indicatorPluginDefinition = extractIndicatorPluginDefinition(moduleExport);
|
|
5639
|
+
if (!indicatorPluginDefinition) {
|
|
5640
|
+
import_logger11.logger.warn(
|
|
5641
|
+
'Skip indicator plugin "%s": export { indicatorEntries } is missing',
|
|
5642
|
+
moduleName
|
|
5643
|
+
);
|
|
5644
|
+
} else {
|
|
5645
|
+
(0, import_indicators2.registerIndicatorEntries)(
|
|
5646
|
+
indicatorPluginDefinition.indicatorEntries,
|
|
5647
|
+
moduleName,
|
|
5648
|
+
projectRoot
|
|
5649
|
+
);
|
|
5650
|
+
}
|
|
5651
|
+
}
|
|
5652
|
+
if (!strategySet.has(moduleName) && !indicatorSet.has(moduleName)) {
|
|
5653
|
+
import_logger11.logger.warn(
|
|
5654
|
+
'Skip plugin "%s": no strategy/indicator sections requested in config',
|
|
5655
|
+
moduleName
|
|
5656
|
+
);
|
|
5657
|
+
}
|
|
5658
|
+
} catch (error) {
|
|
5659
|
+
import_logger11.logger.warn(
|
|
5660
|
+
'Failed to load plugin "%s": %s',
|
|
5661
|
+
moduleName,
|
|
5662
|
+
String(error)
|
|
5663
|
+
);
|
|
5664
|
+
}
|
|
5665
|
+
}
|
|
5666
|
+
})();
|
|
5667
|
+
}
|
|
5668
|
+
await state.pluginsLoadPromise;
|
|
5669
|
+
};
|
|
5670
|
+
var getStrategyManifest = (name, cwd = getTradejsProjectCwd()) => {
|
|
5671
|
+
if (!name) {
|
|
5672
|
+
return void 0;
|
|
5673
|
+
}
|
|
5674
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
5675
|
+
return state.strategyManifestsMap.get(name);
|
|
5676
|
+
};
|
|
5677
|
+
var strategies = new Proxy(
|
|
5678
|
+
{},
|
|
5679
|
+
{
|
|
5680
|
+
get: (_target, property) => {
|
|
5681
|
+
if (typeof property !== "string") {
|
|
5682
|
+
return void 0;
|
|
5683
|
+
}
|
|
5684
|
+
return getStrategyRegistryState().state.strategyCreators.get(property);
|
|
5685
|
+
},
|
|
5686
|
+
ownKeys: () => {
|
|
5687
|
+
return [...getStrategyRegistryState().state.strategyCreators.keys()];
|
|
5688
|
+
},
|
|
5689
|
+
getOwnPropertyDescriptor: () => ({
|
|
5690
|
+
enumerable: true,
|
|
5691
|
+
configurable: true
|
|
5692
|
+
})
|
|
5693
|
+
}
|
|
5694
|
+
);
|
|
1100
5695
|
|
|
1101
5696
|
// src/strategyAdapters/ai.ts
|
|
1102
5697
|
var toRecord2 = (value) => {
|
|
@@ -1614,7 +6209,7 @@ var askAI = async (signal, options = {}) => {
|
|
|
1614
6209
|
payload
|
|
1615
6210
|
}
|
|
1616
6211
|
);
|
|
1617
|
-
await (0,
|
|
6212
|
+
await (0, import_redis2.setData)(import_redis2.redisKeys.analysis(symbol, signal.signalId), content);
|
|
1618
6213
|
return content;
|
|
1619
6214
|
};
|
|
1620
6215
|
// Annotate the CommonJS export names for ESM import in node:
|