@tradejs/node 3.1.29-beta.290 → 3.1.29-beta.291
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 +109 -48
- package/dist/ai.mjs +1 -1
- package/dist/backtest.js +2491 -1871
- package/dist/backtest.mjs +3 -2
- package/dist/{chunk-PB2KHBGH.mjs → chunk-HRON2EAI.mjs} +413 -22
- package/dist/{chunk-UL5Z46JL.mjs → chunk-PYJWXAYR.mjs} +97 -30
- package/dist/chunk-UUNEWAT4.mjs +193 -0
- package/dist/{chunk-BO42QWAB.mjs → chunk-W6PPAADK.mjs} +1 -1
- package/dist/cli.js +834 -773
- package/dist/cli.mjs +1 -1
- package/dist/diagnostics.js +2511 -1891
- package/dist/diagnostics.mjs +4 -3
- package/dist/mcp.d.mts +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +2537 -1917
- package/dist/mcp.mjs +4 -3
- package/dist/portfolio.d.mts +28 -0
- package/dist/portfolio.d.ts +28 -0
- package/dist/portfolio.js +210 -0
- package/dist/portfolio.mjs +9 -0
- package/dist/registry.js +2429 -1809
- package/dist/registry.mjs +3 -2
- package/dist/runtimeDashboard.js +2236 -1614
- package/dist/runtimeDashboard.mjs +4 -3
- package/dist/runtimeStrategies.js +2052 -1432
- package/dist/runtimeStrategies.mjs +4 -3
- package/dist/strategies.d.mts +3 -1
- package/dist/strategies.d.ts +3 -1
- package/dist/strategies.js +2085 -1465
- package/dist/strategies.mjs +3 -2
- package/package.json +10 -5
package/dist/strategies.js
CHANGED
|
@@ -843,869 +843,881 @@ __reExport(strategies_exports, require("@tradejs/core/strategies"), module.expor
|
|
|
843
843
|
var import_aiLanguages2 = require("@tradejs/core/aiLanguages");
|
|
844
844
|
var import_redis = require("@tradejs/infra/redis");
|
|
845
845
|
|
|
846
|
-
// src/
|
|
847
|
-
var
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
);
|
|
851
|
-
var
|
|
852
|
-
var
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
846
|
+
// src/strategyAdapters/ai.ts
|
|
847
|
+
var import_indicators2 = require("@tradejs/core/indicators");
|
|
848
|
+
|
|
849
|
+
// src/tradejsConfig.ts
|
|
850
|
+
var import_fs = __toESM(require("fs"));
|
|
851
|
+
var import_path = __toESM(require("path"));
|
|
852
|
+
var import_url = require("url");
|
|
853
|
+
var import_config = require("@tradejs/core/config");
|
|
854
|
+
var import_logger = require("@tradejs/infra/logger");
|
|
855
|
+
var CONFIG_FILE_NAMES = [
|
|
856
|
+
"tradejs.config.ts",
|
|
857
|
+
"tradejs.config.mts",
|
|
858
|
+
"tradejs.config.js",
|
|
859
|
+
"tradejs.config.mjs",
|
|
860
|
+
"tradejs.config.cjs"
|
|
861
|
+
];
|
|
862
|
+
var TS_MODULE_RE = /\.(cts|mts|ts)$/i;
|
|
863
|
+
var cachedByCwd = /* @__PURE__ */ new Map();
|
|
864
|
+
var announcedConfigFile = /* @__PURE__ */ new Set();
|
|
865
|
+
var tsNodeRegistered = false;
|
|
866
|
+
var tsconfigPathsRegisteredByCwd = /* @__PURE__ */ new Set();
|
|
867
|
+
var tsconfigPathMatchersByCwd = /* @__PURE__ */ new Map();
|
|
868
|
+
var getTradejsProjectCwd = (cwd) => {
|
|
869
|
+
const explicit = String(cwd ?? "").trim();
|
|
870
|
+
if (explicit) {
|
|
871
|
+
return import_path.default.resolve(explicit);
|
|
862
872
|
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
key,
|
|
867
|
-
trimSeriesDeep(nested)
|
|
868
|
-
])
|
|
869
|
-
);
|
|
873
|
+
const fromEnv = String(process.env.PROJECT_CWD || "").trim();
|
|
874
|
+
if (fromEnv) {
|
|
875
|
+
return import_path.default.resolve(fromEnv);
|
|
870
876
|
}
|
|
871
|
-
return
|
|
877
|
+
return process.cwd();
|
|
872
878
|
};
|
|
873
|
-
var
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
return compactSnapshot({ limit: MAX_AI_SERIES_POINTS });
|
|
879
|
+
var normalizeConfig = (rawConfig) => {
|
|
880
|
+
if (!rawConfig || typeof rawConfig !== "object") {
|
|
881
|
+
return {};
|
|
877
882
|
}
|
|
878
|
-
|
|
883
|
+
const config = rawConfig;
|
|
884
|
+
const strategies2 = Array.isArray(config.strategies) ? config.strategies.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
885
|
+
const indicators = Array.isArray(config.indicators) ? config.indicators.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
886
|
+
const connectors = Array.isArray(config.connectors) ? config.connectors.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
887
|
+
const hooks = (0, import_config.normalizeTradejsConfigHooks)(
|
|
888
|
+
config.hooks
|
|
889
|
+
);
|
|
890
|
+
const runtime = config.runtime && typeof config.runtime === "object" && !Array.isArray(config.runtime) ? config.runtime : void 0;
|
|
891
|
+
return {
|
|
892
|
+
strategies: strategies2,
|
|
893
|
+
indicators,
|
|
894
|
+
connectors,
|
|
895
|
+
...hooks ? { hooks } : {},
|
|
896
|
+
...runtime ? { runtime } : {}
|
|
897
|
+
};
|
|
879
898
|
};
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
return null;
|
|
899
|
+
var getNodeCreateRequire = () => {
|
|
900
|
+
const builtinModule = process.getBuiltinModule?.("module");
|
|
901
|
+
if (typeof builtinModule?.createRequire === "function") {
|
|
902
|
+
return builtinModule.createRequire;
|
|
885
903
|
}
|
|
886
|
-
|
|
904
|
+
throw new TypeError("module.createRequire is not available");
|
|
887
905
|
};
|
|
888
|
-
var
|
|
889
|
-
|
|
890
|
-
|
|
906
|
+
var getRequireFn = (cwd = getTradejsProjectCwd()) => getNodeCreateRequire()(import_path.default.join(import_path.default.resolve(cwd), "__tradejs_loader__.js"));
|
|
907
|
+
var ensureTsNodeRegistered = async () => {
|
|
908
|
+
if (tsNodeRegistered) {
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
const tsNodeModule = await import("ts-node");
|
|
912
|
+
const tsNode = tsNodeModule.default ?? tsNodeModule;
|
|
913
|
+
tsNode.register?.({
|
|
914
|
+
transpileOnly: true,
|
|
915
|
+
compilerOptions: {
|
|
916
|
+
module: "Node16",
|
|
917
|
+
moduleResolution: "node16"
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
tsNodeRegistered = true;
|
|
891
921
|
};
|
|
892
|
-
var
|
|
893
|
-
const
|
|
894
|
-
|
|
922
|
+
var ensureTsconfigPathsRegistered = async (cwd = getTradejsProjectCwd()) => {
|
|
923
|
+
const projectRoot = getTradejsProjectCwd(cwd);
|
|
924
|
+
if (tsconfigPathsRegisteredByCwd.has(projectRoot)) {
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
const tsconfigPathsModule = await import("tsconfig-paths");
|
|
928
|
+
const loadConfig = tsconfigPathsModule.loadConfig;
|
|
929
|
+
const register = tsconfigPathsModule.register;
|
|
930
|
+
if (typeof loadConfig !== "function" || typeof register !== "function") {
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
const loadedConfig = loadConfig(projectRoot);
|
|
934
|
+
if (loadedConfig.resultType !== "success") {
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
register({
|
|
938
|
+
baseUrl: loadedConfig.absoluteBaseUrl,
|
|
939
|
+
paths: loadedConfig.paths,
|
|
940
|
+
addMatchAll: false
|
|
941
|
+
});
|
|
942
|
+
tsconfigPathsRegisteredByCwd.add(projectRoot);
|
|
895
943
|
};
|
|
896
|
-
var
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
bps: null,
|
|
903
|
-
absBps: null,
|
|
904
|
-
bias: null,
|
|
905
|
-
severity: null
|
|
906
|
-
});
|
|
907
|
-
var buildSpreadContextFromSignal = (signal) => {
|
|
908
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
909
|
-
const relative = toRecord(baseContext?.relative);
|
|
910
|
-
const execution = toRecord(relative?.execution);
|
|
911
|
-
const spread = toFiniteNumber(execution?.venueSpread);
|
|
912
|
-
const zScore = toFiniteNumber(execution?.venueSpreadZScore);
|
|
913
|
-
if (spread == null) {
|
|
914
|
-
return buildMissingSpreadContext();
|
|
944
|
+
var resolveTsconfigPathModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
945
|
+
const projectRoot = getTradejsProjectCwd(cwd);
|
|
946
|
+
const cachedMatcher = tsconfigPathMatchersByCwd.get(projectRoot);
|
|
947
|
+
if (cachedMatcher) {
|
|
948
|
+
const resolved2 = cachedMatcher(moduleName);
|
|
949
|
+
return resolved2 || null;
|
|
915
950
|
}
|
|
916
|
-
const
|
|
917
|
-
const
|
|
918
|
-
const
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
951
|
+
const tsconfigPathsModule = await import("tsconfig-paths");
|
|
952
|
+
const loadConfig = tsconfigPathsModule.loadConfig;
|
|
953
|
+
const createMatchPath = tsconfigPathsModule.createMatchPath;
|
|
954
|
+
if (typeof loadConfig !== "function" || typeof createMatchPath !== "function") {
|
|
955
|
+
return null;
|
|
956
|
+
}
|
|
957
|
+
const loadedConfig = loadConfig(projectRoot);
|
|
958
|
+
if (loadedConfig.resultType !== "success") {
|
|
959
|
+
return null;
|
|
960
|
+
}
|
|
961
|
+
const matchPath = createMatchPath(
|
|
962
|
+
loadedConfig.absoluteBaseUrl,
|
|
963
|
+
loadedConfig.paths
|
|
964
|
+
);
|
|
965
|
+
const matcher = (requestedModule) => matchPath(requestedModule, void 0, import_fs.default.existsSync, [
|
|
966
|
+
".ts",
|
|
967
|
+
".tsx",
|
|
968
|
+
".mts",
|
|
969
|
+
".cts",
|
|
970
|
+
".js",
|
|
971
|
+
".jsx",
|
|
972
|
+
".mjs",
|
|
973
|
+
".cjs",
|
|
974
|
+
".json"
|
|
975
|
+
]) || "";
|
|
976
|
+
tsconfigPathMatchersByCwd.set(projectRoot, matcher);
|
|
977
|
+
const resolved = matcher(moduleName);
|
|
978
|
+
return resolved || null;
|
|
932
979
|
};
|
|
933
|
-
var
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
const delta = toRecord(participation?.delta);
|
|
937
|
-
const source = String(delta?.source ?? "");
|
|
938
|
-
const isTrueDeltaSource = source === "kline_taker_volume" || source === "agg_trades" || source === "trades";
|
|
939
|
-
if (!delta || !isTrueDeltaSource) {
|
|
940
|
-
return {
|
|
941
|
-
source: source || null,
|
|
942
|
-
available: false,
|
|
943
|
-
buyPressurePct: null,
|
|
944
|
-
buyVolume: null,
|
|
945
|
-
sellVolume: null,
|
|
946
|
-
netDelta: null,
|
|
947
|
-
deltaPct: null,
|
|
948
|
-
signedVolumeZScore: null
|
|
949
|
-
};
|
|
980
|
+
var toImportSpecifier = (moduleName) => {
|
|
981
|
+
if (moduleName.startsWith("file://")) {
|
|
982
|
+
return moduleName;
|
|
950
983
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
buyVolume: toFiniteNumber(delta.buyVolume),
|
|
956
|
-
sellVolume: toFiniteNumber(delta.sellVolume),
|
|
957
|
-
netDelta: toFiniteNumber(delta.netDelta),
|
|
958
|
-
deltaPct: toFiniteNumber(delta.deltaPct),
|
|
959
|
-
signedVolumeZScore: toFiniteNumber(delta.signedVolumeZScore)
|
|
960
|
-
};
|
|
984
|
+
if (import_path.default.isAbsolute(moduleName)) {
|
|
985
|
+
return (0, import_url.pathToFileURL)(moduleName).href;
|
|
986
|
+
}
|
|
987
|
+
return moduleName;
|
|
961
988
|
};
|
|
962
|
-
var
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
const
|
|
966
|
-
if (!
|
|
967
|
-
return
|
|
968
|
-
source: null,
|
|
969
|
-
available: false,
|
|
970
|
-
interval: null,
|
|
971
|
-
stale: null,
|
|
972
|
-
trades: null,
|
|
973
|
-
buyPressurePct: null,
|
|
974
|
-
netBaseDelta: null,
|
|
975
|
-
netQuoteDelta: null
|
|
976
|
-
};
|
|
989
|
+
var isTsModulePath = (moduleName) => TS_MODULE_RE.test(moduleName.split("?")[0]);
|
|
990
|
+
var isRelativeModulePath = (moduleName) => moduleName.startsWith("./") || moduleName.startsWith("../");
|
|
991
|
+
var isBareModuleSpecifier = (moduleName) => {
|
|
992
|
+
const normalized = String(moduleName ?? "").trim();
|
|
993
|
+
if (!normalized) {
|
|
994
|
+
return false;
|
|
977
995
|
}
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
stale: typeof tradeFlow.stale === "boolean" ? tradeFlow.stale : null,
|
|
983
|
-
trades: toFiniteNumber(tradeFlow.trades),
|
|
984
|
-
buyPressurePct: toFiniteNumber(tradeFlow.buyPressurePct),
|
|
985
|
-
netBaseDelta: toFiniteNumber(tradeFlow.netBaseDelta),
|
|
986
|
-
netQuoteDelta: toFiniteNumber(tradeFlow.netQuoteDelta)
|
|
987
|
-
};
|
|
996
|
+
if (normalized.startsWith("file://") || import_path.default.isAbsolute(normalized) || isRelativeModulePath(normalized)) {
|
|
997
|
+
return false;
|
|
998
|
+
}
|
|
999
|
+
return true;
|
|
988
1000
|
};
|
|
989
|
-
var
|
|
990
|
-
const
|
|
991
|
-
const
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
universe: null,
|
|
998
|
-
interval: null,
|
|
999
|
-
stale: null,
|
|
1000
|
-
symbolsCount: null,
|
|
1001
|
-
advanceDeclineRatio: null,
|
|
1002
|
-
pctAboveMa20: null,
|
|
1003
|
-
pctAboveMa50: null,
|
|
1004
|
-
equalWeightedReturn: null,
|
|
1005
|
-
volumeWeightedReturn: null,
|
|
1006
|
-
dispersion: null
|
|
1007
|
-
};
|
|
1001
|
+
var importConfigFile = async (configFilePath) => {
|
|
1002
|
+
const ext = import_path.default.extname(configFilePath).toLowerCase();
|
|
1003
|
+
const configFileUrl = `${toImportSpecifier(configFilePath)}?t=${Date.now()}`;
|
|
1004
|
+
if (ext === ".ts" || ext === ".mts") {
|
|
1005
|
+
const requireFn = getRequireFn(import_path.default.dirname(configFilePath));
|
|
1006
|
+
await ensureTsNodeRegistered();
|
|
1007
|
+
await ensureTsconfigPathsRegistered(import_path.default.dirname(configFilePath));
|
|
1008
|
+
return requireFn(configFilePath);
|
|
1008
1009
|
}
|
|
1009
|
-
return
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
interval: String(breadth.interval ?? ""),
|
|
1014
|
-
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
1015
|
-
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
1016
|
-
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
1017
|
-
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
1018
|
-
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
1019
|
-
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
1020
|
-
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
1021
|
-
dispersion: toFiniteNumber(breadth.dispersion)
|
|
1022
|
-
};
|
|
1010
|
+
return import(
|
|
1011
|
+
/* webpackIgnore: true */
|
|
1012
|
+
configFileUrl
|
|
1013
|
+
);
|
|
1023
1014
|
};
|
|
1024
|
-
var
|
|
1025
|
-
const
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
interval: null,
|
|
1036
|
-
stale: null,
|
|
1037
|
-
symbolsCount: null,
|
|
1038
|
-
advanceDeclineRatio: null,
|
|
1039
|
-
pctAboveMa20: null,
|
|
1040
|
-
pctAboveMa50: null,
|
|
1041
|
-
equalWeightedReturn: null,
|
|
1042
|
-
volumeWeightedReturn: null,
|
|
1043
|
-
dispersion: null
|
|
1044
|
-
};
|
|
1015
|
+
var importTradejsModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
1016
|
+
const normalized = String(moduleName ?? "").trim();
|
|
1017
|
+
if (!normalized) {
|
|
1018
|
+
return {};
|
|
1019
|
+
}
|
|
1020
|
+
let modulePath = normalized;
|
|
1021
|
+
if (normalized.startsWith("file://")) {
|
|
1022
|
+
try {
|
|
1023
|
+
modulePath = (0, import_url.fileURLToPath)(normalized);
|
|
1024
|
+
} catch {
|
|
1025
|
+
modulePath = normalized;
|
|
1045
1026
|
}
|
|
1046
|
-
return {
|
|
1047
|
-
source: String(breadth.source ?? ""),
|
|
1048
|
-
available: true,
|
|
1049
|
-
universe: String(breadth.universe ?? ""),
|
|
1050
|
-
interval: String(breadth.interval ?? ""),
|
|
1051
|
-
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
1052
|
-
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
1053
|
-
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
1054
|
-
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
1055
|
-
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
1056
|
-
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
1057
|
-
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
1058
|
-
dispersion: toFiniteNumber(breadth.dispersion)
|
|
1059
|
-
};
|
|
1060
|
-
};
|
|
1061
|
-
return {
|
|
1062
|
-
top5: build("top5"),
|
|
1063
|
-
top10: build("top10"),
|
|
1064
|
-
top30: build("top30"),
|
|
1065
|
-
top50: build("top50"),
|
|
1066
|
-
top100: build("top100")
|
|
1067
|
-
};
|
|
1068
|
-
};
|
|
1069
|
-
var buildTargetVsBtcContextFromSignal = (signal) => {
|
|
1070
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1071
|
-
const relative = toRecord(baseContext?.relative);
|
|
1072
|
-
const targetVsBtc = toRecord(relative?.targetVsBtc);
|
|
1073
|
-
if (!targetVsBtc) {
|
|
1074
|
-
return {
|
|
1075
|
-
source: null,
|
|
1076
|
-
available: false,
|
|
1077
|
-
ratioReturn1h: null,
|
|
1078
|
-
ratioReturn4h: null,
|
|
1079
|
-
ratioReturn24h: null,
|
|
1080
|
-
alphaVsBtc1h: null,
|
|
1081
|
-
alphaVsBtc4h: null,
|
|
1082
|
-
alphaVsBtc24h: null,
|
|
1083
|
-
betaToBtc20: null,
|
|
1084
|
-
correlationToBtc20: null,
|
|
1085
|
-
ratioTrend: null
|
|
1086
|
-
};
|
|
1087
1027
|
}
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1028
|
+
const requireFn = getRequireFn(
|
|
1029
|
+
import_path.default.isAbsolute(modulePath) ? import_path.default.dirname(modulePath) : cwd
|
|
1030
|
+
);
|
|
1031
|
+
if (isTsModulePath(modulePath)) {
|
|
1032
|
+
await ensureTsNodeRegistered();
|
|
1033
|
+
await ensureTsconfigPathsRegistered(cwd);
|
|
1034
|
+
return requireFn(modulePath);
|
|
1035
|
+
}
|
|
1036
|
+
if (isBareModuleSpecifier(normalized)) {
|
|
1037
|
+
await ensureTsconfigPathsRegistered(cwd);
|
|
1038
|
+
try {
|
|
1039
|
+
return requireFn(normalized);
|
|
1040
|
+
} catch (error) {
|
|
1041
|
+
const resolvedByTsconfig = await resolveTsconfigPathModule(
|
|
1042
|
+
normalized,
|
|
1043
|
+
cwd
|
|
1044
|
+
);
|
|
1045
|
+
if (resolvedByTsconfig && resolvedByTsconfig !== normalized) {
|
|
1046
|
+
return requireFn(resolvedByTsconfig);
|
|
1047
|
+
}
|
|
1048
|
+
throw error;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
try {
|
|
1052
|
+
return await import(
|
|
1053
|
+
/* webpackIgnore: true */
|
|
1054
|
+
toImportSpecifier(normalized)
|
|
1055
|
+
);
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
if (isTsModulePath(modulePath)) {
|
|
1058
|
+
await ensureTsNodeRegistered();
|
|
1059
|
+
await ensureTsconfigPathsRegistered(cwd);
|
|
1060
|
+
return requireFn(modulePath);
|
|
1061
|
+
}
|
|
1062
|
+
throw error;
|
|
1063
|
+
}
|
|
1101
1064
|
};
|
|
1102
|
-
var
|
|
1103
|
-
const
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
altDispersion24h: null
|
|
1121
|
-
};
|
|
1065
|
+
var resolveExportedConfig = (moduleExports) => {
|
|
1066
|
+
const candidate = moduleExports && typeof moduleExports === "object" && "default" in moduleExports ? moduleExports.default : moduleExports;
|
|
1067
|
+
return normalizeConfig(candidate);
|
|
1068
|
+
};
|
|
1069
|
+
var findConfigFilePath = (cwd) => {
|
|
1070
|
+
let currentDir = import_path.default.resolve(cwd);
|
|
1071
|
+
while (true) {
|
|
1072
|
+
for (const fileName of CONFIG_FILE_NAMES) {
|
|
1073
|
+
const fullPath = import_path.default.join(currentDir, fileName);
|
|
1074
|
+
if (import_fs.default.existsSync(fullPath) && import_fs.default.statSync(fullPath).isFile()) {
|
|
1075
|
+
return fullPath;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
const parentDir = import_path.default.dirname(currentDir);
|
|
1079
|
+
if (parentDir === currentDir) {
|
|
1080
|
+
return null;
|
|
1081
|
+
}
|
|
1082
|
+
currentDir = parentDir;
|
|
1122
1083
|
}
|
|
1123
|
-
return {
|
|
1124
|
-
source: String(btcAltRegime.source ?? ""),
|
|
1125
|
-
available: true,
|
|
1126
|
-
universe: String(btcAltRegime.universe ?? ""),
|
|
1127
|
-
interval: String(btcAltRegime.interval ?? ""),
|
|
1128
|
-
stale: typeof btcAltRegime.stale === "boolean" ? btcAltRegime.stale : null,
|
|
1129
|
-
regime: typeof btcAltRegime.regime === "string" ? btcAltRegime.regime : null,
|
|
1130
|
-
btcReturn24h: toFiniteNumber(btcAltRegime.btcReturn24h),
|
|
1131
|
-
altBasketReturn24h: toFiniteNumber(btcAltRegime.altBasketReturn24h),
|
|
1132
|
-
btcVsAltReturn24h: toFiniteNumber(btcAltRegime.btcVsAltReturn24h),
|
|
1133
|
-
btcTurnoverShare24h: toFiniteNumber(btcAltRegime.btcTurnoverShare24h),
|
|
1134
|
-
btcTurnoverShareChange24h: toFiniteNumber(
|
|
1135
|
-
btcAltRegime.btcTurnoverShareChange24h
|
|
1136
|
-
),
|
|
1137
|
-
altVolToBtcVol24h: toFiniteNumber(btcAltRegime.altVolToBtcVol24h),
|
|
1138
|
-
altDispersion24h: toFiniteNumber(btcAltRegime.altDispersion24h)
|
|
1139
|
-
};
|
|
1140
1084
|
};
|
|
1141
|
-
var
|
|
1142
|
-
const
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
if (!cmcGlobal) {
|
|
1146
|
-
return {
|
|
1147
|
-
source: null,
|
|
1148
|
-
available: false,
|
|
1149
|
-
interval: null,
|
|
1150
|
-
asOfTs: null,
|
|
1151
|
-
stale: null,
|
|
1152
|
-
totalMarketCapUsd: null,
|
|
1153
|
-
totalVolumeUsd: null,
|
|
1154
|
-
totalVolumeReportedUsd: null,
|
|
1155
|
-
altMarketCapUsd: null,
|
|
1156
|
-
altVolumeUsd: null,
|
|
1157
|
-
altVolumeReportedUsd: null,
|
|
1158
|
-
btcDominancePct: null,
|
|
1159
|
-
ethDominancePct: null,
|
|
1160
|
-
btcDominanceChange24hPct: null,
|
|
1161
|
-
ethDominanceChange24hPct: null,
|
|
1162
|
-
altMarketCapChange24hPct: null,
|
|
1163
|
-
altVolumeChange24hPct: null,
|
|
1164
|
-
activeCryptocurrencies: null,
|
|
1165
|
-
activeExchanges: null,
|
|
1166
|
-
activeMarketPairs: null,
|
|
1167
|
-
altLiquidityRegime: null
|
|
1168
|
-
};
|
|
1085
|
+
var resolvePluginModuleSpecifier = (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
1086
|
+
const normalized = String(moduleName ?? "").trim();
|
|
1087
|
+
if (!normalized) {
|
|
1088
|
+
return "";
|
|
1169
1089
|
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
btcDominanceChange24hPct: toFiniteNumber(
|
|
1185
|
-
cmcGlobal.btcDominanceChange24hPct
|
|
1186
|
-
),
|
|
1187
|
-
ethDominanceChange24hPct: toFiniteNumber(
|
|
1188
|
-
cmcGlobal.ethDominanceChange24hPct
|
|
1189
|
-
),
|
|
1190
|
-
altMarketCapChange24hPct: toFiniteNumber(
|
|
1191
|
-
cmcGlobal.altMarketCapChange24hPct
|
|
1192
|
-
),
|
|
1193
|
-
altVolumeChange24hPct: toFiniteNumber(cmcGlobal.altVolumeChange24hPct),
|
|
1194
|
-
activeCryptocurrencies: toFiniteNumber(cmcGlobal.activeCryptocurrencies),
|
|
1195
|
-
activeExchanges: toFiniteNumber(cmcGlobal.activeExchanges),
|
|
1196
|
-
activeMarketPairs: toFiniteNumber(cmcGlobal.activeMarketPairs),
|
|
1197
|
-
altLiquidityRegime: typeof cmcGlobal.altLiquidityRegime === "string" ? cmcGlobal.altLiquidityRegime : null
|
|
1198
|
-
};
|
|
1090
|
+
if (normalized.startsWith("file://")) {
|
|
1091
|
+
try {
|
|
1092
|
+
return (0, import_url.fileURLToPath)(normalized);
|
|
1093
|
+
} catch {
|
|
1094
|
+
return normalized;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
if (import_path.default.isAbsolute(normalized)) {
|
|
1098
|
+
return normalized;
|
|
1099
|
+
}
|
|
1100
|
+
if (isRelativeModulePath(normalized)) {
|
|
1101
|
+
return import_path.default.resolve(cwd, normalized);
|
|
1102
|
+
}
|
|
1103
|
+
return normalized;
|
|
1199
1104
|
};
|
|
1200
|
-
var
|
|
1105
|
+
var loadTradejsConfig = async (cwd = getTradejsProjectCwd()) => {
|
|
1106
|
+
const cached = cachedByCwd.get(cwd);
|
|
1107
|
+
if (cached) {
|
|
1108
|
+
return cached;
|
|
1109
|
+
}
|
|
1110
|
+
const configFilePath = findConfigFilePath(cwd);
|
|
1111
|
+
if (!configFilePath) {
|
|
1112
|
+
cachedByCwd.set(cwd, {});
|
|
1113
|
+
return {};
|
|
1114
|
+
}
|
|
1115
|
+
try {
|
|
1116
|
+
const moduleExports = await importConfigFile(configFilePath);
|
|
1117
|
+
const config = resolveExportedConfig(moduleExports);
|
|
1118
|
+
cachedByCwd.set(cwd, config);
|
|
1119
|
+
if (!announcedConfigFile.has(configFilePath)) {
|
|
1120
|
+
announcedConfigFile.add(configFilePath);
|
|
1121
|
+
import_logger.logger.log("debug", "Loaded TradeJS config: %s", configFilePath);
|
|
1122
|
+
}
|
|
1123
|
+
return config;
|
|
1124
|
+
} catch (error) {
|
|
1125
|
+
import_logger.logger.log(
|
|
1126
|
+
"warn",
|
|
1127
|
+
"Failed to load TradeJS config from %s: %s",
|
|
1128
|
+
configFilePath,
|
|
1129
|
+
String(error)
|
|
1130
|
+
);
|
|
1131
|
+
cachedByCwd.set(cwd, {});
|
|
1132
|
+
return {};
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
|
|
1136
|
+
// src/strategyAdapters/ai.ts
|
|
1137
|
+
var import_aiFeatures = require("@tradejs/core/aiFeatures");
|
|
1138
|
+
|
|
1139
|
+
// src/aiShared.ts
|
|
1140
|
+
var MAX_AI_SERIES_POINTS = 5;
|
|
1141
|
+
var COMPACT_INDICATORS_SNAPSHOT_SYMBOL = /* @__PURE__ */ Symbol.for(
|
|
1142
|
+
"tradejs.indicators.compactSnapshot"
|
|
1143
|
+
);
|
|
1144
|
+
var COMPACT_INDICATORS_SNAPSHOT_KEY = "__tradejsCompactIndicatorsSnapshot";
|
|
1145
|
+
var trimSeriesDeep = (value) => {
|
|
1146
|
+
if (Array.isArray(value)) {
|
|
1147
|
+
const trimmed = value.slice(-MAX_AI_SERIES_POINTS);
|
|
1148
|
+
const isMatrix = trimmed.every((item) => Array.isArray(item));
|
|
1149
|
+
if (isMatrix) {
|
|
1150
|
+
return trimmed;
|
|
1151
|
+
}
|
|
1152
|
+
return trimmed.map(
|
|
1153
|
+
(item) => item && typeof item === "object" ? trimSeriesDeep(item) : item
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1156
|
+
if (value && typeof value === "object") {
|
|
1157
|
+
return Object.fromEntries(
|
|
1158
|
+
Object.entries(value).map(([key, nested]) => [
|
|
1159
|
+
key,
|
|
1160
|
+
trimSeriesDeep(nested)
|
|
1161
|
+
])
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
return value;
|
|
1165
|
+
};
|
|
1166
|
+
var buildCompactAiIndicatorsSnapshot = (value) => {
|
|
1167
|
+
const compactSnapshot = value && typeof value === "object" ? value[COMPACT_INDICATORS_SNAPSHOT_SYMBOL] ?? value[COMPACT_INDICATORS_SNAPSHOT_KEY] : void 0;
|
|
1168
|
+
if (typeof compactSnapshot === "function") {
|
|
1169
|
+
return compactSnapshot({ limit: MAX_AI_SERIES_POINTS });
|
|
1170
|
+
}
|
|
1171
|
+
return trimSeriesDeep(value);
|
|
1172
|
+
};
|
|
1173
|
+
|
|
1174
|
+
// src/aiMarketContext.ts
|
|
1175
|
+
var toRecord = (value) => {
|
|
1176
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1177
|
+
return null;
|
|
1178
|
+
}
|
|
1179
|
+
return value;
|
|
1180
|
+
};
|
|
1181
|
+
var toFiniteNumber = (value) => {
|
|
1182
|
+
const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
|
|
1183
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
1184
|
+
};
|
|
1185
|
+
var roundTo = (value, decimals) => {
|
|
1186
|
+
const factor = 10 ** decimals;
|
|
1187
|
+
return Math.round(value * factor) / factor;
|
|
1188
|
+
};
|
|
1189
|
+
var buildMissingSpreadContext = () => ({
|
|
1190
|
+
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1191
|
+
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1192
|
+
available: false,
|
|
1193
|
+
value: null,
|
|
1194
|
+
zScore: null,
|
|
1195
|
+
bps: null,
|
|
1196
|
+
absBps: null,
|
|
1197
|
+
bias: null,
|
|
1198
|
+
severity: null
|
|
1199
|
+
});
|
|
1200
|
+
var buildSpreadContextFromSignal = (signal) => {
|
|
1201
1201
|
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1202
1202
|
const relative = toRecord(baseContext?.relative);
|
|
1203
|
-
const
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
interval: null,
|
|
1209
|
-
asOfTs: null,
|
|
1210
|
-
stale: null,
|
|
1211
|
-
btcMarketCapUsd: null,
|
|
1212
|
-
ethMarketCapUsd: null,
|
|
1213
|
-
btcVolumeUsd: null,
|
|
1214
|
-
ethVolumeUsd: null,
|
|
1215
|
-
btcVolumeToMarketCap: null,
|
|
1216
|
-
ethVolumeToMarketCap: null,
|
|
1217
|
-
ethBtcMarketCapRatio: null,
|
|
1218
|
-
ethBtcMarketCapRatioChange24hPct: null,
|
|
1219
|
-
ethVsBtcVolumeRatio: null,
|
|
1220
|
-
referenceLiquidityRegime: null
|
|
1221
|
-
};
|
|
1203
|
+
const execution = toRecord(relative?.execution);
|
|
1204
|
+
const spread = toFiniteNumber(execution?.venueSpread);
|
|
1205
|
+
const zScore = toFiniteNumber(execution?.venueSpreadZScore);
|
|
1206
|
+
if (spread == null) {
|
|
1207
|
+
return buildMissingSpreadContext();
|
|
1222
1208
|
}
|
|
1209
|
+
const value = roundTo(spread, 8);
|
|
1210
|
+
const bps = roundTo(value * 1e4, 2);
|
|
1211
|
+
const absBps = Math.abs(bps);
|
|
1212
|
+
const bias = absBps < 1 ? "flat" : bps > 0 ? "coinbase_premium" : "binance_premium";
|
|
1213
|
+
const severity = absBps >= 20 ? "wide" : absBps >= 5 ? "elevated" : "normal";
|
|
1223
1214
|
return {
|
|
1224
|
-
source:
|
|
1215
|
+
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1216
|
+
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1225
1217
|
available: true,
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
ethVolumeUsd: toFiniteNumber(cmcReferenceAssets.ethVolumeUsd),
|
|
1233
|
-
btcVolumeToMarketCap: toFiniteNumber(
|
|
1234
|
-
cmcReferenceAssets.btcVolumeToMarketCap
|
|
1235
|
-
),
|
|
1236
|
-
ethVolumeToMarketCap: toFiniteNumber(
|
|
1237
|
-
cmcReferenceAssets.ethVolumeToMarketCap
|
|
1238
|
-
),
|
|
1239
|
-
ethBtcMarketCapRatio: toFiniteNumber(
|
|
1240
|
-
cmcReferenceAssets.ethBtcMarketCapRatio
|
|
1241
|
-
),
|
|
1242
|
-
ethBtcMarketCapRatioChange24hPct: toFiniteNumber(
|
|
1243
|
-
cmcReferenceAssets.ethBtcMarketCapRatioChange24hPct
|
|
1244
|
-
),
|
|
1245
|
-
ethVsBtcVolumeRatio: toFiniteNumber(cmcReferenceAssets.ethVsBtcVolumeRatio),
|
|
1246
|
-
referenceLiquidityRegime: typeof cmcReferenceAssets.referenceLiquidityRegime === "string" ? cmcReferenceAssets.referenceLiquidityRegime : null
|
|
1218
|
+
value,
|
|
1219
|
+
zScore,
|
|
1220
|
+
bps,
|
|
1221
|
+
absBps,
|
|
1222
|
+
bias,
|
|
1223
|
+
severity
|
|
1247
1224
|
};
|
|
1248
1225
|
};
|
|
1249
|
-
var
|
|
1226
|
+
var buildTrueDeltaContextFromSignal = (signal) => {
|
|
1250
1227
|
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1251
|
-
const
|
|
1252
|
-
const
|
|
1253
|
-
|
|
1228
|
+
const participation = toRecord(baseContext?.participation);
|
|
1229
|
+
const delta = toRecord(participation?.delta);
|
|
1230
|
+
const source = String(delta?.source ?? "");
|
|
1231
|
+
const isTrueDeltaSource = source === "kline_taker_volume" || source === "agg_trades" || source === "trades";
|
|
1232
|
+
if (!delta || !isTrueDeltaSource) {
|
|
1254
1233
|
return {
|
|
1255
|
-
source: null,
|
|
1234
|
+
source: source || null,
|
|
1256
1235
|
available: false,
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
binanceVolumeUsd: null,
|
|
1264
|
-
binanceVolumeShare: null,
|
|
1265
|
-
topExchangeVolumeShare: null,
|
|
1266
|
-
liquidityRegime: null
|
|
1236
|
+
buyPressurePct: null,
|
|
1237
|
+
buyVolume: null,
|
|
1238
|
+
sellVolume: null,
|
|
1239
|
+
netDelta: null,
|
|
1240
|
+
deltaPct: null,
|
|
1241
|
+
signedVolumeZScore: null
|
|
1267
1242
|
};
|
|
1268
1243
|
}
|
|
1269
1244
|
return {
|
|
1270
|
-
source
|
|
1245
|
+
source,
|
|
1271
1246
|
available: true,
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
cmcExchangeLiquidity.totalVolumeChange24hPct
|
|
1279
|
-
),
|
|
1280
|
-
binanceVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeUsd),
|
|
1281
|
-
binanceVolumeShare: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeShare),
|
|
1282
|
-
topExchangeVolumeShare: toFiniteNumber(
|
|
1283
|
-
cmcExchangeLiquidity.topExchangeVolumeShare
|
|
1284
|
-
),
|
|
1285
|
-
liquidityRegime: typeof cmcExchangeLiquidity.liquidityRegime === "string" ? cmcExchangeLiquidity.liquidityRegime : null
|
|
1247
|
+
buyPressurePct: toFiniteNumber(delta.buyPressurePct),
|
|
1248
|
+
buyVolume: toFiniteNumber(delta.buyVolume),
|
|
1249
|
+
sellVolume: toFiniteNumber(delta.sellVolume),
|
|
1250
|
+
netDelta: toFiniteNumber(delta.netDelta),
|
|
1251
|
+
deltaPct: toFiniteNumber(delta.deltaPct),
|
|
1252
|
+
signedVolumeZScore: toFiniteNumber(delta.signedVolumeZScore)
|
|
1286
1253
|
};
|
|
1287
1254
|
};
|
|
1288
|
-
var
|
|
1255
|
+
var buildTradeFlowContextFromSignal = (signal) => {
|
|
1289
1256
|
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1290
|
-
const
|
|
1291
|
-
const
|
|
1292
|
-
if (!
|
|
1257
|
+
const participation = toRecord(baseContext?.participation);
|
|
1258
|
+
const tradeFlow = toRecord(participation?.tradeFlow);
|
|
1259
|
+
if (!tradeFlow) {
|
|
1293
1260
|
return {
|
|
1294
1261
|
source: null,
|
|
1295
1262
|
available: false,
|
|
1296
1263
|
interval: null,
|
|
1297
|
-
asOfTs: null,
|
|
1298
1264
|
stale: null,
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
sentimentRegime: null
|
|
1265
|
+
trades: null,
|
|
1266
|
+
buyPressurePct: null,
|
|
1267
|
+
netBaseDelta: null,
|
|
1268
|
+
netQuoteDelta: null
|
|
1304
1269
|
};
|
|
1305
1270
|
}
|
|
1306
1271
|
return {
|
|
1307
|
-
source: String(
|
|
1272
|
+
source: String(tradeFlow.source ?? ""),
|
|
1308
1273
|
available: true,
|
|
1309
|
-
interval:
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
classification: typeof cmcFearGreed.classification === "string" ? cmcFearGreed.classification : null,
|
|
1316
|
-
sentimentRegime: typeof cmcFearGreed.sentimentRegime === "string" ? cmcFearGreed.sentimentRegime : null
|
|
1274
|
+
interval: String(tradeFlow.interval ?? ""),
|
|
1275
|
+
stale: typeof tradeFlow.stale === "boolean" ? tradeFlow.stale : null,
|
|
1276
|
+
trades: toFiniteNumber(tradeFlow.trades),
|
|
1277
|
+
buyPressurePct: toFiniteNumber(tradeFlow.buyPressurePct),
|
|
1278
|
+
netBaseDelta: toFiniteNumber(tradeFlow.netBaseDelta),
|
|
1279
|
+
netQuoteDelta: toFiniteNumber(tradeFlow.netQuoteDelta)
|
|
1317
1280
|
};
|
|
1318
1281
|
};
|
|
1319
|
-
var
|
|
1282
|
+
var buildMarketBreadthContextFromSignal = (signal) => {
|
|
1320
1283
|
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1321
1284
|
const relative = toRecord(baseContext?.relative);
|
|
1322
|
-
const
|
|
1323
|
-
if (!
|
|
1285
|
+
const breadth = toRecord(relative?.marketBreadth);
|
|
1286
|
+
if (!breadth) {
|
|
1324
1287
|
return {
|
|
1325
1288
|
source: null,
|
|
1326
1289
|
available: false,
|
|
1290
|
+
universe: null,
|
|
1327
1291
|
interval: null,
|
|
1328
|
-
asOfTs: null,
|
|
1329
1292
|
stale: null,
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
cmc20TopConstituentWeightPct: null,
|
|
1338
|
-
cmc20ToCmc100Ratio: null,
|
|
1339
|
-
cmc20ToCmc100RatioChange24hPct: null,
|
|
1340
|
-
indexRegime: null
|
|
1293
|
+
symbolsCount: null,
|
|
1294
|
+
advanceDeclineRatio: null,
|
|
1295
|
+
pctAboveMa20: null,
|
|
1296
|
+
pctAboveMa50: null,
|
|
1297
|
+
equalWeightedReturn: null,
|
|
1298
|
+
volumeWeightedReturn: null,
|
|
1299
|
+
dispersion: null
|
|
1341
1300
|
};
|
|
1342
1301
|
}
|
|
1343
1302
|
return {
|
|
1344
|
-
source: String(
|
|
1303
|
+
source: String(breadth.source ?? ""),
|
|
1345
1304
|
available: true,
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
stale: typeof
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
),
|
|
1355
|
-
|
|
1356
|
-
cmc20Change24hPct: toFiniteNumber(cmcIndexes.cmc20Change24hPct),
|
|
1357
|
-
cmc20TopConstituentSymbol: typeof cmcIndexes.cmc20TopConstituentSymbol === "string" ? cmcIndexes.cmc20TopConstituentSymbol : null,
|
|
1358
|
-
cmc20TopConstituentWeightPct: toFiniteNumber(
|
|
1359
|
-
cmcIndexes.cmc20TopConstituentWeightPct
|
|
1360
|
-
),
|
|
1361
|
-
cmc20ToCmc100Ratio: toFiniteNumber(cmcIndexes.cmc20ToCmc100Ratio),
|
|
1362
|
-
cmc20ToCmc100RatioChange24hPct: toFiniteNumber(
|
|
1363
|
-
cmcIndexes.cmc20ToCmc100RatioChange24hPct
|
|
1364
|
-
),
|
|
1365
|
-
indexRegime: typeof cmcIndexes.indexRegime === "string" ? cmcIndexes.indexRegime : null
|
|
1305
|
+
universe: String(breadth.universe ?? ""),
|
|
1306
|
+
interval: String(breadth.interval ?? ""),
|
|
1307
|
+
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
1308
|
+
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
1309
|
+
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
1310
|
+
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
1311
|
+
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
1312
|
+
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
1313
|
+
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
1314
|
+
dispersion: toFiniteNumber(breadth.dispersion)
|
|
1366
1315
|
};
|
|
1367
1316
|
};
|
|
1368
|
-
var
|
|
1317
|
+
var buildMarketBreadthsContextFromSignal = (signal) => {
|
|
1369
1318
|
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1370
1319
|
const relative = toRecord(baseContext?.relative);
|
|
1371
|
-
const
|
|
1372
|
-
const
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1320
|
+
const breadths = toRecord(relative?.marketBreadths);
|
|
1321
|
+
const build = (key) => {
|
|
1322
|
+
const breadth = toRecord(breadths?.[key]);
|
|
1323
|
+
if (!breadth) {
|
|
1324
|
+
return {
|
|
1325
|
+
source: null,
|
|
1326
|
+
available: false,
|
|
1327
|
+
universe: null,
|
|
1328
|
+
interval: null,
|
|
1329
|
+
stale: null,
|
|
1330
|
+
symbolsCount: null,
|
|
1331
|
+
advanceDeclineRatio: null,
|
|
1332
|
+
pctAboveMa20: null,
|
|
1333
|
+
pctAboveMa50: null,
|
|
1334
|
+
equalWeightedReturn: null,
|
|
1335
|
+
volumeWeightedReturn: null,
|
|
1336
|
+
dispersion: null
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1376
1339
|
return {
|
|
1377
|
-
source:
|
|
1378
|
-
available:
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1340
|
+
source: String(breadth.source ?? ""),
|
|
1341
|
+
available: true,
|
|
1342
|
+
universe: String(breadth.universe ?? ""),
|
|
1343
|
+
interval: String(breadth.interval ?? ""),
|
|
1344
|
+
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
1345
|
+
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
1346
|
+
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
1347
|
+
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
1348
|
+
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
1349
|
+
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
1350
|
+
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
1351
|
+
dispersion: toFiniteNumber(breadth.dispersion)
|
|
1383
1352
|
};
|
|
1384
|
-
}
|
|
1353
|
+
};
|
|
1385
1354
|
return {
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
primaryTradeFlow?.buyPressurePct
|
|
1392
|
-
),
|
|
1393
|
-
primaryTradeFlowStale: typeof primaryTradeFlow?.stale === "boolean" ? primaryTradeFlow.stale : null
|
|
1355
|
+
top5: build("top5"),
|
|
1356
|
+
top10: build("top10"),
|
|
1357
|
+
top30: build("top30"),
|
|
1358
|
+
top50: build("top50"),
|
|
1359
|
+
top100: build("top100")
|
|
1394
1360
|
};
|
|
1395
1361
|
};
|
|
1396
|
-
var
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
}
|
|
1400
|
-
participation: {
|
|
1401
|
-
trueDelta: buildTrueDeltaContextFromSignal(signal),
|
|
1402
|
-
tradeFlow: buildTradeFlowContextFromSignal(signal)
|
|
1403
|
-
},
|
|
1404
|
-
relative: {
|
|
1405
|
-
marketBreadth: buildMarketBreadthContextFromSignal(signal),
|
|
1406
|
-
marketBreadths: buildMarketBreadthsContextFromSignal(signal),
|
|
1407
|
-
targetVsBtc: buildTargetVsBtcContextFromSignal(signal),
|
|
1408
|
-
btcAltRegime: buildBtcAltRegimeContextFromSignal(signal),
|
|
1409
|
-
cmcGlobal: buildCmcGlobalContextFromSignal(signal),
|
|
1410
|
-
cmcReferenceAssets: buildCmcReferenceAssetsContextFromSignal(signal),
|
|
1411
|
-
cmcExchangeLiquidity: buildCmcExchangeLiquidityContextFromSignal(signal),
|
|
1412
|
-
cmcFearGreed: buildCmcFearGreedContextFromSignal(signal),
|
|
1413
|
-
cmcIndexes: buildCmcIndexesContextFromSignal(signal),
|
|
1414
|
-
referenceTradeFlow: buildReferenceTradeFlowContextFromSignal(signal)
|
|
1415
|
-
}
|
|
1416
|
-
});
|
|
1417
|
-
|
|
1418
|
-
// src/strategy/manifests.ts
|
|
1419
|
-
var import_indicators = require("@tradejs/core/indicators");
|
|
1420
|
-
|
|
1421
|
-
// src/tradejsConfig.ts
|
|
1422
|
-
var import_fs = __toESM(require("fs"));
|
|
1423
|
-
var import_path = __toESM(require("path"));
|
|
1424
|
-
var import_url = require("url");
|
|
1425
|
-
var import_config = require("@tradejs/core/config");
|
|
1426
|
-
var import_logger = require("@tradejs/infra/logger");
|
|
1427
|
-
var CONFIG_FILE_NAMES = [
|
|
1428
|
-
"tradejs.config.ts",
|
|
1429
|
-
"tradejs.config.mts",
|
|
1430
|
-
"tradejs.config.js",
|
|
1431
|
-
"tradejs.config.mjs",
|
|
1432
|
-
"tradejs.config.cjs"
|
|
1433
|
-
];
|
|
1434
|
-
var TS_MODULE_RE = /\.(cts|mts|ts)$/i;
|
|
1435
|
-
var cachedByCwd = /* @__PURE__ */ new Map();
|
|
1436
|
-
var announcedConfigFile = /* @__PURE__ */ new Set();
|
|
1437
|
-
var tsNodeRegistered = false;
|
|
1438
|
-
var tsconfigPathsRegisteredByCwd = /* @__PURE__ */ new Set();
|
|
1439
|
-
var tsconfigPathMatchersByCwd = /* @__PURE__ */ new Map();
|
|
1440
|
-
var getTradejsProjectCwd = (cwd) => {
|
|
1441
|
-
const explicit = String(cwd ?? "").trim();
|
|
1442
|
-
if (explicit) {
|
|
1443
|
-
return import_path.default.resolve(explicit);
|
|
1444
|
-
}
|
|
1445
|
-
const fromEnv = String(process.env.PROJECT_CWD || "").trim();
|
|
1446
|
-
if (fromEnv) {
|
|
1447
|
-
return import_path.default.resolve(fromEnv);
|
|
1448
|
-
}
|
|
1449
|
-
return process.cwd();
|
|
1450
|
-
};
|
|
1451
|
-
var normalizeConfig = (rawConfig) => {
|
|
1452
|
-
if (!rawConfig || typeof rawConfig !== "object") {
|
|
1453
|
-
return {};
|
|
1454
|
-
}
|
|
1455
|
-
const config = rawConfig;
|
|
1456
|
-
const strategies2 = Array.isArray(config.strategies) ? config.strategies.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1457
|
-
const indicators = Array.isArray(config.indicators) ? config.indicators.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1458
|
-
const connectors = Array.isArray(config.connectors) ? config.connectors.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1459
|
-
const hooks = (0, import_config.normalizeTradejsConfigHooks)(
|
|
1460
|
-
config.hooks
|
|
1461
|
-
);
|
|
1462
|
-
const runtime = config.runtime && typeof config.runtime === "object" && !Array.isArray(config.runtime) ? config.runtime : void 0;
|
|
1362
|
+
var buildTargetVsReference = (signal, asset) => {
|
|
1363
|
+
const base = toRecord(signal.additionalIndicators?.baseContext);
|
|
1364
|
+
const relative = toRecord(base?.relative);
|
|
1365
|
+
const target = toRecord(relative?.[`targetVs${asset}`]);
|
|
1463
1366
|
return {
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1367
|
+
source: target ? String(target.source ?? "") : null,
|
|
1368
|
+
available: Boolean(target),
|
|
1369
|
+
ratioReturn1h: toFiniteNumber(target?.ratioReturn1h),
|
|
1370
|
+
ratioReturn4h: toFiniteNumber(target?.ratioReturn4h),
|
|
1371
|
+
ratioReturn24h: toFiniteNumber(target?.ratioReturn24h),
|
|
1372
|
+
alpha1h: toFiniteNumber(target?.[`alphaVs${asset}1h`]),
|
|
1373
|
+
alpha4h: toFiniteNumber(target?.[`alphaVs${asset}4h`]),
|
|
1374
|
+
alpha24h: toFiniteNumber(target?.[`alphaVs${asset}24h`]),
|
|
1375
|
+
beta: toFiniteNumber(target?.[`betaTo${asset}20`]),
|
|
1376
|
+
correlation: toFiniteNumber(target?.[`correlationTo${asset}20`]),
|
|
1377
|
+
ratioTrend: typeof target?.ratioTrend === "string" ? target.ratioTrend : null
|
|
1469
1378
|
};
|
|
1470
1379
|
};
|
|
1471
|
-
var
|
|
1472
|
-
const
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1380
|
+
var buildTargetVsBtcContextFromSignal = (signal) => {
|
|
1381
|
+
const { alpha1h, alpha4h, alpha24h, beta, correlation, ...shared } = buildTargetVsReference(signal, "Btc");
|
|
1382
|
+
return {
|
|
1383
|
+
...shared,
|
|
1384
|
+
alphaVsBtc1h: alpha1h,
|
|
1385
|
+
alphaVsBtc4h: alpha4h,
|
|
1386
|
+
alphaVsBtc24h: alpha24h,
|
|
1387
|
+
betaToBtc20: beta,
|
|
1388
|
+
correlationToBtc20: correlation
|
|
1389
|
+
};
|
|
1477
1390
|
};
|
|
1478
|
-
var
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
module: "Node16",
|
|
1489
|
-
moduleResolution: "node16"
|
|
1490
|
-
}
|
|
1491
|
-
});
|
|
1492
|
-
tsNodeRegistered = true;
|
|
1391
|
+
var buildTargetVsEthContextFromSignal = (signal) => {
|
|
1392
|
+
const { alpha1h, alpha4h, alpha24h, beta, correlation, ...shared } = buildTargetVsReference(signal, "Eth");
|
|
1393
|
+
return {
|
|
1394
|
+
...shared,
|
|
1395
|
+
alphaVsEth1h: alpha1h,
|
|
1396
|
+
alphaVsEth4h: alpha4h,
|
|
1397
|
+
alphaVsEth24h: alpha24h,
|
|
1398
|
+
betaToEth20: beta,
|
|
1399
|
+
correlationToEth20: correlation
|
|
1400
|
+
};
|
|
1493
1401
|
};
|
|
1494
|
-
var
|
|
1495
|
-
const
|
|
1496
|
-
|
|
1497
|
-
|
|
1402
|
+
var buildBtcAltRegimeContextFromSignal = (signal) => {
|
|
1403
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1404
|
+
const relative = toRecord(baseContext?.relative);
|
|
1405
|
+
const btcAltRegime = toRecord(relative?.btcAltRegime);
|
|
1406
|
+
if (!btcAltRegime) {
|
|
1407
|
+
return {
|
|
1408
|
+
source: null,
|
|
1409
|
+
available: false,
|
|
1410
|
+
universe: null,
|
|
1411
|
+
interval: null,
|
|
1412
|
+
stale: null,
|
|
1413
|
+
regime: null,
|
|
1414
|
+
btcReturn24h: null,
|
|
1415
|
+
altBasketReturn24h: null,
|
|
1416
|
+
btcVsAltReturn24h: null,
|
|
1417
|
+
btcTurnoverShare24h: null,
|
|
1418
|
+
btcTurnoverShareChange24h: null,
|
|
1419
|
+
altVolToBtcVol24h: null,
|
|
1420
|
+
altDispersion24h: null
|
|
1421
|
+
};
|
|
1498
1422
|
}
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1423
|
+
return {
|
|
1424
|
+
source: String(btcAltRegime.source ?? ""),
|
|
1425
|
+
available: true,
|
|
1426
|
+
universe: String(btcAltRegime.universe ?? ""),
|
|
1427
|
+
interval: String(btcAltRegime.interval ?? ""),
|
|
1428
|
+
stale: typeof btcAltRegime.stale === "boolean" ? btcAltRegime.stale : null,
|
|
1429
|
+
regime: typeof btcAltRegime.regime === "string" ? btcAltRegime.regime : null,
|
|
1430
|
+
btcReturn24h: toFiniteNumber(btcAltRegime.btcReturn24h),
|
|
1431
|
+
altBasketReturn24h: toFiniteNumber(btcAltRegime.altBasketReturn24h),
|
|
1432
|
+
btcVsAltReturn24h: toFiniteNumber(btcAltRegime.btcVsAltReturn24h),
|
|
1433
|
+
btcTurnoverShare24h: toFiniteNumber(btcAltRegime.btcTurnoverShare24h),
|
|
1434
|
+
btcTurnoverShareChange24h: toFiniteNumber(
|
|
1435
|
+
btcAltRegime.btcTurnoverShareChange24h
|
|
1436
|
+
),
|
|
1437
|
+
altVolToBtcVol24h: toFiniteNumber(btcAltRegime.altVolToBtcVol24h),
|
|
1438
|
+
altDispersion24h: toFiniteNumber(btcAltRegime.altDispersion24h)
|
|
1439
|
+
};
|
|
1515
1440
|
};
|
|
1516
|
-
var
|
|
1517
|
-
const
|
|
1518
|
-
const
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
return
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1441
|
+
var buildCmcGlobalContextFromSignal = (signal) => {
|
|
1442
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1443
|
+
const relative = toRecord(baseContext?.relative);
|
|
1444
|
+
const cmcGlobal = toRecord(relative?.cmcGlobal);
|
|
1445
|
+
if (!cmcGlobal) {
|
|
1446
|
+
return {
|
|
1447
|
+
source: null,
|
|
1448
|
+
available: false,
|
|
1449
|
+
interval: null,
|
|
1450
|
+
asOfTs: null,
|
|
1451
|
+
stale: null,
|
|
1452
|
+
totalMarketCapUsd: null,
|
|
1453
|
+
totalVolumeUsd: null,
|
|
1454
|
+
totalVolumeReportedUsd: null,
|
|
1455
|
+
altMarketCapUsd: null,
|
|
1456
|
+
altVolumeUsd: null,
|
|
1457
|
+
altVolumeReportedUsd: null,
|
|
1458
|
+
btcDominancePct: null,
|
|
1459
|
+
ethDominancePct: null,
|
|
1460
|
+
btcDominanceChange24hPct: null,
|
|
1461
|
+
ethDominanceChange24hPct: null,
|
|
1462
|
+
altMarketCapChange24hPct: null,
|
|
1463
|
+
altVolumeChange24hPct: null,
|
|
1464
|
+
activeCryptocurrencies: null,
|
|
1465
|
+
activeExchanges: null,
|
|
1466
|
+
activeMarketPairs: null,
|
|
1467
|
+
altLiquidityRegime: null
|
|
1468
|
+
};
|
|
1532
1469
|
}
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
".
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1470
|
+
return {
|
|
1471
|
+
source: String(cmcGlobal.source ?? ""),
|
|
1472
|
+
available: true,
|
|
1473
|
+
interval: typeof cmcGlobal.interval === "string" ? cmcGlobal.interval : null,
|
|
1474
|
+
asOfTs: toFiniteNumber(cmcGlobal.asOfTs),
|
|
1475
|
+
stale: typeof cmcGlobal.stale === "boolean" ? cmcGlobal.stale : null,
|
|
1476
|
+
totalMarketCapUsd: toFiniteNumber(cmcGlobal.totalMarketCapUsd),
|
|
1477
|
+
totalVolumeUsd: toFiniteNumber(cmcGlobal.totalVolumeUsd),
|
|
1478
|
+
totalVolumeReportedUsd: toFiniteNumber(cmcGlobal.totalVolumeReportedUsd),
|
|
1479
|
+
altMarketCapUsd: toFiniteNumber(cmcGlobal.altMarketCapUsd),
|
|
1480
|
+
altVolumeUsd: toFiniteNumber(cmcGlobal.altVolumeUsd),
|
|
1481
|
+
altVolumeReportedUsd: toFiniteNumber(cmcGlobal.altVolumeReportedUsd),
|
|
1482
|
+
btcDominancePct: toFiniteNumber(cmcGlobal.btcDominancePct),
|
|
1483
|
+
ethDominancePct: toFiniteNumber(cmcGlobal.ethDominancePct),
|
|
1484
|
+
btcDominanceChange24hPct: toFiniteNumber(
|
|
1485
|
+
cmcGlobal.btcDominanceChange24hPct
|
|
1486
|
+
),
|
|
1487
|
+
ethDominanceChange24hPct: toFiniteNumber(
|
|
1488
|
+
cmcGlobal.ethDominanceChange24hPct
|
|
1489
|
+
),
|
|
1490
|
+
altMarketCapChange24hPct: toFiniteNumber(
|
|
1491
|
+
cmcGlobal.altMarketCapChange24hPct
|
|
1492
|
+
),
|
|
1493
|
+
altVolumeChange24hPct: toFiniteNumber(cmcGlobal.altVolumeChange24hPct),
|
|
1494
|
+
activeCryptocurrencies: toFiniteNumber(cmcGlobal.activeCryptocurrencies),
|
|
1495
|
+
activeExchanges: toFiniteNumber(cmcGlobal.activeExchanges),
|
|
1496
|
+
activeMarketPairs: toFiniteNumber(cmcGlobal.activeMarketPairs),
|
|
1497
|
+
altLiquidityRegime: typeof cmcGlobal.altLiquidityRegime === "string" ? cmcGlobal.altLiquidityRegime : null
|
|
1498
|
+
};
|
|
1551
1499
|
};
|
|
1552
|
-
var
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
if (
|
|
1557
|
-
return
|
|
1500
|
+
var buildCmcReferenceAssetsContextFromSignal = (signal) => {
|
|
1501
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1502
|
+
const relative = toRecord(baseContext?.relative);
|
|
1503
|
+
const cmcReferenceAssets = toRecord(relative?.cmcReferenceAssets);
|
|
1504
|
+
if (!cmcReferenceAssets) {
|
|
1505
|
+
return {
|
|
1506
|
+
source: null,
|
|
1507
|
+
available: false,
|
|
1508
|
+
interval: null,
|
|
1509
|
+
asOfTs: null,
|
|
1510
|
+
stale: null,
|
|
1511
|
+
btcMarketCapUsd: null,
|
|
1512
|
+
ethMarketCapUsd: null,
|
|
1513
|
+
btcVolumeUsd: null,
|
|
1514
|
+
ethVolumeUsd: null,
|
|
1515
|
+
btcVolumeToMarketCap: null,
|
|
1516
|
+
ethVolumeToMarketCap: null,
|
|
1517
|
+
ethBtcMarketCapRatio: null,
|
|
1518
|
+
ethBtcMarketCapRatioChange24hPct: null,
|
|
1519
|
+
ethVsBtcVolumeRatio: null,
|
|
1520
|
+
referenceLiquidityRegime: null
|
|
1521
|
+
};
|
|
1558
1522
|
}
|
|
1559
|
-
return
|
|
1523
|
+
return {
|
|
1524
|
+
source: String(cmcReferenceAssets.source ?? ""),
|
|
1525
|
+
available: true,
|
|
1526
|
+
interval: typeof cmcReferenceAssets.interval === "string" ? cmcReferenceAssets.interval : null,
|
|
1527
|
+
asOfTs: toFiniteNumber(cmcReferenceAssets.asOfTs),
|
|
1528
|
+
stale: typeof cmcReferenceAssets.stale === "boolean" ? cmcReferenceAssets.stale : null,
|
|
1529
|
+
btcMarketCapUsd: toFiniteNumber(cmcReferenceAssets.btcMarketCapUsd),
|
|
1530
|
+
ethMarketCapUsd: toFiniteNumber(cmcReferenceAssets.ethMarketCapUsd),
|
|
1531
|
+
btcVolumeUsd: toFiniteNumber(cmcReferenceAssets.btcVolumeUsd),
|
|
1532
|
+
ethVolumeUsd: toFiniteNumber(cmcReferenceAssets.ethVolumeUsd),
|
|
1533
|
+
btcVolumeToMarketCap: toFiniteNumber(
|
|
1534
|
+
cmcReferenceAssets.btcVolumeToMarketCap
|
|
1535
|
+
),
|
|
1536
|
+
ethVolumeToMarketCap: toFiniteNumber(
|
|
1537
|
+
cmcReferenceAssets.ethVolumeToMarketCap
|
|
1538
|
+
),
|
|
1539
|
+
ethBtcMarketCapRatio: toFiniteNumber(
|
|
1540
|
+
cmcReferenceAssets.ethBtcMarketCapRatio
|
|
1541
|
+
),
|
|
1542
|
+
ethBtcMarketCapRatioChange24hPct: toFiniteNumber(
|
|
1543
|
+
cmcReferenceAssets.ethBtcMarketCapRatioChange24hPct
|
|
1544
|
+
),
|
|
1545
|
+
ethVsBtcVolumeRatio: toFiniteNumber(cmcReferenceAssets.ethVsBtcVolumeRatio),
|
|
1546
|
+
referenceLiquidityRegime: typeof cmcReferenceAssets.referenceLiquidityRegime === "string" ? cmcReferenceAssets.referenceLiquidityRegime : null
|
|
1547
|
+
};
|
|
1560
1548
|
};
|
|
1561
|
-
var
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
const
|
|
1565
|
-
if (!
|
|
1566
|
-
return
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1549
|
+
var buildCmcExchangeLiquidityContextFromSignal = (signal) => {
|
|
1550
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1551
|
+
const relative = toRecord(baseContext?.relative);
|
|
1552
|
+
const cmcExchangeLiquidity = toRecord(relative?.cmcExchangeLiquidity);
|
|
1553
|
+
if (!cmcExchangeLiquidity) {
|
|
1554
|
+
return {
|
|
1555
|
+
source: null,
|
|
1556
|
+
available: false,
|
|
1557
|
+
interval: null,
|
|
1558
|
+
asOfTs: null,
|
|
1559
|
+
stale: null,
|
|
1560
|
+
exchangesCount: null,
|
|
1561
|
+
totalVolumeUsd: null,
|
|
1562
|
+
totalVolumeChange24hPct: null,
|
|
1563
|
+
binanceVolumeUsd: null,
|
|
1564
|
+
binanceVolumeShare: null,
|
|
1565
|
+
topExchangeVolumeShare: null,
|
|
1566
|
+
liquidityRegime: null
|
|
1567
|
+
};
|
|
1570
1568
|
}
|
|
1571
|
-
return
|
|
1569
|
+
return {
|
|
1570
|
+
source: String(cmcExchangeLiquidity.source ?? ""),
|
|
1571
|
+
available: true,
|
|
1572
|
+
interval: typeof cmcExchangeLiquidity.interval === "string" ? cmcExchangeLiquidity.interval : null,
|
|
1573
|
+
asOfTs: toFiniteNumber(cmcExchangeLiquidity.asOfTs),
|
|
1574
|
+
stale: typeof cmcExchangeLiquidity.stale === "boolean" ? cmcExchangeLiquidity.stale : null,
|
|
1575
|
+
exchangesCount: toFiniteNumber(cmcExchangeLiquidity.exchangesCount),
|
|
1576
|
+
totalVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.totalVolumeUsd),
|
|
1577
|
+
totalVolumeChange24hPct: toFiniteNumber(
|
|
1578
|
+
cmcExchangeLiquidity.totalVolumeChange24hPct
|
|
1579
|
+
),
|
|
1580
|
+
binanceVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeUsd),
|
|
1581
|
+
binanceVolumeShare: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeShare),
|
|
1582
|
+
topExchangeVolumeShare: toFiniteNumber(
|
|
1583
|
+
cmcExchangeLiquidity.topExchangeVolumeShare
|
|
1584
|
+
),
|
|
1585
|
+
liquidityRegime: typeof cmcExchangeLiquidity.liquidityRegime === "string" ? cmcExchangeLiquidity.liquidityRegime : null
|
|
1586
|
+
};
|
|
1572
1587
|
};
|
|
1573
|
-
var
|
|
1574
|
-
const
|
|
1575
|
-
const
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1588
|
+
var buildCmcFearGreedContextFromSignal = (signal) => {
|
|
1589
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1590
|
+
const relative = toRecord(baseContext?.relative);
|
|
1591
|
+
const cmcFearGreed = toRecord(relative?.cmcFearGreed);
|
|
1592
|
+
if (!cmcFearGreed) {
|
|
1593
|
+
return {
|
|
1594
|
+
source: null,
|
|
1595
|
+
available: false,
|
|
1596
|
+
interval: null,
|
|
1597
|
+
asOfTs: null,
|
|
1598
|
+
stale: null,
|
|
1599
|
+
value: null,
|
|
1600
|
+
valueChange24h: null,
|
|
1601
|
+
valueChange7d: null,
|
|
1602
|
+
classification: null,
|
|
1603
|
+
sentimentRegime: null
|
|
1604
|
+
};
|
|
1581
1605
|
}
|
|
1582
|
-
return
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
try {
|
|
1595
|
-
modulePath = (0, import_url.fileURLToPath)(normalized);
|
|
1596
|
-
} catch {
|
|
1597
|
-
modulePath = normalized;
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
|
-
const requireFn = getRequireFn(
|
|
1601
|
-
import_path.default.isAbsolute(modulePath) ? import_path.default.dirname(modulePath) : cwd
|
|
1602
|
-
);
|
|
1603
|
-
if (isTsModulePath(modulePath)) {
|
|
1604
|
-
await ensureTsNodeRegistered();
|
|
1605
|
-
await ensureTsconfigPathsRegistered(cwd);
|
|
1606
|
-
return requireFn(modulePath);
|
|
1607
|
-
}
|
|
1608
|
-
if (isBareModuleSpecifier(normalized)) {
|
|
1609
|
-
await ensureTsconfigPathsRegistered(cwd);
|
|
1610
|
-
try {
|
|
1611
|
-
return requireFn(normalized);
|
|
1612
|
-
} catch (error) {
|
|
1613
|
-
const resolvedByTsconfig = await resolveTsconfigPathModule(
|
|
1614
|
-
normalized,
|
|
1615
|
-
cwd
|
|
1616
|
-
);
|
|
1617
|
-
if (resolvedByTsconfig && resolvedByTsconfig !== normalized) {
|
|
1618
|
-
return requireFn(resolvedByTsconfig);
|
|
1619
|
-
}
|
|
1620
|
-
throw error;
|
|
1621
|
-
}
|
|
1622
|
-
}
|
|
1623
|
-
try {
|
|
1624
|
-
return await import(
|
|
1625
|
-
/* webpackIgnore: true */
|
|
1626
|
-
toImportSpecifier(normalized)
|
|
1627
|
-
);
|
|
1628
|
-
} catch (error) {
|
|
1629
|
-
if (isTsModulePath(modulePath)) {
|
|
1630
|
-
await ensureTsNodeRegistered();
|
|
1631
|
-
await ensureTsconfigPathsRegistered(cwd);
|
|
1632
|
-
return requireFn(modulePath);
|
|
1633
|
-
}
|
|
1634
|
-
throw error;
|
|
1635
|
-
}
|
|
1636
|
-
};
|
|
1637
|
-
var resolveExportedConfig = (moduleExports) => {
|
|
1638
|
-
const candidate = moduleExports && typeof moduleExports === "object" && "default" in moduleExports ? moduleExports.default : moduleExports;
|
|
1639
|
-
return normalizeConfig(candidate);
|
|
1606
|
+
return {
|
|
1607
|
+
source: String(cmcFearGreed.source ?? ""),
|
|
1608
|
+
available: true,
|
|
1609
|
+
interval: typeof cmcFearGreed.interval === "string" ? cmcFearGreed.interval : null,
|
|
1610
|
+
asOfTs: toFiniteNumber(cmcFearGreed.asOfTs),
|
|
1611
|
+
stale: typeof cmcFearGreed.stale === "boolean" ? cmcFearGreed.stale : null,
|
|
1612
|
+
value: toFiniteNumber(cmcFearGreed.value),
|
|
1613
|
+
valueChange24h: toFiniteNumber(cmcFearGreed.valueChange24h),
|
|
1614
|
+
valueChange7d: toFiniteNumber(cmcFearGreed.valueChange7d),
|
|
1615
|
+
classification: typeof cmcFearGreed.classification === "string" ? cmcFearGreed.classification : null,
|
|
1616
|
+
sentimentRegime: typeof cmcFearGreed.sentimentRegime === "string" ? cmcFearGreed.sentimentRegime : null
|
|
1617
|
+
};
|
|
1640
1618
|
};
|
|
1641
|
-
var
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1619
|
+
var buildCmcIndexesContextFromSignal = (signal) => {
|
|
1620
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1621
|
+
const relative = toRecord(baseContext?.relative);
|
|
1622
|
+
const cmcIndexes = toRecord(relative?.cmcIndexes);
|
|
1623
|
+
if (!cmcIndexes) {
|
|
1624
|
+
return {
|
|
1625
|
+
source: null,
|
|
1626
|
+
available: false,
|
|
1627
|
+
interval: null,
|
|
1628
|
+
asOfTs: null,
|
|
1629
|
+
stale: null,
|
|
1630
|
+
cmc100Value: null,
|
|
1631
|
+
cmc100Change24hPct: null,
|
|
1632
|
+
cmc100TopConstituentSymbol: null,
|
|
1633
|
+
cmc100TopConstituentWeightPct: null,
|
|
1634
|
+
cmc20Value: null,
|
|
1635
|
+
cmc20Change24hPct: null,
|
|
1636
|
+
cmc20TopConstituentSymbol: null,
|
|
1637
|
+
cmc20TopConstituentWeightPct: null,
|
|
1638
|
+
cmc20ToCmc100Ratio: null,
|
|
1639
|
+
cmc20ToCmc100RatioChange24hPct: null,
|
|
1640
|
+
indexRegime: null
|
|
1641
|
+
};
|
|
1655
1642
|
}
|
|
1643
|
+
return {
|
|
1644
|
+
source: String(cmcIndexes.source ?? ""),
|
|
1645
|
+
available: true,
|
|
1646
|
+
interval: typeof cmcIndexes.interval === "string" ? cmcIndexes.interval : null,
|
|
1647
|
+
asOfTs: toFiniteNumber(cmcIndexes.asOfTs),
|
|
1648
|
+
stale: typeof cmcIndexes.stale === "boolean" ? cmcIndexes.stale : null,
|
|
1649
|
+
cmc100Value: toFiniteNumber(cmcIndexes.cmc100Value),
|
|
1650
|
+
cmc100Change24hPct: toFiniteNumber(cmcIndexes.cmc100Change24hPct),
|
|
1651
|
+
cmc100TopConstituentSymbol: typeof cmcIndexes.cmc100TopConstituentSymbol === "string" ? cmcIndexes.cmc100TopConstituentSymbol : null,
|
|
1652
|
+
cmc100TopConstituentWeightPct: toFiniteNumber(
|
|
1653
|
+
cmcIndexes.cmc100TopConstituentWeightPct
|
|
1654
|
+
),
|
|
1655
|
+
cmc20Value: toFiniteNumber(cmcIndexes.cmc20Value),
|
|
1656
|
+
cmc20Change24hPct: toFiniteNumber(cmcIndexes.cmc20Change24hPct),
|
|
1657
|
+
cmc20TopConstituentSymbol: typeof cmcIndexes.cmc20TopConstituentSymbol === "string" ? cmcIndexes.cmc20TopConstituentSymbol : null,
|
|
1658
|
+
cmc20TopConstituentWeightPct: toFiniteNumber(
|
|
1659
|
+
cmcIndexes.cmc20TopConstituentWeightPct
|
|
1660
|
+
),
|
|
1661
|
+
cmc20ToCmc100Ratio: toFiniteNumber(cmcIndexes.cmc20ToCmc100Ratio),
|
|
1662
|
+
cmc20ToCmc100RatioChange24hPct: toFiniteNumber(
|
|
1663
|
+
cmcIndexes.cmc20ToCmc100RatioChange24hPct
|
|
1664
|
+
),
|
|
1665
|
+
indexRegime: typeof cmcIndexes.indexRegime === "string" ? cmcIndexes.indexRegime : null
|
|
1666
|
+
};
|
|
1656
1667
|
};
|
|
1657
|
-
var
|
|
1658
|
-
const
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
return import_path.default.resolve(cwd, normalized);
|
|
1668
|
+
var buildReferenceTradeFlowContextFromSignal = (signal) => {
|
|
1669
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1670
|
+
const relative = toRecord(baseContext?.relative);
|
|
1671
|
+
const refs = toRecord(relative?.referenceTradeFlow);
|
|
1672
|
+
const primaryReferenceSymbol = typeof refs?.primaryReferenceSymbol === "string" ? refs.primaryReferenceSymbol : null;
|
|
1673
|
+
const tradeFlowBySymbol = toRecord(refs?.tradeFlowBySymbol);
|
|
1674
|
+
const primaryTradeFlow = primaryReferenceSymbol != null ? toRecord(tradeFlowBySymbol?.[primaryReferenceSymbol]) : null;
|
|
1675
|
+
if (!refs) {
|
|
1676
|
+
return {
|
|
1677
|
+
source: null,
|
|
1678
|
+
available: false,
|
|
1679
|
+
primaryReferenceSymbol: null,
|
|
1680
|
+
referenceSymbols: [],
|
|
1681
|
+
primaryTradeFlowBuyPressurePct: null,
|
|
1682
|
+
primaryTradeFlowStale: null
|
|
1683
|
+
};
|
|
1674
1684
|
}
|
|
1675
|
-
return
|
|
1685
|
+
return {
|
|
1686
|
+
source: String(refs.source ?? ""),
|
|
1687
|
+
available: true,
|
|
1688
|
+
primaryReferenceSymbol,
|
|
1689
|
+
referenceSymbols: Array.isArray(refs.referenceSymbols) ? refs.referenceSymbols.map(String) : [],
|
|
1690
|
+
primaryTradeFlowBuyPressurePct: toFiniteNumber(
|
|
1691
|
+
primaryTradeFlow?.buyPressurePct
|
|
1692
|
+
),
|
|
1693
|
+
primaryTradeFlowStale: typeof primaryTradeFlow?.stale === "boolean" ? primaryTradeFlow.stale : null
|
|
1694
|
+
};
|
|
1676
1695
|
};
|
|
1677
|
-
var
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
import_logger.logger.log(
|
|
1698
|
-
"warn",
|
|
1699
|
-
"Failed to load TradeJS config from %s: %s",
|
|
1700
|
-
configFilePath,
|
|
1701
|
-
String(error)
|
|
1702
|
-
);
|
|
1703
|
-
cachedByCwd.set(cwd, {});
|
|
1704
|
-
return {};
|
|
1696
|
+
var buildAiMarketContext = (signal) => ({
|
|
1697
|
+
execution: {
|
|
1698
|
+
binanceCoinbaseSpread: buildSpreadContextFromSignal(signal)
|
|
1699
|
+
},
|
|
1700
|
+
participation: {
|
|
1701
|
+
trueDelta: buildTrueDeltaContextFromSignal(signal),
|
|
1702
|
+
tradeFlow: buildTradeFlowContextFromSignal(signal)
|
|
1703
|
+
},
|
|
1704
|
+
relative: {
|
|
1705
|
+
marketBreadth: buildMarketBreadthContextFromSignal(signal),
|
|
1706
|
+
marketBreadths: buildMarketBreadthsContextFromSignal(signal),
|
|
1707
|
+
targetVsBtc: buildTargetVsBtcContextFromSignal(signal),
|
|
1708
|
+
targetVsEth: buildTargetVsEthContextFromSignal(signal),
|
|
1709
|
+
btcAltRegime: buildBtcAltRegimeContextFromSignal(signal),
|
|
1710
|
+
cmcGlobal: buildCmcGlobalContextFromSignal(signal),
|
|
1711
|
+
cmcReferenceAssets: buildCmcReferenceAssetsContextFromSignal(signal),
|
|
1712
|
+
cmcExchangeLiquidity: buildCmcExchangeLiquidityContextFromSignal(signal),
|
|
1713
|
+
cmcFearGreed: buildCmcFearGreedContextFromSignal(signal),
|
|
1714
|
+
cmcIndexes: buildCmcIndexesContextFromSignal(signal),
|
|
1715
|
+
referenceTradeFlow: buildReferenceTradeFlowContextFromSignal(signal)
|
|
1705
1716
|
}
|
|
1706
|
-
};
|
|
1717
|
+
});
|
|
1707
1718
|
|
|
1708
1719
|
// src/strategy/manifests.ts
|
|
1720
|
+
var import_indicators = require("@tradejs/core/indicators");
|
|
1709
1721
|
var SHARED_STRATEGY_REGISTRY_KEY = "__tradejsNodeSharedStrategyRegistryV1__";
|
|
1710
1722
|
var sharedRegistryScope = globalThis;
|
|
1711
1723
|
var sharedStrategyRegistry = sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] ?? (sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] = {
|
|
@@ -2169,12 +2181,61 @@ var buildBaseAiPayload = (signal) => {
|
|
|
2169
2181
|
};
|
|
2170
2182
|
};
|
|
2171
2183
|
var defaultAiAdapter = {};
|
|
2184
|
+
var tickerProfileFeatureIds = new Set(
|
|
2185
|
+
import_aiFeatures.TICKER_PROFILE_AI_FEATURES.map(({ id }) => id)
|
|
2186
|
+
);
|
|
2172
2187
|
var getStrategyAiAdapter = (strategy, profileId) => getStrategyProfileAiAdapter(getStrategyManifest(strategy), profileId) ?? defaultAiAdapter;
|
|
2173
2188
|
var getSignalAiAdapter = (signal) => getStrategyAiAdapter(signal.strategy, signal.policyProfileId);
|
|
2174
2189
|
var buildAiPayloadByStrategy = (signal) => {
|
|
2175
2190
|
const basePayload = buildBaseAiPayload(signal);
|
|
2176
2191
|
const adapter = getSignalAiAdapter(signal);
|
|
2177
|
-
|
|
2192
|
+
const payload = adapter.buildPayload?.({ signal, basePayload }) ?? basePayload;
|
|
2193
|
+
const custom = getStrategyManifest(signal.strategy)?.aiFeatures ?? [];
|
|
2194
|
+
const frozen = signal.aiFeatureReplay?.snapshot;
|
|
2195
|
+
if (frozen) (0, import_aiFeatures.validateAiFeatureSnapshot)(frozen);
|
|
2196
|
+
const tickerProfile = signal.additionalIndicators?.tickerProfile ?? signal.additionalIndicators?.baseContext?.tickerProfile;
|
|
2197
|
+
const hasProfile = Boolean(
|
|
2198
|
+
tickerProfile || frozen?.definitions.some(({ id }) => tickerProfileFeatureIds.has(id))
|
|
2199
|
+
);
|
|
2200
|
+
const indicatorFeatures = (0, import_indicators2.getRegisteredIndicatorEntries)(
|
|
2201
|
+
getTradejsProjectCwd()
|
|
2202
|
+
).flatMap((entry) => entry.indicator.enabled ? entry.aiFeatures ?? [] : []);
|
|
2203
|
+
const hasPortfolio = Boolean(
|
|
2204
|
+
signal.additionalIndicators?.portfolioContext || frozen?.definitions.some(
|
|
2205
|
+
({ id }) => import_aiFeatures.PORTFOLIO_AI_FEATURES.some((d) => d.id === id)
|
|
2206
|
+
)
|
|
2207
|
+
);
|
|
2208
|
+
const definitions = [
|
|
2209
|
+
...hasPortfolio ? import_aiFeatures.PORTFOLIO_AI_FEATURES : [],
|
|
2210
|
+
...hasProfile ? import_aiFeatures.TICKER_PROFILE_AI_FEATURES : [],
|
|
2211
|
+
...indicatorFeatures,
|
|
2212
|
+
...custom
|
|
2213
|
+
];
|
|
2214
|
+
if (!definitions.length && !signal.aiFeatureReplay?.snapshot) return payload;
|
|
2215
|
+
(0, import_aiFeatures.validateAiFeatureDefinitions)(definitions);
|
|
2216
|
+
if (signal.aiFeatureReplay && !frozen) {
|
|
2217
|
+
throw new Error(
|
|
2218
|
+
"Dataset has no frozen AI features; regenerate the decision-time export"
|
|
2219
|
+
);
|
|
2220
|
+
}
|
|
2221
|
+
if (frozen && JSON.stringify(frozen.definitions) !== JSON.stringify(definitions)) {
|
|
2222
|
+
throw new Error(
|
|
2223
|
+
"AI feature definition changed; regenerate the decision-time export"
|
|
2224
|
+
);
|
|
2225
|
+
}
|
|
2226
|
+
return {
|
|
2227
|
+
...payload,
|
|
2228
|
+
featureSnapshot: frozen ?? (0, import_aiFeatures.projectAiFeatures)(definitions, {
|
|
2229
|
+
signal: tickerProfile ? {
|
|
2230
|
+
...signal,
|
|
2231
|
+
additionalIndicators: {
|
|
2232
|
+
...signal.additionalIndicators,
|
|
2233
|
+
tickerProfile
|
|
2234
|
+
}
|
|
2235
|
+
} : signal,
|
|
2236
|
+
payload
|
|
2237
|
+
})
|
|
2238
|
+
};
|
|
2178
2239
|
};
|
|
2179
2240
|
var buildAiSystemPromptAddonByStrategy = (signal) => getSignalAiAdapter(signal).buildSystemPromptAddon?.({ signal }) ?? "";
|
|
2180
2241
|
var buildAiHumanPromptAddonByStrategy = (signal, payload) => getSignalAiAdapter(signal).buildHumanPromptAddon?.({
|
|
@@ -2638,535 +2699,216 @@ var runAiPromptLocal = async (signal, options = {}) => {
|
|
|
2638
2699
|
payload
|
|
2639
2700
|
);
|
|
2640
2701
|
};
|
|
2641
|
-
var askAI = async (signal, options = {}) => {
|
|
2642
|
-
const { symbol } = signal;
|
|
2643
|
-
const payload = buildAiPayload(signal);
|
|
2644
|
-
const content = await runAiPrompt(
|
|
2645
|
-
{
|
|
2646
|
-
systemPrompt: buildAiSystemPrompt(signal),
|
|
2647
|
-
humanPrompt: buildAiHumanPrompt(signal, payload)
|
|
2648
|
-
},
|
|
2649
|
-
{
|
|
2650
|
-
...options,
|
|
2651
|
-
signal,
|
|
2652
|
-
payload
|
|
2653
|
-
}
|
|
2654
|
-
);
|
|
2655
|
-
await (0, import_redis.setData)(import_redis.redisKeys.analysis(symbol, signal.signalId), content);
|
|
2656
|
-
return content;
|
|
2657
|
-
};
|
|
2658
|
-
|
|
2659
|
-
// src/strategyRuntime.ts
|
|
2660
|
-
var import_jev6 = require("@tradejs/core/jev");
|
|
2661
|
-
|
|
2662
|
-
// src/jev.ts
|
|
2663
|
-
var import_promises = __toESM(require("fs/promises"));
|
|
2664
|
-
var import_node_path = __toESM(require("path"));
|
|
2665
|
-
var import_jev4 = require("@tradejs/core/jev");
|
|
2666
|
-
var import_jev5 = require("@tradejs/infra/jev");
|
|
2667
|
-
var import_userSettings2 = require("@tradejs/infra/userSettings");
|
|
2668
|
-
init_jevInput();
|
|
2669
|
-
init_jevInput();
|
|
2670
|
-
init_jevTraining();
|
|
2671
|
-
var resolveJevProvider = async (userName) => {
|
|
2672
|
-
const settings = await (0, import_userSettings2.getUserSettings)(userName);
|
|
2673
|
-
const provider = {
|
|
2674
|
-
endpoint: settings.JEV_API_ENDPOINT || import_jev4.JEV_PROVIDERS[0].endpoint,
|
|
2675
|
-
model: settings.JEV_MODEL || import_jev4.JEV_PROVIDERS[0].model
|
|
2676
|
-
};
|
|
2677
|
-
(0, import_jev4.parseJevConfig)({ source: "provider", mode: "observe", provider });
|
|
2678
|
-
if (!settings.JEV_API_KEY)
|
|
2679
|
-
throw new Error("Configure the Jev API key in Account settings");
|
|
2680
|
-
return provider;
|
|
2681
|
-
};
|
|
2682
|
-
var emptyScores = () => Object.fromEntries(import_jev4.JEV_DIMENSIONS.map((key) => [key, null]));
|
|
2683
|
-
var pending = /* @__PURE__ */ new Map();
|
|
2684
|
-
var pendingResponses = /* @__PURE__ */ new Map();
|
|
2685
|
-
var evaluateJevInput = async ({
|
|
2686
|
-
input,
|
|
2687
|
-
config,
|
|
2688
|
-
userName,
|
|
2689
|
-
projectRoot
|
|
2690
|
-
}) => {
|
|
2691
|
-
config = (0, import_jev4.parseJevConfig)(config);
|
|
2692
|
-
const inputHash = (0, import_jev5.jevHash)(input);
|
|
2693
|
-
const questions = questionsForJevInput(input);
|
|
2694
|
-
const questionsHash = (0, import_jev5.jevHash)(questions);
|
|
2695
|
-
const base = {
|
|
2696
|
-
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
2697
|
-
schema: "tradejs-signal-assessment/v5",
|
|
2698
|
-
source: config.source,
|
|
2699
|
-
mode: config.mode,
|
|
2700
|
-
inputHash,
|
|
2701
|
-
questionsHash,
|
|
2702
|
-
geometryStatus: input.geometryStatus,
|
|
2703
|
-
levelsValid: typeof input.features["signal.validLevels"] === "boolean" ? input.features["signal.validLevels"] : null
|
|
2704
|
-
};
|
|
2705
|
-
if (!input.questions.length)
|
|
2706
|
-
return {
|
|
2707
|
-
assessment: {
|
|
2708
|
-
...base,
|
|
2709
|
-
status: "unavailable",
|
|
2710
|
-
model: "",
|
|
2711
|
-
scores: emptyScores(),
|
|
2712
|
-
confidence: emptyScores()
|
|
2713
|
-
}
|
|
2714
|
-
};
|
|
2715
|
-
if (config.source === "local") {
|
|
2716
|
-
const contents = await import_promises.default.readFile(
|
|
2717
|
-
import_node_path.default.resolve(projectRoot, config.modelFile),
|
|
2718
|
-
"utf8"
|
|
2719
|
-
);
|
|
2720
|
-
if ((0, import_jev5.jevFileHash)(contents) !== config.modelSha256)
|
|
2721
|
-
throw new Error("Local Jev gate checksum mismatch");
|
|
2722
|
-
const { validateJevGateModel: validateJevGateModel2 } = await Promise.resolve().then(() => (init_jevTraining(), jevTraining_exports));
|
|
2723
|
-
const model = validateJevGateModel2(JSON.parse(contents));
|
|
2724
|
-
if (model.strategy !== input.strategy || model.questionsHash !== (0, import_jev5.jevHash)(JEV_QUESTIONS))
|
|
2725
|
-
throw new Error("Local Jev gate strategy or question version mismatch");
|
|
2726
|
-
const scores = Object.fromEntries(
|
|
2727
|
-
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
2728
|
-
key,
|
|
2729
|
-
input.questions.includes(key) && model.trees[key] ? (0, import_jev4.predictJevTree)(model.trees[key], input.features) : null
|
|
2730
|
-
])
|
|
2731
|
-
);
|
|
2732
|
-
return {
|
|
2733
|
-
assessment: {
|
|
2734
|
-
...base,
|
|
2735
|
-
status: "available",
|
|
2736
|
-
model: config.modelSha256,
|
|
2737
|
-
scores,
|
|
2738
|
-
confidence: emptyScores()
|
|
2739
|
-
}
|
|
2740
|
-
};
|
|
2741
|
-
}
|
|
2742
|
-
const provider = config.provider ?? await resolveJevProvider(userName);
|
|
2743
|
-
const id = (0, import_jev5.jevHash)({ inputHash, questionsHash, provider });
|
|
2744
|
-
const dir = import_node_path.default.resolve(projectRoot, config.recordsDir ?? "data/ai/jev");
|
|
2745
|
-
const file = import_node_path.default.join(dir, "records", `${id}.json`);
|
|
2746
|
-
const getRecord = async () => {
|
|
2747
|
-
const existing = await (0, import_jev5.readJevArtifact)(file);
|
|
2748
|
-
if (existing) {
|
|
2749
|
-
if (existing.schema !== "tradejs-jev-record/v4" || existing.id !== id || existing.inputHash !== inputHash || existing.questionsHash !== questionsHash || (0, import_jev5.jevHash)(existing.input) !== inputHash || (0, import_jev5.jevHash)(existing.provider) !== (0, import_jev5.jevHash)(provider))
|
|
2750
|
-
throw new Error("Jev recording identity mismatch");
|
|
2751
|
-
(0, import_jev5.validateJevResponse)(existing.response, input.questions);
|
|
2752
|
-
for (const key of import_jev4.JEV_DIMENSIONS) {
|
|
2753
|
-
const expected = input.questions.includes(key) ? existing.response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null;
|
|
2754
|
-
if (existing.scores[key] !== expected)
|
|
2755
|
-
throw new Error("Jev recording scores do not match the response");
|
|
2756
|
-
}
|
|
2757
|
-
return existing;
|
|
2758
|
-
}
|
|
2759
|
-
if (config.source === "recorded")
|
|
2760
|
-
throw new Error(
|
|
2761
|
-
`Missing Jev recording ${id} for ${input.strategy} ${input.symbol} at ${new Date(input.timestamp).toISOString()}. Recorded mode requires the same signal-time facts and provider; a shorter backtest can change warm-up indicators or prior trade state.`
|
|
2762
|
-
);
|
|
2763
|
-
const settings = await (0, import_userSettings2.getUserSettings)(userName);
|
|
2764
|
-
if (!settings.JEV_API_KEY)
|
|
2765
|
-
throw new Error("Configure the Jev API key in Account settings");
|
|
2766
|
-
const apiKey = settings.JEV_API_KEY;
|
|
2767
|
-
const configuredProvider = await resolveJevProvider(userName);
|
|
2768
|
-
if (configuredProvider.endpoint !== provider.endpoint)
|
|
2769
|
-
throw new Error(
|
|
2770
|
-
"Jev account endpoint changed; update the frozen provider configuration"
|
|
2771
|
-
);
|
|
2772
|
-
const started = Date.now();
|
|
2773
|
-
const state = stateForJevInput(input);
|
|
2774
|
-
const responseId = (0, import_jev5.jevHash)({
|
|
2775
|
-
state,
|
|
2776
|
-
questionsHash,
|
|
2777
|
-
provider,
|
|
2778
|
-
user: (0, import_jev5.jevHash)(userName)
|
|
2779
|
-
});
|
|
2780
|
-
const responseFile = import_node_path.default.join(dir, "responses", `${responseId}.json`);
|
|
2781
|
-
let responsePromise = pendingResponses.get(responseFile);
|
|
2782
|
-
if (!responsePromise) {
|
|
2783
|
-
responsePromise = (async () => {
|
|
2784
|
-
const saved = await (0, import_jev5.readJevArtifact)(responseFile);
|
|
2785
|
-
if (saved) return (0, import_jev5.validateJevResponse)(saved, input.questions);
|
|
2786
|
-
const fresh = await (0, import_jev5.requestJev)(provider, apiKey, state, questions);
|
|
2787
|
-
await (0, import_jev5.writeJevArtifact)(responseFile, fresh);
|
|
2788
|
-
return fresh;
|
|
2789
|
-
})();
|
|
2790
|
-
pendingResponses.set(responseFile, responsePromise);
|
|
2791
|
-
}
|
|
2792
|
-
let response;
|
|
2793
|
-
try {
|
|
2794
|
-
response = await responsePromise;
|
|
2795
|
-
} finally {
|
|
2796
|
-
pendingResponses.delete(responseFile);
|
|
2797
|
-
}
|
|
2798
|
-
const scores = Object.fromEntries(
|
|
2799
|
-
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
2800
|
-
key,
|
|
2801
|
-
input.questions.includes(key) ? response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null
|
|
2802
|
-
])
|
|
2803
|
-
);
|
|
2804
|
-
const value = {
|
|
2805
|
-
schema: "tradejs-jev-record/v4",
|
|
2806
|
-
id,
|
|
2807
|
-
inputHash,
|
|
2808
|
-
questionsHash,
|
|
2809
|
-
provider,
|
|
2810
|
-
input,
|
|
2811
|
-
response,
|
|
2812
|
-
scores,
|
|
2813
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2814
|
-
elapsedMs: Date.now() - started
|
|
2815
|
-
};
|
|
2816
|
-
await (0, import_jev5.writeJevArtifact)(file, value);
|
|
2817
|
-
return await (0, import_jev5.readJevArtifact)(file);
|
|
2818
|
-
};
|
|
2819
|
-
const pendingKey = `${file}:${(0, import_jev5.jevHash)(userName)}`;
|
|
2820
|
-
let promise = pending.get(pendingKey);
|
|
2821
|
-
if (!promise) {
|
|
2822
|
-
promise = getRecord();
|
|
2823
|
-
pending.set(pendingKey, promise);
|
|
2824
|
-
}
|
|
2825
|
-
let record;
|
|
2826
|
-
try {
|
|
2827
|
-
record = await promise;
|
|
2828
|
-
} finally {
|
|
2829
|
-
pending.delete(pendingKey);
|
|
2830
|
-
}
|
|
2831
|
-
return {
|
|
2832
|
-
record,
|
|
2833
|
-
assessment: {
|
|
2834
|
-
...base,
|
|
2835
|
-
status: "available",
|
|
2836
|
-
recordId: id,
|
|
2837
|
-
model: record.response.model,
|
|
2838
|
-
scores: record.scores,
|
|
2839
|
-
confidence: Object.fromEntries(
|
|
2840
|
-
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
2841
|
-
key,
|
|
2842
|
-
record.response.answers[key]?.confidence ?? null
|
|
2843
|
-
])
|
|
2844
|
-
)
|
|
2845
|
-
}
|
|
2846
|
-
};
|
|
2847
|
-
};
|
|
2848
|
-
var assessSignalWithJev = async ({
|
|
2849
|
-
signal,
|
|
2850
|
-
config,
|
|
2851
|
-
userName,
|
|
2852
|
-
projectRoot,
|
|
2853
|
-
strict,
|
|
2854
|
-
scope = "runtime"
|
|
2855
|
-
}) => {
|
|
2856
|
-
let assessment;
|
|
2857
|
-
try {
|
|
2858
|
-
const input = buildJevInput(signal);
|
|
2859
|
-
({ assessment } = await evaluateJevInput({
|
|
2860
|
-
input,
|
|
2861
|
-
config,
|
|
2862
|
-
userName,
|
|
2863
|
-
projectRoot
|
|
2864
|
-
}));
|
|
2865
|
-
} catch (error) {
|
|
2866
|
-
if (strict) throw error;
|
|
2867
|
-
assessment = {
|
|
2868
|
-
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
2869
|
-
schema: "tradejs-signal-assessment/v5",
|
|
2870
|
-
source: config.source,
|
|
2871
|
-
mode: config.mode,
|
|
2872
|
-
status: "unavailable",
|
|
2873
|
-
inputHash: "",
|
|
2874
|
-
questionsHash: "",
|
|
2875
|
-
geometryStatus: "absent",
|
|
2876
|
-
levelsValid: null,
|
|
2877
|
-
model: config.provider?.model ?? config.modelSha256 ?? "",
|
|
2878
|
-
scores: emptyScores(),
|
|
2879
|
-
confidence: emptyScores()
|
|
2880
|
-
};
|
|
2881
|
-
}
|
|
2882
|
-
signal.assessment = assessment;
|
|
2883
|
-
signal.additionalIndicators = {
|
|
2884
|
-
...signal.additionalIndicators,
|
|
2885
|
-
jev: (0, import_jev4.jevAssessmentFeatures)(assessment)
|
|
2886
|
-
};
|
|
2887
|
-
const identity = {
|
|
2888
|
-
signalId: signal.signalId,
|
|
2889
|
-
strategy: signal.strategy,
|
|
2890
|
-
symbol: signal.symbol,
|
|
2891
|
-
timestamp: signal.timestamp,
|
|
2892
|
-
scope,
|
|
2893
|
-
user: (0, import_jev5.jevHash)(userName),
|
|
2894
|
-
config,
|
|
2895
|
-
assessment
|
|
2896
|
-
};
|
|
2897
|
-
await (0, import_jev5.writeJevArtifact)(
|
|
2898
|
-
import_node_path.default.resolve(
|
|
2899
|
-
projectRoot,
|
|
2900
|
-
config.recordsDir ?? "data/ai/jev",
|
|
2901
|
-
"decisions",
|
|
2902
|
-
`${(0, import_jev5.jevHash)(identity)}.json`
|
|
2903
|
-
),
|
|
2904
|
-
identity
|
|
2905
|
-
);
|
|
2906
|
-
return assessment;
|
|
2907
|
-
};
|
|
2908
|
-
|
|
2909
|
-
// src/strategyRuntime.ts
|
|
2910
|
-
var import_constants5 = require("@tradejs/core/constants");
|
|
2911
|
-
var import_strategies6 = require("@tradejs/core/strategies");
|
|
2912
|
-
var import_logger11 = require("@tradejs/infra/logger");
|
|
2913
|
-
|
|
2914
|
-
// src/strategyHelpers/runtime.ts
|
|
2915
|
-
var import_logger9 = require("@tradejs/infra/logger");
|
|
2916
|
-
var import_constants3 = require("@tradejs/core/constants");
|
|
2917
|
-
var import_ml2 = require("@tradejs/infra/ml");
|
|
2918
|
-
|
|
2919
|
-
// src/strategy/entrySubmissionValidation.ts
|
|
2920
|
-
var import_logger2 = require("@tradejs/infra/logger");
|
|
2921
|
-
|
|
2922
|
-
// src/strategy/entryExecutionValidation.ts
|
|
2923
|
-
var EntrySubmissionValidationError = class extends Error {
|
|
2924
|
-
constructor() {
|
|
2925
|
-
super(...arguments);
|
|
2926
|
-
this.name = "EntrySubmissionValidationError";
|
|
2927
|
-
}
|
|
2928
|
-
};
|
|
2929
|
-
var resolveEntryExecutionLimits = (config, direction) => {
|
|
2930
|
-
const side = config[direction];
|
|
2931
|
-
const minimum = side && typeof side === "object" && "minRiskRatio" in side ? side.minRiskRatio : void 0;
|
|
2932
|
-
const maximum = config.MAX_LOSS_VALUE;
|
|
2933
|
-
if (minimum == null && maximum == null) return void 0;
|
|
2934
|
-
const minimumNetRiskRatio = minimum == null ? void 0 : Number(minimum);
|
|
2935
|
-
const maxLossValue = maximum == null ? void 0 : Number(maximum);
|
|
2936
|
-
const feeRate = Number(config.RISK_FEE_RATE ?? 0);
|
|
2937
|
-
const slippage = Number(config.RISK_SLIPPAGE_BPS ?? 0);
|
|
2938
|
-
const impact = Number(config.RISK_MARKET_IMPACT_BPS ?? 0);
|
|
2939
|
-
const slippageBps = slippage + impact;
|
|
2940
|
-
if (minimumNetRiskRatio != null && (!Number.isFinite(minimumNetRiskRatio) || minimumNetRiskRatio < 0) || maxLossValue != null && (!Number.isFinite(maxLossValue) || maxLossValue <= 0) || !Number.isFinite(feeRate) || feeRate < 0 || feeRate >= 1 || !Number.isFinite(slippage) || slippage < 0 || !Number.isFinite(impact) || impact < 0 || !Number.isFinite(slippageBps) || slippageBps < 0 || slippageBps >= 1e4)
|
|
2941
|
-
throw new Error("INVALID_ENTRY_EXECUTION_LIMITS");
|
|
2942
|
-
return { minimumNetRiskRatio, maxLossValue, feeRate, slippageBps };
|
|
2943
|
-
};
|
|
2944
|
-
var validateEntryExecution = ({
|
|
2945
|
-
direction,
|
|
2946
|
-
price,
|
|
2947
|
-
qty,
|
|
2948
|
-
stopLossPrice,
|
|
2949
|
-
takeProfitPrice,
|
|
2950
|
-
limits
|
|
2951
|
-
}) => {
|
|
2952
|
-
if (!Number.isFinite(price) || price <= 0 || !Number.isFinite(qty) || qty <= 0 || stopLossPrice == null || !Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || !Number.isFinite(takeProfitPrice) || takeProfitPrice <= 0)
|
|
2953
|
-
throw new EntrySubmissionValidationError(
|
|
2954
|
-
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
2955
|
-
);
|
|
2956
|
-
const side = direction === "LONG" ? 1 : -1;
|
|
2957
|
-
if (side * (price - stopLossPrice) <= 0 || side * (takeProfitPrice - price) <= 0)
|
|
2958
|
-
throw new EntrySubmissionValidationError(
|
|
2959
|
-
"INVALID_ENTRY_PROTECTION_AT_SUBMISSION"
|
|
2960
|
-
);
|
|
2961
|
-
const slippage = limits.slippageBps / 1e4;
|
|
2962
|
-
const cost = (value, sign) => value * slippage + value * (1 + sign * slippage) * limits.feeRate;
|
|
2963
|
-
const entryCost = cost(price, side);
|
|
2964
|
-
const lossPerUnit = Math.abs(price - stopLossPrice) + entryCost + cost(stopLossPrice, -side);
|
|
2965
|
-
const rewardPerUnit = Math.max(
|
|
2966
|
-
0,
|
|
2967
|
-
Math.abs(takeProfitPrice - price) - entryCost - cost(takeProfitPrice, -side)
|
|
2968
|
-
);
|
|
2969
|
-
const netRiskRatio = rewardPerUnit / lossPerUnit;
|
|
2970
|
-
const plannedLossValue = qty * lossPerUnit;
|
|
2971
|
-
if (!Number.isFinite(netRiskRatio) || !Number.isFinite(plannedLossValue))
|
|
2972
|
-
throw new EntrySubmissionValidationError(
|
|
2973
|
-
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
2974
|
-
);
|
|
2975
|
-
if (limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio)
|
|
2976
|
-
throw new EntrySubmissionValidationError(
|
|
2977
|
-
"ENTRY_RR_BELOW_MIN_AT_SUBMISSION"
|
|
2978
|
-
);
|
|
2979
|
-
const riskExceeded = limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9);
|
|
2980
|
-
return { netRiskRatio, plannedLossValue, riskExceeded };
|
|
2981
|
-
};
|
|
2982
|
-
|
|
2983
|
-
// src/strategy/entrySubmissionValidation.ts
|
|
2984
|
-
var MAX_ENTRY_QUOTE_AGE_MS = 5e3;
|
|
2985
|
-
var toFiniteNumberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
2986
|
-
var getOrderArrivalSnapshot = async ({
|
|
2987
|
-
connector,
|
|
2988
|
-
symbol,
|
|
2989
|
-
requireTimestamp = false
|
|
2990
|
-
}) => {
|
|
2991
|
-
if (typeof connector.getTopOfBookTicker !== "function") {
|
|
2992
|
-
return {
|
|
2993
|
-
arrivalSnapshotTime: Date.now(),
|
|
2994
|
-
arrivalSource: "unavailable",
|
|
2995
|
-
bid: null,
|
|
2996
|
-
ask: null,
|
|
2997
|
-
arrivalMid: null,
|
|
2998
|
-
spreadBps: null
|
|
2999
|
-
};
|
|
3000
|
-
}
|
|
3001
|
-
try {
|
|
3002
|
-
const ticker = await connector.getTopOfBookTicker(symbol);
|
|
3003
|
-
const arrivalSnapshotTime = toFiniteNumberOrNull(ticker?.timestamp);
|
|
3004
|
-
const timestampUnavailable = requireTimestamp && arrivalSnapshotTime == null;
|
|
3005
|
-
const bid = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.bidPrice);
|
|
3006
|
-
const ask = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.askPrice);
|
|
3007
|
-
const arrivalMid = bid != null && ask != null ? (bid + ask) / 2 : null;
|
|
3008
|
-
const spreadBps = bid != null && ask != null && arrivalMid != null && arrivalMid > 0 ? (ask - bid) / arrivalMid * 1e4 : null;
|
|
3009
|
-
return {
|
|
3010
|
-
arrivalSnapshotTime: arrivalSnapshotTime ?? Date.now(),
|
|
3011
|
-
arrivalSource: "top_of_book",
|
|
3012
|
-
bid,
|
|
3013
|
-
ask,
|
|
3014
|
-
arrivalMid,
|
|
3015
|
-
spreadBps
|
|
3016
|
-
};
|
|
3017
|
-
} catch (error) {
|
|
3018
|
-
import_logger2.logger.warn(
|
|
3019
|
-
"runtime order arrival snapshot failed: %s %s",
|
|
3020
|
-
symbol,
|
|
3021
|
-
error?.message || String(error)
|
|
3022
|
-
);
|
|
3023
|
-
return {
|
|
3024
|
-
arrivalSnapshotTime: Date.now(),
|
|
3025
|
-
arrivalSource: "top_of_book_error",
|
|
3026
|
-
bid: null,
|
|
3027
|
-
ask: null,
|
|
3028
|
-
arrivalMid: null,
|
|
3029
|
-
spreadBps: null
|
|
3030
|
-
};
|
|
3031
|
-
}
|
|
3032
|
-
};
|
|
3033
|
-
var validateEntryProtectionAtArrival = ({
|
|
3034
|
-
direction,
|
|
3035
|
-
signalPrice,
|
|
3036
|
-
bid,
|
|
3037
|
-
ask,
|
|
3038
|
-
arrivalMid,
|
|
3039
|
-
takeProfits,
|
|
3040
|
-
stopLossPrice
|
|
3041
|
-
}) => {
|
|
3042
|
-
const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
|
|
3043
|
-
const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
|
|
3044
|
-
if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
|
|
3045
|
-
throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
|
|
3046
|
-
}
|
|
3047
|
-
for (const takeProfit of takeProfits) {
|
|
3048
|
-
if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
|
|
3049
|
-
throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
|
|
3050
|
-
}
|
|
3051
|
-
}
|
|
3052
|
-
if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
|
|
3053
|
-
throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
|
|
3054
|
-
}
|
|
3055
|
-
};
|
|
3056
|
-
var createEntrySubmissionValidation = ({
|
|
3057
|
-
connector,
|
|
3058
|
-
symbol,
|
|
3059
|
-
direction,
|
|
3060
|
-
takeProfits,
|
|
3061
|
-
stopLossPrice,
|
|
3062
|
-
limits,
|
|
3063
|
-
onValidated
|
|
3064
|
-
}) => {
|
|
3065
|
-
if (!limits) return void 0;
|
|
3066
|
-
if (!connector.capabilities?.orderSubmissionValidation)
|
|
3067
|
-
throw new EntrySubmissionValidationError(
|
|
3068
|
-
"ENTRY_SUBMISSION_VALIDATION_UNSUPPORTED"
|
|
3069
|
-
);
|
|
3070
|
-
return async ({ qty, executionPrice }) => {
|
|
3071
|
-
const simulated = Reflect.get(connector, "__tradejsTestConnector") === true;
|
|
3072
|
-
const snapshot2 = simulated && executionPrice != null ? {
|
|
3073
|
-
arrivalSnapshotTime: Date.now(),
|
|
3074
|
-
arrivalSource: "simulation",
|
|
3075
|
-
bid: executionPrice,
|
|
3076
|
-
ask: executionPrice,
|
|
3077
|
-
arrivalMid: executionPrice,
|
|
3078
|
-
spreadBps: 0
|
|
3079
|
-
} : await getOrderArrivalSnapshot({
|
|
3080
|
-
connector,
|
|
3081
|
-
symbol,
|
|
3082
|
-
requireTimestamp: true
|
|
3083
|
-
});
|
|
3084
|
-
const age = Date.now() - snapshot2.arrivalSnapshotTime;
|
|
3085
|
-
if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
|
|
3086
|
-
throw new EntrySubmissionValidationError(
|
|
3087
|
-
"ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
|
|
3088
|
-
);
|
|
3089
|
-
if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
|
|
3090
|
-
throw new EntrySubmissionValidationError(
|
|
3091
|
-
"ENTRY_QUOTE_STALE_AT_SUBMISSION"
|
|
3092
|
-
);
|
|
3093
|
-
const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
|
|
3094
|
-
validateEntryProtectionAtArrival({
|
|
3095
|
-
direction,
|
|
3096
|
-
signalPrice: price,
|
|
3097
|
-
bid: snapshot2.bid,
|
|
3098
|
-
ask: snapshot2.ask,
|
|
3099
|
-
arrivalMid: snapshot2.arrivalMid,
|
|
3100
|
-
takeProfits,
|
|
3101
|
-
stopLossPrice
|
|
3102
|
-
});
|
|
3103
|
-
const prices = takeProfits.map((tp) => tp.price);
|
|
3104
|
-
const economics2 = validateEntryExecution({
|
|
3105
|
-
direction,
|
|
3106
|
-
price,
|
|
3107
|
-
qty,
|
|
3108
|
-
stopLossPrice,
|
|
3109
|
-
takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
|
|
3110
|
-
limits
|
|
3111
|
-
});
|
|
3112
|
-
onValidated?.(snapshot2, economics2);
|
|
3113
|
-
};
|
|
3114
|
-
};
|
|
3115
|
-
|
|
3116
|
-
// src/strategyAdapters/ml.ts
|
|
3117
|
-
var defaultMlAdapter = {
|
|
3118
|
-
normalizeStrategyConfig: (strategyConfig) => strategyConfig
|
|
3119
|
-
};
|
|
3120
|
-
var getStrategyMlAdapter = (strategy, profileId) => {
|
|
3121
|
-
const strategyAdapter = getStrategyProfileMlAdapter(
|
|
3122
|
-
getStrategyManifest(strategy),
|
|
3123
|
-
profileId
|
|
2702
|
+
var askAI = async (signal, options = {}) => {
|
|
2703
|
+
const { symbol } = signal;
|
|
2704
|
+
const payload = buildAiPayload(signal);
|
|
2705
|
+
const content = await runAiPrompt(
|
|
2706
|
+
{
|
|
2707
|
+
systemPrompt: buildAiSystemPrompt(signal),
|
|
2708
|
+
humanPrompt: buildAiHumanPrompt(signal, payload)
|
|
2709
|
+
},
|
|
2710
|
+
{
|
|
2711
|
+
...options,
|
|
2712
|
+
signal,
|
|
2713
|
+
payload
|
|
2714
|
+
}
|
|
3124
2715
|
);
|
|
3125
|
-
|
|
3126
|
-
return
|
|
3127
|
-
...defaultMlAdapter,
|
|
3128
|
-
...strategyAdapter
|
|
3129
|
-
};
|
|
2716
|
+
await (0, import_redis.setData)(import_redis.redisKeys.analysis(symbol, signal.signalId), content);
|
|
2717
|
+
return content;
|
|
3130
2718
|
};
|
|
3131
2719
|
|
|
3132
|
-
// src/
|
|
3133
|
-
var
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
2720
|
+
// src/strategy/portfolioRuntime.ts
|
|
2721
|
+
var import_node_crypto2 = require("crypto");
|
|
2722
|
+
var import_portfolio2 = require("@tradejs/core/portfolio");
|
|
2723
|
+
var import_redis3 = require("@tradejs/infra/redis");
|
|
2724
|
+
|
|
2725
|
+
// src/portfolio.ts
|
|
2726
|
+
var import_portfolio = require("@tradejs/core/portfolio");
|
|
2727
|
+
function createPortfolioCoordinator({
|
|
2728
|
+
policy,
|
|
2729
|
+
strategy,
|
|
2730
|
+
store,
|
|
2731
|
+
execution
|
|
2732
|
+
}) {
|
|
2733
|
+
const fingerprint3 = (0, import_portfolio.portfolioFingerprint)(policy, strategy);
|
|
2734
|
+
let cache;
|
|
2735
|
+
async function transact(now2, change, force = false) {
|
|
2736
|
+
for (let attempt = 0; attempt < 16; attempt++) {
|
|
2737
|
+
const previous = await store.read();
|
|
2738
|
+
if (previous && previous.fingerprint !== fingerprint3)
|
|
2739
|
+
throw new Error(
|
|
2740
|
+
"Portfolio policy changed under existing state; reconcile the persisted scope before changing configuration"
|
|
2741
|
+
);
|
|
2742
|
+
const base = previous ?? (0, import_portfolio.createPortfolioState)(fingerprint3);
|
|
2743
|
+
const bucket = Math.floor(now2 / policy.observationIntervalMs);
|
|
2744
|
+
const observation = !force && cache?.revision === base.revision && cache.bucket === bucket ? cache.observation : await execution.observe(now2, base.reservations);
|
|
2745
|
+
const reconciled = (0, import_portfolio.reconcilePortfolio)(
|
|
2746
|
+
policy,
|
|
2747
|
+
strategy,
|
|
2748
|
+
base,
|
|
2749
|
+
observation
|
|
2750
|
+
);
|
|
2751
|
+
for (const intent of base.closing) {
|
|
2752
|
+
if (!observation.positions.some(
|
|
2753
|
+
(p) => p.symbol === intent.position.symbol
|
|
2754
|
+
))
|
|
2755
|
+
await execution.confirmClosed?.(intent, observation.asOf);
|
|
2756
|
+
}
|
|
2757
|
+
const changed = change(reconciled, observation);
|
|
2758
|
+
const next = { ...changed.state, revision: base.revision + 1 };
|
|
2759
|
+
if (await store.compare(previous, next)) {
|
|
2760
|
+
cache = { revision: next.revision, bucket, observation };
|
|
2761
|
+
return { state: next, observation, result: changed.result };
|
|
2762
|
+
}
|
|
2763
|
+
cache = void 0;
|
|
2764
|
+
}
|
|
2765
|
+
throw new Error("Portfolio state contention; entry was not submitted");
|
|
2766
|
+
}
|
|
2767
|
+
async function tick(now2, force = false) {
|
|
2768
|
+
const claimed = await transact(
|
|
2769
|
+
now2,
|
|
2770
|
+
(state, observation) => {
|
|
2771
|
+
const commands = (0, import_portfolio.portfolioCloseCommands)(policy, state, observation);
|
|
2772
|
+
return {
|
|
2773
|
+
state: {
|
|
2774
|
+
...state,
|
|
2775
|
+
closing: state.closing.map(
|
|
2776
|
+
(c) => commands.some((x) => x.intent.positionId === c.positionId) ? { ...c, lastAttemptAt: observation.asOf } : c
|
|
2777
|
+
)
|
|
2778
|
+
},
|
|
2779
|
+
result: commands
|
|
2780
|
+
};
|
|
2781
|
+
},
|
|
2782
|
+
force
|
|
2783
|
+
);
|
|
2784
|
+
for (const command of claimed.result)
|
|
2785
|
+
await execution.close(
|
|
2786
|
+
command.position,
|
|
2787
|
+
command.intent,
|
|
2788
|
+
claimed.observation.asOf
|
|
2789
|
+
);
|
|
2790
|
+
if (claimed.result.length) {
|
|
2791
|
+
cache = void 0;
|
|
2792
|
+
const refreshed = await transact(
|
|
2793
|
+
now2,
|
|
2794
|
+
(state) => ({ state, result: void 0 }),
|
|
2795
|
+
true
|
|
2796
|
+
);
|
|
2797
|
+
return (0, import_portfolio.buildPortfolioContext)(
|
|
2798
|
+
policy,
|
|
2799
|
+
strategy,
|
|
2800
|
+
refreshed.state,
|
|
2801
|
+
refreshed.observation
|
|
2802
|
+
);
|
|
2803
|
+
}
|
|
2804
|
+
return (0, import_portfolio.buildPortfolioContext)(
|
|
2805
|
+
policy,
|
|
2806
|
+
strategy,
|
|
2807
|
+
claimed.state,
|
|
2808
|
+
claimed.observation
|
|
2809
|
+
);
|
|
2810
|
+
}
|
|
2811
|
+
async function updateReservation(now2, id, submitted, qty) {
|
|
2812
|
+
return transact(
|
|
2813
|
+
now2,
|
|
2814
|
+
(s, o) => {
|
|
2815
|
+
const exists = s.reservations.some((r) => r.id === id);
|
|
2816
|
+
const blocked = submitted && (!exists || s.closing.length > 0 || o.asOf <= s.blockedThrough);
|
|
2817
|
+
return {
|
|
2818
|
+
state: {
|
|
2819
|
+
...s,
|
|
2820
|
+
reservations: s.reservations.flatMap(
|
|
2821
|
+
(r) => r.id !== id ? [r] : submitted && !blocked ? [{ ...r, submitted: true, qty: qty ?? r.qty }] : []
|
|
2822
|
+
)
|
|
2823
|
+
},
|
|
2824
|
+
result: !blocked
|
|
2825
|
+
};
|
|
2826
|
+
},
|
|
2827
|
+
true
|
|
2828
|
+
);
|
|
2829
|
+
}
|
|
2830
|
+
return {
|
|
2831
|
+
async context(now2) {
|
|
2832
|
+
const state = await store.read() ?? (0, import_portfolio.createPortfolioState)(fingerprint3);
|
|
2833
|
+
if (state.fingerprint !== fingerprint3)
|
|
2834
|
+
throw new Error("Portfolio policy changed under existing state");
|
|
2835
|
+
const observation = await execution.observe(now2, state.reservations);
|
|
2836
|
+
(0, import_portfolio.validatePortfolioObservation)(observation);
|
|
2837
|
+
return (0, import_portfolio.buildPortfolioContext)(policy, strategy, state, observation);
|
|
2838
|
+
},
|
|
2839
|
+
tick,
|
|
2840
|
+
async submit(now2, entry, send) {
|
|
2841
|
+
await tick(now2);
|
|
2842
|
+
const admitted = await transact(
|
|
2843
|
+
now2,
|
|
2844
|
+
(s, o) => {
|
|
2845
|
+
const r = (0, import_portfolio.reservePortfolioEntry)(policy, strategy, s, o, {
|
|
2846
|
+
...entry,
|
|
2847
|
+
submitted: false,
|
|
2848
|
+
oppositeHandled: false
|
|
2849
|
+
});
|
|
2850
|
+
return { state: r.state, result: r.reason };
|
|
2851
|
+
},
|
|
2852
|
+
true
|
|
2853
|
+
);
|
|
2854
|
+
if (admitted.result) return { reason: admitted.result };
|
|
2855
|
+
let submitted = false;
|
|
2856
|
+
try {
|
|
2857
|
+
const value = await send(
|
|
2858
|
+
async (qty) => {
|
|
2859
|
+
if (!Number.isFinite(qty) || qty <= 0)
|
|
2860
|
+
throw new Error("Invalid submitted portfolio quantity");
|
|
2861
|
+
submitted = true;
|
|
2862
|
+
const update = await updateReservation(now2, entry.id, true, qty);
|
|
2863
|
+
if (!update.result)
|
|
2864
|
+
throw new Error("PORTFOLIO_SUBMISSION_NO_LONGER_ADMITTED");
|
|
2865
|
+
},
|
|
2866
|
+
async () => {
|
|
2867
|
+
await transact(
|
|
2868
|
+
now2,
|
|
2869
|
+
(s) => ({
|
|
2870
|
+
state: {
|
|
2871
|
+
...s,
|
|
2872
|
+
reservations: s.reservations.filter((r) => r.id !== entry.id),
|
|
2873
|
+
completedSignals: {
|
|
2874
|
+
...s.completedSignals,
|
|
2875
|
+
[entry.signalId]: now2
|
|
2876
|
+
}
|
|
2877
|
+
},
|
|
2878
|
+
result: void 0
|
|
2879
|
+
}),
|
|
2880
|
+
true
|
|
2881
|
+
);
|
|
2882
|
+
}
|
|
2883
|
+
);
|
|
2884
|
+
if (!submitted) await updateReservation(now2, entry.id, false);
|
|
2885
|
+
else await tick(now2, true);
|
|
2886
|
+
return { value };
|
|
2887
|
+
} catch (error) {
|
|
2888
|
+
if (!submitted) await updateReservation(now2, entry.id, false);
|
|
2889
|
+
throw error;
|
|
2890
|
+
}
|
|
3148
2891
|
}
|
|
3149
2892
|
};
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
payload.context.strategyConfig,
|
|
3154
|
-
strategyName,
|
|
3155
|
-
profileId
|
|
3156
|
-
)
|
|
3157
|
-
} : void 0;
|
|
2893
|
+
}
|
|
2894
|
+
function createMemoryPortfolioStore() {
|
|
2895
|
+
let state = null;
|
|
3158
2896
|
return {
|
|
3159
|
-
|
|
3160
|
-
|
|
2897
|
+
read: async () => state === null ? null : structuredClone(state),
|
|
2898
|
+
compare: async (expected, next) => {
|
|
2899
|
+
if ((state?.revision ?? -1) !== (expected?.revision ?? -1)) return false;
|
|
2900
|
+
state = structuredClone(next);
|
|
2901
|
+
return true;
|
|
2902
|
+
}
|
|
3161
2903
|
};
|
|
3162
|
-
}
|
|
2904
|
+
}
|
|
3163
2905
|
|
|
3164
2906
|
// src/runtimeJournal.ts
|
|
3165
2907
|
var import_node_crypto = require("crypto");
|
|
3166
2908
|
var import_constants = require("@tradejs/core/constants");
|
|
3167
2909
|
var import_time = require("@tradejs/core/time");
|
|
3168
2910
|
var import_trade = require("@tradejs/core/trade");
|
|
3169
|
-
var
|
|
2911
|
+
var import_logger2 = require("@tradejs/infra/logger");
|
|
3170
2912
|
var import_redis2 = require("@tradejs/infra/redis");
|
|
3171
2913
|
var now = () => Date.now();
|
|
3172
2914
|
var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
|
|
@@ -3218,195 +2960,988 @@ var recordRuntimeTradeOpen = async (params) => {
|
|
|
3218
2960
|
record,
|
|
3219
2961
|
{ expire: 0 }
|
|
3220
2962
|
),
|
|
3221
|
-
(0, import_redis2.setData)(
|
|
3222
|
-
import_redis2.redisKeys.runtimeActiveTrade(userName, record.symbol, runtimeScopeId),
|
|
3223
|
-
{ orderId: record.orderId },
|
|
3224
|
-
{ expire: 0 }
|
|
2963
|
+
(0, import_redis2.setData)(
|
|
2964
|
+
import_redis2.redisKeys.runtimeActiveTrade(userName, record.symbol, runtimeScopeId),
|
|
2965
|
+
{ orderId: record.orderId },
|
|
2966
|
+
{ expire: 0 }
|
|
2967
|
+
)
|
|
2968
|
+
]);
|
|
2969
|
+
} catch (error) {
|
|
2970
|
+
import_logger2.logger.error(
|
|
2971
|
+
"runtime trade open journal failed: %s %s",
|
|
2972
|
+
record.symbol,
|
|
2973
|
+
error?.message || String(error)
|
|
2974
|
+
);
|
|
2975
|
+
}
|
|
2976
|
+
return record;
|
|
2977
|
+
};
|
|
2978
|
+
var recordRuntimeTradeIncrease = async (params) => {
|
|
2979
|
+
const {
|
|
2980
|
+
userName,
|
|
2981
|
+
strategy,
|
|
2982
|
+
symbol,
|
|
2983
|
+
direction,
|
|
2984
|
+
resultingQty,
|
|
2985
|
+
resultingEntryPrice,
|
|
2986
|
+
addedQty,
|
|
2987
|
+
addedEntryPrice,
|
|
2988
|
+
entryTimestamp,
|
|
2989
|
+
fee,
|
|
2990
|
+
accountId,
|
|
2991
|
+
deploymentId
|
|
2992
|
+
} = params;
|
|
2993
|
+
if (!userName) {
|
|
2994
|
+
return null;
|
|
2995
|
+
}
|
|
2996
|
+
const existing = await getActiveRuntimeTrade({
|
|
2997
|
+
userName,
|
|
2998
|
+
symbol,
|
|
2999
|
+
accountId,
|
|
3000
|
+
deploymentId
|
|
3001
|
+
});
|
|
3002
|
+
if (!existing || existing.strategy !== strategy || existing.direction !== direction) {
|
|
3003
|
+
return null;
|
|
3004
|
+
}
|
|
3005
|
+
const addedFee = typeof fee === "number" && Number.isFinite(fee) ? fee : 0;
|
|
3006
|
+
const openFee = (existing.openFee ?? existing.fee ?? 0) + addedFee;
|
|
3007
|
+
const totalFee = (existing.totalFee ?? existing.fee ?? 0) + addedFee;
|
|
3008
|
+
const next = {
|
|
3009
|
+
...existing,
|
|
3010
|
+
...params.orderLeverage ? { orderLeverage: params.orderLeverage } : {},
|
|
3011
|
+
qty: resultingQty,
|
|
3012
|
+
entryPrice: resultingEntryPrice,
|
|
3013
|
+
entryCount: Math.max(1, existing.entryCount ?? 1) + 1,
|
|
3014
|
+
lastEntryPrice: addedEntryPrice,
|
|
3015
|
+
lastEntryQty: addedQty,
|
|
3016
|
+
lastEntryTimestamp: entryTimestamp,
|
|
3017
|
+
currentPrice: resultingEntryPrice,
|
|
3018
|
+
currentPnl: 0,
|
|
3019
|
+
fee: openFee,
|
|
3020
|
+
openFee,
|
|
3021
|
+
totalFee,
|
|
3022
|
+
lastSyncedAt: now()
|
|
3023
|
+
};
|
|
3024
|
+
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
3025
|
+
try {
|
|
3026
|
+
await Promise.all([
|
|
3027
|
+
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, existing.orderId), next, {
|
|
3028
|
+
expire: 0
|
|
3029
|
+
}),
|
|
3030
|
+
(0, import_redis2.setHashJsonField)(
|
|
3031
|
+
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
3032
|
+
existing.orderId,
|
|
3033
|
+
next,
|
|
3034
|
+
{ expire: 0 }
|
|
3035
|
+
)
|
|
3036
|
+
]);
|
|
3037
|
+
} catch (error) {
|
|
3038
|
+
import_logger2.logger.error(
|
|
3039
|
+
"runtime trade increase journal failed: %s %s",
|
|
3040
|
+
symbol,
|
|
3041
|
+
error?.message || String(error)
|
|
3042
|
+
);
|
|
3043
|
+
}
|
|
3044
|
+
return next;
|
|
3045
|
+
};
|
|
3046
|
+
var getActiveRuntimeTrade = async (params) => {
|
|
3047
|
+
const { userName, symbol, accountId, deploymentId } = params;
|
|
3048
|
+
if (!userName) {
|
|
3049
|
+
return null;
|
|
3050
|
+
}
|
|
3051
|
+
const activeRef = await (0, import_redis2.getData)(
|
|
3052
|
+
import_redis2.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId),
|
|
3053
|
+
null
|
|
3054
|
+
);
|
|
3055
|
+
const orderId = String(activeRef?.orderId || "").trim();
|
|
3056
|
+
if (!orderId) {
|
|
3057
|
+
return null;
|
|
3058
|
+
}
|
|
3059
|
+
const existing = await (0, import_redis2.getData)(
|
|
3060
|
+
import_redis2.redisKeys.runtimeTrade(userName, orderId),
|
|
3061
|
+
null
|
|
3062
|
+
);
|
|
3063
|
+
if (!existing || existing.status !== "active") {
|
|
3064
|
+
await (0, import_redis2.delKey)(
|
|
3065
|
+
import_redis2.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
|
|
3066
|
+
);
|
|
3067
|
+
return null;
|
|
3068
|
+
}
|
|
3069
|
+
return existing;
|
|
3070
|
+
};
|
|
3071
|
+
var markRuntimeTradeClosed = async (params) => {
|
|
3072
|
+
const {
|
|
3073
|
+
userName,
|
|
3074
|
+
expectedOrderId,
|
|
3075
|
+
symbol,
|
|
3076
|
+
strategy,
|
|
3077
|
+
exitPrice,
|
|
3078
|
+
exitTimestamp,
|
|
3079
|
+
closedPnl,
|
|
3080
|
+
exitType,
|
|
3081
|
+
accountId,
|
|
3082
|
+
deploymentId
|
|
3083
|
+
} = params;
|
|
3084
|
+
if (!userName) {
|
|
3085
|
+
return null;
|
|
3086
|
+
}
|
|
3087
|
+
const existing = expectedOrderId ? await (0, import_redis2.getDataStrict)(
|
|
3088
|
+
import_redis2.redisKeys.runtimeTrade(userName, expectedOrderId)
|
|
3089
|
+
) : await getActiveRuntimeTrade({
|
|
3090
|
+
userName,
|
|
3091
|
+
symbol,
|
|
3092
|
+
accountId,
|
|
3093
|
+
deploymentId
|
|
3094
|
+
});
|
|
3095
|
+
if (!existing || expectedOrderId && existing.orderId !== expectedOrderId) {
|
|
3096
|
+
return null;
|
|
3097
|
+
}
|
|
3098
|
+
const orderId = existing.orderId;
|
|
3099
|
+
if (strategy && existing.strategy !== strategy) {
|
|
3100
|
+
return null;
|
|
3101
|
+
}
|
|
3102
|
+
const resolvedExitPrice = typeof exitPrice === "number" && Number.isFinite(exitPrice) ? exitPrice : existing.currentPrice ?? existing.entryPrice;
|
|
3103
|
+
const resolvedClosedPnl = typeof closedPnl === "number" && Number.isFinite(closedPnl) ? closedPnl : typeof resolvedExitPrice === "number" && Number.isFinite(resolvedExitPrice) ? calculateClosedPnl({
|
|
3104
|
+
direction: existing.direction,
|
|
3105
|
+
entryPrice: existing.entryPrice,
|
|
3106
|
+
exitPrice: resolvedExitPrice,
|
|
3107
|
+
qty: existing.qty
|
|
3108
|
+
}) : existing.closedPnl ?? existing.currentPnl ?? null;
|
|
3109
|
+
const next = expectedOrderId && existing.status === "closed" ? existing : {
|
|
3110
|
+
...existing,
|
|
3111
|
+
status: "closed",
|
|
3112
|
+
currentPrice: resolvedExitPrice,
|
|
3113
|
+
currentPnl: resolvedClosedPnl,
|
|
3114
|
+
closedPnl: resolvedClosedPnl,
|
|
3115
|
+
exitPrice: resolvedExitPrice,
|
|
3116
|
+
exitTimestamp: typeof exitTimestamp === "number" && Number.isFinite(exitTimestamp) ? exitTimestamp : now(),
|
|
3117
|
+
exitType: exitType ?? existing.exitType ?? null,
|
|
3118
|
+
lastSyncedAt: now()
|
|
3119
|
+
};
|
|
3120
|
+
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
3121
|
+
const closeDayKey = (0, import_time.getRuntimeStorageDayKey)(next.exitTimestamp);
|
|
3122
|
+
try {
|
|
3123
|
+
await Promise.all([
|
|
3124
|
+
(expectedOrderId ? import_redis2.setDataStrict : import_redis2.setData)(
|
|
3125
|
+
import_redis2.redisKeys.runtimeTrade(userName, orderId),
|
|
3126
|
+
next,
|
|
3127
|
+
{
|
|
3128
|
+
expire: import_constants.TTL_1M
|
|
3129
|
+
}
|
|
3130
|
+
),
|
|
3131
|
+
(expectedOrderId ? import_redis2.setHashJsonFieldStrict : import_redis2.setHashJsonField)(
|
|
3132
|
+
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
3133
|
+
orderId,
|
|
3134
|
+
next,
|
|
3135
|
+
{ expire: import_constants.TTL_1M }
|
|
3136
|
+
),
|
|
3137
|
+
(expectedOrderId ? import_redis2.setHashJsonFieldStrict : import_redis2.setHashJsonField)(
|
|
3138
|
+
import_redis2.redisKeys.runtimeClosedTradeBucket(userName, closeDayKey),
|
|
3139
|
+
orderId,
|
|
3140
|
+
next,
|
|
3141
|
+
{ expire: import_constants.TTL_1M }
|
|
3142
|
+
),
|
|
3143
|
+
expectedOrderId ? (0, import_redis2.deleteRuntimeTradeReference)(
|
|
3144
|
+
import_redis2.redisKeys.runtimeActiveTrade(
|
|
3145
|
+
userName,
|
|
3146
|
+
symbol,
|
|
3147
|
+
deploymentId ?? accountId
|
|
3148
|
+
),
|
|
3149
|
+
expectedOrderId
|
|
3150
|
+
) : (0, import_redis2.delKey)(
|
|
3151
|
+
import_redis2.redisKeys.runtimeActiveTrade(
|
|
3152
|
+
userName,
|
|
3153
|
+
symbol,
|
|
3154
|
+
deploymentId ?? accountId
|
|
3155
|
+
)
|
|
3225
3156
|
)
|
|
3226
3157
|
]);
|
|
3227
3158
|
} catch (error) {
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3159
|
+
if (expectedOrderId) throw error;
|
|
3160
|
+
import_logger2.logger.error(
|
|
3161
|
+
"runtime trade close journal failed: %s %s",
|
|
3162
|
+
symbol,
|
|
3231
3163
|
error?.message || String(error)
|
|
3232
3164
|
);
|
|
3233
3165
|
}
|
|
3234
|
-
return
|
|
3166
|
+
return next;
|
|
3235
3167
|
};
|
|
3236
|
-
|
|
3237
|
-
|
|
3168
|
+
|
|
3169
|
+
// src/strategy/portfolioRuntime.ts
|
|
3170
|
+
var coordinators = /* @__PURE__ */ new WeakMap();
|
|
3171
|
+
async function observeRuntimePortfolio(connector, binding, asOf, reservations, observedNow = Date.now) {
|
|
3172
|
+
if (!connector.getOpenPositionPnl || !connector.getEntryExecutions)
|
|
3173
|
+
throw new Error(
|
|
3174
|
+
"Portfolio policies require position marks and entry executions"
|
|
3175
|
+
);
|
|
3176
|
+
const orders = await Promise.all(
|
|
3177
|
+
reservations.map(async (r) => {
|
|
3178
|
+
if (r.submitted && connector.getEntryOrderObservation) {
|
|
3179
|
+
const fact2 = await connector.getEntryOrderObservation({
|
|
3180
|
+
symbol: r.symbol,
|
|
3181
|
+
orderId: r.id
|
|
3182
|
+
});
|
|
3183
|
+
if (fact2) return fact2;
|
|
3184
|
+
}
|
|
3185
|
+
const executions = r.submitted ? await connector.getEntryExecutions({
|
|
3186
|
+
symbol: r.symbol,
|
|
3187
|
+
startTime: r.timestamp,
|
|
3188
|
+
endTime: asOf
|
|
3189
|
+
}) : [];
|
|
3190
|
+
const filledQty = executions.filter(
|
|
3191
|
+
(e) => (e.orderLinkId === r.id || e.orderId === r.id) && e.direction === r.direction
|
|
3192
|
+
).reduce((sum, e) => sum + e.qty, 0);
|
|
3193
|
+
return {
|
|
3194
|
+
id: r.id,
|
|
3195
|
+
status: filledQty >= r.qty ? "filled" : filledQty > 0 ? "partial" : "pending",
|
|
3196
|
+
filledQty,
|
|
3197
|
+
positionId: r.id
|
|
3198
|
+
};
|
|
3199
|
+
})
|
|
3200
|
+
);
|
|
3201
|
+
const snapshots2 = await connector.getOpenPositionPnl();
|
|
3202
|
+
const positions = await Promise.all(
|
|
3203
|
+
snapshots2.map(async (p) => {
|
|
3204
|
+
const reserved = reservations.find(
|
|
3205
|
+
(r) => r.symbol === p.symbol && r.direction === p.direction && orders.some(
|
|
3206
|
+
(o) => o.id === r.id && o.filledQty > 0 && p.qty <= o.filledQty * (1 + 1e-8)
|
|
3207
|
+
)
|
|
3208
|
+
);
|
|
3209
|
+
const trade = await getActiveRuntimeTrade({
|
|
3210
|
+
...binding,
|
|
3211
|
+
symbol: p.symbol
|
|
3212
|
+
});
|
|
3213
|
+
const owner = trade && trade.direction === p.direction && p.qty <= trade.qty * (1 + 1e-8) && Math.abs(p.price - (trade.actualEntryPrice ?? trade.entryPrice)) <= p.price * 1e-8 ? trade : null;
|
|
3214
|
+
return {
|
|
3215
|
+
id: reserved?.id ?? owner?.orderId ?? `unknown:${p.symbol}:${p.direction}`,
|
|
3216
|
+
strategy: reserved?.strategy ?? owner?.strategy ?? null,
|
|
3217
|
+
symbol: p.symbol,
|
|
3218
|
+
direction: p.direction,
|
|
3219
|
+
qty: p.qty,
|
|
3220
|
+
entryPrice: p.price,
|
|
3221
|
+
markPrice: p.currentPrice,
|
|
3222
|
+
markedAt: p.observedAt ?? null,
|
|
3223
|
+
stopLossPrice: p.slPrice ?? p.stopLossPrice ?? null
|
|
3224
|
+
};
|
|
3225
|
+
})
|
|
3226
|
+
);
|
|
3227
|
+
return { asOf: Math.max(asOf, observedNow()), positions, orders };
|
|
3228
|
+
}
|
|
3229
|
+
function persistentStore(key) {
|
|
3230
|
+
const raw = /* @__PURE__ */ new WeakMap();
|
|
3231
|
+
return {
|
|
3232
|
+
async read() {
|
|
3233
|
+
const value = await import_redis3.portfolioStateStorage.read(key);
|
|
3234
|
+
if (value === null) return null;
|
|
3235
|
+
const state = JSON.parse(value);
|
|
3236
|
+
(0, import_portfolio2.assertPortfolioState)(state);
|
|
3237
|
+
raw.set(state, value);
|
|
3238
|
+
return state;
|
|
3239
|
+
},
|
|
3240
|
+
async compare(previous, next) {
|
|
3241
|
+
const expected = previous === null ? null : raw.get(previous);
|
|
3242
|
+
if (expected === void 0)
|
|
3243
|
+
throw new Error("Portfolio CAS requires a storage snapshot");
|
|
3244
|
+
return import_redis3.portfolioStateStorage.compare(key, expected, JSON.stringify(next));
|
|
3245
|
+
}
|
|
3246
|
+
};
|
|
3247
|
+
}
|
|
3248
|
+
function getPortfolioRuntime({
|
|
3249
|
+
value,
|
|
3250
|
+
connectorName,
|
|
3251
|
+
strategy,
|
|
3252
|
+
connector,
|
|
3253
|
+
env,
|
|
3254
|
+
userName,
|
|
3255
|
+
accountId,
|
|
3256
|
+
deploymentId
|
|
3257
|
+
}) {
|
|
3258
|
+
const policy = (0, import_portfolio2.parsePortfolioPolicy)(value);
|
|
3259
|
+
if (!policy) return void 0;
|
|
3260
|
+
const replay = (env === "BACKTEST" || env === "PARITY") && Reflect.get(connector, "__tradejsReplayConnector") === true;
|
|
3261
|
+
if ((env === "BACKTEST" || env === "PARITY") && (!replay || !connector.getPortfolioObservation))
|
|
3262
|
+
throw new Error(
|
|
3263
|
+
"Portfolio policies require shared portfolio replay; isolated symbol backtests are unsupported"
|
|
3264
|
+
);
|
|
3265
|
+
if (!replay && (!userName || !accountId || !deploymentId || !connector.getOpenPositionPnl || !connector.getEntryExecutions))
|
|
3266
|
+
throw new Error(
|
|
3267
|
+
"Portfolio runtime requires exact user/account/deployment binding, marks and executions"
|
|
3268
|
+
);
|
|
3269
|
+
const binding = {
|
|
3238
3270
|
userName,
|
|
3239
|
-
strategy,
|
|
3240
|
-
symbol,
|
|
3241
|
-
direction,
|
|
3242
|
-
resultingQty,
|
|
3243
|
-
resultingEntryPrice,
|
|
3244
|
-
addedQty,
|
|
3245
|
-
addedEntryPrice,
|
|
3246
|
-
entryTimestamp,
|
|
3247
|
-
fee,
|
|
3248
3271
|
accountId,
|
|
3249
3272
|
deploymentId
|
|
3250
|
-
}
|
|
3251
|
-
|
|
3252
|
-
|
|
3273
|
+
};
|
|
3274
|
+
const scope = policy.scope === "strategy" ? strategy : policy.scope === "account" ? "account" : [...policy.scope.strategies].sort();
|
|
3275
|
+
const key = (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify([binding, connectorName, connector.universe, scope])).digest("hex");
|
|
3276
|
+
const fingerprint3 = (0, import_portfolio2.portfolioFingerprint)(policy, strategy);
|
|
3277
|
+
const cacheKey = key;
|
|
3278
|
+
let registry = coordinators.get(connector);
|
|
3279
|
+
if (!registry) {
|
|
3280
|
+
registry = /* @__PURE__ */ new Map();
|
|
3281
|
+
coordinators.set(connector, registry);
|
|
3282
|
+
}
|
|
3283
|
+
const existing = registry.get(cacheKey);
|
|
3284
|
+
if (existing) {
|
|
3285
|
+
if (existing.fingerprint !== fingerprint3)
|
|
3286
|
+
throw new Error("Conflicting portfolio configuration for the same scope");
|
|
3287
|
+
return existing.coordinator;
|
|
3288
|
+
}
|
|
3289
|
+
const coordinator = createPortfolioCoordinator({
|
|
3290
|
+
policy,
|
|
3291
|
+
strategy,
|
|
3292
|
+
store: replay ? createMemoryPortfolioStore() : persistentStore(key),
|
|
3293
|
+
execution: {
|
|
3294
|
+
async confirmClosed(intent, asOf) {
|
|
3295
|
+
if (replay) return;
|
|
3296
|
+
const position = intent.position;
|
|
3297
|
+
await markRuntimeTradeClosed({
|
|
3298
|
+
...binding,
|
|
3299
|
+
strategy: position.strategy,
|
|
3300
|
+
expectedOrderId: position.id,
|
|
3301
|
+
symbol: position.symbol,
|
|
3302
|
+
exitPrice: position.markPrice,
|
|
3303
|
+
exitTimestamp: asOf,
|
|
3304
|
+
exitType: "exit"
|
|
3305
|
+
});
|
|
3306
|
+
},
|
|
3307
|
+
observe: (asOf, reservations) => replay ? connector.getPortfolioObservation(asOf, reservations) : observeRuntimePortfolio(connector, binding, asOf, reservations),
|
|
3308
|
+
async close(position, intent, asOf) {
|
|
3309
|
+
if (!replay) {
|
|
3310
|
+
const trade = await getActiveRuntimeTrade({
|
|
3311
|
+
...binding,
|
|
3312
|
+
symbol: position.symbol
|
|
3313
|
+
});
|
|
3314
|
+
if (!trade || trade.orderId !== position.id)
|
|
3315
|
+
throw new Error("Portfolio close ownership changed");
|
|
3316
|
+
}
|
|
3317
|
+
await connector.closePosition({
|
|
3318
|
+
symbol: position.symbol,
|
|
3319
|
+
direction: position.direction,
|
|
3320
|
+
price: position.markPrice,
|
|
3321
|
+
timestamp: asOf,
|
|
3322
|
+
signal: {
|
|
3323
|
+
signalId: `${position.id}:${intent.reason}`,
|
|
3324
|
+
strategy: position.strategy,
|
|
3325
|
+
symbol: position.symbol,
|
|
3326
|
+
direction: position.direction,
|
|
3327
|
+
interval: "1",
|
|
3328
|
+
timestamp: asOf,
|
|
3329
|
+
prices: {
|
|
3330
|
+
riskRatio: 0,
|
|
3331
|
+
currentPrice: position.markPrice,
|
|
3332
|
+
takeProfitPrice: position.markPrice,
|
|
3333
|
+
stopLossPrice: position.markPrice
|
|
3334
|
+
},
|
|
3335
|
+
figures: {},
|
|
3336
|
+
indicators: {},
|
|
3337
|
+
additionalIndicators: { portfolioExitReason: intent.reason }
|
|
3338
|
+
}
|
|
3339
|
+
});
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
});
|
|
3343
|
+
registry.set(cacheKey, { fingerprint: fingerprint3, coordinator });
|
|
3344
|
+
return coordinator;
|
|
3345
|
+
}
|
|
3346
|
+
|
|
3347
|
+
// src/strategyHelpers/tickerProfiles.ts
|
|
3348
|
+
var import_tickerProfiles = require("@tradejs/infra/tickerProfiles");
|
|
3349
|
+
var import_tickerProfiles2 = require("@tradejs/core/tickerProfiles");
|
|
3350
|
+
var snapshots = /* @__PURE__ */ new Map();
|
|
3351
|
+
var resolveTickerProfileSource = (strategy) => {
|
|
3352
|
+
const declared = getStrategyManifest(strategy)?.contextRequirements?.tickerProfiles;
|
|
3353
|
+
if (declared) return declared;
|
|
3354
|
+
const provider = process.env.TICKER_PROFILES_PROVIDER?.trim();
|
|
3355
|
+
const universeId = process.env.TICKER_PROFILES_UNIVERSE?.trim();
|
|
3356
|
+
if (!provider && !universeId) return void 0;
|
|
3357
|
+
if (!provider || !universeId)
|
|
3358
|
+
throw new Error(
|
|
3359
|
+
"Set both TICKER_PROFILES_PROVIDER and TICKER_PROFILES_UNIVERSE"
|
|
3360
|
+
);
|
|
3361
|
+
return { provider, universeId };
|
|
3362
|
+
};
|
|
3363
|
+
var loadTickerProfileContext = async ({
|
|
3364
|
+
projectRoot,
|
|
3365
|
+
provider,
|
|
3366
|
+
universeId,
|
|
3367
|
+
symbol,
|
|
3368
|
+
timestamp
|
|
3369
|
+
}) => {
|
|
3370
|
+
const asOf = (0, import_tickerProfiles2.tickerProfileMonth)(timestamp), key = JSON.stringify([projectRoot, provider, universeId, asOf]);
|
|
3371
|
+
let cached = snapshots.get(key);
|
|
3372
|
+
if (!cached || cached.expires < Date.now()) {
|
|
3373
|
+
const value = (0, import_tickerProfiles.readTickerProfiles)(projectRoot, provider, universeId, asOf).then((envelope) => {
|
|
3374
|
+
if (!envelope) return null;
|
|
3375
|
+
const s = envelope.snapshot;
|
|
3376
|
+
if (!(0, import_tickerProfiles2.isTickerProfileSnapshot)(s) || s.asOf !== asOf || s.provider !== provider || s.universeId !== universeId)
|
|
3377
|
+
throw new Error("Ticker profile identity/schema mismatch");
|
|
3378
|
+
const entry = snapshots.get(key);
|
|
3379
|
+
if (entry) entry.expires = Infinity;
|
|
3380
|
+
return new Map(
|
|
3381
|
+
s.profiles.map((p) => [
|
|
3382
|
+
p.symbol,
|
|
3383
|
+
Object.freeze({
|
|
3384
|
+
...p,
|
|
3385
|
+
definition: s.definition,
|
|
3386
|
+
provider,
|
|
3387
|
+
universeId,
|
|
3388
|
+
snapshotId: envelope.snapshotId
|
|
3389
|
+
})
|
|
3390
|
+
])
|
|
3391
|
+
);
|
|
3392
|
+
}).catch((error) => {
|
|
3393
|
+
snapshots.delete(key);
|
|
3394
|
+
throw error;
|
|
3395
|
+
});
|
|
3396
|
+
cached = { expires: Date.now() + 6e4, value };
|
|
3397
|
+
snapshots.set(key, cached);
|
|
3398
|
+
if (snapshots.size > 24) {
|
|
3399
|
+
const oldest = snapshots.keys().next().value;
|
|
3400
|
+
if (oldest) snapshots.delete(oldest);
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
return (await cached.value)?.get(symbol);
|
|
3404
|
+
};
|
|
3405
|
+
var withTickerProfile = (baseContext, profile) => Object.defineProperty(
|
|
3406
|
+
Object.create(
|
|
3407
|
+
Object.getPrototypeOf(baseContext),
|
|
3408
|
+
Object.getOwnPropertyDescriptors(baseContext)
|
|
3409
|
+
),
|
|
3410
|
+
"tickerProfile",
|
|
3411
|
+
{ enumerable: true, configurable: true, value: profile }
|
|
3412
|
+
);
|
|
3413
|
+
var enrichSignalWithTickerProfile = async (signal) => {
|
|
3414
|
+
const source = resolveTickerProfileSource(signal.strategy);
|
|
3415
|
+
if (!source || signal.universe === "tradfi" || !signal.symbol.endsWith("USDT"))
|
|
3416
|
+
return;
|
|
3417
|
+
const profile = await loadTickerProfileContext({
|
|
3418
|
+
...source,
|
|
3419
|
+
projectRoot: getTradejsProjectCwd(),
|
|
3420
|
+
symbol: signal.symbol,
|
|
3421
|
+
timestamp: signal.timestamp
|
|
3422
|
+
});
|
|
3423
|
+
if (profile) {
|
|
3424
|
+
signal.additionalIndicators = {
|
|
3425
|
+
...signal.additionalIndicators,
|
|
3426
|
+
tickerProfile: profile
|
|
3427
|
+
};
|
|
3428
|
+
}
|
|
3429
|
+
};
|
|
3430
|
+
|
|
3431
|
+
// src/strategyRuntime.ts
|
|
3432
|
+
var import_jev6 = require("@tradejs/core/jev");
|
|
3433
|
+
|
|
3434
|
+
// src/jev.ts
|
|
3435
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
3436
|
+
var import_node_path = __toESM(require("path"));
|
|
3437
|
+
var import_jev4 = require("@tradejs/core/jev");
|
|
3438
|
+
var import_jev5 = require("@tradejs/infra/jev");
|
|
3439
|
+
var import_userSettings2 = require("@tradejs/infra/userSettings");
|
|
3440
|
+
init_jevInput();
|
|
3441
|
+
init_jevInput();
|
|
3442
|
+
init_jevTraining();
|
|
3443
|
+
var resolveJevProvider = async (userName) => {
|
|
3444
|
+
const settings = await (0, import_userSettings2.getUserSettings)(userName);
|
|
3445
|
+
const provider = {
|
|
3446
|
+
endpoint: settings.JEV_API_ENDPOINT || import_jev4.JEV_PROVIDERS[0].endpoint,
|
|
3447
|
+
model: settings.JEV_MODEL || import_jev4.JEV_PROVIDERS[0].model
|
|
3448
|
+
};
|
|
3449
|
+
(0, import_jev4.parseJevConfig)({ source: "provider", mode: "observe", provider });
|
|
3450
|
+
if (!settings.JEV_API_KEY)
|
|
3451
|
+
throw new Error("Configure the Jev API key in Account settings");
|
|
3452
|
+
return provider;
|
|
3453
|
+
};
|
|
3454
|
+
var emptyScores = () => Object.fromEntries(import_jev4.JEV_DIMENSIONS.map((key) => [key, null]));
|
|
3455
|
+
var pending = /* @__PURE__ */ new Map();
|
|
3456
|
+
var pendingResponses = /* @__PURE__ */ new Map();
|
|
3457
|
+
var evaluateJevInput = async ({
|
|
3458
|
+
input,
|
|
3459
|
+
config,
|
|
3460
|
+
userName,
|
|
3461
|
+
projectRoot
|
|
3462
|
+
}) => {
|
|
3463
|
+
config = (0, import_jev4.parseJevConfig)(config);
|
|
3464
|
+
const inputHash = (0, import_jev5.jevHash)(input);
|
|
3465
|
+
const questions = questionsForJevInput(input);
|
|
3466
|
+
const questionsHash = (0, import_jev5.jevHash)(questions);
|
|
3467
|
+
const base = {
|
|
3468
|
+
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
3469
|
+
schema: "tradejs-signal-assessment/v5",
|
|
3470
|
+
source: config.source,
|
|
3471
|
+
mode: config.mode,
|
|
3472
|
+
inputHash,
|
|
3473
|
+
questionsHash,
|
|
3474
|
+
geometryStatus: input.geometryStatus,
|
|
3475
|
+
levelsValid: typeof input.features["signal.validLevels"] === "boolean" ? input.features["signal.validLevels"] : null
|
|
3476
|
+
};
|
|
3477
|
+
if (!input.questions.length)
|
|
3478
|
+
return {
|
|
3479
|
+
assessment: {
|
|
3480
|
+
...base,
|
|
3481
|
+
status: "unavailable",
|
|
3482
|
+
model: "",
|
|
3483
|
+
scores: emptyScores(),
|
|
3484
|
+
confidence: emptyScores()
|
|
3485
|
+
}
|
|
3486
|
+
};
|
|
3487
|
+
if (config.source === "local") {
|
|
3488
|
+
const contents = await import_promises.default.readFile(
|
|
3489
|
+
import_node_path.default.resolve(projectRoot, config.modelFile),
|
|
3490
|
+
"utf8"
|
|
3491
|
+
);
|
|
3492
|
+
if ((0, import_jev5.jevFileHash)(contents) !== config.modelSha256)
|
|
3493
|
+
throw new Error("Local Jev gate checksum mismatch");
|
|
3494
|
+
const { validateJevGateModel: validateJevGateModel2 } = await Promise.resolve().then(() => (init_jevTraining(), jevTraining_exports));
|
|
3495
|
+
const model = validateJevGateModel2(JSON.parse(contents));
|
|
3496
|
+
if (model.strategy !== input.strategy || model.questionsHash !== (0, import_jev5.jevHash)(JEV_QUESTIONS))
|
|
3497
|
+
throw new Error("Local Jev gate strategy or question version mismatch");
|
|
3498
|
+
const scores = Object.fromEntries(
|
|
3499
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
3500
|
+
key,
|
|
3501
|
+
input.questions.includes(key) && model.trees[key] ? (0, import_jev4.predictJevTree)(model.trees[key], input.features) : null
|
|
3502
|
+
])
|
|
3503
|
+
);
|
|
3504
|
+
return {
|
|
3505
|
+
assessment: {
|
|
3506
|
+
...base,
|
|
3507
|
+
status: "available",
|
|
3508
|
+
model: config.modelSha256,
|
|
3509
|
+
scores,
|
|
3510
|
+
confidence: emptyScores()
|
|
3511
|
+
}
|
|
3512
|
+
};
|
|
3513
|
+
}
|
|
3514
|
+
const provider = config.provider ?? await resolveJevProvider(userName);
|
|
3515
|
+
const id = (0, import_jev5.jevHash)({ inputHash, questionsHash, provider });
|
|
3516
|
+
const dir = import_node_path.default.resolve(projectRoot, config.recordsDir ?? "data/ai/jev");
|
|
3517
|
+
const file = import_node_path.default.join(dir, "records", `${id}.json`);
|
|
3518
|
+
const getRecord = async () => {
|
|
3519
|
+
const existing = await (0, import_jev5.readJevArtifact)(file);
|
|
3520
|
+
if (existing) {
|
|
3521
|
+
if (existing.schema !== "tradejs-jev-record/v4" || existing.id !== id || existing.inputHash !== inputHash || existing.questionsHash !== questionsHash || (0, import_jev5.jevHash)(existing.input) !== inputHash || (0, import_jev5.jevHash)(existing.provider) !== (0, import_jev5.jevHash)(provider))
|
|
3522
|
+
throw new Error("Jev recording identity mismatch");
|
|
3523
|
+
(0, import_jev5.validateJevResponse)(existing.response, input.questions);
|
|
3524
|
+
for (const key of import_jev4.JEV_DIMENSIONS) {
|
|
3525
|
+
const expected = input.questions.includes(key) ? existing.response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null;
|
|
3526
|
+
if (existing.scores[key] !== expected)
|
|
3527
|
+
throw new Error("Jev recording scores do not match the response");
|
|
3528
|
+
}
|
|
3529
|
+
return existing;
|
|
3530
|
+
}
|
|
3531
|
+
if (config.source === "recorded")
|
|
3532
|
+
throw new Error(
|
|
3533
|
+
`Missing Jev recording ${id} for ${input.strategy} ${input.symbol} at ${new Date(input.timestamp).toISOString()}. Recorded mode requires the same signal-time facts and provider; a shorter backtest can change warm-up indicators or prior trade state.`
|
|
3534
|
+
);
|
|
3535
|
+
const settings = await (0, import_userSettings2.getUserSettings)(userName);
|
|
3536
|
+
if (!settings.JEV_API_KEY)
|
|
3537
|
+
throw new Error("Configure the Jev API key in Account settings");
|
|
3538
|
+
const apiKey = settings.JEV_API_KEY;
|
|
3539
|
+
const configuredProvider = await resolveJevProvider(userName);
|
|
3540
|
+
if (configuredProvider.endpoint !== provider.endpoint)
|
|
3541
|
+
throw new Error(
|
|
3542
|
+
"Jev account endpoint changed; update the frozen provider configuration"
|
|
3543
|
+
);
|
|
3544
|
+
const started = Date.now();
|
|
3545
|
+
const state = stateForJevInput(input);
|
|
3546
|
+
const responseId = (0, import_jev5.jevHash)({
|
|
3547
|
+
state,
|
|
3548
|
+
questionsHash,
|
|
3549
|
+
provider,
|
|
3550
|
+
user: (0, import_jev5.jevHash)(userName)
|
|
3551
|
+
});
|
|
3552
|
+
const responseFile = import_node_path.default.join(dir, "responses", `${responseId}.json`);
|
|
3553
|
+
let responsePromise = pendingResponses.get(responseFile);
|
|
3554
|
+
if (!responsePromise) {
|
|
3555
|
+
responsePromise = (async () => {
|
|
3556
|
+
const saved = await (0, import_jev5.readJevArtifact)(responseFile);
|
|
3557
|
+
if (saved) return (0, import_jev5.validateJevResponse)(saved, input.questions);
|
|
3558
|
+
const fresh = await (0, import_jev5.requestJev)(provider, apiKey, state, questions);
|
|
3559
|
+
await (0, import_jev5.writeJevArtifact)(responseFile, fresh);
|
|
3560
|
+
return fresh;
|
|
3561
|
+
})();
|
|
3562
|
+
pendingResponses.set(responseFile, responsePromise);
|
|
3563
|
+
}
|
|
3564
|
+
let response;
|
|
3565
|
+
try {
|
|
3566
|
+
response = await responsePromise;
|
|
3567
|
+
} finally {
|
|
3568
|
+
pendingResponses.delete(responseFile);
|
|
3569
|
+
}
|
|
3570
|
+
const scores = Object.fromEntries(
|
|
3571
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
3572
|
+
key,
|
|
3573
|
+
input.questions.includes(key) ? response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null
|
|
3574
|
+
])
|
|
3575
|
+
);
|
|
3576
|
+
const value = {
|
|
3577
|
+
schema: "tradejs-jev-record/v4",
|
|
3578
|
+
id,
|
|
3579
|
+
inputHash,
|
|
3580
|
+
questionsHash,
|
|
3581
|
+
provider,
|
|
3582
|
+
input,
|
|
3583
|
+
response,
|
|
3584
|
+
scores,
|
|
3585
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3586
|
+
elapsedMs: Date.now() - started
|
|
3587
|
+
};
|
|
3588
|
+
await (0, import_jev5.writeJevArtifact)(file, value);
|
|
3589
|
+
return await (0, import_jev5.readJevArtifact)(file);
|
|
3590
|
+
};
|
|
3591
|
+
const pendingKey = `${file}:${(0, import_jev5.jevHash)(userName)}`;
|
|
3592
|
+
let promise = pending.get(pendingKey);
|
|
3593
|
+
if (!promise) {
|
|
3594
|
+
promise = getRecord();
|
|
3595
|
+
pending.set(pendingKey, promise);
|
|
3253
3596
|
}
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
});
|
|
3260
|
-
if (!existing || existing.strategy !== strategy || existing.direction !== direction) {
|
|
3261
|
-
return null;
|
|
3597
|
+
let record;
|
|
3598
|
+
try {
|
|
3599
|
+
record = await promise;
|
|
3600
|
+
} finally {
|
|
3601
|
+
pending.delete(pendingKey);
|
|
3262
3602
|
}
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
openFee,
|
|
3279
|
-
totalFee,
|
|
3280
|
-
lastSyncedAt: now()
|
|
3603
|
+
return {
|
|
3604
|
+
record,
|
|
3605
|
+
assessment: {
|
|
3606
|
+
...base,
|
|
3607
|
+
status: "available",
|
|
3608
|
+
recordId: id,
|
|
3609
|
+
model: record.response.model,
|
|
3610
|
+
scores: record.scores,
|
|
3611
|
+
confidence: Object.fromEntries(
|
|
3612
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
3613
|
+
key,
|
|
3614
|
+
record.response.answers[key]?.confidence ?? null
|
|
3615
|
+
])
|
|
3616
|
+
)
|
|
3617
|
+
}
|
|
3281
3618
|
};
|
|
3282
|
-
|
|
3619
|
+
};
|
|
3620
|
+
var assessSignalWithJev = async ({
|
|
3621
|
+
signal,
|
|
3622
|
+
config,
|
|
3623
|
+
userName,
|
|
3624
|
+
projectRoot,
|
|
3625
|
+
strict,
|
|
3626
|
+
scope = "runtime"
|
|
3627
|
+
}) => {
|
|
3628
|
+
let assessment;
|
|
3283
3629
|
try {
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
next,
|
|
3292
|
-
{ expire: 0 }
|
|
3293
|
-
)
|
|
3294
|
-
]);
|
|
3630
|
+
const input = buildJevInput(signal);
|
|
3631
|
+
({ assessment } = await evaluateJevInput({
|
|
3632
|
+
input,
|
|
3633
|
+
config,
|
|
3634
|
+
userName,
|
|
3635
|
+
projectRoot
|
|
3636
|
+
}));
|
|
3295
3637
|
} catch (error) {
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
null
|
|
3312
|
-
);
|
|
3313
|
-
const orderId = String(activeRef?.orderId || "").trim();
|
|
3314
|
-
if (!orderId) {
|
|
3315
|
-
return null;
|
|
3638
|
+
if (strict) throw error;
|
|
3639
|
+
assessment = {
|
|
3640
|
+
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
3641
|
+
schema: "tradejs-signal-assessment/v5",
|
|
3642
|
+
source: config.source,
|
|
3643
|
+
mode: config.mode,
|
|
3644
|
+
status: "unavailable",
|
|
3645
|
+
inputHash: "",
|
|
3646
|
+
questionsHash: "",
|
|
3647
|
+
geometryStatus: "absent",
|
|
3648
|
+
levelsValid: null,
|
|
3649
|
+
model: config.provider?.model ?? config.modelSha256 ?? "",
|
|
3650
|
+
scores: emptyScores(),
|
|
3651
|
+
confidence: emptyScores()
|
|
3652
|
+
};
|
|
3316
3653
|
}
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3654
|
+
signal.assessment = assessment;
|
|
3655
|
+
signal.additionalIndicators = {
|
|
3656
|
+
...signal.additionalIndicators,
|
|
3657
|
+
jev: (0, import_jev4.jevAssessmentFeatures)(assessment)
|
|
3658
|
+
};
|
|
3659
|
+
const identity = {
|
|
3660
|
+
signalId: signal.signalId,
|
|
3661
|
+
strategy: signal.strategy,
|
|
3662
|
+
symbol: signal.symbol,
|
|
3663
|
+
timestamp: signal.timestamp,
|
|
3664
|
+
scope,
|
|
3665
|
+
user: (0, import_jev5.jevHash)(userName),
|
|
3666
|
+
config,
|
|
3667
|
+
assessment
|
|
3668
|
+
};
|
|
3669
|
+
await (0, import_jev5.writeJevArtifact)(
|
|
3670
|
+
import_node_path.default.resolve(
|
|
3671
|
+
projectRoot,
|
|
3672
|
+
config.recordsDir ?? "data/ai/jev",
|
|
3673
|
+
"decisions",
|
|
3674
|
+
`${(0, import_jev5.jevHash)(identity)}.json`
|
|
3675
|
+
),
|
|
3676
|
+
identity
|
|
3320
3677
|
);
|
|
3321
|
-
|
|
3322
|
-
await (0, import_redis2.delKey)(
|
|
3323
|
-
import_redis2.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
|
|
3324
|
-
);
|
|
3325
|
-
return null;
|
|
3326
|
-
}
|
|
3327
|
-
return existing;
|
|
3678
|
+
return assessment;
|
|
3328
3679
|
};
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
deploymentId
|
|
3349
|
-
});
|
|
3350
|
-
if (!existing) {
|
|
3351
|
-
return null;
|
|
3352
|
-
}
|
|
3353
|
-
const orderId = existing.orderId;
|
|
3354
|
-
if (strategy && existing.strategy !== strategy) {
|
|
3355
|
-
return null;
|
|
3680
|
+
|
|
3681
|
+
// src/strategyRuntime.ts
|
|
3682
|
+
var import_constants5 = require("@tradejs/core/constants");
|
|
3683
|
+
var import_strategies6 = require("@tradejs/core/strategies");
|
|
3684
|
+
var import_logger11 = require("@tradejs/infra/logger");
|
|
3685
|
+
|
|
3686
|
+
// src/strategyHelpers/runtime.ts
|
|
3687
|
+
var import_logger9 = require("@tradejs/infra/logger");
|
|
3688
|
+
var import_constants3 = require("@tradejs/core/constants");
|
|
3689
|
+
var import_ml2 = require("@tradejs/infra/ml");
|
|
3690
|
+
|
|
3691
|
+
// src/strategy/entrySubmissionValidation.ts
|
|
3692
|
+
var import_logger3 = require("@tradejs/infra/logger");
|
|
3693
|
+
|
|
3694
|
+
// src/strategy/entryExecutionValidation.ts
|
|
3695
|
+
var EntrySubmissionValidationError = class extends Error {
|
|
3696
|
+
constructor() {
|
|
3697
|
+
super(...arguments);
|
|
3698
|
+
this.name = "EntrySubmissionValidationError";
|
|
3356
3699
|
}
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
const
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3700
|
+
};
|
|
3701
|
+
var resolveEntryExecutionLimits = (config, direction) => {
|
|
3702
|
+
const side = config[direction];
|
|
3703
|
+
const minimum = side && typeof side === "object" && "minRiskRatio" in side ? side.minRiskRatio : void 0;
|
|
3704
|
+
const maximum = config.MAX_LOSS_VALUE;
|
|
3705
|
+
if (minimum == null && maximum == null) return void 0;
|
|
3706
|
+
const minimumNetRiskRatio = minimum == null ? void 0 : Number(minimum);
|
|
3707
|
+
const maxLossValue = maximum == null ? void 0 : Number(maximum);
|
|
3708
|
+
const feeRate = Number(config.RISK_FEE_RATE ?? 0);
|
|
3709
|
+
const slippage = Number(config.RISK_SLIPPAGE_BPS ?? 0);
|
|
3710
|
+
const impact = Number(config.RISK_MARKET_IMPACT_BPS ?? 0);
|
|
3711
|
+
const slippageBps = slippage + impact;
|
|
3712
|
+
if (minimumNetRiskRatio != null && (!Number.isFinite(minimumNetRiskRatio) || minimumNetRiskRatio < 0) || maxLossValue != null && (!Number.isFinite(maxLossValue) || maxLossValue <= 0) || !Number.isFinite(feeRate) || feeRate < 0 || feeRate >= 1 || !Number.isFinite(slippage) || slippage < 0 || !Number.isFinite(impact) || impact < 0 || !Number.isFinite(slippageBps) || slippageBps < 0 || slippageBps >= 1e4)
|
|
3713
|
+
throw new Error("INVALID_ENTRY_EXECUTION_LIMITS");
|
|
3714
|
+
return { minimumNetRiskRatio, maxLossValue, feeRate, slippageBps };
|
|
3715
|
+
};
|
|
3716
|
+
var validateEntryExecution = ({
|
|
3717
|
+
direction,
|
|
3718
|
+
price,
|
|
3719
|
+
qty,
|
|
3720
|
+
stopLossPrice,
|
|
3721
|
+
takeProfitPrice,
|
|
3722
|
+
limits
|
|
3723
|
+
}) => {
|
|
3724
|
+
if (!Number.isFinite(price) || price <= 0 || !Number.isFinite(qty) || qty <= 0 || stopLossPrice == null || !Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || !Number.isFinite(takeProfitPrice) || takeProfitPrice <= 0)
|
|
3725
|
+
throw new EntrySubmissionValidationError(
|
|
3726
|
+
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
3727
|
+
);
|
|
3728
|
+
const side = direction === "LONG" ? 1 : -1;
|
|
3729
|
+
if (side * (price - stopLossPrice) <= 0 || side * (takeProfitPrice - price) <= 0)
|
|
3730
|
+
throw new EntrySubmissionValidationError(
|
|
3731
|
+
"INVALID_ENTRY_PROTECTION_AT_SUBMISSION"
|
|
3732
|
+
);
|
|
3733
|
+
const slippage = limits.slippageBps / 1e4;
|
|
3734
|
+
const cost = (value, sign) => value * slippage + value * (1 + sign * slippage) * limits.feeRate;
|
|
3735
|
+
const entryCost = cost(price, side);
|
|
3736
|
+
const lossPerUnit = Math.abs(price - stopLossPrice) + entryCost + cost(stopLossPrice, -side);
|
|
3737
|
+
const rewardPerUnit = Math.max(
|
|
3738
|
+
0,
|
|
3739
|
+
Math.abs(takeProfitPrice - price) - entryCost - cost(takeProfitPrice, -side)
|
|
3740
|
+
);
|
|
3741
|
+
const netRiskRatio = rewardPerUnit / lossPerUnit;
|
|
3742
|
+
const plannedLossValue = qty * lossPerUnit;
|
|
3743
|
+
if (!Number.isFinite(netRiskRatio) || !Number.isFinite(plannedLossValue))
|
|
3744
|
+
throw new EntrySubmissionValidationError(
|
|
3745
|
+
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
3746
|
+
);
|
|
3747
|
+
const belowMinimumNetRiskRatio = limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio;
|
|
3748
|
+
const riskExceeded = limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9);
|
|
3749
|
+
return {
|
|
3750
|
+
netRiskRatio,
|
|
3751
|
+
plannedLossValue,
|
|
3752
|
+
riskExceeded,
|
|
3753
|
+
belowMinimumNetRiskRatio
|
|
3374
3754
|
};
|
|
3375
|
-
|
|
3376
|
-
|
|
3755
|
+
};
|
|
3756
|
+
|
|
3757
|
+
// src/strategy/entrySubmissionValidation.ts
|
|
3758
|
+
var MAX_ENTRY_QUOTE_AGE_MS = 5e3;
|
|
3759
|
+
var toFiniteNumberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
3760
|
+
var getOrderArrivalSnapshot = async ({
|
|
3761
|
+
connector,
|
|
3762
|
+
symbol,
|
|
3763
|
+
requireTimestamp = false
|
|
3764
|
+
}) => {
|
|
3765
|
+
if (typeof connector.getTopOfBookTicker !== "function") {
|
|
3766
|
+
return {
|
|
3767
|
+
arrivalSnapshotTime: Date.now(),
|
|
3768
|
+
arrivalSource: "unavailable",
|
|
3769
|
+
bid: null,
|
|
3770
|
+
ask: null,
|
|
3771
|
+
arrivalMid: null,
|
|
3772
|
+
spreadBps: null
|
|
3773
|
+
};
|
|
3774
|
+
}
|
|
3377
3775
|
try {
|
|
3378
|
-
await
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
),
|
|
3394
|
-
(0, import_redis2.delKey)(
|
|
3395
|
-
import_redis2.redisKeys.runtimeActiveTrade(
|
|
3396
|
-
userName,
|
|
3397
|
-
symbol,
|
|
3398
|
-
deploymentId ?? accountId
|
|
3399
|
-
)
|
|
3400
|
-
)
|
|
3401
|
-
]);
|
|
3776
|
+
const ticker = await connector.getTopOfBookTicker(symbol);
|
|
3777
|
+
const arrivalSnapshotTime = toFiniteNumberOrNull(ticker?.timestamp);
|
|
3778
|
+
const timestampUnavailable = requireTimestamp && arrivalSnapshotTime == null;
|
|
3779
|
+
const bid = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.bidPrice);
|
|
3780
|
+
const ask = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.askPrice);
|
|
3781
|
+
const arrivalMid = bid != null && ask != null ? (bid + ask) / 2 : null;
|
|
3782
|
+
const spreadBps = bid != null && ask != null && arrivalMid != null && arrivalMid > 0 ? (ask - bid) / arrivalMid * 1e4 : null;
|
|
3783
|
+
return {
|
|
3784
|
+
arrivalSnapshotTime: arrivalSnapshotTime ?? Date.now(),
|
|
3785
|
+
arrivalSource: "top_of_book",
|
|
3786
|
+
bid,
|
|
3787
|
+
ask,
|
|
3788
|
+
arrivalMid,
|
|
3789
|
+
spreadBps
|
|
3790
|
+
};
|
|
3402
3791
|
} catch (error) {
|
|
3403
|
-
import_logger3.logger.
|
|
3404
|
-
"runtime
|
|
3792
|
+
import_logger3.logger.warn(
|
|
3793
|
+
"runtime order arrival snapshot failed: %s %s",
|
|
3405
3794
|
symbol,
|
|
3406
3795
|
error?.message || String(error)
|
|
3407
3796
|
);
|
|
3797
|
+
return {
|
|
3798
|
+
arrivalSnapshotTime: Date.now(),
|
|
3799
|
+
arrivalSource: "top_of_book_error",
|
|
3800
|
+
bid: null,
|
|
3801
|
+
ask: null,
|
|
3802
|
+
arrivalMid: null,
|
|
3803
|
+
spreadBps: null
|
|
3804
|
+
};
|
|
3408
3805
|
}
|
|
3409
|
-
|
|
3806
|
+
};
|
|
3807
|
+
var validateEntryProtectionAtArrival = ({
|
|
3808
|
+
direction,
|
|
3809
|
+
signalPrice,
|
|
3810
|
+
bid,
|
|
3811
|
+
ask,
|
|
3812
|
+
arrivalMid,
|
|
3813
|
+
takeProfits,
|
|
3814
|
+
stopLossPrice
|
|
3815
|
+
}) => {
|
|
3816
|
+
const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
|
|
3817
|
+
const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
|
|
3818
|
+
if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
|
|
3819
|
+
throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
|
|
3820
|
+
}
|
|
3821
|
+
for (const takeProfit of takeProfits) {
|
|
3822
|
+
if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
|
|
3823
|
+
throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
|
|
3827
|
+
throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
|
|
3828
|
+
}
|
|
3829
|
+
};
|
|
3830
|
+
var createEntrySubmissionValidation = ({
|
|
3831
|
+
connector,
|
|
3832
|
+
symbol,
|
|
3833
|
+
direction,
|
|
3834
|
+
takeProfits,
|
|
3835
|
+
stopLossPrice,
|
|
3836
|
+
limits,
|
|
3837
|
+
onValidated
|
|
3838
|
+
}) => {
|
|
3839
|
+
if (!limits) return void 0;
|
|
3840
|
+
if (!connector.capabilities?.orderSubmissionValidation)
|
|
3841
|
+
throw new EntrySubmissionValidationError(
|
|
3842
|
+
"ENTRY_SUBMISSION_VALIDATION_UNSUPPORTED"
|
|
3843
|
+
);
|
|
3844
|
+
return async ({ qty, executionPrice }) => {
|
|
3845
|
+
const simulated = Reflect.get(connector, "__tradejsTestConnector") === true;
|
|
3846
|
+
const snapshot2 = simulated && executionPrice != null ? {
|
|
3847
|
+
arrivalSnapshotTime: Date.now(),
|
|
3848
|
+
arrivalSource: "simulation",
|
|
3849
|
+
bid: executionPrice,
|
|
3850
|
+
ask: executionPrice,
|
|
3851
|
+
arrivalMid: executionPrice,
|
|
3852
|
+
spreadBps: 0
|
|
3853
|
+
} : await getOrderArrivalSnapshot({
|
|
3854
|
+
connector,
|
|
3855
|
+
symbol,
|
|
3856
|
+
requireTimestamp: true
|
|
3857
|
+
});
|
|
3858
|
+
const age = Date.now() - snapshot2.arrivalSnapshotTime;
|
|
3859
|
+
if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
|
|
3860
|
+
throw new EntrySubmissionValidationError(
|
|
3861
|
+
"ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
|
|
3862
|
+
);
|
|
3863
|
+
if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
|
|
3864
|
+
throw new EntrySubmissionValidationError(
|
|
3865
|
+
"ENTRY_QUOTE_STALE_AT_SUBMISSION"
|
|
3866
|
+
);
|
|
3867
|
+
const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
|
|
3868
|
+
validateEntryProtectionAtArrival({
|
|
3869
|
+
direction,
|
|
3870
|
+
signalPrice: price,
|
|
3871
|
+
bid: snapshot2.bid,
|
|
3872
|
+
ask: snapshot2.ask,
|
|
3873
|
+
arrivalMid: snapshot2.arrivalMid,
|
|
3874
|
+
takeProfits,
|
|
3875
|
+
stopLossPrice
|
|
3876
|
+
});
|
|
3877
|
+
const prices = takeProfits.map((tp) => tp.price);
|
|
3878
|
+
const economics2 = validateEntryExecution({
|
|
3879
|
+
direction,
|
|
3880
|
+
price,
|
|
3881
|
+
qty,
|
|
3882
|
+
stopLossPrice,
|
|
3883
|
+
takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
|
|
3884
|
+
limits
|
|
3885
|
+
});
|
|
3886
|
+
if (economics2.belowMinimumNetRiskRatio) {
|
|
3887
|
+
import_logger3.logger.warn(
|
|
3888
|
+
"entry submission warning: %s %s ENTRY_RR_BELOW_MIN_AT_SUBMISSION (RR %s, threshold %s)",
|
|
3889
|
+
symbol,
|
|
3890
|
+
direction,
|
|
3891
|
+
economics2.netRiskRatio,
|
|
3892
|
+
limits.minimumNetRiskRatio
|
|
3893
|
+
);
|
|
3894
|
+
}
|
|
3895
|
+
onValidated?.(snapshot2, economics2);
|
|
3896
|
+
};
|
|
3897
|
+
};
|
|
3898
|
+
|
|
3899
|
+
// src/strategyAdapters/ml.ts
|
|
3900
|
+
var defaultMlAdapter = {
|
|
3901
|
+
normalizeStrategyConfig: (strategyConfig) => strategyConfig
|
|
3902
|
+
};
|
|
3903
|
+
var getStrategyMlAdapter = (strategy, profileId) => {
|
|
3904
|
+
const strategyAdapter = getStrategyProfileMlAdapter(
|
|
3905
|
+
getStrategyManifest(strategy),
|
|
3906
|
+
profileId
|
|
3907
|
+
);
|
|
3908
|
+
if (!strategyAdapter) return defaultMlAdapter;
|
|
3909
|
+
return {
|
|
3910
|
+
...defaultMlAdapter,
|
|
3911
|
+
...strategyAdapter
|
|
3912
|
+
};
|
|
3913
|
+
};
|
|
3914
|
+
|
|
3915
|
+
// src/mlPayload.ts
|
|
3916
|
+
var normalizeStrategyConfig = (strategyConfig, strategyName, profileId) => {
|
|
3917
|
+
return getStrategyMlAdapter(
|
|
3918
|
+
strategyName,
|
|
3919
|
+
profileId
|
|
3920
|
+
).normalizeStrategyConfig?.(strategyConfig);
|
|
3921
|
+
};
|
|
3922
|
+
var buildMlPayload = (payload) => {
|
|
3923
|
+
const strategyName = payload.signal?.strategy ?? payload.context?.strategyName;
|
|
3924
|
+
const profileId = payload.signal?.policyProfileId;
|
|
3925
|
+
const mlAdapter = getStrategyMlAdapter(strategyName, profileId);
|
|
3926
|
+
const normalizedSignal = mlAdapter.normalizeSignal?.(payload.signal) ?? payload.signal;
|
|
3927
|
+
const nextSignal = {
|
|
3928
|
+
...normalizedSignal,
|
|
3929
|
+
indicators: {
|
|
3930
|
+
...normalizedSignal?.indicators ?? {}
|
|
3931
|
+
}
|
|
3932
|
+
};
|
|
3933
|
+
const nextContext = payload.context ? {
|
|
3934
|
+
...payload.context,
|
|
3935
|
+
strategyConfig: normalizeStrategyConfig(
|
|
3936
|
+
payload.context.strategyConfig,
|
|
3937
|
+
strategyName,
|
|
3938
|
+
profileId
|
|
3939
|
+
)
|
|
3940
|
+
} : void 0;
|
|
3941
|
+
return {
|
|
3942
|
+
signal: nextSignal,
|
|
3943
|
+
context: nextContext
|
|
3944
|
+
};
|
|
3410
3945
|
};
|
|
3411
3946
|
|
|
3412
3947
|
// src/strategyHelpers/marketContextStages.ts
|
|
@@ -3418,7 +3953,7 @@ var import_logger4 = require("@tradejs/infra/logger");
|
|
|
3418
3953
|
var import_strategies = require("@tradejs/core/strategies");
|
|
3419
3954
|
|
|
3420
3955
|
// src/binanceBreadthUniverses.ts
|
|
3421
|
-
var
|
|
3956
|
+
var import_node_crypto3 = require("crypto");
|
|
3422
3957
|
|
|
3423
3958
|
// src/config/binanceBreadthUniverses.json
|
|
3424
3959
|
var binanceBreadthUniverses_default = {
|
|
@@ -3657,7 +4192,7 @@ var BINANCE_BREADTH_UNIVERSE_KEYS = [
|
|
|
3657
4192
|
"top50",
|
|
3658
4193
|
"top100"
|
|
3659
4194
|
];
|
|
3660
|
-
var fingerprint = (value, length) => (0,
|
|
4195
|
+
var fingerprint = (value, length) => (0, import_node_crypto3.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, length);
|
|
3661
4196
|
var normalizeSymbols = (symbols) => symbols.map((symbol) => symbol.trim().toUpperCase()).filter(Boolean);
|
|
3662
4197
|
var buildBinanceBreadthUniverseSnapshot = ({
|
|
3663
4198
|
rankedSymbols,
|
|
@@ -4833,7 +5368,7 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
4833
5368
|
};
|
|
4834
5369
|
|
|
4835
5370
|
// src/strategyHelpers/derivativesContext.ts
|
|
4836
|
-
var
|
|
5371
|
+
var import_indicators3 = require("@tradejs/core/indicators");
|
|
4837
5372
|
var import_data2 = require("@tradejs/core/data");
|
|
4838
5373
|
var import_strategies3 = require("@tradejs/core/strategies");
|
|
4839
5374
|
var import_constants2 = require("@tradejs/core/constants");
|
|
@@ -4867,7 +5402,7 @@ var parseLookbackMs = () => {
|
|
|
4867
5402
|
};
|
|
4868
5403
|
var withHourlyFallbackRows = (rowsByInterval) => ({
|
|
4869
5404
|
"15m": rowsByInterval["15m"] ?? [],
|
|
4870
|
-
"1h": (0,
|
|
5405
|
+
"1h": (0, import_indicators3.buildCoinalyzeHourlyRowsWithFallback)({
|
|
4871
5406
|
rows15m: rowsByInterval["15m"],
|
|
4872
5407
|
fallbackRows1h: rowsByInterval["1h"]
|
|
4873
5408
|
})
|
|
@@ -4995,7 +5530,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
4995
5530
|
const targetSymbol = normalizeSymbol(signal.symbol);
|
|
4996
5531
|
const lookbackMs = parseLookbackMs();
|
|
4997
5532
|
const decisionTimeMs = signal.timestamp + (0, import_data2.intervalToMs)(signal.interval);
|
|
4998
|
-
const derivativesEndMs = (0,
|
|
5533
|
+
const derivativesEndMs = (0, import_indicators3.getLastClosedDerivativesBarStartMs)(
|
|
4999
5534
|
decisionTimeMs,
|
|
5000
5535
|
"15m"
|
|
5001
5536
|
);
|
|
@@ -5010,7 +5545,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
5010
5545
|
});
|
|
5011
5546
|
return [
|
|
5012
5547
|
symbol,
|
|
5013
|
-
(0,
|
|
5548
|
+
(0, import_indicators3.buildDerivativesContext)({
|
|
5014
5549
|
symbol,
|
|
5015
5550
|
direction: signal.direction,
|
|
5016
5551
|
timestamp: derivativesEndMs,
|
|
@@ -5037,7 +5572,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
5037
5572
|
lookbackMs,
|
|
5038
5573
|
...params.abortSignal ? { signal: params.abortSignal } : {}
|
|
5039
5574
|
});
|
|
5040
|
-
const context = (0,
|
|
5575
|
+
const context = (0, import_indicators3.buildDerivativesContext)({
|
|
5041
5576
|
symbol: targetSymbol,
|
|
5042
5577
|
direction: signal.direction,
|
|
5043
5578
|
timestamp: derivativesEndMs,
|
|
@@ -5085,7 +5620,7 @@ var import_hyperliquidWhales2 = require("@tradejs/infra/timescale/hyperliquidWha
|
|
|
5085
5620
|
var import_logger7 = require("@tradejs/infra/logger");
|
|
5086
5621
|
|
|
5087
5622
|
// src/hyperliquidWhaleUniverse.ts
|
|
5088
|
-
var
|
|
5623
|
+
var import_node_crypto4 = require("crypto");
|
|
5089
5624
|
|
|
5090
5625
|
// src/config/hyperliquidPerpUniverse.json
|
|
5091
5626
|
var hyperliquidPerpUniverse_default = {
|
|
@@ -5257,7 +5792,7 @@ var hyperliquidWhales_default = {
|
|
|
5257
5792
|
};
|
|
5258
5793
|
|
|
5259
5794
|
// src/hyperliquidWhaleUniverse.ts
|
|
5260
|
-
var fingerprint2 = (value) => (0,
|
|
5795
|
+
var fingerprint2 = (value) => (0, import_node_crypto4.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
|
|
5261
5796
|
var validateUnique = (values, label) => {
|
|
5262
5797
|
if (new Set(values).size !== values.length) {
|
|
5263
5798
|
throw new Error(`Hyperliquid ${label} snapshot contains duplicates`);
|
|
@@ -5748,6 +6283,7 @@ var enrichSignalWithMarketContextStages = async ({
|
|
|
5748
6283
|
})
|
|
5749
6284
|
});
|
|
5750
6285
|
}
|
|
6286
|
+
await enrichSignalWithTickerProfile(signal);
|
|
5751
6287
|
const results = [];
|
|
5752
6288
|
for (const stage of stages) {
|
|
5753
6289
|
if (abortSignal?.aborted) break;
|
|
@@ -5795,12 +6331,12 @@ var findReplayAiAnalysis = ({
|
|
|
5795
6331
|
direction,
|
|
5796
6332
|
ai
|
|
5797
6333
|
}) => {
|
|
5798
|
-
const
|
|
5799
|
-
if (!Array.isArray(
|
|
6334
|
+
const snapshots2 = ai?.replayAnalyses;
|
|
6335
|
+
if (!Array.isArray(snapshots2) || !snapshots2.length) {
|
|
5800
6336
|
return void 0;
|
|
5801
6337
|
}
|
|
5802
6338
|
let best = null;
|
|
5803
|
-
for (const snapshot2 of
|
|
6339
|
+
for (const snapshot2 of snapshots2) {
|
|
5804
6340
|
if (snapshot2.symbol !== signal.symbol || snapshot2.direction !== direction || snapshot2.strategy && snapshot2.strategy !== signal.strategy) {
|
|
5805
6341
|
continue;
|
|
5806
6342
|
}
|
|
@@ -5973,7 +6509,9 @@ var executeEntryOrder = async ({
|
|
|
5973
6509
|
beforePlaceOrder,
|
|
5974
6510
|
recordRuntimeTrade = true,
|
|
5975
6511
|
leverage,
|
|
5976
|
-
executionLimits
|
|
6512
|
+
executionLimits,
|
|
6513
|
+
markPortfolioSubmitted,
|
|
6514
|
+
rejectPortfolioSubmission
|
|
5977
6515
|
}) => {
|
|
5978
6516
|
await beforePlaceOrder?.();
|
|
5979
6517
|
const previousPosition = positionIntent === "increase" ? await connector.getPosition(symbol) : null;
|
|
@@ -6046,7 +6584,13 @@ var executeEntryOrder = async ({
|
|
|
6046
6584
|
...typeof leverage === "number" && Number.isFinite(leverage) ? { leverage } : {},
|
|
6047
6585
|
orderId,
|
|
6048
6586
|
signal,
|
|
6049
|
-
...
|
|
6587
|
+
...rejectPortfolioSubmission ? { onRejected: rejectPortfolioSubmission } : {},
|
|
6588
|
+
...beforeSubmit || markPortfolioSubmitted ? {
|
|
6589
|
+
beforeSubmit: async (context) => {
|
|
6590
|
+
await beforeSubmit?.(context);
|
|
6591
|
+
await markPortfolioSubmitted?.(context.qty);
|
|
6592
|
+
}
|
|
6593
|
+
} : {}
|
|
6050
6594
|
});
|
|
6051
6595
|
const orderAckTime = Date.now();
|
|
6052
6596
|
const placedQty = typeof signal.orderQty === "number" && Number.isFinite(signal.orderQty) && signal.orderQty > 0 ? signal.orderQty : qty;
|
|
@@ -7034,7 +7578,7 @@ var handleProtectDecision = async ({
|
|
|
7034
7578
|
}
|
|
7035
7579
|
return decision.code;
|
|
7036
7580
|
};
|
|
7037
|
-
var
|
|
7581
|
+
var executeAdmittedEntryDecision = async ({
|
|
7038
7582
|
connector,
|
|
7039
7583
|
symbol,
|
|
7040
7584
|
decision,
|
|
@@ -7048,7 +7592,9 @@ var executeEntryDecision = async ({
|
|
|
7048
7592
|
ai,
|
|
7049
7593
|
recordRuntimeJournal,
|
|
7050
7594
|
invokeStageHooks,
|
|
7051
|
-
notifyRuntimeError
|
|
7595
|
+
notifyRuntimeError,
|
|
7596
|
+
markPortfolioSubmitted,
|
|
7597
|
+
rejectPortfolioSubmission
|
|
7052
7598
|
}) => {
|
|
7053
7599
|
const signal = decision.signal;
|
|
7054
7600
|
const beforePlaceOrder = async () => {
|
|
@@ -7095,6 +7641,8 @@ var executeEntryDecision = async ({
|
|
|
7095
7641
|
...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {},
|
|
7096
7642
|
signal,
|
|
7097
7643
|
beforePlaceOrder,
|
|
7644
|
+
markPortfolioSubmitted,
|
|
7645
|
+
rejectPortfolioSubmission,
|
|
7098
7646
|
recordRuntimeTrade: recordRuntimeJournal,
|
|
7099
7647
|
executionLimits: resolveEntryExecutionLimits(
|
|
7100
7648
|
hookCtx.strategyConfig,
|
|
@@ -7235,6 +7783,37 @@ var executeEntryDecision = async ({
|
|
|
7235
7783
|
}
|
|
7236
7784
|
return signal ?? decision.code;
|
|
7237
7785
|
};
|
|
7786
|
+
async function executeEntryDecision(params) {
|
|
7787
|
+
if (!params.portfolio) return executeAdmittedEntryDecision(params);
|
|
7788
|
+
const { decision, portfolio } = params;
|
|
7789
|
+
const signal = decision.signal;
|
|
7790
|
+
if (!signal || decision.orderPlan.positionIntent === "increase")
|
|
7791
|
+
return "PORTFOLIO_REQUIRES_NEW_SIGNAL_POSITION";
|
|
7792
|
+
signal.orderId ??= createRuntimeOrderId(signal.strategy);
|
|
7793
|
+
const result = await portfolio.submit(
|
|
7794
|
+
params.portfolioTime ?? decision.entryContext.timestamp,
|
|
7795
|
+
{
|
|
7796
|
+
id: signal.orderId,
|
|
7797
|
+
signalId: signal.signalId,
|
|
7798
|
+
strategy: signal.strategy,
|
|
7799
|
+
symbol: params.symbol,
|
|
7800
|
+
direction: decision.entryContext.direction,
|
|
7801
|
+
qty: decision.orderPlan.qty,
|
|
7802
|
+
timestamp: decision.entryContext.timestamp
|
|
7803
|
+
},
|
|
7804
|
+
(markPortfolioSubmitted, rejectPortfolioSubmission) => executeAdmittedEntryDecision({
|
|
7805
|
+
...params,
|
|
7806
|
+
markPortfolioSubmitted,
|
|
7807
|
+
rejectPortfolioSubmission
|
|
7808
|
+
})
|
|
7809
|
+
);
|
|
7810
|
+
if (result.reason) {
|
|
7811
|
+
signal.orderStatus = "failed";
|
|
7812
|
+
signal.orderFailureReason = result.reason;
|
|
7813
|
+
return signal;
|
|
7814
|
+
}
|
|
7815
|
+
return result.value;
|
|
7816
|
+
}
|
|
7238
7817
|
|
|
7239
7818
|
// src/strategyRuntime.ts
|
|
7240
7819
|
var cloneWithPropertyDescriptors = (value) => Object.create(
|
|
@@ -7262,6 +7841,7 @@ var createStrategyRuntime = ({
|
|
|
7262
7841
|
};
|
|
7263
7842
|
const creator = async ({
|
|
7264
7843
|
userName,
|
|
7844
|
+
connectorName,
|
|
7265
7845
|
config: baseConfig,
|
|
7266
7846
|
symbol,
|
|
7267
7847
|
universe: requestedUniverse,
|
|
@@ -7300,6 +7880,17 @@ var createStrategyRuntime = ({
|
|
|
7300
7880
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
7301
7881
|
const projectHooks = projectConfig.hooks;
|
|
7302
7882
|
const env = String(config.ENV ?? "BACKTEST");
|
|
7883
|
+
const portfolio = getPortfolioRuntime({
|
|
7884
|
+
connectorName,
|
|
7885
|
+
value: baseConfig?.PORTFOLIO_POLICY ?? config.PORTFOLIO_POLICY,
|
|
7886
|
+
strategy: strategyName,
|
|
7887
|
+
connector,
|
|
7888
|
+
env,
|
|
7889
|
+
userName,
|
|
7890
|
+
accountId,
|
|
7891
|
+
deploymentId
|
|
7892
|
+
});
|
|
7893
|
+
const portfolioContext = portfolio ? (now2) => isRuntimeOrderExecutionEnabled({ config, env, connector }) ? portfolio.tick(now2) : portfolio.context(now2) : void 0;
|
|
7303
7894
|
const researchPolicyValue = baseConfig?.RESEARCH_ENTRY_POLICY ?? config.RESEARCH_ENTRY_POLICY;
|
|
7304
7895
|
const researchEntryPolicy = researchPolicyValue == null ? void 0 : parseResearchEntryPolicy(researchPolicyValue);
|
|
7305
7896
|
if (researchEntryPolicy && (env !== "BACKTEST" || Reflect.get(connector, "__tradejsTestConnector") !== true))
|
|
@@ -7598,6 +8189,20 @@ var createStrategyRuntime = ({
|
|
|
7598
8189
|
interval: decisionInterval
|
|
7599
8190
|
}) => {
|
|
7600
8191
|
if (!baseContext) return void 0;
|
|
8192
|
+
const profileSource = strategyManifest?.contextRequirements?.tickerProfiles;
|
|
8193
|
+
if (profileSource) {
|
|
8194
|
+
const profile = await loadTickerProfileContext({
|
|
8195
|
+
...profileSource,
|
|
8196
|
+
projectRoot,
|
|
8197
|
+
symbol: decisionSymbol,
|
|
8198
|
+
timestamp: candle.timestamp
|
|
8199
|
+
});
|
|
8200
|
+
if (!profile)
|
|
8201
|
+
throw new Error(
|
|
8202
|
+
"Required ticker profile is unavailable at decision time"
|
|
8203
|
+
);
|
|
8204
|
+
baseContext = withTickerProfile(baseContext, profile);
|
|
8205
|
+
}
|
|
7601
8206
|
if (!coreContextRequirements.has("hyperliquidWhales")) {
|
|
7602
8207
|
return baseContext;
|
|
7603
8208
|
}
|
|
@@ -7765,6 +8370,8 @@ var createStrategyRuntime = ({
|
|
|
7765
8370
|
runtime: pending2.runtime
|
|
7766
8371
|
});
|
|
7767
8372
|
return executeEntryDecision({
|
|
8373
|
+
portfolio,
|
|
8374
|
+
portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
|
|
7768
8375
|
connector,
|
|
7769
8376
|
symbol,
|
|
7770
8377
|
decision: pending2.decision,
|
|
@@ -7782,6 +8389,10 @@ var createStrategyRuntime = ({
|
|
|
7782
8389
|
});
|
|
7783
8390
|
};
|
|
7784
8391
|
const runWithDecisionOverride = async (candle, btcCandle, options = {}) => {
|
|
8392
|
+
if (portfolioContext)
|
|
8393
|
+
await portfolioContext(
|
|
8394
|
+
env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now()
|
|
8395
|
+
);
|
|
7785
8396
|
appendCurrentMarketData(candle, btcCandle, options.ethCandle);
|
|
7786
8397
|
const ethCandle = resolveEthCandle(candle, options.ethCandle);
|
|
7787
8398
|
indicatorsState.setCurrentBar(candle, btcCandle, ethCandle);
|
|
@@ -7952,6 +8563,13 @@ var createStrategyRuntime = ({
|
|
|
7952
8563
|
});
|
|
7953
8564
|
const signal = decision.signal;
|
|
7954
8565
|
if (signal) {
|
|
8566
|
+
if (portfolio)
|
|
8567
|
+
signal.additionalIndicators = {
|
|
8568
|
+
...signal.additionalIndicators,
|
|
8569
|
+
portfolioContext: await portfolioContext(
|
|
8570
|
+
env === "BACKTEST" || env === "PARITY" ? decision.entryContext.timestamp : Date.now()
|
|
8571
|
+
)
|
|
8572
|
+
};
|
|
7955
8573
|
if (runtimeLineage) signal.runtimeLineage = runtimeLineage;
|
|
7956
8574
|
if (universe) signal.universe = universe;
|
|
7957
8575
|
if (assetClass) signal.assetClass = assetClass;
|
|
@@ -8191,6 +8809,8 @@ var createStrategyRuntime = ({
|
|
|
8191
8809
|
});
|
|
8192
8810
|
}
|
|
8193
8811
|
return executeEntryDecision({
|
|
8812
|
+
portfolio,
|
|
8813
|
+
portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
|
|
8194
8814
|
connector,
|
|
8195
8815
|
symbol,
|
|
8196
8816
|
decision,
|