@tradejs/node 3.1.29-beta.290 → 3.1.29-beta.292
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 +2594 -1904
- package/dist/backtest.mjs +3 -2
- package/dist/{chunk-PB2KHBGH.mjs → chunk-EVKQ4DI6.mjs} +461 -33
- package/dist/chunk-NJEYNGRU.mjs +226 -0
- package/dist/{chunk-UL5Z46JL.mjs → chunk-PYJWXAYR.mjs} +97 -30
- package/dist/{chunk-BO42QWAB.mjs → chunk-W6PPAADK.mjs} +1 -1
- package/dist/cli.js +834 -773
- package/dist/cli.mjs +1 -1
- package/dist/diagnostics.js +2611 -1921
- package/dist/diagnostics.mjs +4 -3
- package/dist/mcp.d.mts +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +2634 -1944
- package/dist/mcp.mjs +4 -3
- package/dist/portfolio.d.mts +28 -0
- package/dist/portfolio.d.ts +28 -0
- package/dist/portfolio.js +243 -0
- package/dist/portfolio.mjs +9 -0
- package/dist/registry.js +2614 -1924
- package/dist/registry.mjs +3 -2
- package/dist/runtimeDashboard.js +2335 -1643
- package/dist/runtimeDashboard.mjs +4 -3
- package/dist/runtimeStrategies.js +2615 -1925
- package/dist/runtimeStrategies.mjs +4 -3
- package/dist/strategies.d.mts +3 -1
- package/dist/strategies.d.ts +3 -1
- package/dist/strategies.js +2153 -1463
- 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?.({
|
|
@@ -2647,526 +2708,240 @@ var askAI = async (signal, options = {}) => {
|
|
|
2647
2708
|
humanPrompt: buildAiHumanPrompt(signal, payload)
|
|
2648
2709
|
},
|
|
2649
2710
|
{
|
|
2650
|
-
...options,
|
|
2651
|
-
signal,
|
|
2652
|
-
payload
|
|
2653
|
-
}
|
|
2654
|
-
);
|
|
2655
|
-
await (0, import_redis.setData)(import_redis.redisKeys.analysis(symbol, signal.signalId), content);
|
|
2656
|
-
return content;
|
|
2657
|
-
};
|
|
2658
|
-
|
|
2659
|
-
// src/strategyRuntime.ts
|
|
2660
|
-
var import_jev6 = require("@tradejs/core/jev");
|
|
2661
|
-
|
|
2662
|
-
// src/jev.ts
|
|
2663
|
-
var import_promises = __toESM(require("fs/promises"));
|
|
2664
|
-
var import_node_path = __toESM(require("path"));
|
|
2665
|
-
var import_jev4 = require("@tradejs/core/jev");
|
|
2666
|
-
var import_jev5 = require("@tradejs/infra/jev");
|
|
2667
|
-
var import_userSettings2 = require("@tradejs/infra/userSettings");
|
|
2668
|
-
init_jevInput();
|
|
2669
|
-
init_jevInput();
|
|
2670
|
-
init_jevTraining();
|
|
2671
|
-
var resolveJevProvider = async (userName) => {
|
|
2672
|
-
const settings = await (0, import_userSettings2.getUserSettings)(userName);
|
|
2673
|
-
const provider = {
|
|
2674
|
-
endpoint: settings.JEV_API_ENDPOINT || import_jev4.JEV_PROVIDERS[0].endpoint,
|
|
2675
|
-
model: settings.JEV_MODEL || import_jev4.JEV_PROVIDERS[0].model
|
|
2676
|
-
};
|
|
2677
|
-
(0, import_jev4.parseJevConfig)({ source: "provider", mode: "observe", provider });
|
|
2678
|
-
if (!settings.JEV_API_KEY)
|
|
2679
|
-
throw new Error("Configure the Jev API key in Account settings");
|
|
2680
|
-
return provider;
|
|
2681
|
-
};
|
|
2682
|
-
var emptyScores = () => Object.fromEntries(import_jev4.JEV_DIMENSIONS.map((key) => [key, null]));
|
|
2683
|
-
var pending = /* @__PURE__ */ new Map();
|
|
2684
|
-
var pendingResponses = /* @__PURE__ */ new Map();
|
|
2685
|
-
var evaluateJevInput = async ({
|
|
2686
|
-
input,
|
|
2687
|
-
config,
|
|
2688
|
-
userName,
|
|
2689
|
-
projectRoot
|
|
2690
|
-
}) => {
|
|
2691
|
-
config = (0, import_jev4.parseJevConfig)(config);
|
|
2692
|
-
const inputHash = (0, import_jev5.jevHash)(input);
|
|
2693
|
-
const questions = questionsForJevInput(input);
|
|
2694
|
-
const questionsHash = (0, import_jev5.jevHash)(questions);
|
|
2695
|
-
const base = {
|
|
2696
|
-
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
2697
|
-
schema: "tradejs-signal-assessment/v5",
|
|
2698
|
-
source: config.source,
|
|
2699
|
-
mode: config.mode,
|
|
2700
|
-
inputHash,
|
|
2701
|
-
questionsHash,
|
|
2702
|
-
geometryStatus: input.geometryStatus,
|
|
2703
|
-
levelsValid: typeof input.features["signal.validLevels"] === "boolean" ? input.features["signal.validLevels"] : null
|
|
2704
|
-
};
|
|
2705
|
-
if (!input.questions.length)
|
|
2706
|
-
return {
|
|
2707
|
-
assessment: {
|
|
2708
|
-
...base,
|
|
2709
|
-
status: "unavailable",
|
|
2710
|
-
model: "",
|
|
2711
|
-
scores: emptyScores(),
|
|
2712
|
-
confidence: emptyScores()
|
|
2713
|
-
}
|
|
2714
|
-
};
|
|
2715
|
-
if (config.source === "local") {
|
|
2716
|
-
const contents = await import_promises.default.readFile(
|
|
2717
|
-
import_node_path.default.resolve(projectRoot, config.modelFile),
|
|
2718
|
-
"utf8"
|
|
2719
|
-
);
|
|
2720
|
-
if ((0, import_jev5.jevFileHash)(contents) !== config.modelSha256)
|
|
2721
|
-
throw new Error("Local Jev gate checksum mismatch");
|
|
2722
|
-
const { validateJevGateModel: validateJevGateModel2 } = await Promise.resolve().then(() => (init_jevTraining(), jevTraining_exports));
|
|
2723
|
-
const model = validateJevGateModel2(JSON.parse(contents));
|
|
2724
|
-
if (model.strategy !== input.strategy || model.questionsHash !== (0, import_jev5.jevHash)(JEV_QUESTIONS))
|
|
2725
|
-
throw new Error("Local Jev gate strategy or question version mismatch");
|
|
2726
|
-
const scores = Object.fromEntries(
|
|
2727
|
-
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
2728
|
-
key,
|
|
2729
|
-
input.questions.includes(key) && model.trees[key] ? (0, import_jev4.predictJevTree)(model.trees[key], input.features) : null
|
|
2730
|
-
])
|
|
2731
|
-
);
|
|
2732
|
-
return {
|
|
2733
|
-
assessment: {
|
|
2734
|
-
...base,
|
|
2735
|
-
status: "available",
|
|
2736
|
-
model: config.modelSha256,
|
|
2737
|
-
scores,
|
|
2738
|
-
confidence: emptyScores()
|
|
2739
|
-
}
|
|
2740
|
-
};
|
|
2741
|
-
}
|
|
2742
|
-
const provider = config.provider ?? await resolveJevProvider(userName);
|
|
2743
|
-
const id = (0, import_jev5.jevHash)({ inputHash, questionsHash, provider });
|
|
2744
|
-
const dir = import_node_path.default.resolve(projectRoot, config.recordsDir ?? "data/ai/jev");
|
|
2745
|
-
const file = import_node_path.default.join(dir, "records", `${id}.json`);
|
|
2746
|
-
const getRecord = async () => {
|
|
2747
|
-
const existing = await (0, import_jev5.readJevArtifact)(file);
|
|
2748
|
-
if (existing) {
|
|
2749
|
-
if (existing.schema !== "tradejs-jev-record/v4" || existing.id !== id || existing.inputHash !== inputHash || existing.questionsHash !== questionsHash || (0, import_jev5.jevHash)(existing.input) !== inputHash || (0, import_jev5.jevHash)(existing.provider) !== (0, import_jev5.jevHash)(provider))
|
|
2750
|
-
throw new Error("Jev recording identity mismatch");
|
|
2751
|
-
(0, import_jev5.validateJevResponse)(existing.response, input.questions);
|
|
2752
|
-
for (const key of import_jev4.JEV_DIMENSIONS) {
|
|
2753
|
-
const expected = input.questions.includes(key) ? existing.response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null;
|
|
2754
|
-
if (existing.scores[key] !== expected)
|
|
2755
|
-
throw new Error("Jev recording scores do not match the response");
|
|
2756
|
-
}
|
|
2757
|
-
return existing;
|
|
2758
|
-
}
|
|
2759
|
-
if (config.source === "recorded")
|
|
2760
|
-
throw new Error(
|
|
2761
|
-
`Missing Jev recording ${id} for ${input.strategy} ${input.symbol} at ${new Date(input.timestamp).toISOString()}. Recorded mode requires the same signal-time facts and provider; a shorter backtest can change warm-up indicators or prior trade state.`
|
|
2762
|
-
);
|
|
2763
|
-
const settings = await (0, import_userSettings2.getUserSettings)(userName);
|
|
2764
|
-
if (!settings.JEV_API_KEY)
|
|
2765
|
-
throw new Error("Configure the Jev API key in Account settings");
|
|
2766
|
-
const apiKey = settings.JEV_API_KEY;
|
|
2767
|
-
const configuredProvider = await resolveJevProvider(userName);
|
|
2768
|
-
if (configuredProvider.endpoint !== provider.endpoint)
|
|
2769
|
-
throw new Error(
|
|
2770
|
-
"Jev account endpoint changed; update the frozen provider configuration"
|
|
2771
|
-
);
|
|
2772
|
-
const started = Date.now();
|
|
2773
|
-
const state = stateForJevInput(input);
|
|
2774
|
-
const responseId = (0, import_jev5.jevHash)({
|
|
2775
|
-
state,
|
|
2776
|
-
questionsHash,
|
|
2777
|
-
provider,
|
|
2778
|
-
user: (0, import_jev5.jevHash)(userName)
|
|
2779
|
-
});
|
|
2780
|
-
const responseFile = import_node_path.default.join(dir, "responses", `${responseId}.json`);
|
|
2781
|
-
let responsePromise = pendingResponses.get(responseFile);
|
|
2782
|
-
if (!responsePromise) {
|
|
2783
|
-
responsePromise = (async () => {
|
|
2784
|
-
const saved = await (0, import_jev5.readJevArtifact)(responseFile);
|
|
2785
|
-
if (saved) return (0, import_jev5.validateJevResponse)(saved, input.questions);
|
|
2786
|
-
const fresh = await (0, import_jev5.requestJev)(provider, apiKey, state, questions);
|
|
2787
|
-
await (0, import_jev5.writeJevArtifact)(responseFile, fresh);
|
|
2788
|
-
return fresh;
|
|
2789
|
-
})();
|
|
2790
|
-
pendingResponses.set(responseFile, responsePromise);
|
|
2791
|
-
}
|
|
2792
|
-
let response;
|
|
2793
|
-
try {
|
|
2794
|
-
response = await responsePromise;
|
|
2795
|
-
} finally {
|
|
2796
|
-
pendingResponses.delete(responseFile);
|
|
2797
|
-
}
|
|
2798
|
-
const scores = Object.fromEntries(
|
|
2799
|
-
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
2800
|
-
key,
|
|
2801
|
-
input.questions.includes(key) ? response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null
|
|
2802
|
-
])
|
|
2803
|
-
);
|
|
2804
|
-
const value = {
|
|
2805
|
-
schema: "tradejs-jev-record/v4",
|
|
2806
|
-
id,
|
|
2807
|
-
inputHash,
|
|
2808
|
-
questionsHash,
|
|
2809
|
-
provider,
|
|
2810
|
-
input,
|
|
2811
|
-
response,
|
|
2812
|
-
scores,
|
|
2813
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2814
|
-
elapsedMs: Date.now() - started
|
|
2815
|
-
};
|
|
2816
|
-
await (0, import_jev5.writeJevArtifact)(file, value);
|
|
2817
|
-
return await (0, import_jev5.readJevArtifact)(file);
|
|
2818
|
-
};
|
|
2819
|
-
const pendingKey = `${file}:${(0, import_jev5.jevHash)(userName)}`;
|
|
2820
|
-
let promise = pending.get(pendingKey);
|
|
2821
|
-
if (!promise) {
|
|
2822
|
-
promise = getRecord();
|
|
2823
|
-
pending.set(pendingKey, promise);
|
|
2824
|
-
}
|
|
2825
|
-
let record;
|
|
2826
|
-
try {
|
|
2827
|
-
record = await promise;
|
|
2828
|
-
} finally {
|
|
2829
|
-
pending.delete(pendingKey);
|
|
2830
|
-
}
|
|
2831
|
-
return {
|
|
2832
|
-
record,
|
|
2833
|
-
assessment: {
|
|
2834
|
-
...base,
|
|
2835
|
-
status: "available",
|
|
2836
|
-
recordId: id,
|
|
2837
|
-
model: record.response.model,
|
|
2838
|
-
scores: record.scores,
|
|
2839
|
-
confidence: Object.fromEntries(
|
|
2840
|
-
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
2841
|
-
key,
|
|
2842
|
-
record.response.answers[key]?.confidence ?? null
|
|
2843
|
-
])
|
|
2844
|
-
)
|
|
2845
|
-
}
|
|
2846
|
-
};
|
|
2847
|
-
};
|
|
2848
|
-
var assessSignalWithJev = async ({
|
|
2849
|
-
signal,
|
|
2850
|
-
config,
|
|
2851
|
-
userName,
|
|
2852
|
-
projectRoot,
|
|
2853
|
-
strict,
|
|
2854
|
-
scope = "runtime"
|
|
2855
|
-
}) => {
|
|
2856
|
-
let assessment;
|
|
2857
|
-
try {
|
|
2858
|
-
const input = buildJevInput(signal);
|
|
2859
|
-
({ assessment } = await evaluateJevInput({
|
|
2860
|
-
input,
|
|
2861
|
-
config,
|
|
2862
|
-
userName,
|
|
2863
|
-
projectRoot
|
|
2864
|
-
}));
|
|
2865
|
-
} catch (error) {
|
|
2866
|
-
if (strict) throw error;
|
|
2867
|
-
assessment = {
|
|
2868
|
-
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
2869
|
-
schema: "tradejs-signal-assessment/v5",
|
|
2870
|
-
source: config.source,
|
|
2871
|
-
mode: config.mode,
|
|
2872
|
-
status: "unavailable",
|
|
2873
|
-
inputHash: "",
|
|
2874
|
-
questionsHash: "",
|
|
2875
|
-
geometryStatus: "absent",
|
|
2876
|
-
levelsValid: null,
|
|
2877
|
-
model: config.provider?.model ?? config.modelSha256 ?? "",
|
|
2878
|
-
scores: emptyScores(),
|
|
2879
|
-
confidence: emptyScores()
|
|
2880
|
-
};
|
|
2881
|
-
}
|
|
2882
|
-
signal.assessment = assessment;
|
|
2883
|
-
signal.additionalIndicators = {
|
|
2884
|
-
...signal.additionalIndicators,
|
|
2885
|
-
jev: (0, import_jev4.jevAssessmentFeatures)(assessment)
|
|
2886
|
-
};
|
|
2887
|
-
const identity = {
|
|
2888
|
-
signalId: signal.signalId,
|
|
2889
|
-
strategy: signal.strategy,
|
|
2890
|
-
symbol: signal.symbol,
|
|
2891
|
-
timestamp: signal.timestamp,
|
|
2892
|
-
scope,
|
|
2893
|
-
user: (0, import_jev5.jevHash)(userName),
|
|
2894
|
-
config,
|
|
2895
|
-
assessment
|
|
2896
|
-
};
|
|
2897
|
-
await (0, import_jev5.writeJevArtifact)(
|
|
2898
|
-
import_node_path.default.resolve(
|
|
2899
|
-
projectRoot,
|
|
2900
|
-
config.recordsDir ?? "data/ai/jev",
|
|
2901
|
-
"decisions",
|
|
2902
|
-
`${(0, import_jev5.jevHash)(identity)}.json`
|
|
2903
|
-
),
|
|
2904
|
-
identity
|
|
2905
|
-
);
|
|
2906
|
-
return assessment;
|
|
2907
|
-
};
|
|
2908
|
-
|
|
2909
|
-
// src/strategyRuntime.ts
|
|
2910
|
-
var import_constants5 = require("@tradejs/core/constants");
|
|
2911
|
-
var import_strategies6 = require("@tradejs/core/strategies");
|
|
2912
|
-
var import_logger11 = require("@tradejs/infra/logger");
|
|
2913
|
-
|
|
2914
|
-
// src/strategyHelpers/runtime.ts
|
|
2915
|
-
var import_logger9 = require("@tradejs/infra/logger");
|
|
2916
|
-
var import_constants3 = require("@tradejs/core/constants");
|
|
2917
|
-
var import_ml2 = require("@tradejs/infra/ml");
|
|
2918
|
-
|
|
2919
|
-
// src/strategy/entrySubmissionValidation.ts
|
|
2920
|
-
var import_logger2 = require("@tradejs/infra/logger");
|
|
2921
|
-
|
|
2922
|
-
// src/strategy/entryExecutionValidation.ts
|
|
2923
|
-
var EntrySubmissionValidationError = class extends Error {
|
|
2924
|
-
constructor() {
|
|
2925
|
-
super(...arguments);
|
|
2926
|
-
this.name = "EntrySubmissionValidationError";
|
|
2927
|
-
}
|
|
2928
|
-
};
|
|
2929
|
-
var resolveEntryExecutionLimits = (config, direction) => {
|
|
2930
|
-
const side = config[direction];
|
|
2931
|
-
const minimum = side && typeof side === "object" && "minRiskRatio" in side ? side.minRiskRatio : void 0;
|
|
2932
|
-
const maximum = config.MAX_LOSS_VALUE;
|
|
2933
|
-
if (minimum == null && maximum == null) return void 0;
|
|
2934
|
-
const minimumNetRiskRatio = minimum == null ? void 0 : Number(minimum);
|
|
2935
|
-
const maxLossValue = maximum == null ? void 0 : Number(maximum);
|
|
2936
|
-
const feeRate = Number(config.RISK_FEE_RATE ?? 0);
|
|
2937
|
-
const slippage = Number(config.RISK_SLIPPAGE_BPS ?? 0);
|
|
2938
|
-
const impact = Number(config.RISK_MARKET_IMPACT_BPS ?? 0);
|
|
2939
|
-
const slippageBps = slippage + impact;
|
|
2940
|
-
if (minimumNetRiskRatio != null && (!Number.isFinite(minimumNetRiskRatio) || minimumNetRiskRatio < 0) || maxLossValue != null && (!Number.isFinite(maxLossValue) || maxLossValue <= 0) || !Number.isFinite(feeRate) || feeRate < 0 || feeRate >= 1 || !Number.isFinite(slippage) || slippage < 0 || !Number.isFinite(impact) || impact < 0 || !Number.isFinite(slippageBps) || slippageBps < 0 || slippageBps >= 1e4)
|
|
2941
|
-
throw new Error("INVALID_ENTRY_EXECUTION_LIMITS");
|
|
2942
|
-
return { minimumNetRiskRatio, maxLossValue, feeRate, slippageBps };
|
|
2943
|
-
};
|
|
2944
|
-
var validateEntryExecution = ({
|
|
2945
|
-
direction,
|
|
2946
|
-
price,
|
|
2947
|
-
qty,
|
|
2948
|
-
stopLossPrice,
|
|
2949
|
-
takeProfitPrice,
|
|
2950
|
-
limits
|
|
2951
|
-
}) => {
|
|
2952
|
-
if (!Number.isFinite(price) || price <= 0 || !Number.isFinite(qty) || qty <= 0 || stopLossPrice == null || !Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || !Number.isFinite(takeProfitPrice) || takeProfitPrice <= 0)
|
|
2953
|
-
throw new EntrySubmissionValidationError(
|
|
2954
|
-
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
2955
|
-
);
|
|
2956
|
-
const side = direction === "LONG" ? 1 : -1;
|
|
2957
|
-
if (side * (price - stopLossPrice) <= 0 || side * (takeProfitPrice - price) <= 0)
|
|
2958
|
-
throw new EntrySubmissionValidationError(
|
|
2959
|
-
"INVALID_ENTRY_PROTECTION_AT_SUBMISSION"
|
|
2960
|
-
);
|
|
2961
|
-
const slippage = limits.slippageBps / 1e4;
|
|
2962
|
-
const cost = (value, sign) => value * slippage + value * (1 + sign * slippage) * limits.feeRate;
|
|
2963
|
-
const entryCost = cost(price, side);
|
|
2964
|
-
const lossPerUnit = Math.abs(price - stopLossPrice) + entryCost + cost(stopLossPrice, -side);
|
|
2965
|
-
const rewardPerUnit = Math.max(
|
|
2966
|
-
0,
|
|
2967
|
-
Math.abs(takeProfitPrice - price) - entryCost - cost(takeProfitPrice, -side)
|
|
2968
|
-
);
|
|
2969
|
-
const netRiskRatio = rewardPerUnit / lossPerUnit;
|
|
2970
|
-
const plannedLossValue = qty * lossPerUnit;
|
|
2971
|
-
if (!Number.isFinite(netRiskRatio) || !Number.isFinite(plannedLossValue))
|
|
2972
|
-
throw new EntrySubmissionValidationError(
|
|
2973
|
-
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
2974
|
-
);
|
|
2975
|
-
if (limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio)
|
|
2976
|
-
throw new EntrySubmissionValidationError(
|
|
2977
|
-
"ENTRY_RR_BELOW_MIN_AT_SUBMISSION"
|
|
2978
|
-
);
|
|
2979
|
-
const riskExceeded = limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9);
|
|
2980
|
-
return { netRiskRatio, plannedLossValue, riskExceeded };
|
|
2981
|
-
};
|
|
2982
|
-
|
|
2983
|
-
// src/strategy/entrySubmissionValidation.ts
|
|
2984
|
-
var MAX_ENTRY_QUOTE_AGE_MS = 5e3;
|
|
2985
|
-
var toFiniteNumberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
2986
|
-
var getOrderArrivalSnapshot = async ({
|
|
2987
|
-
connector,
|
|
2988
|
-
symbol,
|
|
2989
|
-
requireTimestamp = false
|
|
2990
|
-
}) => {
|
|
2991
|
-
if (typeof connector.getTopOfBookTicker !== "function") {
|
|
2992
|
-
return {
|
|
2993
|
-
arrivalSnapshotTime: Date.now(),
|
|
2994
|
-
arrivalSource: "unavailable",
|
|
2995
|
-
bid: null,
|
|
2996
|
-
ask: null,
|
|
2997
|
-
arrivalMid: null,
|
|
2998
|
-
spreadBps: null
|
|
2999
|
-
};
|
|
3000
|
-
}
|
|
3001
|
-
try {
|
|
3002
|
-
const ticker = await connector.getTopOfBookTicker(symbol);
|
|
3003
|
-
const arrivalSnapshotTime = toFiniteNumberOrNull(ticker?.timestamp);
|
|
3004
|
-
const timestampUnavailable = requireTimestamp && arrivalSnapshotTime == null;
|
|
3005
|
-
const bid = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.bidPrice);
|
|
3006
|
-
const ask = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.askPrice);
|
|
3007
|
-
const arrivalMid = bid != null && ask != null ? (bid + ask) / 2 : null;
|
|
3008
|
-
const spreadBps = bid != null && ask != null && arrivalMid != null && arrivalMid > 0 ? (ask - bid) / arrivalMid * 1e4 : null;
|
|
3009
|
-
return {
|
|
3010
|
-
arrivalSnapshotTime: arrivalSnapshotTime ?? Date.now(),
|
|
3011
|
-
arrivalSource: "top_of_book",
|
|
3012
|
-
bid,
|
|
3013
|
-
ask,
|
|
3014
|
-
arrivalMid,
|
|
3015
|
-
spreadBps
|
|
3016
|
-
};
|
|
3017
|
-
} catch (error) {
|
|
3018
|
-
import_logger2.logger.warn(
|
|
3019
|
-
"runtime order arrival snapshot failed: %s %s",
|
|
3020
|
-
symbol,
|
|
3021
|
-
error?.message || String(error)
|
|
3022
|
-
);
|
|
3023
|
-
return {
|
|
3024
|
-
arrivalSnapshotTime: Date.now(),
|
|
3025
|
-
arrivalSource: "top_of_book_error",
|
|
3026
|
-
bid: null,
|
|
3027
|
-
ask: null,
|
|
3028
|
-
arrivalMid: null,
|
|
3029
|
-
spreadBps: null
|
|
3030
|
-
};
|
|
3031
|
-
}
|
|
3032
|
-
};
|
|
3033
|
-
var validateEntryProtectionAtArrival = ({
|
|
3034
|
-
direction,
|
|
3035
|
-
signalPrice,
|
|
3036
|
-
bid,
|
|
3037
|
-
ask,
|
|
3038
|
-
arrivalMid,
|
|
3039
|
-
takeProfits,
|
|
3040
|
-
stopLossPrice
|
|
3041
|
-
}) => {
|
|
3042
|
-
const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
|
|
3043
|
-
const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
|
|
3044
|
-
if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
|
|
3045
|
-
throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
|
|
3046
|
-
}
|
|
3047
|
-
for (const takeProfit of takeProfits) {
|
|
3048
|
-
if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
|
|
3049
|
-
throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
|
|
3050
|
-
}
|
|
3051
|
-
}
|
|
3052
|
-
if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
|
|
3053
|
-
throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
|
|
3054
|
-
}
|
|
3055
|
-
};
|
|
3056
|
-
var createEntrySubmissionValidation = ({
|
|
3057
|
-
connector,
|
|
3058
|
-
symbol,
|
|
3059
|
-
direction,
|
|
3060
|
-
takeProfits,
|
|
3061
|
-
stopLossPrice,
|
|
3062
|
-
limits,
|
|
3063
|
-
onValidated
|
|
3064
|
-
}) => {
|
|
3065
|
-
if (!limits) return void 0;
|
|
3066
|
-
if (!connector.capabilities?.orderSubmissionValidation)
|
|
3067
|
-
throw new EntrySubmissionValidationError(
|
|
3068
|
-
"ENTRY_SUBMISSION_VALIDATION_UNSUPPORTED"
|
|
3069
|
-
);
|
|
3070
|
-
return async ({ qty, executionPrice }) => {
|
|
3071
|
-
const simulated = Reflect.get(connector, "__tradejsTestConnector") === true;
|
|
3072
|
-
const snapshot2 = simulated && executionPrice != null ? {
|
|
3073
|
-
arrivalSnapshotTime: Date.now(),
|
|
3074
|
-
arrivalSource: "simulation",
|
|
3075
|
-
bid: executionPrice,
|
|
3076
|
-
ask: executionPrice,
|
|
3077
|
-
arrivalMid: executionPrice,
|
|
3078
|
-
spreadBps: 0
|
|
3079
|
-
} : await getOrderArrivalSnapshot({
|
|
3080
|
-
connector,
|
|
3081
|
-
symbol,
|
|
3082
|
-
requireTimestamp: true
|
|
3083
|
-
});
|
|
3084
|
-
const age = Date.now() - snapshot2.arrivalSnapshotTime;
|
|
3085
|
-
if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
|
|
3086
|
-
throw new EntrySubmissionValidationError(
|
|
3087
|
-
"ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
|
|
3088
|
-
);
|
|
3089
|
-
if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
|
|
3090
|
-
throw new EntrySubmissionValidationError(
|
|
3091
|
-
"ENTRY_QUOTE_STALE_AT_SUBMISSION"
|
|
3092
|
-
);
|
|
3093
|
-
const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
|
|
3094
|
-
validateEntryProtectionAtArrival({
|
|
3095
|
-
direction,
|
|
3096
|
-
signalPrice: price,
|
|
3097
|
-
bid: snapshot2.bid,
|
|
3098
|
-
ask: snapshot2.ask,
|
|
3099
|
-
arrivalMid: snapshot2.arrivalMid,
|
|
3100
|
-
takeProfits,
|
|
3101
|
-
stopLossPrice
|
|
3102
|
-
});
|
|
3103
|
-
const prices = takeProfits.map((tp) => tp.price);
|
|
3104
|
-
const economics2 = validateEntryExecution({
|
|
3105
|
-
direction,
|
|
3106
|
-
price,
|
|
3107
|
-
qty,
|
|
3108
|
-
stopLossPrice,
|
|
3109
|
-
takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
|
|
3110
|
-
limits
|
|
3111
|
-
});
|
|
3112
|
-
onValidated?.(snapshot2, economics2);
|
|
3113
|
-
};
|
|
3114
|
-
};
|
|
3115
|
-
|
|
3116
|
-
// src/strategyAdapters/ml.ts
|
|
3117
|
-
var defaultMlAdapter = {
|
|
3118
|
-
normalizeStrategyConfig: (strategyConfig) => strategyConfig
|
|
3119
|
-
};
|
|
3120
|
-
var getStrategyMlAdapter = (strategy, profileId) => {
|
|
3121
|
-
const strategyAdapter = getStrategyProfileMlAdapter(
|
|
3122
|
-
getStrategyManifest(strategy),
|
|
3123
|
-
profileId
|
|
2711
|
+
...options,
|
|
2712
|
+
signal,
|
|
2713
|
+
payload
|
|
2714
|
+
}
|
|
3124
2715
|
);
|
|
3125
|
-
|
|
3126
|
-
return
|
|
3127
|
-
...defaultMlAdapter,
|
|
3128
|
-
...strategyAdapter
|
|
3129
|
-
};
|
|
2716
|
+
await (0, import_redis.setData)(import_redis.redisKeys.analysis(symbol, signal.signalId), content);
|
|
2717
|
+
return content;
|
|
3130
2718
|
};
|
|
3131
2719
|
|
|
3132
|
-
// src/
|
|
3133
|
-
var
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
2720
|
+
// src/strategy/portfolioRuntime.ts
|
|
2721
|
+
var import_node_crypto2 = require("crypto");
|
|
2722
|
+
var import_portfolio2 = require("@tradejs/core/portfolio");
|
|
2723
|
+
var import_redis3 = require("@tradejs/infra/redis");
|
|
2724
|
+
|
|
2725
|
+
// src/portfolio.ts
|
|
2726
|
+
var import_portfolio = require("@tradejs/core/portfolio");
|
|
2727
|
+
function createPortfolioCoordinator({
|
|
2728
|
+
policy,
|
|
2729
|
+
strategy,
|
|
2730
|
+
store,
|
|
2731
|
+
execution
|
|
2732
|
+
}) {
|
|
2733
|
+
const fingerprint3 = (0, import_portfolio.portfolioFingerprint)(policy, strategy);
|
|
2734
|
+
let cache;
|
|
2735
|
+
async function transact(now2, change, force = false) {
|
|
2736
|
+
for (let attempt = 0; attempt < 16; attempt++) {
|
|
2737
|
+
const previous = await store.read();
|
|
2738
|
+
if (previous && previous.fingerprint !== fingerprint3)
|
|
2739
|
+
throw new Error(
|
|
2740
|
+
"Portfolio policy changed under existing state; reconcile the persisted scope before changing configuration"
|
|
2741
|
+
);
|
|
2742
|
+
const base = previous ?? (0, import_portfolio.createPortfolioState)(fingerprint3);
|
|
2743
|
+
const bucket = Math.floor(now2 / policy.observationIntervalMs);
|
|
2744
|
+
const observation = !force && cache?.revision === base.revision && cache.bucket === bucket ? cache.observation : await execution.observe(now2, base.reservations);
|
|
2745
|
+
const reconciled = (0, import_portfolio.reconcilePortfolio)(
|
|
2746
|
+
policy,
|
|
2747
|
+
strategy,
|
|
2748
|
+
base,
|
|
2749
|
+
observation
|
|
2750
|
+
);
|
|
2751
|
+
for (const intent of base.closing) {
|
|
2752
|
+
if (!observation.positions.some(
|
|
2753
|
+
(p) => p.symbol === intent.position.symbol
|
|
2754
|
+
))
|
|
2755
|
+
await execution.confirmClosed?.(intent, observation.asOf);
|
|
2756
|
+
}
|
|
2757
|
+
const changed = change(reconciled, observation);
|
|
2758
|
+
const next = { ...changed.state, revision: base.revision + 1 };
|
|
2759
|
+
if (await store.compare(previous, next)) {
|
|
2760
|
+
cache = { revision: next.revision, bucket, observation };
|
|
2761
|
+
return { state: next, observation, result: changed.result };
|
|
2762
|
+
}
|
|
2763
|
+
cache = void 0;
|
|
2764
|
+
}
|
|
2765
|
+
throw new Error("Portfolio state contention; entry was not submitted");
|
|
2766
|
+
}
|
|
2767
|
+
async function tick(now2, force = false) {
|
|
2768
|
+
const claimed = await transact(
|
|
2769
|
+
now2,
|
|
2770
|
+
(state, observation) => {
|
|
2771
|
+
const commands = (0, import_portfolio.portfolioCloseCommands)(policy, state, observation);
|
|
2772
|
+
return {
|
|
2773
|
+
state: {
|
|
2774
|
+
...state,
|
|
2775
|
+
closing: state.closing.map(
|
|
2776
|
+
(c) => commands.some((x) => x.intent.positionId === c.positionId) ? { ...c, lastAttemptAt: observation.asOf } : c
|
|
2777
|
+
)
|
|
2778
|
+
},
|
|
2779
|
+
result: commands
|
|
2780
|
+
};
|
|
2781
|
+
},
|
|
2782
|
+
force
|
|
2783
|
+
);
|
|
2784
|
+
for (const command of claimed.result)
|
|
2785
|
+
await execution.close(
|
|
2786
|
+
command.position,
|
|
2787
|
+
command.intent,
|
|
2788
|
+
claimed.observation.asOf
|
|
2789
|
+
);
|
|
2790
|
+
if (claimed.result.length) {
|
|
2791
|
+
cache = void 0;
|
|
2792
|
+
const refreshed = await transact(
|
|
2793
|
+
now2,
|
|
2794
|
+
(state) => ({ state, result: void 0 }),
|
|
2795
|
+
true
|
|
2796
|
+
);
|
|
2797
|
+
return (0, import_portfolio.buildPortfolioContext)(
|
|
2798
|
+
policy,
|
|
2799
|
+
strategy,
|
|
2800
|
+
refreshed.state,
|
|
2801
|
+
refreshed.observation
|
|
2802
|
+
);
|
|
2803
|
+
}
|
|
2804
|
+
return (0, import_portfolio.buildPortfolioContext)(
|
|
2805
|
+
policy,
|
|
2806
|
+
strategy,
|
|
2807
|
+
claimed.state,
|
|
2808
|
+
claimed.observation
|
|
2809
|
+
);
|
|
2810
|
+
}
|
|
2811
|
+
async function updateReservation(now2, id, submitted, qty) {
|
|
2812
|
+
return transact(
|
|
2813
|
+
now2,
|
|
2814
|
+
(s, o) => {
|
|
2815
|
+
const exists = s.reservations.some((r) => r.id === id);
|
|
2816
|
+
const blocked = submitted && (!exists || s.closing.length > 0 || o.asOf <= s.blockedThrough);
|
|
2817
|
+
return {
|
|
2818
|
+
state: {
|
|
2819
|
+
...s,
|
|
2820
|
+
reservations: s.reservations.flatMap(
|
|
2821
|
+
(r) => r.id !== id ? [r] : submitted && !blocked ? [{ ...r, submitted: true, qty: qty ?? r.qty }] : []
|
|
2822
|
+
)
|
|
2823
|
+
},
|
|
2824
|
+
result: !blocked
|
|
2825
|
+
};
|
|
2826
|
+
},
|
|
2827
|
+
true
|
|
2828
|
+
);
|
|
2829
|
+
}
|
|
2830
|
+
return {
|
|
2831
|
+
async context(now2) {
|
|
2832
|
+
const state = await store.read() ?? (0, import_portfolio.createPortfolioState)(fingerprint3);
|
|
2833
|
+
if (state.fingerprint !== fingerprint3)
|
|
2834
|
+
throw new Error("Portfolio policy changed under existing state");
|
|
2835
|
+
const observation = await execution.observe(now2, state.reservations);
|
|
2836
|
+
(0, import_portfolio.validatePortfolioObservation)(observation);
|
|
2837
|
+
return (0, import_portfolio.buildPortfolioContext)(policy, strategy, state, observation);
|
|
2838
|
+
},
|
|
2839
|
+
tick,
|
|
2840
|
+
async submit(now2, entry, send) {
|
|
2841
|
+
await tick(now2);
|
|
2842
|
+
const admitted = await transact(
|
|
2843
|
+
now2,
|
|
2844
|
+
(s, o) => {
|
|
2845
|
+
const r = (0, import_portfolio.reservePortfolioEntry)(policy, strategy, s, o, {
|
|
2846
|
+
...entry,
|
|
2847
|
+
submitted: false,
|
|
2848
|
+
oppositeHandled: false
|
|
2849
|
+
});
|
|
2850
|
+
return { state: r.state, result: r.reason };
|
|
2851
|
+
},
|
|
2852
|
+
true
|
|
2853
|
+
);
|
|
2854
|
+
if (admitted.result) return { reason: admitted.result };
|
|
2855
|
+
let submitted = false;
|
|
2856
|
+
let retry = 0;
|
|
2857
|
+
let attemptId = entry.id;
|
|
2858
|
+
let needsReservation = false;
|
|
2859
|
+
try {
|
|
2860
|
+
const value = await send(
|
|
2861
|
+
async (qty) => {
|
|
2862
|
+
if (!Number.isFinite(qty) || qty <= 0)
|
|
2863
|
+
throw new Error("Invalid submitted portfolio quantity");
|
|
2864
|
+
if (needsReservation) {
|
|
2865
|
+
attemptId = `${entry.id.slice(0, 30)}r${++retry}`;
|
|
2866
|
+
const admission = await transact(
|
|
2867
|
+
now2,
|
|
2868
|
+
(s, o) => {
|
|
2869
|
+
const r = (0, import_portfolio.reservePortfolioEntry)(policy, strategy, s, o, {
|
|
2870
|
+
...entry,
|
|
2871
|
+
id: attemptId,
|
|
2872
|
+
qty,
|
|
2873
|
+
submitted: true,
|
|
2874
|
+
oppositeHandled: false
|
|
2875
|
+
});
|
|
2876
|
+
return { state: r.state, result: r.reason };
|
|
2877
|
+
},
|
|
2878
|
+
true
|
|
2879
|
+
);
|
|
2880
|
+
if (admission.result) throw new Error(admission.result);
|
|
2881
|
+
needsReservation = false;
|
|
2882
|
+
submitted = true;
|
|
2883
|
+
return attemptId;
|
|
2884
|
+
}
|
|
2885
|
+
const update = await updateReservation(now2, attemptId, true, qty);
|
|
2886
|
+
if (!update.result)
|
|
2887
|
+
throw new Error("PORTFOLIO_SUBMISSION_NO_LONGER_ADMITTED");
|
|
2888
|
+
submitted = true;
|
|
2889
|
+
return attemptId;
|
|
2890
|
+
},
|
|
2891
|
+
async (retrying = false) => {
|
|
2892
|
+
await transact(
|
|
2893
|
+
now2,
|
|
2894
|
+
(s) => ({
|
|
2895
|
+
state: {
|
|
2896
|
+
...s,
|
|
2897
|
+
reservations: s.reservations.filter(
|
|
2898
|
+
(r) => r.id !== attemptId
|
|
2899
|
+
),
|
|
2900
|
+
completedSignals: {
|
|
2901
|
+
...Object.fromEntries(
|
|
2902
|
+
Object.entries(s.completedSignals).filter(
|
|
2903
|
+
([id]) => !retrying || id !== entry.signalId
|
|
2904
|
+
)
|
|
2905
|
+
),
|
|
2906
|
+
...retrying ? {} : { [entry.signalId]: now2 }
|
|
2907
|
+
}
|
|
2908
|
+
},
|
|
2909
|
+
result: void 0
|
|
2910
|
+
}),
|
|
2911
|
+
true
|
|
2912
|
+
);
|
|
2913
|
+
submitted = false;
|
|
2914
|
+
needsReservation = retrying;
|
|
2915
|
+
}
|
|
2916
|
+
);
|
|
2917
|
+
if (!submitted) await updateReservation(now2, attemptId, false);
|
|
2918
|
+
else await tick(now2, true);
|
|
2919
|
+
return { value };
|
|
2920
|
+
} catch (error) {
|
|
2921
|
+
if (!submitted) await updateReservation(now2, attemptId, false);
|
|
2922
|
+
throw error;
|
|
2923
|
+
}
|
|
3148
2924
|
}
|
|
3149
2925
|
};
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
payload.context.strategyConfig,
|
|
3154
|
-
strategyName,
|
|
3155
|
-
profileId
|
|
3156
|
-
)
|
|
3157
|
-
} : void 0;
|
|
2926
|
+
}
|
|
2927
|
+
function createMemoryPortfolioStore() {
|
|
2928
|
+
let state = null;
|
|
3158
2929
|
return {
|
|
3159
|
-
|
|
3160
|
-
|
|
2930
|
+
read: async () => state === null ? null : structuredClone(state),
|
|
2931
|
+
compare: async (expected, next) => {
|
|
2932
|
+
if ((state?.revision ?? -1) !== (expected?.revision ?? -1)) return false;
|
|
2933
|
+
state = structuredClone(next);
|
|
2934
|
+
return true;
|
|
2935
|
+
}
|
|
3161
2936
|
};
|
|
3162
|
-
}
|
|
2937
|
+
}
|
|
3163
2938
|
|
|
3164
2939
|
// src/runtimeJournal.ts
|
|
3165
2940
|
var import_node_crypto = require("crypto");
|
|
3166
2941
|
var import_constants = require("@tradejs/core/constants");
|
|
3167
2942
|
var import_time = require("@tradejs/core/time");
|
|
3168
2943
|
var import_trade = require("@tradejs/core/trade");
|
|
3169
|
-
var
|
|
2944
|
+
var import_logger2 = require("@tradejs/infra/logger");
|
|
3170
2945
|
var import_redis2 = require("@tradejs/infra/redis");
|
|
3171
2946
|
var now = () => Date.now();
|
|
3172
2947
|
var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
|
|
@@ -3225,188 +3000,992 @@ var recordRuntimeTradeOpen = async (params) => {
|
|
|
3225
3000
|
)
|
|
3226
3001
|
]);
|
|
3227
3002
|
} catch (error) {
|
|
3228
|
-
|
|
3229
|
-
"runtime trade open journal failed: %s %s",
|
|
3230
|
-
record.symbol,
|
|
3003
|
+
import_logger2.logger.error(
|
|
3004
|
+
"runtime trade open journal failed: %s %s",
|
|
3005
|
+
record.symbol,
|
|
3006
|
+
error?.message || String(error)
|
|
3007
|
+
);
|
|
3008
|
+
}
|
|
3009
|
+
return record;
|
|
3010
|
+
};
|
|
3011
|
+
var recordRuntimeTradeIncrease = async (params) => {
|
|
3012
|
+
const {
|
|
3013
|
+
userName,
|
|
3014
|
+
strategy,
|
|
3015
|
+
symbol,
|
|
3016
|
+
direction,
|
|
3017
|
+
resultingQty,
|
|
3018
|
+
resultingEntryPrice,
|
|
3019
|
+
addedQty,
|
|
3020
|
+
addedEntryPrice,
|
|
3021
|
+
entryTimestamp,
|
|
3022
|
+
fee,
|
|
3023
|
+
accountId,
|
|
3024
|
+
deploymentId
|
|
3025
|
+
} = params;
|
|
3026
|
+
if (!userName) {
|
|
3027
|
+
return null;
|
|
3028
|
+
}
|
|
3029
|
+
const existing = await getActiveRuntimeTrade({
|
|
3030
|
+
userName,
|
|
3031
|
+
symbol,
|
|
3032
|
+
accountId,
|
|
3033
|
+
deploymentId
|
|
3034
|
+
});
|
|
3035
|
+
if (!existing || existing.strategy !== strategy || existing.direction !== direction) {
|
|
3036
|
+
return null;
|
|
3037
|
+
}
|
|
3038
|
+
const addedFee = typeof fee === "number" && Number.isFinite(fee) ? fee : 0;
|
|
3039
|
+
const openFee = (existing.openFee ?? existing.fee ?? 0) + addedFee;
|
|
3040
|
+
const totalFee = (existing.totalFee ?? existing.fee ?? 0) + addedFee;
|
|
3041
|
+
const next = {
|
|
3042
|
+
...existing,
|
|
3043
|
+
...params.orderLeverage ? { orderLeverage: params.orderLeverage } : {},
|
|
3044
|
+
qty: resultingQty,
|
|
3045
|
+
entryPrice: resultingEntryPrice,
|
|
3046
|
+
entryCount: Math.max(1, existing.entryCount ?? 1) + 1,
|
|
3047
|
+
lastEntryPrice: addedEntryPrice,
|
|
3048
|
+
lastEntryQty: addedQty,
|
|
3049
|
+
lastEntryTimestamp: entryTimestamp,
|
|
3050
|
+
currentPrice: resultingEntryPrice,
|
|
3051
|
+
currentPnl: 0,
|
|
3052
|
+
fee: openFee,
|
|
3053
|
+
openFee,
|
|
3054
|
+
totalFee,
|
|
3055
|
+
lastSyncedAt: now()
|
|
3056
|
+
};
|
|
3057
|
+
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
3058
|
+
try {
|
|
3059
|
+
await Promise.all([
|
|
3060
|
+
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, existing.orderId), next, {
|
|
3061
|
+
expire: 0
|
|
3062
|
+
}),
|
|
3063
|
+
(0, import_redis2.setHashJsonField)(
|
|
3064
|
+
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
3065
|
+
existing.orderId,
|
|
3066
|
+
next,
|
|
3067
|
+
{ expire: 0 }
|
|
3068
|
+
)
|
|
3069
|
+
]);
|
|
3070
|
+
} catch (error) {
|
|
3071
|
+
import_logger2.logger.error(
|
|
3072
|
+
"runtime trade increase journal failed: %s %s",
|
|
3073
|
+
symbol,
|
|
3074
|
+
error?.message || String(error)
|
|
3075
|
+
);
|
|
3076
|
+
}
|
|
3077
|
+
return next;
|
|
3078
|
+
};
|
|
3079
|
+
var getActiveRuntimeTrade = async (params) => {
|
|
3080
|
+
const { userName, symbol, accountId, deploymentId } = params;
|
|
3081
|
+
if (!userName) {
|
|
3082
|
+
return null;
|
|
3083
|
+
}
|
|
3084
|
+
const activeRef = await (0, import_redis2.getData)(
|
|
3085
|
+
import_redis2.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId),
|
|
3086
|
+
null
|
|
3087
|
+
);
|
|
3088
|
+
const orderId = String(activeRef?.orderId || "").trim();
|
|
3089
|
+
if (!orderId) {
|
|
3090
|
+
return null;
|
|
3091
|
+
}
|
|
3092
|
+
const existing = await (0, import_redis2.getData)(
|
|
3093
|
+
import_redis2.redisKeys.runtimeTrade(userName, orderId),
|
|
3094
|
+
null
|
|
3095
|
+
);
|
|
3096
|
+
if (!existing || existing.status !== "active") {
|
|
3097
|
+
await (0, import_redis2.delKey)(
|
|
3098
|
+
import_redis2.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
|
|
3099
|
+
);
|
|
3100
|
+
return null;
|
|
3101
|
+
}
|
|
3102
|
+
return existing;
|
|
3103
|
+
};
|
|
3104
|
+
var markRuntimeTradeClosed = async (params) => {
|
|
3105
|
+
const {
|
|
3106
|
+
userName,
|
|
3107
|
+
expectedOrderId,
|
|
3108
|
+
symbol,
|
|
3109
|
+
strategy,
|
|
3110
|
+
exitPrice,
|
|
3111
|
+
exitTimestamp,
|
|
3112
|
+
closedPnl,
|
|
3113
|
+
exitType,
|
|
3114
|
+
accountId,
|
|
3115
|
+
deploymentId
|
|
3116
|
+
} = params;
|
|
3117
|
+
if (!userName) {
|
|
3118
|
+
return null;
|
|
3119
|
+
}
|
|
3120
|
+
const existing = expectedOrderId ? await (0, import_redis2.getDataStrict)(
|
|
3121
|
+
import_redis2.redisKeys.runtimeTrade(userName, expectedOrderId)
|
|
3122
|
+
) : await getActiveRuntimeTrade({
|
|
3123
|
+
userName,
|
|
3124
|
+
symbol,
|
|
3125
|
+
accountId,
|
|
3126
|
+
deploymentId
|
|
3127
|
+
});
|
|
3128
|
+
if (!existing || expectedOrderId && existing.orderId !== expectedOrderId) {
|
|
3129
|
+
return null;
|
|
3130
|
+
}
|
|
3131
|
+
const orderId = existing.orderId;
|
|
3132
|
+
if (strategy && existing.strategy !== strategy) {
|
|
3133
|
+
return null;
|
|
3134
|
+
}
|
|
3135
|
+
const resolvedExitPrice = typeof exitPrice === "number" && Number.isFinite(exitPrice) ? exitPrice : existing.currentPrice ?? existing.entryPrice;
|
|
3136
|
+
const resolvedClosedPnl = typeof closedPnl === "number" && Number.isFinite(closedPnl) ? closedPnl : typeof resolvedExitPrice === "number" && Number.isFinite(resolvedExitPrice) ? calculateClosedPnl({
|
|
3137
|
+
direction: existing.direction,
|
|
3138
|
+
entryPrice: existing.entryPrice,
|
|
3139
|
+
exitPrice: resolvedExitPrice,
|
|
3140
|
+
qty: existing.qty
|
|
3141
|
+
}) : existing.closedPnl ?? existing.currentPnl ?? null;
|
|
3142
|
+
const next = expectedOrderId && existing.status === "closed" ? existing : {
|
|
3143
|
+
...existing,
|
|
3144
|
+
status: "closed",
|
|
3145
|
+
currentPrice: resolvedExitPrice,
|
|
3146
|
+
currentPnl: resolvedClosedPnl,
|
|
3147
|
+
closedPnl: resolvedClosedPnl,
|
|
3148
|
+
exitPrice: resolvedExitPrice,
|
|
3149
|
+
exitTimestamp: typeof exitTimestamp === "number" && Number.isFinite(exitTimestamp) ? exitTimestamp : now(),
|
|
3150
|
+
exitType: exitType ?? existing.exitType ?? null,
|
|
3151
|
+
lastSyncedAt: now()
|
|
3152
|
+
};
|
|
3153
|
+
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
3154
|
+
const closeDayKey = (0, import_time.getRuntimeStorageDayKey)(next.exitTimestamp);
|
|
3155
|
+
try {
|
|
3156
|
+
await Promise.all([
|
|
3157
|
+
(expectedOrderId ? import_redis2.setDataStrict : import_redis2.setData)(
|
|
3158
|
+
import_redis2.redisKeys.runtimeTrade(userName, orderId),
|
|
3159
|
+
next,
|
|
3160
|
+
{
|
|
3161
|
+
expire: import_constants.TTL_1M
|
|
3162
|
+
}
|
|
3163
|
+
),
|
|
3164
|
+
(expectedOrderId ? import_redis2.setHashJsonFieldStrict : import_redis2.setHashJsonField)(
|
|
3165
|
+
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
3166
|
+
orderId,
|
|
3167
|
+
next,
|
|
3168
|
+
{ expire: import_constants.TTL_1M }
|
|
3169
|
+
),
|
|
3170
|
+
(expectedOrderId ? import_redis2.setHashJsonFieldStrict : import_redis2.setHashJsonField)(
|
|
3171
|
+
import_redis2.redisKeys.runtimeClosedTradeBucket(userName, closeDayKey),
|
|
3172
|
+
orderId,
|
|
3173
|
+
next,
|
|
3174
|
+
{ expire: import_constants.TTL_1M }
|
|
3175
|
+
),
|
|
3176
|
+
expectedOrderId ? (0, import_redis2.deleteRuntimeTradeReference)(
|
|
3177
|
+
import_redis2.redisKeys.runtimeActiveTrade(
|
|
3178
|
+
userName,
|
|
3179
|
+
symbol,
|
|
3180
|
+
deploymentId ?? accountId
|
|
3181
|
+
),
|
|
3182
|
+
expectedOrderId
|
|
3183
|
+
) : (0, import_redis2.delKey)(
|
|
3184
|
+
import_redis2.redisKeys.runtimeActiveTrade(
|
|
3185
|
+
userName,
|
|
3186
|
+
symbol,
|
|
3187
|
+
deploymentId ?? accountId
|
|
3188
|
+
)
|
|
3189
|
+
)
|
|
3190
|
+
]);
|
|
3191
|
+
} catch (error) {
|
|
3192
|
+
if (expectedOrderId) throw error;
|
|
3193
|
+
import_logger2.logger.error(
|
|
3194
|
+
"runtime trade close journal failed: %s %s",
|
|
3195
|
+
symbol,
|
|
3231
3196
|
error?.message || String(error)
|
|
3232
3197
|
);
|
|
3233
3198
|
}
|
|
3234
|
-
return
|
|
3199
|
+
return next;
|
|
3235
3200
|
};
|
|
3236
|
-
|
|
3237
|
-
|
|
3201
|
+
|
|
3202
|
+
// src/strategy/portfolioRuntime.ts
|
|
3203
|
+
var coordinators = /* @__PURE__ */ new WeakMap();
|
|
3204
|
+
async function observeRuntimePortfolio(connector, binding, asOf, reservations, observedNow = Date.now) {
|
|
3205
|
+
if (!connector.getOpenPositionPnl || !connector.getEntryExecutions)
|
|
3206
|
+
throw new Error(
|
|
3207
|
+
"Portfolio policies require position marks and entry executions"
|
|
3208
|
+
);
|
|
3209
|
+
const orders = await Promise.all(
|
|
3210
|
+
reservations.map(async (r) => {
|
|
3211
|
+
if (r.submitted && connector.getEntryOrderObservation) {
|
|
3212
|
+
const fact2 = await connector.getEntryOrderObservation({
|
|
3213
|
+
symbol: r.symbol,
|
|
3214
|
+
orderId: r.id
|
|
3215
|
+
});
|
|
3216
|
+
if (fact2) return fact2;
|
|
3217
|
+
}
|
|
3218
|
+
const executions = r.submitted ? await connector.getEntryExecutions({
|
|
3219
|
+
symbol: r.symbol,
|
|
3220
|
+
startTime: r.timestamp,
|
|
3221
|
+
endTime: asOf
|
|
3222
|
+
}) : [];
|
|
3223
|
+
const filledQty = executions.filter(
|
|
3224
|
+
(e) => (e.orderLinkId === r.id || e.orderId === r.id) && e.direction === r.direction
|
|
3225
|
+
).reduce((sum, e) => sum + e.qty, 0);
|
|
3226
|
+
return {
|
|
3227
|
+
id: r.id,
|
|
3228
|
+
status: filledQty >= r.qty ? "filled" : filledQty > 0 ? "partial" : "pending",
|
|
3229
|
+
filledQty,
|
|
3230
|
+
positionId: r.id
|
|
3231
|
+
};
|
|
3232
|
+
})
|
|
3233
|
+
);
|
|
3234
|
+
const snapshots2 = await connector.getOpenPositionPnl();
|
|
3235
|
+
const positions = await Promise.all(
|
|
3236
|
+
snapshots2.map(async (p) => {
|
|
3237
|
+
const reserved = reservations.find(
|
|
3238
|
+
(r) => r.symbol === p.symbol && r.direction === p.direction && orders.some(
|
|
3239
|
+
(o) => o.id === r.id && o.filledQty > 0 && p.qty <= o.filledQty * (1 + 1e-8)
|
|
3240
|
+
)
|
|
3241
|
+
);
|
|
3242
|
+
const trade = await getActiveRuntimeTrade({
|
|
3243
|
+
...binding,
|
|
3244
|
+
symbol: p.symbol
|
|
3245
|
+
});
|
|
3246
|
+
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;
|
|
3247
|
+
return {
|
|
3248
|
+
id: reserved?.id ?? owner?.orderId ?? `unknown:${p.symbol}:${p.direction}`,
|
|
3249
|
+
strategy: reserved?.strategy ?? owner?.strategy ?? null,
|
|
3250
|
+
symbol: p.symbol,
|
|
3251
|
+
direction: p.direction,
|
|
3252
|
+
qty: p.qty,
|
|
3253
|
+
entryPrice: p.price,
|
|
3254
|
+
markPrice: p.currentPrice,
|
|
3255
|
+
markedAt: p.observedAt ?? null,
|
|
3256
|
+
stopLossPrice: p.slPrice ?? p.stopLossPrice ?? null
|
|
3257
|
+
};
|
|
3258
|
+
})
|
|
3259
|
+
);
|
|
3260
|
+
return { asOf: Math.max(asOf, observedNow()), positions, orders };
|
|
3261
|
+
}
|
|
3262
|
+
function persistentStore(key) {
|
|
3263
|
+
const raw = /* @__PURE__ */ new WeakMap();
|
|
3264
|
+
return {
|
|
3265
|
+
async read() {
|
|
3266
|
+
const value = await import_redis3.portfolioStateStorage.read(key);
|
|
3267
|
+
if (value === null) return null;
|
|
3268
|
+
const state = JSON.parse(value);
|
|
3269
|
+
(0, import_portfolio2.assertPortfolioState)(state);
|
|
3270
|
+
raw.set(state, value);
|
|
3271
|
+
return state;
|
|
3272
|
+
},
|
|
3273
|
+
async compare(previous, next) {
|
|
3274
|
+
const expected = previous === null ? null : raw.get(previous);
|
|
3275
|
+
if (expected === void 0)
|
|
3276
|
+
throw new Error("Portfolio CAS requires a storage snapshot");
|
|
3277
|
+
return import_redis3.portfolioStateStorage.compare(key, expected, JSON.stringify(next));
|
|
3278
|
+
}
|
|
3279
|
+
};
|
|
3280
|
+
}
|
|
3281
|
+
function getPortfolioRuntime({
|
|
3282
|
+
value,
|
|
3283
|
+
connectorName,
|
|
3284
|
+
strategy,
|
|
3285
|
+
connector,
|
|
3286
|
+
env,
|
|
3287
|
+
userName,
|
|
3288
|
+
accountId,
|
|
3289
|
+
deploymentId,
|
|
3290
|
+
symbol,
|
|
3291
|
+
canClose
|
|
3292
|
+
}) {
|
|
3293
|
+
const policy = (0, import_portfolio2.parsePortfolioPolicy)(value);
|
|
3294
|
+
if (!policy) return void 0;
|
|
3295
|
+
const replay = (env === "BACKTEST" || env === "PARITY") && Reflect.get(connector, "__tradejsReplayConnector") === true;
|
|
3296
|
+
if ((env === "BACKTEST" || env === "PARITY") && (!replay || !connector.getPortfolioObservation))
|
|
3297
|
+
throw new Error(
|
|
3298
|
+
"Portfolio policies require shared portfolio replay; isolated symbol backtests are unsupported"
|
|
3299
|
+
);
|
|
3300
|
+
if (!replay && (!userName || !accountId || !deploymentId || !connector.getOpenPositionPnl || !connector.getEntryExecutions))
|
|
3301
|
+
throw new Error(
|
|
3302
|
+
"Portfolio runtime requires exact user/account/deployment binding, marks and executions"
|
|
3303
|
+
);
|
|
3304
|
+
const binding = {
|
|
3238
3305
|
userName,
|
|
3239
|
-
strategy,
|
|
3240
|
-
symbol,
|
|
3241
|
-
direction,
|
|
3242
|
-
resultingQty,
|
|
3243
|
-
resultingEntryPrice,
|
|
3244
|
-
addedQty,
|
|
3245
|
-
addedEntryPrice,
|
|
3246
|
-
entryTimestamp,
|
|
3247
|
-
fee,
|
|
3248
3306
|
accountId,
|
|
3249
3307
|
deploymentId
|
|
3250
|
-
}
|
|
3251
|
-
|
|
3252
|
-
|
|
3308
|
+
};
|
|
3309
|
+
const scope = policy.scope === "strategy" ? strategy : policy.scope === "account" ? "account" : [...policy.scope.strategies].sort();
|
|
3310
|
+
const key = (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify([binding, connectorName, connector.universe, scope])).digest("hex");
|
|
3311
|
+
const fingerprint3 = (0, import_portfolio2.portfolioFingerprint)(policy, strategy);
|
|
3312
|
+
const cacheKey = key;
|
|
3313
|
+
let registry = coordinators.get(connector);
|
|
3314
|
+
if (!registry) {
|
|
3315
|
+
registry = /* @__PURE__ */ new Map();
|
|
3316
|
+
coordinators.set(connector, registry);
|
|
3317
|
+
}
|
|
3318
|
+
const existing = registry.get(cacheKey);
|
|
3319
|
+
if (existing) {
|
|
3320
|
+
if (existing.fingerprint !== fingerprint3)
|
|
3321
|
+
throw new Error("Conflicting portfolio configuration for the same scope");
|
|
3322
|
+
if (symbol && canClose)
|
|
3323
|
+
existing.permissions.set(JSON.stringify([strategy, symbol]), canClose);
|
|
3324
|
+
return existing.coordinator;
|
|
3325
|
+
}
|
|
3326
|
+
const permissions = /* @__PURE__ */ new Map();
|
|
3327
|
+
if (symbol && canClose)
|
|
3328
|
+
permissions.set(JSON.stringify([strategy, symbol]), canClose);
|
|
3329
|
+
const coordinator = createPortfolioCoordinator({
|
|
3330
|
+
policy,
|
|
3331
|
+
strategy,
|
|
3332
|
+
store: replay ? createMemoryPortfolioStore() : persistentStore(key),
|
|
3333
|
+
execution: {
|
|
3334
|
+
async confirmClosed(intent, asOf) {
|
|
3335
|
+
if (replay) return;
|
|
3336
|
+
const position = intent.position;
|
|
3337
|
+
await markRuntimeTradeClosed({
|
|
3338
|
+
...binding,
|
|
3339
|
+
strategy: position.strategy,
|
|
3340
|
+
expectedOrderId: position.id,
|
|
3341
|
+
symbol: position.symbol,
|
|
3342
|
+
exitPrice: position.markPrice,
|
|
3343
|
+
exitTimestamp: asOf,
|
|
3344
|
+
exitType: "exit"
|
|
3345
|
+
});
|
|
3346
|
+
},
|
|
3347
|
+
observe: (asOf, reservations) => replay ? connector.getPortfolioObservation(asOf, reservations) : observeRuntimePortfolio(connector, binding, asOf, reservations),
|
|
3348
|
+
async close(position, intent, asOf) {
|
|
3349
|
+
const permission = permissions.get(
|
|
3350
|
+
JSON.stringify([position.strategy, position.symbol])
|
|
3351
|
+
);
|
|
3352
|
+
if (!permission || !await permission(position, intent, asOf)) return;
|
|
3353
|
+
if (!replay) {
|
|
3354
|
+
const trade = await getActiveRuntimeTrade({
|
|
3355
|
+
...binding,
|
|
3356
|
+
symbol: position.symbol
|
|
3357
|
+
});
|
|
3358
|
+
if (!trade || trade.orderId !== position.id)
|
|
3359
|
+
throw new Error("Portfolio close ownership changed");
|
|
3360
|
+
}
|
|
3361
|
+
await connector.closePosition({
|
|
3362
|
+
symbol: position.symbol,
|
|
3363
|
+
direction: position.direction,
|
|
3364
|
+
price: position.markPrice,
|
|
3365
|
+
timestamp: asOf,
|
|
3366
|
+
signal: {
|
|
3367
|
+
signalId: `${position.id}:${intent.reason}`,
|
|
3368
|
+
strategy: position.strategy,
|
|
3369
|
+
symbol: position.symbol,
|
|
3370
|
+
direction: position.direction,
|
|
3371
|
+
interval: "1",
|
|
3372
|
+
timestamp: asOf,
|
|
3373
|
+
prices: {
|
|
3374
|
+
riskRatio: 0,
|
|
3375
|
+
currentPrice: position.markPrice,
|
|
3376
|
+
takeProfitPrice: position.markPrice,
|
|
3377
|
+
stopLossPrice: position.markPrice
|
|
3378
|
+
},
|
|
3379
|
+
figures: {},
|
|
3380
|
+
indicators: {},
|
|
3381
|
+
additionalIndicators: { portfolioExitReason: intent.reason }
|
|
3382
|
+
}
|
|
3383
|
+
});
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
});
|
|
3387
|
+
registry.set(cacheKey, { fingerprint: fingerprint3, coordinator, permissions });
|
|
3388
|
+
return coordinator;
|
|
3389
|
+
}
|
|
3390
|
+
|
|
3391
|
+
// src/strategyHelpers/tickerProfiles.ts
|
|
3392
|
+
var import_tickerProfiles = require("@tradejs/infra/tickerProfiles");
|
|
3393
|
+
var import_tickerProfiles2 = require("@tradejs/core/tickerProfiles");
|
|
3394
|
+
var snapshots = /* @__PURE__ */ new Map();
|
|
3395
|
+
var resolveTickerProfileSource = (strategy) => {
|
|
3396
|
+
const declared = getStrategyManifest(strategy)?.contextRequirements?.tickerProfiles;
|
|
3397
|
+
if (declared) return declared;
|
|
3398
|
+
const provider = process.env.TICKER_PROFILES_PROVIDER?.trim();
|
|
3399
|
+
const universeId = process.env.TICKER_PROFILES_UNIVERSE?.trim();
|
|
3400
|
+
if (!provider && !universeId) return void 0;
|
|
3401
|
+
if (!provider || !universeId)
|
|
3402
|
+
throw new Error(
|
|
3403
|
+
"Set both TICKER_PROFILES_PROVIDER and TICKER_PROFILES_UNIVERSE"
|
|
3404
|
+
);
|
|
3405
|
+
return { provider, universeId };
|
|
3406
|
+
};
|
|
3407
|
+
var loadTickerProfileContext = async ({
|
|
3408
|
+
projectRoot,
|
|
3409
|
+
provider,
|
|
3410
|
+
universeId,
|
|
3411
|
+
symbol,
|
|
3412
|
+
timestamp
|
|
3413
|
+
}) => {
|
|
3414
|
+
const asOf = (0, import_tickerProfiles2.tickerProfileMonth)(timestamp), key = JSON.stringify([projectRoot, provider, universeId, asOf]);
|
|
3415
|
+
let cached = snapshots.get(key);
|
|
3416
|
+
if (!cached || cached.expires < Date.now()) {
|
|
3417
|
+
const value = (0, import_tickerProfiles.readTickerProfiles)(projectRoot, provider, universeId, asOf).then((envelope) => {
|
|
3418
|
+
if (!envelope) return null;
|
|
3419
|
+
const s = envelope.snapshot;
|
|
3420
|
+
if (!(0, import_tickerProfiles2.isTickerProfileSnapshot)(s) || s.asOf !== asOf || s.provider !== provider || s.universeId !== universeId)
|
|
3421
|
+
throw new Error("Ticker profile identity/schema mismatch");
|
|
3422
|
+
const entry = snapshots.get(key);
|
|
3423
|
+
if (entry) entry.expires = Infinity;
|
|
3424
|
+
return new Map(
|
|
3425
|
+
s.profiles.map((p) => [
|
|
3426
|
+
p.symbol,
|
|
3427
|
+
Object.freeze({
|
|
3428
|
+
...p,
|
|
3429
|
+
definition: s.definition,
|
|
3430
|
+
provider,
|
|
3431
|
+
universeId,
|
|
3432
|
+
snapshotId: envelope.snapshotId
|
|
3433
|
+
})
|
|
3434
|
+
])
|
|
3435
|
+
);
|
|
3436
|
+
}).catch((error) => {
|
|
3437
|
+
snapshots.delete(key);
|
|
3438
|
+
throw error;
|
|
3439
|
+
});
|
|
3440
|
+
cached = { expires: Date.now() + 6e4, value };
|
|
3441
|
+
snapshots.set(key, cached);
|
|
3442
|
+
if (snapshots.size > 24) {
|
|
3443
|
+
const oldest = snapshots.keys().next().value;
|
|
3444
|
+
if (oldest) snapshots.delete(oldest);
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
return (await cached.value)?.get(symbol);
|
|
3448
|
+
};
|
|
3449
|
+
var withTickerProfile = (baseContext, profile) => Object.defineProperty(
|
|
3450
|
+
Object.create(
|
|
3451
|
+
Object.getPrototypeOf(baseContext),
|
|
3452
|
+
Object.getOwnPropertyDescriptors(baseContext)
|
|
3453
|
+
),
|
|
3454
|
+
"tickerProfile",
|
|
3455
|
+
{ enumerable: true, configurable: true, value: profile }
|
|
3456
|
+
);
|
|
3457
|
+
var enrichSignalWithTickerProfile = async (signal) => {
|
|
3458
|
+
const source = resolveTickerProfileSource(signal.strategy);
|
|
3459
|
+
if (!source || signal.universe === "tradfi" || !signal.symbol.endsWith("USDT"))
|
|
3460
|
+
return;
|
|
3461
|
+
const profile = await loadTickerProfileContext({
|
|
3462
|
+
...source,
|
|
3463
|
+
projectRoot: getTradejsProjectCwd(),
|
|
3464
|
+
symbol: signal.symbol,
|
|
3465
|
+
timestamp: signal.timestamp
|
|
3466
|
+
});
|
|
3467
|
+
if (profile) {
|
|
3468
|
+
signal.additionalIndicators = {
|
|
3469
|
+
...signal.additionalIndicators,
|
|
3470
|
+
tickerProfile: profile
|
|
3471
|
+
};
|
|
3472
|
+
}
|
|
3473
|
+
};
|
|
3474
|
+
|
|
3475
|
+
// src/strategyRuntime.ts
|
|
3476
|
+
var import_jev6 = require("@tradejs/core/jev");
|
|
3477
|
+
|
|
3478
|
+
// src/jev.ts
|
|
3479
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
3480
|
+
var import_node_path = __toESM(require("path"));
|
|
3481
|
+
var import_jev4 = require("@tradejs/core/jev");
|
|
3482
|
+
var import_jev5 = require("@tradejs/infra/jev");
|
|
3483
|
+
var import_userSettings2 = require("@tradejs/infra/userSettings");
|
|
3484
|
+
init_jevInput();
|
|
3485
|
+
init_jevInput();
|
|
3486
|
+
init_jevTraining();
|
|
3487
|
+
var resolveJevProvider = async (userName) => {
|
|
3488
|
+
const settings = await (0, import_userSettings2.getUserSettings)(userName);
|
|
3489
|
+
const provider = {
|
|
3490
|
+
endpoint: settings.JEV_API_ENDPOINT || import_jev4.JEV_PROVIDERS[0].endpoint,
|
|
3491
|
+
model: settings.JEV_MODEL || import_jev4.JEV_PROVIDERS[0].model
|
|
3492
|
+
};
|
|
3493
|
+
(0, import_jev4.parseJevConfig)({ source: "provider", mode: "observe", provider });
|
|
3494
|
+
if (!settings.JEV_API_KEY)
|
|
3495
|
+
throw new Error("Configure the Jev API key in Account settings");
|
|
3496
|
+
return provider;
|
|
3497
|
+
};
|
|
3498
|
+
var emptyScores = () => Object.fromEntries(import_jev4.JEV_DIMENSIONS.map((key) => [key, null]));
|
|
3499
|
+
var pending = /* @__PURE__ */ new Map();
|
|
3500
|
+
var pendingResponses = /* @__PURE__ */ new Map();
|
|
3501
|
+
var evaluateJevInput = async ({
|
|
3502
|
+
input,
|
|
3503
|
+
config,
|
|
3504
|
+
userName,
|
|
3505
|
+
projectRoot
|
|
3506
|
+
}) => {
|
|
3507
|
+
config = (0, import_jev4.parseJevConfig)(config);
|
|
3508
|
+
const inputHash = (0, import_jev5.jevHash)(input);
|
|
3509
|
+
const questions = questionsForJevInput(input);
|
|
3510
|
+
const questionsHash = (0, import_jev5.jevHash)(questions);
|
|
3511
|
+
const base = {
|
|
3512
|
+
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
3513
|
+
schema: "tradejs-signal-assessment/v5",
|
|
3514
|
+
source: config.source,
|
|
3515
|
+
mode: config.mode,
|
|
3516
|
+
inputHash,
|
|
3517
|
+
questionsHash,
|
|
3518
|
+
geometryStatus: input.geometryStatus,
|
|
3519
|
+
levelsValid: typeof input.features["signal.validLevels"] === "boolean" ? input.features["signal.validLevels"] : null
|
|
3520
|
+
};
|
|
3521
|
+
if (!input.questions.length)
|
|
3522
|
+
return {
|
|
3523
|
+
assessment: {
|
|
3524
|
+
...base,
|
|
3525
|
+
status: "unavailable",
|
|
3526
|
+
model: "",
|
|
3527
|
+
scores: emptyScores(),
|
|
3528
|
+
confidence: emptyScores()
|
|
3529
|
+
}
|
|
3530
|
+
};
|
|
3531
|
+
if (config.source === "local") {
|
|
3532
|
+
const contents = await import_promises.default.readFile(
|
|
3533
|
+
import_node_path.default.resolve(projectRoot, config.modelFile),
|
|
3534
|
+
"utf8"
|
|
3535
|
+
);
|
|
3536
|
+
if ((0, import_jev5.jevFileHash)(contents) !== config.modelSha256)
|
|
3537
|
+
throw new Error("Local Jev gate checksum mismatch");
|
|
3538
|
+
const { validateJevGateModel: validateJevGateModel2 } = await Promise.resolve().then(() => (init_jevTraining(), jevTraining_exports));
|
|
3539
|
+
const model = validateJevGateModel2(JSON.parse(contents));
|
|
3540
|
+
if (model.strategy !== input.strategy || model.questionsHash !== (0, import_jev5.jevHash)(JEV_QUESTIONS))
|
|
3541
|
+
throw new Error("Local Jev gate strategy or question version mismatch");
|
|
3542
|
+
const scores = Object.fromEntries(
|
|
3543
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
3544
|
+
key,
|
|
3545
|
+
input.questions.includes(key) && model.trees[key] ? (0, import_jev4.predictJevTree)(model.trees[key], input.features) : null
|
|
3546
|
+
])
|
|
3547
|
+
);
|
|
3548
|
+
return {
|
|
3549
|
+
assessment: {
|
|
3550
|
+
...base,
|
|
3551
|
+
status: "available",
|
|
3552
|
+
model: config.modelSha256,
|
|
3553
|
+
scores,
|
|
3554
|
+
confidence: emptyScores()
|
|
3555
|
+
}
|
|
3556
|
+
};
|
|
3557
|
+
}
|
|
3558
|
+
const provider = config.provider ?? await resolveJevProvider(userName);
|
|
3559
|
+
const id = (0, import_jev5.jevHash)({ inputHash, questionsHash, provider });
|
|
3560
|
+
const dir = import_node_path.default.resolve(projectRoot, config.recordsDir ?? "data/ai/jev");
|
|
3561
|
+
const file = import_node_path.default.join(dir, "records", `${id}.json`);
|
|
3562
|
+
const getRecord = async () => {
|
|
3563
|
+
const existing = await (0, import_jev5.readJevArtifact)(file);
|
|
3564
|
+
if (existing) {
|
|
3565
|
+
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))
|
|
3566
|
+
throw new Error("Jev recording identity mismatch");
|
|
3567
|
+
(0, import_jev5.validateJevResponse)(existing.response, input.questions);
|
|
3568
|
+
for (const key of import_jev4.JEV_DIMENSIONS) {
|
|
3569
|
+
const expected = input.questions.includes(key) ? existing.response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null;
|
|
3570
|
+
if (existing.scores[key] !== expected)
|
|
3571
|
+
throw new Error("Jev recording scores do not match the response");
|
|
3572
|
+
}
|
|
3573
|
+
return existing;
|
|
3574
|
+
}
|
|
3575
|
+
if (config.source === "recorded")
|
|
3576
|
+
throw new Error(
|
|
3577
|
+
`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.`
|
|
3578
|
+
);
|
|
3579
|
+
const settings = await (0, import_userSettings2.getUserSettings)(userName);
|
|
3580
|
+
if (!settings.JEV_API_KEY)
|
|
3581
|
+
throw new Error("Configure the Jev API key in Account settings");
|
|
3582
|
+
const apiKey = settings.JEV_API_KEY;
|
|
3583
|
+
const configuredProvider = await resolveJevProvider(userName);
|
|
3584
|
+
if (configuredProvider.endpoint !== provider.endpoint)
|
|
3585
|
+
throw new Error(
|
|
3586
|
+
"Jev account endpoint changed; update the frozen provider configuration"
|
|
3587
|
+
);
|
|
3588
|
+
const started = Date.now();
|
|
3589
|
+
const state = stateForJevInput(input);
|
|
3590
|
+
const responseId = (0, import_jev5.jevHash)({
|
|
3591
|
+
state,
|
|
3592
|
+
questionsHash,
|
|
3593
|
+
provider,
|
|
3594
|
+
user: (0, import_jev5.jevHash)(userName)
|
|
3595
|
+
});
|
|
3596
|
+
const responseFile = import_node_path.default.join(dir, "responses", `${responseId}.json`);
|
|
3597
|
+
let responsePromise = pendingResponses.get(responseFile);
|
|
3598
|
+
if (!responsePromise) {
|
|
3599
|
+
responsePromise = (async () => {
|
|
3600
|
+
const saved = await (0, import_jev5.readJevArtifact)(responseFile);
|
|
3601
|
+
if (saved) return (0, import_jev5.validateJevResponse)(saved, input.questions);
|
|
3602
|
+
const fresh = await (0, import_jev5.requestJev)(provider, apiKey, state, questions);
|
|
3603
|
+
await (0, import_jev5.writeJevArtifact)(responseFile, fresh);
|
|
3604
|
+
return fresh;
|
|
3605
|
+
})();
|
|
3606
|
+
pendingResponses.set(responseFile, responsePromise);
|
|
3607
|
+
}
|
|
3608
|
+
let response;
|
|
3609
|
+
try {
|
|
3610
|
+
response = await responsePromise;
|
|
3611
|
+
} finally {
|
|
3612
|
+
pendingResponses.delete(responseFile);
|
|
3613
|
+
}
|
|
3614
|
+
const scores = Object.fromEntries(
|
|
3615
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
3616
|
+
key,
|
|
3617
|
+
input.questions.includes(key) ? response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null
|
|
3618
|
+
])
|
|
3619
|
+
);
|
|
3620
|
+
const value = {
|
|
3621
|
+
schema: "tradejs-jev-record/v4",
|
|
3622
|
+
id,
|
|
3623
|
+
inputHash,
|
|
3624
|
+
questionsHash,
|
|
3625
|
+
provider,
|
|
3626
|
+
input,
|
|
3627
|
+
response,
|
|
3628
|
+
scores,
|
|
3629
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3630
|
+
elapsedMs: Date.now() - started
|
|
3631
|
+
};
|
|
3632
|
+
await (0, import_jev5.writeJevArtifact)(file, value);
|
|
3633
|
+
return await (0, import_jev5.readJevArtifact)(file);
|
|
3634
|
+
};
|
|
3635
|
+
const pendingKey = `${file}:${(0, import_jev5.jevHash)(userName)}`;
|
|
3636
|
+
let promise = pending.get(pendingKey);
|
|
3637
|
+
if (!promise) {
|
|
3638
|
+
promise = getRecord();
|
|
3639
|
+
pending.set(pendingKey, promise);
|
|
3253
3640
|
}
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
});
|
|
3260
|
-
if (!existing || existing.strategy !== strategy || existing.direction !== direction) {
|
|
3261
|
-
return null;
|
|
3641
|
+
let record;
|
|
3642
|
+
try {
|
|
3643
|
+
record = await promise;
|
|
3644
|
+
} finally {
|
|
3645
|
+
pending.delete(pendingKey);
|
|
3262
3646
|
}
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
openFee,
|
|
3279
|
-
totalFee,
|
|
3280
|
-
lastSyncedAt: now()
|
|
3647
|
+
return {
|
|
3648
|
+
record,
|
|
3649
|
+
assessment: {
|
|
3650
|
+
...base,
|
|
3651
|
+
status: "available",
|
|
3652
|
+
recordId: id,
|
|
3653
|
+
model: record.response.model,
|
|
3654
|
+
scores: record.scores,
|
|
3655
|
+
confidence: Object.fromEntries(
|
|
3656
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
3657
|
+
key,
|
|
3658
|
+
record.response.answers[key]?.confidence ?? null
|
|
3659
|
+
])
|
|
3660
|
+
)
|
|
3661
|
+
}
|
|
3281
3662
|
};
|
|
3282
|
-
|
|
3663
|
+
};
|
|
3664
|
+
var assessSignalWithJev = async ({
|
|
3665
|
+
signal,
|
|
3666
|
+
config,
|
|
3667
|
+
userName,
|
|
3668
|
+
projectRoot,
|
|
3669
|
+
strict,
|
|
3670
|
+
scope = "runtime"
|
|
3671
|
+
}) => {
|
|
3672
|
+
let assessment;
|
|
3283
3673
|
try {
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
next,
|
|
3292
|
-
{ expire: 0 }
|
|
3293
|
-
)
|
|
3294
|
-
]);
|
|
3674
|
+
const input = buildJevInput(signal);
|
|
3675
|
+
({ assessment } = await evaluateJevInput({
|
|
3676
|
+
input,
|
|
3677
|
+
config,
|
|
3678
|
+
userName,
|
|
3679
|
+
projectRoot
|
|
3680
|
+
}));
|
|
3295
3681
|
} catch (error) {
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
null
|
|
3312
|
-
);
|
|
3313
|
-
const orderId = String(activeRef?.orderId || "").trim();
|
|
3314
|
-
if (!orderId) {
|
|
3315
|
-
return null;
|
|
3682
|
+
if (strict) throw error;
|
|
3683
|
+
assessment = {
|
|
3684
|
+
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
3685
|
+
schema: "tradejs-signal-assessment/v5",
|
|
3686
|
+
source: config.source,
|
|
3687
|
+
mode: config.mode,
|
|
3688
|
+
status: "unavailable",
|
|
3689
|
+
inputHash: "",
|
|
3690
|
+
questionsHash: "",
|
|
3691
|
+
geometryStatus: "absent",
|
|
3692
|
+
levelsValid: null,
|
|
3693
|
+
model: config.provider?.model ?? config.modelSha256 ?? "",
|
|
3694
|
+
scores: emptyScores(),
|
|
3695
|
+
confidence: emptyScores()
|
|
3696
|
+
};
|
|
3316
3697
|
}
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3698
|
+
signal.assessment = assessment;
|
|
3699
|
+
signal.additionalIndicators = {
|
|
3700
|
+
...signal.additionalIndicators,
|
|
3701
|
+
jev: (0, import_jev4.jevAssessmentFeatures)(assessment)
|
|
3702
|
+
};
|
|
3703
|
+
const identity = {
|
|
3704
|
+
signalId: signal.signalId,
|
|
3705
|
+
strategy: signal.strategy,
|
|
3706
|
+
symbol: signal.symbol,
|
|
3707
|
+
timestamp: signal.timestamp,
|
|
3708
|
+
scope,
|
|
3709
|
+
user: (0, import_jev5.jevHash)(userName),
|
|
3710
|
+
config,
|
|
3711
|
+
assessment
|
|
3712
|
+
};
|
|
3713
|
+
await (0, import_jev5.writeJevArtifact)(
|
|
3714
|
+
import_node_path.default.resolve(
|
|
3715
|
+
projectRoot,
|
|
3716
|
+
config.recordsDir ?? "data/ai/jev",
|
|
3717
|
+
"decisions",
|
|
3718
|
+
`${(0, import_jev5.jevHash)(identity)}.json`
|
|
3719
|
+
),
|
|
3720
|
+
identity
|
|
3320
3721
|
);
|
|
3321
|
-
|
|
3322
|
-
await (0, import_redis2.delKey)(
|
|
3323
|
-
import_redis2.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
|
|
3324
|
-
);
|
|
3325
|
-
return null;
|
|
3326
|
-
}
|
|
3327
|
-
return existing;
|
|
3722
|
+
return assessment;
|
|
3328
3723
|
};
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
deploymentId
|
|
3349
|
-
});
|
|
3350
|
-
if (!existing) {
|
|
3351
|
-
return null;
|
|
3352
|
-
}
|
|
3353
|
-
const orderId = existing.orderId;
|
|
3354
|
-
if (strategy && existing.strategy !== strategy) {
|
|
3355
|
-
return null;
|
|
3724
|
+
|
|
3725
|
+
// src/strategyRuntime.ts
|
|
3726
|
+
var import_constants5 = require("@tradejs/core/constants");
|
|
3727
|
+
var import_strategies6 = require("@tradejs/core/strategies");
|
|
3728
|
+
var import_logger11 = require("@tradejs/infra/logger");
|
|
3729
|
+
|
|
3730
|
+
// src/strategyHelpers/runtime.ts
|
|
3731
|
+
var import_logger9 = require("@tradejs/infra/logger");
|
|
3732
|
+
var import_constants3 = require("@tradejs/core/constants");
|
|
3733
|
+
var import_ml2 = require("@tradejs/infra/ml");
|
|
3734
|
+
|
|
3735
|
+
// src/strategy/entrySubmissionValidation.ts
|
|
3736
|
+
var import_logger3 = require("@tradejs/infra/logger");
|
|
3737
|
+
|
|
3738
|
+
// src/strategy/entryExecutionValidation.ts
|
|
3739
|
+
var EntrySubmissionValidationError = class extends Error {
|
|
3740
|
+
constructor() {
|
|
3741
|
+
super(...arguments);
|
|
3742
|
+
this.name = "EntrySubmissionValidationError";
|
|
3356
3743
|
}
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
const
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3744
|
+
};
|
|
3745
|
+
var resolveEntryExecutionLimits = (config, direction) => {
|
|
3746
|
+
const side = config[direction];
|
|
3747
|
+
const minimum = side && typeof side === "object" && "minRiskRatio" in side ? side.minRiskRatio : void 0;
|
|
3748
|
+
const maximum = config.MAX_LOSS_VALUE;
|
|
3749
|
+
if (minimum == null && maximum == null) return void 0;
|
|
3750
|
+
const minimumNetRiskRatio = minimum == null ? void 0 : Number(minimum);
|
|
3751
|
+
const maxLossValue = maximum == null ? void 0 : Number(maximum);
|
|
3752
|
+
const feeRate = Number(config.RISK_FEE_RATE ?? 0);
|
|
3753
|
+
const slippage = Number(config.RISK_SLIPPAGE_BPS ?? 0);
|
|
3754
|
+
const impact = Number(config.RISK_MARKET_IMPACT_BPS ?? 0);
|
|
3755
|
+
const slippageBps = slippage + impact;
|
|
3756
|
+
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)
|
|
3757
|
+
throw new Error("INVALID_ENTRY_EXECUTION_LIMITS");
|
|
3758
|
+
return { minimumNetRiskRatio, maxLossValue, feeRate, slippageBps };
|
|
3759
|
+
};
|
|
3760
|
+
var validateEntryExecution = ({
|
|
3761
|
+
direction,
|
|
3762
|
+
price,
|
|
3763
|
+
qty,
|
|
3764
|
+
stopLossPrice,
|
|
3765
|
+
takeProfitPrice,
|
|
3766
|
+
limits
|
|
3767
|
+
}) => {
|
|
3768
|
+
if (!Number.isFinite(price) || price <= 0 || !Number.isFinite(qty) || qty <= 0 || stopLossPrice == null || !Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || !Number.isFinite(takeProfitPrice) || takeProfitPrice <= 0)
|
|
3769
|
+
throw new EntrySubmissionValidationError(
|
|
3770
|
+
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
3771
|
+
);
|
|
3772
|
+
const side = direction === "LONG" ? 1 : -1;
|
|
3773
|
+
if (side * (price - stopLossPrice) <= 0 || side * (takeProfitPrice - price) <= 0)
|
|
3774
|
+
throw new EntrySubmissionValidationError(
|
|
3775
|
+
"INVALID_ENTRY_PROTECTION_AT_SUBMISSION"
|
|
3776
|
+
);
|
|
3777
|
+
const slippage = limits.slippageBps / 1e4;
|
|
3778
|
+
const cost = (value, sign) => value * slippage + value * (1 + sign * slippage) * limits.feeRate;
|
|
3779
|
+
const entryCost = cost(price, side);
|
|
3780
|
+
const lossPerUnit = Math.abs(price - stopLossPrice) + entryCost + cost(stopLossPrice, -side);
|
|
3781
|
+
const rewardPerUnit = Math.max(
|
|
3782
|
+
0,
|
|
3783
|
+
Math.abs(takeProfitPrice - price) - entryCost - cost(takeProfitPrice, -side)
|
|
3784
|
+
);
|
|
3785
|
+
const netRiskRatio = rewardPerUnit / lossPerUnit;
|
|
3786
|
+
const plannedLossValue = qty * lossPerUnit;
|
|
3787
|
+
if (!Number.isFinite(netRiskRatio) || !Number.isFinite(plannedLossValue))
|
|
3788
|
+
throw new EntrySubmissionValidationError(
|
|
3789
|
+
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
3790
|
+
);
|
|
3791
|
+
const belowMinimumNetRiskRatio = limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio;
|
|
3792
|
+
const riskExceeded = limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9);
|
|
3793
|
+
return {
|
|
3794
|
+
netRiskRatio,
|
|
3795
|
+
plannedLossValue,
|
|
3796
|
+
riskExceeded,
|
|
3797
|
+
belowMinimumNetRiskRatio
|
|
3374
3798
|
};
|
|
3375
|
-
|
|
3376
|
-
|
|
3799
|
+
};
|
|
3800
|
+
|
|
3801
|
+
// src/strategy/entrySubmissionValidation.ts
|
|
3802
|
+
var MAX_ENTRY_QUOTE_AGE_MS = 5e3;
|
|
3803
|
+
var toFiniteNumberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
3804
|
+
var getOrderArrivalSnapshot = async ({
|
|
3805
|
+
connector,
|
|
3806
|
+
symbol,
|
|
3807
|
+
requireTimestamp = false
|
|
3808
|
+
}) => {
|
|
3809
|
+
if (typeof connector.getTopOfBookTicker !== "function") {
|
|
3810
|
+
return {
|
|
3811
|
+
arrivalSnapshotTime: Date.now(),
|
|
3812
|
+
arrivalSource: "unavailable",
|
|
3813
|
+
bid: null,
|
|
3814
|
+
ask: null,
|
|
3815
|
+
arrivalMid: null,
|
|
3816
|
+
spreadBps: null
|
|
3817
|
+
};
|
|
3818
|
+
}
|
|
3377
3819
|
try {
|
|
3378
|
-
await
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
),
|
|
3394
|
-
(0, import_redis2.delKey)(
|
|
3395
|
-
import_redis2.redisKeys.runtimeActiveTrade(
|
|
3396
|
-
userName,
|
|
3397
|
-
symbol,
|
|
3398
|
-
deploymentId ?? accountId
|
|
3399
|
-
)
|
|
3400
|
-
)
|
|
3401
|
-
]);
|
|
3820
|
+
const ticker = await connector.getTopOfBookTicker(symbol);
|
|
3821
|
+
const arrivalSnapshotTime = toFiniteNumberOrNull(ticker?.timestamp);
|
|
3822
|
+
const timestampUnavailable = requireTimestamp && arrivalSnapshotTime == null;
|
|
3823
|
+
const bid = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.bidPrice);
|
|
3824
|
+
const ask = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.askPrice);
|
|
3825
|
+
const arrivalMid = bid != null && ask != null ? (bid + ask) / 2 : null;
|
|
3826
|
+
const spreadBps = bid != null && ask != null && arrivalMid != null && arrivalMid > 0 ? (ask - bid) / arrivalMid * 1e4 : null;
|
|
3827
|
+
return {
|
|
3828
|
+
arrivalSnapshotTime: arrivalSnapshotTime ?? Date.now(),
|
|
3829
|
+
arrivalSource: "top_of_book",
|
|
3830
|
+
bid,
|
|
3831
|
+
ask,
|
|
3832
|
+
arrivalMid,
|
|
3833
|
+
spreadBps
|
|
3834
|
+
};
|
|
3402
3835
|
} catch (error) {
|
|
3403
|
-
import_logger3.logger.
|
|
3404
|
-
"runtime
|
|
3836
|
+
import_logger3.logger.warn(
|
|
3837
|
+
"runtime order arrival snapshot failed: %s %s",
|
|
3405
3838
|
symbol,
|
|
3406
3839
|
error?.message || String(error)
|
|
3407
3840
|
);
|
|
3841
|
+
return {
|
|
3842
|
+
arrivalSnapshotTime: Date.now(),
|
|
3843
|
+
arrivalSource: "top_of_book_error",
|
|
3844
|
+
bid: null,
|
|
3845
|
+
ask: null,
|
|
3846
|
+
arrivalMid: null,
|
|
3847
|
+
spreadBps: null
|
|
3848
|
+
};
|
|
3408
3849
|
}
|
|
3409
|
-
|
|
3850
|
+
};
|
|
3851
|
+
var validateEntryProtectionAtArrival = ({
|
|
3852
|
+
direction,
|
|
3853
|
+
signalPrice,
|
|
3854
|
+
bid,
|
|
3855
|
+
ask,
|
|
3856
|
+
arrivalMid,
|
|
3857
|
+
takeProfits,
|
|
3858
|
+
stopLossPrice
|
|
3859
|
+
}) => {
|
|
3860
|
+
const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
|
|
3861
|
+
const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
|
|
3862
|
+
if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
|
|
3863
|
+
throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
|
|
3864
|
+
}
|
|
3865
|
+
for (const takeProfit of takeProfits) {
|
|
3866
|
+
if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
|
|
3867
|
+
throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
|
|
3868
|
+
}
|
|
3869
|
+
}
|
|
3870
|
+
if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
|
|
3871
|
+
throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
|
|
3872
|
+
}
|
|
3873
|
+
};
|
|
3874
|
+
var createEntrySubmissionValidation = ({
|
|
3875
|
+
connector,
|
|
3876
|
+
symbol,
|
|
3877
|
+
direction,
|
|
3878
|
+
takeProfits,
|
|
3879
|
+
stopLossPrice,
|
|
3880
|
+
limits,
|
|
3881
|
+
onValidated
|
|
3882
|
+
}) => {
|
|
3883
|
+
if (!limits) return void 0;
|
|
3884
|
+
if (!connector.capabilities?.orderSubmissionValidation)
|
|
3885
|
+
throw new EntrySubmissionValidationError(
|
|
3886
|
+
"ENTRY_SUBMISSION_VALIDATION_UNSUPPORTED"
|
|
3887
|
+
);
|
|
3888
|
+
return async ({ qty, executionPrice }) => {
|
|
3889
|
+
const simulated = Reflect.get(connector, "__tradejsTestConnector") === true;
|
|
3890
|
+
const snapshot2 = simulated && executionPrice != null ? {
|
|
3891
|
+
arrivalSnapshotTime: Date.now(),
|
|
3892
|
+
arrivalSource: "simulation",
|
|
3893
|
+
bid: executionPrice,
|
|
3894
|
+
ask: executionPrice,
|
|
3895
|
+
arrivalMid: executionPrice,
|
|
3896
|
+
spreadBps: 0
|
|
3897
|
+
} : await getOrderArrivalSnapshot({
|
|
3898
|
+
connector,
|
|
3899
|
+
symbol,
|
|
3900
|
+
requireTimestamp: true
|
|
3901
|
+
});
|
|
3902
|
+
const age = Date.now() - snapshot2.arrivalSnapshotTime;
|
|
3903
|
+
if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
|
|
3904
|
+
throw new EntrySubmissionValidationError(
|
|
3905
|
+
"ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
|
|
3906
|
+
);
|
|
3907
|
+
if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
|
|
3908
|
+
throw new EntrySubmissionValidationError(
|
|
3909
|
+
"ENTRY_QUOTE_STALE_AT_SUBMISSION"
|
|
3910
|
+
);
|
|
3911
|
+
const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
|
|
3912
|
+
validateEntryProtectionAtArrival({
|
|
3913
|
+
direction,
|
|
3914
|
+
signalPrice: price,
|
|
3915
|
+
bid: snapshot2.bid,
|
|
3916
|
+
ask: snapshot2.ask,
|
|
3917
|
+
arrivalMid: snapshot2.arrivalMid,
|
|
3918
|
+
takeProfits,
|
|
3919
|
+
stopLossPrice
|
|
3920
|
+
});
|
|
3921
|
+
const prices = takeProfits.map((tp) => tp.price);
|
|
3922
|
+
const economics2 = validateEntryExecution({
|
|
3923
|
+
direction,
|
|
3924
|
+
price,
|
|
3925
|
+
qty,
|
|
3926
|
+
stopLossPrice,
|
|
3927
|
+
takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
|
|
3928
|
+
limits
|
|
3929
|
+
});
|
|
3930
|
+
if (economics2.belowMinimumNetRiskRatio) {
|
|
3931
|
+
import_logger3.logger.warn(
|
|
3932
|
+
"entry submission warning: %s %s ENTRY_RR_BELOW_MIN_AT_SUBMISSION (RR %s, threshold %s)",
|
|
3933
|
+
symbol,
|
|
3934
|
+
direction,
|
|
3935
|
+
economics2.netRiskRatio,
|
|
3936
|
+
limits.minimumNetRiskRatio
|
|
3937
|
+
);
|
|
3938
|
+
}
|
|
3939
|
+
onValidated?.(snapshot2, economics2);
|
|
3940
|
+
};
|
|
3941
|
+
};
|
|
3942
|
+
|
|
3943
|
+
// src/strategyAdapters/ml.ts
|
|
3944
|
+
var defaultMlAdapter = {
|
|
3945
|
+
normalizeStrategyConfig: (strategyConfig) => strategyConfig
|
|
3946
|
+
};
|
|
3947
|
+
var getStrategyMlAdapter = (strategy, profileId) => {
|
|
3948
|
+
const strategyAdapter = getStrategyProfileMlAdapter(
|
|
3949
|
+
getStrategyManifest(strategy),
|
|
3950
|
+
profileId
|
|
3951
|
+
);
|
|
3952
|
+
if (!strategyAdapter) return defaultMlAdapter;
|
|
3953
|
+
return {
|
|
3954
|
+
...defaultMlAdapter,
|
|
3955
|
+
...strategyAdapter
|
|
3956
|
+
};
|
|
3957
|
+
};
|
|
3958
|
+
|
|
3959
|
+
// src/mlPayload.ts
|
|
3960
|
+
var normalizeStrategyConfig = (strategyConfig, strategyName, profileId) => {
|
|
3961
|
+
return getStrategyMlAdapter(
|
|
3962
|
+
strategyName,
|
|
3963
|
+
profileId
|
|
3964
|
+
).normalizeStrategyConfig?.(strategyConfig);
|
|
3965
|
+
};
|
|
3966
|
+
var buildMlPayload = (payload) => {
|
|
3967
|
+
const strategyName = payload.signal?.strategy ?? payload.context?.strategyName;
|
|
3968
|
+
const profileId = payload.signal?.policyProfileId;
|
|
3969
|
+
const mlAdapter = getStrategyMlAdapter(strategyName, profileId);
|
|
3970
|
+
const normalizedSignal = mlAdapter.normalizeSignal?.(payload.signal) ?? payload.signal;
|
|
3971
|
+
const nextSignal = {
|
|
3972
|
+
...normalizedSignal,
|
|
3973
|
+
indicators: {
|
|
3974
|
+
...normalizedSignal?.indicators ?? {}
|
|
3975
|
+
}
|
|
3976
|
+
};
|
|
3977
|
+
const nextContext = payload.context ? {
|
|
3978
|
+
...payload.context,
|
|
3979
|
+
strategyConfig: normalizeStrategyConfig(
|
|
3980
|
+
payload.context.strategyConfig,
|
|
3981
|
+
strategyName,
|
|
3982
|
+
profileId
|
|
3983
|
+
)
|
|
3984
|
+
} : void 0;
|
|
3985
|
+
return {
|
|
3986
|
+
signal: nextSignal,
|
|
3987
|
+
context: nextContext
|
|
3988
|
+
};
|
|
3410
3989
|
};
|
|
3411
3990
|
|
|
3412
3991
|
// src/strategyHelpers/marketContextStages.ts
|
|
@@ -3418,7 +3997,7 @@ var import_logger4 = require("@tradejs/infra/logger");
|
|
|
3418
3997
|
var import_strategies = require("@tradejs/core/strategies");
|
|
3419
3998
|
|
|
3420
3999
|
// src/binanceBreadthUniverses.ts
|
|
3421
|
-
var
|
|
4000
|
+
var import_node_crypto3 = require("crypto");
|
|
3422
4001
|
|
|
3423
4002
|
// src/config/binanceBreadthUniverses.json
|
|
3424
4003
|
var binanceBreadthUniverses_default = {
|
|
@@ -3657,7 +4236,7 @@ var BINANCE_BREADTH_UNIVERSE_KEYS = [
|
|
|
3657
4236
|
"top50",
|
|
3658
4237
|
"top100"
|
|
3659
4238
|
];
|
|
3660
|
-
var fingerprint = (value, length) => (0,
|
|
4239
|
+
var fingerprint = (value, length) => (0, import_node_crypto3.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, length);
|
|
3661
4240
|
var normalizeSymbols = (symbols) => symbols.map((symbol) => symbol.trim().toUpperCase()).filter(Boolean);
|
|
3662
4241
|
var buildBinanceBreadthUniverseSnapshot = ({
|
|
3663
4242
|
rankedSymbols,
|
|
@@ -4833,7 +5412,7 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
4833
5412
|
};
|
|
4834
5413
|
|
|
4835
5414
|
// src/strategyHelpers/derivativesContext.ts
|
|
4836
|
-
var
|
|
5415
|
+
var import_indicators3 = require("@tradejs/core/indicators");
|
|
4837
5416
|
var import_data2 = require("@tradejs/core/data");
|
|
4838
5417
|
var import_strategies3 = require("@tradejs/core/strategies");
|
|
4839
5418
|
var import_constants2 = require("@tradejs/core/constants");
|
|
@@ -4867,7 +5446,7 @@ var parseLookbackMs = () => {
|
|
|
4867
5446
|
};
|
|
4868
5447
|
var withHourlyFallbackRows = (rowsByInterval) => ({
|
|
4869
5448
|
"15m": rowsByInterval["15m"] ?? [],
|
|
4870
|
-
"1h": (0,
|
|
5449
|
+
"1h": (0, import_indicators3.buildCoinalyzeHourlyRowsWithFallback)({
|
|
4871
5450
|
rows15m: rowsByInterval["15m"],
|
|
4872
5451
|
fallbackRows1h: rowsByInterval["1h"]
|
|
4873
5452
|
})
|
|
@@ -4995,7 +5574,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
4995
5574
|
const targetSymbol = normalizeSymbol(signal.symbol);
|
|
4996
5575
|
const lookbackMs = parseLookbackMs();
|
|
4997
5576
|
const decisionTimeMs = signal.timestamp + (0, import_data2.intervalToMs)(signal.interval);
|
|
4998
|
-
const derivativesEndMs = (0,
|
|
5577
|
+
const derivativesEndMs = (0, import_indicators3.getLastClosedDerivativesBarStartMs)(
|
|
4999
5578
|
decisionTimeMs,
|
|
5000
5579
|
"15m"
|
|
5001
5580
|
);
|
|
@@ -5010,7 +5589,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
5010
5589
|
});
|
|
5011
5590
|
return [
|
|
5012
5591
|
symbol,
|
|
5013
|
-
(0,
|
|
5592
|
+
(0, import_indicators3.buildDerivativesContext)({
|
|
5014
5593
|
symbol,
|
|
5015
5594
|
direction: signal.direction,
|
|
5016
5595
|
timestamp: derivativesEndMs,
|
|
@@ -5037,7 +5616,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
5037
5616
|
lookbackMs,
|
|
5038
5617
|
...params.abortSignal ? { signal: params.abortSignal } : {}
|
|
5039
5618
|
});
|
|
5040
|
-
const context = (0,
|
|
5619
|
+
const context = (0, import_indicators3.buildDerivativesContext)({
|
|
5041
5620
|
symbol: targetSymbol,
|
|
5042
5621
|
direction: signal.direction,
|
|
5043
5622
|
timestamp: derivativesEndMs,
|
|
@@ -5085,7 +5664,7 @@ var import_hyperliquidWhales2 = require("@tradejs/infra/timescale/hyperliquidWha
|
|
|
5085
5664
|
var import_logger7 = require("@tradejs/infra/logger");
|
|
5086
5665
|
|
|
5087
5666
|
// src/hyperliquidWhaleUniverse.ts
|
|
5088
|
-
var
|
|
5667
|
+
var import_node_crypto4 = require("crypto");
|
|
5089
5668
|
|
|
5090
5669
|
// src/config/hyperliquidPerpUniverse.json
|
|
5091
5670
|
var hyperliquidPerpUniverse_default = {
|
|
@@ -5257,7 +5836,7 @@ var hyperliquidWhales_default = {
|
|
|
5257
5836
|
};
|
|
5258
5837
|
|
|
5259
5838
|
// src/hyperliquidWhaleUniverse.ts
|
|
5260
|
-
var fingerprint2 = (value) => (0,
|
|
5839
|
+
var fingerprint2 = (value) => (0, import_node_crypto4.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
|
|
5261
5840
|
var validateUnique = (values, label) => {
|
|
5262
5841
|
if (new Set(values).size !== values.length) {
|
|
5263
5842
|
throw new Error(`Hyperliquid ${label} snapshot contains duplicates`);
|
|
@@ -5748,6 +6327,7 @@ var enrichSignalWithMarketContextStages = async ({
|
|
|
5748
6327
|
})
|
|
5749
6328
|
});
|
|
5750
6329
|
}
|
|
6330
|
+
await enrichSignalWithTickerProfile(signal);
|
|
5751
6331
|
const results = [];
|
|
5752
6332
|
for (const stage of stages) {
|
|
5753
6333
|
if (abortSignal?.aborted) break;
|
|
@@ -5795,12 +6375,12 @@ var findReplayAiAnalysis = ({
|
|
|
5795
6375
|
direction,
|
|
5796
6376
|
ai
|
|
5797
6377
|
}) => {
|
|
5798
|
-
const
|
|
5799
|
-
if (!Array.isArray(
|
|
6378
|
+
const snapshots2 = ai?.replayAnalyses;
|
|
6379
|
+
if (!Array.isArray(snapshots2) || !snapshots2.length) {
|
|
5800
6380
|
return void 0;
|
|
5801
6381
|
}
|
|
5802
6382
|
let best = null;
|
|
5803
|
-
for (const snapshot2 of
|
|
6383
|
+
for (const snapshot2 of snapshots2) {
|
|
5804
6384
|
if (snapshot2.symbol !== signal.symbol || snapshot2.direction !== direction || snapshot2.strategy && snapshot2.strategy !== signal.strategy) {
|
|
5805
6385
|
continue;
|
|
5806
6386
|
}
|
|
@@ -5973,11 +6553,13 @@ var executeEntryOrder = async ({
|
|
|
5973
6553
|
beforePlaceOrder,
|
|
5974
6554
|
recordRuntimeTrade = true,
|
|
5975
6555
|
leverage,
|
|
5976
|
-
executionLimits
|
|
6556
|
+
executionLimits,
|
|
6557
|
+
markPortfolioSubmitted,
|
|
6558
|
+
rejectPortfolioSubmission
|
|
5977
6559
|
}) => {
|
|
5978
6560
|
await beforePlaceOrder?.();
|
|
5979
6561
|
const previousPosition = positionIntent === "increase" ? await connector.getPosition(symbol) : null;
|
|
5980
|
-
|
|
6562
|
+
let orderId = signal.orderId || createRuntimeOrderId(signal.strategy);
|
|
5981
6563
|
const signalTimestamp = signal.timestamp;
|
|
5982
6564
|
const signalClosePrice = currentPrice;
|
|
5983
6565
|
signal.orderId = orderId;
|
|
@@ -6046,9 +6628,16 @@ var executeEntryOrder = async ({
|
|
|
6046
6628
|
...typeof leverage === "number" && Number.isFinite(leverage) ? { leverage } : {},
|
|
6047
6629
|
orderId,
|
|
6048
6630
|
signal,
|
|
6049
|
-
...
|
|
6631
|
+
...rejectPortfolioSubmission ? { onRejected: rejectPortfolioSubmission } : {},
|
|
6632
|
+
...beforeSubmit || markPortfolioSubmitted ? {
|
|
6633
|
+
beforeSubmit: async (context) => {
|
|
6634
|
+
await beforeSubmit?.(context);
|
|
6635
|
+
return markPortfolioSubmitted?.(context.qty);
|
|
6636
|
+
}
|
|
6637
|
+
} : {}
|
|
6050
6638
|
});
|
|
6051
6639
|
const orderAckTime = Date.now();
|
|
6640
|
+
if (markPortfolioSubmitted && signal.orderId) orderId = signal.orderId;
|
|
6052
6641
|
const placedQty = typeof signal.orderQty === "number" && Number.isFinite(signal.orderQty) && signal.orderQty > 0 ? signal.orderQty : qty;
|
|
6053
6642
|
const currentPosition = await connector.getPosition(symbol);
|
|
6054
6643
|
const fillTime = Date.now();
|
|
@@ -7034,7 +7623,7 @@ var handleProtectDecision = async ({
|
|
|
7034
7623
|
}
|
|
7035
7624
|
return decision.code;
|
|
7036
7625
|
};
|
|
7037
|
-
var
|
|
7626
|
+
var executeAdmittedEntryDecision = async ({
|
|
7038
7627
|
connector,
|
|
7039
7628
|
symbol,
|
|
7040
7629
|
decision,
|
|
@@ -7048,7 +7637,9 @@ var executeEntryDecision = async ({
|
|
|
7048
7637
|
ai,
|
|
7049
7638
|
recordRuntimeJournal,
|
|
7050
7639
|
invokeStageHooks,
|
|
7051
|
-
notifyRuntimeError
|
|
7640
|
+
notifyRuntimeError,
|
|
7641
|
+
markPortfolioSubmitted,
|
|
7642
|
+
rejectPortfolioSubmission
|
|
7052
7643
|
}) => {
|
|
7053
7644
|
const signal = decision.signal;
|
|
7054
7645
|
const beforePlaceOrder = async () => {
|
|
@@ -7095,6 +7686,8 @@ var executeEntryDecision = async ({
|
|
|
7095
7686
|
...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {},
|
|
7096
7687
|
signal,
|
|
7097
7688
|
beforePlaceOrder,
|
|
7689
|
+
markPortfolioSubmitted,
|
|
7690
|
+
rejectPortfolioSubmission,
|
|
7098
7691
|
recordRuntimeTrade: recordRuntimeJournal,
|
|
7099
7692
|
executionLimits: resolveEntryExecutionLimits(
|
|
7100
7693
|
hookCtx.strategyConfig,
|
|
@@ -7235,6 +7828,37 @@ var executeEntryDecision = async ({
|
|
|
7235
7828
|
}
|
|
7236
7829
|
return signal ?? decision.code;
|
|
7237
7830
|
};
|
|
7831
|
+
async function executeEntryDecision(params) {
|
|
7832
|
+
if (!params.portfolio) return executeAdmittedEntryDecision(params);
|
|
7833
|
+
const { decision, portfolio } = params;
|
|
7834
|
+
const signal = decision.signal;
|
|
7835
|
+
if (!signal || decision.orderPlan.positionIntent === "increase")
|
|
7836
|
+
return "PORTFOLIO_REQUIRES_NEW_SIGNAL_POSITION";
|
|
7837
|
+
signal.orderId ??= createRuntimeOrderId(signal.strategy);
|
|
7838
|
+
const result = await portfolio.submit(
|
|
7839
|
+
params.portfolioTime ?? decision.entryContext.timestamp,
|
|
7840
|
+
{
|
|
7841
|
+
id: signal.orderId,
|
|
7842
|
+
signalId: signal.signalId,
|
|
7843
|
+
strategy: signal.strategy,
|
|
7844
|
+
symbol: params.symbol,
|
|
7845
|
+
direction: decision.entryContext.direction,
|
|
7846
|
+
qty: decision.orderPlan.qty,
|
|
7847
|
+
timestamp: decision.entryContext.timestamp
|
|
7848
|
+
},
|
|
7849
|
+
(markPortfolioSubmitted, rejectPortfolioSubmission) => executeAdmittedEntryDecision({
|
|
7850
|
+
...params,
|
|
7851
|
+
markPortfolioSubmitted,
|
|
7852
|
+
rejectPortfolioSubmission
|
|
7853
|
+
})
|
|
7854
|
+
);
|
|
7855
|
+
if (result.reason) {
|
|
7856
|
+
signal.orderStatus = "failed";
|
|
7857
|
+
signal.orderFailureReason = result.reason;
|
|
7858
|
+
return signal;
|
|
7859
|
+
}
|
|
7860
|
+
return result.value;
|
|
7861
|
+
}
|
|
7238
7862
|
|
|
7239
7863
|
// src/strategyRuntime.ts
|
|
7240
7864
|
var cloneWithPropertyDescriptors = (value) => Object.create(
|
|
@@ -7262,6 +7886,7 @@ var createStrategyRuntime = ({
|
|
|
7262
7886
|
};
|
|
7263
7887
|
const creator = async ({
|
|
7264
7888
|
userName,
|
|
7889
|
+
connectorName,
|
|
7265
7890
|
config: baseConfig,
|
|
7266
7891
|
symbol,
|
|
7267
7892
|
universe: requestedUniverse,
|
|
@@ -7300,6 +7925,37 @@ var createStrategyRuntime = ({
|
|
|
7300
7925
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
7301
7926
|
const projectHooks = projectConfig.hooks;
|
|
7302
7927
|
const env = String(config.ENV ?? "BACKTEST");
|
|
7928
|
+
let portfolioMarket;
|
|
7929
|
+
const portfolio = getPortfolioRuntime({
|
|
7930
|
+
symbol,
|
|
7931
|
+
canClose: async (position, intent, asOf) => {
|
|
7932
|
+
if (!portfolioMarket || portfolioMarket.candle.timestamp > asOf || !isRuntimeOrderExecutionEnabled({ config, env, connector }))
|
|
7933
|
+
return false;
|
|
7934
|
+
const gate = await authorizeClose(
|
|
7935
|
+
{
|
|
7936
|
+
kind: "exit",
|
|
7937
|
+
code: intent.reason,
|
|
7938
|
+
closePlan: {
|
|
7939
|
+
direction: position.direction,
|
|
7940
|
+
price: position.markPrice,
|
|
7941
|
+
timestamp: asOf
|
|
7942
|
+
}
|
|
7943
|
+
},
|
|
7944
|
+
portfolioMarket,
|
|
7945
|
+
position.strategy
|
|
7946
|
+
);
|
|
7947
|
+
return gate?.allow !== false;
|
|
7948
|
+
},
|
|
7949
|
+
connectorName,
|
|
7950
|
+
value: baseConfig?.PORTFOLIO_POLICY ?? config.PORTFOLIO_POLICY,
|
|
7951
|
+
strategy: strategyName,
|
|
7952
|
+
connector,
|
|
7953
|
+
env,
|
|
7954
|
+
userName,
|
|
7955
|
+
accountId,
|
|
7956
|
+
deploymentId
|
|
7957
|
+
});
|
|
7958
|
+
const portfolioContext = portfolio ? (now2) => isRuntimeOrderExecutionEnabled({ config, env, connector }) ? portfolio.tick(now2) : portfolio.context(now2) : void 0;
|
|
7303
7959
|
const researchPolicyValue = baseConfig?.RESEARCH_ENTRY_POLICY ?? config.RESEARCH_ENTRY_POLICY;
|
|
7304
7960
|
const researchEntryPolicy = researchPolicyValue == null ? void 0 : parseResearchEntryPolicy(researchPolicyValue);
|
|
7305
7961
|
if (researchEntryPolicy && (env !== "BACKTEST" || Reflect.get(connector, "__tradejsTestConnector") !== true))
|
|
@@ -7464,7 +8120,10 @@ var createStrategyRuntime = ({
|
|
|
7464
8120
|
return invokeHook(stage, hook, params, errorContext);
|
|
7465
8121
|
};
|
|
7466
8122
|
const invokeGateHooks = async (stage, hook, params, errorContext = {}) => {
|
|
7467
|
-
const failureResult =
|
|
8123
|
+
const failureResult = {
|
|
8124
|
+
allow: false,
|
|
8125
|
+
reason: "ERROR"
|
|
8126
|
+
};
|
|
7468
8127
|
const projectResults = await invokeProjectHooks(
|
|
7469
8128
|
stage,
|
|
7470
8129
|
params,
|
|
@@ -7485,6 +8144,12 @@ var createStrategyRuntime = ({
|
|
|
7485
8144
|
failureResult
|
|
7486
8145
|
);
|
|
7487
8146
|
};
|
|
8147
|
+
const authorizeClose = (decision, market, owner = strategyName) => invokeGateHooks(
|
|
8148
|
+
"beforeClosePosition",
|
|
8149
|
+
resolveManifest(owner)?.hooks?.beforeClosePosition,
|
|
8150
|
+
{ ctx: getHookCtx(owner), market, decision },
|
|
8151
|
+
{ decision, market }
|
|
8152
|
+
);
|
|
7488
8153
|
const applyProjectAfterCoreDecisionHooks = async ({
|
|
7489
8154
|
hookCtx,
|
|
7490
8155
|
market,
|
|
@@ -7598,6 +8263,20 @@ var createStrategyRuntime = ({
|
|
|
7598
8263
|
interval: decisionInterval
|
|
7599
8264
|
}) => {
|
|
7600
8265
|
if (!baseContext) return void 0;
|
|
8266
|
+
const profileSource = strategyManifest?.contextRequirements?.tickerProfiles;
|
|
8267
|
+
if (profileSource) {
|
|
8268
|
+
const profile = await loadTickerProfileContext({
|
|
8269
|
+
...profileSource,
|
|
8270
|
+
projectRoot,
|
|
8271
|
+
symbol: decisionSymbol,
|
|
8272
|
+
timestamp: candle.timestamp
|
|
8273
|
+
});
|
|
8274
|
+
if (!profile)
|
|
8275
|
+
throw new Error(
|
|
8276
|
+
"Required ticker profile is unavailable at decision time"
|
|
8277
|
+
);
|
|
8278
|
+
baseContext = withTickerProfile(baseContext, profile);
|
|
8279
|
+
}
|
|
7601
8280
|
if (!coreContextRequirements.has("hyperliquidWhales")) {
|
|
7602
8281
|
return baseContext;
|
|
7603
8282
|
}
|
|
@@ -7765,6 +8444,8 @@ var createStrategyRuntime = ({
|
|
|
7765
8444
|
runtime: pending2.runtime
|
|
7766
8445
|
});
|
|
7767
8446
|
return executeEntryDecision({
|
|
8447
|
+
portfolio,
|
|
8448
|
+
portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
|
|
7768
8449
|
connector,
|
|
7769
8450
|
symbol,
|
|
7770
8451
|
decision: pending2.decision,
|
|
@@ -7782,6 +8463,11 @@ var createStrategyRuntime = ({
|
|
|
7782
8463
|
});
|
|
7783
8464
|
};
|
|
7784
8465
|
const runWithDecisionOverride = async (candle, btcCandle, options = {}) => {
|
|
8466
|
+
portfolioMarket = { candle, btcCandle };
|
|
8467
|
+
if (portfolioContext)
|
|
8468
|
+
await portfolioContext(
|
|
8469
|
+
env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now()
|
|
8470
|
+
);
|
|
7785
8471
|
appendCurrentMarketData(candle, btcCandle, options.ethCandle);
|
|
7786
8472
|
const ethCandle = resolveEthCandle(candle, options.ethCandle);
|
|
7787
8473
|
indicatorsState.setCurrentBar(candle, btcCandle, ethCandle);
|
|
@@ -7884,15 +8570,10 @@ var createStrategyRuntime = ({
|
|
|
7884
8570
|
if (!makeOrdersEnabled) {
|
|
7885
8571
|
return decision.code;
|
|
7886
8572
|
}
|
|
7887
|
-
const closeGate = await
|
|
7888
|
-
|
|
7889
|
-
|
|
7890
|
-
|
|
7891
|
-
ctx: decisionHookCtx,
|
|
7892
|
-
market,
|
|
7893
|
-
decision
|
|
7894
|
-
},
|
|
7895
|
-
{ decision, market }
|
|
8573
|
+
const closeGate = await authorizeClose(
|
|
8574
|
+
decision,
|
|
8575
|
+
market,
|
|
8576
|
+
decisionHookCtx.strategyName
|
|
7896
8577
|
);
|
|
7897
8578
|
if (closeGate?.allow === false) {
|
|
7898
8579
|
return closeGate.reason ? `CLOSE_BLOCKED_BY_HOOK:${closeGate.reason}` : "CLOSE_BLOCKED_BY_HOOK";
|
|
@@ -7952,6 +8633,13 @@ var createStrategyRuntime = ({
|
|
|
7952
8633
|
});
|
|
7953
8634
|
const signal = decision.signal;
|
|
7954
8635
|
if (signal) {
|
|
8636
|
+
if (portfolio)
|
|
8637
|
+
signal.additionalIndicators = {
|
|
8638
|
+
...signal.additionalIndicators,
|
|
8639
|
+
portfolioContext: await portfolioContext(
|
|
8640
|
+
env === "BACKTEST" || env === "PARITY" ? decision.entryContext.timestamp : Date.now()
|
|
8641
|
+
)
|
|
8642
|
+
};
|
|
7955
8643
|
if (runtimeLineage) signal.runtimeLineage = runtimeLineage;
|
|
7956
8644
|
if (universe) signal.universe = universe;
|
|
7957
8645
|
if (assetClass) signal.assetClass = assetClass;
|
|
@@ -8191,6 +8879,8 @@ var createStrategyRuntime = ({
|
|
|
8191
8879
|
});
|
|
8192
8880
|
}
|
|
8193
8881
|
return executeEntryDecision({
|
|
8882
|
+
portfolio,
|
|
8883
|
+
portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
|
|
8194
8884
|
connector,
|
|
8195
8885
|
symbol,
|
|
8196
8886
|
decision,
|