@tradejs/node 3.1.29-beta.289 → 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 +2504 -1883
- package/dist/backtest.mjs +3 -2
- package/dist/{chunk-KYGJBMKE.mjs → chunk-HRON2EAI.mjs} +432 -40
- 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 +837 -775
- package/dist/cli.mjs +4 -3
- package/dist/diagnostics.js +2532 -1911
- package/dist/diagnostics.mjs +4 -3
- package/dist/mcp.d.mts +4 -2
- package/dist/mcp.d.ts +4 -2
- package/dist/mcp.js +2614 -1971
- package/dist/mcp.mjs +27 -5
- 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 +2091 -1470
- package/dist/registry.mjs +3 -2
- package/dist/runtimeDashboard.js +2255 -1632
- package/dist/runtimeDashboard.mjs +4 -3
- package/dist/runtimeStrategies.js +2417 -1796
- package/dist/runtimeStrategies.mjs +4 -3
- package/dist/strategies.d.mts +3 -1
- package/dist/strategies.d.ts +3 -1
- package/dist/strategies.js +2106 -1485
- 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?.({
|
|
@@ -2635,541 +2696,219 @@ var runAiPromptLocal = async (signal, options = {}) => {
|
|
|
2635
2696
|
takeProfitPrice: approvalAllowedNow ? signal.prices?.takeProfitPrice ?? null : null,
|
|
2636
2697
|
stopLossPrice: approvalAllowedNow ? signal.prices?.stopLossPrice ?? null : null
|
|
2637
2698
|
},
|
|
2638
|
-
payload
|
|
2639
|
-
);
|
|
2640
|
-
};
|
|
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
|
-
if (limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9))
|
|
2980
|
-
throw new EntrySubmissionValidationError(
|
|
2981
|
-
"ENTRY_RISK_EXCEEDS_MAX_LOSS_AT_SUBMISSION"
|
|
2982
|
-
);
|
|
2983
|
-
return { netRiskRatio, plannedLossValue };
|
|
2984
|
-
};
|
|
2985
|
-
|
|
2986
|
-
// src/strategy/entrySubmissionValidation.ts
|
|
2987
|
-
var MAX_ENTRY_QUOTE_AGE_MS = 5e3;
|
|
2988
|
-
var toFiniteNumberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
2989
|
-
var getOrderArrivalSnapshot = async ({
|
|
2990
|
-
connector,
|
|
2991
|
-
symbol,
|
|
2992
|
-
requireTimestamp = false
|
|
2993
|
-
}) => {
|
|
2994
|
-
if (typeof connector.getTopOfBookTicker !== "function") {
|
|
2995
|
-
return {
|
|
2996
|
-
arrivalSnapshotTime: Date.now(),
|
|
2997
|
-
arrivalSource: "unavailable",
|
|
2998
|
-
bid: null,
|
|
2999
|
-
ask: null,
|
|
3000
|
-
arrivalMid: null,
|
|
3001
|
-
spreadBps: null
|
|
3002
|
-
};
|
|
3003
|
-
}
|
|
3004
|
-
try {
|
|
3005
|
-
const ticker = await connector.getTopOfBookTicker(symbol);
|
|
3006
|
-
const arrivalSnapshotTime = toFiniteNumberOrNull(ticker?.timestamp);
|
|
3007
|
-
const timestampUnavailable = requireTimestamp && arrivalSnapshotTime == null;
|
|
3008
|
-
const bid = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.bidPrice);
|
|
3009
|
-
const ask = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.askPrice);
|
|
3010
|
-
const arrivalMid = bid != null && ask != null ? (bid + ask) / 2 : null;
|
|
3011
|
-
const spreadBps = bid != null && ask != null && arrivalMid != null && arrivalMid > 0 ? (ask - bid) / arrivalMid * 1e4 : null;
|
|
3012
|
-
return {
|
|
3013
|
-
arrivalSnapshotTime: arrivalSnapshotTime ?? Date.now(),
|
|
3014
|
-
arrivalSource: "top_of_book",
|
|
3015
|
-
bid,
|
|
3016
|
-
ask,
|
|
3017
|
-
arrivalMid,
|
|
3018
|
-
spreadBps
|
|
3019
|
-
};
|
|
3020
|
-
} catch (error) {
|
|
3021
|
-
import_logger2.logger.warn(
|
|
3022
|
-
"runtime order arrival snapshot failed: %s %s",
|
|
3023
|
-
symbol,
|
|
3024
|
-
error?.message || String(error)
|
|
3025
|
-
);
|
|
3026
|
-
return {
|
|
3027
|
-
arrivalSnapshotTime: Date.now(),
|
|
3028
|
-
arrivalSource: "top_of_book_error",
|
|
3029
|
-
bid: null,
|
|
3030
|
-
ask: null,
|
|
3031
|
-
arrivalMid: null,
|
|
3032
|
-
spreadBps: null
|
|
3033
|
-
};
|
|
3034
|
-
}
|
|
3035
|
-
};
|
|
3036
|
-
var validateEntryProtectionAtArrival = ({
|
|
3037
|
-
direction,
|
|
3038
|
-
signalPrice,
|
|
3039
|
-
bid,
|
|
3040
|
-
ask,
|
|
3041
|
-
arrivalMid,
|
|
3042
|
-
takeProfits,
|
|
3043
|
-
stopLossPrice
|
|
3044
|
-
}) => {
|
|
3045
|
-
const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
|
|
3046
|
-
const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
|
|
3047
|
-
if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
|
|
3048
|
-
throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
|
|
3049
|
-
}
|
|
3050
|
-
for (const takeProfit of takeProfits) {
|
|
3051
|
-
if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
|
|
3052
|
-
throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
|
|
3053
|
-
}
|
|
3054
|
-
}
|
|
3055
|
-
if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
|
|
3056
|
-
throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
|
|
3057
|
-
}
|
|
3058
|
-
};
|
|
3059
|
-
var createEntrySubmissionValidation = ({
|
|
3060
|
-
connector,
|
|
3061
|
-
symbol,
|
|
3062
|
-
direction,
|
|
3063
|
-
takeProfits,
|
|
3064
|
-
stopLossPrice,
|
|
3065
|
-
limits,
|
|
3066
|
-
onValidated
|
|
3067
|
-
}) => {
|
|
3068
|
-
if (!limits) return void 0;
|
|
3069
|
-
if (!connector.capabilities?.orderSubmissionValidation)
|
|
3070
|
-
throw new EntrySubmissionValidationError(
|
|
3071
|
-
"ENTRY_SUBMISSION_VALIDATION_UNSUPPORTED"
|
|
3072
|
-
);
|
|
3073
|
-
return async ({ qty, executionPrice }) => {
|
|
3074
|
-
const simulated = Reflect.get(connector, "__tradejsTestConnector") === true;
|
|
3075
|
-
const snapshot2 = simulated && executionPrice != null ? {
|
|
3076
|
-
arrivalSnapshotTime: Date.now(),
|
|
3077
|
-
arrivalSource: "simulation",
|
|
3078
|
-
bid: executionPrice,
|
|
3079
|
-
ask: executionPrice,
|
|
3080
|
-
arrivalMid: executionPrice,
|
|
3081
|
-
spreadBps: 0
|
|
3082
|
-
} : await getOrderArrivalSnapshot({
|
|
3083
|
-
connector,
|
|
3084
|
-
symbol,
|
|
3085
|
-
requireTimestamp: true
|
|
3086
|
-
});
|
|
3087
|
-
const age = Date.now() - snapshot2.arrivalSnapshotTime;
|
|
3088
|
-
if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
|
|
3089
|
-
throw new EntrySubmissionValidationError(
|
|
3090
|
-
"ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
|
|
3091
|
-
);
|
|
3092
|
-
if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
|
|
3093
|
-
throw new EntrySubmissionValidationError(
|
|
3094
|
-
"ENTRY_QUOTE_STALE_AT_SUBMISSION"
|
|
3095
|
-
);
|
|
3096
|
-
const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
|
|
3097
|
-
validateEntryProtectionAtArrival({
|
|
3098
|
-
direction,
|
|
3099
|
-
signalPrice: price,
|
|
3100
|
-
bid: snapshot2.bid,
|
|
3101
|
-
ask: snapshot2.ask,
|
|
3102
|
-
arrivalMid: snapshot2.arrivalMid,
|
|
3103
|
-
takeProfits,
|
|
3104
|
-
stopLossPrice
|
|
3105
|
-
});
|
|
3106
|
-
const prices = takeProfits.map((tp) => tp.price);
|
|
3107
|
-
const economics2 = validateEntryExecution({
|
|
3108
|
-
direction,
|
|
3109
|
-
price,
|
|
3110
|
-
qty,
|
|
3111
|
-
stopLossPrice,
|
|
3112
|
-
takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
|
|
3113
|
-
limits
|
|
3114
|
-
});
|
|
3115
|
-
onValidated?.(snapshot2, economics2);
|
|
3116
|
-
};
|
|
3117
|
-
};
|
|
3118
|
-
|
|
3119
|
-
// src/strategyAdapters/ml.ts
|
|
3120
|
-
var defaultMlAdapter = {
|
|
3121
|
-
normalizeStrategyConfig: (strategyConfig) => strategyConfig
|
|
3122
|
-
};
|
|
3123
|
-
var getStrategyMlAdapter = (strategy, profileId) => {
|
|
3124
|
-
const strategyAdapter = getStrategyProfileMlAdapter(
|
|
3125
|
-
getStrategyManifest(strategy),
|
|
3126
|
-
profileId
|
|
2699
|
+
payload
|
|
3127
2700
|
);
|
|
3128
|
-
if (!strategyAdapter) return defaultMlAdapter;
|
|
3129
|
-
return {
|
|
3130
|
-
...defaultMlAdapter,
|
|
3131
|
-
...strategyAdapter
|
|
3132
|
-
};
|
|
3133
2701
|
};
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
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
|
+
}
|
|
2715
|
+
);
|
|
2716
|
+
await (0, import_redis.setData)(import_redis.redisKeys.analysis(symbol, signal.signalId), content);
|
|
2717
|
+
return content;
|
|
3141
2718
|
};
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
2719
|
+
|
|
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
|
+
}
|
|
3151
2891
|
}
|
|
3152
2892
|
};
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
payload.context.strategyConfig,
|
|
3157
|
-
strategyName,
|
|
3158
|
-
profileId
|
|
3159
|
-
)
|
|
3160
|
-
} : void 0;
|
|
2893
|
+
}
|
|
2894
|
+
function createMemoryPortfolioStore() {
|
|
2895
|
+
let state = null;
|
|
3161
2896
|
return {
|
|
3162
|
-
|
|
3163
|
-
|
|
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
|
+
}
|
|
3164
2903
|
};
|
|
3165
|
-
}
|
|
2904
|
+
}
|
|
3166
2905
|
|
|
3167
2906
|
// src/runtimeJournal.ts
|
|
3168
2907
|
var import_node_crypto = require("crypto");
|
|
3169
2908
|
var import_constants = require("@tradejs/core/constants");
|
|
3170
2909
|
var import_time = require("@tradejs/core/time");
|
|
3171
2910
|
var import_trade = require("@tradejs/core/trade");
|
|
3172
|
-
var
|
|
2911
|
+
var import_logger2 = require("@tradejs/infra/logger");
|
|
3173
2912
|
var import_redis2 = require("@tradejs/infra/redis");
|
|
3174
2913
|
var now = () => Date.now();
|
|
3175
2914
|
var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
|
|
@@ -3221,195 +2960,988 @@ var recordRuntimeTradeOpen = async (params) => {
|
|
|
3221
2960
|
record,
|
|
3222
2961
|
{ expire: 0 }
|
|
3223
2962
|
),
|
|
3224
|
-
(0, import_redis2.setData)(
|
|
3225
|
-
import_redis2.redisKeys.runtimeActiveTrade(userName, record.symbol, runtimeScopeId),
|
|
3226
|
-
{ orderId: record.orderId },
|
|
3227
|
-
{ 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
|
+
)
|
|
3228
3156
|
)
|
|
3229
3157
|
]);
|
|
3230
3158
|
} catch (error) {
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3159
|
+
if (expectedOrderId) throw error;
|
|
3160
|
+
import_logger2.logger.error(
|
|
3161
|
+
"runtime trade close journal failed: %s %s",
|
|
3162
|
+
symbol,
|
|
3234
3163
|
error?.message || String(error)
|
|
3235
3164
|
);
|
|
3236
3165
|
}
|
|
3237
|
-
return
|
|
3166
|
+
return next;
|
|
3238
3167
|
};
|
|
3239
|
-
|
|
3240
|
-
|
|
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 = {
|
|
3241
3270
|
userName,
|
|
3242
|
-
strategy,
|
|
3243
|
-
symbol,
|
|
3244
|
-
direction,
|
|
3245
|
-
resultingQty,
|
|
3246
|
-
resultingEntryPrice,
|
|
3247
|
-
addedQty,
|
|
3248
|
-
addedEntryPrice,
|
|
3249
|
-
entryTimestamp,
|
|
3250
|
-
fee,
|
|
3251
3271
|
accountId,
|
|
3252
3272
|
deploymentId
|
|
3253
|
-
}
|
|
3254
|
-
|
|
3255
|
-
|
|
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);
|
|
3256
3596
|
}
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
});
|
|
3263
|
-
if (!existing || existing.strategy !== strategy || existing.direction !== direction) {
|
|
3264
|
-
return null;
|
|
3597
|
+
let record;
|
|
3598
|
+
try {
|
|
3599
|
+
record = await promise;
|
|
3600
|
+
} finally {
|
|
3601
|
+
pending.delete(pendingKey);
|
|
3265
3602
|
}
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
openFee,
|
|
3282
|
-
totalFee,
|
|
3283
|
-
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
|
+
}
|
|
3284
3618
|
};
|
|
3285
|
-
|
|
3619
|
+
};
|
|
3620
|
+
var assessSignalWithJev = async ({
|
|
3621
|
+
signal,
|
|
3622
|
+
config,
|
|
3623
|
+
userName,
|
|
3624
|
+
projectRoot,
|
|
3625
|
+
strict,
|
|
3626
|
+
scope = "runtime"
|
|
3627
|
+
}) => {
|
|
3628
|
+
let assessment;
|
|
3286
3629
|
try {
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
next,
|
|
3295
|
-
{ expire: 0 }
|
|
3296
|
-
)
|
|
3297
|
-
]);
|
|
3630
|
+
const input = buildJevInput(signal);
|
|
3631
|
+
({ assessment } = await evaluateJevInput({
|
|
3632
|
+
input,
|
|
3633
|
+
config,
|
|
3634
|
+
userName,
|
|
3635
|
+
projectRoot
|
|
3636
|
+
}));
|
|
3298
3637
|
} catch (error) {
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
null
|
|
3315
|
-
);
|
|
3316
|
-
const orderId = String(activeRef?.orderId || "").trim();
|
|
3317
|
-
if (!orderId) {
|
|
3318
|
-
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
|
+
};
|
|
3319
3653
|
}
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
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
|
|
3323
3677
|
);
|
|
3324
|
-
|
|
3325
|
-
await (0, import_redis2.delKey)(
|
|
3326
|
-
import_redis2.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
|
|
3327
|
-
);
|
|
3328
|
-
return null;
|
|
3329
|
-
}
|
|
3330
|
-
return existing;
|
|
3678
|
+
return assessment;
|
|
3331
3679
|
};
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
deploymentId
|
|
3352
|
-
});
|
|
3353
|
-
if (!existing) {
|
|
3354
|
-
return null;
|
|
3355
|
-
}
|
|
3356
|
-
const orderId = existing.orderId;
|
|
3357
|
-
if (strategy && existing.strategy !== strategy) {
|
|
3358
|
-
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";
|
|
3359
3699
|
}
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
const
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
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
|
|
3377
3754
|
};
|
|
3378
|
-
|
|
3379
|
-
|
|
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
|
+
}
|
|
3380
3775
|
try {
|
|
3381
|
-
await
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
),
|
|
3397
|
-
(0, import_redis2.delKey)(
|
|
3398
|
-
import_redis2.redisKeys.runtimeActiveTrade(
|
|
3399
|
-
userName,
|
|
3400
|
-
symbol,
|
|
3401
|
-
deploymentId ?? accountId
|
|
3402
|
-
)
|
|
3403
|
-
)
|
|
3404
|
-
]);
|
|
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
|
+
};
|
|
3405
3791
|
} catch (error) {
|
|
3406
|
-
import_logger3.logger.
|
|
3407
|
-
"runtime
|
|
3792
|
+
import_logger3.logger.warn(
|
|
3793
|
+
"runtime order arrival snapshot failed: %s %s",
|
|
3408
3794
|
symbol,
|
|
3409
3795
|
error?.message || String(error)
|
|
3410
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
|
+
};
|
|
3411
3805
|
}
|
|
3412
|
-
|
|
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
|
+
};
|
|
3413
3945
|
};
|
|
3414
3946
|
|
|
3415
3947
|
// src/strategyHelpers/marketContextStages.ts
|
|
@@ -3421,7 +3953,7 @@ var import_logger4 = require("@tradejs/infra/logger");
|
|
|
3421
3953
|
var import_strategies = require("@tradejs/core/strategies");
|
|
3422
3954
|
|
|
3423
3955
|
// src/binanceBreadthUniverses.ts
|
|
3424
|
-
var
|
|
3956
|
+
var import_node_crypto3 = require("crypto");
|
|
3425
3957
|
|
|
3426
3958
|
// src/config/binanceBreadthUniverses.json
|
|
3427
3959
|
var binanceBreadthUniverses_default = {
|
|
@@ -3660,7 +4192,7 @@ var BINANCE_BREADTH_UNIVERSE_KEYS = [
|
|
|
3660
4192
|
"top50",
|
|
3661
4193
|
"top100"
|
|
3662
4194
|
];
|
|
3663
|
-
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);
|
|
3664
4196
|
var normalizeSymbols = (symbols) => symbols.map((symbol) => symbol.trim().toUpperCase()).filter(Boolean);
|
|
3665
4197
|
var buildBinanceBreadthUniverseSnapshot = ({
|
|
3666
4198
|
rankedSymbols,
|
|
@@ -4836,7 +5368,7 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
4836
5368
|
};
|
|
4837
5369
|
|
|
4838
5370
|
// src/strategyHelpers/derivativesContext.ts
|
|
4839
|
-
var
|
|
5371
|
+
var import_indicators3 = require("@tradejs/core/indicators");
|
|
4840
5372
|
var import_data2 = require("@tradejs/core/data");
|
|
4841
5373
|
var import_strategies3 = require("@tradejs/core/strategies");
|
|
4842
5374
|
var import_constants2 = require("@tradejs/core/constants");
|
|
@@ -4870,7 +5402,7 @@ var parseLookbackMs = () => {
|
|
|
4870
5402
|
};
|
|
4871
5403
|
var withHourlyFallbackRows = (rowsByInterval) => ({
|
|
4872
5404
|
"15m": rowsByInterval["15m"] ?? [],
|
|
4873
|
-
"1h": (0,
|
|
5405
|
+
"1h": (0, import_indicators3.buildCoinalyzeHourlyRowsWithFallback)({
|
|
4874
5406
|
rows15m: rowsByInterval["15m"],
|
|
4875
5407
|
fallbackRows1h: rowsByInterval["1h"]
|
|
4876
5408
|
})
|
|
@@ -4998,7 +5530,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
4998
5530
|
const targetSymbol = normalizeSymbol(signal.symbol);
|
|
4999
5531
|
const lookbackMs = parseLookbackMs();
|
|
5000
5532
|
const decisionTimeMs = signal.timestamp + (0, import_data2.intervalToMs)(signal.interval);
|
|
5001
|
-
const derivativesEndMs = (0,
|
|
5533
|
+
const derivativesEndMs = (0, import_indicators3.getLastClosedDerivativesBarStartMs)(
|
|
5002
5534
|
decisionTimeMs,
|
|
5003
5535
|
"15m"
|
|
5004
5536
|
);
|
|
@@ -5013,7 +5545,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
5013
5545
|
});
|
|
5014
5546
|
return [
|
|
5015
5547
|
symbol,
|
|
5016
|
-
(0,
|
|
5548
|
+
(0, import_indicators3.buildDerivativesContext)({
|
|
5017
5549
|
symbol,
|
|
5018
5550
|
direction: signal.direction,
|
|
5019
5551
|
timestamp: derivativesEndMs,
|
|
@@ -5040,7 +5572,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
5040
5572
|
lookbackMs,
|
|
5041
5573
|
...params.abortSignal ? { signal: params.abortSignal } : {}
|
|
5042
5574
|
});
|
|
5043
|
-
const context = (0,
|
|
5575
|
+
const context = (0, import_indicators3.buildDerivativesContext)({
|
|
5044
5576
|
symbol: targetSymbol,
|
|
5045
5577
|
direction: signal.direction,
|
|
5046
5578
|
timestamp: derivativesEndMs,
|
|
@@ -5088,7 +5620,7 @@ var import_hyperliquidWhales2 = require("@tradejs/infra/timescale/hyperliquidWha
|
|
|
5088
5620
|
var import_logger7 = require("@tradejs/infra/logger");
|
|
5089
5621
|
|
|
5090
5622
|
// src/hyperliquidWhaleUniverse.ts
|
|
5091
|
-
var
|
|
5623
|
+
var import_node_crypto4 = require("crypto");
|
|
5092
5624
|
|
|
5093
5625
|
// src/config/hyperliquidPerpUniverse.json
|
|
5094
5626
|
var hyperliquidPerpUniverse_default = {
|
|
@@ -5260,7 +5792,7 @@ var hyperliquidWhales_default = {
|
|
|
5260
5792
|
};
|
|
5261
5793
|
|
|
5262
5794
|
// src/hyperliquidWhaleUniverse.ts
|
|
5263
|
-
var fingerprint2 = (value) => (0,
|
|
5795
|
+
var fingerprint2 = (value) => (0, import_node_crypto4.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
|
|
5264
5796
|
var validateUnique = (values, label) => {
|
|
5265
5797
|
if (new Set(values).size !== values.length) {
|
|
5266
5798
|
throw new Error(`Hyperliquid ${label} snapshot contains duplicates`);
|
|
@@ -5751,6 +6283,7 @@ var enrichSignalWithMarketContextStages = async ({
|
|
|
5751
6283
|
})
|
|
5752
6284
|
});
|
|
5753
6285
|
}
|
|
6286
|
+
await enrichSignalWithTickerProfile(signal);
|
|
5754
6287
|
const results = [];
|
|
5755
6288
|
for (const stage of stages) {
|
|
5756
6289
|
if (abortSignal?.aborted) break;
|
|
@@ -5798,12 +6331,12 @@ var findReplayAiAnalysis = ({
|
|
|
5798
6331
|
direction,
|
|
5799
6332
|
ai
|
|
5800
6333
|
}) => {
|
|
5801
|
-
const
|
|
5802
|
-
if (!Array.isArray(
|
|
6334
|
+
const snapshots2 = ai?.replayAnalyses;
|
|
6335
|
+
if (!Array.isArray(snapshots2) || !snapshots2.length) {
|
|
5803
6336
|
return void 0;
|
|
5804
6337
|
}
|
|
5805
6338
|
let best = null;
|
|
5806
|
-
for (const snapshot2 of
|
|
6339
|
+
for (const snapshot2 of snapshots2) {
|
|
5807
6340
|
if (snapshot2.symbol !== signal.symbol || snapshot2.direction !== direction || snapshot2.strategy && snapshot2.strategy !== signal.strategy) {
|
|
5808
6341
|
continue;
|
|
5809
6342
|
}
|
|
@@ -5976,7 +6509,9 @@ var executeEntryOrder = async ({
|
|
|
5976
6509
|
beforePlaceOrder,
|
|
5977
6510
|
recordRuntimeTrade = true,
|
|
5978
6511
|
leverage,
|
|
5979
|
-
executionLimits
|
|
6512
|
+
executionLimits,
|
|
6513
|
+
markPortfolioSubmitted,
|
|
6514
|
+
rejectPortfolioSubmission
|
|
5980
6515
|
}) => {
|
|
5981
6516
|
await beforePlaceOrder?.();
|
|
5982
6517
|
const previousPosition = positionIntent === "increase" ? await connector.getPosition(symbol) : null;
|
|
@@ -6009,12 +6544,12 @@ var executeEntryOrder = async ({
|
|
|
6009
6544
|
if (executionLimits?.maxLossValue != null) {
|
|
6010
6545
|
signal.riskDecision = {
|
|
6011
6546
|
stage: "order_plan",
|
|
6012
|
-
status: "approved",
|
|
6013
|
-
reason: "PLANNED_LOSS_WITHIN_MAX_LOSS_VALUE",
|
|
6547
|
+
status: economics2.riskExceeded ? "warning" : "approved",
|
|
6548
|
+
reason: economics2.riskExceeded ? "ENTRY_RISK_EXCEEDS_MAX_LOSS_AT_SUBMISSION" : "PLANNED_LOSS_WITHIN_MAX_LOSS_VALUE",
|
|
6014
6549
|
plannedLossValue: economics2.plannedLossValue,
|
|
6015
6550
|
maxLossValue: executionLimits.maxLossValue,
|
|
6016
6551
|
limitUtilization: economics2.plannedLossValue / executionLimits.maxLossValue,
|
|
6017
|
-
enforced:
|
|
6552
|
+
enforced: false
|
|
6018
6553
|
};
|
|
6019
6554
|
}
|
|
6020
6555
|
}
|
|
@@ -6049,7 +6584,13 @@ var executeEntryOrder = async ({
|
|
|
6049
6584
|
...typeof leverage === "number" && Number.isFinite(leverage) ? { leverage } : {},
|
|
6050
6585
|
orderId,
|
|
6051
6586
|
signal,
|
|
6052
|
-
...
|
|
6587
|
+
...rejectPortfolioSubmission ? { onRejected: rejectPortfolioSubmission } : {},
|
|
6588
|
+
...beforeSubmit || markPortfolioSubmitted ? {
|
|
6589
|
+
beforeSubmit: async (context) => {
|
|
6590
|
+
await beforeSubmit?.(context);
|
|
6591
|
+
await markPortfolioSubmitted?.(context.qty);
|
|
6592
|
+
}
|
|
6593
|
+
} : {}
|
|
6053
6594
|
});
|
|
6054
6595
|
const orderAckTime = Date.now();
|
|
6055
6596
|
const placedQty = typeof signal.orderQty === "number" && Number.isFinite(signal.orderQty) && signal.orderQty > 0 ? signal.orderQty : qty;
|
|
@@ -7037,7 +7578,7 @@ var handleProtectDecision = async ({
|
|
|
7037
7578
|
}
|
|
7038
7579
|
return decision.code;
|
|
7039
7580
|
};
|
|
7040
|
-
var
|
|
7581
|
+
var executeAdmittedEntryDecision = async ({
|
|
7041
7582
|
connector,
|
|
7042
7583
|
symbol,
|
|
7043
7584
|
decision,
|
|
@@ -7051,7 +7592,9 @@ var executeEntryDecision = async ({
|
|
|
7051
7592
|
ai,
|
|
7052
7593
|
recordRuntimeJournal,
|
|
7053
7594
|
invokeStageHooks,
|
|
7054
|
-
notifyRuntimeError
|
|
7595
|
+
notifyRuntimeError,
|
|
7596
|
+
markPortfolioSubmitted,
|
|
7597
|
+
rejectPortfolioSubmission
|
|
7055
7598
|
}) => {
|
|
7056
7599
|
const signal = decision.signal;
|
|
7057
7600
|
const beforePlaceOrder = async () => {
|
|
@@ -7098,6 +7641,8 @@ var executeEntryDecision = async ({
|
|
|
7098
7641
|
...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {},
|
|
7099
7642
|
signal,
|
|
7100
7643
|
beforePlaceOrder,
|
|
7644
|
+
markPortfolioSubmitted,
|
|
7645
|
+
rejectPortfolioSubmission,
|
|
7101
7646
|
recordRuntimeTrade: recordRuntimeJournal,
|
|
7102
7647
|
executionLimits: resolveEntryExecutionLimits(
|
|
7103
7648
|
hookCtx.strategyConfig,
|
|
@@ -7238,6 +7783,37 @@ var executeEntryDecision = async ({
|
|
|
7238
7783
|
}
|
|
7239
7784
|
return signal ?? decision.code;
|
|
7240
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
|
+
}
|
|
7241
7817
|
|
|
7242
7818
|
// src/strategyRuntime.ts
|
|
7243
7819
|
var cloneWithPropertyDescriptors = (value) => Object.create(
|
|
@@ -7265,6 +7841,7 @@ var createStrategyRuntime = ({
|
|
|
7265
7841
|
};
|
|
7266
7842
|
const creator = async ({
|
|
7267
7843
|
userName,
|
|
7844
|
+
connectorName,
|
|
7268
7845
|
config: baseConfig,
|
|
7269
7846
|
symbol,
|
|
7270
7847
|
universe: requestedUniverse,
|
|
@@ -7303,6 +7880,17 @@ var createStrategyRuntime = ({
|
|
|
7303
7880
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
7304
7881
|
const projectHooks = projectConfig.hooks;
|
|
7305
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;
|
|
7306
7894
|
const researchPolicyValue = baseConfig?.RESEARCH_ENTRY_POLICY ?? config.RESEARCH_ENTRY_POLICY;
|
|
7307
7895
|
const researchEntryPolicy = researchPolicyValue == null ? void 0 : parseResearchEntryPolicy(researchPolicyValue);
|
|
7308
7896
|
if (researchEntryPolicy && (env !== "BACKTEST" || Reflect.get(connector, "__tradejsTestConnector") !== true))
|
|
@@ -7340,6 +7928,9 @@ var createStrategyRuntime = ({
|
|
|
7340
7928
|
});
|
|
7341
7929
|
const strategyPolicyProfile = getPolicyProfile();
|
|
7342
7930
|
const indicatorPeriods = (0, import_strategies6.buildDefaultIndicatorPeriods)(config);
|
|
7931
|
+
const strategyConfig = { ...config };
|
|
7932
|
+
delete strategyConfig.RECORD_RUNTIME_TRADES;
|
|
7933
|
+
delete strategyConfig.SIMULATE_ORDERS;
|
|
7343
7934
|
const hookBase = {
|
|
7344
7935
|
connector,
|
|
7345
7936
|
strategyName,
|
|
@@ -7350,7 +7941,7 @@ var createStrategyRuntime = ({
|
|
|
7350
7941
|
accountId,
|
|
7351
7942
|
deploymentId,
|
|
7352
7943
|
policyProfileId: strategyPolicyProfile?.id ?? requestedPolicyProfileId,
|
|
7353
|
-
strategyConfig
|
|
7944
|
+
strategyConfig,
|
|
7354
7945
|
env,
|
|
7355
7946
|
isConfigFromBacktest
|
|
7356
7947
|
};
|
|
@@ -7418,7 +8009,7 @@ var createStrategyRuntime = ({
|
|
|
7418
8009
|
);
|
|
7419
8010
|
}
|
|
7420
8011
|
};
|
|
7421
|
-
const invokeHook = async (stage, hook, params, errorContext = {}) => {
|
|
8012
|
+
const invokeHook = async (stage, hook, params, errorContext = {}, failureResult) => {
|
|
7422
8013
|
if (!hook) {
|
|
7423
8014
|
return void 0;
|
|
7424
8015
|
}
|
|
@@ -7438,17 +8029,18 @@ var createStrategyRuntime = ({
|
|
|
7438
8029
|
entry: errorContext.entry,
|
|
7439
8030
|
market: errorContext.market
|
|
7440
8031
|
});
|
|
7441
|
-
return
|
|
8032
|
+
return failureResult;
|
|
7442
8033
|
}
|
|
7443
8034
|
};
|
|
7444
|
-
const invokeProjectHooks = async (stage, params, errorContext = {}) => {
|
|
8035
|
+
const invokeProjectHooks = async (stage, params, errorContext = {}, failureResult) => {
|
|
7445
8036
|
const results = [];
|
|
7446
8037
|
for (const hook of getProjectHookList(stage)) {
|
|
7447
8038
|
const result = await invokeHook(
|
|
7448
8039
|
stage,
|
|
7449
8040
|
hook,
|
|
7450
8041
|
params,
|
|
7451
|
-
errorContext
|
|
8042
|
+
errorContext,
|
|
8043
|
+
failureResult
|
|
7452
8044
|
);
|
|
7453
8045
|
if (result !== void 0) {
|
|
7454
8046
|
results.push(result);
|
|
@@ -7463,10 +8055,12 @@ var createStrategyRuntime = ({
|
|
|
7463
8055
|
return invokeHook(stage, hook, params, errorContext);
|
|
7464
8056
|
};
|
|
7465
8057
|
const invokeGateHooks = async (stage, hook, params, errorContext = {}) => {
|
|
8058
|
+
const failureResult = stage === "beforeEntryGate" ? { allow: false, reason: "ERROR" } : void 0;
|
|
7466
8059
|
const projectResults = await invokeProjectHooks(
|
|
7467
8060
|
stage,
|
|
7468
8061
|
params,
|
|
7469
|
-
errorContext
|
|
8062
|
+
errorContext,
|
|
8063
|
+
failureResult
|
|
7470
8064
|
);
|
|
7471
8065
|
const projectBlock = projectResults.find(
|
|
7472
8066
|
(result) => result?.allow === false
|
|
@@ -7478,7 +8072,8 @@ var createStrategyRuntime = ({
|
|
|
7478
8072
|
stage,
|
|
7479
8073
|
hook,
|
|
7480
8074
|
params,
|
|
7481
|
-
errorContext
|
|
8075
|
+
errorContext,
|
|
8076
|
+
failureResult
|
|
7482
8077
|
);
|
|
7483
8078
|
};
|
|
7484
8079
|
const applyProjectAfterCoreDecisionHooks = async ({
|
|
@@ -7594,6 +8189,20 @@ var createStrategyRuntime = ({
|
|
|
7594
8189
|
interval: decisionInterval
|
|
7595
8190
|
}) => {
|
|
7596
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
|
+
}
|
|
7597
8206
|
if (!coreContextRequirements.has("hyperliquidWhales")) {
|
|
7598
8207
|
return baseContext;
|
|
7599
8208
|
}
|
|
@@ -7624,11 +8233,8 @@ var createStrategyRuntime = ({
|
|
|
7624
8233
|
return enrichedBaseContext;
|
|
7625
8234
|
}
|
|
7626
8235
|
});
|
|
7627
|
-
const coreConfig = { ...config };
|
|
7628
|
-
delete coreConfig.RECORD_RUNTIME_TRADES;
|
|
7629
|
-
delete coreConfig.SIMULATE_ORDERS;
|
|
7630
8236
|
const core = await createCore({
|
|
7631
|
-
config:
|
|
8237
|
+
config: strategyConfig,
|
|
7632
8238
|
data,
|
|
7633
8239
|
strategyApi,
|
|
7634
8240
|
indicatorsState
|
|
@@ -7764,6 +8370,8 @@ var createStrategyRuntime = ({
|
|
|
7764
8370
|
runtime: pending2.runtime
|
|
7765
8371
|
});
|
|
7766
8372
|
return executeEntryDecision({
|
|
8373
|
+
portfolio,
|
|
8374
|
+
portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
|
|
7767
8375
|
connector,
|
|
7768
8376
|
symbol,
|
|
7769
8377
|
decision: pending2.decision,
|
|
@@ -7781,6 +8389,10 @@ var createStrategyRuntime = ({
|
|
|
7781
8389
|
});
|
|
7782
8390
|
};
|
|
7783
8391
|
const runWithDecisionOverride = async (candle, btcCandle, options = {}) => {
|
|
8392
|
+
if (portfolioContext)
|
|
8393
|
+
await portfolioContext(
|
|
8394
|
+
env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now()
|
|
8395
|
+
);
|
|
7784
8396
|
appendCurrentMarketData(candle, btcCandle, options.ethCandle);
|
|
7785
8397
|
const ethCandle = resolveEthCandle(candle, options.ethCandle);
|
|
7786
8398
|
indicatorsState.setCurrentBar(candle, btcCandle, ethCandle);
|
|
@@ -7951,6 +8563,13 @@ var createStrategyRuntime = ({
|
|
|
7951
8563
|
});
|
|
7952
8564
|
const signal = decision.signal;
|
|
7953
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
|
+
};
|
|
7954
8573
|
if (runtimeLineage) signal.runtimeLineage = runtimeLineage;
|
|
7955
8574
|
if (universe) signal.universe = universe;
|
|
7956
8575
|
if (assetClass) signal.assetClass = assetClass;
|
|
@@ -8190,6 +8809,8 @@ var createStrategyRuntime = ({
|
|
|
8190
8809
|
});
|
|
8191
8810
|
}
|
|
8192
8811
|
return executeEntryDecision({
|
|
8812
|
+
portfolio,
|
|
8813
|
+
portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
|
|
8193
8814
|
connector,
|
|
8194
8815
|
symbol,
|
|
8195
8816
|
decision,
|