@tradejs/node 3.1.29-beta.289 → 3.1.29-beta.291
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai.js +109 -48
- package/dist/ai.mjs +1 -1
- package/dist/backtest.js +2504 -1883
- package/dist/backtest.mjs +3 -2
- package/dist/{chunk-KYGJBMKE.mjs → chunk-HRON2EAI.mjs} +432 -40
- package/dist/{chunk-UL5Z46JL.mjs → chunk-PYJWXAYR.mjs} +97 -30
- package/dist/chunk-UUNEWAT4.mjs +193 -0
- package/dist/{chunk-BO42QWAB.mjs → chunk-W6PPAADK.mjs} +1 -1
- package/dist/cli.js +837 -775
- package/dist/cli.mjs +4 -3
- package/dist/diagnostics.js +2532 -1911
- package/dist/diagnostics.mjs +4 -3
- package/dist/mcp.d.mts +4 -2
- package/dist/mcp.d.ts +4 -2
- package/dist/mcp.js +2614 -1971
- package/dist/mcp.mjs +27 -5
- package/dist/portfolio.d.mts +28 -0
- package/dist/portfolio.d.ts +28 -0
- package/dist/portfolio.js +210 -0
- package/dist/portfolio.mjs +9 -0
- package/dist/registry.js +2091 -1470
- package/dist/registry.mjs +3 -2
- package/dist/runtimeDashboard.js +2255 -1632
- package/dist/runtimeDashboard.mjs +4 -3
- package/dist/runtimeStrategies.js +2417 -1796
- package/dist/runtimeStrategies.mjs +4 -3
- package/dist/strategies.d.mts +3 -1
- package/dist/strategies.d.ts +3 -1
- package/dist/strategies.js +2106 -1485
- package/dist/strategies.mjs +3 -2
- package/package.json +10 -5
package/dist/diagnostics.js
CHANGED
|
@@ -808,1674 +808,2405 @@ __export(diagnostics_exports, {
|
|
|
808
808
|
module.exports = __toCommonJS(diagnostics_exports);
|
|
809
809
|
|
|
810
810
|
// src/diagnostics/runtimeEvidenceArtifacts.ts
|
|
811
|
-
var
|
|
811
|
+
var import_node_crypto6 = require("crypto");
|
|
812
812
|
var import_promises3 = __toESM(require("fs/promises"));
|
|
813
813
|
var import_node_path3 = __toESM(require("path"));
|
|
814
814
|
|
|
815
815
|
// src/runtimeStrategies.ts
|
|
816
816
|
var import_jev8 = require("@tradejs/core/jev");
|
|
817
|
-
var
|
|
817
|
+
var import_node_crypto5 = require("crypto");
|
|
818
818
|
var import_promises2 = require("fs/promises");
|
|
819
819
|
var import_node_path2 = __toESM(require("path"));
|
|
820
820
|
var import_runtimeControls = require("@tradejs/infra/runtimeControls");
|
|
821
821
|
var import_tradingAccounts = require("@tradejs/infra/tradingAccounts");
|
|
822
822
|
|
|
823
|
-
// src/
|
|
824
|
-
var
|
|
823
|
+
// src/strategy/portfolioRuntime.ts
|
|
824
|
+
var import_node_crypto2 = require("crypto");
|
|
825
|
+
var import_portfolio2 = require("@tradejs/core/portfolio");
|
|
826
|
+
var import_redis2 = require("@tradejs/infra/redis");
|
|
825
827
|
|
|
826
|
-
// src/
|
|
827
|
-
var
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
const questions = questionsForJevInput(input);
|
|
858
|
-
const questionsHash = (0, import_jev5.jevHash)(questions);
|
|
859
|
-
const base = {
|
|
860
|
-
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
861
|
-
schema: "tradejs-signal-assessment/v5",
|
|
862
|
-
source: config.source,
|
|
863
|
-
mode: config.mode,
|
|
864
|
-
inputHash,
|
|
865
|
-
questionsHash,
|
|
866
|
-
geometryStatus: input.geometryStatus,
|
|
867
|
-
levelsValid: typeof input.features["signal.validLevels"] === "boolean" ? input.features["signal.validLevels"] : null
|
|
868
|
-
};
|
|
869
|
-
if (!input.questions.length)
|
|
870
|
-
return {
|
|
871
|
-
assessment: {
|
|
872
|
-
...base,
|
|
873
|
-
status: "unavailable",
|
|
874
|
-
model: "",
|
|
875
|
-
scores: emptyScores(),
|
|
876
|
-
confidence: emptyScores()
|
|
877
|
-
}
|
|
878
|
-
};
|
|
879
|
-
if (config.source === "local") {
|
|
880
|
-
const contents = await import_promises.default.readFile(
|
|
881
|
-
import_node_path.default.resolve(projectRoot, config.modelFile),
|
|
882
|
-
"utf8"
|
|
883
|
-
);
|
|
884
|
-
if ((0, import_jev5.jevFileHash)(contents) !== config.modelSha256)
|
|
885
|
-
throw new Error("Local Jev gate checksum mismatch");
|
|
886
|
-
const { validateJevGateModel: validateJevGateModel2 } = await Promise.resolve().then(() => (init_jevTraining(), jevTraining_exports));
|
|
887
|
-
const model = validateJevGateModel2(JSON.parse(contents));
|
|
888
|
-
if (model.strategy !== input.strategy || model.questionsHash !== (0, import_jev5.jevHash)(JEV_QUESTIONS))
|
|
889
|
-
throw new Error("Local Jev gate strategy or question version mismatch");
|
|
890
|
-
const scores = Object.fromEntries(
|
|
891
|
-
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
892
|
-
key,
|
|
893
|
-
input.questions.includes(key) && model.trees[key] ? (0, import_jev4.predictJevTree)(model.trees[key], input.features) : null
|
|
894
|
-
])
|
|
895
|
-
);
|
|
896
|
-
return {
|
|
897
|
-
assessment: {
|
|
898
|
-
...base,
|
|
899
|
-
status: "available",
|
|
900
|
-
model: config.modelSha256,
|
|
901
|
-
scores,
|
|
902
|
-
confidence: emptyScores()
|
|
828
|
+
// src/portfolio.ts
|
|
829
|
+
var import_portfolio = require("@tradejs/core/portfolio");
|
|
830
|
+
function createPortfolioCoordinator({
|
|
831
|
+
policy,
|
|
832
|
+
strategy,
|
|
833
|
+
store,
|
|
834
|
+
execution
|
|
835
|
+
}) {
|
|
836
|
+
const fingerprint3 = (0, import_portfolio.portfolioFingerprint)(policy, strategy);
|
|
837
|
+
let cache;
|
|
838
|
+
async function transact(now2, change, force = false) {
|
|
839
|
+
for (let attempt = 0; attempt < 16; attempt++) {
|
|
840
|
+
const previous = await store.read();
|
|
841
|
+
if (previous && previous.fingerprint !== fingerprint3)
|
|
842
|
+
throw new Error(
|
|
843
|
+
"Portfolio policy changed under existing state; reconcile the persisted scope before changing configuration"
|
|
844
|
+
);
|
|
845
|
+
const base = previous ?? (0, import_portfolio.createPortfolioState)(fingerprint3);
|
|
846
|
+
const bucket = Math.floor(now2 / policy.observationIntervalMs);
|
|
847
|
+
const observation = !force && cache?.revision === base.revision && cache.bucket === bucket ? cache.observation : await execution.observe(now2, base.reservations);
|
|
848
|
+
const reconciled = (0, import_portfolio.reconcilePortfolio)(
|
|
849
|
+
policy,
|
|
850
|
+
strategy,
|
|
851
|
+
base,
|
|
852
|
+
observation
|
|
853
|
+
);
|
|
854
|
+
for (const intent of base.closing) {
|
|
855
|
+
if (!observation.positions.some(
|
|
856
|
+
(p) => p.symbol === intent.position.symbol
|
|
857
|
+
))
|
|
858
|
+
await execution.confirmClosed?.(intent, observation.asOf);
|
|
903
859
|
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
const file = import_node_path.default.join(dir, "records", `${id}.json`);
|
|
910
|
-
const getRecord = async () => {
|
|
911
|
-
const existing = await (0, import_jev5.readJevArtifact)(file);
|
|
912
|
-
if (existing) {
|
|
913
|
-
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))
|
|
914
|
-
throw new Error("Jev recording identity mismatch");
|
|
915
|
-
(0, import_jev5.validateJevResponse)(existing.response, input.questions);
|
|
916
|
-
for (const key of import_jev4.JEV_DIMENSIONS) {
|
|
917
|
-
const expected = input.questions.includes(key) ? existing.response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null;
|
|
918
|
-
if (existing.scores[key] !== expected)
|
|
919
|
-
throw new Error("Jev recording scores do not match the response");
|
|
860
|
+
const changed = change(reconciled, observation);
|
|
861
|
+
const next = { ...changed.state, revision: base.revision + 1 };
|
|
862
|
+
if (await store.compare(previous, next)) {
|
|
863
|
+
cache = { revision: next.revision, bucket, observation };
|
|
864
|
+
return { state: next, observation, result: changed.result };
|
|
920
865
|
}
|
|
921
|
-
|
|
866
|
+
cache = void 0;
|
|
922
867
|
}
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
868
|
+
throw new Error("Portfolio state contention; entry was not submitted");
|
|
869
|
+
}
|
|
870
|
+
async function tick(now2, force = false) {
|
|
871
|
+
const claimed = await transact(
|
|
872
|
+
now2,
|
|
873
|
+
(state, observation) => {
|
|
874
|
+
const commands = (0, import_portfolio.portfolioCloseCommands)(policy, state, observation);
|
|
875
|
+
return {
|
|
876
|
+
state: {
|
|
877
|
+
...state,
|
|
878
|
+
closing: state.closing.map(
|
|
879
|
+
(c) => commands.some((x) => x.intent.positionId === c.positionId) ? { ...c, lastAttemptAt: observation.asOf } : c
|
|
880
|
+
)
|
|
881
|
+
},
|
|
882
|
+
result: commands
|
|
883
|
+
};
|
|
884
|
+
},
|
|
885
|
+
force
|
|
886
|
+
);
|
|
887
|
+
for (const command of claimed.result)
|
|
888
|
+
await execution.close(
|
|
889
|
+
command.position,
|
|
890
|
+
command.intent,
|
|
891
|
+
claimed.observation.asOf
|
|
926
892
|
);
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
893
|
+
if (claimed.result.length) {
|
|
894
|
+
cache = void 0;
|
|
895
|
+
const refreshed = await transact(
|
|
896
|
+
now2,
|
|
897
|
+
(state) => ({ state, result: void 0 }),
|
|
898
|
+
true
|
|
899
|
+
);
|
|
900
|
+
return (0, import_portfolio.buildPortfolioContext)(
|
|
901
|
+
policy,
|
|
902
|
+
strategy,
|
|
903
|
+
refreshed.state,
|
|
904
|
+
refreshed.observation
|
|
935
905
|
);
|
|
936
|
-
const started = Date.now();
|
|
937
|
-
const state = stateForJevInput(input);
|
|
938
|
-
const responseId = (0, import_jev5.jevHash)({
|
|
939
|
-
state,
|
|
940
|
-
questionsHash,
|
|
941
|
-
provider,
|
|
942
|
-
user: (0, import_jev5.jevHash)(userName)
|
|
943
|
-
});
|
|
944
|
-
const responseFile = import_node_path.default.join(dir, "responses", `${responseId}.json`);
|
|
945
|
-
let responsePromise = pendingResponses.get(responseFile);
|
|
946
|
-
if (!responsePromise) {
|
|
947
|
-
responsePromise = (async () => {
|
|
948
|
-
const saved = await (0, import_jev5.readJevArtifact)(responseFile);
|
|
949
|
-
if (saved) return (0, import_jev5.validateJevResponse)(saved, input.questions);
|
|
950
|
-
const fresh = await (0, import_jev5.requestJev)(provider, apiKey, state, questions);
|
|
951
|
-
await (0, import_jev5.writeJevArtifact)(responseFile, fresh);
|
|
952
|
-
return fresh;
|
|
953
|
-
})();
|
|
954
|
-
pendingResponses.set(responseFile, responsePromise);
|
|
955
|
-
}
|
|
956
|
-
let response;
|
|
957
|
-
try {
|
|
958
|
-
response = await responsePromise;
|
|
959
|
-
} finally {
|
|
960
|
-
pendingResponses.delete(responseFile);
|
|
961
906
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
907
|
+
return (0, import_portfolio.buildPortfolioContext)(
|
|
908
|
+
policy,
|
|
909
|
+
strategy,
|
|
910
|
+
claimed.state,
|
|
911
|
+
claimed.observation
|
|
967
912
|
);
|
|
968
|
-
const value = {
|
|
969
|
-
schema: "tradejs-jev-record/v4",
|
|
970
|
-
id,
|
|
971
|
-
inputHash,
|
|
972
|
-
questionsHash,
|
|
973
|
-
provider,
|
|
974
|
-
input,
|
|
975
|
-
response,
|
|
976
|
-
scores,
|
|
977
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
978
|
-
elapsedMs: Date.now() - started
|
|
979
|
-
};
|
|
980
|
-
await (0, import_jev5.writeJevArtifact)(file, value);
|
|
981
|
-
return await (0, import_jev5.readJevArtifact)(file);
|
|
982
|
-
};
|
|
983
|
-
const pendingKey = `${file}:${(0, import_jev5.jevHash)(userName)}`;
|
|
984
|
-
let promise = pending.get(pendingKey);
|
|
985
|
-
if (!promise) {
|
|
986
|
-
promise = getRecord();
|
|
987
|
-
pending.set(pendingKey, promise);
|
|
988
913
|
}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
914
|
+
async function updateReservation(now2, id, submitted, qty) {
|
|
915
|
+
return transact(
|
|
916
|
+
now2,
|
|
917
|
+
(s, o) => {
|
|
918
|
+
const exists2 = s.reservations.some((r) => r.id === id);
|
|
919
|
+
const blocked = submitted && (!exists2 || s.closing.length > 0 || o.asOf <= s.blockedThrough);
|
|
920
|
+
return {
|
|
921
|
+
state: {
|
|
922
|
+
...s,
|
|
923
|
+
reservations: s.reservations.flatMap(
|
|
924
|
+
(r) => r.id !== id ? [r] : submitted && !blocked ? [{ ...r, submitted: true, qty: qty ?? r.qty }] : []
|
|
925
|
+
)
|
|
926
|
+
},
|
|
927
|
+
result: !blocked
|
|
928
|
+
};
|
|
929
|
+
},
|
|
930
|
+
true
|
|
931
|
+
);
|
|
994
932
|
}
|
|
995
933
|
return {
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
934
|
+
async context(now2) {
|
|
935
|
+
const state = await store.read() ?? (0, import_portfolio.createPortfolioState)(fingerprint3);
|
|
936
|
+
if (state.fingerprint !== fingerprint3)
|
|
937
|
+
throw new Error("Portfolio policy changed under existing state");
|
|
938
|
+
const observation = await execution.observe(now2, state.reservations);
|
|
939
|
+
(0, import_portfolio.validatePortfolioObservation)(observation);
|
|
940
|
+
return (0, import_portfolio.buildPortfolioContext)(policy, strategy, state, observation);
|
|
941
|
+
},
|
|
942
|
+
tick,
|
|
943
|
+
async submit(now2, entry, send) {
|
|
944
|
+
await tick(now2);
|
|
945
|
+
const admitted = await transact(
|
|
946
|
+
now2,
|
|
947
|
+
(s, o) => {
|
|
948
|
+
const r = (0, import_portfolio.reservePortfolioEntry)(policy, strategy, s, o, {
|
|
949
|
+
...entry,
|
|
950
|
+
submitted: false,
|
|
951
|
+
oppositeHandled: false
|
|
952
|
+
});
|
|
953
|
+
return { state: r.state, result: r.reason };
|
|
954
|
+
},
|
|
955
|
+
true
|
|
956
|
+
);
|
|
957
|
+
if (admitted.result) return { reason: admitted.result };
|
|
958
|
+
let submitted = false;
|
|
959
|
+
try {
|
|
960
|
+
const value = await send(
|
|
961
|
+
async (qty) => {
|
|
962
|
+
if (!Number.isFinite(qty) || qty <= 0)
|
|
963
|
+
throw new Error("Invalid submitted portfolio quantity");
|
|
964
|
+
submitted = true;
|
|
965
|
+
const update = await updateReservation(now2, entry.id, true, qty);
|
|
966
|
+
if (!update.result)
|
|
967
|
+
throw new Error("PORTFOLIO_SUBMISSION_NO_LONGER_ADMITTED");
|
|
968
|
+
},
|
|
969
|
+
async () => {
|
|
970
|
+
await transact(
|
|
971
|
+
now2,
|
|
972
|
+
(s) => ({
|
|
973
|
+
state: {
|
|
974
|
+
...s,
|
|
975
|
+
reservations: s.reservations.filter((r) => r.id !== entry.id),
|
|
976
|
+
completedSignals: {
|
|
977
|
+
...s.completedSignals,
|
|
978
|
+
[entry.signalId]: now2
|
|
979
|
+
}
|
|
980
|
+
},
|
|
981
|
+
result: void 0
|
|
982
|
+
}),
|
|
983
|
+
true
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
);
|
|
987
|
+
if (!submitted) await updateReservation(now2, entry.id, false);
|
|
988
|
+
else await tick(now2, true);
|
|
989
|
+
return { value };
|
|
990
|
+
} catch (error) {
|
|
991
|
+
if (!submitted) await updateReservation(now2, entry.id, false);
|
|
992
|
+
throw error;
|
|
993
|
+
}
|
|
1009
994
|
}
|
|
1010
995
|
};
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
try {
|
|
1022
|
-
const input = buildJevInput(signal);
|
|
1023
|
-
({ assessment } = await evaluateJevInput({
|
|
1024
|
-
input,
|
|
1025
|
-
config,
|
|
1026
|
-
userName,
|
|
1027
|
-
projectRoot
|
|
1028
|
-
}));
|
|
1029
|
-
} catch (error) {
|
|
1030
|
-
if (strict) throw error;
|
|
1031
|
-
assessment = {
|
|
1032
|
-
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
1033
|
-
schema: "tradejs-signal-assessment/v5",
|
|
1034
|
-
source: config.source,
|
|
1035
|
-
mode: config.mode,
|
|
1036
|
-
status: "unavailable",
|
|
1037
|
-
inputHash: "",
|
|
1038
|
-
questionsHash: "",
|
|
1039
|
-
geometryStatus: "absent",
|
|
1040
|
-
levelsValid: null,
|
|
1041
|
-
model: config.provider?.model ?? config.modelSha256 ?? "",
|
|
1042
|
-
scores: emptyScores(),
|
|
1043
|
-
confidence: emptyScores()
|
|
1044
|
-
};
|
|
1045
|
-
}
|
|
1046
|
-
signal.assessment = assessment;
|
|
1047
|
-
signal.additionalIndicators = {
|
|
1048
|
-
...signal.additionalIndicators,
|
|
1049
|
-
jev: (0, import_jev4.jevAssessmentFeatures)(assessment)
|
|
996
|
+
}
|
|
997
|
+
function createMemoryPortfolioStore() {
|
|
998
|
+
let state = null;
|
|
999
|
+
return {
|
|
1000
|
+
read: async () => state === null ? null : structuredClone(state),
|
|
1001
|
+
compare: async (expected, next) => {
|
|
1002
|
+
if ((state?.revision ?? -1) !== (expected?.revision ?? -1)) return false;
|
|
1003
|
+
state = structuredClone(next);
|
|
1004
|
+
return true;
|
|
1005
|
+
}
|
|
1050
1006
|
};
|
|
1051
|
-
|
|
1052
|
-
signalId: signal.signalId,
|
|
1053
|
-
strategy: signal.strategy,
|
|
1054
|
-
symbol: signal.symbol,
|
|
1055
|
-
timestamp: signal.timestamp,
|
|
1056
|
-
scope,
|
|
1057
|
-
user: (0, import_jev5.jevHash)(userName),
|
|
1058
|
-
config,
|
|
1059
|
-
assessment
|
|
1060
|
-
};
|
|
1061
|
-
await (0, import_jev5.writeJevArtifact)(
|
|
1062
|
-
import_node_path.default.resolve(
|
|
1063
|
-
projectRoot,
|
|
1064
|
-
config.recordsDir ?? "data/ai/jev",
|
|
1065
|
-
"decisions",
|
|
1066
|
-
`${(0, import_jev5.jevHash)(identity)}.json`
|
|
1067
|
-
),
|
|
1068
|
-
identity
|
|
1069
|
-
);
|
|
1070
|
-
return assessment;
|
|
1071
|
-
};
|
|
1072
|
-
|
|
1073
|
-
// src/strategyRuntime.ts
|
|
1074
|
-
var import_constants5 = require("@tradejs/core/constants");
|
|
1075
|
-
var import_strategies6 = require("@tradejs/core/strategies");
|
|
1076
|
-
var import_logger11 = require("@tradejs/infra/logger");
|
|
1077
|
-
|
|
1078
|
-
// src/strategyHelpers/runtime.ts
|
|
1079
|
-
var import_logger9 = require("@tradejs/infra/logger");
|
|
1080
|
-
var import_constants3 = require("@tradejs/core/constants");
|
|
1081
|
-
var import_ml2 = require("@tradejs/infra/ml");
|
|
1007
|
+
}
|
|
1082
1008
|
|
|
1083
|
-
// src/
|
|
1009
|
+
// src/runtimeJournal.ts
|
|
1010
|
+
var import_node_crypto = require("crypto");
|
|
1011
|
+
var import_constants = require("@tradejs/core/constants");
|
|
1012
|
+
var import_time = require("@tradejs/core/time");
|
|
1013
|
+
var import_trade = require("@tradejs/core/trade");
|
|
1084
1014
|
var import_logger = require("@tradejs/infra/logger");
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
var
|
|
1088
|
-
|
|
1089
|
-
super(...arguments);
|
|
1090
|
-
this.name = "EntrySubmissionValidationError";
|
|
1091
|
-
}
|
|
1092
|
-
};
|
|
1093
|
-
var resolveEntryExecutionLimits = (config, direction) => {
|
|
1094
|
-
const side = config[direction];
|
|
1095
|
-
const minimum = side && typeof side === "object" && "minRiskRatio" in side ? side.minRiskRatio : void 0;
|
|
1096
|
-
const maximum = config.MAX_LOSS_VALUE;
|
|
1097
|
-
if (minimum == null && maximum == null) return void 0;
|
|
1098
|
-
const minimumNetRiskRatio = minimum == null ? void 0 : Number(minimum);
|
|
1099
|
-
const maxLossValue = maximum == null ? void 0 : Number(maximum);
|
|
1100
|
-
const feeRate = Number(config.RISK_FEE_RATE ?? 0);
|
|
1101
|
-
const slippage = Number(config.RISK_SLIPPAGE_BPS ?? 0);
|
|
1102
|
-
const impact = Number(config.RISK_MARKET_IMPACT_BPS ?? 0);
|
|
1103
|
-
const slippageBps = slippage + impact;
|
|
1104
|
-
if (minimumNetRiskRatio != null && (!Number.isFinite(minimumNetRiskRatio) || minimumNetRiskRatio < 0) || maxLossValue != null && (!Number.isFinite(maxLossValue) || maxLossValue <= 0) || !Number.isFinite(feeRate) || feeRate < 0 || feeRate >= 1 || !Number.isFinite(slippage) || slippage < 0 || !Number.isFinite(impact) || impact < 0 || !Number.isFinite(slippageBps) || slippageBps < 0 || slippageBps >= 1e4)
|
|
1105
|
-
throw new Error("INVALID_ENTRY_EXECUTION_LIMITS");
|
|
1106
|
-
return { minimumNetRiskRatio, maxLossValue, feeRate, slippageBps };
|
|
1107
|
-
};
|
|
1108
|
-
var validateEntryExecution = ({
|
|
1015
|
+
var import_redis = require("@tradejs/infra/redis");
|
|
1016
|
+
var now = () => Date.now();
|
|
1017
|
+
var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
|
|
1018
|
+
var calculateClosedPnl = ({
|
|
1109
1019
|
direction,
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
takeProfitPrice,
|
|
1114
|
-
limits
|
|
1020
|
+
entryPrice,
|
|
1021
|
+
exitPrice,
|
|
1022
|
+
qty
|
|
1115
1023
|
}) => {
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
1119
|
-
);
|
|
1120
|
-
const side = direction === "LONG" ? 1 : -1;
|
|
1121
|
-
if (side * (price - stopLossPrice) <= 0 || side * (takeProfitPrice - price) <= 0)
|
|
1122
|
-
throw new EntrySubmissionValidationError(
|
|
1123
|
-
"INVALID_ENTRY_PROTECTION_AT_SUBMISSION"
|
|
1124
|
-
);
|
|
1125
|
-
const slippage = limits.slippageBps / 1e4;
|
|
1126
|
-
const cost = (value, sign) => value * slippage + value * (1 + sign * slippage) * limits.feeRate;
|
|
1127
|
-
const entryCost = cost(price, side);
|
|
1128
|
-
const lossPerUnit = Math.abs(price - stopLossPrice) + entryCost + cost(stopLossPrice, -side);
|
|
1129
|
-
const rewardPerUnit = Math.max(
|
|
1130
|
-
0,
|
|
1131
|
-
Math.abs(takeProfitPrice - price) - entryCost - cost(takeProfitPrice, -side)
|
|
1132
|
-
);
|
|
1133
|
-
const netRiskRatio = rewardPerUnit / lossPerUnit;
|
|
1134
|
-
const plannedLossValue = qty * lossPerUnit;
|
|
1135
|
-
if (!Number.isFinite(netRiskRatio) || !Number.isFinite(plannedLossValue))
|
|
1136
|
-
throw new EntrySubmissionValidationError(
|
|
1137
|
-
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
1138
|
-
);
|
|
1139
|
-
if (limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio)
|
|
1140
|
-
throw new EntrySubmissionValidationError(
|
|
1141
|
-
"ENTRY_RR_BELOW_MIN_AT_SUBMISSION"
|
|
1142
|
-
);
|
|
1143
|
-
if (limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9))
|
|
1144
|
-
throw new EntrySubmissionValidationError(
|
|
1145
|
-
"ENTRY_RISK_EXCEEDS_MAX_LOSS_AT_SUBMISSION"
|
|
1146
|
-
);
|
|
1147
|
-
return { netRiskRatio, plannedLossValue };
|
|
1024
|
+
const pnl = direction === "LONG" ? (exitPrice - entryPrice) * qty : (entryPrice - exitPrice) * qty;
|
|
1025
|
+
return Number.isFinite(pnl) ? pnl : null;
|
|
1148
1026
|
};
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
}
|
|
1158
|
-
if (
|
|
1159
|
-
return
|
|
1160
|
-
arrivalSnapshotTime: Date.now(),
|
|
1161
|
-
arrivalSource: "unavailable",
|
|
1162
|
-
bid: null,
|
|
1163
|
-
ask: null,
|
|
1164
|
-
arrivalMid: null,
|
|
1165
|
-
spreadBps: null
|
|
1166
|
-
};
|
|
1027
|
+
var createRuntimeOrderId = (strategy) => {
|
|
1028
|
+
const prefix = (0, import_trade.createRuntimeOrderLinkPrefix)(strategy);
|
|
1029
|
+
if (prefix === "tjs-") {
|
|
1030
|
+
return `tjs-${(0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 24).toLowerCase()}`;
|
|
1031
|
+
}
|
|
1032
|
+
return `${prefix}${toRandomOrderSuffix()}`;
|
|
1033
|
+
};
|
|
1034
|
+
var recordRuntimeTradeOpen = async (params) => {
|
|
1035
|
+
const { userName } = params;
|
|
1036
|
+
if (!userName) {
|
|
1037
|
+
return null;
|
|
1167
1038
|
}
|
|
1039
|
+
const record2 = {
|
|
1040
|
+
...params,
|
|
1041
|
+
entryCount: 1,
|
|
1042
|
+
lastEntryPrice: params.entryPrice,
|
|
1043
|
+
lastEntryQty: params.qty,
|
|
1044
|
+
lastEntryTimestamp: params.entryTimestamp,
|
|
1045
|
+
status: "active",
|
|
1046
|
+
currentPrice: params.entryPrice,
|
|
1047
|
+
currentPnl: 0,
|
|
1048
|
+
closedPnl: null,
|
|
1049
|
+
exitPrice: null,
|
|
1050
|
+
exitTimestamp: null,
|
|
1051
|
+
lastSyncedAt: now()
|
|
1052
|
+
};
|
|
1053
|
+
const dayKey = (0, import_time.getRuntimeStorageDayKey)(record2.entryTimestamp);
|
|
1054
|
+
const runtimeScopeId = record2.deploymentId ?? record2.accountId;
|
|
1168
1055
|
try {
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1056
|
+
await Promise.all([
|
|
1057
|
+
(0, import_redis.setData)(import_redis.redisKeys.runtimeTrade(userName, record2.orderId), record2, {
|
|
1058
|
+
expire: 0
|
|
1059
|
+
}),
|
|
1060
|
+
(0, import_redis.setHashJsonField)(
|
|
1061
|
+
import_redis.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
1062
|
+
record2.orderId,
|
|
1063
|
+
record2,
|
|
1064
|
+
{ expire: 0 }
|
|
1065
|
+
),
|
|
1066
|
+
(0, import_redis.setData)(
|
|
1067
|
+
import_redis.redisKeys.runtimeActiveTrade(userName, record2.symbol, runtimeScopeId),
|
|
1068
|
+
{ orderId: record2.orderId },
|
|
1069
|
+
{ expire: 0 }
|
|
1070
|
+
)
|
|
1071
|
+
]);
|
|
1184
1072
|
} catch (error) {
|
|
1185
|
-
import_logger.logger.
|
|
1186
|
-
"runtime
|
|
1187
|
-
symbol,
|
|
1073
|
+
import_logger.logger.error(
|
|
1074
|
+
"runtime trade open journal failed: %s %s",
|
|
1075
|
+
record2.symbol,
|
|
1188
1076
|
error?.message || String(error)
|
|
1189
1077
|
);
|
|
1190
|
-
return {
|
|
1191
|
-
arrivalSnapshotTime: Date.now(),
|
|
1192
|
-
arrivalSource: "top_of_book_error",
|
|
1193
|
-
bid: null,
|
|
1194
|
-
ask: null,
|
|
1195
|
-
arrivalMid: null,
|
|
1196
|
-
spreadBps: null
|
|
1197
|
-
};
|
|
1198
1078
|
}
|
|
1079
|
+
return record2;
|
|
1199
1080
|
};
|
|
1200
|
-
var
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
}
|
|
1081
|
+
var recordRuntimeTradeIncrease = async (params) => {
|
|
1082
|
+
const {
|
|
1083
|
+
userName,
|
|
1084
|
+
strategy,
|
|
1085
|
+
symbol,
|
|
1086
|
+
direction,
|
|
1087
|
+
resultingQty,
|
|
1088
|
+
resultingEntryPrice,
|
|
1089
|
+
addedQty,
|
|
1090
|
+
addedEntryPrice,
|
|
1091
|
+
entryTimestamp,
|
|
1092
|
+
fee,
|
|
1093
|
+
accountId,
|
|
1094
|
+
deploymentId
|
|
1095
|
+
} = params;
|
|
1096
|
+
if (!userName) {
|
|
1097
|
+
return null;
|
|
1218
1098
|
}
|
|
1219
|
-
|
|
1220
|
-
|
|
1099
|
+
const existing = await getActiveRuntimeTrade({
|
|
1100
|
+
userName,
|
|
1101
|
+
symbol,
|
|
1102
|
+
accountId,
|
|
1103
|
+
deploymentId
|
|
1104
|
+
});
|
|
1105
|
+
if (!existing || existing.strategy !== strategy || existing.direction !== direction) {
|
|
1106
|
+
return null;
|
|
1221
1107
|
}
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
arrivalSnapshotTime: Date.now(),
|
|
1241
|
-
arrivalSource: "simulation",
|
|
1242
|
-
bid: executionPrice,
|
|
1243
|
-
ask: executionPrice,
|
|
1244
|
-
arrivalMid: executionPrice,
|
|
1245
|
-
spreadBps: 0
|
|
1246
|
-
} : await getOrderArrivalSnapshot({
|
|
1247
|
-
connector,
|
|
1248
|
-
symbol,
|
|
1249
|
-
requireTimestamp: true
|
|
1250
|
-
});
|
|
1251
|
-
const age = Date.now() - snapshot2.arrivalSnapshotTime;
|
|
1252
|
-
if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
|
|
1253
|
-
throw new EntrySubmissionValidationError(
|
|
1254
|
-
"ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
|
|
1255
|
-
);
|
|
1256
|
-
if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
|
|
1257
|
-
throw new EntrySubmissionValidationError(
|
|
1258
|
-
"ENTRY_QUOTE_STALE_AT_SUBMISSION"
|
|
1259
|
-
);
|
|
1260
|
-
const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
|
|
1261
|
-
validateEntryProtectionAtArrival({
|
|
1262
|
-
direction,
|
|
1263
|
-
signalPrice: price,
|
|
1264
|
-
bid: snapshot2.bid,
|
|
1265
|
-
ask: snapshot2.ask,
|
|
1266
|
-
arrivalMid: snapshot2.arrivalMid,
|
|
1267
|
-
takeProfits,
|
|
1268
|
-
stopLossPrice
|
|
1269
|
-
});
|
|
1270
|
-
const prices = takeProfits.map((tp) => tp.price);
|
|
1271
|
-
const economics2 = validateEntryExecution({
|
|
1272
|
-
direction,
|
|
1273
|
-
price,
|
|
1274
|
-
qty,
|
|
1275
|
-
stopLossPrice,
|
|
1276
|
-
takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
|
|
1277
|
-
limits
|
|
1278
|
-
});
|
|
1279
|
-
onValidated?.(snapshot2, economics2);
|
|
1108
|
+
const addedFee = typeof fee === "number" && Number.isFinite(fee) ? fee : 0;
|
|
1109
|
+
const openFee = (existing.openFee ?? existing.fee ?? 0) + addedFee;
|
|
1110
|
+
const totalFee = (existing.totalFee ?? existing.fee ?? 0) + addedFee;
|
|
1111
|
+
const next = {
|
|
1112
|
+
...existing,
|
|
1113
|
+
...params.orderLeverage ? { orderLeverage: params.orderLeverage } : {},
|
|
1114
|
+
qty: resultingQty,
|
|
1115
|
+
entryPrice: resultingEntryPrice,
|
|
1116
|
+
entryCount: Math.max(1, existing.entryCount ?? 1) + 1,
|
|
1117
|
+
lastEntryPrice: addedEntryPrice,
|
|
1118
|
+
lastEntryQty: addedQty,
|
|
1119
|
+
lastEntryTimestamp: entryTimestamp,
|
|
1120
|
+
currentPrice: resultingEntryPrice,
|
|
1121
|
+
currentPnl: 0,
|
|
1122
|
+
fee: openFee,
|
|
1123
|
+
openFee,
|
|
1124
|
+
totalFee,
|
|
1125
|
+
lastSyncedAt: now()
|
|
1280
1126
|
};
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
}
|
|
1300
|
-
return trimmed.map(
|
|
1301
|
-
(item) => item && typeof item === "object" ? trimSeriesDeep(item) : item
|
|
1302
|
-
);
|
|
1303
|
-
}
|
|
1304
|
-
if (value && typeof value === "object") {
|
|
1305
|
-
return Object.fromEntries(
|
|
1306
|
-
Object.entries(value).map(([key, nested]) => [
|
|
1307
|
-
key,
|
|
1308
|
-
trimSeriesDeep(nested)
|
|
1309
|
-
])
|
|
1127
|
+
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
1128
|
+
try {
|
|
1129
|
+
await Promise.all([
|
|
1130
|
+
(0, import_redis.setData)(import_redis.redisKeys.runtimeTrade(userName, existing.orderId), next, {
|
|
1131
|
+
expire: 0
|
|
1132
|
+
}),
|
|
1133
|
+
(0, import_redis.setHashJsonField)(
|
|
1134
|
+
import_redis.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
1135
|
+
existing.orderId,
|
|
1136
|
+
next,
|
|
1137
|
+
{ expire: 0 }
|
|
1138
|
+
)
|
|
1139
|
+
]);
|
|
1140
|
+
} catch (error) {
|
|
1141
|
+
import_logger.logger.error(
|
|
1142
|
+
"runtime trade increase journal failed: %s %s",
|
|
1143
|
+
symbol,
|
|
1144
|
+
error?.message || String(error)
|
|
1310
1145
|
);
|
|
1311
1146
|
}
|
|
1312
|
-
return
|
|
1147
|
+
return next;
|
|
1313
1148
|
};
|
|
1314
|
-
var
|
|
1315
|
-
const
|
|
1316
|
-
if (
|
|
1317
|
-
return
|
|
1149
|
+
var getActiveRuntimeTrade = async (params) => {
|
|
1150
|
+
const { userName, symbol, accountId, deploymentId } = params;
|
|
1151
|
+
if (!userName) {
|
|
1152
|
+
return null;
|
|
1318
1153
|
}
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
if (!
|
|
1154
|
+
const activeRef = await (0, import_redis.getData)(
|
|
1155
|
+
import_redis.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId),
|
|
1156
|
+
null
|
|
1157
|
+
);
|
|
1158
|
+
const orderId = String(activeRef?.orderId || "").trim();
|
|
1159
|
+
if (!orderId) {
|
|
1325
1160
|
return null;
|
|
1326
1161
|
}
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
};
|
|
1337
|
-
var buildMissingSpreadContext = () => ({
|
|
1338
|
-
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1339
|
-
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1340
|
-
available: false,
|
|
1341
|
-
value: null,
|
|
1342
|
-
zScore: null,
|
|
1343
|
-
bps: null,
|
|
1344
|
-
absBps: null,
|
|
1345
|
-
bias: null,
|
|
1346
|
-
severity: null
|
|
1347
|
-
});
|
|
1348
|
-
var buildSpreadContextFromSignal = (signal) => {
|
|
1349
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1350
|
-
const relative = toRecord(baseContext?.relative);
|
|
1351
|
-
const execution = toRecord(relative?.execution);
|
|
1352
|
-
const spread = toFiniteNumber(execution?.venueSpread);
|
|
1353
|
-
const zScore = toFiniteNumber(execution?.venueSpreadZScore);
|
|
1354
|
-
if (spread == null) {
|
|
1355
|
-
return buildMissingSpreadContext();
|
|
1162
|
+
const existing = await (0, import_redis.getData)(
|
|
1163
|
+
import_redis.redisKeys.runtimeTrade(userName, orderId),
|
|
1164
|
+
null
|
|
1165
|
+
);
|
|
1166
|
+
if (!existing || existing.status !== "active") {
|
|
1167
|
+
await (0, import_redis.delKey)(
|
|
1168
|
+
import_redis.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
|
|
1169
|
+
);
|
|
1170
|
+
return null;
|
|
1356
1171
|
}
|
|
1357
|
-
|
|
1358
|
-
const bps = roundTo(value * 1e4, 2);
|
|
1359
|
-
const absBps = Math.abs(bps);
|
|
1360
|
-
const bias = absBps < 1 ? "flat" : bps > 0 ? "coinbase_premium" : "binance_premium";
|
|
1361
|
-
const severity = absBps >= 20 ? "wide" : absBps >= 5 ? "elevated" : "normal";
|
|
1362
|
-
return {
|
|
1363
|
-
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1364
|
-
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1365
|
-
available: true,
|
|
1366
|
-
value,
|
|
1367
|
-
zScore,
|
|
1368
|
-
bps,
|
|
1369
|
-
absBps,
|
|
1370
|
-
bias,
|
|
1371
|
-
severity
|
|
1372
|
-
};
|
|
1172
|
+
return existing;
|
|
1373
1173
|
};
|
|
1374
|
-
var
|
|
1375
|
-
const
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
signedVolumeZScore: null
|
|
1390
|
-
};
|
|
1174
|
+
var markRuntimeTradeClosed = async (params) => {
|
|
1175
|
+
const {
|
|
1176
|
+
userName,
|
|
1177
|
+
expectedOrderId,
|
|
1178
|
+
symbol,
|
|
1179
|
+
strategy,
|
|
1180
|
+
exitPrice,
|
|
1181
|
+
exitTimestamp,
|
|
1182
|
+
closedPnl,
|
|
1183
|
+
exitType,
|
|
1184
|
+
accountId,
|
|
1185
|
+
deploymentId
|
|
1186
|
+
} = params;
|
|
1187
|
+
if (!userName) {
|
|
1188
|
+
return null;
|
|
1391
1189
|
}
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
};
|
|
1403
|
-
var buildTradeFlowContextFromSignal = (signal) => {
|
|
1404
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1405
|
-
const participation = toRecord(baseContext?.participation);
|
|
1406
|
-
const tradeFlow = toRecord(participation?.tradeFlow);
|
|
1407
|
-
if (!tradeFlow) {
|
|
1408
|
-
return {
|
|
1409
|
-
source: null,
|
|
1410
|
-
available: false,
|
|
1411
|
-
interval: null,
|
|
1412
|
-
stale: null,
|
|
1413
|
-
trades: null,
|
|
1414
|
-
buyPressurePct: null,
|
|
1415
|
-
netBaseDelta: null,
|
|
1416
|
-
netQuoteDelta: null
|
|
1417
|
-
};
|
|
1190
|
+
const existing = expectedOrderId ? await (0, import_redis.getDataStrict)(
|
|
1191
|
+
import_redis.redisKeys.runtimeTrade(userName, expectedOrderId)
|
|
1192
|
+
) : await getActiveRuntimeTrade({
|
|
1193
|
+
userName,
|
|
1194
|
+
symbol,
|
|
1195
|
+
accountId,
|
|
1196
|
+
deploymentId
|
|
1197
|
+
});
|
|
1198
|
+
if (!existing || expectedOrderId && existing.orderId !== expectedOrderId) {
|
|
1199
|
+
return null;
|
|
1418
1200
|
}
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1201
|
+
const orderId = existing.orderId;
|
|
1202
|
+
if (strategy && existing.strategy !== strategy) {
|
|
1203
|
+
return null;
|
|
1204
|
+
}
|
|
1205
|
+
const resolvedExitPrice = typeof exitPrice === "number" && Number.isFinite(exitPrice) ? exitPrice : existing.currentPrice ?? existing.entryPrice;
|
|
1206
|
+
const resolvedClosedPnl = typeof closedPnl === "number" && Number.isFinite(closedPnl) ? closedPnl : typeof resolvedExitPrice === "number" && Number.isFinite(resolvedExitPrice) ? calculateClosedPnl({
|
|
1207
|
+
direction: existing.direction,
|
|
1208
|
+
entryPrice: existing.entryPrice,
|
|
1209
|
+
exitPrice: resolvedExitPrice,
|
|
1210
|
+
qty: existing.qty
|
|
1211
|
+
}) : existing.closedPnl ?? existing.currentPnl ?? null;
|
|
1212
|
+
const next = expectedOrderId && existing.status === "closed" ? existing : {
|
|
1213
|
+
...existing,
|
|
1214
|
+
status: "closed",
|
|
1215
|
+
currentPrice: resolvedExitPrice,
|
|
1216
|
+
currentPnl: resolvedClosedPnl,
|
|
1217
|
+
closedPnl: resolvedClosedPnl,
|
|
1218
|
+
exitPrice: resolvedExitPrice,
|
|
1219
|
+
exitTimestamp: typeof exitTimestamp === "number" && Number.isFinite(exitTimestamp) ? exitTimestamp : now(),
|
|
1220
|
+
exitType: exitType ?? existing.exitType ?? null,
|
|
1221
|
+
lastSyncedAt: now()
|
|
1428
1222
|
};
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1223
|
+
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
1224
|
+
const closeDayKey = (0, import_time.getRuntimeStorageDayKey)(next.exitTimestamp);
|
|
1225
|
+
try {
|
|
1226
|
+
await Promise.all([
|
|
1227
|
+
(expectedOrderId ? import_redis.setDataStrict : import_redis.setData)(
|
|
1228
|
+
import_redis.redisKeys.runtimeTrade(userName, orderId),
|
|
1229
|
+
next,
|
|
1230
|
+
{
|
|
1231
|
+
expire: import_constants.TTL_1M
|
|
1232
|
+
}
|
|
1233
|
+
),
|
|
1234
|
+
(expectedOrderId ? import_redis.setHashJsonFieldStrict : import_redis.setHashJsonField)(
|
|
1235
|
+
import_redis.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
1236
|
+
orderId,
|
|
1237
|
+
next,
|
|
1238
|
+
{ expire: import_constants.TTL_1M }
|
|
1239
|
+
),
|
|
1240
|
+
(expectedOrderId ? import_redis.setHashJsonFieldStrict : import_redis.setHashJsonField)(
|
|
1241
|
+
import_redis.redisKeys.runtimeClosedTradeBucket(userName, closeDayKey),
|
|
1242
|
+
orderId,
|
|
1243
|
+
next,
|
|
1244
|
+
{ expire: import_constants.TTL_1M }
|
|
1245
|
+
),
|
|
1246
|
+
expectedOrderId ? (0, import_redis.deleteRuntimeTradeReference)(
|
|
1247
|
+
import_redis.redisKeys.runtimeActiveTrade(
|
|
1248
|
+
userName,
|
|
1249
|
+
symbol,
|
|
1250
|
+
deploymentId ?? accountId
|
|
1251
|
+
),
|
|
1252
|
+
expectedOrderId
|
|
1253
|
+
) : (0, import_redis.delKey)(
|
|
1254
|
+
import_redis.redisKeys.runtimeActiveTrade(
|
|
1255
|
+
userName,
|
|
1256
|
+
symbol,
|
|
1257
|
+
deploymentId ?? accountId
|
|
1258
|
+
)
|
|
1259
|
+
)
|
|
1260
|
+
]);
|
|
1261
|
+
} catch (error) {
|
|
1262
|
+
if (expectedOrderId) throw error;
|
|
1263
|
+
import_logger.logger.error(
|
|
1264
|
+
"runtime trade close journal failed: %s %s",
|
|
1265
|
+
symbol,
|
|
1266
|
+
error?.message || String(error)
|
|
1267
|
+
);
|
|
1449
1268
|
}
|
|
1450
|
-
return
|
|
1451
|
-
source: String(breadth.source ?? ""),
|
|
1452
|
-
available: true,
|
|
1453
|
-
universe: String(breadth.universe ?? ""),
|
|
1454
|
-
interval: String(breadth.interval ?? ""),
|
|
1455
|
-
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
1456
|
-
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
1457
|
-
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
1458
|
-
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
1459
|
-
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
1460
|
-
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
1461
|
-
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
1462
|
-
dispersion: toFiniteNumber(breadth.dispersion)
|
|
1463
|
-
};
|
|
1269
|
+
return next;
|
|
1464
1270
|
};
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1271
|
+
|
|
1272
|
+
// src/strategy/portfolioRuntime.ts
|
|
1273
|
+
var coordinators = /* @__PURE__ */ new WeakMap();
|
|
1274
|
+
async function observeRuntimePortfolio(connector, binding, asOf, reservations, observedNow = Date.now) {
|
|
1275
|
+
if (!connector.getOpenPositionPnl || !connector.getEntryExecutions)
|
|
1276
|
+
throw new Error(
|
|
1277
|
+
"Portfolio policies require position marks and entry executions"
|
|
1278
|
+
);
|
|
1279
|
+
const orders = await Promise.all(
|
|
1280
|
+
reservations.map(async (r) => {
|
|
1281
|
+
if (r.submitted && connector.getEntryOrderObservation) {
|
|
1282
|
+
const fact2 = await connector.getEntryOrderObservation({
|
|
1283
|
+
symbol: r.symbol,
|
|
1284
|
+
orderId: r.id
|
|
1285
|
+
});
|
|
1286
|
+
if (fact2) return fact2;
|
|
1287
|
+
}
|
|
1288
|
+
const executions = r.submitted ? await connector.getEntryExecutions({
|
|
1289
|
+
symbol: r.symbol,
|
|
1290
|
+
startTime: r.timestamp,
|
|
1291
|
+
endTime: asOf
|
|
1292
|
+
}) : [];
|
|
1293
|
+
const filledQty = executions.filter(
|
|
1294
|
+
(e) => (e.orderLinkId === r.id || e.orderId === r.id) && e.direction === r.direction
|
|
1295
|
+
).reduce((sum, e) => sum + e.qty, 0);
|
|
1472
1296
|
return {
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1297
|
+
id: r.id,
|
|
1298
|
+
status: filledQty >= r.qty ? "filled" : filledQty > 0 ? "partial" : "pending",
|
|
1299
|
+
filledQty,
|
|
1300
|
+
positionId: r.id
|
|
1301
|
+
};
|
|
1302
|
+
})
|
|
1303
|
+
);
|
|
1304
|
+
const snapshots2 = await connector.getOpenPositionPnl();
|
|
1305
|
+
const positions = await Promise.all(
|
|
1306
|
+
snapshots2.map(async (p) => {
|
|
1307
|
+
const reserved = reservations.find(
|
|
1308
|
+
(r) => r.symbol === p.symbol && r.direction === p.direction && orders.some(
|
|
1309
|
+
(o) => o.id === r.id && o.filledQty > 0 && p.qty <= o.filledQty * (1 + 1e-8)
|
|
1310
|
+
)
|
|
1311
|
+
);
|
|
1312
|
+
const trade = await getActiveRuntimeTrade({
|
|
1313
|
+
...binding,
|
|
1314
|
+
symbol: p.symbol
|
|
1315
|
+
});
|
|
1316
|
+
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;
|
|
1317
|
+
return {
|
|
1318
|
+
id: reserved?.id ?? owner?.orderId ?? `unknown:${p.symbol}:${p.direction}`,
|
|
1319
|
+
strategy: reserved?.strategy ?? owner?.strategy ?? null,
|
|
1320
|
+
symbol: p.symbol,
|
|
1321
|
+
direction: p.direction,
|
|
1322
|
+
qty: p.qty,
|
|
1323
|
+
entryPrice: p.price,
|
|
1324
|
+
markPrice: p.currentPrice,
|
|
1325
|
+
markedAt: p.observedAt ?? null,
|
|
1326
|
+
stopLossPrice: p.slPrice ?? p.stopLossPrice ?? null
|
|
1485
1327
|
};
|
|
1328
|
+
})
|
|
1329
|
+
);
|
|
1330
|
+
return { asOf: Math.max(asOf, observedNow()), positions, orders };
|
|
1331
|
+
}
|
|
1332
|
+
function persistentStore(key) {
|
|
1333
|
+
const raw = /* @__PURE__ */ new WeakMap();
|
|
1334
|
+
return {
|
|
1335
|
+
async read() {
|
|
1336
|
+
const value = await import_redis2.portfolioStateStorage.read(key);
|
|
1337
|
+
if (value === null) return null;
|
|
1338
|
+
const state = JSON.parse(value);
|
|
1339
|
+
(0, import_portfolio2.assertPortfolioState)(state);
|
|
1340
|
+
raw.set(state, value);
|
|
1341
|
+
return state;
|
|
1342
|
+
},
|
|
1343
|
+
async compare(previous, next) {
|
|
1344
|
+
const expected = previous === null ? null : raw.get(previous);
|
|
1345
|
+
if (expected === void 0)
|
|
1346
|
+
throw new Error("Portfolio CAS requires a storage snapshot");
|
|
1347
|
+
return import_redis2.portfolioStateStorage.compare(key, expected, JSON.stringify(next));
|
|
1486
1348
|
}
|
|
1487
|
-
return {
|
|
1488
|
-
source: String(breadth.source ?? ""),
|
|
1489
|
-
available: true,
|
|
1490
|
-
universe: String(breadth.universe ?? ""),
|
|
1491
|
-
interval: String(breadth.interval ?? ""),
|
|
1492
|
-
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
1493
|
-
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
1494
|
-
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
1495
|
-
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
1496
|
-
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
1497
|
-
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
1498
|
-
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
1499
|
-
dispersion: toFiniteNumber(breadth.dispersion)
|
|
1500
|
-
};
|
|
1501
1349
|
};
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1350
|
+
}
|
|
1351
|
+
function getPortfolioRuntime({
|
|
1352
|
+
value,
|
|
1353
|
+
connectorName,
|
|
1354
|
+
strategy,
|
|
1355
|
+
connector,
|
|
1356
|
+
env,
|
|
1357
|
+
userName,
|
|
1358
|
+
accountId,
|
|
1359
|
+
deploymentId
|
|
1360
|
+
}) {
|
|
1361
|
+
const policy = (0, import_portfolio2.parsePortfolioPolicy)(value);
|
|
1362
|
+
if (!policy) return void 0;
|
|
1363
|
+
const replay = (env === "BACKTEST" || env === "PARITY") && Reflect.get(connector, "__tradejsReplayConnector") === true;
|
|
1364
|
+
if ((env === "BACKTEST" || env === "PARITY") && (!replay || !connector.getPortfolioObservation))
|
|
1365
|
+
throw new Error(
|
|
1366
|
+
"Portfolio policies require shared portfolio replay; isolated symbol backtests are unsupported"
|
|
1367
|
+
);
|
|
1368
|
+
if (!replay && (!userName || !accountId || !deploymentId || !connector.getOpenPositionPnl || !connector.getEntryExecutions))
|
|
1369
|
+
throw new Error(
|
|
1370
|
+
"Portfolio runtime requires exact user/account/deployment binding, marks and executions"
|
|
1371
|
+
);
|
|
1372
|
+
const binding = {
|
|
1373
|
+
userName,
|
|
1374
|
+
accountId,
|
|
1375
|
+
deploymentId
|
|
1508
1376
|
};
|
|
1377
|
+
const scope = policy.scope === "strategy" ? strategy : policy.scope === "account" ? "account" : [...policy.scope.strategies].sort();
|
|
1378
|
+
const key = (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify([binding, connectorName, connector.universe, scope])).digest("hex");
|
|
1379
|
+
const fingerprint3 = (0, import_portfolio2.portfolioFingerprint)(policy, strategy);
|
|
1380
|
+
const cacheKey = key;
|
|
1381
|
+
let registry = coordinators.get(connector);
|
|
1382
|
+
if (!registry) {
|
|
1383
|
+
registry = /* @__PURE__ */ new Map();
|
|
1384
|
+
coordinators.set(connector, registry);
|
|
1385
|
+
}
|
|
1386
|
+
const existing = registry.get(cacheKey);
|
|
1387
|
+
if (existing) {
|
|
1388
|
+
if (existing.fingerprint !== fingerprint3)
|
|
1389
|
+
throw new Error("Conflicting portfolio configuration for the same scope");
|
|
1390
|
+
return existing.coordinator;
|
|
1391
|
+
}
|
|
1392
|
+
const coordinator = createPortfolioCoordinator({
|
|
1393
|
+
policy,
|
|
1394
|
+
strategy,
|
|
1395
|
+
store: replay ? createMemoryPortfolioStore() : persistentStore(key),
|
|
1396
|
+
execution: {
|
|
1397
|
+
async confirmClosed(intent, asOf) {
|
|
1398
|
+
if (replay) return;
|
|
1399
|
+
const position = intent.position;
|
|
1400
|
+
await markRuntimeTradeClosed({
|
|
1401
|
+
...binding,
|
|
1402
|
+
strategy: position.strategy,
|
|
1403
|
+
expectedOrderId: position.id,
|
|
1404
|
+
symbol: position.symbol,
|
|
1405
|
+
exitPrice: position.markPrice,
|
|
1406
|
+
exitTimestamp: asOf,
|
|
1407
|
+
exitType: "exit"
|
|
1408
|
+
});
|
|
1409
|
+
},
|
|
1410
|
+
observe: (asOf, reservations) => replay ? connector.getPortfolioObservation(asOf, reservations) : observeRuntimePortfolio(connector, binding, asOf, reservations),
|
|
1411
|
+
async close(position, intent, asOf) {
|
|
1412
|
+
if (!replay) {
|
|
1413
|
+
const trade = await getActiveRuntimeTrade({
|
|
1414
|
+
...binding,
|
|
1415
|
+
symbol: position.symbol
|
|
1416
|
+
});
|
|
1417
|
+
if (!trade || trade.orderId !== position.id)
|
|
1418
|
+
throw new Error("Portfolio close ownership changed");
|
|
1419
|
+
}
|
|
1420
|
+
await connector.closePosition({
|
|
1421
|
+
symbol: position.symbol,
|
|
1422
|
+
direction: position.direction,
|
|
1423
|
+
price: position.markPrice,
|
|
1424
|
+
timestamp: asOf,
|
|
1425
|
+
signal: {
|
|
1426
|
+
signalId: `${position.id}:${intent.reason}`,
|
|
1427
|
+
strategy: position.strategy,
|
|
1428
|
+
symbol: position.symbol,
|
|
1429
|
+
direction: position.direction,
|
|
1430
|
+
interval: "1",
|
|
1431
|
+
timestamp: asOf,
|
|
1432
|
+
prices: {
|
|
1433
|
+
riskRatio: 0,
|
|
1434
|
+
currentPrice: position.markPrice,
|
|
1435
|
+
takeProfitPrice: position.markPrice,
|
|
1436
|
+
stopLossPrice: position.markPrice
|
|
1437
|
+
},
|
|
1438
|
+
figures: {},
|
|
1439
|
+
indicators: {},
|
|
1440
|
+
additionalIndicators: { portfolioExitReason: intent.reason }
|
|
1441
|
+
}
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
});
|
|
1446
|
+
registry.set(cacheKey, { fingerprint: fingerprint3, coordinator });
|
|
1447
|
+
return coordinator;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
// src/strategyHelpers/tickerProfiles.ts
|
|
1451
|
+
var import_tickerProfiles = require("@tradejs/infra/tickerProfiles");
|
|
1452
|
+
var import_tickerProfiles2 = require("@tradejs/core/tickerProfiles");
|
|
1453
|
+
|
|
1454
|
+
// src/tradejsConfig.ts
|
|
1455
|
+
var import_fs = __toESM(require("fs"));
|
|
1456
|
+
var import_path = __toESM(require("path"));
|
|
1457
|
+
var import_url = require("url");
|
|
1458
|
+
var import_config = require("@tradejs/core/config");
|
|
1459
|
+
var import_logger2 = require("@tradejs/infra/logger");
|
|
1460
|
+
var CONFIG_FILE_NAMES = [
|
|
1461
|
+
"tradejs.config.ts",
|
|
1462
|
+
"tradejs.config.mts",
|
|
1463
|
+
"tradejs.config.js",
|
|
1464
|
+
"tradejs.config.mjs",
|
|
1465
|
+
"tradejs.config.cjs"
|
|
1466
|
+
];
|
|
1467
|
+
var TS_MODULE_RE = /\.(cts|mts|ts)$/i;
|
|
1468
|
+
var cachedByCwd = /* @__PURE__ */ new Map();
|
|
1469
|
+
var announcedConfigFile = /* @__PURE__ */ new Set();
|
|
1470
|
+
var tsNodeRegistered = false;
|
|
1471
|
+
var tsconfigPathsRegisteredByCwd = /* @__PURE__ */ new Set();
|
|
1472
|
+
var tsconfigPathMatchersByCwd = /* @__PURE__ */ new Map();
|
|
1473
|
+
var getTradejsProjectCwd = (cwd) => {
|
|
1474
|
+
const explicit = String(cwd ?? "").trim();
|
|
1475
|
+
if (explicit) {
|
|
1476
|
+
return import_path.default.resolve(explicit);
|
|
1477
|
+
}
|
|
1478
|
+
const fromEnv = String(process.env.PROJECT_CWD || "").trim();
|
|
1479
|
+
if (fromEnv) {
|
|
1480
|
+
return import_path.default.resolve(fromEnv);
|
|
1481
|
+
}
|
|
1482
|
+
return process.cwd();
|
|
1509
1483
|
};
|
|
1510
|
-
var
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
const targetVsBtc = toRecord(relative?.targetVsBtc);
|
|
1514
|
-
if (!targetVsBtc) {
|
|
1515
|
-
return {
|
|
1516
|
-
source: null,
|
|
1517
|
-
available: false,
|
|
1518
|
-
ratioReturn1h: null,
|
|
1519
|
-
ratioReturn4h: null,
|
|
1520
|
-
ratioReturn24h: null,
|
|
1521
|
-
alphaVsBtc1h: null,
|
|
1522
|
-
alphaVsBtc4h: null,
|
|
1523
|
-
alphaVsBtc24h: null,
|
|
1524
|
-
betaToBtc20: null,
|
|
1525
|
-
correlationToBtc20: null,
|
|
1526
|
-
ratioTrend: null
|
|
1527
|
-
};
|
|
1484
|
+
var normalizeConfig = (rawConfig) => {
|
|
1485
|
+
if (!rawConfig || typeof rawConfig !== "object") {
|
|
1486
|
+
return {};
|
|
1528
1487
|
}
|
|
1488
|
+
const config = rawConfig;
|
|
1489
|
+
const strategies2 = Array.isArray(config.strategies) ? config.strategies.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1490
|
+
const indicators = Array.isArray(config.indicators) ? config.indicators.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1491
|
+
const connectors = Array.isArray(config.connectors) ? config.connectors.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1492
|
+
const hooks = (0, import_config.normalizeTradejsConfigHooks)(
|
|
1493
|
+
config.hooks
|
|
1494
|
+
);
|
|
1495
|
+
const runtime = config.runtime && typeof config.runtime === "object" && !Array.isArray(config.runtime) ? config.runtime : void 0;
|
|
1529
1496
|
return {
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
alphaVsBtc1h: toFiniteNumber(targetVsBtc.alphaVsBtc1h),
|
|
1536
|
-
alphaVsBtc4h: toFiniteNumber(targetVsBtc.alphaVsBtc4h),
|
|
1537
|
-
alphaVsBtc24h: toFiniteNumber(targetVsBtc.alphaVsBtc24h),
|
|
1538
|
-
betaToBtc20: toFiniteNumber(targetVsBtc.betaToBtc20),
|
|
1539
|
-
correlationToBtc20: toFiniteNumber(targetVsBtc.correlationToBtc20),
|
|
1540
|
-
ratioTrend: typeof targetVsBtc.ratioTrend === "string" ? targetVsBtc.ratioTrend : null
|
|
1497
|
+
strategies: strategies2,
|
|
1498
|
+
indicators,
|
|
1499
|
+
connectors,
|
|
1500
|
+
...hooks ? { hooks } : {},
|
|
1501
|
+
...runtime ? { runtime } : {}
|
|
1541
1502
|
};
|
|
1542
1503
|
};
|
|
1543
|
-
var
|
|
1544
|
-
const
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
if (!btcAltRegime) {
|
|
1548
|
-
return {
|
|
1549
|
-
source: null,
|
|
1550
|
-
available: false,
|
|
1551
|
-
universe: null,
|
|
1552
|
-
interval: null,
|
|
1553
|
-
stale: null,
|
|
1554
|
-
regime: null,
|
|
1555
|
-
btcReturn24h: null,
|
|
1556
|
-
altBasketReturn24h: null,
|
|
1557
|
-
btcVsAltReturn24h: null,
|
|
1558
|
-
btcTurnoverShare24h: null,
|
|
1559
|
-
btcTurnoverShareChange24h: null,
|
|
1560
|
-
altVolToBtcVol24h: null,
|
|
1561
|
-
altDispersion24h: null
|
|
1562
|
-
};
|
|
1504
|
+
var getNodeCreateRequire = () => {
|
|
1505
|
+
const builtinModule = process.getBuiltinModule?.("module");
|
|
1506
|
+
if (typeof builtinModule?.createRequire === "function") {
|
|
1507
|
+
return builtinModule.createRequire;
|
|
1563
1508
|
}
|
|
1564
|
-
|
|
1565
|
-
source: String(btcAltRegime.source ?? ""),
|
|
1566
|
-
available: true,
|
|
1567
|
-
universe: String(btcAltRegime.universe ?? ""),
|
|
1568
|
-
interval: String(btcAltRegime.interval ?? ""),
|
|
1569
|
-
stale: typeof btcAltRegime.stale === "boolean" ? btcAltRegime.stale : null,
|
|
1570
|
-
regime: typeof btcAltRegime.regime === "string" ? btcAltRegime.regime : null,
|
|
1571
|
-
btcReturn24h: toFiniteNumber(btcAltRegime.btcReturn24h),
|
|
1572
|
-
altBasketReturn24h: toFiniteNumber(btcAltRegime.altBasketReturn24h),
|
|
1573
|
-
btcVsAltReturn24h: toFiniteNumber(btcAltRegime.btcVsAltReturn24h),
|
|
1574
|
-
btcTurnoverShare24h: toFiniteNumber(btcAltRegime.btcTurnoverShare24h),
|
|
1575
|
-
btcTurnoverShareChange24h: toFiniteNumber(
|
|
1576
|
-
btcAltRegime.btcTurnoverShareChange24h
|
|
1577
|
-
),
|
|
1578
|
-
altVolToBtcVol24h: toFiniteNumber(btcAltRegime.altVolToBtcVol24h),
|
|
1579
|
-
altDispersion24h: toFiniteNumber(btcAltRegime.altDispersion24h)
|
|
1580
|
-
};
|
|
1509
|
+
throw new TypeError("module.createRequire is not available");
|
|
1581
1510
|
};
|
|
1582
|
-
var
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1511
|
+
var getRequireFn = (cwd = getTradejsProjectCwd()) => getNodeCreateRequire()(import_path.default.join(import_path.default.resolve(cwd), "__tradejs_loader__.js"));
|
|
1512
|
+
var ensureTsNodeRegistered = async () => {
|
|
1513
|
+
if (tsNodeRegistered) {
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
1516
|
+
const tsNodeModule = await import("ts-node");
|
|
1517
|
+
const tsNode = tsNodeModule.default ?? tsNodeModule;
|
|
1518
|
+
tsNode.register?.({
|
|
1519
|
+
transpileOnly: true,
|
|
1520
|
+
compilerOptions: {
|
|
1521
|
+
module: "Node16",
|
|
1522
|
+
moduleResolution: "node16"
|
|
1523
|
+
}
|
|
1524
|
+
});
|
|
1525
|
+
tsNodeRegistered = true;
|
|
1526
|
+
};
|
|
1527
|
+
var ensureTsconfigPathsRegistered = async (cwd = getTradejsProjectCwd()) => {
|
|
1528
|
+
const projectRoot = getTradejsProjectCwd(cwd);
|
|
1529
|
+
if (tsconfigPathsRegisteredByCwd.has(projectRoot)) {
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
const tsconfigPathsModule = await import("tsconfig-paths");
|
|
1533
|
+
const loadConfig = tsconfigPathsModule.loadConfig;
|
|
1534
|
+
const register = tsconfigPathsModule.register;
|
|
1535
|
+
if (typeof loadConfig !== "function" || typeof register !== "function") {
|
|
1536
|
+
return;
|
|
1537
|
+
}
|
|
1538
|
+
const loadedConfig = loadConfig(projectRoot);
|
|
1539
|
+
if (loadedConfig.resultType !== "success") {
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
register({
|
|
1543
|
+
baseUrl: loadedConfig.absoluteBaseUrl,
|
|
1544
|
+
paths: loadedConfig.paths,
|
|
1545
|
+
addMatchAll: false
|
|
1546
|
+
});
|
|
1547
|
+
tsconfigPathsRegisteredByCwd.add(projectRoot);
|
|
1548
|
+
};
|
|
1549
|
+
var resolveTsconfigPathModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
1550
|
+
const projectRoot = getTradejsProjectCwd(cwd);
|
|
1551
|
+
const cachedMatcher = tsconfigPathMatchersByCwd.get(projectRoot);
|
|
1552
|
+
if (cachedMatcher) {
|
|
1553
|
+
const resolved2 = cachedMatcher(moduleName);
|
|
1554
|
+
return resolved2 || null;
|
|
1555
|
+
}
|
|
1556
|
+
const tsconfigPathsModule = await import("tsconfig-paths");
|
|
1557
|
+
const loadConfig = tsconfigPathsModule.loadConfig;
|
|
1558
|
+
const createMatchPath = tsconfigPathsModule.createMatchPath;
|
|
1559
|
+
if (typeof loadConfig !== "function" || typeof createMatchPath !== "function") {
|
|
1560
|
+
return null;
|
|
1561
|
+
}
|
|
1562
|
+
const loadedConfig = loadConfig(projectRoot);
|
|
1563
|
+
if (loadedConfig.resultType !== "success") {
|
|
1564
|
+
return null;
|
|
1565
|
+
}
|
|
1566
|
+
const matchPath = createMatchPath(
|
|
1567
|
+
loadedConfig.absoluteBaseUrl,
|
|
1568
|
+
loadedConfig.paths
|
|
1569
|
+
);
|
|
1570
|
+
const matcher = (requestedModule) => matchPath(requestedModule, void 0, import_fs.default.existsSync, [
|
|
1571
|
+
".ts",
|
|
1572
|
+
".tsx",
|
|
1573
|
+
".mts",
|
|
1574
|
+
".cts",
|
|
1575
|
+
".js",
|
|
1576
|
+
".jsx",
|
|
1577
|
+
".mjs",
|
|
1578
|
+
".cjs",
|
|
1579
|
+
".json"
|
|
1580
|
+
]) || "";
|
|
1581
|
+
tsconfigPathMatchersByCwd.set(projectRoot, matcher);
|
|
1582
|
+
const resolved = matcher(moduleName);
|
|
1583
|
+
return resolved || null;
|
|
1584
|
+
};
|
|
1585
|
+
var toImportSpecifier = (moduleName) => {
|
|
1586
|
+
if (moduleName.startsWith("file://")) {
|
|
1587
|
+
return moduleName;
|
|
1588
|
+
}
|
|
1589
|
+
if (import_path.default.isAbsolute(moduleName)) {
|
|
1590
|
+
return (0, import_url.pathToFileURL)(moduleName).href;
|
|
1591
|
+
}
|
|
1592
|
+
return moduleName;
|
|
1593
|
+
};
|
|
1594
|
+
var isTsModulePath = (moduleName) => TS_MODULE_RE.test(moduleName.split("?")[0]);
|
|
1595
|
+
var isRelativeModulePath = (moduleName) => moduleName.startsWith("./") || moduleName.startsWith("../");
|
|
1596
|
+
var isBareModuleSpecifier = (moduleName) => {
|
|
1597
|
+
const normalized = String(moduleName ?? "").trim();
|
|
1598
|
+
if (!normalized) {
|
|
1599
|
+
return false;
|
|
1600
|
+
}
|
|
1601
|
+
if (normalized.startsWith("file://") || import_path.default.isAbsolute(normalized) || isRelativeModulePath(normalized)) {
|
|
1602
|
+
return false;
|
|
1603
|
+
}
|
|
1604
|
+
return true;
|
|
1605
|
+
};
|
|
1606
|
+
var importConfigFile = async (configFilePath) => {
|
|
1607
|
+
const ext = import_path.default.extname(configFilePath).toLowerCase();
|
|
1608
|
+
const configFileUrl = `${toImportSpecifier(configFilePath)}?t=${Date.now()}`;
|
|
1609
|
+
if (ext === ".ts" || ext === ".mts") {
|
|
1610
|
+
const requireFn = getRequireFn(import_path.default.dirname(configFilePath));
|
|
1611
|
+
await ensureTsNodeRegistered();
|
|
1612
|
+
await ensureTsconfigPathsRegistered(import_path.default.dirname(configFilePath));
|
|
1613
|
+
return requireFn(configFilePath);
|
|
1614
|
+
}
|
|
1615
|
+
return import(
|
|
1616
|
+
/* webpackIgnore: true */
|
|
1617
|
+
configFileUrl
|
|
1618
|
+
);
|
|
1619
|
+
};
|
|
1620
|
+
var importTradejsModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
1621
|
+
const normalized = String(moduleName ?? "").trim();
|
|
1622
|
+
if (!normalized) {
|
|
1623
|
+
return {};
|
|
1624
|
+
}
|
|
1625
|
+
let modulePath = normalized;
|
|
1626
|
+
if (normalized.startsWith("file://")) {
|
|
1627
|
+
try {
|
|
1628
|
+
modulePath = (0, import_url.fileURLToPath)(normalized);
|
|
1629
|
+
} catch {
|
|
1630
|
+
modulePath = normalized;
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
const requireFn = getRequireFn(
|
|
1634
|
+
import_path.default.isAbsolute(modulePath) ? import_path.default.dirname(modulePath) : cwd
|
|
1635
|
+
);
|
|
1636
|
+
if (isTsModulePath(modulePath)) {
|
|
1637
|
+
await ensureTsNodeRegistered();
|
|
1638
|
+
await ensureTsconfigPathsRegistered(cwd);
|
|
1639
|
+
return requireFn(modulePath);
|
|
1640
|
+
}
|
|
1641
|
+
if (isBareModuleSpecifier(normalized)) {
|
|
1642
|
+
await ensureTsconfigPathsRegistered(cwd);
|
|
1643
|
+
try {
|
|
1644
|
+
return requireFn(normalized);
|
|
1645
|
+
} catch (error) {
|
|
1646
|
+
const resolvedByTsconfig = await resolveTsconfigPathModule(
|
|
1647
|
+
normalized,
|
|
1648
|
+
cwd
|
|
1649
|
+
);
|
|
1650
|
+
if (resolvedByTsconfig && resolvedByTsconfig !== normalized) {
|
|
1651
|
+
return requireFn(resolvedByTsconfig);
|
|
1652
|
+
}
|
|
1653
|
+
throw error;
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
try {
|
|
1657
|
+
return await import(
|
|
1658
|
+
/* webpackIgnore: true */
|
|
1659
|
+
toImportSpecifier(normalized)
|
|
1660
|
+
);
|
|
1661
|
+
} catch (error) {
|
|
1662
|
+
if (isTsModulePath(modulePath)) {
|
|
1663
|
+
await ensureTsNodeRegistered();
|
|
1664
|
+
await ensureTsconfigPathsRegistered(cwd);
|
|
1665
|
+
return requireFn(modulePath);
|
|
1666
|
+
}
|
|
1667
|
+
throw error;
|
|
1668
|
+
}
|
|
1669
|
+
};
|
|
1670
|
+
var resolveExportedConfig = (moduleExports) => {
|
|
1671
|
+
const candidate = moduleExports && typeof moduleExports === "object" && "default" in moduleExports ? moduleExports.default : moduleExports;
|
|
1672
|
+
return normalizeConfig(candidate);
|
|
1673
|
+
};
|
|
1674
|
+
var findConfigFilePath = (cwd) => {
|
|
1675
|
+
let currentDir = import_path.default.resolve(cwd);
|
|
1676
|
+
while (true) {
|
|
1677
|
+
for (const fileName of CONFIG_FILE_NAMES) {
|
|
1678
|
+
const fullPath = import_path.default.join(currentDir, fileName);
|
|
1679
|
+
if (import_fs.default.existsSync(fullPath) && import_fs.default.statSync(fullPath).isFile()) {
|
|
1680
|
+
return fullPath;
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
const parentDir = import_path.default.dirname(currentDir);
|
|
1684
|
+
if (parentDir === currentDir) {
|
|
1685
|
+
return null;
|
|
1686
|
+
}
|
|
1687
|
+
currentDir = parentDir;
|
|
1688
|
+
}
|
|
1689
|
+
};
|
|
1690
|
+
var resolvePluginModuleSpecifier = (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
1691
|
+
const normalized = String(moduleName ?? "").trim();
|
|
1692
|
+
if (!normalized) {
|
|
1693
|
+
return "";
|
|
1694
|
+
}
|
|
1695
|
+
if (normalized.startsWith("file://")) {
|
|
1696
|
+
try {
|
|
1697
|
+
return (0, import_url.fileURLToPath)(normalized);
|
|
1698
|
+
} catch {
|
|
1699
|
+
return normalized;
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
if (import_path.default.isAbsolute(normalized)) {
|
|
1703
|
+
return normalized;
|
|
1704
|
+
}
|
|
1705
|
+
if (isRelativeModulePath(normalized)) {
|
|
1706
|
+
return import_path.default.resolve(cwd, normalized);
|
|
1707
|
+
}
|
|
1708
|
+
return normalized;
|
|
1709
|
+
};
|
|
1710
|
+
var loadTradejsConfig = async (cwd = getTradejsProjectCwd()) => {
|
|
1711
|
+
const cached = cachedByCwd.get(cwd);
|
|
1712
|
+
if (cached) {
|
|
1713
|
+
return cached;
|
|
1714
|
+
}
|
|
1715
|
+
const configFilePath = findConfigFilePath(cwd);
|
|
1716
|
+
if (!configFilePath) {
|
|
1717
|
+
cachedByCwd.set(cwd, {});
|
|
1718
|
+
return {};
|
|
1719
|
+
}
|
|
1720
|
+
try {
|
|
1721
|
+
const moduleExports = await importConfigFile(configFilePath);
|
|
1722
|
+
const config = resolveExportedConfig(moduleExports);
|
|
1723
|
+
cachedByCwd.set(cwd, config);
|
|
1724
|
+
if (!announcedConfigFile.has(configFilePath)) {
|
|
1725
|
+
announcedConfigFile.add(configFilePath);
|
|
1726
|
+
import_logger2.logger.log("debug", "Loaded TradeJS config: %s", configFilePath);
|
|
1727
|
+
}
|
|
1728
|
+
return config;
|
|
1729
|
+
} catch (error) {
|
|
1730
|
+
import_logger2.logger.log(
|
|
1731
|
+
"warn",
|
|
1732
|
+
"Failed to load TradeJS config from %s: %s",
|
|
1733
|
+
configFilePath,
|
|
1734
|
+
String(error)
|
|
1735
|
+
);
|
|
1736
|
+
cachedByCwd.set(cwd, {});
|
|
1737
|
+
return {};
|
|
1738
|
+
}
|
|
1739
|
+
};
|
|
1740
|
+
|
|
1741
|
+
// src/strategy/manifests.ts
|
|
1742
|
+
var import_indicators = require("@tradejs/core/indicators");
|
|
1743
|
+
var SHARED_STRATEGY_REGISTRY_KEY = "__tradejsNodeSharedStrategyRegistryV1__";
|
|
1744
|
+
var sharedRegistryScope = globalThis;
|
|
1745
|
+
var sharedStrategyRegistry = sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] ?? (sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] = {
|
|
1746
|
+
registryStateByProjectRoot: /* @__PURE__ */ new Map()
|
|
1747
|
+
});
|
|
1748
|
+
var createStrategyRegistryState = () => ({
|
|
1749
|
+
strategyCreators: /* @__PURE__ */ new Map(),
|
|
1750
|
+
strategyManifestsMap: /* @__PURE__ */ new Map(),
|
|
1751
|
+
strategyEntriesMap: /* @__PURE__ */ new Map(),
|
|
1752
|
+
strategySourcesMap: /* @__PURE__ */ new Map(),
|
|
1753
|
+
strategyModuleResolutions: /* @__PURE__ */ new Map(),
|
|
1754
|
+
pluginsLoadPromise: null
|
|
1755
|
+
});
|
|
1756
|
+
var registryStateByProjectRoot = sharedStrategyRegistry.registryStateByProjectRoot;
|
|
1757
|
+
var getStrategyRegistryState = (cwd = getTradejsProjectCwd()) => {
|
|
1758
|
+
const projectRoot = getTradejsProjectCwd(cwd);
|
|
1759
|
+
let state = registryStateByProjectRoot.get(projectRoot);
|
|
1760
|
+
if (!state) {
|
|
1761
|
+
state = createStrategyRegistryState();
|
|
1762
|
+
registryStateByProjectRoot.set(projectRoot, state);
|
|
1763
|
+
}
|
|
1764
|
+
return {
|
|
1765
|
+
projectRoot,
|
|
1766
|
+
state
|
|
1767
|
+
};
|
|
1768
|
+
};
|
|
1769
|
+
var toUniqueModules = (modules = []) => [
|
|
1770
|
+
...new Set(modules.map((moduleName) => moduleName.trim()).filter(Boolean))
|
|
1771
|
+
];
|
|
1772
|
+
var getConfiguredPluginModuleNames = async (cwd = getTradejsProjectCwd()) => {
|
|
1773
|
+
const config = await loadTradejsConfig(cwd);
|
|
1774
|
+
return {
|
|
1775
|
+
strategyModules: toUniqueModules(config.strategies),
|
|
1776
|
+
indicatorModules: toUniqueModules(config.indicators)
|
|
1777
|
+
};
|
|
1778
|
+
};
|
|
1779
|
+
var extractModuleEntries = (moduleExport, key) => {
|
|
1780
|
+
if (!moduleExport || typeof moduleExport !== "object") {
|
|
1781
|
+
return null;
|
|
1782
|
+
}
|
|
1783
|
+
const candidate = moduleExport;
|
|
1784
|
+
if (Array.isArray(candidate[key])) {
|
|
1785
|
+
return candidate[key];
|
|
1786
|
+
}
|
|
1787
|
+
const defaultExport = candidate.default;
|
|
1788
|
+
if (defaultExport && Array.isArray(defaultExport[key])) {
|
|
1789
|
+
return defaultExport[key];
|
|
1790
|
+
}
|
|
1791
|
+
return null;
|
|
1792
|
+
};
|
|
1793
|
+
var extractStrategyPluginDefinition = (moduleExport) => {
|
|
1794
|
+
const strategyEntries = extractModuleEntries(
|
|
1795
|
+
moduleExport,
|
|
1796
|
+
"strategyEntries"
|
|
1797
|
+
);
|
|
1798
|
+
return strategyEntries ? { strategyEntries } : null;
|
|
1799
|
+
};
|
|
1800
|
+
var extractIndicatorPluginDefinition = (moduleExport) => {
|
|
1801
|
+
const indicatorEntries = extractModuleEntries(
|
|
1802
|
+
moduleExport,
|
|
1803
|
+
"indicatorEntries"
|
|
1804
|
+
);
|
|
1805
|
+
return indicatorEntries ? { indicatorEntries } : null;
|
|
1806
|
+
};
|
|
1807
|
+
var validateStrategyEntries = (moduleName, entries, state) => {
|
|
1808
|
+
const issues = [];
|
|
1809
|
+
const names = /* @__PURE__ */ new Set();
|
|
1810
|
+
entries.forEach((entry, index) => {
|
|
1811
|
+
const entryPath = `${moduleName}.strategyEntries[${index}]`;
|
|
1812
|
+
const strategyName = entry?.manifest?.name;
|
|
1813
|
+
if (typeof strategyName !== "string" || !strategyName.trim()) {
|
|
1814
|
+
issues.push(`${entryPath}: manifest.name is required`);
|
|
1815
|
+
} else if (names.has(strategyName) || state?.strategyEntriesMap.has(strategyName)) {
|
|
1816
|
+
issues.push(`${entryPath}: duplicate strategy ${strategyName}`);
|
|
1817
|
+
} else {
|
|
1818
|
+
names.add(strategyName);
|
|
1819
|
+
}
|
|
1820
|
+
if (!entry || typeof entry !== "object" || !entry.defaults || typeof entry.defaults !== "object" || Array.isArray(entry.defaults)) {
|
|
1821
|
+
issues.push(`${entryPath}: defaults must be an object`);
|
|
1822
|
+
}
|
|
1823
|
+
if (typeof entry?.parseConfig !== "function") {
|
|
1824
|
+
issues.push(`${entryPath}: parseConfig is required`);
|
|
1825
|
+
}
|
|
1826
|
+
if (typeof entry?.createCore !== "function") {
|
|
1827
|
+
issues.push(`${entryPath}: createCore is required`);
|
|
1828
|
+
}
|
|
1829
|
+
});
|
|
1830
|
+
return issues;
|
|
1831
|
+
};
|
|
1832
|
+
var registerEntries = (entries, source, state) => {
|
|
1833
|
+
for (const entry of entries) {
|
|
1834
|
+
const strategyName = entry.manifest.name;
|
|
1835
|
+
state.strategyManifestsMap.set(strategyName, entry.manifest);
|
|
1836
|
+
state.strategyEntriesMap.set(strategyName, entry);
|
|
1837
|
+
state.strategySourcesMap.set(strategyName, source);
|
|
1838
|
+
materializeStrategyCreator(strategyName, state);
|
|
1839
|
+
}
|
|
1840
|
+
};
|
|
1841
|
+
var materializeStrategyCreator = (strategyName, state) => {
|
|
1842
|
+
if (state.strategyCreators.has(strategyName) || !sharedStrategyRegistry.strategyRuntimeFactory) {
|
|
1843
|
+
return;
|
|
1844
|
+
}
|
|
1845
|
+
const entry = state.strategyEntriesMap.get(strategyName);
|
|
1846
|
+
if (!entry) return;
|
|
1847
|
+
state.strategyCreators.set(
|
|
1848
|
+
strategyName,
|
|
1849
|
+
sharedStrategyRegistry.strategyRuntimeFactory({
|
|
1850
|
+
strategyName,
|
|
1851
|
+
defaults: entry.defaults,
|
|
1852
|
+
createCore: entry.createCore,
|
|
1853
|
+
manifest: entry.manifest,
|
|
1854
|
+
detectorKey: entry.detectorKey,
|
|
1855
|
+
detectorNoSignalSkipReason: entry.detectorNoSignalSkipReason,
|
|
1856
|
+
resolveRegisteredManifest: (name) => state.strategyManifestsMap.get(name)
|
|
1857
|
+
})
|
|
1858
|
+
);
|
|
1859
|
+
};
|
|
1860
|
+
var setStrategyRuntimeFactory = (factory) => {
|
|
1861
|
+
sharedStrategyRegistry.strategyRuntimeFactory = factory;
|
|
1862
|
+
for (const state of registryStateByProjectRoot.values()) {
|
|
1863
|
+
for (const strategyName of state.strategyEntriesMap.keys()) {
|
|
1864
|
+
materializeStrategyCreator(strategyName, state);
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
};
|
|
1868
|
+
var importStrategyPluginModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
1869
|
+
if (typeof importTradejsModule === "function") {
|
|
1870
|
+
return importTradejsModule(moduleName, cwd);
|
|
1871
|
+
}
|
|
1872
|
+
return import(
|
|
1873
|
+
/* webpackIgnore: true */
|
|
1874
|
+
moduleName
|
|
1875
|
+
);
|
|
1876
|
+
};
|
|
1877
|
+
var resolveStrategyModuleEntry = async ({
|
|
1878
|
+
strategyName,
|
|
1879
|
+
moduleName,
|
|
1880
|
+
cwd
|
|
1881
|
+
}) => {
|
|
1882
|
+
const { projectRoot, state } = getStrategyRegistryState(cwd);
|
|
1883
|
+
const source = moduleName.trim();
|
|
1884
|
+
const resolvedModuleName = resolvePluginModuleSpecifier(source, projectRoot);
|
|
1885
|
+
const cacheKey = `${resolvedModuleName}\0${strategyName}`;
|
|
1886
|
+
const cached = state.strategyModuleResolutions.get(cacheKey);
|
|
1887
|
+
if (cached) return cached;
|
|
1888
|
+
const resolution = (async () => {
|
|
1889
|
+
const moduleExport = await importStrategyPluginModule(
|
|
1890
|
+
resolvedModuleName,
|
|
1891
|
+
projectRoot
|
|
1892
|
+
);
|
|
1893
|
+
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
1894
|
+
if (!pluginDefinition) {
|
|
1895
|
+
throw new Error(`${source}: export { strategyEntries } is missing`);
|
|
1896
|
+
}
|
|
1897
|
+
const issues = validateStrategyEntries(
|
|
1898
|
+
source,
|
|
1899
|
+
pluginDefinition.strategyEntries
|
|
1900
|
+
);
|
|
1901
|
+
if (issues.length > 0) {
|
|
1902
|
+
throw new Error(
|
|
1903
|
+
["Invalid TradeJS strategy module:", ...issues].join("\n")
|
|
1904
|
+
);
|
|
1905
|
+
}
|
|
1906
|
+
const entry = pluginDefinition.strategyEntries.find(
|
|
1907
|
+
(candidate) => candidate.manifest.name === strategyName
|
|
1908
|
+
);
|
|
1909
|
+
if (!entry) {
|
|
1910
|
+
throw new Error(`${source}: strategy ${strategyName} is missing`);
|
|
1911
|
+
}
|
|
1912
|
+
const factory = sharedStrategyRegistry.strategyRuntimeFactory;
|
|
1913
|
+
if (!factory) {
|
|
1914
|
+
throw new Error("Strategy runtime factory is not configured");
|
|
1915
|
+
}
|
|
1916
|
+
const manifestsByName = new Map(
|
|
1917
|
+
pluginDefinition.strategyEntries.map(
|
|
1918
|
+
(candidate) => [candidate.manifest.name, candidate.manifest]
|
|
1919
|
+
)
|
|
1920
|
+
);
|
|
1921
|
+
return {
|
|
1922
|
+
entry,
|
|
1923
|
+
creator: factory({
|
|
1924
|
+
strategyName,
|
|
1925
|
+
defaults: entry.defaults,
|
|
1926
|
+
createCore: entry.createCore,
|
|
1927
|
+
manifest: entry.manifest,
|
|
1928
|
+
detectorKey: entry.detectorKey,
|
|
1929
|
+
detectorNoSignalSkipReason: entry.detectorNoSignalSkipReason,
|
|
1930
|
+
resolveRegisteredManifest: (name) => manifestsByName.get(name) ?? state.strategyManifestsMap.get(name)
|
|
1931
|
+
}),
|
|
1932
|
+
source
|
|
1609
1933
|
};
|
|
1934
|
+
})();
|
|
1935
|
+
state.strategyModuleResolutions.set(cacheKey, resolution);
|
|
1936
|
+
try {
|
|
1937
|
+
return await resolution;
|
|
1938
|
+
} catch (error) {
|
|
1939
|
+
state.strategyModuleResolutions.delete(cacheKey);
|
|
1940
|
+
throw error;
|
|
1941
|
+
}
|
|
1942
|
+
};
|
|
1943
|
+
var ensureStrategyPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
|
|
1944
|
+
const { projectRoot, state } = getStrategyRegistryState(cwd);
|
|
1945
|
+
if (!state.pluginsLoadPromise) {
|
|
1946
|
+
(0, import_indicators.resetIndicatorRegistryCache)(projectRoot);
|
|
1947
|
+
state.pluginsLoadPromise = (async () => {
|
|
1948
|
+
const { strategyModules, indicatorModules } = await getConfiguredPluginModuleNames(projectRoot);
|
|
1949
|
+
const strategySet = new Set(strategyModules);
|
|
1950
|
+
const indicatorSet = new Set(indicatorModules);
|
|
1951
|
+
const pluginModuleNames = [
|
|
1952
|
+
.../* @__PURE__ */ new Set([...strategyModules, ...indicatorModules])
|
|
1953
|
+
];
|
|
1954
|
+
if (!pluginModuleNames.length) {
|
|
1955
|
+
return;
|
|
1956
|
+
}
|
|
1957
|
+
const issues = [];
|
|
1958
|
+
for (const moduleName of pluginModuleNames) {
|
|
1959
|
+
try {
|
|
1960
|
+
const resolvedModuleName = resolvePluginModuleSpecifier(
|
|
1961
|
+
moduleName,
|
|
1962
|
+
projectRoot
|
|
1963
|
+
);
|
|
1964
|
+
const moduleExport = await importStrategyPluginModule(
|
|
1965
|
+
resolvedModuleName,
|
|
1966
|
+
projectRoot
|
|
1967
|
+
);
|
|
1968
|
+
if (strategySet.has(moduleName)) {
|
|
1969
|
+
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
1970
|
+
if (!pluginDefinition) {
|
|
1971
|
+
issues.push(
|
|
1972
|
+
`${moduleName}: export { strategyEntries } is missing`
|
|
1973
|
+
);
|
|
1974
|
+
} else {
|
|
1975
|
+
const entryIssues = validateStrategyEntries(
|
|
1976
|
+
moduleName,
|
|
1977
|
+
pluginDefinition.strategyEntries,
|
|
1978
|
+
state
|
|
1979
|
+
);
|
|
1980
|
+
issues.push(...entryIssues);
|
|
1981
|
+
if (entryIssues.length === 0) {
|
|
1982
|
+
registerEntries(
|
|
1983
|
+
pluginDefinition.strategyEntries,
|
|
1984
|
+
moduleName,
|
|
1985
|
+
state
|
|
1986
|
+
);
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
if (indicatorSet.has(moduleName)) {
|
|
1991
|
+
const indicatorPluginDefinition = extractIndicatorPluginDefinition(moduleExport);
|
|
1992
|
+
if (!indicatorPluginDefinition) {
|
|
1993
|
+
issues.push(
|
|
1994
|
+
`${moduleName}: export { indicatorEntries } is missing`
|
|
1995
|
+
);
|
|
1996
|
+
} else {
|
|
1997
|
+
(0, import_indicators.registerIndicatorEntries)(
|
|
1998
|
+
indicatorPluginDefinition.indicatorEntries,
|
|
1999
|
+
moduleName,
|
|
2000
|
+
projectRoot
|
|
2001
|
+
);
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
if (!strategySet.has(moduleName) && !indicatorSet.has(moduleName)) {
|
|
2005
|
+
issues.push(`${moduleName}: plugin is not declared in config`);
|
|
2006
|
+
}
|
|
2007
|
+
} catch (error) {
|
|
2008
|
+
issues.push(`${moduleName}: failed to import: ${String(error)}`);
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
if (issues.length > 0) {
|
|
2012
|
+
throw new Error(
|
|
2013
|
+
["Invalid TradeJS plugin catalog:", ...issues].join("\n")
|
|
2014
|
+
);
|
|
2015
|
+
}
|
|
2016
|
+
})();
|
|
1610
2017
|
}
|
|
1611
|
-
|
|
1612
|
-
source: String(cmcGlobal.source ?? ""),
|
|
1613
|
-
available: true,
|
|
1614
|
-
interval: typeof cmcGlobal.interval === "string" ? cmcGlobal.interval : null,
|
|
1615
|
-
asOfTs: toFiniteNumber(cmcGlobal.asOfTs),
|
|
1616
|
-
stale: typeof cmcGlobal.stale === "boolean" ? cmcGlobal.stale : null,
|
|
1617
|
-
totalMarketCapUsd: toFiniteNumber(cmcGlobal.totalMarketCapUsd),
|
|
1618
|
-
totalVolumeUsd: toFiniteNumber(cmcGlobal.totalVolumeUsd),
|
|
1619
|
-
totalVolumeReportedUsd: toFiniteNumber(cmcGlobal.totalVolumeReportedUsd),
|
|
1620
|
-
altMarketCapUsd: toFiniteNumber(cmcGlobal.altMarketCapUsd),
|
|
1621
|
-
altVolumeUsd: toFiniteNumber(cmcGlobal.altVolumeUsd),
|
|
1622
|
-
altVolumeReportedUsd: toFiniteNumber(cmcGlobal.altVolumeReportedUsd),
|
|
1623
|
-
btcDominancePct: toFiniteNumber(cmcGlobal.btcDominancePct),
|
|
1624
|
-
ethDominancePct: toFiniteNumber(cmcGlobal.ethDominancePct),
|
|
1625
|
-
btcDominanceChange24hPct: toFiniteNumber(
|
|
1626
|
-
cmcGlobal.btcDominanceChange24hPct
|
|
1627
|
-
),
|
|
1628
|
-
ethDominanceChange24hPct: toFiniteNumber(
|
|
1629
|
-
cmcGlobal.ethDominanceChange24hPct
|
|
1630
|
-
),
|
|
1631
|
-
altMarketCapChange24hPct: toFiniteNumber(
|
|
1632
|
-
cmcGlobal.altMarketCapChange24hPct
|
|
1633
|
-
),
|
|
1634
|
-
altVolumeChange24hPct: toFiniteNumber(cmcGlobal.altVolumeChange24hPct),
|
|
1635
|
-
activeCryptocurrencies: toFiniteNumber(cmcGlobal.activeCryptocurrencies),
|
|
1636
|
-
activeExchanges: toFiniteNumber(cmcGlobal.activeExchanges),
|
|
1637
|
-
activeMarketPairs: toFiniteNumber(cmcGlobal.activeMarketPairs),
|
|
1638
|
-
altLiquidityRegime: typeof cmcGlobal.altLiquidityRegime === "string" ? cmcGlobal.altLiquidityRegime : null
|
|
1639
|
-
};
|
|
2018
|
+
await state.pluginsLoadPromise;
|
|
1640
2019
|
};
|
|
1641
|
-
var
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
if (!cmcReferenceAssets) {
|
|
1646
|
-
return {
|
|
1647
|
-
source: null,
|
|
1648
|
-
available: false,
|
|
1649
|
-
interval: null,
|
|
1650
|
-
asOfTs: null,
|
|
1651
|
-
stale: null,
|
|
1652
|
-
btcMarketCapUsd: null,
|
|
1653
|
-
ethMarketCapUsd: null,
|
|
1654
|
-
btcVolumeUsd: null,
|
|
1655
|
-
ethVolumeUsd: null,
|
|
1656
|
-
btcVolumeToMarketCap: null,
|
|
1657
|
-
ethVolumeToMarketCap: null,
|
|
1658
|
-
ethBtcMarketCapRatio: null,
|
|
1659
|
-
ethBtcMarketCapRatioChange24hPct: null,
|
|
1660
|
-
ethVsBtcVolumeRatio: null,
|
|
1661
|
-
referenceLiquidityRegime: null
|
|
1662
|
-
};
|
|
2020
|
+
var getStrategyCreator = async (name, cwd = getTradejsProjectCwd(), moduleName) => {
|
|
2021
|
+
if (moduleName?.trim()) {
|
|
2022
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
2023
|
+
return (await resolveStrategyModuleEntry({ strategyName: name, moduleName, cwd })).creator;
|
|
1663
2024
|
}
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
interval: typeof cmcReferenceAssets.interval === "string" ? cmcReferenceAssets.interval : null,
|
|
1668
|
-
asOfTs: toFiniteNumber(cmcReferenceAssets.asOfTs),
|
|
1669
|
-
stale: typeof cmcReferenceAssets.stale === "boolean" ? cmcReferenceAssets.stale : null,
|
|
1670
|
-
btcMarketCapUsd: toFiniteNumber(cmcReferenceAssets.btcMarketCapUsd),
|
|
1671
|
-
ethMarketCapUsd: toFiniteNumber(cmcReferenceAssets.ethMarketCapUsd),
|
|
1672
|
-
btcVolumeUsd: toFiniteNumber(cmcReferenceAssets.btcVolumeUsd),
|
|
1673
|
-
ethVolumeUsd: toFiniteNumber(cmcReferenceAssets.ethVolumeUsd),
|
|
1674
|
-
btcVolumeToMarketCap: toFiniteNumber(
|
|
1675
|
-
cmcReferenceAssets.btcVolumeToMarketCap
|
|
1676
|
-
),
|
|
1677
|
-
ethVolumeToMarketCap: toFiniteNumber(
|
|
1678
|
-
cmcReferenceAssets.ethVolumeToMarketCap
|
|
1679
|
-
),
|
|
1680
|
-
ethBtcMarketCapRatio: toFiniteNumber(
|
|
1681
|
-
cmcReferenceAssets.ethBtcMarketCapRatio
|
|
1682
|
-
),
|
|
1683
|
-
ethBtcMarketCapRatioChange24hPct: toFiniteNumber(
|
|
1684
|
-
cmcReferenceAssets.ethBtcMarketCapRatioChange24hPct
|
|
1685
|
-
),
|
|
1686
|
-
ethVsBtcVolumeRatio: toFiniteNumber(cmcReferenceAssets.ethVsBtcVolumeRatio),
|
|
1687
|
-
referenceLiquidityRegime: typeof cmcReferenceAssets.referenceLiquidityRegime === "string" ? cmcReferenceAssets.referenceLiquidityRegime : null
|
|
1688
|
-
};
|
|
2025
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
2026
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
2027
|
+
return state.strategyCreators.get(name);
|
|
1689
2028
|
};
|
|
1690
|
-
var
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
if (!cmcExchangeLiquidity) {
|
|
1695
|
-
return {
|
|
1696
|
-
source: null,
|
|
1697
|
-
available: false,
|
|
1698
|
-
interval: null,
|
|
1699
|
-
asOfTs: null,
|
|
1700
|
-
stale: null,
|
|
1701
|
-
exchangesCount: null,
|
|
1702
|
-
totalVolumeUsd: null,
|
|
1703
|
-
totalVolumeChange24hPct: null,
|
|
1704
|
-
binanceVolumeUsd: null,
|
|
1705
|
-
binanceVolumeShare: null,
|
|
1706
|
-
topExchangeVolumeShare: null,
|
|
1707
|
-
liquidityRegime: null
|
|
1708
|
-
};
|
|
2029
|
+
var getStrategyEntry = async (name, cwd = getTradejsProjectCwd(), moduleName) => {
|
|
2030
|
+
if (moduleName?.trim()) {
|
|
2031
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
2032
|
+
return (await resolveStrategyModuleEntry({ strategyName: name, moduleName, cwd })).entry;
|
|
1709
2033
|
}
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
interval: typeof cmcExchangeLiquidity.interval === "string" ? cmcExchangeLiquidity.interval : null,
|
|
1714
|
-
asOfTs: toFiniteNumber(cmcExchangeLiquidity.asOfTs),
|
|
1715
|
-
stale: typeof cmcExchangeLiquidity.stale === "boolean" ? cmcExchangeLiquidity.stale : null,
|
|
1716
|
-
exchangesCount: toFiniteNumber(cmcExchangeLiquidity.exchangesCount),
|
|
1717
|
-
totalVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.totalVolumeUsd),
|
|
1718
|
-
totalVolumeChange24hPct: toFiniteNumber(
|
|
1719
|
-
cmcExchangeLiquidity.totalVolumeChange24hPct
|
|
1720
|
-
),
|
|
1721
|
-
binanceVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeUsd),
|
|
1722
|
-
binanceVolumeShare: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeShare),
|
|
1723
|
-
topExchangeVolumeShare: toFiniteNumber(
|
|
1724
|
-
cmcExchangeLiquidity.topExchangeVolumeShare
|
|
1725
|
-
),
|
|
1726
|
-
liquidityRegime: typeof cmcExchangeLiquidity.liquidityRegime === "string" ? cmcExchangeLiquidity.liquidityRegime : null
|
|
1727
|
-
};
|
|
2034
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
2035
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
2036
|
+
return state.strategyEntriesMap.get(name);
|
|
1728
2037
|
};
|
|
1729
|
-
var
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
if (!cmcFearGreed) {
|
|
1734
|
-
return {
|
|
1735
|
-
source: null,
|
|
1736
|
-
available: false,
|
|
1737
|
-
interval: null,
|
|
1738
|
-
asOfTs: null,
|
|
1739
|
-
stale: null,
|
|
1740
|
-
value: null,
|
|
1741
|
-
valueChange24h: null,
|
|
1742
|
-
valueChange7d: null,
|
|
1743
|
-
classification: null,
|
|
1744
|
-
sentimentRegime: null
|
|
1745
|
-
};
|
|
2038
|
+
var getStrategyPluginSource = async (name, cwd = getTradejsProjectCwd(), moduleName) => {
|
|
2039
|
+
if (moduleName?.trim()) {
|
|
2040
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
2041
|
+
return (await resolveStrategyModuleEntry({ strategyName: name, moduleName, cwd })).source;
|
|
1746
2042
|
}
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
interval: typeof cmcFearGreed.interval === "string" ? cmcFearGreed.interval : null,
|
|
1751
|
-
asOfTs: toFiniteNumber(cmcFearGreed.asOfTs),
|
|
1752
|
-
stale: typeof cmcFearGreed.stale === "boolean" ? cmcFearGreed.stale : null,
|
|
1753
|
-
value: toFiniteNumber(cmcFearGreed.value),
|
|
1754
|
-
valueChange24h: toFiniteNumber(cmcFearGreed.valueChange24h),
|
|
1755
|
-
valueChange7d: toFiniteNumber(cmcFearGreed.valueChange7d),
|
|
1756
|
-
classification: typeof cmcFearGreed.classification === "string" ? cmcFearGreed.classification : null,
|
|
1757
|
-
sentimentRegime: typeof cmcFearGreed.sentimentRegime === "string" ? cmcFearGreed.sentimentRegime : null
|
|
1758
|
-
};
|
|
2043
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
2044
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
2045
|
+
return state.strategySourcesMap.get(name);
|
|
1759
2046
|
};
|
|
1760
|
-
var
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
}
|
|
2047
|
+
var getStrategyManifest = (name, cwd = getTradejsProjectCwd()) => {
|
|
2048
|
+
if (!name) {
|
|
2049
|
+
return void 0;
|
|
2050
|
+
}
|
|
2051
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
2052
|
+
return state.strategyManifestsMap.get(name);
|
|
2053
|
+
};
|
|
2054
|
+
var strategies = new Proxy(
|
|
2055
|
+
{},
|
|
2056
|
+
{
|
|
2057
|
+
get: (_target, property) => {
|
|
2058
|
+
if (typeof property !== "string") {
|
|
2059
|
+
return void 0;
|
|
2060
|
+
}
|
|
2061
|
+
return getStrategyRegistryState().state.strategyCreators.get(property);
|
|
2062
|
+
},
|
|
2063
|
+
ownKeys: () => {
|
|
2064
|
+
return [...getStrategyRegistryState().state.strategyCreators.keys()];
|
|
2065
|
+
},
|
|
2066
|
+
getOwnPropertyDescriptor: () => ({
|
|
2067
|
+
enumerable: true,
|
|
2068
|
+
configurable: true
|
|
2069
|
+
})
|
|
2070
|
+
}
|
|
2071
|
+
);
|
|
2072
|
+
|
|
2073
|
+
// src/strategyHelpers/tickerProfiles.ts
|
|
2074
|
+
var snapshots = /* @__PURE__ */ new Map();
|
|
2075
|
+
var resolveTickerProfileSource = (strategy) => {
|
|
2076
|
+
const declared = getStrategyManifest(strategy)?.contextRequirements?.tickerProfiles;
|
|
2077
|
+
if (declared) return declared;
|
|
2078
|
+
const provider = process.env.TICKER_PROFILES_PROVIDER?.trim();
|
|
2079
|
+
const universeId = process.env.TICKER_PROFILES_UNIVERSE?.trim();
|
|
2080
|
+
if (!provider && !universeId) return void 0;
|
|
2081
|
+
if (!provider || !universeId)
|
|
2082
|
+
throw new Error(
|
|
2083
|
+
"Set both TICKER_PROFILES_PROVIDER and TICKER_PROFILES_UNIVERSE"
|
|
2084
|
+
);
|
|
2085
|
+
return { provider, universeId };
|
|
2086
|
+
};
|
|
2087
|
+
var loadTickerProfileContext = async ({
|
|
2088
|
+
projectRoot,
|
|
2089
|
+
provider,
|
|
2090
|
+
universeId,
|
|
2091
|
+
symbol,
|
|
2092
|
+
timestamp
|
|
2093
|
+
}) => {
|
|
2094
|
+
const asOf = (0, import_tickerProfiles2.tickerProfileMonth)(timestamp), key = JSON.stringify([projectRoot, provider, universeId, asOf]);
|
|
2095
|
+
let cached = snapshots.get(key);
|
|
2096
|
+
if (!cached || cached.expires < Date.now()) {
|
|
2097
|
+
const value = (0, import_tickerProfiles.readTickerProfiles)(projectRoot, provider, universeId, asOf).then((envelope) => {
|
|
2098
|
+
if (!envelope) return null;
|
|
2099
|
+
const s = envelope.snapshot;
|
|
2100
|
+
if (!(0, import_tickerProfiles2.isTickerProfileSnapshot)(s) || s.asOf !== asOf || s.provider !== provider || s.universeId !== universeId)
|
|
2101
|
+
throw new Error("Ticker profile identity/schema mismatch");
|
|
2102
|
+
const entry = snapshots.get(key);
|
|
2103
|
+
if (entry) entry.expires = Infinity;
|
|
2104
|
+
return new Map(
|
|
2105
|
+
s.profiles.map((p) => [
|
|
2106
|
+
p.symbol,
|
|
2107
|
+
Object.freeze({
|
|
2108
|
+
...p,
|
|
2109
|
+
definition: s.definition,
|
|
2110
|
+
provider,
|
|
2111
|
+
universeId,
|
|
2112
|
+
snapshotId: envelope.snapshotId
|
|
2113
|
+
})
|
|
2114
|
+
])
|
|
2115
|
+
);
|
|
2116
|
+
}).catch((error) => {
|
|
2117
|
+
snapshots.delete(key);
|
|
2118
|
+
throw error;
|
|
2119
|
+
});
|
|
2120
|
+
cached = { expires: Date.now() + 6e4, value };
|
|
2121
|
+
snapshots.set(key, cached);
|
|
2122
|
+
if (snapshots.size > 24) {
|
|
2123
|
+
const oldest = snapshots.keys().next().value;
|
|
2124
|
+
if (oldest) snapshots.delete(oldest);
|
|
2125
|
+
}
|
|
1783
2126
|
}
|
|
1784
|
-
return
|
|
1785
|
-
source: String(cmcIndexes.source ?? ""),
|
|
1786
|
-
available: true,
|
|
1787
|
-
interval: typeof cmcIndexes.interval === "string" ? cmcIndexes.interval : null,
|
|
1788
|
-
asOfTs: toFiniteNumber(cmcIndexes.asOfTs),
|
|
1789
|
-
stale: typeof cmcIndexes.stale === "boolean" ? cmcIndexes.stale : null,
|
|
1790
|
-
cmc100Value: toFiniteNumber(cmcIndexes.cmc100Value),
|
|
1791
|
-
cmc100Change24hPct: toFiniteNumber(cmcIndexes.cmc100Change24hPct),
|
|
1792
|
-
cmc100TopConstituentSymbol: typeof cmcIndexes.cmc100TopConstituentSymbol === "string" ? cmcIndexes.cmc100TopConstituentSymbol : null,
|
|
1793
|
-
cmc100TopConstituentWeightPct: toFiniteNumber(
|
|
1794
|
-
cmcIndexes.cmc100TopConstituentWeightPct
|
|
1795
|
-
),
|
|
1796
|
-
cmc20Value: toFiniteNumber(cmcIndexes.cmc20Value),
|
|
1797
|
-
cmc20Change24hPct: toFiniteNumber(cmcIndexes.cmc20Change24hPct),
|
|
1798
|
-
cmc20TopConstituentSymbol: typeof cmcIndexes.cmc20TopConstituentSymbol === "string" ? cmcIndexes.cmc20TopConstituentSymbol : null,
|
|
1799
|
-
cmc20TopConstituentWeightPct: toFiniteNumber(
|
|
1800
|
-
cmcIndexes.cmc20TopConstituentWeightPct
|
|
1801
|
-
),
|
|
1802
|
-
cmc20ToCmc100Ratio: toFiniteNumber(cmcIndexes.cmc20ToCmc100Ratio),
|
|
1803
|
-
cmc20ToCmc100RatioChange24hPct: toFiniteNumber(
|
|
1804
|
-
cmcIndexes.cmc20ToCmc100RatioChange24hPct
|
|
1805
|
-
),
|
|
1806
|
-
indexRegime: typeof cmcIndexes.indexRegime === "string" ? cmcIndexes.indexRegime : null
|
|
1807
|
-
};
|
|
2127
|
+
return (await cached.value)?.get(symbol);
|
|
1808
2128
|
};
|
|
1809
|
-
var
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
2129
|
+
var withTickerProfile = (baseContext, profile) => Object.defineProperty(
|
|
2130
|
+
Object.create(
|
|
2131
|
+
Object.getPrototypeOf(baseContext),
|
|
2132
|
+
Object.getOwnPropertyDescriptors(baseContext)
|
|
2133
|
+
),
|
|
2134
|
+
"tickerProfile",
|
|
2135
|
+
{ enumerable: true, configurable: true, value: profile }
|
|
2136
|
+
);
|
|
2137
|
+
var enrichSignalWithTickerProfile = async (signal) => {
|
|
2138
|
+
const source = resolveTickerProfileSource(signal.strategy);
|
|
2139
|
+
if (!source || signal.universe === "tradfi" || !signal.symbol.endsWith("USDT"))
|
|
2140
|
+
return;
|
|
2141
|
+
const profile = await loadTickerProfileContext({
|
|
2142
|
+
...source,
|
|
2143
|
+
projectRoot: getTradejsProjectCwd(),
|
|
2144
|
+
symbol: signal.symbol,
|
|
2145
|
+
timestamp: signal.timestamp
|
|
2146
|
+
});
|
|
2147
|
+
if (profile) {
|
|
2148
|
+
signal.additionalIndicators = {
|
|
2149
|
+
...signal.additionalIndicators,
|
|
2150
|
+
tickerProfile: profile
|
|
1824
2151
|
};
|
|
1825
2152
|
}
|
|
1826
|
-
return {
|
|
1827
|
-
source: String(refs.source ?? ""),
|
|
1828
|
-
available: true,
|
|
1829
|
-
primaryReferenceSymbol,
|
|
1830
|
-
referenceSymbols: Array.isArray(refs.referenceSymbols) ? refs.referenceSymbols.map(String) : [],
|
|
1831
|
-
primaryTradeFlowBuyPressurePct: toFiniteNumber(
|
|
1832
|
-
primaryTradeFlow?.buyPressurePct
|
|
1833
|
-
),
|
|
1834
|
-
primaryTradeFlowStale: typeof primaryTradeFlow?.stale === "boolean" ? primaryTradeFlow.stale : null
|
|
1835
|
-
};
|
|
1836
2153
|
};
|
|
1837
|
-
var buildAiMarketContext = (signal) => ({
|
|
1838
|
-
execution: {
|
|
1839
|
-
binanceCoinbaseSpread: buildSpreadContextFromSignal(signal)
|
|
1840
|
-
},
|
|
1841
|
-
participation: {
|
|
1842
|
-
trueDelta: buildTrueDeltaContextFromSignal(signal),
|
|
1843
|
-
tradeFlow: buildTradeFlowContextFromSignal(signal)
|
|
1844
|
-
},
|
|
1845
|
-
relative: {
|
|
1846
|
-
marketBreadth: buildMarketBreadthContextFromSignal(signal),
|
|
1847
|
-
marketBreadths: buildMarketBreadthsContextFromSignal(signal),
|
|
1848
|
-
targetVsBtc: buildTargetVsBtcContextFromSignal(signal),
|
|
1849
|
-
btcAltRegime: buildBtcAltRegimeContextFromSignal(signal),
|
|
1850
|
-
cmcGlobal: buildCmcGlobalContextFromSignal(signal),
|
|
1851
|
-
cmcReferenceAssets: buildCmcReferenceAssetsContextFromSignal(signal),
|
|
1852
|
-
cmcExchangeLiquidity: buildCmcExchangeLiquidityContextFromSignal(signal),
|
|
1853
|
-
cmcFearGreed: buildCmcFearGreedContextFromSignal(signal),
|
|
1854
|
-
cmcIndexes: buildCmcIndexesContextFromSignal(signal),
|
|
1855
|
-
referenceTradeFlow: buildReferenceTradeFlowContextFromSignal(signal)
|
|
1856
|
-
}
|
|
1857
|
-
});
|
|
1858
2154
|
|
|
1859
|
-
// src/
|
|
1860
|
-
var
|
|
2155
|
+
// src/strategyRuntime.ts
|
|
2156
|
+
var import_jev6 = require("@tradejs/core/jev");
|
|
1861
2157
|
|
|
1862
|
-
// src/
|
|
1863
|
-
var
|
|
1864
|
-
var
|
|
1865
|
-
var
|
|
1866
|
-
var
|
|
1867
|
-
var
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
]
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
var getTradejsProjectCwd = (cwd) => {
|
|
1882
|
-
const explicit = String(cwd ?? "").trim();
|
|
1883
|
-
if (explicit) {
|
|
1884
|
-
return import_path.default.resolve(explicit);
|
|
1885
|
-
}
|
|
1886
|
-
const fromEnv = String(process.env.PROJECT_CWD || "").trim();
|
|
1887
|
-
if (fromEnv) {
|
|
1888
|
-
return import_path.default.resolve(fromEnv);
|
|
1889
|
-
}
|
|
1890
|
-
return process.cwd();
|
|
2158
|
+
// src/jev.ts
|
|
2159
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
2160
|
+
var import_node_path = __toESM(require("path"));
|
|
2161
|
+
var import_jev4 = require("@tradejs/core/jev");
|
|
2162
|
+
var import_jev5 = require("@tradejs/infra/jev");
|
|
2163
|
+
var import_userSettings = require("@tradejs/infra/userSettings");
|
|
2164
|
+
init_jevInput();
|
|
2165
|
+
init_jevInput();
|
|
2166
|
+
init_jevTraining();
|
|
2167
|
+
var resolveJevProvider = async (userName) => {
|
|
2168
|
+
const settings = await (0, import_userSettings.getUserSettings)(userName);
|
|
2169
|
+
const provider = {
|
|
2170
|
+
endpoint: settings.JEV_API_ENDPOINT || import_jev4.JEV_PROVIDERS[0].endpoint,
|
|
2171
|
+
model: settings.JEV_MODEL || import_jev4.JEV_PROVIDERS[0].model
|
|
2172
|
+
};
|
|
2173
|
+
(0, import_jev4.parseJevConfig)({ source: "provider", mode: "observe", provider });
|
|
2174
|
+
if (!settings.JEV_API_KEY)
|
|
2175
|
+
throw new Error("Configure the Jev API key in Account settings");
|
|
2176
|
+
return provider;
|
|
1891
2177
|
};
|
|
1892
|
-
var
|
|
1893
|
-
|
|
1894
|
-
|
|
2178
|
+
var emptyScores = () => Object.fromEntries(import_jev4.JEV_DIMENSIONS.map((key) => [key, null]));
|
|
2179
|
+
var pending = /* @__PURE__ */ new Map();
|
|
2180
|
+
var pendingResponses = /* @__PURE__ */ new Map();
|
|
2181
|
+
var evaluateJevInput = async ({
|
|
2182
|
+
input,
|
|
2183
|
+
config,
|
|
2184
|
+
userName,
|
|
2185
|
+
projectRoot
|
|
2186
|
+
}) => {
|
|
2187
|
+
config = (0, import_jev4.parseJevConfig)(config);
|
|
2188
|
+
const inputHash = (0, import_jev5.jevHash)(input);
|
|
2189
|
+
const questions = questionsForJevInput(input);
|
|
2190
|
+
const questionsHash = (0, import_jev5.jevHash)(questions);
|
|
2191
|
+
const base = {
|
|
2192
|
+
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
2193
|
+
schema: "tradejs-signal-assessment/v5",
|
|
2194
|
+
source: config.source,
|
|
2195
|
+
mode: config.mode,
|
|
2196
|
+
inputHash,
|
|
2197
|
+
questionsHash,
|
|
2198
|
+
geometryStatus: input.geometryStatus,
|
|
2199
|
+
levelsValid: typeof input.features["signal.validLevels"] === "boolean" ? input.features["signal.validLevels"] : null
|
|
2200
|
+
};
|
|
2201
|
+
if (!input.questions.length)
|
|
2202
|
+
return {
|
|
2203
|
+
assessment: {
|
|
2204
|
+
...base,
|
|
2205
|
+
status: "unavailable",
|
|
2206
|
+
model: "",
|
|
2207
|
+
scores: emptyScores(),
|
|
2208
|
+
confidence: emptyScores()
|
|
2209
|
+
}
|
|
2210
|
+
};
|
|
2211
|
+
if (config.source === "local") {
|
|
2212
|
+
const contents = await import_promises.default.readFile(
|
|
2213
|
+
import_node_path.default.resolve(projectRoot, config.modelFile),
|
|
2214
|
+
"utf8"
|
|
2215
|
+
);
|
|
2216
|
+
if ((0, import_jev5.jevFileHash)(contents) !== config.modelSha256)
|
|
2217
|
+
throw new Error("Local Jev gate checksum mismatch");
|
|
2218
|
+
const { validateJevGateModel: validateJevGateModel2 } = await Promise.resolve().then(() => (init_jevTraining(), jevTraining_exports));
|
|
2219
|
+
const model = validateJevGateModel2(JSON.parse(contents));
|
|
2220
|
+
if (model.strategy !== input.strategy || model.questionsHash !== (0, import_jev5.jevHash)(JEV_QUESTIONS))
|
|
2221
|
+
throw new Error("Local Jev gate strategy or question version mismatch");
|
|
2222
|
+
const scores = Object.fromEntries(
|
|
2223
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
2224
|
+
key,
|
|
2225
|
+
input.questions.includes(key) && model.trees[key] ? (0, import_jev4.predictJevTree)(model.trees[key], input.features) : null
|
|
2226
|
+
])
|
|
2227
|
+
);
|
|
2228
|
+
return {
|
|
2229
|
+
assessment: {
|
|
2230
|
+
...base,
|
|
2231
|
+
status: "available",
|
|
2232
|
+
model: config.modelSha256,
|
|
2233
|
+
scores,
|
|
2234
|
+
confidence: emptyScores()
|
|
2235
|
+
}
|
|
2236
|
+
};
|
|
1895
2237
|
}
|
|
1896
|
-
const
|
|
1897
|
-
const
|
|
1898
|
-
const
|
|
1899
|
-
const
|
|
1900
|
-
const
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
2238
|
+
const provider = config.provider ?? await resolveJevProvider(userName);
|
|
2239
|
+
const id = (0, import_jev5.jevHash)({ inputHash, questionsHash, provider });
|
|
2240
|
+
const dir = import_node_path.default.resolve(projectRoot, config.recordsDir ?? "data/ai/jev");
|
|
2241
|
+
const file = import_node_path.default.join(dir, "records", `${id}.json`);
|
|
2242
|
+
const getRecord = async () => {
|
|
2243
|
+
const existing = await (0, import_jev5.readJevArtifact)(file);
|
|
2244
|
+
if (existing) {
|
|
2245
|
+
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))
|
|
2246
|
+
throw new Error("Jev recording identity mismatch");
|
|
2247
|
+
(0, import_jev5.validateJevResponse)(existing.response, input.questions);
|
|
2248
|
+
for (const key of import_jev4.JEV_DIMENSIONS) {
|
|
2249
|
+
const expected = input.questions.includes(key) ? existing.response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null;
|
|
2250
|
+
if (existing.scores[key] !== expected)
|
|
2251
|
+
throw new Error("Jev recording scores do not match the response");
|
|
2252
|
+
}
|
|
2253
|
+
return existing;
|
|
2254
|
+
}
|
|
2255
|
+
if (config.source === "recorded")
|
|
2256
|
+
throw new Error(
|
|
2257
|
+
`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.`
|
|
2258
|
+
);
|
|
2259
|
+
const settings = await (0, import_userSettings.getUserSettings)(userName);
|
|
2260
|
+
if (!settings.JEV_API_KEY)
|
|
2261
|
+
throw new Error("Configure the Jev API key in Account settings");
|
|
2262
|
+
const apiKey = settings.JEV_API_KEY;
|
|
2263
|
+
const configuredProvider = await resolveJevProvider(userName);
|
|
2264
|
+
if (configuredProvider.endpoint !== provider.endpoint)
|
|
2265
|
+
throw new Error(
|
|
2266
|
+
"Jev account endpoint changed; update the frozen provider configuration"
|
|
2267
|
+
);
|
|
2268
|
+
const started = Date.now();
|
|
2269
|
+
const state = stateForJevInput(input);
|
|
2270
|
+
const responseId = (0, import_jev5.jevHash)({
|
|
2271
|
+
state,
|
|
2272
|
+
questionsHash,
|
|
2273
|
+
provider,
|
|
2274
|
+
user: (0, import_jev5.jevHash)(userName)
|
|
2275
|
+
});
|
|
2276
|
+
const responseFile = import_node_path.default.join(dir, "responses", `${responseId}.json`);
|
|
2277
|
+
let responsePromise = pendingResponses.get(responseFile);
|
|
2278
|
+
if (!responsePromise) {
|
|
2279
|
+
responsePromise = (async () => {
|
|
2280
|
+
const saved = await (0, import_jev5.readJevArtifact)(responseFile);
|
|
2281
|
+
if (saved) return (0, import_jev5.validateJevResponse)(saved, input.questions);
|
|
2282
|
+
const fresh = await (0, import_jev5.requestJev)(provider, apiKey, state, questions);
|
|
2283
|
+
await (0, import_jev5.writeJevArtifact)(responseFile, fresh);
|
|
2284
|
+
return fresh;
|
|
2285
|
+
})();
|
|
2286
|
+
pendingResponses.set(responseFile, responsePromise);
|
|
2287
|
+
}
|
|
2288
|
+
let response;
|
|
2289
|
+
try {
|
|
2290
|
+
response = await responsePromise;
|
|
2291
|
+
} finally {
|
|
2292
|
+
pendingResponses.delete(responseFile);
|
|
2293
|
+
}
|
|
2294
|
+
const scores = Object.fromEntries(
|
|
2295
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
2296
|
+
key,
|
|
2297
|
+
input.questions.includes(key) ? response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null
|
|
2298
|
+
])
|
|
2299
|
+
);
|
|
2300
|
+
const value = {
|
|
2301
|
+
schema: "tradejs-jev-record/v4",
|
|
2302
|
+
id,
|
|
2303
|
+
inputHash,
|
|
2304
|
+
questionsHash,
|
|
2305
|
+
provider,
|
|
2306
|
+
input,
|
|
2307
|
+
response,
|
|
2308
|
+
scores,
|
|
2309
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2310
|
+
elapsedMs: Date.now() - started
|
|
2311
|
+
};
|
|
2312
|
+
await (0, import_jev5.writeJevArtifact)(file, value);
|
|
2313
|
+
return await (0, import_jev5.readJevArtifact)(file);
|
|
1910
2314
|
};
|
|
1911
|
-
}
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
2315
|
+
const pendingKey = `${file}:${(0, import_jev5.jevHash)(userName)}`;
|
|
2316
|
+
let promise = pending.get(pendingKey);
|
|
2317
|
+
if (!promise) {
|
|
2318
|
+
promise = getRecord();
|
|
2319
|
+
pending.set(pendingKey, promise);
|
|
1916
2320
|
}
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
return;
|
|
2321
|
+
let record2;
|
|
2322
|
+
try {
|
|
2323
|
+
record2 = await promise;
|
|
2324
|
+
} finally {
|
|
2325
|
+
pending.delete(pendingKey);
|
|
1923
2326
|
}
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
2327
|
+
return {
|
|
2328
|
+
record: record2,
|
|
2329
|
+
assessment: {
|
|
2330
|
+
...base,
|
|
2331
|
+
status: "available",
|
|
2332
|
+
recordId: id,
|
|
2333
|
+
model: record2.response.model,
|
|
2334
|
+
scores: record2.scores,
|
|
2335
|
+
confidence: Object.fromEntries(
|
|
2336
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
2337
|
+
key,
|
|
2338
|
+
record2.response.answers[key]?.confidence ?? null
|
|
2339
|
+
])
|
|
2340
|
+
)
|
|
1931
2341
|
}
|
|
1932
|
-
}
|
|
1933
|
-
tsNodeRegistered = true;
|
|
1934
|
-
};
|
|
1935
|
-
var ensureTsconfigPathsRegistered = async (cwd = getTradejsProjectCwd()) => {
|
|
1936
|
-
const projectRoot = getTradejsProjectCwd(cwd);
|
|
1937
|
-
if (tsconfigPathsRegisteredByCwd.has(projectRoot)) {
|
|
1938
|
-
return;
|
|
1939
|
-
}
|
|
1940
|
-
const tsconfigPathsModule = await import("tsconfig-paths");
|
|
1941
|
-
const loadConfig = tsconfigPathsModule.loadConfig;
|
|
1942
|
-
const register = tsconfigPathsModule.register;
|
|
1943
|
-
if (typeof loadConfig !== "function" || typeof register !== "function") {
|
|
1944
|
-
return;
|
|
1945
|
-
}
|
|
1946
|
-
const loadedConfig = loadConfig(projectRoot);
|
|
1947
|
-
if (loadedConfig.resultType !== "success") {
|
|
1948
|
-
return;
|
|
1949
|
-
}
|
|
1950
|
-
register({
|
|
1951
|
-
baseUrl: loadedConfig.absoluteBaseUrl,
|
|
1952
|
-
paths: loadedConfig.paths,
|
|
1953
|
-
addMatchAll: false
|
|
1954
|
-
});
|
|
1955
|
-
tsconfigPathsRegisteredByCwd.add(projectRoot);
|
|
2342
|
+
};
|
|
1956
2343
|
};
|
|
1957
|
-
var
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
2344
|
+
var assessSignalWithJev = async ({
|
|
2345
|
+
signal,
|
|
2346
|
+
config,
|
|
2347
|
+
userName,
|
|
2348
|
+
projectRoot,
|
|
2349
|
+
strict,
|
|
2350
|
+
scope = "runtime"
|
|
2351
|
+
}) => {
|
|
2352
|
+
let assessment;
|
|
2353
|
+
try {
|
|
2354
|
+
const input = buildJevInput(signal);
|
|
2355
|
+
({ assessment } = await evaluateJevInput({
|
|
2356
|
+
input,
|
|
2357
|
+
config,
|
|
2358
|
+
userName,
|
|
2359
|
+
projectRoot
|
|
2360
|
+
}));
|
|
2361
|
+
} catch (error) {
|
|
2362
|
+
if (strict) throw error;
|
|
2363
|
+
assessment = {
|
|
2364
|
+
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
2365
|
+
schema: "tradejs-signal-assessment/v5",
|
|
2366
|
+
source: config.source,
|
|
2367
|
+
mode: config.mode,
|
|
2368
|
+
status: "unavailable",
|
|
2369
|
+
inputHash: "",
|
|
2370
|
+
questionsHash: "",
|
|
2371
|
+
geometryStatus: "absent",
|
|
2372
|
+
levelsValid: null,
|
|
2373
|
+
model: config.provider?.model ?? config.modelSha256 ?? "",
|
|
2374
|
+
scores: emptyScores(),
|
|
2375
|
+
confidence: emptyScores()
|
|
2376
|
+
};
|
|
1973
2377
|
}
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
2378
|
+
signal.assessment = assessment;
|
|
2379
|
+
signal.additionalIndicators = {
|
|
2380
|
+
...signal.additionalIndicators,
|
|
2381
|
+
jev: (0, import_jev4.jevAssessmentFeatures)(assessment)
|
|
2382
|
+
};
|
|
2383
|
+
const identity = {
|
|
2384
|
+
signalId: signal.signalId,
|
|
2385
|
+
strategy: signal.strategy,
|
|
2386
|
+
symbol: signal.symbol,
|
|
2387
|
+
timestamp: signal.timestamp,
|
|
2388
|
+
scope,
|
|
2389
|
+
user: (0, import_jev5.jevHash)(userName),
|
|
2390
|
+
config,
|
|
2391
|
+
assessment
|
|
2392
|
+
};
|
|
2393
|
+
await (0, import_jev5.writeJevArtifact)(
|
|
2394
|
+
import_node_path.default.resolve(
|
|
2395
|
+
projectRoot,
|
|
2396
|
+
config.recordsDir ?? "data/ai/jev",
|
|
2397
|
+
"decisions",
|
|
2398
|
+
`${(0, import_jev5.jevHash)(identity)}.json`
|
|
2399
|
+
),
|
|
2400
|
+
identity
|
|
1977
2401
|
);
|
|
1978
|
-
|
|
1979
|
-
".ts",
|
|
1980
|
-
".tsx",
|
|
1981
|
-
".mts",
|
|
1982
|
-
".cts",
|
|
1983
|
-
".js",
|
|
1984
|
-
".jsx",
|
|
1985
|
-
".mjs",
|
|
1986
|
-
".cjs",
|
|
1987
|
-
".json"
|
|
1988
|
-
]) || "";
|
|
1989
|
-
tsconfigPathMatchersByCwd.set(projectRoot, matcher);
|
|
1990
|
-
const resolved = matcher(moduleName);
|
|
1991
|
-
return resolved || null;
|
|
2402
|
+
return assessment;
|
|
1992
2403
|
};
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
2404
|
+
|
|
2405
|
+
// src/strategyRuntime.ts
|
|
2406
|
+
var import_constants5 = require("@tradejs/core/constants");
|
|
2407
|
+
var import_strategies6 = require("@tradejs/core/strategies");
|
|
2408
|
+
var import_logger11 = require("@tradejs/infra/logger");
|
|
2409
|
+
|
|
2410
|
+
// src/strategyHelpers/runtime.ts
|
|
2411
|
+
var import_logger9 = require("@tradejs/infra/logger");
|
|
2412
|
+
var import_constants3 = require("@tradejs/core/constants");
|
|
2413
|
+
var import_ml2 = require("@tradejs/infra/ml");
|
|
2414
|
+
|
|
2415
|
+
// src/strategy/entrySubmissionValidation.ts
|
|
2416
|
+
var import_logger3 = require("@tradejs/infra/logger");
|
|
2417
|
+
|
|
2418
|
+
// src/strategy/entryExecutionValidation.ts
|
|
2419
|
+
var EntrySubmissionValidationError = class extends Error {
|
|
2420
|
+
constructor() {
|
|
2421
|
+
super(...arguments);
|
|
2422
|
+
this.name = "EntrySubmissionValidationError";
|
|
1999
2423
|
}
|
|
2000
|
-
return moduleName;
|
|
2001
2424
|
};
|
|
2002
|
-
var
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
const
|
|
2006
|
-
if (
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2425
|
+
var resolveEntryExecutionLimits = (config, direction) => {
|
|
2426
|
+
const side = config[direction];
|
|
2427
|
+
const minimum = side && typeof side === "object" && "minRiskRatio" in side ? side.minRiskRatio : void 0;
|
|
2428
|
+
const maximum = config.MAX_LOSS_VALUE;
|
|
2429
|
+
if (minimum == null && maximum == null) return void 0;
|
|
2430
|
+
const minimumNetRiskRatio = minimum == null ? void 0 : Number(minimum);
|
|
2431
|
+
const maxLossValue = maximum == null ? void 0 : Number(maximum);
|
|
2432
|
+
const feeRate = Number(config.RISK_FEE_RATE ?? 0);
|
|
2433
|
+
const slippage = Number(config.RISK_SLIPPAGE_BPS ?? 0);
|
|
2434
|
+
const impact = Number(config.RISK_MARKET_IMPACT_BPS ?? 0);
|
|
2435
|
+
const slippageBps = slippage + impact;
|
|
2436
|
+
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)
|
|
2437
|
+
throw new Error("INVALID_ENTRY_EXECUTION_LIMITS");
|
|
2438
|
+
return { minimumNetRiskRatio, maxLossValue, feeRate, slippageBps };
|
|
2013
2439
|
};
|
|
2014
|
-
var
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2440
|
+
var validateEntryExecution = ({
|
|
2441
|
+
direction,
|
|
2442
|
+
price,
|
|
2443
|
+
qty,
|
|
2444
|
+
stopLossPrice,
|
|
2445
|
+
takeProfitPrice,
|
|
2446
|
+
limits
|
|
2447
|
+
}) => {
|
|
2448
|
+
if (!Number.isFinite(price) || price <= 0 || !Number.isFinite(qty) || qty <= 0 || stopLossPrice == null || !Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || !Number.isFinite(takeProfitPrice) || takeProfitPrice <= 0)
|
|
2449
|
+
throw new EntrySubmissionValidationError(
|
|
2450
|
+
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
2451
|
+
);
|
|
2452
|
+
const side = direction === "LONG" ? 1 : -1;
|
|
2453
|
+
if (side * (price - stopLossPrice) <= 0 || side * (takeProfitPrice - price) <= 0)
|
|
2454
|
+
throw new EntrySubmissionValidationError(
|
|
2455
|
+
"INVALID_ENTRY_PROTECTION_AT_SUBMISSION"
|
|
2456
|
+
);
|
|
2457
|
+
const slippage = limits.slippageBps / 1e4;
|
|
2458
|
+
const cost = (value, sign) => value * slippage + value * (1 + sign * slippage) * limits.feeRate;
|
|
2459
|
+
const entryCost = cost(price, side);
|
|
2460
|
+
const lossPerUnit = Math.abs(price - stopLossPrice) + entryCost + cost(stopLossPrice, -side);
|
|
2461
|
+
const rewardPerUnit = Math.max(
|
|
2462
|
+
0,
|
|
2463
|
+
Math.abs(takeProfitPrice - price) - entryCost - cost(takeProfitPrice, -side)
|
|
2026
2464
|
);
|
|
2465
|
+
const netRiskRatio = rewardPerUnit / lossPerUnit;
|
|
2466
|
+
const plannedLossValue = qty * lossPerUnit;
|
|
2467
|
+
if (!Number.isFinite(netRiskRatio) || !Number.isFinite(plannedLossValue))
|
|
2468
|
+
throw new EntrySubmissionValidationError(
|
|
2469
|
+
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
2470
|
+
);
|
|
2471
|
+
const belowMinimumNetRiskRatio = limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio;
|
|
2472
|
+
const riskExceeded = limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9);
|
|
2473
|
+
return {
|
|
2474
|
+
netRiskRatio,
|
|
2475
|
+
plannedLossValue,
|
|
2476
|
+
riskExceeded,
|
|
2477
|
+
belowMinimumNetRiskRatio
|
|
2478
|
+
};
|
|
2027
2479
|
};
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
await ensureTsconfigPathsRegistered(cwd);
|
|
2047
|
-
return requireFn(modulePath);
|
|
2048
|
-
}
|
|
2049
|
-
if (isBareModuleSpecifier(normalized)) {
|
|
2050
|
-
await ensureTsconfigPathsRegistered(cwd);
|
|
2051
|
-
try {
|
|
2052
|
-
return requireFn(normalized);
|
|
2053
|
-
} catch (error) {
|
|
2054
|
-
const resolvedByTsconfig = await resolveTsconfigPathModule(
|
|
2055
|
-
normalized,
|
|
2056
|
-
cwd
|
|
2057
|
-
);
|
|
2058
|
-
if (resolvedByTsconfig && resolvedByTsconfig !== normalized) {
|
|
2059
|
-
return requireFn(resolvedByTsconfig);
|
|
2060
|
-
}
|
|
2061
|
-
throw error;
|
|
2062
|
-
}
|
|
2480
|
+
|
|
2481
|
+
// src/strategy/entrySubmissionValidation.ts
|
|
2482
|
+
var MAX_ENTRY_QUOTE_AGE_MS = 5e3;
|
|
2483
|
+
var toFiniteNumberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
2484
|
+
var getOrderArrivalSnapshot = async ({
|
|
2485
|
+
connector,
|
|
2486
|
+
symbol,
|
|
2487
|
+
requireTimestamp = false
|
|
2488
|
+
}) => {
|
|
2489
|
+
if (typeof connector.getTopOfBookTicker !== "function") {
|
|
2490
|
+
return {
|
|
2491
|
+
arrivalSnapshotTime: Date.now(),
|
|
2492
|
+
arrivalSource: "unavailable",
|
|
2493
|
+
bid: null,
|
|
2494
|
+
ask: null,
|
|
2495
|
+
arrivalMid: null,
|
|
2496
|
+
spreadBps: null
|
|
2497
|
+
};
|
|
2063
2498
|
}
|
|
2064
2499
|
try {
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
);
|
|
2500
|
+
const ticker = await connector.getTopOfBookTicker(symbol);
|
|
2501
|
+
const arrivalSnapshotTime = toFiniteNumberOrNull(ticker?.timestamp);
|
|
2502
|
+
const timestampUnavailable = requireTimestamp && arrivalSnapshotTime == null;
|
|
2503
|
+
const bid = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.bidPrice);
|
|
2504
|
+
const ask = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.askPrice);
|
|
2505
|
+
const arrivalMid = bid != null && ask != null ? (bid + ask) / 2 : null;
|
|
2506
|
+
const spreadBps = bid != null && ask != null && arrivalMid != null && arrivalMid > 0 ? (ask - bid) / arrivalMid * 1e4 : null;
|
|
2507
|
+
return {
|
|
2508
|
+
arrivalSnapshotTime: arrivalSnapshotTime ?? Date.now(),
|
|
2509
|
+
arrivalSource: "top_of_book",
|
|
2510
|
+
bid,
|
|
2511
|
+
ask,
|
|
2512
|
+
arrivalMid,
|
|
2513
|
+
spreadBps
|
|
2514
|
+
};
|
|
2069
2515
|
} catch (error) {
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
let currentDir = import_path.default.resolve(cwd);
|
|
2084
|
-
while (true) {
|
|
2085
|
-
for (const fileName of CONFIG_FILE_NAMES) {
|
|
2086
|
-
const fullPath = import_path.default.join(currentDir, fileName);
|
|
2087
|
-
if (import_fs.default.existsSync(fullPath) && import_fs.default.statSync(fullPath).isFile()) {
|
|
2088
|
-
return fullPath;
|
|
2089
|
-
}
|
|
2090
|
-
}
|
|
2091
|
-
const parentDir = import_path.default.dirname(currentDir);
|
|
2092
|
-
if (parentDir === currentDir) {
|
|
2093
|
-
return null;
|
|
2094
|
-
}
|
|
2095
|
-
currentDir = parentDir;
|
|
2516
|
+
import_logger3.logger.warn(
|
|
2517
|
+
"runtime order arrival snapshot failed: %s %s",
|
|
2518
|
+
symbol,
|
|
2519
|
+
error?.message || String(error)
|
|
2520
|
+
);
|
|
2521
|
+
return {
|
|
2522
|
+
arrivalSnapshotTime: Date.now(),
|
|
2523
|
+
arrivalSource: "top_of_book_error",
|
|
2524
|
+
bid: null,
|
|
2525
|
+
ask: null,
|
|
2526
|
+
arrivalMid: null,
|
|
2527
|
+
spreadBps: null
|
|
2528
|
+
};
|
|
2096
2529
|
}
|
|
2097
2530
|
};
|
|
2098
|
-
var
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2531
|
+
var validateEntryProtectionAtArrival = ({
|
|
2532
|
+
direction,
|
|
2533
|
+
signalPrice,
|
|
2534
|
+
bid,
|
|
2535
|
+
ask,
|
|
2536
|
+
arrivalMid,
|
|
2537
|
+
takeProfits,
|
|
2538
|
+
stopLossPrice
|
|
2539
|
+
}) => {
|
|
2540
|
+
const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
|
|
2541
|
+
const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
|
|
2542
|
+
if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
|
|
2543
|
+
throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
|
|
2102
2544
|
}
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
} catch {
|
|
2107
|
-
return normalized;
|
|
2545
|
+
for (const takeProfit of takeProfits) {
|
|
2546
|
+
if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
|
|
2547
|
+
throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
|
|
2108
2548
|
}
|
|
2109
2549
|
}
|
|
2110
|
-
if (
|
|
2111
|
-
|
|
2112
|
-
}
|
|
2113
|
-
if (isRelativeModulePath(normalized)) {
|
|
2114
|
-
return import_path.default.resolve(cwd, normalized);
|
|
2550
|
+
if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
|
|
2551
|
+
throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
|
|
2115
2552
|
}
|
|
2116
|
-
return normalized;
|
|
2117
2553
|
};
|
|
2118
|
-
var
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2554
|
+
var createEntrySubmissionValidation = ({
|
|
2555
|
+
connector,
|
|
2556
|
+
symbol,
|
|
2557
|
+
direction,
|
|
2558
|
+
takeProfits,
|
|
2559
|
+
stopLossPrice,
|
|
2560
|
+
limits,
|
|
2561
|
+
onValidated
|
|
2562
|
+
}) => {
|
|
2563
|
+
if (!limits) return void 0;
|
|
2564
|
+
if (!connector.capabilities?.orderSubmissionValidation)
|
|
2565
|
+
throw new EntrySubmissionValidationError(
|
|
2566
|
+
"ENTRY_SUBMISSION_VALIDATION_UNSUPPORTED"
|
|
2567
|
+
);
|
|
2568
|
+
return async ({ qty, executionPrice }) => {
|
|
2569
|
+
const simulated = Reflect.get(connector, "__tradejsTestConnector") === true;
|
|
2570
|
+
const snapshot2 = simulated && executionPrice != null ? {
|
|
2571
|
+
arrivalSnapshotTime: Date.now(),
|
|
2572
|
+
arrivalSource: "simulation",
|
|
2573
|
+
bid: executionPrice,
|
|
2574
|
+
ask: executionPrice,
|
|
2575
|
+
arrivalMid: executionPrice,
|
|
2576
|
+
spreadBps: 0
|
|
2577
|
+
} : await getOrderArrivalSnapshot({
|
|
2578
|
+
connector,
|
|
2579
|
+
symbol,
|
|
2580
|
+
requireTimestamp: true
|
|
2581
|
+
});
|
|
2582
|
+
const age = Date.now() - snapshot2.arrivalSnapshotTime;
|
|
2583
|
+
if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
|
|
2584
|
+
throw new EntrySubmissionValidationError(
|
|
2585
|
+
"ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
|
|
2586
|
+
);
|
|
2587
|
+
if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
|
|
2588
|
+
throw new EntrySubmissionValidationError(
|
|
2589
|
+
"ENTRY_QUOTE_STALE_AT_SUBMISSION"
|
|
2590
|
+
);
|
|
2591
|
+
const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
|
|
2592
|
+
validateEntryProtectionAtArrival({
|
|
2593
|
+
direction,
|
|
2594
|
+
signalPrice: price,
|
|
2595
|
+
bid: snapshot2.bid,
|
|
2596
|
+
ask: snapshot2.ask,
|
|
2597
|
+
arrivalMid: snapshot2.arrivalMid,
|
|
2598
|
+
takeProfits,
|
|
2599
|
+
stopLossPrice
|
|
2600
|
+
});
|
|
2601
|
+
const prices = takeProfits.map((tp) => tp.price);
|
|
2602
|
+
const economics2 = validateEntryExecution({
|
|
2603
|
+
direction,
|
|
2604
|
+
price,
|
|
2605
|
+
qty,
|
|
2606
|
+
stopLossPrice,
|
|
2607
|
+
takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
|
|
2608
|
+
limits
|
|
2609
|
+
});
|
|
2610
|
+
if (economics2.belowMinimumNetRiskRatio) {
|
|
2611
|
+
import_logger3.logger.warn(
|
|
2612
|
+
"entry submission warning: %s %s ENTRY_RR_BELOW_MIN_AT_SUBMISSION (RR %s, threshold %s)",
|
|
2613
|
+
symbol,
|
|
2614
|
+
direction,
|
|
2615
|
+
economics2.netRiskRatio,
|
|
2616
|
+
limits.minimumNetRiskRatio
|
|
2617
|
+
);
|
|
2618
|
+
}
|
|
2619
|
+
onValidated?.(snapshot2, economics2);
|
|
2620
|
+
};
|
|
2621
|
+
};
|
|
2622
|
+
|
|
2623
|
+
// src/ai.ts
|
|
2624
|
+
var import_aiLanguages2 = require("@tradejs/core/aiLanguages");
|
|
2625
|
+
var import_redis3 = require("@tradejs/infra/redis");
|
|
2626
|
+
|
|
2627
|
+
// src/strategyAdapters/ai.ts
|
|
2628
|
+
var import_indicators2 = require("@tradejs/core/indicators");
|
|
2629
|
+
var import_aiFeatures = require("@tradejs/core/aiFeatures");
|
|
2630
|
+
|
|
2631
|
+
// src/aiShared.ts
|
|
2632
|
+
var MAX_AI_SERIES_POINTS = 5;
|
|
2633
|
+
var COMPACT_INDICATORS_SNAPSHOT_SYMBOL = /* @__PURE__ */ Symbol.for(
|
|
2634
|
+
"tradejs.indicators.compactSnapshot"
|
|
2635
|
+
);
|
|
2636
|
+
var COMPACT_INDICATORS_SNAPSHOT_KEY = "__tradejsCompactIndicatorsSnapshot";
|
|
2637
|
+
var trimSeriesDeep = (value) => {
|
|
2638
|
+
if (Array.isArray(value)) {
|
|
2639
|
+
const trimmed = value.slice(-MAX_AI_SERIES_POINTS);
|
|
2640
|
+
const isMatrix = trimmed.every((item) => Array.isArray(item));
|
|
2641
|
+
if (isMatrix) {
|
|
2642
|
+
return trimmed;
|
|
2135
2643
|
}
|
|
2136
|
-
return
|
|
2137
|
-
|
|
2138
|
-
import_logger2.logger.log(
|
|
2139
|
-
"warn",
|
|
2140
|
-
"Failed to load TradeJS config from %s: %s",
|
|
2141
|
-
configFilePath,
|
|
2142
|
-
String(error)
|
|
2644
|
+
return trimmed.map(
|
|
2645
|
+
(item) => item && typeof item === "object" ? trimSeriesDeep(item) : item
|
|
2143
2646
|
);
|
|
2144
|
-
cachedByCwd.set(cwd, {});
|
|
2145
|
-
return {};
|
|
2146
2647
|
}
|
|
2648
|
+
if (value && typeof value === "object") {
|
|
2649
|
+
return Object.fromEntries(
|
|
2650
|
+
Object.entries(value).map(([key, nested]) => [
|
|
2651
|
+
key,
|
|
2652
|
+
trimSeriesDeep(nested)
|
|
2653
|
+
])
|
|
2654
|
+
);
|
|
2655
|
+
}
|
|
2656
|
+
return value;
|
|
2657
|
+
};
|
|
2658
|
+
var buildCompactAiIndicatorsSnapshot = (value) => {
|
|
2659
|
+
const compactSnapshot = value && typeof value === "object" ? value[COMPACT_INDICATORS_SNAPSHOT_SYMBOL] ?? value[COMPACT_INDICATORS_SNAPSHOT_KEY] : void 0;
|
|
2660
|
+
if (typeof compactSnapshot === "function") {
|
|
2661
|
+
return compactSnapshot({ limit: MAX_AI_SERIES_POINTS });
|
|
2662
|
+
}
|
|
2663
|
+
return trimSeriesDeep(value);
|
|
2147
2664
|
};
|
|
2148
2665
|
|
|
2149
|
-
// src/
|
|
2150
|
-
var
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2666
|
+
// src/aiMarketContext.ts
|
|
2667
|
+
var toRecord = (value) => {
|
|
2668
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2669
|
+
return null;
|
|
2670
|
+
}
|
|
2671
|
+
return value;
|
|
2672
|
+
};
|
|
2673
|
+
var toFiniteNumber = (value) => {
|
|
2674
|
+
const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
|
|
2675
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
2676
|
+
};
|
|
2677
|
+
var roundTo = (value, decimals) => {
|
|
2678
|
+
const factor = 10 ** decimals;
|
|
2679
|
+
return Math.round(value * factor) / factor;
|
|
2680
|
+
};
|
|
2681
|
+
var buildMissingSpreadContext = () => ({
|
|
2682
|
+
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2683
|
+
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2684
|
+
available: false,
|
|
2685
|
+
value: null,
|
|
2686
|
+
zScore: null,
|
|
2687
|
+
bps: null,
|
|
2688
|
+
absBps: null,
|
|
2689
|
+
bias: null,
|
|
2690
|
+
severity: null
|
|
2162
2691
|
});
|
|
2163
|
-
var
|
|
2164
|
-
|
|
2165
|
-
const
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2692
|
+
var buildSpreadContextFromSignal = (signal) => {
|
|
2693
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2694
|
+
const relative = toRecord(baseContext?.relative);
|
|
2695
|
+
const execution = toRecord(relative?.execution);
|
|
2696
|
+
const spread = toFiniteNumber(execution?.venueSpread);
|
|
2697
|
+
const zScore = toFiniteNumber(execution?.venueSpreadZScore);
|
|
2698
|
+
if (spread == null) {
|
|
2699
|
+
return buildMissingSpreadContext();
|
|
2170
2700
|
}
|
|
2701
|
+
const value = roundTo(spread, 8);
|
|
2702
|
+
const bps = roundTo(value * 1e4, 2);
|
|
2703
|
+
const absBps = Math.abs(bps);
|
|
2704
|
+
const bias = absBps < 1 ? "flat" : bps > 0 ? "coinbase_premium" : "binance_premium";
|
|
2705
|
+
const severity = absBps >= 20 ? "wide" : absBps >= 5 ? "elevated" : "normal";
|
|
2171
2706
|
return {
|
|
2172
|
-
|
|
2173
|
-
|
|
2707
|
+
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2708
|
+
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2709
|
+
available: true,
|
|
2710
|
+
value,
|
|
2711
|
+
zScore,
|
|
2712
|
+
bps,
|
|
2713
|
+
absBps,
|
|
2714
|
+
bias,
|
|
2715
|
+
severity
|
|
2174
2716
|
};
|
|
2175
2717
|
};
|
|
2176
|
-
var
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
const
|
|
2718
|
+
var buildTrueDeltaContextFromSignal = (signal) => {
|
|
2719
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2720
|
+
const participation = toRecord(baseContext?.participation);
|
|
2721
|
+
const delta = toRecord(participation?.delta);
|
|
2722
|
+
const source = String(delta?.source ?? "");
|
|
2723
|
+
const isTrueDeltaSource = source === "kline_taker_volume" || source === "agg_trades" || source === "trades";
|
|
2724
|
+
if (!delta || !isTrueDeltaSource) {
|
|
2725
|
+
return {
|
|
2726
|
+
source: source || null,
|
|
2727
|
+
available: false,
|
|
2728
|
+
buyPressurePct: null,
|
|
2729
|
+
buyVolume: null,
|
|
2730
|
+
sellVolume: null,
|
|
2731
|
+
netDelta: null,
|
|
2732
|
+
deltaPct: null,
|
|
2733
|
+
signedVolumeZScore: null
|
|
2734
|
+
};
|
|
2735
|
+
}
|
|
2181
2736
|
return {
|
|
2182
|
-
|
|
2183
|
-
|
|
2737
|
+
source,
|
|
2738
|
+
available: true,
|
|
2739
|
+
buyPressurePct: toFiniteNumber(delta.buyPressurePct),
|
|
2740
|
+
buyVolume: toFiniteNumber(delta.buyVolume),
|
|
2741
|
+
sellVolume: toFiniteNumber(delta.sellVolume),
|
|
2742
|
+
netDelta: toFiniteNumber(delta.netDelta),
|
|
2743
|
+
deltaPct: toFiniteNumber(delta.deltaPct),
|
|
2744
|
+
signedVolumeZScore: toFiniteNumber(delta.signedVolumeZScore)
|
|
2184
2745
|
};
|
|
2185
2746
|
};
|
|
2186
|
-
var
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2747
|
+
var buildTradeFlowContextFromSignal = (signal) => {
|
|
2748
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2749
|
+
const participation = toRecord(baseContext?.participation);
|
|
2750
|
+
const tradeFlow = toRecord(participation?.tradeFlow);
|
|
2751
|
+
if (!tradeFlow) {
|
|
2752
|
+
return {
|
|
2753
|
+
source: null,
|
|
2754
|
+
available: false,
|
|
2755
|
+
interval: null,
|
|
2756
|
+
stale: null,
|
|
2757
|
+
trades: null,
|
|
2758
|
+
buyPressurePct: null,
|
|
2759
|
+
netBaseDelta: null,
|
|
2760
|
+
netQuoteDelta: null
|
|
2761
|
+
};
|
|
2197
2762
|
}
|
|
2198
|
-
return
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2763
|
+
return {
|
|
2764
|
+
source: String(tradeFlow.source ?? ""),
|
|
2765
|
+
available: true,
|
|
2766
|
+
interval: String(tradeFlow.interval ?? ""),
|
|
2767
|
+
stale: typeof tradeFlow.stale === "boolean" ? tradeFlow.stale : null,
|
|
2768
|
+
trades: toFiniteNumber(tradeFlow.trades),
|
|
2769
|
+
buyPressurePct: toFiniteNumber(tradeFlow.buyPressurePct),
|
|
2770
|
+
netBaseDelta: toFiniteNumber(tradeFlow.netBaseDelta),
|
|
2771
|
+
netQuoteDelta: toFiniteNumber(tradeFlow.netQuoteDelta)
|
|
2772
|
+
};
|
|
2206
2773
|
};
|
|
2207
|
-
var
|
|
2208
|
-
const
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
)
|
|
2212
|
-
|
|
2774
|
+
var buildMarketBreadthContextFromSignal = (signal) => {
|
|
2775
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2776
|
+
const relative = toRecord(baseContext?.relative);
|
|
2777
|
+
const breadth = toRecord(relative?.marketBreadth);
|
|
2778
|
+
if (!breadth) {
|
|
2779
|
+
return {
|
|
2780
|
+
source: null,
|
|
2781
|
+
available: false,
|
|
2782
|
+
universe: null,
|
|
2783
|
+
interval: null,
|
|
2784
|
+
stale: null,
|
|
2785
|
+
symbolsCount: null,
|
|
2786
|
+
advanceDeclineRatio: null,
|
|
2787
|
+
pctAboveMa20: null,
|
|
2788
|
+
pctAboveMa50: null,
|
|
2789
|
+
equalWeightedReturn: null,
|
|
2790
|
+
volumeWeightedReturn: null,
|
|
2791
|
+
dispersion: null
|
|
2792
|
+
};
|
|
2793
|
+
}
|
|
2794
|
+
return {
|
|
2795
|
+
source: String(breadth.source ?? ""),
|
|
2796
|
+
available: true,
|
|
2797
|
+
universe: String(breadth.universe ?? ""),
|
|
2798
|
+
interval: String(breadth.interval ?? ""),
|
|
2799
|
+
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
2800
|
+
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
2801
|
+
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
2802
|
+
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
2803
|
+
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
2804
|
+
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
2805
|
+
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
2806
|
+
dispersion: toFiniteNumber(breadth.dispersion)
|
|
2807
|
+
};
|
|
2213
2808
|
};
|
|
2214
|
-
var
|
|
2215
|
-
const
|
|
2216
|
-
const
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
const
|
|
2220
|
-
if (
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2809
|
+
var buildMarketBreadthsContextFromSignal = (signal) => {
|
|
2810
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2811
|
+
const relative = toRecord(baseContext?.relative);
|
|
2812
|
+
const breadths = toRecord(relative?.marketBreadths);
|
|
2813
|
+
const build = (key) => {
|
|
2814
|
+
const breadth = toRecord(breadths?.[key]);
|
|
2815
|
+
if (!breadth) {
|
|
2816
|
+
return {
|
|
2817
|
+
source: null,
|
|
2818
|
+
available: false,
|
|
2819
|
+
universe: null,
|
|
2820
|
+
interval: null,
|
|
2821
|
+
stale: null,
|
|
2822
|
+
symbolsCount: null,
|
|
2823
|
+
advanceDeclineRatio: null,
|
|
2824
|
+
pctAboveMa20: null,
|
|
2825
|
+
pctAboveMa50: null,
|
|
2826
|
+
equalWeightedReturn: null,
|
|
2827
|
+
volumeWeightedReturn: null,
|
|
2828
|
+
dispersion: null
|
|
2829
|
+
};
|
|
2235
2830
|
}
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2831
|
+
return {
|
|
2832
|
+
source: String(breadth.source ?? ""),
|
|
2833
|
+
available: true,
|
|
2834
|
+
universe: String(breadth.universe ?? ""),
|
|
2835
|
+
interval: String(breadth.interval ?? ""),
|
|
2836
|
+
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
2837
|
+
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
2838
|
+
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
2839
|
+
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
2840
|
+
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
2841
|
+
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
2842
|
+
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
2843
|
+
dispersion: toFiniteNumber(breadth.dispersion)
|
|
2844
|
+
};
|
|
2845
|
+
};
|
|
2846
|
+
return {
|
|
2847
|
+
top5: build("top5"),
|
|
2848
|
+
top10: build("top10"),
|
|
2849
|
+
top30: build("top30"),
|
|
2850
|
+
top50: build("top50"),
|
|
2851
|
+
top100: build("top100")
|
|
2852
|
+
};
|
|
2247
2853
|
};
|
|
2248
|
-
var
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
}
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
);
|
|
2854
|
+
var buildTargetVsReference = (signal, asset) => {
|
|
2855
|
+
const base = toRecord(signal.additionalIndicators?.baseContext);
|
|
2856
|
+
const relative = toRecord(base?.relative);
|
|
2857
|
+
const target = toRecord(relative?.[`targetVs${asset}`]);
|
|
2858
|
+
return {
|
|
2859
|
+
source: target ? String(target.source ?? "") : null,
|
|
2860
|
+
available: Boolean(target),
|
|
2861
|
+
ratioReturn1h: toFiniteNumber(target?.ratioReturn1h),
|
|
2862
|
+
ratioReturn4h: toFiniteNumber(target?.ratioReturn4h),
|
|
2863
|
+
ratioReturn24h: toFiniteNumber(target?.ratioReturn24h),
|
|
2864
|
+
alpha1h: toFiniteNumber(target?.[`alphaVs${asset}1h`]),
|
|
2865
|
+
alpha4h: toFiniteNumber(target?.[`alphaVs${asset}4h`]),
|
|
2866
|
+
alpha24h: toFiniteNumber(target?.[`alphaVs${asset}24h`]),
|
|
2867
|
+
beta: toFiniteNumber(target?.[`betaTo${asset}20`]),
|
|
2868
|
+
correlation: toFiniteNumber(target?.[`correlationTo${asset}20`]),
|
|
2869
|
+
ratioTrend: typeof target?.ratioTrend === "string" ? target.ratioTrend : null
|
|
2870
|
+
};
|
|
2266
2871
|
};
|
|
2267
|
-
var
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2872
|
+
var buildTargetVsBtcContextFromSignal = (signal) => {
|
|
2873
|
+
const { alpha1h, alpha4h, alpha24h, beta, correlation, ...shared } = buildTargetVsReference(signal, "Btc");
|
|
2874
|
+
return {
|
|
2875
|
+
...shared,
|
|
2876
|
+
alphaVsBtc1h: alpha1h,
|
|
2877
|
+
alphaVsBtc4h: alpha4h,
|
|
2878
|
+
alphaVsBtc24h: alpha24h,
|
|
2879
|
+
betaToBtc20: beta,
|
|
2880
|
+
correlationToBtc20: correlation
|
|
2881
|
+
};
|
|
2274
2882
|
};
|
|
2275
|
-
var
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2883
|
+
var buildTargetVsEthContextFromSignal = (signal) => {
|
|
2884
|
+
const { alpha1h, alpha4h, alpha24h, beta, correlation, ...shared } = buildTargetVsReference(signal, "Eth");
|
|
2885
|
+
return {
|
|
2886
|
+
...shared,
|
|
2887
|
+
alphaVsEth1h: alpha1h,
|
|
2888
|
+
alphaVsEth4h: alpha4h,
|
|
2889
|
+
alphaVsEth24h: alpha24h,
|
|
2890
|
+
betaToEth20: beta,
|
|
2891
|
+
correlationToEth20: correlation
|
|
2892
|
+
};
|
|
2283
2893
|
};
|
|
2284
|
-
var
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
const { projectRoot, state } = getStrategyRegistryState(cwd);
|
|
2290
|
-
const source = moduleName.trim();
|
|
2291
|
-
const resolvedModuleName = resolvePluginModuleSpecifier(source, projectRoot);
|
|
2292
|
-
const cacheKey = `${resolvedModuleName}\0${strategyName}`;
|
|
2293
|
-
const cached = state.strategyModuleResolutions.get(cacheKey);
|
|
2294
|
-
if (cached) return cached;
|
|
2295
|
-
const resolution = (async () => {
|
|
2296
|
-
const moduleExport = await importStrategyPluginModule(
|
|
2297
|
-
resolvedModuleName,
|
|
2298
|
-
projectRoot
|
|
2299
|
-
);
|
|
2300
|
-
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
2301
|
-
if (!pluginDefinition) {
|
|
2302
|
-
throw new Error(`${source}: export { strategyEntries } is missing`);
|
|
2303
|
-
}
|
|
2304
|
-
const issues = validateStrategyEntries(
|
|
2305
|
-
source,
|
|
2306
|
-
pluginDefinition.strategyEntries
|
|
2307
|
-
);
|
|
2308
|
-
if (issues.length > 0) {
|
|
2309
|
-
throw new Error(
|
|
2310
|
-
["Invalid TradeJS strategy module:", ...issues].join("\n")
|
|
2311
|
-
);
|
|
2312
|
-
}
|
|
2313
|
-
const entry = pluginDefinition.strategyEntries.find(
|
|
2314
|
-
(candidate) => candidate.manifest.name === strategyName
|
|
2315
|
-
);
|
|
2316
|
-
if (!entry) {
|
|
2317
|
-
throw new Error(`${source}: strategy ${strategyName} is missing`);
|
|
2318
|
-
}
|
|
2319
|
-
const factory = sharedStrategyRegistry.strategyRuntimeFactory;
|
|
2320
|
-
if (!factory) {
|
|
2321
|
-
throw new Error("Strategy runtime factory is not configured");
|
|
2322
|
-
}
|
|
2323
|
-
const manifestsByName = new Map(
|
|
2324
|
-
pluginDefinition.strategyEntries.map(
|
|
2325
|
-
(candidate) => [candidate.manifest.name, candidate.manifest]
|
|
2326
|
-
)
|
|
2327
|
-
);
|
|
2894
|
+
var buildBtcAltRegimeContextFromSignal = (signal) => {
|
|
2895
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2896
|
+
const relative = toRecord(baseContext?.relative);
|
|
2897
|
+
const btcAltRegime = toRecord(relative?.btcAltRegime);
|
|
2898
|
+
if (!btcAltRegime) {
|
|
2328
2899
|
return {
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2900
|
+
source: null,
|
|
2901
|
+
available: false,
|
|
2902
|
+
universe: null,
|
|
2903
|
+
interval: null,
|
|
2904
|
+
stale: null,
|
|
2905
|
+
regime: null,
|
|
2906
|
+
btcReturn24h: null,
|
|
2907
|
+
altBasketReturn24h: null,
|
|
2908
|
+
btcVsAltReturn24h: null,
|
|
2909
|
+
btcTurnoverShare24h: null,
|
|
2910
|
+
btcTurnoverShareChange24h: null,
|
|
2911
|
+
altVolToBtcVol24h: null,
|
|
2912
|
+
altDispersion24h: null
|
|
2340
2913
|
};
|
|
2341
|
-
})();
|
|
2342
|
-
state.strategyModuleResolutions.set(cacheKey, resolution);
|
|
2343
|
-
try {
|
|
2344
|
-
return await resolution;
|
|
2345
|
-
} catch (error) {
|
|
2346
|
-
state.strategyModuleResolutions.delete(cacheKey);
|
|
2347
|
-
throw error;
|
|
2348
2914
|
}
|
|
2915
|
+
return {
|
|
2916
|
+
source: String(btcAltRegime.source ?? ""),
|
|
2917
|
+
available: true,
|
|
2918
|
+
universe: String(btcAltRegime.universe ?? ""),
|
|
2919
|
+
interval: String(btcAltRegime.interval ?? ""),
|
|
2920
|
+
stale: typeof btcAltRegime.stale === "boolean" ? btcAltRegime.stale : null,
|
|
2921
|
+
regime: typeof btcAltRegime.regime === "string" ? btcAltRegime.regime : null,
|
|
2922
|
+
btcReturn24h: toFiniteNumber(btcAltRegime.btcReturn24h),
|
|
2923
|
+
altBasketReturn24h: toFiniteNumber(btcAltRegime.altBasketReturn24h),
|
|
2924
|
+
btcVsAltReturn24h: toFiniteNumber(btcAltRegime.btcVsAltReturn24h),
|
|
2925
|
+
btcTurnoverShare24h: toFiniteNumber(btcAltRegime.btcTurnoverShare24h),
|
|
2926
|
+
btcTurnoverShareChange24h: toFiniteNumber(
|
|
2927
|
+
btcAltRegime.btcTurnoverShareChange24h
|
|
2928
|
+
),
|
|
2929
|
+
altVolToBtcVol24h: toFiniteNumber(btcAltRegime.altVolToBtcVol24h),
|
|
2930
|
+
altDispersion24h: toFiniteNumber(btcAltRegime.altDispersion24h)
|
|
2931
|
+
};
|
|
2349
2932
|
};
|
|
2350
|
-
var
|
|
2351
|
-
const
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
issues.push(
|
|
2379
|
-
`${moduleName}: export { strategyEntries } is missing`
|
|
2380
|
-
);
|
|
2381
|
-
} else {
|
|
2382
|
-
const entryIssues = validateStrategyEntries(
|
|
2383
|
-
moduleName,
|
|
2384
|
-
pluginDefinition.strategyEntries,
|
|
2385
|
-
state
|
|
2386
|
-
);
|
|
2387
|
-
issues.push(...entryIssues);
|
|
2388
|
-
if (entryIssues.length === 0) {
|
|
2389
|
-
registerEntries(
|
|
2390
|
-
pluginDefinition.strategyEntries,
|
|
2391
|
-
moduleName,
|
|
2392
|
-
state
|
|
2393
|
-
);
|
|
2394
|
-
}
|
|
2395
|
-
}
|
|
2396
|
-
}
|
|
2397
|
-
if (indicatorSet.has(moduleName)) {
|
|
2398
|
-
const indicatorPluginDefinition = extractIndicatorPluginDefinition(moduleExport);
|
|
2399
|
-
if (!indicatorPluginDefinition) {
|
|
2400
|
-
issues.push(
|
|
2401
|
-
`${moduleName}: export { indicatorEntries } is missing`
|
|
2402
|
-
);
|
|
2403
|
-
} else {
|
|
2404
|
-
(0, import_indicators.registerIndicatorEntries)(
|
|
2405
|
-
indicatorPluginDefinition.indicatorEntries,
|
|
2406
|
-
moduleName,
|
|
2407
|
-
projectRoot
|
|
2408
|
-
);
|
|
2409
|
-
}
|
|
2410
|
-
}
|
|
2411
|
-
if (!strategySet.has(moduleName) && !indicatorSet.has(moduleName)) {
|
|
2412
|
-
issues.push(`${moduleName}: plugin is not declared in config`);
|
|
2413
|
-
}
|
|
2414
|
-
} catch (error) {
|
|
2415
|
-
issues.push(`${moduleName}: failed to import: ${String(error)}`);
|
|
2416
|
-
}
|
|
2417
|
-
}
|
|
2418
|
-
if (issues.length > 0) {
|
|
2419
|
-
throw new Error(
|
|
2420
|
-
["Invalid TradeJS plugin catalog:", ...issues].join("\n")
|
|
2421
|
-
);
|
|
2422
|
-
}
|
|
2423
|
-
})();
|
|
2933
|
+
var buildCmcGlobalContextFromSignal = (signal) => {
|
|
2934
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2935
|
+
const relative = toRecord(baseContext?.relative);
|
|
2936
|
+
const cmcGlobal = toRecord(relative?.cmcGlobal);
|
|
2937
|
+
if (!cmcGlobal) {
|
|
2938
|
+
return {
|
|
2939
|
+
source: null,
|
|
2940
|
+
available: false,
|
|
2941
|
+
interval: null,
|
|
2942
|
+
asOfTs: null,
|
|
2943
|
+
stale: null,
|
|
2944
|
+
totalMarketCapUsd: null,
|
|
2945
|
+
totalVolumeUsd: null,
|
|
2946
|
+
totalVolumeReportedUsd: null,
|
|
2947
|
+
altMarketCapUsd: null,
|
|
2948
|
+
altVolumeUsd: null,
|
|
2949
|
+
altVolumeReportedUsd: null,
|
|
2950
|
+
btcDominancePct: null,
|
|
2951
|
+
ethDominancePct: null,
|
|
2952
|
+
btcDominanceChange24hPct: null,
|
|
2953
|
+
ethDominanceChange24hPct: null,
|
|
2954
|
+
altMarketCapChange24hPct: null,
|
|
2955
|
+
altVolumeChange24hPct: null,
|
|
2956
|
+
activeCryptocurrencies: null,
|
|
2957
|
+
activeExchanges: null,
|
|
2958
|
+
activeMarketPairs: null,
|
|
2959
|
+
altLiquidityRegime: null
|
|
2960
|
+
};
|
|
2424
2961
|
}
|
|
2425
|
-
|
|
2962
|
+
return {
|
|
2963
|
+
source: String(cmcGlobal.source ?? ""),
|
|
2964
|
+
available: true,
|
|
2965
|
+
interval: typeof cmcGlobal.interval === "string" ? cmcGlobal.interval : null,
|
|
2966
|
+
asOfTs: toFiniteNumber(cmcGlobal.asOfTs),
|
|
2967
|
+
stale: typeof cmcGlobal.stale === "boolean" ? cmcGlobal.stale : null,
|
|
2968
|
+
totalMarketCapUsd: toFiniteNumber(cmcGlobal.totalMarketCapUsd),
|
|
2969
|
+
totalVolumeUsd: toFiniteNumber(cmcGlobal.totalVolumeUsd),
|
|
2970
|
+
totalVolumeReportedUsd: toFiniteNumber(cmcGlobal.totalVolumeReportedUsd),
|
|
2971
|
+
altMarketCapUsd: toFiniteNumber(cmcGlobal.altMarketCapUsd),
|
|
2972
|
+
altVolumeUsd: toFiniteNumber(cmcGlobal.altVolumeUsd),
|
|
2973
|
+
altVolumeReportedUsd: toFiniteNumber(cmcGlobal.altVolumeReportedUsd),
|
|
2974
|
+
btcDominancePct: toFiniteNumber(cmcGlobal.btcDominancePct),
|
|
2975
|
+
ethDominancePct: toFiniteNumber(cmcGlobal.ethDominancePct),
|
|
2976
|
+
btcDominanceChange24hPct: toFiniteNumber(
|
|
2977
|
+
cmcGlobal.btcDominanceChange24hPct
|
|
2978
|
+
),
|
|
2979
|
+
ethDominanceChange24hPct: toFiniteNumber(
|
|
2980
|
+
cmcGlobal.ethDominanceChange24hPct
|
|
2981
|
+
),
|
|
2982
|
+
altMarketCapChange24hPct: toFiniteNumber(
|
|
2983
|
+
cmcGlobal.altMarketCapChange24hPct
|
|
2984
|
+
),
|
|
2985
|
+
altVolumeChange24hPct: toFiniteNumber(cmcGlobal.altVolumeChange24hPct),
|
|
2986
|
+
activeCryptocurrencies: toFiniteNumber(cmcGlobal.activeCryptocurrencies),
|
|
2987
|
+
activeExchanges: toFiniteNumber(cmcGlobal.activeExchanges),
|
|
2988
|
+
activeMarketPairs: toFiniteNumber(cmcGlobal.activeMarketPairs),
|
|
2989
|
+
altLiquidityRegime: typeof cmcGlobal.altLiquidityRegime === "string" ? cmcGlobal.altLiquidityRegime : null
|
|
2990
|
+
};
|
|
2426
2991
|
};
|
|
2427
|
-
var
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2992
|
+
var buildCmcReferenceAssetsContextFromSignal = (signal) => {
|
|
2993
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2994
|
+
const relative = toRecord(baseContext?.relative);
|
|
2995
|
+
const cmcReferenceAssets = toRecord(relative?.cmcReferenceAssets);
|
|
2996
|
+
if (!cmcReferenceAssets) {
|
|
2997
|
+
return {
|
|
2998
|
+
source: null,
|
|
2999
|
+
available: false,
|
|
3000
|
+
interval: null,
|
|
3001
|
+
asOfTs: null,
|
|
3002
|
+
stale: null,
|
|
3003
|
+
btcMarketCapUsd: null,
|
|
3004
|
+
ethMarketCapUsd: null,
|
|
3005
|
+
btcVolumeUsd: null,
|
|
3006
|
+
ethVolumeUsd: null,
|
|
3007
|
+
btcVolumeToMarketCap: null,
|
|
3008
|
+
ethVolumeToMarketCap: null,
|
|
3009
|
+
ethBtcMarketCapRatio: null,
|
|
3010
|
+
ethBtcMarketCapRatioChange24hPct: null,
|
|
3011
|
+
ethVsBtcVolumeRatio: null,
|
|
3012
|
+
referenceLiquidityRegime: null
|
|
3013
|
+
};
|
|
2431
3014
|
}
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
3015
|
+
return {
|
|
3016
|
+
source: String(cmcReferenceAssets.source ?? ""),
|
|
3017
|
+
available: true,
|
|
3018
|
+
interval: typeof cmcReferenceAssets.interval === "string" ? cmcReferenceAssets.interval : null,
|
|
3019
|
+
asOfTs: toFiniteNumber(cmcReferenceAssets.asOfTs),
|
|
3020
|
+
stale: typeof cmcReferenceAssets.stale === "boolean" ? cmcReferenceAssets.stale : null,
|
|
3021
|
+
btcMarketCapUsd: toFiniteNumber(cmcReferenceAssets.btcMarketCapUsd),
|
|
3022
|
+
ethMarketCapUsd: toFiniteNumber(cmcReferenceAssets.ethMarketCapUsd),
|
|
3023
|
+
btcVolumeUsd: toFiniteNumber(cmcReferenceAssets.btcVolumeUsd),
|
|
3024
|
+
ethVolumeUsd: toFiniteNumber(cmcReferenceAssets.ethVolumeUsd),
|
|
3025
|
+
btcVolumeToMarketCap: toFiniteNumber(
|
|
3026
|
+
cmcReferenceAssets.btcVolumeToMarketCap
|
|
3027
|
+
),
|
|
3028
|
+
ethVolumeToMarketCap: toFiniteNumber(
|
|
3029
|
+
cmcReferenceAssets.ethVolumeToMarketCap
|
|
3030
|
+
),
|
|
3031
|
+
ethBtcMarketCapRatio: toFiniteNumber(
|
|
3032
|
+
cmcReferenceAssets.ethBtcMarketCapRatio
|
|
3033
|
+
),
|
|
3034
|
+
ethBtcMarketCapRatioChange24hPct: toFiniteNumber(
|
|
3035
|
+
cmcReferenceAssets.ethBtcMarketCapRatioChange24hPct
|
|
3036
|
+
),
|
|
3037
|
+
ethVsBtcVolumeRatio: toFiniteNumber(cmcReferenceAssets.ethVsBtcVolumeRatio),
|
|
3038
|
+
referenceLiquidityRegime: typeof cmcReferenceAssets.referenceLiquidityRegime === "string" ? cmcReferenceAssets.referenceLiquidityRegime : null
|
|
3039
|
+
};
|
|
2435
3040
|
};
|
|
2436
|
-
var
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
3041
|
+
var buildCmcExchangeLiquidityContextFromSignal = (signal) => {
|
|
3042
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3043
|
+
const relative = toRecord(baseContext?.relative);
|
|
3044
|
+
const cmcExchangeLiquidity = toRecord(relative?.cmcExchangeLiquidity);
|
|
3045
|
+
if (!cmcExchangeLiquidity) {
|
|
3046
|
+
return {
|
|
3047
|
+
source: null,
|
|
3048
|
+
available: false,
|
|
3049
|
+
interval: null,
|
|
3050
|
+
asOfTs: null,
|
|
3051
|
+
stale: null,
|
|
3052
|
+
exchangesCount: null,
|
|
3053
|
+
totalVolumeUsd: null,
|
|
3054
|
+
totalVolumeChange24hPct: null,
|
|
3055
|
+
binanceVolumeUsd: null,
|
|
3056
|
+
binanceVolumeShare: null,
|
|
3057
|
+
topExchangeVolumeShare: null,
|
|
3058
|
+
liquidityRegime: null
|
|
3059
|
+
};
|
|
2440
3060
|
}
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
3061
|
+
return {
|
|
3062
|
+
source: String(cmcExchangeLiquidity.source ?? ""),
|
|
3063
|
+
available: true,
|
|
3064
|
+
interval: typeof cmcExchangeLiquidity.interval === "string" ? cmcExchangeLiquidity.interval : null,
|
|
3065
|
+
asOfTs: toFiniteNumber(cmcExchangeLiquidity.asOfTs),
|
|
3066
|
+
stale: typeof cmcExchangeLiquidity.stale === "boolean" ? cmcExchangeLiquidity.stale : null,
|
|
3067
|
+
exchangesCount: toFiniteNumber(cmcExchangeLiquidity.exchangesCount),
|
|
3068
|
+
totalVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.totalVolumeUsd),
|
|
3069
|
+
totalVolumeChange24hPct: toFiniteNumber(
|
|
3070
|
+
cmcExchangeLiquidity.totalVolumeChange24hPct
|
|
3071
|
+
),
|
|
3072
|
+
binanceVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeUsd),
|
|
3073
|
+
binanceVolumeShare: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeShare),
|
|
3074
|
+
topExchangeVolumeShare: toFiniteNumber(
|
|
3075
|
+
cmcExchangeLiquidity.topExchangeVolumeShare
|
|
3076
|
+
),
|
|
3077
|
+
liquidityRegime: typeof cmcExchangeLiquidity.liquidityRegime === "string" ? cmcExchangeLiquidity.liquidityRegime : null
|
|
3078
|
+
};
|
|
2444
3079
|
};
|
|
2445
|
-
var
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
3080
|
+
var buildCmcFearGreedContextFromSignal = (signal) => {
|
|
3081
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3082
|
+
const relative = toRecord(baseContext?.relative);
|
|
3083
|
+
const cmcFearGreed = toRecord(relative?.cmcFearGreed);
|
|
3084
|
+
if (!cmcFearGreed) {
|
|
3085
|
+
return {
|
|
3086
|
+
source: null,
|
|
3087
|
+
available: false,
|
|
3088
|
+
interval: null,
|
|
3089
|
+
asOfTs: null,
|
|
3090
|
+
stale: null,
|
|
3091
|
+
value: null,
|
|
3092
|
+
valueChange24h: null,
|
|
3093
|
+
valueChange7d: null,
|
|
3094
|
+
classification: null,
|
|
3095
|
+
sentimentRegime: null
|
|
3096
|
+
};
|
|
2449
3097
|
}
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
3098
|
+
return {
|
|
3099
|
+
source: String(cmcFearGreed.source ?? ""),
|
|
3100
|
+
available: true,
|
|
3101
|
+
interval: typeof cmcFearGreed.interval === "string" ? cmcFearGreed.interval : null,
|
|
3102
|
+
asOfTs: toFiniteNumber(cmcFearGreed.asOfTs),
|
|
3103
|
+
stale: typeof cmcFearGreed.stale === "boolean" ? cmcFearGreed.stale : null,
|
|
3104
|
+
value: toFiniteNumber(cmcFearGreed.value),
|
|
3105
|
+
valueChange24h: toFiniteNumber(cmcFearGreed.valueChange24h),
|
|
3106
|
+
valueChange7d: toFiniteNumber(cmcFearGreed.valueChange7d),
|
|
3107
|
+
classification: typeof cmcFearGreed.classification === "string" ? cmcFearGreed.classification : null,
|
|
3108
|
+
sentimentRegime: typeof cmcFearGreed.sentimentRegime === "string" ? cmcFearGreed.sentimentRegime : null
|
|
3109
|
+
};
|
|
2453
3110
|
};
|
|
2454
|
-
var
|
|
2455
|
-
|
|
2456
|
-
|
|
3111
|
+
var buildCmcIndexesContextFromSignal = (signal) => {
|
|
3112
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3113
|
+
const relative = toRecord(baseContext?.relative);
|
|
3114
|
+
const cmcIndexes = toRecord(relative?.cmcIndexes);
|
|
3115
|
+
if (!cmcIndexes) {
|
|
3116
|
+
return {
|
|
3117
|
+
source: null,
|
|
3118
|
+
available: false,
|
|
3119
|
+
interval: null,
|
|
3120
|
+
asOfTs: null,
|
|
3121
|
+
stale: null,
|
|
3122
|
+
cmc100Value: null,
|
|
3123
|
+
cmc100Change24hPct: null,
|
|
3124
|
+
cmc100TopConstituentSymbol: null,
|
|
3125
|
+
cmc100TopConstituentWeightPct: null,
|
|
3126
|
+
cmc20Value: null,
|
|
3127
|
+
cmc20Change24hPct: null,
|
|
3128
|
+
cmc20TopConstituentSymbol: null,
|
|
3129
|
+
cmc20TopConstituentWeightPct: null,
|
|
3130
|
+
cmc20ToCmc100Ratio: null,
|
|
3131
|
+
cmc20ToCmc100RatioChange24hPct: null,
|
|
3132
|
+
indexRegime: null
|
|
3133
|
+
};
|
|
2457
3134
|
}
|
|
2458
|
-
|
|
2459
|
-
|
|
3135
|
+
return {
|
|
3136
|
+
source: String(cmcIndexes.source ?? ""),
|
|
3137
|
+
available: true,
|
|
3138
|
+
interval: typeof cmcIndexes.interval === "string" ? cmcIndexes.interval : null,
|
|
3139
|
+
asOfTs: toFiniteNumber(cmcIndexes.asOfTs),
|
|
3140
|
+
stale: typeof cmcIndexes.stale === "boolean" ? cmcIndexes.stale : null,
|
|
3141
|
+
cmc100Value: toFiniteNumber(cmcIndexes.cmc100Value),
|
|
3142
|
+
cmc100Change24hPct: toFiniteNumber(cmcIndexes.cmc100Change24hPct),
|
|
3143
|
+
cmc100TopConstituentSymbol: typeof cmcIndexes.cmc100TopConstituentSymbol === "string" ? cmcIndexes.cmc100TopConstituentSymbol : null,
|
|
3144
|
+
cmc100TopConstituentWeightPct: toFiniteNumber(
|
|
3145
|
+
cmcIndexes.cmc100TopConstituentWeightPct
|
|
3146
|
+
),
|
|
3147
|
+
cmc20Value: toFiniteNumber(cmcIndexes.cmc20Value),
|
|
3148
|
+
cmc20Change24hPct: toFiniteNumber(cmcIndexes.cmc20Change24hPct),
|
|
3149
|
+
cmc20TopConstituentSymbol: typeof cmcIndexes.cmc20TopConstituentSymbol === "string" ? cmcIndexes.cmc20TopConstituentSymbol : null,
|
|
3150
|
+
cmc20TopConstituentWeightPct: toFiniteNumber(
|
|
3151
|
+
cmcIndexes.cmc20TopConstituentWeightPct
|
|
3152
|
+
),
|
|
3153
|
+
cmc20ToCmc100Ratio: toFiniteNumber(cmcIndexes.cmc20ToCmc100Ratio),
|
|
3154
|
+
cmc20ToCmc100RatioChange24hPct: toFiniteNumber(
|
|
3155
|
+
cmcIndexes.cmc20ToCmc100RatioChange24hPct
|
|
3156
|
+
),
|
|
3157
|
+
indexRegime: typeof cmcIndexes.indexRegime === "string" ? cmcIndexes.indexRegime : null
|
|
3158
|
+
};
|
|
3159
|
+
};
|
|
3160
|
+
var buildReferenceTradeFlowContextFromSignal = (signal) => {
|
|
3161
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3162
|
+
const relative = toRecord(baseContext?.relative);
|
|
3163
|
+
const refs = toRecord(relative?.referenceTradeFlow);
|
|
3164
|
+
const primaryReferenceSymbol = typeof refs?.primaryReferenceSymbol === "string" ? refs.primaryReferenceSymbol : null;
|
|
3165
|
+
const tradeFlowBySymbol = toRecord(refs?.tradeFlowBySymbol);
|
|
3166
|
+
const primaryTradeFlow = primaryReferenceSymbol != null ? toRecord(tradeFlowBySymbol?.[primaryReferenceSymbol]) : null;
|
|
3167
|
+
if (!refs) {
|
|
3168
|
+
return {
|
|
3169
|
+
source: null,
|
|
3170
|
+
available: false,
|
|
3171
|
+
primaryReferenceSymbol: null,
|
|
3172
|
+
referenceSymbols: [],
|
|
3173
|
+
primaryTradeFlowBuyPressurePct: null,
|
|
3174
|
+
primaryTradeFlowStale: null
|
|
3175
|
+
};
|
|
3176
|
+
}
|
|
3177
|
+
return {
|
|
3178
|
+
source: String(refs.source ?? ""),
|
|
3179
|
+
available: true,
|
|
3180
|
+
primaryReferenceSymbol,
|
|
3181
|
+
referenceSymbols: Array.isArray(refs.referenceSymbols) ? refs.referenceSymbols.map(String) : [],
|
|
3182
|
+
primaryTradeFlowBuyPressurePct: toFiniteNumber(
|
|
3183
|
+
primaryTradeFlow?.buyPressurePct
|
|
3184
|
+
),
|
|
3185
|
+
primaryTradeFlowStale: typeof primaryTradeFlow?.stale === "boolean" ? primaryTradeFlow.stale : null
|
|
3186
|
+
};
|
|
2460
3187
|
};
|
|
2461
|
-
var
|
|
2462
|
-
{
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
3188
|
+
var buildAiMarketContext = (signal) => ({
|
|
3189
|
+
execution: {
|
|
3190
|
+
binanceCoinbaseSpread: buildSpreadContextFromSignal(signal)
|
|
3191
|
+
},
|
|
3192
|
+
participation: {
|
|
3193
|
+
trueDelta: buildTrueDeltaContextFromSignal(signal),
|
|
3194
|
+
tradeFlow: buildTradeFlowContextFromSignal(signal)
|
|
3195
|
+
},
|
|
3196
|
+
relative: {
|
|
3197
|
+
marketBreadth: buildMarketBreadthContextFromSignal(signal),
|
|
3198
|
+
marketBreadths: buildMarketBreadthsContextFromSignal(signal),
|
|
3199
|
+
targetVsBtc: buildTargetVsBtcContextFromSignal(signal),
|
|
3200
|
+
targetVsEth: buildTargetVsEthContextFromSignal(signal),
|
|
3201
|
+
btcAltRegime: buildBtcAltRegimeContextFromSignal(signal),
|
|
3202
|
+
cmcGlobal: buildCmcGlobalContextFromSignal(signal),
|
|
3203
|
+
cmcReferenceAssets: buildCmcReferenceAssetsContextFromSignal(signal),
|
|
3204
|
+
cmcExchangeLiquidity: buildCmcExchangeLiquidityContextFromSignal(signal),
|
|
3205
|
+
cmcFearGreed: buildCmcFearGreedContextFromSignal(signal),
|
|
3206
|
+
cmcIndexes: buildCmcIndexesContextFromSignal(signal),
|
|
3207
|
+
referenceTradeFlow: buildReferenceTradeFlowContextFromSignal(signal)
|
|
2477
3208
|
}
|
|
2478
|
-
);
|
|
3209
|
+
});
|
|
2479
3210
|
|
|
2480
3211
|
// src/strategy/policyProfiles.ts
|
|
2481
3212
|
var profileMatches = (profile, universe, assetClass) => {
|
|
@@ -2577,12 +3308,61 @@ var buildBaseAiPayload = (signal) => {
|
|
|
2577
3308
|
};
|
|
2578
3309
|
};
|
|
2579
3310
|
var defaultAiAdapter = {};
|
|
3311
|
+
var tickerProfileFeatureIds = new Set(
|
|
3312
|
+
import_aiFeatures.TICKER_PROFILE_AI_FEATURES.map(({ id }) => id)
|
|
3313
|
+
);
|
|
2580
3314
|
var getStrategyAiAdapter = (strategy, profileId) => getStrategyProfileAiAdapter(getStrategyManifest(strategy), profileId) ?? defaultAiAdapter;
|
|
2581
3315
|
var getSignalAiAdapter = (signal) => getStrategyAiAdapter(signal.strategy, signal.policyProfileId);
|
|
2582
3316
|
var buildAiPayloadByStrategy = (signal) => {
|
|
2583
3317
|
const basePayload = buildBaseAiPayload(signal);
|
|
2584
3318
|
const adapter = getSignalAiAdapter(signal);
|
|
2585
|
-
|
|
3319
|
+
const payload = adapter.buildPayload?.({ signal, basePayload }) ?? basePayload;
|
|
3320
|
+
const custom = getStrategyManifest(signal.strategy)?.aiFeatures ?? [];
|
|
3321
|
+
const frozen = signal.aiFeatureReplay?.snapshot;
|
|
3322
|
+
if (frozen) (0, import_aiFeatures.validateAiFeatureSnapshot)(frozen);
|
|
3323
|
+
const tickerProfile = signal.additionalIndicators?.tickerProfile ?? signal.additionalIndicators?.baseContext?.tickerProfile;
|
|
3324
|
+
const hasProfile = Boolean(
|
|
3325
|
+
tickerProfile || frozen?.definitions.some(({ id }) => tickerProfileFeatureIds.has(id))
|
|
3326
|
+
);
|
|
3327
|
+
const indicatorFeatures = (0, import_indicators2.getRegisteredIndicatorEntries)(
|
|
3328
|
+
getTradejsProjectCwd()
|
|
3329
|
+
).flatMap((entry) => entry.indicator.enabled ? entry.aiFeatures ?? [] : []);
|
|
3330
|
+
const hasPortfolio = Boolean(
|
|
3331
|
+
signal.additionalIndicators?.portfolioContext || frozen?.definitions.some(
|
|
3332
|
+
({ id }) => import_aiFeatures.PORTFOLIO_AI_FEATURES.some((d) => d.id === id)
|
|
3333
|
+
)
|
|
3334
|
+
);
|
|
3335
|
+
const definitions = [
|
|
3336
|
+
...hasPortfolio ? import_aiFeatures.PORTFOLIO_AI_FEATURES : [],
|
|
3337
|
+
...hasProfile ? import_aiFeatures.TICKER_PROFILE_AI_FEATURES : [],
|
|
3338
|
+
...indicatorFeatures,
|
|
3339
|
+
...custom
|
|
3340
|
+
];
|
|
3341
|
+
if (!definitions.length && !signal.aiFeatureReplay?.snapshot) return payload;
|
|
3342
|
+
(0, import_aiFeatures.validateAiFeatureDefinitions)(definitions);
|
|
3343
|
+
if (signal.aiFeatureReplay && !frozen) {
|
|
3344
|
+
throw new Error(
|
|
3345
|
+
"Dataset has no frozen AI features; regenerate the decision-time export"
|
|
3346
|
+
);
|
|
3347
|
+
}
|
|
3348
|
+
if (frozen && JSON.stringify(frozen.definitions) !== JSON.stringify(definitions)) {
|
|
3349
|
+
throw new Error(
|
|
3350
|
+
"AI feature definition changed; regenerate the decision-time export"
|
|
3351
|
+
);
|
|
3352
|
+
}
|
|
3353
|
+
return {
|
|
3354
|
+
...payload,
|
|
3355
|
+
featureSnapshot: frozen ?? (0, import_aiFeatures.projectAiFeatures)(definitions, {
|
|
3356
|
+
signal: tickerProfile ? {
|
|
3357
|
+
...signal,
|
|
3358
|
+
additionalIndicators: {
|
|
3359
|
+
...signal.additionalIndicators,
|
|
3360
|
+
tickerProfile
|
|
3361
|
+
}
|
|
3362
|
+
} : signal,
|
|
3363
|
+
payload
|
|
3364
|
+
})
|
|
3365
|
+
};
|
|
2586
3366
|
};
|
|
2587
3367
|
var buildAiSystemPromptAddonByStrategy = (signal) => getSignalAiAdapter(signal).buildSystemPromptAddon?.({ signal }) ?? "";
|
|
2588
3368
|
var buildAiHumanPromptAddonByStrategy = (signal, payload) => getSignalAiAdapter(signal).buildHumanPromptAddon?.({
|
|
@@ -2993,355 +3773,107 @@ var runAiPrompt = async ({ systemPrompt, humanPrompt }, options = {}) => {
|
|
|
2993
3773
|
model: options.model,
|
|
2994
3774
|
messages: [
|
|
2995
3775
|
{ role: "system", content: systemPrompt },
|
|
2996
|
-
{
|
|
2997
|
-
role: "system",
|
|
2998
|
-
content: `Write all user-visible text fields in ${responseLanguage}. Keep field names and JSON syntax unchanged.`
|
|
2999
|
-
},
|
|
3000
|
-
{ role: "user", content: humanPrompt }
|
|
3001
|
-
]
|
|
3002
|
-
});
|
|
3003
|
-
const parsed = parseAIResponse(response.content);
|
|
3004
|
-
const normalized = normalizeAnalysis(parsed);
|
|
3005
|
-
if (!options.signal) {
|
|
3006
|
-
return normalized;
|
|
3007
|
-
}
|
|
3008
|
-
return postProcessAiAnalysisByStrategy(
|
|
3009
|
-
options.signal,
|
|
3010
|
-
normalized,
|
|
3011
|
-
options.payload
|
|
3012
|
-
);
|
|
3013
|
-
};
|
|
3014
|
-
var runAiPromptLocal = async (signal, options = {}) => {
|
|
3015
|
-
const payload = options.payload ?? buildAiPayload(signal);
|
|
3016
|
-
const gateContext = getDeterministicAiGateContext(payload);
|
|
3017
|
-
const signalDirection = getSignalDirection(signal);
|
|
3018
|
-
const deterministicQuality = getDeterministicQuality(gateContext);
|
|
3019
|
-
const approvalAllowedNow = typeof gateContext?.approvalAllowedNow === "boolean" ? gateContext.approvalAllowedNow : deterministicQuality >= 4;
|
|
3020
|
-
return postProcessLocalAiAnalysisByStrategy(
|
|
3021
|
-
signal,
|
|
3022
|
-
{
|
|
3023
|
-
direction: approvalAllowedNow ? signalDirection : null,
|
|
3024
|
-
quality: deterministicQuality,
|
|
3025
|
-
needRetest: !approvalAllowedNow,
|
|
3026
|
-
retestPrice: null,
|
|
3027
|
-
takeProfitPrice: approvalAllowedNow ? signal.prices?.takeProfitPrice ?? null : null,
|
|
3028
|
-
stopLossPrice: approvalAllowedNow ? signal.prices?.stopLossPrice ?? null : null
|
|
3029
|
-
},
|
|
3030
|
-
payload
|
|
3031
|
-
);
|
|
3032
|
-
};
|
|
3033
|
-
var askAI = async (signal, options = {}) => {
|
|
3034
|
-
const { symbol } = signal;
|
|
3035
|
-
const payload = buildAiPayload(signal);
|
|
3036
|
-
const content = await runAiPrompt(
|
|
3037
|
-
{
|
|
3038
|
-
systemPrompt: buildAiSystemPrompt(signal),
|
|
3039
|
-
humanPrompt: buildAiHumanPrompt(signal, payload)
|
|
3040
|
-
},
|
|
3041
|
-
{
|
|
3042
|
-
...options,
|
|
3043
|
-
signal,
|
|
3044
|
-
payload
|
|
3045
|
-
}
|
|
3046
|
-
);
|
|
3047
|
-
await (0, import_redis.setData)(import_redis.redisKeys.analysis(symbol, signal.signalId), content);
|
|
3048
|
-
return content;
|
|
3049
|
-
};
|
|
3050
|
-
|
|
3051
|
-
// src/strategyAdapters/ml.ts
|
|
3052
|
-
var defaultMlAdapter = {
|
|
3053
|
-
normalizeStrategyConfig: (strategyConfig) => strategyConfig
|
|
3054
|
-
};
|
|
3055
|
-
var getStrategyMlAdapter = (strategy, profileId) => {
|
|
3056
|
-
const strategyAdapter = getStrategyProfileMlAdapter(
|
|
3057
|
-
getStrategyManifest(strategy),
|
|
3058
|
-
profileId
|
|
3059
|
-
);
|
|
3060
|
-
if (!strategyAdapter) return defaultMlAdapter;
|
|
3061
|
-
return {
|
|
3062
|
-
...defaultMlAdapter,
|
|
3063
|
-
...strategyAdapter
|
|
3064
|
-
};
|
|
3065
|
-
};
|
|
3066
|
-
|
|
3067
|
-
// src/mlPayload.ts
|
|
3068
|
-
var normalizeStrategyConfig = (strategyConfig, strategyName, profileId) => {
|
|
3069
|
-
return getStrategyMlAdapter(
|
|
3070
|
-
strategyName,
|
|
3071
|
-
profileId
|
|
3072
|
-
).normalizeStrategyConfig?.(strategyConfig);
|
|
3073
|
-
};
|
|
3074
|
-
var buildMlPayload = (payload) => {
|
|
3075
|
-
const strategyName = payload.signal?.strategy ?? payload.context?.strategyName;
|
|
3076
|
-
const profileId = payload.signal?.policyProfileId;
|
|
3077
|
-
const mlAdapter = getStrategyMlAdapter(strategyName, profileId);
|
|
3078
|
-
const normalizedSignal = mlAdapter.normalizeSignal?.(payload.signal) ?? payload.signal;
|
|
3079
|
-
const nextSignal = {
|
|
3080
|
-
...normalizedSignal,
|
|
3081
|
-
indicators: {
|
|
3082
|
-
...normalizedSignal?.indicators ?? {}
|
|
3083
|
-
}
|
|
3084
|
-
};
|
|
3085
|
-
const nextContext = payload.context ? {
|
|
3086
|
-
...payload.context,
|
|
3087
|
-
strategyConfig: normalizeStrategyConfig(
|
|
3088
|
-
payload.context.strategyConfig,
|
|
3089
|
-
strategyName,
|
|
3090
|
-
profileId
|
|
3091
|
-
)
|
|
3092
|
-
} : void 0;
|
|
3093
|
-
return {
|
|
3094
|
-
signal: nextSignal,
|
|
3095
|
-
context: nextContext
|
|
3096
|
-
};
|
|
3097
|
-
};
|
|
3098
|
-
|
|
3099
|
-
// src/runtimeJournal.ts
|
|
3100
|
-
var import_node_crypto = require("crypto");
|
|
3101
|
-
var import_constants = require("@tradejs/core/constants");
|
|
3102
|
-
var import_time = require("@tradejs/core/time");
|
|
3103
|
-
var import_trade = require("@tradejs/core/trade");
|
|
3104
|
-
var import_logger3 = require("@tradejs/infra/logger");
|
|
3105
|
-
var import_redis2 = require("@tradejs/infra/redis");
|
|
3106
|
-
var now = () => Date.now();
|
|
3107
|
-
var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
|
|
3108
|
-
var calculateClosedPnl = ({
|
|
3109
|
-
direction,
|
|
3110
|
-
entryPrice,
|
|
3111
|
-
exitPrice,
|
|
3112
|
-
qty
|
|
3113
|
-
}) => {
|
|
3114
|
-
const pnl = direction === "LONG" ? (exitPrice - entryPrice) * qty : (entryPrice - exitPrice) * qty;
|
|
3115
|
-
return Number.isFinite(pnl) ? pnl : null;
|
|
3116
|
-
};
|
|
3117
|
-
var createRuntimeOrderId = (strategy) => {
|
|
3118
|
-
const prefix = (0, import_trade.createRuntimeOrderLinkPrefix)(strategy);
|
|
3119
|
-
if (prefix === "tjs-") {
|
|
3120
|
-
return `tjs-${(0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 24).toLowerCase()}`;
|
|
3121
|
-
}
|
|
3122
|
-
return `${prefix}${toRandomOrderSuffix()}`;
|
|
3123
|
-
};
|
|
3124
|
-
var recordRuntimeTradeOpen = async (params) => {
|
|
3125
|
-
const { userName } = params;
|
|
3126
|
-
if (!userName) {
|
|
3127
|
-
return null;
|
|
3128
|
-
}
|
|
3129
|
-
const record2 = {
|
|
3130
|
-
...params,
|
|
3131
|
-
entryCount: 1,
|
|
3132
|
-
lastEntryPrice: params.entryPrice,
|
|
3133
|
-
lastEntryQty: params.qty,
|
|
3134
|
-
lastEntryTimestamp: params.entryTimestamp,
|
|
3135
|
-
status: "active",
|
|
3136
|
-
currentPrice: params.entryPrice,
|
|
3137
|
-
currentPnl: 0,
|
|
3138
|
-
closedPnl: null,
|
|
3139
|
-
exitPrice: null,
|
|
3140
|
-
exitTimestamp: null,
|
|
3141
|
-
lastSyncedAt: now()
|
|
3142
|
-
};
|
|
3143
|
-
const dayKey = (0, import_time.getRuntimeStorageDayKey)(record2.entryTimestamp);
|
|
3144
|
-
const runtimeScopeId = record2.deploymentId ?? record2.accountId;
|
|
3145
|
-
try {
|
|
3146
|
-
await Promise.all([
|
|
3147
|
-
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, record2.orderId), record2, {
|
|
3148
|
-
expire: 0
|
|
3149
|
-
}),
|
|
3150
|
-
(0, import_redis2.setHashJsonField)(
|
|
3151
|
-
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
3152
|
-
record2.orderId,
|
|
3153
|
-
record2,
|
|
3154
|
-
{ expire: 0 }
|
|
3155
|
-
),
|
|
3156
|
-
(0, import_redis2.setData)(
|
|
3157
|
-
import_redis2.redisKeys.runtimeActiveTrade(userName, record2.symbol, runtimeScopeId),
|
|
3158
|
-
{ orderId: record2.orderId },
|
|
3159
|
-
{ expire: 0 }
|
|
3160
|
-
)
|
|
3161
|
-
]);
|
|
3162
|
-
} catch (error) {
|
|
3163
|
-
import_logger3.logger.error(
|
|
3164
|
-
"runtime trade open journal failed: %s %s",
|
|
3165
|
-
record2.symbol,
|
|
3166
|
-
error?.message || String(error)
|
|
3167
|
-
);
|
|
3168
|
-
}
|
|
3169
|
-
return record2;
|
|
3170
|
-
};
|
|
3171
|
-
var recordRuntimeTradeIncrease = async (params) => {
|
|
3172
|
-
const {
|
|
3173
|
-
userName,
|
|
3174
|
-
strategy,
|
|
3175
|
-
symbol,
|
|
3176
|
-
direction,
|
|
3177
|
-
resultingQty,
|
|
3178
|
-
resultingEntryPrice,
|
|
3179
|
-
addedQty,
|
|
3180
|
-
addedEntryPrice,
|
|
3181
|
-
entryTimestamp,
|
|
3182
|
-
fee,
|
|
3183
|
-
accountId,
|
|
3184
|
-
deploymentId
|
|
3185
|
-
} = params;
|
|
3186
|
-
if (!userName) {
|
|
3187
|
-
return null;
|
|
3188
|
-
}
|
|
3189
|
-
const existing = await getActiveRuntimeTrade({
|
|
3190
|
-
userName,
|
|
3191
|
-
symbol,
|
|
3192
|
-
accountId,
|
|
3193
|
-
deploymentId
|
|
3776
|
+
{
|
|
3777
|
+
role: "system",
|
|
3778
|
+
content: `Write all user-visible text fields in ${responseLanguage}. Keep field names and JSON syntax unchanged.`
|
|
3779
|
+
},
|
|
3780
|
+
{ role: "user", content: humanPrompt }
|
|
3781
|
+
]
|
|
3194
3782
|
});
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
const openFee = (existing.openFee ?? existing.fee ?? 0) + addedFee;
|
|
3200
|
-
const totalFee = (existing.totalFee ?? existing.fee ?? 0) + addedFee;
|
|
3201
|
-
const next = {
|
|
3202
|
-
...existing,
|
|
3203
|
-
...params.orderLeverage ? { orderLeverage: params.orderLeverage } : {},
|
|
3204
|
-
qty: resultingQty,
|
|
3205
|
-
entryPrice: resultingEntryPrice,
|
|
3206
|
-
entryCount: Math.max(1, existing.entryCount ?? 1) + 1,
|
|
3207
|
-
lastEntryPrice: addedEntryPrice,
|
|
3208
|
-
lastEntryQty: addedQty,
|
|
3209
|
-
lastEntryTimestamp: entryTimestamp,
|
|
3210
|
-
currentPrice: resultingEntryPrice,
|
|
3211
|
-
currentPnl: 0,
|
|
3212
|
-
fee: openFee,
|
|
3213
|
-
openFee,
|
|
3214
|
-
totalFee,
|
|
3215
|
-
lastSyncedAt: now()
|
|
3216
|
-
};
|
|
3217
|
-
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
3218
|
-
try {
|
|
3219
|
-
await Promise.all([
|
|
3220
|
-
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, existing.orderId), next, {
|
|
3221
|
-
expire: 0
|
|
3222
|
-
}),
|
|
3223
|
-
(0, import_redis2.setHashJsonField)(
|
|
3224
|
-
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
3225
|
-
existing.orderId,
|
|
3226
|
-
next,
|
|
3227
|
-
{ expire: 0 }
|
|
3228
|
-
)
|
|
3229
|
-
]);
|
|
3230
|
-
} catch (error) {
|
|
3231
|
-
import_logger3.logger.error(
|
|
3232
|
-
"runtime trade increase journal failed: %s %s",
|
|
3233
|
-
symbol,
|
|
3234
|
-
error?.message || String(error)
|
|
3235
|
-
);
|
|
3783
|
+
const parsed = parseAIResponse(response.content);
|
|
3784
|
+
const normalized = normalizeAnalysis(parsed);
|
|
3785
|
+
if (!options.signal) {
|
|
3786
|
+
return normalized;
|
|
3236
3787
|
}
|
|
3237
|
-
return
|
|
3788
|
+
return postProcessAiAnalysisByStrategy(
|
|
3789
|
+
options.signal,
|
|
3790
|
+
normalized,
|
|
3791
|
+
options.payload
|
|
3792
|
+
);
|
|
3238
3793
|
};
|
|
3239
|
-
var
|
|
3240
|
-
const
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
const
|
|
3245
|
-
|
|
3246
|
-
|
|
3794
|
+
var runAiPromptLocal = async (signal, options = {}) => {
|
|
3795
|
+
const payload = options.payload ?? buildAiPayload(signal);
|
|
3796
|
+
const gateContext = getDeterministicAiGateContext(payload);
|
|
3797
|
+
const signalDirection = getSignalDirection(signal);
|
|
3798
|
+
const deterministicQuality = getDeterministicQuality(gateContext);
|
|
3799
|
+
const approvalAllowedNow = typeof gateContext?.approvalAllowedNow === "boolean" ? gateContext.approvalAllowedNow : deterministicQuality >= 4;
|
|
3800
|
+
return postProcessLocalAiAnalysisByStrategy(
|
|
3801
|
+
signal,
|
|
3802
|
+
{
|
|
3803
|
+
direction: approvalAllowedNow ? signalDirection : null,
|
|
3804
|
+
quality: deterministicQuality,
|
|
3805
|
+
needRetest: !approvalAllowedNow,
|
|
3806
|
+
retestPrice: null,
|
|
3807
|
+
takeProfitPrice: approvalAllowedNow ? signal.prices?.takeProfitPrice ?? null : null,
|
|
3808
|
+
stopLossPrice: approvalAllowedNow ? signal.prices?.stopLossPrice ?? null : null
|
|
3809
|
+
},
|
|
3810
|
+
payload
|
|
3247
3811
|
);
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
const
|
|
3253
|
-
|
|
3254
|
-
|
|
3812
|
+
};
|
|
3813
|
+
var askAI = async (signal, options = {}) => {
|
|
3814
|
+
const { symbol } = signal;
|
|
3815
|
+
const payload = buildAiPayload(signal);
|
|
3816
|
+
const content = await runAiPrompt(
|
|
3817
|
+
{
|
|
3818
|
+
systemPrompt: buildAiSystemPrompt(signal),
|
|
3819
|
+
humanPrompt: buildAiHumanPrompt(signal, payload)
|
|
3820
|
+
},
|
|
3821
|
+
{
|
|
3822
|
+
...options,
|
|
3823
|
+
signal,
|
|
3824
|
+
payload
|
|
3825
|
+
}
|
|
3255
3826
|
);
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
import_redis2.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
|
|
3259
|
-
);
|
|
3260
|
-
return null;
|
|
3261
|
-
}
|
|
3262
|
-
return existing;
|
|
3827
|
+
await (0, import_redis3.setData)(import_redis3.redisKeys.analysis(symbol, signal.signalId), content);
|
|
3828
|
+
return content;
|
|
3263
3829
|
};
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
}
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
const
|
|
3293
|
-
const
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
}
|
|
3299
|
-
const
|
|
3300
|
-
...
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3830
|
+
|
|
3831
|
+
// src/strategyAdapters/ml.ts
|
|
3832
|
+
var defaultMlAdapter = {
|
|
3833
|
+
normalizeStrategyConfig: (strategyConfig) => strategyConfig
|
|
3834
|
+
};
|
|
3835
|
+
var getStrategyMlAdapter = (strategy, profileId) => {
|
|
3836
|
+
const strategyAdapter = getStrategyProfileMlAdapter(
|
|
3837
|
+
getStrategyManifest(strategy),
|
|
3838
|
+
profileId
|
|
3839
|
+
);
|
|
3840
|
+
if (!strategyAdapter) return defaultMlAdapter;
|
|
3841
|
+
return {
|
|
3842
|
+
...defaultMlAdapter,
|
|
3843
|
+
...strategyAdapter
|
|
3844
|
+
};
|
|
3845
|
+
};
|
|
3846
|
+
|
|
3847
|
+
// src/mlPayload.ts
|
|
3848
|
+
var normalizeStrategyConfig = (strategyConfig, strategyName, profileId) => {
|
|
3849
|
+
return getStrategyMlAdapter(
|
|
3850
|
+
strategyName,
|
|
3851
|
+
profileId
|
|
3852
|
+
).normalizeStrategyConfig?.(strategyConfig);
|
|
3853
|
+
};
|
|
3854
|
+
var buildMlPayload = (payload) => {
|
|
3855
|
+
const strategyName = payload.signal?.strategy ?? payload.context?.strategyName;
|
|
3856
|
+
const profileId = payload.signal?.policyProfileId;
|
|
3857
|
+
const mlAdapter = getStrategyMlAdapter(strategyName, profileId);
|
|
3858
|
+
const normalizedSignal = mlAdapter.normalizeSignal?.(payload.signal) ?? payload.signal;
|
|
3859
|
+
const nextSignal = {
|
|
3860
|
+
...normalizedSignal,
|
|
3861
|
+
indicators: {
|
|
3862
|
+
...normalizedSignal?.indicators ?? {}
|
|
3863
|
+
}
|
|
3864
|
+
};
|
|
3865
|
+
const nextContext = payload.context ? {
|
|
3866
|
+
...payload.context,
|
|
3867
|
+
strategyConfig: normalizeStrategyConfig(
|
|
3868
|
+
payload.context.strategyConfig,
|
|
3869
|
+
strategyName,
|
|
3870
|
+
profileId
|
|
3871
|
+
)
|
|
3872
|
+
} : void 0;
|
|
3873
|
+
return {
|
|
3874
|
+
signal: nextSignal,
|
|
3875
|
+
context: nextContext
|
|
3309
3876
|
};
|
|
3310
|
-
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
3311
|
-
const closeDayKey = (0, import_time.getRuntimeStorageDayKey)(next.exitTimestamp);
|
|
3312
|
-
try {
|
|
3313
|
-
await Promise.all([
|
|
3314
|
-
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, orderId), next, {
|
|
3315
|
-
expire: import_constants.TTL_1M
|
|
3316
|
-
}),
|
|
3317
|
-
(0, import_redis2.setHashJsonField)(
|
|
3318
|
-
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
3319
|
-
orderId,
|
|
3320
|
-
next,
|
|
3321
|
-
{ expire: import_constants.TTL_1M }
|
|
3322
|
-
),
|
|
3323
|
-
(0, import_redis2.setHashJsonField)(
|
|
3324
|
-
import_redis2.redisKeys.runtimeClosedTradeBucket(userName, closeDayKey),
|
|
3325
|
-
orderId,
|
|
3326
|
-
next,
|
|
3327
|
-
{ expire: import_constants.TTL_1M }
|
|
3328
|
-
),
|
|
3329
|
-
(0, import_redis2.delKey)(
|
|
3330
|
-
import_redis2.redisKeys.runtimeActiveTrade(
|
|
3331
|
-
userName,
|
|
3332
|
-
symbol,
|
|
3333
|
-
deploymentId ?? accountId
|
|
3334
|
-
)
|
|
3335
|
-
)
|
|
3336
|
-
]);
|
|
3337
|
-
} catch (error) {
|
|
3338
|
-
import_logger3.logger.error(
|
|
3339
|
-
"runtime trade close journal failed: %s %s",
|
|
3340
|
-
symbol,
|
|
3341
|
-
error?.message || String(error)
|
|
3342
|
-
);
|
|
3343
|
-
}
|
|
3344
|
-
return next;
|
|
3345
3877
|
};
|
|
3346
3878
|
|
|
3347
3879
|
// src/strategyHelpers/marketContextStages.ts
|
|
@@ -3353,7 +3885,7 @@ var import_logger4 = require("@tradejs/infra/logger");
|
|
|
3353
3885
|
var import_strategies = require("@tradejs/core/strategies");
|
|
3354
3886
|
|
|
3355
3887
|
// src/binanceBreadthUniverses.ts
|
|
3356
|
-
var
|
|
3888
|
+
var import_node_crypto3 = require("crypto");
|
|
3357
3889
|
|
|
3358
3890
|
// src/config/binanceBreadthUniverses.json
|
|
3359
3891
|
var binanceBreadthUniverses_default = {
|
|
@@ -3592,7 +4124,7 @@ var BINANCE_BREADTH_UNIVERSE_KEYS = [
|
|
|
3592
4124
|
"top50",
|
|
3593
4125
|
"top100"
|
|
3594
4126
|
];
|
|
3595
|
-
var fingerprint = (value, length) => (0,
|
|
4127
|
+
var fingerprint = (value, length) => (0, import_node_crypto3.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, length);
|
|
3596
4128
|
var normalizeSymbols = (symbols) => symbols.map((symbol) => symbol.trim().toUpperCase()).filter(Boolean);
|
|
3597
4129
|
var buildBinanceBreadthUniverseSnapshot = ({
|
|
3598
4130
|
rankedSymbols,
|
|
@@ -4616,7 +5148,7 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
4616
5148
|
};
|
|
4617
5149
|
|
|
4618
5150
|
// src/strategyHelpers/derivativesContext.ts
|
|
4619
|
-
var
|
|
5151
|
+
var import_indicators3 = require("@tradejs/core/indicators");
|
|
4620
5152
|
var import_data2 = require("@tradejs/core/data");
|
|
4621
5153
|
var import_strategies3 = require("@tradejs/core/strategies");
|
|
4622
5154
|
var import_constants2 = require("@tradejs/core/constants");
|
|
@@ -4650,7 +5182,7 @@ var parseLookbackMs = () => {
|
|
|
4650
5182
|
};
|
|
4651
5183
|
var withHourlyFallbackRows = (rowsByInterval) => ({
|
|
4652
5184
|
"15m": rowsByInterval["15m"] ?? [],
|
|
4653
|
-
"1h": (0,
|
|
5185
|
+
"1h": (0, import_indicators3.buildCoinalyzeHourlyRowsWithFallback)({
|
|
4654
5186
|
rows15m: rowsByInterval["15m"],
|
|
4655
5187
|
fallbackRows1h: rowsByInterval["1h"]
|
|
4656
5188
|
})
|
|
@@ -4778,7 +5310,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
4778
5310
|
const targetSymbol = normalizeSymbol(signal.symbol);
|
|
4779
5311
|
const lookbackMs = parseLookbackMs();
|
|
4780
5312
|
const decisionTimeMs = signal.timestamp + (0, import_data2.intervalToMs)(signal.interval);
|
|
4781
|
-
const derivativesEndMs = (0,
|
|
5313
|
+
const derivativesEndMs = (0, import_indicators3.getLastClosedDerivativesBarStartMs)(
|
|
4782
5314
|
decisionTimeMs,
|
|
4783
5315
|
"15m"
|
|
4784
5316
|
);
|
|
@@ -4793,7 +5325,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
4793
5325
|
});
|
|
4794
5326
|
return [
|
|
4795
5327
|
symbol,
|
|
4796
|
-
(0,
|
|
5328
|
+
(0, import_indicators3.buildDerivativesContext)({
|
|
4797
5329
|
symbol,
|
|
4798
5330
|
direction: signal.direction,
|
|
4799
5331
|
timestamp: derivativesEndMs,
|
|
@@ -4820,7 +5352,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
4820
5352
|
lookbackMs,
|
|
4821
5353
|
...params.abortSignal ? { signal: params.abortSignal } : {}
|
|
4822
5354
|
});
|
|
4823
|
-
const context = (0,
|
|
5355
|
+
const context = (0, import_indicators3.buildDerivativesContext)({
|
|
4824
5356
|
symbol: targetSymbol,
|
|
4825
5357
|
direction: signal.direction,
|
|
4826
5358
|
timestamp: derivativesEndMs,
|
|
@@ -4868,7 +5400,7 @@ var import_hyperliquidWhales2 = require("@tradejs/infra/timescale/hyperliquidWha
|
|
|
4868
5400
|
var import_logger7 = require("@tradejs/infra/logger");
|
|
4869
5401
|
|
|
4870
5402
|
// src/hyperliquidWhaleUniverse.ts
|
|
4871
|
-
var
|
|
5403
|
+
var import_node_crypto4 = require("crypto");
|
|
4872
5404
|
|
|
4873
5405
|
// src/config/hyperliquidPerpUniverse.json
|
|
4874
5406
|
var hyperliquidPerpUniverse_default = {
|
|
@@ -5040,7 +5572,7 @@ var hyperliquidWhales_default = {
|
|
|
5040
5572
|
};
|
|
5041
5573
|
|
|
5042
5574
|
// src/hyperliquidWhaleUniverse.ts
|
|
5043
|
-
var fingerprint2 = (value) => (0,
|
|
5575
|
+
var fingerprint2 = (value) => (0, import_node_crypto4.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
|
|
5044
5576
|
var validateUnique = (values, label) => {
|
|
5045
5577
|
if (new Set(values).size !== values.length) {
|
|
5046
5578
|
throw new Error(`Hyperliquid ${label} snapshot contains duplicates`);
|
|
@@ -5527,6 +6059,7 @@ var enrichSignalWithMarketContextStages = async ({
|
|
|
5527
6059
|
})
|
|
5528
6060
|
});
|
|
5529
6061
|
}
|
|
6062
|
+
await enrichSignalWithTickerProfile(signal);
|
|
5530
6063
|
const results = [];
|
|
5531
6064
|
for (const stage of stages) {
|
|
5532
6065
|
if (abortSignal?.aborted) break;
|
|
@@ -5574,12 +6107,12 @@ var findReplayAiAnalysis = ({
|
|
|
5574
6107
|
direction,
|
|
5575
6108
|
ai
|
|
5576
6109
|
}) => {
|
|
5577
|
-
const
|
|
5578
|
-
if (!Array.isArray(
|
|
6110
|
+
const snapshots2 = ai?.replayAnalyses;
|
|
6111
|
+
if (!Array.isArray(snapshots2) || !snapshots2.length) {
|
|
5579
6112
|
return void 0;
|
|
5580
6113
|
}
|
|
5581
6114
|
let best = null;
|
|
5582
|
-
for (const snapshot2 of
|
|
6115
|
+
for (const snapshot2 of snapshots2) {
|
|
5583
6116
|
if (snapshot2.symbol !== signal.symbol || snapshot2.direction !== direction || snapshot2.strategy && snapshot2.strategy !== signal.strategy) {
|
|
5584
6117
|
continue;
|
|
5585
6118
|
}
|
|
@@ -5739,7 +6272,9 @@ var executeEntryOrder = async ({
|
|
|
5739
6272
|
beforePlaceOrder,
|
|
5740
6273
|
recordRuntimeTrade = true,
|
|
5741
6274
|
leverage,
|
|
5742
|
-
executionLimits
|
|
6275
|
+
executionLimits,
|
|
6276
|
+
markPortfolioSubmitted,
|
|
6277
|
+
rejectPortfolioSubmission
|
|
5743
6278
|
}) => {
|
|
5744
6279
|
await beforePlaceOrder?.();
|
|
5745
6280
|
const previousPosition = positionIntent === "increase" ? await connector.getPosition(symbol) : null;
|
|
@@ -5772,12 +6307,12 @@ var executeEntryOrder = async ({
|
|
|
5772
6307
|
if (executionLimits?.maxLossValue != null) {
|
|
5773
6308
|
signal.riskDecision = {
|
|
5774
6309
|
stage: "order_plan",
|
|
5775
|
-
status: "approved",
|
|
5776
|
-
reason: "PLANNED_LOSS_WITHIN_MAX_LOSS_VALUE",
|
|
6310
|
+
status: economics2.riskExceeded ? "warning" : "approved",
|
|
6311
|
+
reason: economics2.riskExceeded ? "ENTRY_RISK_EXCEEDS_MAX_LOSS_AT_SUBMISSION" : "PLANNED_LOSS_WITHIN_MAX_LOSS_VALUE",
|
|
5777
6312
|
plannedLossValue: economics2.plannedLossValue,
|
|
5778
6313
|
maxLossValue: executionLimits.maxLossValue,
|
|
5779
6314
|
limitUtilization: economics2.plannedLossValue / executionLimits.maxLossValue,
|
|
5780
|
-
enforced:
|
|
6315
|
+
enforced: false
|
|
5781
6316
|
};
|
|
5782
6317
|
}
|
|
5783
6318
|
}
|
|
@@ -5812,7 +6347,13 @@ var executeEntryOrder = async ({
|
|
|
5812
6347
|
...typeof leverage === "number" && Number.isFinite(leverage) ? { leverage } : {},
|
|
5813
6348
|
orderId,
|
|
5814
6349
|
signal,
|
|
5815
|
-
...
|
|
6350
|
+
...rejectPortfolioSubmission ? { onRejected: rejectPortfolioSubmission } : {},
|
|
6351
|
+
...beforeSubmit || markPortfolioSubmitted ? {
|
|
6352
|
+
beforeSubmit: async (context) => {
|
|
6353
|
+
await beforeSubmit?.(context);
|
|
6354
|
+
await markPortfolioSubmitted?.(context.qty);
|
|
6355
|
+
}
|
|
6356
|
+
} : {}
|
|
5816
6357
|
});
|
|
5817
6358
|
const orderAckTime = Date.now();
|
|
5818
6359
|
const placedQty = typeof signal.orderQty === "number" && Number.isFinite(signal.orderQty) && signal.orderQty > 0 ? signal.orderQty : qty;
|
|
@@ -6800,7 +7341,7 @@ var handleProtectDecision = async ({
|
|
|
6800
7341
|
}
|
|
6801
7342
|
return decision.code;
|
|
6802
7343
|
};
|
|
6803
|
-
var
|
|
7344
|
+
var executeAdmittedEntryDecision = async ({
|
|
6804
7345
|
connector,
|
|
6805
7346
|
symbol,
|
|
6806
7347
|
decision,
|
|
@@ -6814,7 +7355,9 @@ var executeEntryDecision = async ({
|
|
|
6814
7355
|
ai,
|
|
6815
7356
|
recordRuntimeJournal,
|
|
6816
7357
|
invokeStageHooks,
|
|
6817
|
-
notifyRuntimeError
|
|
7358
|
+
notifyRuntimeError,
|
|
7359
|
+
markPortfolioSubmitted,
|
|
7360
|
+
rejectPortfolioSubmission
|
|
6818
7361
|
}) => {
|
|
6819
7362
|
const signal = decision.signal;
|
|
6820
7363
|
const beforePlaceOrder = async () => {
|
|
@@ -6861,6 +7404,8 @@ var executeEntryDecision = async ({
|
|
|
6861
7404
|
...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {},
|
|
6862
7405
|
signal,
|
|
6863
7406
|
beforePlaceOrder,
|
|
7407
|
+
markPortfolioSubmitted,
|
|
7408
|
+
rejectPortfolioSubmission,
|
|
6864
7409
|
recordRuntimeTrade: recordRuntimeJournal,
|
|
6865
7410
|
executionLimits: resolveEntryExecutionLimits(
|
|
6866
7411
|
hookCtx.strategyConfig,
|
|
@@ -7001,6 +7546,37 @@ var executeEntryDecision = async ({
|
|
|
7001
7546
|
}
|
|
7002
7547
|
return signal ?? decision.code;
|
|
7003
7548
|
};
|
|
7549
|
+
async function executeEntryDecision(params) {
|
|
7550
|
+
if (!params.portfolio) return executeAdmittedEntryDecision(params);
|
|
7551
|
+
const { decision, portfolio } = params;
|
|
7552
|
+
const signal = decision.signal;
|
|
7553
|
+
if (!signal || decision.orderPlan.positionIntent === "increase")
|
|
7554
|
+
return "PORTFOLIO_REQUIRES_NEW_SIGNAL_POSITION";
|
|
7555
|
+
signal.orderId ??= createRuntimeOrderId(signal.strategy);
|
|
7556
|
+
const result = await portfolio.submit(
|
|
7557
|
+
params.portfolioTime ?? decision.entryContext.timestamp,
|
|
7558
|
+
{
|
|
7559
|
+
id: signal.orderId,
|
|
7560
|
+
signalId: signal.signalId,
|
|
7561
|
+
strategy: signal.strategy,
|
|
7562
|
+
symbol: params.symbol,
|
|
7563
|
+
direction: decision.entryContext.direction,
|
|
7564
|
+
qty: decision.orderPlan.qty,
|
|
7565
|
+
timestamp: decision.entryContext.timestamp
|
|
7566
|
+
},
|
|
7567
|
+
(markPortfolioSubmitted, rejectPortfolioSubmission) => executeAdmittedEntryDecision({
|
|
7568
|
+
...params,
|
|
7569
|
+
markPortfolioSubmitted,
|
|
7570
|
+
rejectPortfolioSubmission
|
|
7571
|
+
})
|
|
7572
|
+
);
|
|
7573
|
+
if (result.reason) {
|
|
7574
|
+
signal.orderStatus = "failed";
|
|
7575
|
+
signal.orderFailureReason = result.reason;
|
|
7576
|
+
return signal;
|
|
7577
|
+
}
|
|
7578
|
+
return result.value;
|
|
7579
|
+
}
|
|
7004
7580
|
|
|
7005
7581
|
// src/strategyRuntime.ts
|
|
7006
7582
|
var cloneWithPropertyDescriptors = (value) => Object.create(
|
|
@@ -7028,6 +7604,7 @@ var createStrategyRuntime = ({
|
|
|
7028
7604
|
};
|
|
7029
7605
|
const creator = async ({
|
|
7030
7606
|
userName,
|
|
7607
|
+
connectorName,
|
|
7031
7608
|
config: baseConfig,
|
|
7032
7609
|
symbol,
|
|
7033
7610
|
universe: requestedUniverse,
|
|
@@ -7066,6 +7643,17 @@ var createStrategyRuntime = ({
|
|
|
7066
7643
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
7067
7644
|
const projectHooks = projectConfig.hooks;
|
|
7068
7645
|
const env = String(config.ENV ?? "BACKTEST");
|
|
7646
|
+
const portfolio = getPortfolioRuntime({
|
|
7647
|
+
connectorName,
|
|
7648
|
+
value: baseConfig?.PORTFOLIO_POLICY ?? config.PORTFOLIO_POLICY,
|
|
7649
|
+
strategy: strategyName,
|
|
7650
|
+
connector,
|
|
7651
|
+
env,
|
|
7652
|
+
userName,
|
|
7653
|
+
accountId,
|
|
7654
|
+
deploymentId
|
|
7655
|
+
});
|
|
7656
|
+
const portfolioContext = portfolio ? (now2) => isRuntimeOrderExecutionEnabled({ config, env, connector }) ? portfolio.tick(now2) : portfolio.context(now2) : void 0;
|
|
7069
7657
|
const researchPolicyValue = baseConfig?.RESEARCH_ENTRY_POLICY ?? config.RESEARCH_ENTRY_POLICY;
|
|
7070
7658
|
const researchEntryPolicy = researchPolicyValue == null ? void 0 : parseResearchEntryPolicy(researchPolicyValue);
|
|
7071
7659
|
if (researchEntryPolicy && (env !== "BACKTEST" || Reflect.get(connector, "__tradejsTestConnector") !== true))
|
|
@@ -7103,6 +7691,9 @@ var createStrategyRuntime = ({
|
|
|
7103
7691
|
});
|
|
7104
7692
|
const strategyPolicyProfile = getPolicyProfile();
|
|
7105
7693
|
const indicatorPeriods = (0, import_strategies6.buildDefaultIndicatorPeriods)(config);
|
|
7694
|
+
const strategyConfig = { ...config };
|
|
7695
|
+
delete strategyConfig.RECORD_RUNTIME_TRADES;
|
|
7696
|
+
delete strategyConfig.SIMULATE_ORDERS;
|
|
7106
7697
|
const hookBase = {
|
|
7107
7698
|
connector,
|
|
7108
7699
|
strategyName,
|
|
@@ -7113,7 +7704,7 @@ var createStrategyRuntime = ({
|
|
|
7113
7704
|
accountId,
|
|
7114
7705
|
deploymentId,
|
|
7115
7706
|
policyProfileId: strategyPolicyProfile?.id ?? requestedPolicyProfileId,
|
|
7116
|
-
strategyConfig
|
|
7707
|
+
strategyConfig,
|
|
7117
7708
|
env,
|
|
7118
7709
|
isConfigFromBacktest
|
|
7119
7710
|
};
|
|
@@ -7181,7 +7772,7 @@ var createStrategyRuntime = ({
|
|
|
7181
7772
|
);
|
|
7182
7773
|
}
|
|
7183
7774
|
};
|
|
7184
|
-
const invokeHook = async (stage, hook, params, errorContext = {}) => {
|
|
7775
|
+
const invokeHook = async (stage, hook, params, errorContext = {}, failureResult) => {
|
|
7185
7776
|
if (!hook) {
|
|
7186
7777
|
return void 0;
|
|
7187
7778
|
}
|
|
@@ -7201,17 +7792,18 @@ var createStrategyRuntime = ({
|
|
|
7201
7792
|
entry: errorContext.entry,
|
|
7202
7793
|
market: errorContext.market
|
|
7203
7794
|
});
|
|
7204
|
-
return
|
|
7795
|
+
return failureResult;
|
|
7205
7796
|
}
|
|
7206
7797
|
};
|
|
7207
|
-
const invokeProjectHooks = async (stage, params, errorContext = {}) => {
|
|
7798
|
+
const invokeProjectHooks = async (stage, params, errorContext = {}, failureResult) => {
|
|
7208
7799
|
const results = [];
|
|
7209
7800
|
for (const hook of getProjectHookList(stage)) {
|
|
7210
7801
|
const result = await invokeHook(
|
|
7211
7802
|
stage,
|
|
7212
7803
|
hook,
|
|
7213
7804
|
params,
|
|
7214
|
-
errorContext
|
|
7805
|
+
errorContext,
|
|
7806
|
+
failureResult
|
|
7215
7807
|
);
|
|
7216
7808
|
if (result !== void 0) {
|
|
7217
7809
|
results.push(result);
|
|
@@ -7226,10 +7818,12 @@ var createStrategyRuntime = ({
|
|
|
7226
7818
|
return invokeHook(stage, hook, params, errorContext);
|
|
7227
7819
|
};
|
|
7228
7820
|
const invokeGateHooks = async (stage, hook, params, errorContext = {}) => {
|
|
7821
|
+
const failureResult = stage === "beforeEntryGate" ? { allow: false, reason: "ERROR" } : void 0;
|
|
7229
7822
|
const projectResults = await invokeProjectHooks(
|
|
7230
7823
|
stage,
|
|
7231
7824
|
params,
|
|
7232
|
-
errorContext
|
|
7825
|
+
errorContext,
|
|
7826
|
+
failureResult
|
|
7233
7827
|
);
|
|
7234
7828
|
const projectBlock = projectResults.find(
|
|
7235
7829
|
(result) => result?.allow === false
|
|
@@ -7241,7 +7835,8 @@ var createStrategyRuntime = ({
|
|
|
7241
7835
|
stage,
|
|
7242
7836
|
hook,
|
|
7243
7837
|
params,
|
|
7244
|
-
errorContext
|
|
7838
|
+
errorContext,
|
|
7839
|
+
failureResult
|
|
7245
7840
|
);
|
|
7246
7841
|
};
|
|
7247
7842
|
const applyProjectAfterCoreDecisionHooks = async ({
|
|
@@ -7357,6 +7952,20 @@ var createStrategyRuntime = ({
|
|
|
7357
7952
|
interval: decisionInterval
|
|
7358
7953
|
}) => {
|
|
7359
7954
|
if (!baseContext) return void 0;
|
|
7955
|
+
const profileSource = strategyManifest?.contextRequirements?.tickerProfiles;
|
|
7956
|
+
if (profileSource) {
|
|
7957
|
+
const profile = await loadTickerProfileContext({
|
|
7958
|
+
...profileSource,
|
|
7959
|
+
projectRoot,
|
|
7960
|
+
symbol: decisionSymbol,
|
|
7961
|
+
timestamp: candle.timestamp
|
|
7962
|
+
});
|
|
7963
|
+
if (!profile)
|
|
7964
|
+
throw new Error(
|
|
7965
|
+
"Required ticker profile is unavailable at decision time"
|
|
7966
|
+
);
|
|
7967
|
+
baseContext = withTickerProfile(baseContext, profile);
|
|
7968
|
+
}
|
|
7360
7969
|
if (!coreContextRequirements.has("hyperliquidWhales")) {
|
|
7361
7970
|
return baseContext;
|
|
7362
7971
|
}
|
|
@@ -7387,11 +7996,8 @@ var createStrategyRuntime = ({
|
|
|
7387
7996
|
return enrichedBaseContext;
|
|
7388
7997
|
}
|
|
7389
7998
|
});
|
|
7390
|
-
const coreConfig = { ...config };
|
|
7391
|
-
delete coreConfig.RECORD_RUNTIME_TRADES;
|
|
7392
|
-
delete coreConfig.SIMULATE_ORDERS;
|
|
7393
7999
|
const core = await createCore({
|
|
7394
|
-
config:
|
|
8000
|
+
config: strategyConfig,
|
|
7395
8001
|
data,
|
|
7396
8002
|
strategyApi,
|
|
7397
8003
|
indicatorsState
|
|
@@ -7527,6 +8133,8 @@ var createStrategyRuntime = ({
|
|
|
7527
8133
|
runtime: pending2.runtime
|
|
7528
8134
|
});
|
|
7529
8135
|
return executeEntryDecision({
|
|
8136
|
+
portfolio,
|
|
8137
|
+
portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
|
|
7530
8138
|
connector,
|
|
7531
8139
|
symbol,
|
|
7532
8140
|
decision: pending2.decision,
|
|
@@ -7544,6 +8152,10 @@ var createStrategyRuntime = ({
|
|
|
7544
8152
|
});
|
|
7545
8153
|
};
|
|
7546
8154
|
const runWithDecisionOverride = async (candle, btcCandle, options = {}) => {
|
|
8155
|
+
if (portfolioContext)
|
|
8156
|
+
await portfolioContext(
|
|
8157
|
+
env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now()
|
|
8158
|
+
);
|
|
7547
8159
|
appendCurrentMarketData(candle, btcCandle, options.ethCandle);
|
|
7548
8160
|
const ethCandle = resolveEthCandle(candle, options.ethCandle);
|
|
7549
8161
|
indicatorsState.setCurrentBar(candle, btcCandle, ethCandle);
|
|
@@ -7714,6 +8326,13 @@ var createStrategyRuntime = ({
|
|
|
7714
8326
|
});
|
|
7715
8327
|
const signal = decision.signal;
|
|
7716
8328
|
if (signal) {
|
|
8329
|
+
if (portfolio)
|
|
8330
|
+
signal.additionalIndicators = {
|
|
8331
|
+
...signal.additionalIndicators,
|
|
8332
|
+
portfolioContext: await portfolioContext(
|
|
8333
|
+
env === "BACKTEST" || env === "PARITY" ? decision.entryContext.timestamp : Date.now()
|
|
8334
|
+
)
|
|
8335
|
+
};
|
|
7717
8336
|
if (runtimeLineage) signal.runtimeLineage = runtimeLineage;
|
|
7718
8337
|
if (universe) signal.universe = universe;
|
|
7719
8338
|
if (assetClass) signal.assetClass = assetClass;
|
|
@@ -7953,6 +8572,8 @@ var createStrategyRuntime = ({
|
|
|
7953
8572
|
});
|
|
7954
8573
|
}
|
|
7955
8574
|
return executeEntryDecision({
|
|
8575
|
+
portfolio,
|
|
8576
|
+
portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
|
|
7956
8577
|
connector,
|
|
7957
8578
|
symbol,
|
|
7958
8579
|
decision,
|
|
@@ -8064,7 +8685,7 @@ var normalizeForCanonicalJson = (value) => {
|
|
|
8064
8685
|
}
|
|
8065
8686
|
return value;
|
|
8066
8687
|
};
|
|
8067
|
-
var revision = (prefix, value) => `${prefix}:${(0,
|
|
8688
|
+
var revision = (prefix, value) => `${prefix}:${(0, import_node_crypto5.createHash)("sha256").update(JSON.stringify(normalizeForCanonicalJson(value))).digest("hex").slice(0, 16)}`;
|
|
8068
8689
|
var computeStrategyRevision = ({
|
|
8069
8690
|
strategyName,
|
|
8070
8691
|
strategyPackage,
|
|
@@ -8668,13 +9289,13 @@ var assertCurrentRuntimeEvidenceArtifact = (artifact) => {
|
|
|
8668
9289
|
throw new Error(`Runtime evidence payload ${field} must be an array`);
|
|
8669
9290
|
}
|
|
8670
9291
|
}
|
|
8671
|
-
const
|
|
9292
|
+
const snapshots2 = new Map(
|
|
8672
9293
|
activeStrategies.map((strategy) => [strategy.strategyName, strategy])
|
|
8673
9294
|
);
|
|
8674
9295
|
const assertRows = (label, rows, lineageField) => {
|
|
8675
9296
|
for (const row of rows) {
|
|
8676
9297
|
const strategyName = String(row.strategy ?? "").trim();
|
|
8677
|
-
const strategy =
|
|
9298
|
+
const strategy = snapshots2.get(strategyName);
|
|
8678
9299
|
if (!strategy || row.deploymentId !== deployment.id || row.accountId !== deployment.accountId || !runtimeLineageMatchesStrategySnapshot({
|
|
8679
9300
|
lineage: isRecord2(row[lineageField]) ? row[lineageField] : void 0,
|
|
8680
9301
|
deployment,
|
|
@@ -8848,7 +9469,7 @@ var RUNTIME_EVIDENCE_MANIFEST_FILE = "manifest.json";
|
|
|
8848
9469
|
var RUNTIME_EVIDENCE_PAYLOAD_FILE = "runtime-evidence.json";
|
|
8849
9470
|
var RUNTIME_EVIDENCE_COMPLETE_FILE = ".complete";
|
|
8850
9471
|
var asRecord2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
8851
|
-
var sha256 = (value) => (0,
|
|
9472
|
+
var sha256 = (value) => (0, import_node_crypto6.createHash)("sha256").update(value).digest("hex");
|
|
8852
9473
|
var runtimeEvidenceIdentitySha256 = (artifact) => {
|
|
8853
9474
|
const identityArtifact = { ...artifact };
|
|
8854
9475
|
delete identityArtifact.generatedAt;
|
|
@@ -8965,7 +9586,7 @@ var publishRuntimeEvidenceBundle = async ({
|
|
|
8965
9586
|
const bundleDir = import_node_path3.default.join(readyRoot, artifactId);
|
|
8966
9587
|
const temporaryDir = import_node_path3.default.join(
|
|
8967
9588
|
incomingRoot,
|
|
8968
|
-
`${artifactId}.tmp-${(0,
|
|
9589
|
+
`${artifactId}.tmp-${(0, import_node_crypto6.randomUUID)()}`
|
|
8969
9590
|
);
|
|
8970
9591
|
await import_promises3.default.mkdir(temporaryDir, { recursive: true });
|
|
8971
9592
|
try {
|
|
@@ -9066,11 +9687,11 @@ var discoverRuntimeEvidenceBundles = async (rootDir) => {
|
|
|
9066
9687
|
};
|
|
9067
9688
|
|
|
9068
9689
|
// src/diagnostics/runtimeEvidenceProducer.ts
|
|
9069
|
-
var
|
|
9690
|
+
var import_node_crypto7 = require("crypto");
|
|
9070
9691
|
var import_promises4 = __toESM(require("fs/promises"));
|
|
9071
9692
|
var import_node_path4 = __toESM(require("path"));
|
|
9072
9693
|
var isRecord3 = (value) => value != null && typeof value === "object" && !Array.isArray(value);
|
|
9073
|
-
var sha2562 = (value) => (0,
|
|
9694
|
+
var sha2562 = (value) => (0, import_node_crypto7.createHash)("sha256").update(value).digest("hex");
|
|
9074
9695
|
var parseRuntimeEvidenceProducer = (value) => {
|
|
9075
9696
|
const producer = isRecord3(value) ? value : null;
|
|
9076
9697
|
const runtimePackageManifest = isRecord3(producer?.runtimePackageManifest) ? producer.runtimePackageManifest : null;
|
|
@@ -9125,7 +9746,7 @@ var resolveRuntimeEvidenceProducer = async ({
|
|
|
9125
9746
|
};
|
|
9126
9747
|
|
|
9127
9748
|
// src/diagnostics/runtimeFeedbackArtifacts.ts
|
|
9128
|
-
var
|
|
9749
|
+
var import_node_crypto8 = require("crypto");
|
|
9129
9750
|
var import_promises5 = __toESM(require("fs/promises"));
|
|
9130
9751
|
var import_node_path5 = __toESM(require("path"));
|
|
9131
9752
|
var RUNTIME_FEEDBACK_MANIFEST_FILE = "manifest.json";
|
|
@@ -9133,7 +9754,7 @@ var RUNTIME_FEEDBACK_REPLAY_FILE = "replay-runtime-evidence.json";
|
|
|
9133
9754
|
var RUNTIME_FEEDBACK_LOG_FILE = "replay.log";
|
|
9134
9755
|
var RUNTIME_FEEDBACK_COMPLETE_FILE = ".complete";
|
|
9135
9756
|
var isRecord4 = (value) => value != null && typeof value === "object" && !Array.isArray(value);
|
|
9136
|
-
var sha2563 = (value) => (0,
|
|
9757
|
+
var sha2563 = (value) => (0, import_node_crypto8.createHash)("sha256").update(value).digest("hex");
|
|
9137
9758
|
var safeSegment2 = (value, fallback) => {
|
|
9138
9759
|
const normalized = value.trim().replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
9139
9760
|
return normalized && normalized !== "." && normalized !== ".." ? normalized : fallback;
|
|
@@ -9408,7 +10029,7 @@ var assertMcpFeedbackContract = (version, tools) => {
|
|
|
9408
10029
|
};
|
|
9409
10030
|
|
|
9410
10031
|
// src/diagnostics/mcpBundleDownload.ts
|
|
9411
|
-
var
|
|
10032
|
+
var import_node_crypto9 = require("crypto");
|
|
9412
10033
|
var import_promises6 = __toESM(require("fs/promises"));
|
|
9413
10034
|
var import_node_path6 = __toESM(require("path"));
|
|
9414
10035
|
var McpReadTransportError = class extends Error {
|
|
@@ -9418,7 +10039,7 @@ var McpReadTransportError = class extends Error {
|
|
|
9418
10039
|
}
|
|
9419
10040
|
};
|
|
9420
10041
|
var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
9421
|
-
var digest = (bytes) => (0,
|
|
10042
|
+
var digest = (bytes) => (0, import_node_crypto9.createHash)("sha256").update(bytes).digest("hex");
|
|
9422
10043
|
var exists = async (file) => import_promises6.default.lstat(file).catch((error) => {
|
|
9423
10044
|
if (error.code === "ENOENT") return void 0;
|
|
9424
10045
|
throw error;
|