@tradejs/node 3.1.29-beta.290 → 3.1.29-beta.291
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai.js +109 -48
- package/dist/ai.mjs +1 -1
- package/dist/backtest.js +2491 -1871
- package/dist/backtest.mjs +3 -2
- package/dist/{chunk-PB2KHBGH.mjs → chunk-HRON2EAI.mjs} +413 -22
- package/dist/{chunk-UL5Z46JL.mjs → chunk-PYJWXAYR.mjs} +97 -30
- package/dist/chunk-UUNEWAT4.mjs +193 -0
- package/dist/{chunk-BO42QWAB.mjs → chunk-W6PPAADK.mjs} +1 -1
- package/dist/cli.js +834 -773
- package/dist/cli.mjs +1 -1
- package/dist/diagnostics.js +2511 -1891
- package/dist/diagnostics.mjs +4 -3
- package/dist/mcp.d.mts +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +2537 -1917
- package/dist/mcp.mjs +4 -3
- package/dist/portfolio.d.mts +28 -0
- package/dist/portfolio.d.ts +28 -0
- package/dist/portfolio.js +210 -0
- package/dist/portfolio.mjs +9 -0
- package/dist/registry.js +2429 -1809
- package/dist/registry.mjs +3 -2
- package/dist/runtimeDashboard.js +2236 -1614
- package/dist/runtimeDashboard.mjs +4 -3
- package/dist/runtimeStrategies.js +2052 -1432
- package/dist/runtimeStrategies.mjs +4 -3
- package/dist/strategies.d.mts +3 -1
- package/dist/strategies.d.ts +3 -1
- package/dist/strategies.js +2085 -1465
- package/dist/strategies.mjs +3 -2
- package/package.json +10 -5
package/dist/mcp.js
CHANGED
|
@@ -806,1668 +806,2402 @@ __export(mcp_exports, {
|
|
|
806
806
|
requireMcpDeployment: () => requireMcpDeployment
|
|
807
807
|
});
|
|
808
808
|
module.exports = __toCommonJS(mcp_exports);
|
|
809
|
-
var
|
|
810
|
-
var
|
|
809
|
+
var import_node_crypto7 = require("crypto");
|
|
810
|
+
var import_indicators4 = require("@tradejs/core/indicators");
|
|
811
811
|
var import_data6 = require("@tradejs/core/data");
|
|
812
|
-
var
|
|
812
|
+
var import_redis5 = require("@tradejs/infra/redis");
|
|
813
813
|
var import_runtimeHeartbeats = require("@tradejs/infra/runtimeHeartbeats");
|
|
814
814
|
var import_tradingAccounts3 = require("@tradejs/infra/tradingAccounts");
|
|
815
815
|
var import_time3 = require("@tradejs/core/time");
|
|
816
816
|
|
|
817
|
-
// src/
|
|
818
|
-
var
|
|
817
|
+
// src/strategy/portfolioRuntime.ts
|
|
818
|
+
var import_node_crypto2 = require("crypto");
|
|
819
|
+
var import_portfolio2 = require("@tradejs/core/portfolio");
|
|
820
|
+
var import_redis2 = require("@tradejs/infra/redis");
|
|
819
821
|
|
|
820
|
-
// src/
|
|
821
|
-
var
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
const questions = questionsForJevInput(input);
|
|
852
|
-
const questionsHash = (0, import_jev5.jevHash)(questions);
|
|
853
|
-
const base = {
|
|
854
|
-
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
855
|
-
schema: "tradejs-signal-assessment/v5",
|
|
856
|
-
source: config.source,
|
|
857
|
-
mode: config.mode,
|
|
858
|
-
inputHash,
|
|
859
|
-
questionsHash,
|
|
860
|
-
geometryStatus: input.geometryStatus,
|
|
861
|
-
levelsValid: typeof input.features["signal.validLevels"] === "boolean" ? input.features["signal.validLevels"] : null
|
|
862
|
-
};
|
|
863
|
-
if (!input.questions.length)
|
|
864
|
-
return {
|
|
865
|
-
assessment: {
|
|
866
|
-
...base,
|
|
867
|
-
status: "unavailable",
|
|
868
|
-
model: "",
|
|
869
|
-
scores: emptyScores(),
|
|
870
|
-
confidence: emptyScores()
|
|
871
|
-
}
|
|
872
|
-
};
|
|
873
|
-
if (config.source === "local") {
|
|
874
|
-
const contents = await import_promises.default.readFile(
|
|
875
|
-
import_node_path.default.resolve(projectRoot, config.modelFile),
|
|
876
|
-
"utf8"
|
|
877
|
-
);
|
|
878
|
-
if ((0, import_jev5.jevFileHash)(contents) !== config.modelSha256)
|
|
879
|
-
throw new Error("Local Jev gate checksum mismatch");
|
|
880
|
-
const { validateJevGateModel: validateJevGateModel2 } = await Promise.resolve().then(() => (init_jevTraining(), jevTraining_exports));
|
|
881
|
-
const model = validateJevGateModel2(JSON.parse(contents));
|
|
882
|
-
if (model.strategy !== input.strategy || model.questionsHash !== (0, import_jev5.jevHash)(JEV_QUESTIONS))
|
|
883
|
-
throw new Error("Local Jev gate strategy or question version mismatch");
|
|
884
|
-
const scores = Object.fromEntries(
|
|
885
|
-
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
886
|
-
key,
|
|
887
|
-
input.questions.includes(key) && model.trees[key] ? (0, import_jev4.predictJevTree)(model.trees[key], input.features) : null
|
|
888
|
-
])
|
|
889
|
-
);
|
|
890
|
-
return {
|
|
891
|
-
assessment: {
|
|
892
|
-
...base,
|
|
893
|
-
status: "available",
|
|
894
|
-
model: config.modelSha256,
|
|
895
|
-
scores,
|
|
896
|
-
confidence: emptyScores()
|
|
822
|
+
// src/portfolio.ts
|
|
823
|
+
var import_portfolio = require("@tradejs/core/portfolio");
|
|
824
|
+
function createPortfolioCoordinator({
|
|
825
|
+
policy,
|
|
826
|
+
strategy,
|
|
827
|
+
store,
|
|
828
|
+
execution
|
|
829
|
+
}) {
|
|
830
|
+
const fingerprint3 = (0, import_portfolio.portfolioFingerprint)(policy, strategy);
|
|
831
|
+
let cache;
|
|
832
|
+
async function transact(now2, change, force = false) {
|
|
833
|
+
for (let attempt = 0; attempt < 16; attempt++) {
|
|
834
|
+
const previous = await store.read();
|
|
835
|
+
if (previous && previous.fingerprint !== fingerprint3)
|
|
836
|
+
throw new Error(
|
|
837
|
+
"Portfolio policy changed under existing state; reconcile the persisted scope before changing configuration"
|
|
838
|
+
);
|
|
839
|
+
const base = previous ?? (0, import_portfolio.createPortfolioState)(fingerprint3);
|
|
840
|
+
const bucket = Math.floor(now2 / policy.observationIntervalMs);
|
|
841
|
+
const observation = !force && cache?.revision === base.revision && cache.bucket === bucket ? cache.observation : await execution.observe(now2, base.reservations);
|
|
842
|
+
const reconciled = (0, import_portfolio.reconcilePortfolio)(
|
|
843
|
+
policy,
|
|
844
|
+
strategy,
|
|
845
|
+
base,
|
|
846
|
+
observation
|
|
847
|
+
);
|
|
848
|
+
for (const intent of base.closing) {
|
|
849
|
+
if (!observation.positions.some(
|
|
850
|
+
(p) => p.symbol === intent.position.symbol
|
|
851
|
+
))
|
|
852
|
+
await execution.confirmClosed?.(intent, observation.asOf);
|
|
897
853
|
}
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
const file = import_node_path.default.join(dir, "records", `${id}.json`);
|
|
904
|
-
const getRecord = async () => {
|
|
905
|
-
const existing = await (0, import_jev5.readJevArtifact)(file);
|
|
906
|
-
if (existing) {
|
|
907
|
-
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))
|
|
908
|
-
throw new Error("Jev recording identity mismatch");
|
|
909
|
-
(0, import_jev5.validateJevResponse)(existing.response, input.questions);
|
|
910
|
-
for (const key of import_jev4.JEV_DIMENSIONS) {
|
|
911
|
-
const expected = input.questions.includes(key) ? existing.response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null;
|
|
912
|
-
if (existing.scores[key] !== expected)
|
|
913
|
-
throw new Error("Jev recording scores do not match the response");
|
|
854
|
+
const changed = change(reconciled, observation);
|
|
855
|
+
const next = { ...changed.state, revision: base.revision + 1 };
|
|
856
|
+
if (await store.compare(previous, next)) {
|
|
857
|
+
cache = { revision: next.revision, bucket, observation };
|
|
858
|
+
return { state: next, observation, result: changed.result };
|
|
914
859
|
}
|
|
915
|
-
|
|
860
|
+
cache = void 0;
|
|
916
861
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
862
|
+
throw new Error("Portfolio state contention; entry was not submitted");
|
|
863
|
+
}
|
|
864
|
+
async function tick(now2, force = false) {
|
|
865
|
+
const claimed = await transact(
|
|
866
|
+
now2,
|
|
867
|
+
(state, observation) => {
|
|
868
|
+
const commands = (0, import_portfolio.portfolioCloseCommands)(policy, state, observation);
|
|
869
|
+
return {
|
|
870
|
+
state: {
|
|
871
|
+
...state,
|
|
872
|
+
closing: state.closing.map(
|
|
873
|
+
(c) => commands.some((x) => x.intent.positionId === c.positionId) ? { ...c, lastAttemptAt: observation.asOf } : c
|
|
874
|
+
)
|
|
875
|
+
},
|
|
876
|
+
result: commands
|
|
877
|
+
};
|
|
878
|
+
},
|
|
879
|
+
force
|
|
880
|
+
);
|
|
881
|
+
for (const command of claimed.result)
|
|
882
|
+
await execution.close(
|
|
883
|
+
command.position,
|
|
884
|
+
command.intent,
|
|
885
|
+
claimed.observation.asOf
|
|
920
886
|
);
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
887
|
+
if (claimed.result.length) {
|
|
888
|
+
cache = void 0;
|
|
889
|
+
const refreshed = await transact(
|
|
890
|
+
now2,
|
|
891
|
+
(state) => ({ state, result: void 0 }),
|
|
892
|
+
true
|
|
893
|
+
);
|
|
894
|
+
return (0, import_portfolio.buildPortfolioContext)(
|
|
895
|
+
policy,
|
|
896
|
+
strategy,
|
|
897
|
+
refreshed.state,
|
|
898
|
+
refreshed.observation
|
|
929
899
|
);
|
|
930
|
-
const started = Date.now();
|
|
931
|
-
const state = stateForJevInput(input);
|
|
932
|
-
const responseId = (0, import_jev5.jevHash)({
|
|
933
|
-
state,
|
|
934
|
-
questionsHash,
|
|
935
|
-
provider,
|
|
936
|
-
user: (0, import_jev5.jevHash)(userName)
|
|
937
|
-
});
|
|
938
|
-
const responseFile = import_node_path.default.join(dir, "responses", `${responseId}.json`);
|
|
939
|
-
let responsePromise = pendingResponses.get(responseFile);
|
|
940
|
-
if (!responsePromise) {
|
|
941
|
-
responsePromise = (async () => {
|
|
942
|
-
const saved = await (0, import_jev5.readJevArtifact)(responseFile);
|
|
943
|
-
if (saved) return (0, import_jev5.validateJevResponse)(saved, input.questions);
|
|
944
|
-
const fresh = await (0, import_jev5.requestJev)(provider, apiKey, state, questions);
|
|
945
|
-
await (0, import_jev5.writeJevArtifact)(responseFile, fresh);
|
|
946
|
-
return fresh;
|
|
947
|
-
})();
|
|
948
|
-
pendingResponses.set(responseFile, responsePromise);
|
|
949
|
-
}
|
|
950
|
-
let response;
|
|
951
|
-
try {
|
|
952
|
-
response = await responsePromise;
|
|
953
|
-
} finally {
|
|
954
|
-
pendingResponses.delete(responseFile);
|
|
955
900
|
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
901
|
+
return (0, import_portfolio.buildPortfolioContext)(
|
|
902
|
+
policy,
|
|
903
|
+
strategy,
|
|
904
|
+
claimed.state,
|
|
905
|
+
claimed.observation
|
|
961
906
|
);
|
|
962
|
-
const value = {
|
|
963
|
-
schema: "tradejs-jev-record/v4",
|
|
964
|
-
id,
|
|
965
|
-
inputHash,
|
|
966
|
-
questionsHash,
|
|
967
|
-
provider,
|
|
968
|
-
input,
|
|
969
|
-
response,
|
|
970
|
-
scores,
|
|
971
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
972
|
-
elapsedMs: Date.now() - started
|
|
973
|
-
};
|
|
974
|
-
await (0, import_jev5.writeJevArtifact)(file, value);
|
|
975
|
-
return await (0, import_jev5.readJevArtifact)(file);
|
|
976
|
-
};
|
|
977
|
-
const pendingKey = `${file}:${(0, import_jev5.jevHash)(userName)}`;
|
|
978
|
-
let promise = pending.get(pendingKey);
|
|
979
|
-
if (!promise) {
|
|
980
|
-
promise = getRecord();
|
|
981
|
-
pending.set(pendingKey, promise);
|
|
982
907
|
}
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
908
|
+
async function updateReservation(now2, id, submitted, qty) {
|
|
909
|
+
return transact(
|
|
910
|
+
now2,
|
|
911
|
+
(s, o) => {
|
|
912
|
+
const exists = s.reservations.some((r) => r.id === id);
|
|
913
|
+
const blocked = submitted && (!exists || s.closing.length > 0 || o.asOf <= s.blockedThrough);
|
|
914
|
+
return {
|
|
915
|
+
state: {
|
|
916
|
+
...s,
|
|
917
|
+
reservations: s.reservations.flatMap(
|
|
918
|
+
(r) => r.id !== id ? [r] : submitted && !blocked ? [{ ...r, submitted: true, qty: qty ?? r.qty }] : []
|
|
919
|
+
)
|
|
920
|
+
},
|
|
921
|
+
result: !blocked
|
|
922
|
+
};
|
|
923
|
+
},
|
|
924
|
+
true
|
|
925
|
+
);
|
|
988
926
|
}
|
|
989
927
|
return {
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
928
|
+
async context(now2) {
|
|
929
|
+
const state = await store.read() ?? (0, import_portfolio.createPortfolioState)(fingerprint3);
|
|
930
|
+
if (state.fingerprint !== fingerprint3)
|
|
931
|
+
throw new Error("Portfolio policy changed under existing state");
|
|
932
|
+
const observation = await execution.observe(now2, state.reservations);
|
|
933
|
+
(0, import_portfolio.validatePortfolioObservation)(observation);
|
|
934
|
+
return (0, import_portfolio.buildPortfolioContext)(policy, strategy, state, observation);
|
|
935
|
+
},
|
|
936
|
+
tick,
|
|
937
|
+
async submit(now2, entry, send) {
|
|
938
|
+
await tick(now2);
|
|
939
|
+
const admitted = await transact(
|
|
940
|
+
now2,
|
|
941
|
+
(s, o) => {
|
|
942
|
+
const r = (0, import_portfolio.reservePortfolioEntry)(policy, strategy, s, o, {
|
|
943
|
+
...entry,
|
|
944
|
+
submitted: false,
|
|
945
|
+
oppositeHandled: false
|
|
946
|
+
});
|
|
947
|
+
return { state: r.state, result: r.reason };
|
|
948
|
+
},
|
|
949
|
+
true
|
|
950
|
+
);
|
|
951
|
+
if (admitted.result) return { reason: admitted.result };
|
|
952
|
+
let submitted = false;
|
|
953
|
+
try {
|
|
954
|
+
const value = await send(
|
|
955
|
+
async (qty) => {
|
|
956
|
+
if (!Number.isFinite(qty) || qty <= 0)
|
|
957
|
+
throw new Error("Invalid submitted portfolio quantity");
|
|
958
|
+
submitted = true;
|
|
959
|
+
const update = await updateReservation(now2, entry.id, true, qty);
|
|
960
|
+
if (!update.result)
|
|
961
|
+
throw new Error("PORTFOLIO_SUBMISSION_NO_LONGER_ADMITTED");
|
|
962
|
+
},
|
|
963
|
+
async () => {
|
|
964
|
+
await transact(
|
|
965
|
+
now2,
|
|
966
|
+
(s) => ({
|
|
967
|
+
state: {
|
|
968
|
+
...s,
|
|
969
|
+
reservations: s.reservations.filter((r) => r.id !== entry.id),
|
|
970
|
+
completedSignals: {
|
|
971
|
+
...s.completedSignals,
|
|
972
|
+
[entry.signalId]: now2
|
|
973
|
+
}
|
|
974
|
+
},
|
|
975
|
+
result: void 0
|
|
976
|
+
}),
|
|
977
|
+
true
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
);
|
|
981
|
+
if (!submitted) await updateReservation(now2, entry.id, false);
|
|
982
|
+
else await tick(now2, true);
|
|
983
|
+
return { value };
|
|
984
|
+
} catch (error) {
|
|
985
|
+
if (!submitted) await updateReservation(now2, entry.id, false);
|
|
986
|
+
throw error;
|
|
987
|
+
}
|
|
1003
988
|
}
|
|
1004
989
|
};
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
try {
|
|
1016
|
-
const input = buildJevInput(signal);
|
|
1017
|
-
({ assessment } = await evaluateJevInput({
|
|
1018
|
-
input,
|
|
1019
|
-
config,
|
|
1020
|
-
userName,
|
|
1021
|
-
projectRoot
|
|
1022
|
-
}));
|
|
1023
|
-
} catch (error) {
|
|
1024
|
-
if (strict) throw error;
|
|
1025
|
-
assessment = {
|
|
1026
|
-
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
1027
|
-
schema: "tradejs-signal-assessment/v5",
|
|
1028
|
-
source: config.source,
|
|
1029
|
-
mode: config.mode,
|
|
1030
|
-
status: "unavailable",
|
|
1031
|
-
inputHash: "",
|
|
1032
|
-
questionsHash: "",
|
|
1033
|
-
geometryStatus: "absent",
|
|
1034
|
-
levelsValid: null,
|
|
1035
|
-
model: config.provider?.model ?? config.modelSha256 ?? "",
|
|
1036
|
-
scores: emptyScores(),
|
|
1037
|
-
confidence: emptyScores()
|
|
1038
|
-
};
|
|
1039
|
-
}
|
|
1040
|
-
signal.assessment = assessment;
|
|
1041
|
-
signal.additionalIndicators = {
|
|
1042
|
-
...signal.additionalIndicators,
|
|
1043
|
-
jev: (0, import_jev4.jevAssessmentFeatures)(assessment)
|
|
990
|
+
}
|
|
991
|
+
function createMemoryPortfolioStore() {
|
|
992
|
+
let state = null;
|
|
993
|
+
return {
|
|
994
|
+
read: async () => state === null ? null : structuredClone(state),
|
|
995
|
+
compare: async (expected, next) => {
|
|
996
|
+
if ((state?.revision ?? -1) !== (expected?.revision ?? -1)) return false;
|
|
997
|
+
state = structuredClone(next);
|
|
998
|
+
return true;
|
|
999
|
+
}
|
|
1044
1000
|
};
|
|
1045
|
-
|
|
1046
|
-
signalId: signal.signalId,
|
|
1047
|
-
strategy: signal.strategy,
|
|
1048
|
-
symbol: signal.symbol,
|
|
1049
|
-
timestamp: signal.timestamp,
|
|
1050
|
-
scope,
|
|
1051
|
-
user: (0, import_jev5.jevHash)(userName),
|
|
1052
|
-
config,
|
|
1053
|
-
assessment
|
|
1054
|
-
};
|
|
1055
|
-
await (0, import_jev5.writeJevArtifact)(
|
|
1056
|
-
import_node_path.default.resolve(
|
|
1057
|
-
projectRoot,
|
|
1058
|
-
config.recordsDir ?? "data/ai/jev",
|
|
1059
|
-
"decisions",
|
|
1060
|
-
`${(0, import_jev5.jevHash)(identity)}.json`
|
|
1061
|
-
),
|
|
1062
|
-
identity
|
|
1063
|
-
);
|
|
1064
|
-
return assessment;
|
|
1065
|
-
};
|
|
1066
|
-
|
|
1067
|
-
// src/strategyRuntime.ts
|
|
1068
|
-
var import_constants5 = require("@tradejs/core/constants");
|
|
1069
|
-
var import_strategies6 = require("@tradejs/core/strategies");
|
|
1070
|
-
var import_logger11 = require("@tradejs/infra/logger");
|
|
1071
|
-
|
|
1072
|
-
// src/strategyHelpers/runtime.ts
|
|
1073
|
-
var import_logger9 = require("@tradejs/infra/logger");
|
|
1074
|
-
var import_constants3 = require("@tradejs/core/constants");
|
|
1075
|
-
var import_ml2 = require("@tradejs/infra/ml");
|
|
1001
|
+
}
|
|
1076
1002
|
|
|
1077
|
-
// src/
|
|
1003
|
+
// src/runtimeJournal.ts
|
|
1004
|
+
var import_node_crypto = require("crypto");
|
|
1005
|
+
var import_constants = require("@tradejs/core/constants");
|
|
1006
|
+
var import_time = require("@tradejs/core/time");
|
|
1007
|
+
var import_trade = require("@tradejs/core/trade");
|
|
1078
1008
|
var import_logger = require("@tradejs/infra/logger");
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
var
|
|
1082
|
-
|
|
1083
|
-
super(...arguments);
|
|
1084
|
-
this.name = "EntrySubmissionValidationError";
|
|
1085
|
-
}
|
|
1086
|
-
};
|
|
1087
|
-
var resolveEntryExecutionLimits = (config, direction) => {
|
|
1088
|
-
const side = config[direction];
|
|
1089
|
-
const minimum = side && typeof side === "object" && "minRiskRatio" in side ? side.minRiskRatio : void 0;
|
|
1090
|
-
const maximum = config.MAX_LOSS_VALUE;
|
|
1091
|
-
if (minimum == null && maximum == null) return void 0;
|
|
1092
|
-
const minimumNetRiskRatio = minimum == null ? void 0 : Number(minimum);
|
|
1093
|
-
const maxLossValue = maximum == null ? void 0 : Number(maximum);
|
|
1094
|
-
const feeRate = Number(config.RISK_FEE_RATE ?? 0);
|
|
1095
|
-
const slippage = Number(config.RISK_SLIPPAGE_BPS ?? 0);
|
|
1096
|
-
const impact = Number(config.RISK_MARKET_IMPACT_BPS ?? 0);
|
|
1097
|
-
const slippageBps = slippage + impact;
|
|
1098
|
-
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)
|
|
1099
|
-
throw new Error("INVALID_ENTRY_EXECUTION_LIMITS");
|
|
1100
|
-
return { minimumNetRiskRatio, maxLossValue, feeRate, slippageBps };
|
|
1101
|
-
};
|
|
1102
|
-
var validateEntryExecution = ({
|
|
1009
|
+
var import_redis = require("@tradejs/infra/redis");
|
|
1010
|
+
var now = () => Date.now();
|
|
1011
|
+
var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
|
|
1012
|
+
var calculateClosedPnl = ({
|
|
1103
1013
|
direction,
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
takeProfitPrice,
|
|
1108
|
-
limits
|
|
1014
|
+
entryPrice,
|
|
1015
|
+
exitPrice,
|
|
1016
|
+
qty
|
|
1109
1017
|
}) => {
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
1113
|
-
);
|
|
1114
|
-
const side = direction === "LONG" ? 1 : -1;
|
|
1115
|
-
if (side * (price - stopLossPrice) <= 0 || side * (takeProfitPrice - price) <= 0)
|
|
1116
|
-
throw new EntrySubmissionValidationError(
|
|
1117
|
-
"INVALID_ENTRY_PROTECTION_AT_SUBMISSION"
|
|
1118
|
-
);
|
|
1119
|
-
const slippage = limits.slippageBps / 1e4;
|
|
1120
|
-
const cost = (value, sign) => value * slippage + value * (1 + sign * slippage) * limits.feeRate;
|
|
1121
|
-
const entryCost = cost(price, side);
|
|
1122
|
-
const lossPerUnit = Math.abs(price - stopLossPrice) + entryCost + cost(stopLossPrice, -side);
|
|
1123
|
-
const rewardPerUnit = Math.max(
|
|
1124
|
-
0,
|
|
1125
|
-
Math.abs(takeProfitPrice - price) - entryCost - cost(takeProfitPrice, -side)
|
|
1126
|
-
);
|
|
1127
|
-
const netRiskRatio = rewardPerUnit / lossPerUnit;
|
|
1128
|
-
const plannedLossValue = qty * lossPerUnit;
|
|
1129
|
-
if (!Number.isFinite(netRiskRatio) || !Number.isFinite(plannedLossValue))
|
|
1130
|
-
throw new EntrySubmissionValidationError(
|
|
1131
|
-
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
1132
|
-
);
|
|
1133
|
-
if (limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio)
|
|
1134
|
-
throw new EntrySubmissionValidationError(
|
|
1135
|
-
"ENTRY_RR_BELOW_MIN_AT_SUBMISSION"
|
|
1136
|
-
);
|
|
1137
|
-
const riskExceeded = limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9);
|
|
1138
|
-
return { netRiskRatio, plannedLossValue, riskExceeded };
|
|
1018
|
+
const pnl = direction === "LONG" ? (exitPrice - entryPrice) * qty : (entryPrice - exitPrice) * qty;
|
|
1019
|
+
return Number.isFinite(pnl) ? pnl : null;
|
|
1139
1020
|
};
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
}
|
|
1149
|
-
if (
|
|
1150
|
-
return
|
|
1151
|
-
arrivalSnapshotTime: Date.now(),
|
|
1152
|
-
arrivalSource: "unavailable",
|
|
1153
|
-
bid: null,
|
|
1154
|
-
ask: null,
|
|
1155
|
-
arrivalMid: null,
|
|
1156
|
-
spreadBps: null
|
|
1157
|
-
};
|
|
1021
|
+
var createRuntimeOrderId = (strategy) => {
|
|
1022
|
+
const prefix = (0, import_trade.createRuntimeOrderLinkPrefix)(strategy);
|
|
1023
|
+
if (prefix === "tjs-") {
|
|
1024
|
+
return `tjs-${(0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 24).toLowerCase()}`;
|
|
1025
|
+
}
|
|
1026
|
+
return `${prefix}${toRandomOrderSuffix()}`;
|
|
1027
|
+
};
|
|
1028
|
+
var recordRuntimeTradeOpen = async (params) => {
|
|
1029
|
+
const { userName } = params;
|
|
1030
|
+
if (!userName) {
|
|
1031
|
+
return null;
|
|
1158
1032
|
}
|
|
1033
|
+
const record = {
|
|
1034
|
+
...params,
|
|
1035
|
+
entryCount: 1,
|
|
1036
|
+
lastEntryPrice: params.entryPrice,
|
|
1037
|
+
lastEntryQty: params.qty,
|
|
1038
|
+
lastEntryTimestamp: params.entryTimestamp,
|
|
1039
|
+
status: "active",
|
|
1040
|
+
currentPrice: params.entryPrice,
|
|
1041
|
+
currentPnl: 0,
|
|
1042
|
+
closedPnl: null,
|
|
1043
|
+
exitPrice: null,
|
|
1044
|
+
exitTimestamp: null,
|
|
1045
|
+
lastSyncedAt: now()
|
|
1046
|
+
};
|
|
1047
|
+
const dayKey = (0, import_time.getRuntimeStorageDayKey)(record.entryTimestamp);
|
|
1048
|
+
const runtimeScopeId = record.deploymentId ?? record.accountId;
|
|
1159
1049
|
try {
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1050
|
+
await Promise.all([
|
|
1051
|
+
(0, import_redis.setData)(import_redis.redisKeys.runtimeTrade(userName, record.orderId), record, {
|
|
1052
|
+
expire: 0
|
|
1053
|
+
}),
|
|
1054
|
+
(0, import_redis.setHashJsonField)(
|
|
1055
|
+
import_redis.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
1056
|
+
record.orderId,
|
|
1057
|
+
record,
|
|
1058
|
+
{ expire: 0 }
|
|
1059
|
+
),
|
|
1060
|
+
(0, import_redis.setData)(
|
|
1061
|
+
import_redis.redisKeys.runtimeActiveTrade(userName, record.symbol, runtimeScopeId),
|
|
1062
|
+
{ orderId: record.orderId },
|
|
1063
|
+
{ expire: 0 }
|
|
1064
|
+
)
|
|
1065
|
+
]);
|
|
1175
1066
|
} catch (error) {
|
|
1176
|
-
import_logger.logger.
|
|
1177
|
-
"runtime
|
|
1178
|
-
symbol,
|
|
1067
|
+
import_logger.logger.error(
|
|
1068
|
+
"runtime trade open journal failed: %s %s",
|
|
1069
|
+
record.symbol,
|
|
1179
1070
|
error?.message || String(error)
|
|
1180
1071
|
);
|
|
1181
|
-
return {
|
|
1182
|
-
arrivalSnapshotTime: Date.now(),
|
|
1183
|
-
arrivalSource: "top_of_book_error",
|
|
1184
|
-
bid: null,
|
|
1185
|
-
ask: null,
|
|
1186
|
-
arrivalMid: null,
|
|
1187
|
-
spreadBps: null
|
|
1188
|
-
};
|
|
1189
1072
|
}
|
|
1073
|
+
return record;
|
|
1190
1074
|
};
|
|
1191
|
-
var
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
}
|
|
1075
|
+
var recordRuntimeTradeIncrease = async (params) => {
|
|
1076
|
+
const {
|
|
1077
|
+
userName,
|
|
1078
|
+
strategy,
|
|
1079
|
+
symbol,
|
|
1080
|
+
direction,
|
|
1081
|
+
resultingQty,
|
|
1082
|
+
resultingEntryPrice,
|
|
1083
|
+
addedQty,
|
|
1084
|
+
addedEntryPrice,
|
|
1085
|
+
entryTimestamp,
|
|
1086
|
+
fee,
|
|
1087
|
+
accountId,
|
|
1088
|
+
deploymentId
|
|
1089
|
+
} = params;
|
|
1090
|
+
if (!userName) {
|
|
1091
|
+
return null;
|
|
1209
1092
|
}
|
|
1210
|
-
|
|
1211
|
-
|
|
1093
|
+
const existing = await getActiveRuntimeTrade({
|
|
1094
|
+
userName,
|
|
1095
|
+
symbol,
|
|
1096
|
+
accountId,
|
|
1097
|
+
deploymentId
|
|
1098
|
+
});
|
|
1099
|
+
if (!existing || existing.strategy !== strategy || existing.direction !== direction) {
|
|
1100
|
+
return null;
|
|
1212
1101
|
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
arrivalSnapshotTime: Date.now(),
|
|
1232
|
-
arrivalSource: "simulation",
|
|
1233
|
-
bid: executionPrice,
|
|
1234
|
-
ask: executionPrice,
|
|
1235
|
-
arrivalMid: executionPrice,
|
|
1236
|
-
spreadBps: 0
|
|
1237
|
-
} : await getOrderArrivalSnapshot({
|
|
1238
|
-
connector,
|
|
1239
|
-
symbol,
|
|
1240
|
-
requireTimestamp: true
|
|
1241
|
-
});
|
|
1242
|
-
const age = Date.now() - snapshot2.arrivalSnapshotTime;
|
|
1243
|
-
if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
|
|
1244
|
-
throw new EntrySubmissionValidationError(
|
|
1245
|
-
"ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
|
|
1246
|
-
);
|
|
1247
|
-
if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
|
|
1248
|
-
throw new EntrySubmissionValidationError(
|
|
1249
|
-
"ENTRY_QUOTE_STALE_AT_SUBMISSION"
|
|
1250
|
-
);
|
|
1251
|
-
const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
|
|
1252
|
-
validateEntryProtectionAtArrival({
|
|
1253
|
-
direction,
|
|
1254
|
-
signalPrice: price,
|
|
1255
|
-
bid: snapshot2.bid,
|
|
1256
|
-
ask: snapshot2.ask,
|
|
1257
|
-
arrivalMid: snapshot2.arrivalMid,
|
|
1258
|
-
takeProfits,
|
|
1259
|
-
stopLossPrice
|
|
1260
|
-
});
|
|
1261
|
-
const prices = takeProfits.map((tp) => tp.price);
|
|
1262
|
-
const economics2 = validateEntryExecution({
|
|
1263
|
-
direction,
|
|
1264
|
-
price,
|
|
1265
|
-
qty,
|
|
1266
|
-
stopLossPrice,
|
|
1267
|
-
takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
|
|
1268
|
-
limits
|
|
1269
|
-
});
|
|
1270
|
-
onValidated?.(snapshot2, economics2);
|
|
1102
|
+
const addedFee = typeof fee === "number" && Number.isFinite(fee) ? fee : 0;
|
|
1103
|
+
const openFee = (existing.openFee ?? existing.fee ?? 0) + addedFee;
|
|
1104
|
+
const totalFee = (existing.totalFee ?? existing.fee ?? 0) + addedFee;
|
|
1105
|
+
const next = {
|
|
1106
|
+
...existing,
|
|
1107
|
+
...params.orderLeverage ? { orderLeverage: params.orderLeverage } : {},
|
|
1108
|
+
qty: resultingQty,
|
|
1109
|
+
entryPrice: resultingEntryPrice,
|
|
1110
|
+
entryCount: Math.max(1, existing.entryCount ?? 1) + 1,
|
|
1111
|
+
lastEntryPrice: addedEntryPrice,
|
|
1112
|
+
lastEntryQty: addedQty,
|
|
1113
|
+
lastEntryTimestamp: entryTimestamp,
|
|
1114
|
+
currentPrice: resultingEntryPrice,
|
|
1115
|
+
currentPnl: 0,
|
|
1116
|
+
fee: openFee,
|
|
1117
|
+
openFee,
|
|
1118
|
+
totalFee,
|
|
1119
|
+
lastSyncedAt: now()
|
|
1271
1120
|
};
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
}
|
|
1291
|
-
return trimmed.map(
|
|
1292
|
-
(item) => item && typeof item === "object" ? trimSeriesDeep(item) : item
|
|
1293
|
-
);
|
|
1294
|
-
}
|
|
1295
|
-
if (value && typeof value === "object") {
|
|
1296
|
-
return Object.fromEntries(
|
|
1297
|
-
Object.entries(value).map(([key, nested]) => [
|
|
1298
|
-
key,
|
|
1299
|
-
trimSeriesDeep(nested)
|
|
1300
|
-
])
|
|
1121
|
+
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
1122
|
+
try {
|
|
1123
|
+
await Promise.all([
|
|
1124
|
+
(0, import_redis.setData)(import_redis.redisKeys.runtimeTrade(userName, existing.orderId), next, {
|
|
1125
|
+
expire: 0
|
|
1126
|
+
}),
|
|
1127
|
+
(0, import_redis.setHashJsonField)(
|
|
1128
|
+
import_redis.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
1129
|
+
existing.orderId,
|
|
1130
|
+
next,
|
|
1131
|
+
{ expire: 0 }
|
|
1132
|
+
)
|
|
1133
|
+
]);
|
|
1134
|
+
} catch (error) {
|
|
1135
|
+
import_logger.logger.error(
|
|
1136
|
+
"runtime trade increase journal failed: %s %s",
|
|
1137
|
+
symbol,
|
|
1138
|
+
error?.message || String(error)
|
|
1301
1139
|
);
|
|
1302
1140
|
}
|
|
1303
|
-
return
|
|
1141
|
+
return next;
|
|
1304
1142
|
};
|
|
1305
|
-
var
|
|
1306
|
-
const
|
|
1307
|
-
if (
|
|
1308
|
-
return
|
|
1143
|
+
var getActiveRuntimeTrade = async (params) => {
|
|
1144
|
+
const { userName, symbol, accountId, deploymentId } = params;
|
|
1145
|
+
if (!userName) {
|
|
1146
|
+
return null;
|
|
1309
1147
|
}
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
if (!
|
|
1148
|
+
const activeRef = await (0, import_redis.getData)(
|
|
1149
|
+
import_redis.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId),
|
|
1150
|
+
null
|
|
1151
|
+
);
|
|
1152
|
+
const orderId = String(activeRef?.orderId || "").trim();
|
|
1153
|
+
if (!orderId) {
|
|
1316
1154
|
return null;
|
|
1317
1155
|
}
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
};
|
|
1328
|
-
var buildMissingSpreadContext = () => ({
|
|
1329
|
-
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1330
|
-
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1331
|
-
available: false,
|
|
1332
|
-
value: null,
|
|
1333
|
-
zScore: null,
|
|
1334
|
-
bps: null,
|
|
1335
|
-
absBps: null,
|
|
1336
|
-
bias: null,
|
|
1337
|
-
severity: null
|
|
1338
|
-
});
|
|
1339
|
-
var buildSpreadContextFromSignal = (signal) => {
|
|
1340
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1341
|
-
const relative = toRecord(baseContext?.relative);
|
|
1342
|
-
const execution = toRecord(relative?.execution);
|
|
1343
|
-
const spread = toFiniteNumber(execution?.venueSpread);
|
|
1344
|
-
const zScore = toFiniteNumber(execution?.venueSpreadZScore);
|
|
1345
|
-
if (spread == null) {
|
|
1346
|
-
return buildMissingSpreadContext();
|
|
1156
|
+
const existing = await (0, import_redis.getData)(
|
|
1157
|
+
import_redis.redisKeys.runtimeTrade(userName, orderId),
|
|
1158
|
+
null
|
|
1159
|
+
);
|
|
1160
|
+
if (!existing || existing.status !== "active") {
|
|
1161
|
+
await (0, import_redis.delKey)(
|
|
1162
|
+
import_redis.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
|
|
1163
|
+
);
|
|
1164
|
+
return null;
|
|
1347
1165
|
}
|
|
1348
|
-
|
|
1349
|
-
const bps = roundTo(value * 1e4, 2);
|
|
1350
|
-
const absBps = Math.abs(bps);
|
|
1351
|
-
const bias = absBps < 1 ? "flat" : bps > 0 ? "coinbase_premium" : "binance_premium";
|
|
1352
|
-
const severity = absBps >= 20 ? "wide" : absBps >= 5 ? "elevated" : "normal";
|
|
1353
|
-
return {
|
|
1354
|
-
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1355
|
-
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1356
|
-
available: true,
|
|
1357
|
-
value,
|
|
1358
|
-
zScore,
|
|
1359
|
-
bps,
|
|
1360
|
-
absBps,
|
|
1361
|
-
bias,
|
|
1362
|
-
severity
|
|
1363
|
-
};
|
|
1166
|
+
return existing;
|
|
1364
1167
|
};
|
|
1365
|
-
var
|
|
1366
|
-
const
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
signedVolumeZScore: null
|
|
1381
|
-
};
|
|
1168
|
+
var markRuntimeTradeClosed = async (params) => {
|
|
1169
|
+
const {
|
|
1170
|
+
userName,
|
|
1171
|
+
expectedOrderId,
|
|
1172
|
+
symbol,
|
|
1173
|
+
strategy,
|
|
1174
|
+
exitPrice,
|
|
1175
|
+
exitTimestamp,
|
|
1176
|
+
closedPnl,
|
|
1177
|
+
exitType,
|
|
1178
|
+
accountId,
|
|
1179
|
+
deploymentId
|
|
1180
|
+
} = params;
|
|
1181
|
+
if (!userName) {
|
|
1182
|
+
return null;
|
|
1382
1183
|
}
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
};
|
|
1394
|
-
var buildTradeFlowContextFromSignal = (signal) => {
|
|
1395
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1396
|
-
const participation = toRecord(baseContext?.participation);
|
|
1397
|
-
const tradeFlow = toRecord(participation?.tradeFlow);
|
|
1398
|
-
if (!tradeFlow) {
|
|
1399
|
-
return {
|
|
1400
|
-
source: null,
|
|
1401
|
-
available: false,
|
|
1402
|
-
interval: null,
|
|
1403
|
-
stale: null,
|
|
1404
|
-
trades: null,
|
|
1405
|
-
buyPressurePct: null,
|
|
1406
|
-
netBaseDelta: null,
|
|
1407
|
-
netQuoteDelta: null
|
|
1408
|
-
};
|
|
1184
|
+
const existing = expectedOrderId ? await (0, import_redis.getDataStrict)(
|
|
1185
|
+
import_redis.redisKeys.runtimeTrade(userName, expectedOrderId)
|
|
1186
|
+
) : await getActiveRuntimeTrade({
|
|
1187
|
+
userName,
|
|
1188
|
+
symbol,
|
|
1189
|
+
accountId,
|
|
1190
|
+
deploymentId
|
|
1191
|
+
});
|
|
1192
|
+
if (!existing || expectedOrderId && existing.orderId !== expectedOrderId) {
|
|
1193
|
+
return null;
|
|
1409
1194
|
}
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1195
|
+
const orderId = existing.orderId;
|
|
1196
|
+
if (strategy && existing.strategy !== strategy) {
|
|
1197
|
+
return null;
|
|
1198
|
+
}
|
|
1199
|
+
const resolvedExitPrice = typeof exitPrice === "number" && Number.isFinite(exitPrice) ? exitPrice : existing.currentPrice ?? existing.entryPrice;
|
|
1200
|
+
const resolvedClosedPnl = typeof closedPnl === "number" && Number.isFinite(closedPnl) ? closedPnl : typeof resolvedExitPrice === "number" && Number.isFinite(resolvedExitPrice) ? calculateClosedPnl({
|
|
1201
|
+
direction: existing.direction,
|
|
1202
|
+
entryPrice: existing.entryPrice,
|
|
1203
|
+
exitPrice: resolvedExitPrice,
|
|
1204
|
+
qty: existing.qty
|
|
1205
|
+
}) : existing.closedPnl ?? existing.currentPnl ?? null;
|
|
1206
|
+
const next = expectedOrderId && existing.status === "closed" ? existing : {
|
|
1207
|
+
...existing,
|
|
1208
|
+
status: "closed",
|
|
1209
|
+
currentPrice: resolvedExitPrice,
|
|
1210
|
+
currentPnl: resolvedClosedPnl,
|
|
1211
|
+
closedPnl: resolvedClosedPnl,
|
|
1212
|
+
exitPrice: resolvedExitPrice,
|
|
1213
|
+
exitTimestamp: typeof exitTimestamp === "number" && Number.isFinite(exitTimestamp) ? exitTimestamp : now(),
|
|
1214
|
+
exitType: exitType ?? existing.exitType ?? null,
|
|
1215
|
+
lastSyncedAt: now()
|
|
1419
1216
|
};
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1217
|
+
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
1218
|
+
const closeDayKey = (0, import_time.getRuntimeStorageDayKey)(next.exitTimestamp);
|
|
1219
|
+
try {
|
|
1220
|
+
await Promise.all([
|
|
1221
|
+
(expectedOrderId ? import_redis.setDataStrict : import_redis.setData)(
|
|
1222
|
+
import_redis.redisKeys.runtimeTrade(userName, orderId),
|
|
1223
|
+
next,
|
|
1224
|
+
{
|
|
1225
|
+
expire: import_constants.TTL_1M
|
|
1226
|
+
}
|
|
1227
|
+
),
|
|
1228
|
+
(expectedOrderId ? import_redis.setHashJsonFieldStrict : import_redis.setHashJsonField)(
|
|
1229
|
+
import_redis.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
1230
|
+
orderId,
|
|
1231
|
+
next,
|
|
1232
|
+
{ expire: import_constants.TTL_1M }
|
|
1233
|
+
),
|
|
1234
|
+
(expectedOrderId ? import_redis.setHashJsonFieldStrict : import_redis.setHashJsonField)(
|
|
1235
|
+
import_redis.redisKeys.runtimeClosedTradeBucket(userName, closeDayKey),
|
|
1236
|
+
orderId,
|
|
1237
|
+
next,
|
|
1238
|
+
{ expire: import_constants.TTL_1M }
|
|
1239
|
+
),
|
|
1240
|
+
expectedOrderId ? (0, import_redis.deleteRuntimeTradeReference)(
|
|
1241
|
+
import_redis.redisKeys.runtimeActiveTrade(
|
|
1242
|
+
userName,
|
|
1243
|
+
symbol,
|
|
1244
|
+
deploymentId ?? accountId
|
|
1245
|
+
),
|
|
1246
|
+
expectedOrderId
|
|
1247
|
+
) : (0, import_redis.delKey)(
|
|
1248
|
+
import_redis.redisKeys.runtimeActiveTrade(
|
|
1249
|
+
userName,
|
|
1250
|
+
symbol,
|
|
1251
|
+
deploymentId ?? accountId
|
|
1252
|
+
)
|
|
1253
|
+
)
|
|
1254
|
+
]);
|
|
1255
|
+
} catch (error) {
|
|
1256
|
+
if (expectedOrderId) throw error;
|
|
1257
|
+
import_logger.logger.error(
|
|
1258
|
+
"runtime trade close journal failed: %s %s",
|
|
1259
|
+
symbol,
|
|
1260
|
+
error?.message || String(error)
|
|
1261
|
+
);
|
|
1440
1262
|
}
|
|
1441
|
-
return
|
|
1442
|
-
source: String(breadth.source ?? ""),
|
|
1443
|
-
available: true,
|
|
1444
|
-
universe: String(breadth.universe ?? ""),
|
|
1445
|
-
interval: String(breadth.interval ?? ""),
|
|
1446
|
-
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
1447
|
-
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
1448
|
-
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
1449
|
-
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
1450
|
-
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
1451
|
-
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
1452
|
-
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
1453
|
-
dispersion: toFiniteNumber(breadth.dispersion)
|
|
1454
|
-
};
|
|
1263
|
+
return next;
|
|
1455
1264
|
};
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1265
|
+
|
|
1266
|
+
// src/strategy/portfolioRuntime.ts
|
|
1267
|
+
var coordinators = /* @__PURE__ */ new WeakMap();
|
|
1268
|
+
async function observeRuntimePortfolio(connector, binding, asOf, reservations, observedNow = Date.now) {
|
|
1269
|
+
if (!connector.getOpenPositionPnl || !connector.getEntryExecutions)
|
|
1270
|
+
throw new Error(
|
|
1271
|
+
"Portfolio policies require position marks and entry executions"
|
|
1272
|
+
);
|
|
1273
|
+
const orders = await Promise.all(
|
|
1274
|
+
reservations.map(async (r) => {
|
|
1275
|
+
if (r.submitted && connector.getEntryOrderObservation) {
|
|
1276
|
+
const fact2 = await connector.getEntryOrderObservation({
|
|
1277
|
+
symbol: r.symbol,
|
|
1278
|
+
orderId: r.id
|
|
1279
|
+
});
|
|
1280
|
+
if (fact2) return fact2;
|
|
1281
|
+
}
|
|
1282
|
+
const executions = r.submitted ? await connector.getEntryExecutions({
|
|
1283
|
+
symbol: r.symbol,
|
|
1284
|
+
startTime: r.timestamp,
|
|
1285
|
+
endTime: asOf
|
|
1286
|
+
}) : [];
|
|
1287
|
+
const filledQty = executions.filter(
|
|
1288
|
+
(e) => (e.orderLinkId === r.id || e.orderId === r.id) && e.direction === r.direction
|
|
1289
|
+
).reduce((sum, e) => sum + e.qty, 0);
|
|
1463
1290
|
return {
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1291
|
+
id: r.id,
|
|
1292
|
+
status: filledQty >= r.qty ? "filled" : filledQty > 0 ? "partial" : "pending",
|
|
1293
|
+
filledQty,
|
|
1294
|
+
positionId: r.id
|
|
1295
|
+
};
|
|
1296
|
+
})
|
|
1297
|
+
);
|
|
1298
|
+
const snapshots2 = await connector.getOpenPositionPnl();
|
|
1299
|
+
const positions = await Promise.all(
|
|
1300
|
+
snapshots2.map(async (p) => {
|
|
1301
|
+
const reserved = reservations.find(
|
|
1302
|
+
(r) => r.symbol === p.symbol && r.direction === p.direction && orders.some(
|
|
1303
|
+
(o) => o.id === r.id && o.filledQty > 0 && p.qty <= o.filledQty * (1 + 1e-8)
|
|
1304
|
+
)
|
|
1305
|
+
);
|
|
1306
|
+
const trade = await getActiveRuntimeTrade({
|
|
1307
|
+
...binding,
|
|
1308
|
+
symbol: p.symbol
|
|
1309
|
+
});
|
|
1310
|
+
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;
|
|
1311
|
+
return {
|
|
1312
|
+
id: reserved?.id ?? owner?.orderId ?? `unknown:${p.symbol}:${p.direction}`,
|
|
1313
|
+
strategy: reserved?.strategy ?? owner?.strategy ?? null,
|
|
1314
|
+
symbol: p.symbol,
|
|
1315
|
+
direction: p.direction,
|
|
1316
|
+
qty: p.qty,
|
|
1317
|
+
entryPrice: p.price,
|
|
1318
|
+
markPrice: p.currentPrice,
|
|
1319
|
+
markedAt: p.observedAt ?? null,
|
|
1320
|
+
stopLossPrice: p.slPrice ?? p.stopLossPrice ?? null
|
|
1476
1321
|
};
|
|
1322
|
+
})
|
|
1323
|
+
);
|
|
1324
|
+
return { asOf: Math.max(asOf, observedNow()), positions, orders };
|
|
1325
|
+
}
|
|
1326
|
+
function persistentStore(key) {
|
|
1327
|
+
const raw = /* @__PURE__ */ new WeakMap();
|
|
1328
|
+
return {
|
|
1329
|
+
async read() {
|
|
1330
|
+
const value = await import_redis2.portfolioStateStorage.read(key);
|
|
1331
|
+
if (value === null) return null;
|
|
1332
|
+
const state = JSON.parse(value);
|
|
1333
|
+
(0, import_portfolio2.assertPortfolioState)(state);
|
|
1334
|
+
raw.set(state, value);
|
|
1335
|
+
return state;
|
|
1336
|
+
},
|
|
1337
|
+
async compare(previous, next) {
|
|
1338
|
+
const expected = previous === null ? null : raw.get(previous);
|
|
1339
|
+
if (expected === void 0)
|
|
1340
|
+
throw new Error("Portfolio CAS requires a storage snapshot");
|
|
1341
|
+
return import_redis2.portfolioStateStorage.compare(key, expected, JSON.stringify(next));
|
|
1477
1342
|
}
|
|
1478
|
-
return {
|
|
1479
|
-
source: String(breadth.source ?? ""),
|
|
1480
|
-
available: true,
|
|
1481
|
-
universe: String(breadth.universe ?? ""),
|
|
1482
|
-
interval: String(breadth.interval ?? ""),
|
|
1483
|
-
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
1484
|
-
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
1485
|
-
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
1486
|
-
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
1487
|
-
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
1488
|
-
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
1489
|
-
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
1490
|
-
dispersion: toFiniteNumber(breadth.dispersion)
|
|
1491
|
-
};
|
|
1492
1343
|
};
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1344
|
+
}
|
|
1345
|
+
function getPortfolioRuntime({
|
|
1346
|
+
value,
|
|
1347
|
+
connectorName,
|
|
1348
|
+
strategy,
|
|
1349
|
+
connector,
|
|
1350
|
+
env,
|
|
1351
|
+
userName,
|
|
1352
|
+
accountId,
|
|
1353
|
+
deploymentId
|
|
1354
|
+
}) {
|
|
1355
|
+
const policy = (0, import_portfolio2.parsePortfolioPolicy)(value);
|
|
1356
|
+
if (!policy) return void 0;
|
|
1357
|
+
const replay = (env === "BACKTEST" || env === "PARITY") && Reflect.get(connector, "__tradejsReplayConnector") === true;
|
|
1358
|
+
if ((env === "BACKTEST" || env === "PARITY") && (!replay || !connector.getPortfolioObservation))
|
|
1359
|
+
throw new Error(
|
|
1360
|
+
"Portfolio policies require shared portfolio replay; isolated symbol backtests are unsupported"
|
|
1361
|
+
);
|
|
1362
|
+
if (!replay && (!userName || !accountId || !deploymentId || !connector.getOpenPositionPnl || !connector.getEntryExecutions))
|
|
1363
|
+
throw new Error(
|
|
1364
|
+
"Portfolio runtime requires exact user/account/deployment binding, marks and executions"
|
|
1365
|
+
);
|
|
1366
|
+
const binding = {
|
|
1367
|
+
userName,
|
|
1368
|
+
accountId,
|
|
1369
|
+
deploymentId
|
|
1499
1370
|
};
|
|
1371
|
+
const scope = policy.scope === "strategy" ? strategy : policy.scope === "account" ? "account" : [...policy.scope.strategies].sort();
|
|
1372
|
+
const key = (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify([binding, connectorName, connector.universe, scope])).digest("hex");
|
|
1373
|
+
const fingerprint3 = (0, import_portfolio2.portfolioFingerprint)(policy, strategy);
|
|
1374
|
+
const cacheKey = key;
|
|
1375
|
+
let registry = coordinators.get(connector);
|
|
1376
|
+
if (!registry) {
|
|
1377
|
+
registry = /* @__PURE__ */ new Map();
|
|
1378
|
+
coordinators.set(connector, registry);
|
|
1379
|
+
}
|
|
1380
|
+
const existing = registry.get(cacheKey);
|
|
1381
|
+
if (existing) {
|
|
1382
|
+
if (existing.fingerprint !== fingerprint3)
|
|
1383
|
+
throw new Error("Conflicting portfolio configuration for the same scope");
|
|
1384
|
+
return existing.coordinator;
|
|
1385
|
+
}
|
|
1386
|
+
const coordinator = createPortfolioCoordinator({
|
|
1387
|
+
policy,
|
|
1388
|
+
strategy,
|
|
1389
|
+
store: replay ? createMemoryPortfolioStore() : persistentStore(key),
|
|
1390
|
+
execution: {
|
|
1391
|
+
async confirmClosed(intent, asOf) {
|
|
1392
|
+
if (replay) return;
|
|
1393
|
+
const position = intent.position;
|
|
1394
|
+
await markRuntimeTradeClosed({
|
|
1395
|
+
...binding,
|
|
1396
|
+
strategy: position.strategy,
|
|
1397
|
+
expectedOrderId: position.id,
|
|
1398
|
+
symbol: position.symbol,
|
|
1399
|
+
exitPrice: position.markPrice,
|
|
1400
|
+
exitTimestamp: asOf,
|
|
1401
|
+
exitType: "exit"
|
|
1402
|
+
});
|
|
1403
|
+
},
|
|
1404
|
+
observe: (asOf, reservations) => replay ? connector.getPortfolioObservation(asOf, reservations) : observeRuntimePortfolio(connector, binding, asOf, reservations),
|
|
1405
|
+
async close(position, intent, asOf) {
|
|
1406
|
+
if (!replay) {
|
|
1407
|
+
const trade = await getActiveRuntimeTrade({
|
|
1408
|
+
...binding,
|
|
1409
|
+
symbol: position.symbol
|
|
1410
|
+
});
|
|
1411
|
+
if (!trade || trade.orderId !== position.id)
|
|
1412
|
+
throw new Error("Portfolio close ownership changed");
|
|
1413
|
+
}
|
|
1414
|
+
await connector.closePosition({
|
|
1415
|
+
symbol: position.symbol,
|
|
1416
|
+
direction: position.direction,
|
|
1417
|
+
price: position.markPrice,
|
|
1418
|
+
timestamp: asOf,
|
|
1419
|
+
signal: {
|
|
1420
|
+
signalId: `${position.id}:${intent.reason}`,
|
|
1421
|
+
strategy: position.strategy,
|
|
1422
|
+
symbol: position.symbol,
|
|
1423
|
+
direction: position.direction,
|
|
1424
|
+
interval: "1",
|
|
1425
|
+
timestamp: asOf,
|
|
1426
|
+
prices: {
|
|
1427
|
+
riskRatio: 0,
|
|
1428
|
+
currentPrice: position.markPrice,
|
|
1429
|
+
takeProfitPrice: position.markPrice,
|
|
1430
|
+
stopLossPrice: position.markPrice
|
|
1431
|
+
},
|
|
1432
|
+
figures: {},
|
|
1433
|
+
indicators: {},
|
|
1434
|
+
additionalIndicators: { portfolioExitReason: intent.reason }
|
|
1435
|
+
}
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
});
|
|
1440
|
+
registry.set(cacheKey, { fingerprint: fingerprint3, coordinator });
|
|
1441
|
+
return coordinator;
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
// src/strategyHelpers/tickerProfiles.ts
|
|
1445
|
+
var import_tickerProfiles = require("@tradejs/infra/tickerProfiles");
|
|
1446
|
+
var import_tickerProfiles2 = require("@tradejs/core/tickerProfiles");
|
|
1447
|
+
|
|
1448
|
+
// src/tradejsConfig.ts
|
|
1449
|
+
var import_fs = __toESM(require("fs"));
|
|
1450
|
+
var import_path = __toESM(require("path"));
|
|
1451
|
+
var import_url = require("url");
|
|
1452
|
+
var import_config = require("@tradejs/core/config");
|
|
1453
|
+
var import_logger2 = require("@tradejs/infra/logger");
|
|
1454
|
+
var CONFIG_FILE_NAMES = [
|
|
1455
|
+
"tradejs.config.ts",
|
|
1456
|
+
"tradejs.config.mts",
|
|
1457
|
+
"tradejs.config.js",
|
|
1458
|
+
"tradejs.config.mjs",
|
|
1459
|
+
"tradejs.config.cjs"
|
|
1460
|
+
];
|
|
1461
|
+
var TS_MODULE_RE = /\.(cts|mts|ts)$/i;
|
|
1462
|
+
var cachedByCwd = /* @__PURE__ */ new Map();
|
|
1463
|
+
var announcedConfigFile = /* @__PURE__ */ new Set();
|
|
1464
|
+
var tsNodeRegistered = false;
|
|
1465
|
+
var tsconfigPathsRegisteredByCwd = /* @__PURE__ */ new Set();
|
|
1466
|
+
var tsconfigPathMatchersByCwd = /* @__PURE__ */ new Map();
|
|
1467
|
+
var getTradejsProjectCwd = (cwd) => {
|
|
1468
|
+
const explicit = String(cwd ?? "").trim();
|
|
1469
|
+
if (explicit) {
|
|
1470
|
+
return import_path.default.resolve(explicit);
|
|
1471
|
+
}
|
|
1472
|
+
const fromEnv = String(process.env.PROJECT_CWD || "").trim();
|
|
1473
|
+
if (fromEnv) {
|
|
1474
|
+
return import_path.default.resolve(fromEnv);
|
|
1475
|
+
}
|
|
1476
|
+
return process.cwd();
|
|
1500
1477
|
};
|
|
1501
|
-
var
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
const targetVsBtc = toRecord(relative?.targetVsBtc);
|
|
1505
|
-
if (!targetVsBtc) {
|
|
1506
|
-
return {
|
|
1507
|
-
source: null,
|
|
1508
|
-
available: false,
|
|
1509
|
-
ratioReturn1h: null,
|
|
1510
|
-
ratioReturn4h: null,
|
|
1511
|
-
ratioReturn24h: null,
|
|
1512
|
-
alphaVsBtc1h: null,
|
|
1513
|
-
alphaVsBtc4h: null,
|
|
1514
|
-
alphaVsBtc24h: null,
|
|
1515
|
-
betaToBtc20: null,
|
|
1516
|
-
correlationToBtc20: null,
|
|
1517
|
-
ratioTrend: null
|
|
1518
|
-
};
|
|
1478
|
+
var normalizeConfig = (rawConfig) => {
|
|
1479
|
+
if (!rawConfig || typeof rawConfig !== "object") {
|
|
1480
|
+
return {};
|
|
1519
1481
|
}
|
|
1482
|
+
const config = rawConfig;
|
|
1483
|
+
const strategies2 = Array.isArray(config.strategies) ? config.strategies.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1484
|
+
const indicators = Array.isArray(config.indicators) ? config.indicators.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1485
|
+
const connectors = Array.isArray(config.connectors) ? config.connectors.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1486
|
+
const hooks = (0, import_config.normalizeTradejsConfigHooks)(
|
|
1487
|
+
config.hooks
|
|
1488
|
+
);
|
|
1489
|
+
const runtime = config.runtime && typeof config.runtime === "object" && !Array.isArray(config.runtime) ? config.runtime : void 0;
|
|
1520
1490
|
return {
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
alphaVsBtc1h: toFiniteNumber(targetVsBtc.alphaVsBtc1h),
|
|
1527
|
-
alphaVsBtc4h: toFiniteNumber(targetVsBtc.alphaVsBtc4h),
|
|
1528
|
-
alphaVsBtc24h: toFiniteNumber(targetVsBtc.alphaVsBtc24h),
|
|
1529
|
-
betaToBtc20: toFiniteNumber(targetVsBtc.betaToBtc20),
|
|
1530
|
-
correlationToBtc20: toFiniteNumber(targetVsBtc.correlationToBtc20),
|
|
1531
|
-
ratioTrend: typeof targetVsBtc.ratioTrend === "string" ? targetVsBtc.ratioTrend : null
|
|
1491
|
+
strategies: strategies2,
|
|
1492
|
+
indicators,
|
|
1493
|
+
connectors,
|
|
1494
|
+
...hooks ? { hooks } : {},
|
|
1495
|
+
...runtime ? { runtime } : {}
|
|
1532
1496
|
};
|
|
1533
1497
|
};
|
|
1534
|
-
var
|
|
1535
|
-
const
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
if (!btcAltRegime) {
|
|
1539
|
-
return {
|
|
1540
|
-
source: null,
|
|
1541
|
-
available: false,
|
|
1542
|
-
universe: null,
|
|
1543
|
-
interval: null,
|
|
1544
|
-
stale: null,
|
|
1545
|
-
regime: null,
|
|
1546
|
-
btcReturn24h: null,
|
|
1547
|
-
altBasketReturn24h: null,
|
|
1548
|
-
btcVsAltReturn24h: null,
|
|
1549
|
-
btcTurnoverShare24h: null,
|
|
1550
|
-
btcTurnoverShareChange24h: null,
|
|
1551
|
-
altVolToBtcVol24h: null,
|
|
1552
|
-
altDispersion24h: null
|
|
1553
|
-
};
|
|
1498
|
+
var getNodeCreateRequire = () => {
|
|
1499
|
+
const builtinModule = process.getBuiltinModule?.("module");
|
|
1500
|
+
if (typeof builtinModule?.createRequire === "function") {
|
|
1501
|
+
return builtinModule.createRequire;
|
|
1554
1502
|
}
|
|
1555
|
-
|
|
1556
|
-
source: String(btcAltRegime.source ?? ""),
|
|
1557
|
-
available: true,
|
|
1558
|
-
universe: String(btcAltRegime.universe ?? ""),
|
|
1559
|
-
interval: String(btcAltRegime.interval ?? ""),
|
|
1560
|
-
stale: typeof btcAltRegime.stale === "boolean" ? btcAltRegime.stale : null,
|
|
1561
|
-
regime: typeof btcAltRegime.regime === "string" ? btcAltRegime.regime : null,
|
|
1562
|
-
btcReturn24h: toFiniteNumber(btcAltRegime.btcReturn24h),
|
|
1563
|
-
altBasketReturn24h: toFiniteNumber(btcAltRegime.altBasketReturn24h),
|
|
1564
|
-
btcVsAltReturn24h: toFiniteNumber(btcAltRegime.btcVsAltReturn24h),
|
|
1565
|
-
btcTurnoverShare24h: toFiniteNumber(btcAltRegime.btcTurnoverShare24h),
|
|
1566
|
-
btcTurnoverShareChange24h: toFiniteNumber(
|
|
1567
|
-
btcAltRegime.btcTurnoverShareChange24h
|
|
1568
|
-
),
|
|
1569
|
-
altVolToBtcVol24h: toFiniteNumber(btcAltRegime.altVolToBtcVol24h),
|
|
1570
|
-
altDispersion24h: toFiniteNumber(btcAltRegime.altDispersion24h)
|
|
1571
|
-
};
|
|
1503
|
+
throw new TypeError("module.createRequire is not available");
|
|
1572
1504
|
};
|
|
1573
|
-
var
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1505
|
+
var getRequireFn = (cwd = getTradejsProjectCwd()) => getNodeCreateRequire()(import_path.default.join(import_path.default.resolve(cwd), "__tradejs_loader__.js"));
|
|
1506
|
+
var ensureTsNodeRegistered = async () => {
|
|
1507
|
+
if (tsNodeRegistered) {
|
|
1508
|
+
return;
|
|
1509
|
+
}
|
|
1510
|
+
const tsNodeModule = await import("ts-node");
|
|
1511
|
+
const tsNode = tsNodeModule.default ?? tsNodeModule;
|
|
1512
|
+
tsNode.register?.({
|
|
1513
|
+
transpileOnly: true,
|
|
1514
|
+
compilerOptions: {
|
|
1515
|
+
module: "Node16",
|
|
1516
|
+
moduleResolution: "node16"
|
|
1517
|
+
}
|
|
1518
|
+
});
|
|
1519
|
+
tsNodeRegistered = true;
|
|
1520
|
+
};
|
|
1521
|
+
var ensureTsconfigPathsRegistered = async (cwd = getTradejsProjectCwd()) => {
|
|
1522
|
+
const projectRoot = getTradejsProjectCwd(cwd);
|
|
1523
|
+
if (tsconfigPathsRegisteredByCwd.has(projectRoot)) {
|
|
1524
|
+
return;
|
|
1525
|
+
}
|
|
1526
|
+
const tsconfigPathsModule = await import("tsconfig-paths");
|
|
1527
|
+
const loadConfig = tsconfigPathsModule.loadConfig;
|
|
1528
|
+
const register = tsconfigPathsModule.register;
|
|
1529
|
+
if (typeof loadConfig !== "function" || typeof register !== "function") {
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
const loadedConfig = loadConfig(projectRoot);
|
|
1533
|
+
if (loadedConfig.resultType !== "success") {
|
|
1534
|
+
return;
|
|
1535
|
+
}
|
|
1536
|
+
register({
|
|
1537
|
+
baseUrl: loadedConfig.absoluteBaseUrl,
|
|
1538
|
+
paths: loadedConfig.paths,
|
|
1539
|
+
addMatchAll: false
|
|
1540
|
+
});
|
|
1541
|
+
tsconfigPathsRegisteredByCwd.add(projectRoot);
|
|
1542
|
+
};
|
|
1543
|
+
var resolveTsconfigPathModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
1544
|
+
const projectRoot = getTradejsProjectCwd(cwd);
|
|
1545
|
+
const cachedMatcher = tsconfigPathMatchersByCwd.get(projectRoot);
|
|
1546
|
+
if (cachedMatcher) {
|
|
1547
|
+
const resolved2 = cachedMatcher(moduleName);
|
|
1548
|
+
return resolved2 || null;
|
|
1549
|
+
}
|
|
1550
|
+
const tsconfigPathsModule = await import("tsconfig-paths");
|
|
1551
|
+
const loadConfig = tsconfigPathsModule.loadConfig;
|
|
1552
|
+
const createMatchPath = tsconfigPathsModule.createMatchPath;
|
|
1553
|
+
if (typeof loadConfig !== "function" || typeof createMatchPath !== "function") {
|
|
1554
|
+
return null;
|
|
1555
|
+
}
|
|
1556
|
+
const loadedConfig = loadConfig(projectRoot);
|
|
1557
|
+
if (loadedConfig.resultType !== "success") {
|
|
1558
|
+
return null;
|
|
1559
|
+
}
|
|
1560
|
+
const matchPath = createMatchPath(
|
|
1561
|
+
loadedConfig.absoluteBaseUrl,
|
|
1562
|
+
loadedConfig.paths
|
|
1563
|
+
);
|
|
1564
|
+
const matcher = (requestedModule) => matchPath(requestedModule, void 0, import_fs.default.existsSync, [
|
|
1565
|
+
".ts",
|
|
1566
|
+
".tsx",
|
|
1567
|
+
".mts",
|
|
1568
|
+
".cts",
|
|
1569
|
+
".js",
|
|
1570
|
+
".jsx",
|
|
1571
|
+
".mjs",
|
|
1572
|
+
".cjs",
|
|
1573
|
+
".json"
|
|
1574
|
+
]) || "";
|
|
1575
|
+
tsconfigPathMatchersByCwd.set(projectRoot, matcher);
|
|
1576
|
+
const resolved = matcher(moduleName);
|
|
1577
|
+
return resolved || null;
|
|
1578
|
+
};
|
|
1579
|
+
var toImportSpecifier = (moduleName) => {
|
|
1580
|
+
if (moduleName.startsWith("file://")) {
|
|
1581
|
+
return moduleName;
|
|
1582
|
+
}
|
|
1583
|
+
if (import_path.default.isAbsolute(moduleName)) {
|
|
1584
|
+
return (0, import_url.pathToFileURL)(moduleName).href;
|
|
1585
|
+
}
|
|
1586
|
+
return moduleName;
|
|
1587
|
+
};
|
|
1588
|
+
var isTsModulePath = (moduleName) => TS_MODULE_RE.test(moduleName.split("?")[0]);
|
|
1589
|
+
var isRelativeModulePath = (moduleName) => moduleName.startsWith("./") || moduleName.startsWith("../");
|
|
1590
|
+
var isBareModuleSpecifier = (moduleName) => {
|
|
1591
|
+
const normalized = String(moduleName ?? "").trim();
|
|
1592
|
+
if (!normalized) {
|
|
1593
|
+
return false;
|
|
1594
|
+
}
|
|
1595
|
+
if (normalized.startsWith("file://") || import_path.default.isAbsolute(normalized) || isRelativeModulePath(normalized)) {
|
|
1596
|
+
return false;
|
|
1597
|
+
}
|
|
1598
|
+
return true;
|
|
1599
|
+
};
|
|
1600
|
+
var importConfigFile = async (configFilePath) => {
|
|
1601
|
+
const ext = import_path.default.extname(configFilePath).toLowerCase();
|
|
1602
|
+
const configFileUrl = `${toImportSpecifier(configFilePath)}?t=${Date.now()}`;
|
|
1603
|
+
if (ext === ".ts" || ext === ".mts") {
|
|
1604
|
+
const requireFn = getRequireFn(import_path.default.dirname(configFilePath));
|
|
1605
|
+
await ensureTsNodeRegistered();
|
|
1606
|
+
await ensureTsconfigPathsRegistered(import_path.default.dirname(configFilePath));
|
|
1607
|
+
return requireFn(configFilePath);
|
|
1608
|
+
}
|
|
1609
|
+
return import(
|
|
1610
|
+
/* webpackIgnore: true */
|
|
1611
|
+
configFileUrl
|
|
1612
|
+
);
|
|
1613
|
+
};
|
|
1614
|
+
var importTradejsModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
1615
|
+
const normalized = String(moduleName ?? "").trim();
|
|
1616
|
+
if (!normalized) {
|
|
1617
|
+
return {};
|
|
1618
|
+
}
|
|
1619
|
+
let modulePath = normalized;
|
|
1620
|
+
if (normalized.startsWith("file://")) {
|
|
1621
|
+
try {
|
|
1622
|
+
modulePath = (0, import_url.fileURLToPath)(normalized);
|
|
1623
|
+
} catch {
|
|
1624
|
+
modulePath = normalized;
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
const requireFn = getRequireFn(
|
|
1628
|
+
import_path.default.isAbsolute(modulePath) ? import_path.default.dirname(modulePath) : cwd
|
|
1629
|
+
);
|
|
1630
|
+
if (isTsModulePath(modulePath)) {
|
|
1631
|
+
await ensureTsNodeRegistered();
|
|
1632
|
+
await ensureTsconfigPathsRegistered(cwd);
|
|
1633
|
+
return requireFn(modulePath);
|
|
1634
|
+
}
|
|
1635
|
+
if (isBareModuleSpecifier(normalized)) {
|
|
1636
|
+
await ensureTsconfigPathsRegistered(cwd);
|
|
1637
|
+
try {
|
|
1638
|
+
return requireFn(normalized);
|
|
1639
|
+
} catch (error) {
|
|
1640
|
+
const resolvedByTsconfig = await resolveTsconfigPathModule(
|
|
1641
|
+
normalized,
|
|
1642
|
+
cwd
|
|
1643
|
+
);
|
|
1644
|
+
if (resolvedByTsconfig && resolvedByTsconfig !== normalized) {
|
|
1645
|
+
return requireFn(resolvedByTsconfig);
|
|
1646
|
+
}
|
|
1647
|
+
throw error;
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
try {
|
|
1651
|
+
return await import(
|
|
1652
|
+
/* webpackIgnore: true */
|
|
1653
|
+
toImportSpecifier(normalized)
|
|
1654
|
+
);
|
|
1655
|
+
} catch (error) {
|
|
1656
|
+
if (isTsModulePath(modulePath)) {
|
|
1657
|
+
await ensureTsNodeRegistered();
|
|
1658
|
+
await ensureTsconfigPathsRegistered(cwd);
|
|
1659
|
+
return requireFn(modulePath);
|
|
1660
|
+
}
|
|
1661
|
+
throw error;
|
|
1662
|
+
}
|
|
1663
|
+
};
|
|
1664
|
+
var resolveExportedConfig = (moduleExports) => {
|
|
1665
|
+
const candidate = moduleExports && typeof moduleExports === "object" && "default" in moduleExports ? moduleExports.default : moduleExports;
|
|
1666
|
+
return normalizeConfig(candidate);
|
|
1667
|
+
};
|
|
1668
|
+
var findConfigFilePath = (cwd) => {
|
|
1669
|
+
let currentDir = import_path.default.resolve(cwd);
|
|
1670
|
+
while (true) {
|
|
1671
|
+
for (const fileName of CONFIG_FILE_NAMES) {
|
|
1672
|
+
const fullPath = import_path.default.join(currentDir, fileName);
|
|
1673
|
+
if (import_fs.default.existsSync(fullPath) && import_fs.default.statSync(fullPath).isFile()) {
|
|
1674
|
+
return fullPath;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
const parentDir = import_path.default.dirname(currentDir);
|
|
1678
|
+
if (parentDir === currentDir) {
|
|
1679
|
+
return null;
|
|
1680
|
+
}
|
|
1681
|
+
currentDir = parentDir;
|
|
1682
|
+
}
|
|
1683
|
+
};
|
|
1684
|
+
var resolvePluginModuleSpecifier = (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
1685
|
+
const normalized = String(moduleName ?? "").trim();
|
|
1686
|
+
if (!normalized) {
|
|
1687
|
+
return "";
|
|
1688
|
+
}
|
|
1689
|
+
if (normalized.startsWith("file://")) {
|
|
1690
|
+
try {
|
|
1691
|
+
return (0, import_url.fileURLToPath)(normalized);
|
|
1692
|
+
} catch {
|
|
1693
|
+
return normalized;
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
if (import_path.default.isAbsolute(normalized)) {
|
|
1697
|
+
return normalized;
|
|
1698
|
+
}
|
|
1699
|
+
if (isRelativeModulePath(normalized)) {
|
|
1700
|
+
return import_path.default.resolve(cwd, normalized);
|
|
1701
|
+
}
|
|
1702
|
+
return normalized;
|
|
1703
|
+
};
|
|
1704
|
+
var loadTradejsConfig = async (cwd = getTradejsProjectCwd()) => {
|
|
1705
|
+
const cached = cachedByCwd.get(cwd);
|
|
1706
|
+
if (cached) {
|
|
1707
|
+
return cached;
|
|
1708
|
+
}
|
|
1709
|
+
const configFilePath = findConfigFilePath(cwd);
|
|
1710
|
+
if (!configFilePath) {
|
|
1711
|
+
cachedByCwd.set(cwd, {});
|
|
1712
|
+
return {};
|
|
1713
|
+
}
|
|
1714
|
+
try {
|
|
1715
|
+
const moduleExports = await importConfigFile(configFilePath);
|
|
1716
|
+
const config = resolveExportedConfig(moduleExports);
|
|
1717
|
+
cachedByCwd.set(cwd, config);
|
|
1718
|
+
if (!announcedConfigFile.has(configFilePath)) {
|
|
1719
|
+
announcedConfigFile.add(configFilePath);
|
|
1720
|
+
import_logger2.logger.log("debug", "Loaded TradeJS config: %s", configFilePath);
|
|
1721
|
+
}
|
|
1722
|
+
return config;
|
|
1723
|
+
} catch (error) {
|
|
1724
|
+
import_logger2.logger.log(
|
|
1725
|
+
"warn",
|
|
1726
|
+
"Failed to load TradeJS config from %s: %s",
|
|
1727
|
+
configFilePath,
|
|
1728
|
+
String(error)
|
|
1729
|
+
);
|
|
1730
|
+
cachedByCwd.set(cwd, {});
|
|
1731
|
+
return {};
|
|
1732
|
+
}
|
|
1733
|
+
};
|
|
1734
|
+
|
|
1735
|
+
// src/strategy/manifests.ts
|
|
1736
|
+
var import_indicators = require("@tradejs/core/indicators");
|
|
1737
|
+
var SHARED_STRATEGY_REGISTRY_KEY = "__tradejsNodeSharedStrategyRegistryV1__";
|
|
1738
|
+
var sharedRegistryScope = globalThis;
|
|
1739
|
+
var sharedStrategyRegistry = sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] ?? (sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] = {
|
|
1740
|
+
registryStateByProjectRoot: /* @__PURE__ */ new Map()
|
|
1741
|
+
});
|
|
1742
|
+
var createStrategyRegistryState = () => ({
|
|
1743
|
+
strategyCreators: /* @__PURE__ */ new Map(),
|
|
1744
|
+
strategyManifestsMap: /* @__PURE__ */ new Map(),
|
|
1745
|
+
strategyEntriesMap: /* @__PURE__ */ new Map(),
|
|
1746
|
+
strategySourcesMap: /* @__PURE__ */ new Map(),
|
|
1747
|
+
strategyModuleResolutions: /* @__PURE__ */ new Map(),
|
|
1748
|
+
pluginsLoadPromise: null
|
|
1749
|
+
});
|
|
1750
|
+
var registryStateByProjectRoot = sharedStrategyRegistry.registryStateByProjectRoot;
|
|
1751
|
+
var getStrategyRegistryState = (cwd = getTradejsProjectCwd()) => {
|
|
1752
|
+
const projectRoot = getTradejsProjectCwd(cwd);
|
|
1753
|
+
let state = registryStateByProjectRoot.get(projectRoot);
|
|
1754
|
+
if (!state) {
|
|
1755
|
+
state = createStrategyRegistryState();
|
|
1756
|
+
registryStateByProjectRoot.set(projectRoot, state);
|
|
1757
|
+
}
|
|
1758
|
+
return {
|
|
1759
|
+
projectRoot,
|
|
1760
|
+
state
|
|
1761
|
+
};
|
|
1762
|
+
};
|
|
1763
|
+
var toUniqueModules = (modules = []) => [
|
|
1764
|
+
...new Set(modules.map((moduleName) => moduleName.trim()).filter(Boolean))
|
|
1765
|
+
];
|
|
1766
|
+
var getConfiguredPluginModuleNames = async (cwd = getTradejsProjectCwd()) => {
|
|
1767
|
+
const config = await loadTradejsConfig(cwd);
|
|
1768
|
+
return {
|
|
1769
|
+
strategyModules: toUniqueModules(config.strategies),
|
|
1770
|
+
indicatorModules: toUniqueModules(config.indicators)
|
|
1771
|
+
};
|
|
1772
|
+
};
|
|
1773
|
+
var extractModuleEntries = (moduleExport, key) => {
|
|
1774
|
+
if (!moduleExport || typeof moduleExport !== "object") {
|
|
1775
|
+
return null;
|
|
1776
|
+
}
|
|
1777
|
+
const candidate = moduleExport;
|
|
1778
|
+
if (Array.isArray(candidate[key])) {
|
|
1779
|
+
return candidate[key];
|
|
1780
|
+
}
|
|
1781
|
+
const defaultExport = candidate.default;
|
|
1782
|
+
if (defaultExport && Array.isArray(defaultExport[key])) {
|
|
1783
|
+
return defaultExport[key];
|
|
1784
|
+
}
|
|
1785
|
+
return null;
|
|
1786
|
+
};
|
|
1787
|
+
var extractStrategyPluginDefinition = (moduleExport) => {
|
|
1788
|
+
const strategyEntries = extractModuleEntries(
|
|
1789
|
+
moduleExport,
|
|
1790
|
+
"strategyEntries"
|
|
1791
|
+
);
|
|
1792
|
+
return strategyEntries ? { strategyEntries } : null;
|
|
1793
|
+
};
|
|
1794
|
+
var extractIndicatorPluginDefinition = (moduleExport) => {
|
|
1795
|
+
const indicatorEntries = extractModuleEntries(
|
|
1796
|
+
moduleExport,
|
|
1797
|
+
"indicatorEntries"
|
|
1798
|
+
);
|
|
1799
|
+
return indicatorEntries ? { indicatorEntries } : null;
|
|
1800
|
+
};
|
|
1801
|
+
var validateStrategyEntries = (moduleName, entries, state) => {
|
|
1802
|
+
const issues = [];
|
|
1803
|
+
const names = /* @__PURE__ */ new Set();
|
|
1804
|
+
entries.forEach((entry, index) => {
|
|
1805
|
+
const entryPath = `${moduleName}.strategyEntries[${index}]`;
|
|
1806
|
+
const strategyName = entry?.manifest?.name;
|
|
1807
|
+
if (typeof strategyName !== "string" || !strategyName.trim()) {
|
|
1808
|
+
issues.push(`${entryPath}: manifest.name is required`);
|
|
1809
|
+
} else if (names.has(strategyName) || state?.strategyEntriesMap.has(strategyName)) {
|
|
1810
|
+
issues.push(`${entryPath}: duplicate strategy ${strategyName}`);
|
|
1811
|
+
} else {
|
|
1812
|
+
names.add(strategyName);
|
|
1813
|
+
}
|
|
1814
|
+
if (!entry || typeof entry !== "object" || !entry.defaults || typeof entry.defaults !== "object" || Array.isArray(entry.defaults)) {
|
|
1815
|
+
issues.push(`${entryPath}: defaults must be an object`);
|
|
1816
|
+
}
|
|
1817
|
+
if (typeof entry?.parseConfig !== "function") {
|
|
1818
|
+
issues.push(`${entryPath}: parseConfig is required`);
|
|
1819
|
+
}
|
|
1820
|
+
if (typeof entry?.createCore !== "function") {
|
|
1821
|
+
issues.push(`${entryPath}: createCore is required`);
|
|
1822
|
+
}
|
|
1823
|
+
});
|
|
1824
|
+
return issues;
|
|
1825
|
+
};
|
|
1826
|
+
var registerEntries = (entries, source, state) => {
|
|
1827
|
+
for (const entry of entries) {
|
|
1828
|
+
const strategyName = entry.manifest.name;
|
|
1829
|
+
state.strategyManifestsMap.set(strategyName, entry.manifest);
|
|
1830
|
+
state.strategyEntriesMap.set(strategyName, entry);
|
|
1831
|
+
state.strategySourcesMap.set(strategyName, source);
|
|
1832
|
+
materializeStrategyCreator(strategyName, state);
|
|
1833
|
+
}
|
|
1834
|
+
};
|
|
1835
|
+
var materializeStrategyCreator = (strategyName, state) => {
|
|
1836
|
+
if (state.strategyCreators.has(strategyName) || !sharedStrategyRegistry.strategyRuntimeFactory) {
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
const entry = state.strategyEntriesMap.get(strategyName);
|
|
1840
|
+
if (!entry) return;
|
|
1841
|
+
state.strategyCreators.set(
|
|
1842
|
+
strategyName,
|
|
1843
|
+
sharedStrategyRegistry.strategyRuntimeFactory({
|
|
1844
|
+
strategyName,
|
|
1845
|
+
defaults: entry.defaults,
|
|
1846
|
+
createCore: entry.createCore,
|
|
1847
|
+
manifest: entry.manifest,
|
|
1848
|
+
detectorKey: entry.detectorKey,
|
|
1849
|
+
detectorNoSignalSkipReason: entry.detectorNoSignalSkipReason,
|
|
1850
|
+
resolveRegisteredManifest: (name) => state.strategyManifestsMap.get(name)
|
|
1851
|
+
})
|
|
1852
|
+
);
|
|
1853
|
+
};
|
|
1854
|
+
var setStrategyRuntimeFactory = (factory) => {
|
|
1855
|
+
sharedStrategyRegistry.strategyRuntimeFactory = factory;
|
|
1856
|
+
for (const state of registryStateByProjectRoot.values()) {
|
|
1857
|
+
for (const strategyName of state.strategyEntriesMap.keys()) {
|
|
1858
|
+
materializeStrategyCreator(strategyName, state);
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
};
|
|
1862
|
+
var importStrategyPluginModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
1863
|
+
if (typeof importTradejsModule === "function") {
|
|
1864
|
+
return importTradejsModule(moduleName, cwd);
|
|
1865
|
+
}
|
|
1866
|
+
return import(
|
|
1867
|
+
/* webpackIgnore: true */
|
|
1868
|
+
moduleName
|
|
1869
|
+
);
|
|
1870
|
+
};
|
|
1871
|
+
var resolveStrategyModuleEntry = async ({
|
|
1872
|
+
strategyName,
|
|
1873
|
+
moduleName,
|
|
1874
|
+
cwd
|
|
1875
|
+
}) => {
|
|
1876
|
+
const { projectRoot, state } = getStrategyRegistryState(cwd);
|
|
1877
|
+
const source = moduleName.trim();
|
|
1878
|
+
const resolvedModuleName = resolvePluginModuleSpecifier(source, projectRoot);
|
|
1879
|
+
const cacheKey = `${resolvedModuleName}\0${strategyName}`;
|
|
1880
|
+
const cached = state.strategyModuleResolutions.get(cacheKey);
|
|
1881
|
+
if (cached) return cached;
|
|
1882
|
+
const resolution = (async () => {
|
|
1883
|
+
const moduleExport = await importStrategyPluginModule(
|
|
1884
|
+
resolvedModuleName,
|
|
1885
|
+
projectRoot
|
|
1886
|
+
);
|
|
1887
|
+
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
1888
|
+
if (!pluginDefinition) {
|
|
1889
|
+
throw new Error(`${source}: export { strategyEntries } is missing`);
|
|
1890
|
+
}
|
|
1891
|
+
const issues = validateStrategyEntries(
|
|
1892
|
+
source,
|
|
1893
|
+
pluginDefinition.strategyEntries
|
|
1894
|
+
);
|
|
1895
|
+
if (issues.length > 0) {
|
|
1896
|
+
throw new Error(
|
|
1897
|
+
["Invalid TradeJS strategy module:", ...issues].join("\n")
|
|
1898
|
+
);
|
|
1899
|
+
}
|
|
1900
|
+
const entry = pluginDefinition.strategyEntries.find(
|
|
1901
|
+
(candidate) => candidate.manifest.name === strategyName
|
|
1902
|
+
);
|
|
1903
|
+
if (!entry) {
|
|
1904
|
+
throw new Error(`${source}: strategy ${strategyName} is missing`);
|
|
1905
|
+
}
|
|
1906
|
+
const factory = sharedStrategyRegistry.strategyRuntimeFactory;
|
|
1907
|
+
if (!factory) {
|
|
1908
|
+
throw new Error("Strategy runtime factory is not configured");
|
|
1909
|
+
}
|
|
1910
|
+
const manifestsByName = new Map(
|
|
1911
|
+
pluginDefinition.strategyEntries.map(
|
|
1912
|
+
(candidate) => [candidate.manifest.name, candidate.manifest]
|
|
1913
|
+
)
|
|
1914
|
+
);
|
|
1915
|
+
return {
|
|
1916
|
+
entry,
|
|
1917
|
+
creator: factory({
|
|
1918
|
+
strategyName,
|
|
1919
|
+
defaults: entry.defaults,
|
|
1920
|
+
createCore: entry.createCore,
|
|
1921
|
+
manifest: entry.manifest,
|
|
1922
|
+
detectorKey: entry.detectorKey,
|
|
1923
|
+
detectorNoSignalSkipReason: entry.detectorNoSignalSkipReason,
|
|
1924
|
+
resolveRegisteredManifest: (name) => manifestsByName.get(name) ?? state.strategyManifestsMap.get(name)
|
|
1925
|
+
}),
|
|
1926
|
+
source
|
|
1600
1927
|
};
|
|
1928
|
+
})();
|
|
1929
|
+
state.strategyModuleResolutions.set(cacheKey, resolution);
|
|
1930
|
+
try {
|
|
1931
|
+
return await resolution;
|
|
1932
|
+
} catch (error) {
|
|
1933
|
+
state.strategyModuleResolutions.delete(cacheKey);
|
|
1934
|
+
throw error;
|
|
1935
|
+
}
|
|
1936
|
+
};
|
|
1937
|
+
var ensureStrategyPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
|
|
1938
|
+
const { projectRoot, state } = getStrategyRegistryState(cwd);
|
|
1939
|
+
if (!state.pluginsLoadPromise) {
|
|
1940
|
+
(0, import_indicators.resetIndicatorRegistryCache)(projectRoot);
|
|
1941
|
+
state.pluginsLoadPromise = (async () => {
|
|
1942
|
+
const { strategyModules, indicatorModules } = await getConfiguredPluginModuleNames(projectRoot);
|
|
1943
|
+
const strategySet = new Set(strategyModules);
|
|
1944
|
+
const indicatorSet = new Set(indicatorModules);
|
|
1945
|
+
const pluginModuleNames = [
|
|
1946
|
+
.../* @__PURE__ */ new Set([...strategyModules, ...indicatorModules])
|
|
1947
|
+
];
|
|
1948
|
+
if (!pluginModuleNames.length) {
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
const issues = [];
|
|
1952
|
+
for (const moduleName of pluginModuleNames) {
|
|
1953
|
+
try {
|
|
1954
|
+
const resolvedModuleName = resolvePluginModuleSpecifier(
|
|
1955
|
+
moduleName,
|
|
1956
|
+
projectRoot
|
|
1957
|
+
);
|
|
1958
|
+
const moduleExport = await importStrategyPluginModule(
|
|
1959
|
+
resolvedModuleName,
|
|
1960
|
+
projectRoot
|
|
1961
|
+
);
|
|
1962
|
+
if (strategySet.has(moduleName)) {
|
|
1963
|
+
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
1964
|
+
if (!pluginDefinition) {
|
|
1965
|
+
issues.push(
|
|
1966
|
+
`${moduleName}: export { strategyEntries } is missing`
|
|
1967
|
+
);
|
|
1968
|
+
} else {
|
|
1969
|
+
const entryIssues = validateStrategyEntries(
|
|
1970
|
+
moduleName,
|
|
1971
|
+
pluginDefinition.strategyEntries,
|
|
1972
|
+
state
|
|
1973
|
+
);
|
|
1974
|
+
issues.push(...entryIssues);
|
|
1975
|
+
if (entryIssues.length === 0) {
|
|
1976
|
+
registerEntries(
|
|
1977
|
+
pluginDefinition.strategyEntries,
|
|
1978
|
+
moduleName,
|
|
1979
|
+
state
|
|
1980
|
+
);
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
if (indicatorSet.has(moduleName)) {
|
|
1985
|
+
const indicatorPluginDefinition = extractIndicatorPluginDefinition(moduleExport);
|
|
1986
|
+
if (!indicatorPluginDefinition) {
|
|
1987
|
+
issues.push(
|
|
1988
|
+
`${moduleName}: export { indicatorEntries } is missing`
|
|
1989
|
+
);
|
|
1990
|
+
} else {
|
|
1991
|
+
(0, import_indicators.registerIndicatorEntries)(
|
|
1992
|
+
indicatorPluginDefinition.indicatorEntries,
|
|
1993
|
+
moduleName,
|
|
1994
|
+
projectRoot
|
|
1995
|
+
);
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
if (!strategySet.has(moduleName) && !indicatorSet.has(moduleName)) {
|
|
1999
|
+
issues.push(`${moduleName}: plugin is not declared in config`);
|
|
2000
|
+
}
|
|
2001
|
+
} catch (error) {
|
|
2002
|
+
issues.push(`${moduleName}: failed to import: ${String(error)}`);
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
if (issues.length > 0) {
|
|
2006
|
+
throw new Error(
|
|
2007
|
+
["Invalid TradeJS plugin catalog:", ...issues].join("\n")
|
|
2008
|
+
);
|
|
2009
|
+
}
|
|
2010
|
+
})();
|
|
1601
2011
|
}
|
|
1602
|
-
|
|
1603
|
-
source: String(cmcGlobal.source ?? ""),
|
|
1604
|
-
available: true,
|
|
1605
|
-
interval: typeof cmcGlobal.interval === "string" ? cmcGlobal.interval : null,
|
|
1606
|
-
asOfTs: toFiniteNumber(cmcGlobal.asOfTs),
|
|
1607
|
-
stale: typeof cmcGlobal.stale === "boolean" ? cmcGlobal.stale : null,
|
|
1608
|
-
totalMarketCapUsd: toFiniteNumber(cmcGlobal.totalMarketCapUsd),
|
|
1609
|
-
totalVolumeUsd: toFiniteNumber(cmcGlobal.totalVolumeUsd),
|
|
1610
|
-
totalVolumeReportedUsd: toFiniteNumber(cmcGlobal.totalVolumeReportedUsd),
|
|
1611
|
-
altMarketCapUsd: toFiniteNumber(cmcGlobal.altMarketCapUsd),
|
|
1612
|
-
altVolumeUsd: toFiniteNumber(cmcGlobal.altVolumeUsd),
|
|
1613
|
-
altVolumeReportedUsd: toFiniteNumber(cmcGlobal.altVolumeReportedUsd),
|
|
1614
|
-
btcDominancePct: toFiniteNumber(cmcGlobal.btcDominancePct),
|
|
1615
|
-
ethDominancePct: toFiniteNumber(cmcGlobal.ethDominancePct),
|
|
1616
|
-
btcDominanceChange24hPct: toFiniteNumber(
|
|
1617
|
-
cmcGlobal.btcDominanceChange24hPct
|
|
1618
|
-
),
|
|
1619
|
-
ethDominanceChange24hPct: toFiniteNumber(
|
|
1620
|
-
cmcGlobal.ethDominanceChange24hPct
|
|
1621
|
-
),
|
|
1622
|
-
altMarketCapChange24hPct: toFiniteNumber(
|
|
1623
|
-
cmcGlobal.altMarketCapChange24hPct
|
|
1624
|
-
),
|
|
1625
|
-
altVolumeChange24hPct: toFiniteNumber(cmcGlobal.altVolumeChange24hPct),
|
|
1626
|
-
activeCryptocurrencies: toFiniteNumber(cmcGlobal.activeCryptocurrencies),
|
|
1627
|
-
activeExchanges: toFiniteNumber(cmcGlobal.activeExchanges),
|
|
1628
|
-
activeMarketPairs: toFiniteNumber(cmcGlobal.activeMarketPairs),
|
|
1629
|
-
altLiquidityRegime: typeof cmcGlobal.altLiquidityRegime === "string" ? cmcGlobal.altLiquidityRegime : null
|
|
1630
|
-
};
|
|
2012
|
+
await state.pluginsLoadPromise;
|
|
1631
2013
|
};
|
|
1632
|
-
var
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
return {
|
|
1638
|
-
source: null,
|
|
1639
|
-
available: false,
|
|
1640
|
-
interval: null,
|
|
1641
|
-
asOfTs: null,
|
|
1642
|
-
stale: null,
|
|
1643
|
-
btcMarketCapUsd: null,
|
|
1644
|
-
ethMarketCapUsd: null,
|
|
1645
|
-
btcVolumeUsd: null,
|
|
1646
|
-
ethVolumeUsd: null,
|
|
1647
|
-
btcVolumeToMarketCap: null,
|
|
1648
|
-
ethVolumeToMarketCap: null,
|
|
1649
|
-
ethBtcMarketCapRatio: null,
|
|
1650
|
-
ethBtcMarketCapRatioChange24hPct: null,
|
|
1651
|
-
ethVsBtcVolumeRatio: null,
|
|
1652
|
-
referenceLiquidityRegime: null
|
|
1653
|
-
};
|
|
2014
|
+
var ensureIndicatorPluginsLoaded = async (cwd = getTradejsProjectCwd()) => ensureStrategyPluginsLoaded(cwd);
|
|
2015
|
+
var getStrategyCreator = async (name, cwd = getTradejsProjectCwd(), moduleName) => {
|
|
2016
|
+
if (moduleName?.trim()) {
|
|
2017
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
2018
|
+
return (await resolveStrategyModuleEntry({ strategyName: name, moduleName, cwd })).creator;
|
|
1654
2019
|
}
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
interval: typeof cmcReferenceAssets.interval === "string" ? cmcReferenceAssets.interval : null,
|
|
1659
|
-
asOfTs: toFiniteNumber(cmcReferenceAssets.asOfTs),
|
|
1660
|
-
stale: typeof cmcReferenceAssets.stale === "boolean" ? cmcReferenceAssets.stale : null,
|
|
1661
|
-
btcMarketCapUsd: toFiniteNumber(cmcReferenceAssets.btcMarketCapUsd),
|
|
1662
|
-
ethMarketCapUsd: toFiniteNumber(cmcReferenceAssets.ethMarketCapUsd),
|
|
1663
|
-
btcVolumeUsd: toFiniteNumber(cmcReferenceAssets.btcVolumeUsd),
|
|
1664
|
-
ethVolumeUsd: toFiniteNumber(cmcReferenceAssets.ethVolumeUsd),
|
|
1665
|
-
btcVolumeToMarketCap: toFiniteNumber(
|
|
1666
|
-
cmcReferenceAssets.btcVolumeToMarketCap
|
|
1667
|
-
),
|
|
1668
|
-
ethVolumeToMarketCap: toFiniteNumber(
|
|
1669
|
-
cmcReferenceAssets.ethVolumeToMarketCap
|
|
1670
|
-
),
|
|
1671
|
-
ethBtcMarketCapRatio: toFiniteNumber(
|
|
1672
|
-
cmcReferenceAssets.ethBtcMarketCapRatio
|
|
1673
|
-
),
|
|
1674
|
-
ethBtcMarketCapRatioChange24hPct: toFiniteNumber(
|
|
1675
|
-
cmcReferenceAssets.ethBtcMarketCapRatioChange24hPct
|
|
1676
|
-
),
|
|
1677
|
-
ethVsBtcVolumeRatio: toFiniteNumber(cmcReferenceAssets.ethVsBtcVolumeRatio),
|
|
1678
|
-
referenceLiquidityRegime: typeof cmcReferenceAssets.referenceLiquidityRegime === "string" ? cmcReferenceAssets.referenceLiquidityRegime : null
|
|
1679
|
-
};
|
|
2020
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
2021
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
2022
|
+
return state.strategyCreators.get(name);
|
|
1680
2023
|
};
|
|
1681
|
-
var
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
if (!cmcExchangeLiquidity) {
|
|
1686
|
-
return {
|
|
1687
|
-
source: null,
|
|
1688
|
-
available: false,
|
|
1689
|
-
interval: null,
|
|
1690
|
-
asOfTs: null,
|
|
1691
|
-
stale: null,
|
|
1692
|
-
exchangesCount: null,
|
|
1693
|
-
totalVolumeUsd: null,
|
|
1694
|
-
totalVolumeChange24hPct: null,
|
|
1695
|
-
binanceVolumeUsd: null,
|
|
1696
|
-
binanceVolumeShare: null,
|
|
1697
|
-
topExchangeVolumeShare: null,
|
|
1698
|
-
liquidityRegime: null
|
|
1699
|
-
};
|
|
2024
|
+
var getStrategyEntry = async (name, cwd = getTradejsProjectCwd(), moduleName) => {
|
|
2025
|
+
if (moduleName?.trim()) {
|
|
2026
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
2027
|
+
return (await resolveStrategyModuleEntry({ strategyName: name, moduleName, cwd })).entry;
|
|
1700
2028
|
}
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
interval: typeof cmcExchangeLiquidity.interval === "string" ? cmcExchangeLiquidity.interval : null,
|
|
1705
|
-
asOfTs: toFiniteNumber(cmcExchangeLiquidity.asOfTs),
|
|
1706
|
-
stale: typeof cmcExchangeLiquidity.stale === "boolean" ? cmcExchangeLiquidity.stale : null,
|
|
1707
|
-
exchangesCount: toFiniteNumber(cmcExchangeLiquidity.exchangesCount),
|
|
1708
|
-
totalVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.totalVolumeUsd),
|
|
1709
|
-
totalVolumeChange24hPct: toFiniteNumber(
|
|
1710
|
-
cmcExchangeLiquidity.totalVolumeChange24hPct
|
|
1711
|
-
),
|
|
1712
|
-
binanceVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeUsd),
|
|
1713
|
-
binanceVolumeShare: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeShare),
|
|
1714
|
-
topExchangeVolumeShare: toFiniteNumber(
|
|
1715
|
-
cmcExchangeLiquidity.topExchangeVolumeShare
|
|
1716
|
-
),
|
|
1717
|
-
liquidityRegime: typeof cmcExchangeLiquidity.liquidityRegime === "string" ? cmcExchangeLiquidity.liquidityRegime : null
|
|
1718
|
-
};
|
|
2029
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
2030
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
2031
|
+
return state.strategyEntriesMap.get(name);
|
|
1719
2032
|
};
|
|
1720
|
-
var
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
if (!cmcFearGreed) {
|
|
1725
|
-
return {
|
|
1726
|
-
source: null,
|
|
1727
|
-
available: false,
|
|
1728
|
-
interval: null,
|
|
1729
|
-
asOfTs: null,
|
|
1730
|
-
stale: null,
|
|
1731
|
-
value: null,
|
|
1732
|
-
valueChange24h: null,
|
|
1733
|
-
valueChange7d: null,
|
|
1734
|
-
classification: null,
|
|
1735
|
-
sentimentRegime: null
|
|
1736
|
-
};
|
|
2033
|
+
var getStrategyPluginSource = async (name, cwd = getTradejsProjectCwd(), moduleName) => {
|
|
2034
|
+
if (moduleName?.trim()) {
|
|
2035
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
2036
|
+
return (await resolveStrategyModuleEntry({ strategyName: name, moduleName, cwd })).source;
|
|
1737
2037
|
}
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
interval: typeof cmcFearGreed.interval === "string" ? cmcFearGreed.interval : null,
|
|
1742
|
-
asOfTs: toFiniteNumber(cmcFearGreed.asOfTs),
|
|
1743
|
-
stale: typeof cmcFearGreed.stale === "boolean" ? cmcFearGreed.stale : null,
|
|
1744
|
-
value: toFiniteNumber(cmcFearGreed.value),
|
|
1745
|
-
valueChange24h: toFiniteNumber(cmcFearGreed.valueChange24h),
|
|
1746
|
-
valueChange7d: toFiniteNumber(cmcFearGreed.valueChange7d),
|
|
1747
|
-
classification: typeof cmcFearGreed.classification === "string" ? cmcFearGreed.classification : null,
|
|
1748
|
-
sentimentRegime: typeof cmcFearGreed.sentimentRegime === "string" ? cmcFearGreed.sentimentRegime : null
|
|
1749
|
-
};
|
|
2038
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
2039
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
2040
|
+
return state.strategySourcesMap.get(name);
|
|
1750
2041
|
};
|
|
1751
|
-
var
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
}
|
|
2042
|
+
var getStrategyManifest = (name, cwd = getTradejsProjectCwd()) => {
|
|
2043
|
+
if (!name) {
|
|
2044
|
+
return void 0;
|
|
2045
|
+
}
|
|
2046
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
2047
|
+
return state.strategyManifestsMap.get(name);
|
|
2048
|
+
};
|
|
2049
|
+
var strategies = new Proxy(
|
|
2050
|
+
{},
|
|
2051
|
+
{
|
|
2052
|
+
get: (_target, property) => {
|
|
2053
|
+
if (typeof property !== "string") {
|
|
2054
|
+
return void 0;
|
|
2055
|
+
}
|
|
2056
|
+
return getStrategyRegistryState().state.strategyCreators.get(property);
|
|
2057
|
+
},
|
|
2058
|
+
ownKeys: () => {
|
|
2059
|
+
return [...getStrategyRegistryState().state.strategyCreators.keys()];
|
|
2060
|
+
},
|
|
2061
|
+
getOwnPropertyDescriptor: () => ({
|
|
2062
|
+
enumerable: true,
|
|
2063
|
+
configurable: true
|
|
2064
|
+
})
|
|
2065
|
+
}
|
|
2066
|
+
);
|
|
2067
|
+
|
|
2068
|
+
// src/strategyHelpers/tickerProfiles.ts
|
|
2069
|
+
var snapshots = /* @__PURE__ */ new Map();
|
|
2070
|
+
var resolveTickerProfileSource = (strategy) => {
|
|
2071
|
+
const declared = getStrategyManifest(strategy)?.contextRequirements?.tickerProfiles;
|
|
2072
|
+
if (declared) return declared;
|
|
2073
|
+
const provider = process.env.TICKER_PROFILES_PROVIDER?.trim();
|
|
2074
|
+
const universeId = process.env.TICKER_PROFILES_UNIVERSE?.trim();
|
|
2075
|
+
if (!provider && !universeId) return void 0;
|
|
2076
|
+
if (!provider || !universeId)
|
|
2077
|
+
throw new Error(
|
|
2078
|
+
"Set both TICKER_PROFILES_PROVIDER and TICKER_PROFILES_UNIVERSE"
|
|
2079
|
+
);
|
|
2080
|
+
return { provider, universeId };
|
|
2081
|
+
};
|
|
2082
|
+
var loadTickerProfileContext = async ({
|
|
2083
|
+
projectRoot,
|
|
2084
|
+
provider,
|
|
2085
|
+
universeId,
|
|
2086
|
+
symbol,
|
|
2087
|
+
timestamp
|
|
2088
|
+
}) => {
|
|
2089
|
+
const asOf = (0, import_tickerProfiles2.tickerProfileMonth)(timestamp), key = JSON.stringify([projectRoot, provider, universeId, asOf]);
|
|
2090
|
+
let cached = snapshots.get(key);
|
|
2091
|
+
if (!cached || cached.expires < Date.now()) {
|
|
2092
|
+
const value = (0, import_tickerProfiles.readTickerProfiles)(projectRoot, provider, universeId, asOf).then((envelope) => {
|
|
2093
|
+
if (!envelope) return null;
|
|
2094
|
+
const s = envelope.snapshot;
|
|
2095
|
+
if (!(0, import_tickerProfiles2.isTickerProfileSnapshot)(s) || s.asOf !== asOf || s.provider !== provider || s.universeId !== universeId)
|
|
2096
|
+
throw new Error("Ticker profile identity/schema mismatch");
|
|
2097
|
+
const entry = snapshots.get(key);
|
|
2098
|
+
if (entry) entry.expires = Infinity;
|
|
2099
|
+
return new Map(
|
|
2100
|
+
s.profiles.map((p) => [
|
|
2101
|
+
p.symbol,
|
|
2102
|
+
Object.freeze({
|
|
2103
|
+
...p,
|
|
2104
|
+
definition: s.definition,
|
|
2105
|
+
provider,
|
|
2106
|
+
universeId,
|
|
2107
|
+
snapshotId: envelope.snapshotId
|
|
2108
|
+
})
|
|
2109
|
+
])
|
|
2110
|
+
);
|
|
2111
|
+
}).catch((error) => {
|
|
2112
|
+
snapshots.delete(key);
|
|
2113
|
+
throw error;
|
|
2114
|
+
});
|
|
2115
|
+
cached = { expires: Date.now() + 6e4, value };
|
|
2116
|
+
snapshots.set(key, cached);
|
|
2117
|
+
if (snapshots.size > 24) {
|
|
2118
|
+
const oldest = snapshots.keys().next().value;
|
|
2119
|
+
if (oldest) snapshots.delete(oldest);
|
|
2120
|
+
}
|
|
1774
2121
|
}
|
|
1775
|
-
return
|
|
1776
|
-
source: String(cmcIndexes.source ?? ""),
|
|
1777
|
-
available: true,
|
|
1778
|
-
interval: typeof cmcIndexes.interval === "string" ? cmcIndexes.interval : null,
|
|
1779
|
-
asOfTs: toFiniteNumber(cmcIndexes.asOfTs),
|
|
1780
|
-
stale: typeof cmcIndexes.stale === "boolean" ? cmcIndexes.stale : null,
|
|
1781
|
-
cmc100Value: toFiniteNumber(cmcIndexes.cmc100Value),
|
|
1782
|
-
cmc100Change24hPct: toFiniteNumber(cmcIndexes.cmc100Change24hPct),
|
|
1783
|
-
cmc100TopConstituentSymbol: typeof cmcIndexes.cmc100TopConstituentSymbol === "string" ? cmcIndexes.cmc100TopConstituentSymbol : null,
|
|
1784
|
-
cmc100TopConstituentWeightPct: toFiniteNumber(
|
|
1785
|
-
cmcIndexes.cmc100TopConstituentWeightPct
|
|
1786
|
-
),
|
|
1787
|
-
cmc20Value: toFiniteNumber(cmcIndexes.cmc20Value),
|
|
1788
|
-
cmc20Change24hPct: toFiniteNumber(cmcIndexes.cmc20Change24hPct),
|
|
1789
|
-
cmc20TopConstituentSymbol: typeof cmcIndexes.cmc20TopConstituentSymbol === "string" ? cmcIndexes.cmc20TopConstituentSymbol : null,
|
|
1790
|
-
cmc20TopConstituentWeightPct: toFiniteNumber(
|
|
1791
|
-
cmcIndexes.cmc20TopConstituentWeightPct
|
|
1792
|
-
),
|
|
1793
|
-
cmc20ToCmc100Ratio: toFiniteNumber(cmcIndexes.cmc20ToCmc100Ratio),
|
|
1794
|
-
cmc20ToCmc100RatioChange24hPct: toFiniteNumber(
|
|
1795
|
-
cmcIndexes.cmc20ToCmc100RatioChange24hPct
|
|
1796
|
-
),
|
|
1797
|
-
indexRegime: typeof cmcIndexes.indexRegime === "string" ? cmcIndexes.indexRegime : null
|
|
1798
|
-
};
|
|
2122
|
+
return (await cached.value)?.get(symbol);
|
|
1799
2123
|
};
|
|
1800
|
-
var
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
2124
|
+
var withTickerProfile = (baseContext, profile) => Object.defineProperty(
|
|
2125
|
+
Object.create(
|
|
2126
|
+
Object.getPrototypeOf(baseContext),
|
|
2127
|
+
Object.getOwnPropertyDescriptors(baseContext)
|
|
2128
|
+
),
|
|
2129
|
+
"tickerProfile",
|
|
2130
|
+
{ enumerable: true, configurable: true, value: profile }
|
|
2131
|
+
);
|
|
2132
|
+
var enrichSignalWithTickerProfile = async (signal) => {
|
|
2133
|
+
const source = resolveTickerProfileSource(signal.strategy);
|
|
2134
|
+
if (!source || signal.universe === "tradfi" || !signal.symbol.endsWith("USDT"))
|
|
2135
|
+
return;
|
|
2136
|
+
const profile = await loadTickerProfileContext({
|
|
2137
|
+
...source,
|
|
2138
|
+
projectRoot: getTradejsProjectCwd(),
|
|
2139
|
+
symbol: signal.symbol,
|
|
2140
|
+
timestamp: signal.timestamp
|
|
2141
|
+
});
|
|
2142
|
+
if (profile) {
|
|
2143
|
+
signal.additionalIndicators = {
|
|
2144
|
+
...signal.additionalIndicators,
|
|
2145
|
+
tickerProfile: profile
|
|
1815
2146
|
};
|
|
1816
2147
|
}
|
|
1817
|
-
return {
|
|
1818
|
-
source: String(refs.source ?? ""),
|
|
1819
|
-
available: true,
|
|
1820
|
-
primaryReferenceSymbol,
|
|
1821
|
-
referenceSymbols: Array.isArray(refs.referenceSymbols) ? refs.referenceSymbols.map(String) : [],
|
|
1822
|
-
primaryTradeFlowBuyPressurePct: toFiniteNumber(
|
|
1823
|
-
primaryTradeFlow?.buyPressurePct
|
|
1824
|
-
),
|
|
1825
|
-
primaryTradeFlowStale: typeof primaryTradeFlow?.stale === "boolean" ? primaryTradeFlow.stale : null
|
|
1826
|
-
};
|
|
1827
2148
|
};
|
|
1828
|
-
var buildAiMarketContext = (signal) => ({
|
|
1829
|
-
execution: {
|
|
1830
|
-
binanceCoinbaseSpread: buildSpreadContextFromSignal(signal)
|
|
1831
|
-
},
|
|
1832
|
-
participation: {
|
|
1833
|
-
trueDelta: buildTrueDeltaContextFromSignal(signal),
|
|
1834
|
-
tradeFlow: buildTradeFlowContextFromSignal(signal)
|
|
1835
|
-
},
|
|
1836
|
-
relative: {
|
|
1837
|
-
marketBreadth: buildMarketBreadthContextFromSignal(signal),
|
|
1838
|
-
marketBreadths: buildMarketBreadthsContextFromSignal(signal),
|
|
1839
|
-
targetVsBtc: buildTargetVsBtcContextFromSignal(signal),
|
|
1840
|
-
btcAltRegime: buildBtcAltRegimeContextFromSignal(signal),
|
|
1841
|
-
cmcGlobal: buildCmcGlobalContextFromSignal(signal),
|
|
1842
|
-
cmcReferenceAssets: buildCmcReferenceAssetsContextFromSignal(signal),
|
|
1843
|
-
cmcExchangeLiquidity: buildCmcExchangeLiquidityContextFromSignal(signal),
|
|
1844
|
-
cmcFearGreed: buildCmcFearGreedContextFromSignal(signal),
|
|
1845
|
-
cmcIndexes: buildCmcIndexesContextFromSignal(signal),
|
|
1846
|
-
referenceTradeFlow: buildReferenceTradeFlowContextFromSignal(signal)
|
|
1847
|
-
}
|
|
1848
|
-
});
|
|
1849
2149
|
|
|
1850
|
-
// src/
|
|
1851
|
-
var
|
|
2150
|
+
// src/strategyRuntime.ts
|
|
2151
|
+
var import_jev6 = require("@tradejs/core/jev");
|
|
1852
2152
|
|
|
1853
|
-
// src/
|
|
1854
|
-
var
|
|
1855
|
-
var
|
|
1856
|
-
var
|
|
1857
|
-
var
|
|
1858
|
-
var
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
]
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
var getTradejsProjectCwd = (cwd) => {
|
|
1873
|
-
const explicit = String(cwd ?? "").trim();
|
|
1874
|
-
if (explicit) {
|
|
1875
|
-
return import_path.default.resolve(explicit);
|
|
1876
|
-
}
|
|
1877
|
-
const fromEnv = String(process.env.PROJECT_CWD || "").trim();
|
|
1878
|
-
if (fromEnv) {
|
|
1879
|
-
return import_path.default.resolve(fromEnv);
|
|
1880
|
-
}
|
|
1881
|
-
return process.cwd();
|
|
2153
|
+
// src/jev.ts
|
|
2154
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
2155
|
+
var import_node_path = __toESM(require("path"));
|
|
2156
|
+
var import_jev4 = require("@tradejs/core/jev");
|
|
2157
|
+
var import_jev5 = require("@tradejs/infra/jev");
|
|
2158
|
+
var import_userSettings = require("@tradejs/infra/userSettings");
|
|
2159
|
+
init_jevInput();
|
|
2160
|
+
init_jevInput();
|
|
2161
|
+
init_jevTraining();
|
|
2162
|
+
var resolveJevProvider = async (userName) => {
|
|
2163
|
+
const settings = await (0, import_userSettings.getUserSettings)(userName);
|
|
2164
|
+
const provider = {
|
|
2165
|
+
endpoint: settings.JEV_API_ENDPOINT || import_jev4.JEV_PROVIDERS[0].endpoint,
|
|
2166
|
+
model: settings.JEV_MODEL || import_jev4.JEV_PROVIDERS[0].model
|
|
2167
|
+
};
|
|
2168
|
+
(0, import_jev4.parseJevConfig)({ source: "provider", mode: "observe", provider });
|
|
2169
|
+
if (!settings.JEV_API_KEY)
|
|
2170
|
+
throw new Error("Configure the Jev API key in Account settings");
|
|
2171
|
+
return provider;
|
|
1882
2172
|
};
|
|
1883
|
-
var
|
|
1884
|
-
|
|
1885
|
-
|
|
2173
|
+
var emptyScores = () => Object.fromEntries(import_jev4.JEV_DIMENSIONS.map((key) => [key, null]));
|
|
2174
|
+
var pending = /* @__PURE__ */ new Map();
|
|
2175
|
+
var pendingResponses = /* @__PURE__ */ new Map();
|
|
2176
|
+
var evaluateJevInput = async ({
|
|
2177
|
+
input,
|
|
2178
|
+
config,
|
|
2179
|
+
userName,
|
|
2180
|
+
projectRoot
|
|
2181
|
+
}) => {
|
|
2182
|
+
config = (0, import_jev4.parseJevConfig)(config);
|
|
2183
|
+
const inputHash = (0, import_jev5.jevHash)(input);
|
|
2184
|
+
const questions = questionsForJevInput(input);
|
|
2185
|
+
const questionsHash = (0, import_jev5.jevHash)(questions);
|
|
2186
|
+
const base = {
|
|
2187
|
+
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
2188
|
+
schema: "tradejs-signal-assessment/v5",
|
|
2189
|
+
source: config.source,
|
|
2190
|
+
mode: config.mode,
|
|
2191
|
+
inputHash,
|
|
2192
|
+
questionsHash,
|
|
2193
|
+
geometryStatus: input.geometryStatus,
|
|
2194
|
+
levelsValid: typeof input.features["signal.validLevels"] === "boolean" ? input.features["signal.validLevels"] : null
|
|
2195
|
+
};
|
|
2196
|
+
if (!input.questions.length)
|
|
2197
|
+
return {
|
|
2198
|
+
assessment: {
|
|
2199
|
+
...base,
|
|
2200
|
+
status: "unavailable",
|
|
2201
|
+
model: "",
|
|
2202
|
+
scores: emptyScores(),
|
|
2203
|
+
confidence: emptyScores()
|
|
2204
|
+
}
|
|
2205
|
+
};
|
|
2206
|
+
if (config.source === "local") {
|
|
2207
|
+
const contents = await import_promises.default.readFile(
|
|
2208
|
+
import_node_path.default.resolve(projectRoot, config.modelFile),
|
|
2209
|
+
"utf8"
|
|
2210
|
+
);
|
|
2211
|
+
if ((0, import_jev5.jevFileHash)(contents) !== config.modelSha256)
|
|
2212
|
+
throw new Error("Local Jev gate checksum mismatch");
|
|
2213
|
+
const { validateJevGateModel: validateJevGateModel2 } = await Promise.resolve().then(() => (init_jevTraining(), jevTraining_exports));
|
|
2214
|
+
const model = validateJevGateModel2(JSON.parse(contents));
|
|
2215
|
+
if (model.strategy !== input.strategy || model.questionsHash !== (0, import_jev5.jevHash)(JEV_QUESTIONS))
|
|
2216
|
+
throw new Error("Local Jev gate strategy or question version mismatch");
|
|
2217
|
+
const scores = Object.fromEntries(
|
|
2218
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
2219
|
+
key,
|
|
2220
|
+
input.questions.includes(key) && model.trees[key] ? (0, import_jev4.predictJevTree)(model.trees[key], input.features) : null
|
|
2221
|
+
])
|
|
2222
|
+
);
|
|
2223
|
+
return {
|
|
2224
|
+
assessment: {
|
|
2225
|
+
...base,
|
|
2226
|
+
status: "available",
|
|
2227
|
+
model: config.modelSha256,
|
|
2228
|
+
scores,
|
|
2229
|
+
confidence: emptyScores()
|
|
2230
|
+
}
|
|
2231
|
+
};
|
|
1886
2232
|
}
|
|
1887
|
-
const
|
|
1888
|
-
const
|
|
1889
|
-
const
|
|
1890
|
-
const
|
|
1891
|
-
const
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
2233
|
+
const provider = config.provider ?? await resolveJevProvider(userName);
|
|
2234
|
+
const id = (0, import_jev5.jevHash)({ inputHash, questionsHash, provider });
|
|
2235
|
+
const dir = import_node_path.default.resolve(projectRoot, config.recordsDir ?? "data/ai/jev");
|
|
2236
|
+
const file = import_node_path.default.join(dir, "records", `${id}.json`);
|
|
2237
|
+
const getRecord = async () => {
|
|
2238
|
+
const existing = await (0, import_jev5.readJevArtifact)(file);
|
|
2239
|
+
if (existing) {
|
|
2240
|
+
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))
|
|
2241
|
+
throw new Error("Jev recording identity mismatch");
|
|
2242
|
+
(0, import_jev5.validateJevResponse)(existing.response, input.questions);
|
|
2243
|
+
for (const key of import_jev4.JEV_DIMENSIONS) {
|
|
2244
|
+
const expected = input.questions.includes(key) ? existing.response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null;
|
|
2245
|
+
if (existing.scores[key] !== expected)
|
|
2246
|
+
throw new Error("Jev recording scores do not match the response");
|
|
2247
|
+
}
|
|
2248
|
+
return existing;
|
|
2249
|
+
}
|
|
2250
|
+
if (config.source === "recorded")
|
|
2251
|
+
throw new Error(
|
|
2252
|
+
`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.`
|
|
2253
|
+
);
|
|
2254
|
+
const settings = await (0, import_userSettings.getUserSettings)(userName);
|
|
2255
|
+
if (!settings.JEV_API_KEY)
|
|
2256
|
+
throw new Error("Configure the Jev API key in Account settings");
|
|
2257
|
+
const apiKey = settings.JEV_API_KEY;
|
|
2258
|
+
const configuredProvider = await resolveJevProvider(userName);
|
|
2259
|
+
if (configuredProvider.endpoint !== provider.endpoint)
|
|
2260
|
+
throw new Error(
|
|
2261
|
+
"Jev account endpoint changed; update the frozen provider configuration"
|
|
2262
|
+
);
|
|
2263
|
+
const started = Date.now();
|
|
2264
|
+
const state = stateForJevInput(input);
|
|
2265
|
+
const responseId = (0, import_jev5.jevHash)({
|
|
2266
|
+
state,
|
|
2267
|
+
questionsHash,
|
|
2268
|
+
provider,
|
|
2269
|
+
user: (0, import_jev5.jevHash)(userName)
|
|
2270
|
+
});
|
|
2271
|
+
const responseFile = import_node_path.default.join(dir, "responses", `${responseId}.json`);
|
|
2272
|
+
let responsePromise = pendingResponses.get(responseFile);
|
|
2273
|
+
if (!responsePromise) {
|
|
2274
|
+
responsePromise = (async () => {
|
|
2275
|
+
const saved = await (0, import_jev5.readJevArtifact)(responseFile);
|
|
2276
|
+
if (saved) return (0, import_jev5.validateJevResponse)(saved, input.questions);
|
|
2277
|
+
const fresh = await (0, import_jev5.requestJev)(provider, apiKey, state, questions);
|
|
2278
|
+
await (0, import_jev5.writeJevArtifact)(responseFile, fresh);
|
|
2279
|
+
return fresh;
|
|
2280
|
+
})();
|
|
2281
|
+
pendingResponses.set(responseFile, responsePromise);
|
|
2282
|
+
}
|
|
2283
|
+
let response;
|
|
2284
|
+
try {
|
|
2285
|
+
response = await responsePromise;
|
|
2286
|
+
} finally {
|
|
2287
|
+
pendingResponses.delete(responseFile);
|
|
2288
|
+
}
|
|
2289
|
+
const scores = Object.fromEntries(
|
|
2290
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
2291
|
+
key,
|
|
2292
|
+
input.questions.includes(key) ? response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null
|
|
2293
|
+
])
|
|
2294
|
+
);
|
|
2295
|
+
const value = {
|
|
2296
|
+
schema: "tradejs-jev-record/v4",
|
|
2297
|
+
id,
|
|
2298
|
+
inputHash,
|
|
2299
|
+
questionsHash,
|
|
2300
|
+
provider,
|
|
2301
|
+
input,
|
|
2302
|
+
response,
|
|
2303
|
+
scores,
|
|
2304
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2305
|
+
elapsedMs: Date.now() - started
|
|
2306
|
+
};
|
|
2307
|
+
await (0, import_jev5.writeJevArtifact)(file, value);
|
|
2308
|
+
return await (0, import_jev5.readJevArtifact)(file);
|
|
1901
2309
|
};
|
|
1902
|
-
}
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
2310
|
+
const pendingKey = `${file}:${(0, import_jev5.jevHash)(userName)}`;
|
|
2311
|
+
let promise = pending.get(pendingKey);
|
|
2312
|
+
if (!promise) {
|
|
2313
|
+
promise = getRecord();
|
|
2314
|
+
pending.set(pendingKey, promise);
|
|
1907
2315
|
}
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
return;
|
|
2316
|
+
let record;
|
|
2317
|
+
try {
|
|
2318
|
+
record = await promise;
|
|
2319
|
+
} finally {
|
|
2320
|
+
pending.delete(pendingKey);
|
|
1914
2321
|
}
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
2322
|
+
return {
|
|
2323
|
+
record,
|
|
2324
|
+
assessment: {
|
|
2325
|
+
...base,
|
|
2326
|
+
status: "available",
|
|
2327
|
+
recordId: id,
|
|
2328
|
+
model: record.response.model,
|
|
2329
|
+
scores: record.scores,
|
|
2330
|
+
confidence: Object.fromEntries(
|
|
2331
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
2332
|
+
key,
|
|
2333
|
+
record.response.answers[key]?.confidence ?? null
|
|
2334
|
+
])
|
|
2335
|
+
)
|
|
1922
2336
|
}
|
|
1923
|
-
}
|
|
1924
|
-
tsNodeRegistered = true;
|
|
1925
|
-
};
|
|
1926
|
-
var ensureTsconfigPathsRegistered = async (cwd = getTradejsProjectCwd()) => {
|
|
1927
|
-
const projectRoot = getTradejsProjectCwd(cwd);
|
|
1928
|
-
if (tsconfigPathsRegisteredByCwd.has(projectRoot)) {
|
|
1929
|
-
return;
|
|
1930
|
-
}
|
|
1931
|
-
const tsconfigPathsModule = await import("tsconfig-paths");
|
|
1932
|
-
const loadConfig = tsconfigPathsModule.loadConfig;
|
|
1933
|
-
const register = tsconfigPathsModule.register;
|
|
1934
|
-
if (typeof loadConfig !== "function" || typeof register !== "function") {
|
|
1935
|
-
return;
|
|
1936
|
-
}
|
|
1937
|
-
const loadedConfig = loadConfig(projectRoot);
|
|
1938
|
-
if (loadedConfig.resultType !== "success") {
|
|
1939
|
-
return;
|
|
1940
|
-
}
|
|
1941
|
-
register({
|
|
1942
|
-
baseUrl: loadedConfig.absoluteBaseUrl,
|
|
1943
|
-
paths: loadedConfig.paths,
|
|
1944
|
-
addMatchAll: false
|
|
1945
|
-
});
|
|
1946
|
-
tsconfigPathsRegisteredByCwd.add(projectRoot);
|
|
2337
|
+
};
|
|
1947
2338
|
};
|
|
1948
|
-
var
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
2339
|
+
var assessSignalWithJev = async ({
|
|
2340
|
+
signal,
|
|
2341
|
+
config,
|
|
2342
|
+
userName,
|
|
2343
|
+
projectRoot,
|
|
2344
|
+
strict,
|
|
2345
|
+
scope = "runtime"
|
|
2346
|
+
}) => {
|
|
2347
|
+
let assessment;
|
|
2348
|
+
try {
|
|
2349
|
+
const input = buildJevInput(signal);
|
|
2350
|
+
({ assessment } = await evaluateJevInput({
|
|
2351
|
+
input,
|
|
2352
|
+
config,
|
|
2353
|
+
userName,
|
|
2354
|
+
projectRoot
|
|
2355
|
+
}));
|
|
2356
|
+
} catch (error) {
|
|
2357
|
+
if (strict) throw error;
|
|
2358
|
+
assessment = {
|
|
2359
|
+
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
2360
|
+
schema: "tradejs-signal-assessment/v5",
|
|
2361
|
+
source: config.source,
|
|
2362
|
+
mode: config.mode,
|
|
2363
|
+
status: "unavailable",
|
|
2364
|
+
inputHash: "",
|
|
2365
|
+
questionsHash: "",
|
|
2366
|
+
geometryStatus: "absent",
|
|
2367
|
+
levelsValid: null,
|
|
2368
|
+
model: config.provider?.model ?? config.modelSha256 ?? "",
|
|
2369
|
+
scores: emptyScores(),
|
|
2370
|
+
confidence: emptyScores()
|
|
2371
|
+
};
|
|
1964
2372
|
}
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
2373
|
+
signal.assessment = assessment;
|
|
2374
|
+
signal.additionalIndicators = {
|
|
2375
|
+
...signal.additionalIndicators,
|
|
2376
|
+
jev: (0, import_jev4.jevAssessmentFeatures)(assessment)
|
|
2377
|
+
};
|
|
2378
|
+
const identity = {
|
|
2379
|
+
signalId: signal.signalId,
|
|
2380
|
+
strategy: signal.strategy,
|
|
2381
|
+
symbol: signal.symbol,
|
|
2382
|
+
timestamp: signal.timestamp,
|
|
2383
|
+
scope,
|
|
2384
|
+
user: (0, import_jev5.jevHash)(userName),
|
|
2385
|
+
config,
|
|
2386
|
+
assessment
|
|
2387
|
+
};
|
|
2388
|
+
await (0, import_jev5.writeJevArtifact)(
|
|
2389
|
+
import_node_path.default.resolve(
|
|
2390
|
+
projectRoot,
|
|
2391
|
+
config.recordsDir ?? "data/ai/jev",
|
|
2392
|
+
"decisions",
|
|
2393
|
+
`${(0, import_jev5.jevHash)(identity)}.json`
|
|
2394
|
+
),
|
|
2395
|
+
identity
|
|
1968
2396
|
);
|
|
1969
|
-
|
|
1970
|
-
".ts",
|
|
1971
|
-
".tsx",
|
|
1972
|
-
".mts",
|
|
1973
|
-
".cts",
|
|
1974
|
-
".js",
|
|
1975
|
-
".jsx",
|
|
1976
|
-
".mjs",
|
|
1977
|
-
".cjs",
|
|
1978
|
-
".json"
|
|
1979
|
-
]) || "";
|
|
1980
|
-
tsconfigPathMatchersByCwd.set(projectRoot, matcher);
|
|
1981
|
-
const resolved = matcher(moduleName);
|
|
1982
|
-
return resolved || null;
|
|
2397
|
+
return assessment;
|
|
1983
2398
|
};
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
2399
|
+
|
|
2400
|
+
// src/strategyRuntime.ts
|
|
2401
|
+
var import_constants5 = require("@tradejs/core/constants");
|
|
2402
|
+
var import_strategies6 = require("@tradejs/core/strategies");
|
|
2403
|
+
var import_logger11 = require("@tradejs/infra/logger");
|
|
2404
|
+
|
|
2405
|
+
// src/strategyHelpers/runtime.ts
|
|
2406
|
+
var import_logger9 = require("@tradejs/infra/logger");
|
|
2407
|
+
var import_constants3 = require("@tradejs/core/constants");
|
|
2408
|
+
var import_ml2 = require("@tradejs/infra/ml");
|
|
2409
|
+
|
|
2410
|
+
// src/strategy/entrySubmissionValidation.ts
|
|
2411
|
+
var import_logger3 = require("@tradejs/infra/logger");
|
|
2412
|
+
|
|
2413
|
+
// src/strategy/entryExecutionValidation.ts
|
|
2414
|
+
var EntrySubmissionValidationError = class extends Error {
|
|
2415
|
+
constructor() {
|
|
2416
|
+
super(...arguments);
|
|
2417
|
+
this.name = "EntrySubmissionValidationError";
|
|
1990
2418
|
}
|
|
1991
|
-
return moduleName;
|
|
1992
2419
|
};
|
|
1993
|
-
var
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
const
|
|
1997
|
-
if (
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2420
|
+
var resolveEntryExecutionLimits = (config, direction) => {
|
|
2421
|
+
const side = config[direction];
|
|
2422
|
+
const minimum = side && typeof side === "object" && "minRiskRatio" in side ? side.minRiskRatio : void 0;
|
|
2423
|
+
const maximum = config.MAX_LOSS_VALUE;
|
|
2424
|
+
if (minimum == null && maximum == null) return void 0;
|
|
2425
|
+
const minimumNetRiskRatio = minimum == null ? void 0 : Number(minimum);
|
|
2426
|
+
const maxLossValue = maximum == null ? void 0 : Number(maximum);
|
|
2427
|
+
const feeRate = Number(config.RISK_FEE_RATE ?? 0);
|
|
2428
|
+
const slippage = Number(config.RISK_SLIPPAGE_BPS ?? 0);
|
|
2429
|
+
const impact = Number(config.RISK_MARKET_IMPACT_BPS ?? 0);
|
|
2430
|
+
const slippageBps = slippage + impact;
|
|
2431
|
+
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)
|
|
2432
|
+
throw new Error("INVALID_ENTRY_EXECUTION_LIMITS");
|
|
2433
|
+
return { minimumNetRiskRatio, maxLossValue, feeRate, slippageBps };
|
|
2004
2434
|
};
|
|
2005
|
-
var
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2435
|
+
var validateEntryExecution = ({
|
|
2436
|
+
direction,
|
|
2437
|
+
price,
|
|
2438
|
+
qty,
|
|
2439
|
+
stopLossPrice,
|
|
2440
|
+
takeProfitPrice,
|
|
2441
|
+
limits
|
|
2442
|
+
}) => {
|
|
2443
|
+
if (!Number.isFinite(price) || price <= 0 || !Number.isFinite(qty) || qty <= 0 || stopLossPrice == null || !Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || !Number.isFinite(takeProfitPrice) || takeProfitPrice <= 0)
|
|
2444
|
+
throw new EntrySubmissionValidationError(
|
|
2445
|
+
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
2446
|
+
);
|
|
2447
|
+
const side = direction === "LONG" ? 1 : -1;
|
|
2448
|
+
if (side * (price - stopLossPrice) <= 0 || side * (takeProfitPrice - price) <= 0)
|
|
2449
|
+
throw new EntrySubmissionValidationError(
|
|
2450
|
+
"INVALID_ENTRY_PROTECTION_AT_SUBMISSION"
|
|
2451
|
+
);
|
|
2452
|
+
const slippage = limits.slippageBps / 1e4;
|
|
2453
|
+
const cost = (value, sign) => value * slippage + value * (1 + sign * slippage) * limits.feeRate;
|
|
2454
|
+
const entryCost = cost(price, side);
|
|
2455
|
+
const lossPerUnit = Math.abs(price - stopLossPrice) + entryCost + cost(stopLossPrice, -side);
|
|
2456
|
+
const rewardPerUnit = Math.max(
|
|
2457
|
+
0,
|
|
2458
|
+
Math.abs(takeProfitPrice - price) - entryCost - cost(takeProfitPrice, -side)
|
|
2017
2459
|
);
|
|
2460
|
+
const netRiskRatio = rewardPerUnit / lossPerUnit;
|
|
2461
|
+
const plannedLossValue = qty * lossPerUnit;
|
|
2462
|
+
if (!Number.isFinite(netRiskRatio) || !Number.isFinite(plannedLossValue))
|
|
2463
|
+
throw new EntrySubmissionValidationError(
|
|
2464
|
+
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
2465
|
+
);
|
|
2466
|
+
const belowMinimumNetRiskRatio = limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio;
|
|
2467
|
+
const riskExceeded = limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9);
|
|
2468
|
+
return {
|
|
2469
|
+
netRiskRatio,
|
|
2470
|
+
plannedLossValue,
|
|
2471
|
+
riskExceeded,
|
|
2472
|
+
belowMinimumNetRiskRatio
|
|
2473
|
+
};
|
|
2018
2474
|
};
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
await ensureTsconfigPathsRegistered(cwd);
|
|
2038
|
-
return requireFn(modulePath);
|
|
2039
|
-
}
|
|
2040
|
-
if (isBareModuleSpecifier(normalized)) {
|
|
2041
|
-
await ensureTsconfigPathsRegistered(cwd);
|
|
2042
|
-
try {
|
|
2043
|
-
return requireFn(normalized);
|
|
2044
|
-
} catch (error) {
|
|
2045
|
-
const resolvedByTsconfig = await resolveTsconfigPathModule(
|
|
2046
|
-
normalized,
|
|
2047
|
-
cwd
|
|
2048
|
-
);
|
|
2049
|
-
if (resolvedByTsconfig && resolvedByTsconfig !== normalized) {
|
|
2050
|
-
return requireFn(resolvedByTsconfig);
|
|
2051
|
-
}
|
|
2052
|
-
throw error;
|
|
2053
|
-
}
|
|
2475
|
+
|
|
2476
|
+
// src/strategy/entrySubmissionValidation.ts
|
|
2477
|
+
var MAX_ENTRY_QUOTE_AGE_MS = 5e3;
|
|
2478
|
+
var toFiniteNumberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
2479
|
+
var getOrderArrivalSnapshot = async ({
|
|
2480
|
+
connector,
|
|
2481
|
+
symbol,
|
|
2482
|
+
requireTimestamp = false
|
|
2483
|
+
}) => {
|
|
2484
|
+
if (typeof connector.getTopOfBookTicker !== "function") {
|
|
2485
|
+
return {
|
|
2486
|
+
arrivalSnapshotTime: Date.now(),
|
|
2487
|
+
arrivalSource: "unavailable",
|
|
2488
|
+
bid: null,
|
|
2489
|
+
ask: null,
|
|
2490
|
+
arrivalMid: null,
|
|
2491
|
+
spreadBps: null
|
|
2492
|
+
};
|
|
2054
2493
|
}
|
|
2055
2494
|
try {
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
);
|
|
2495
|
+
const ticker = await connector.getTopOfBookTicker(symbol);
|
|
2496
|
+
const arrivalSnapshotTime = toFiniteNumberOrNull(ticker?.timestamp);
|
|
2497
|
+
const timestampUnavailable = requireTimestamp && arrivalSnapshotTime == null;
|
|
2498
|
+
const bid = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.bidPrice);
|
|
2499
|
+
const ask = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.askPrice);
|
|
2500
|
+
const arrivalMid = bid != null && ask != null ? (bid + ask) / 2 : null;
|
|
2501
|
+
const spreadBps = bid != null && ask != null && arrivalMid != null && arrivalMid > 0 ? (ask - bid) / arrivalMid * 1e4 : null;
|
|
2502
|
+
return {
|
|
2503
|
+
arrivalSnapshotTime: arrivalSnapshotTime ?? Date.now(),
|
|
2504
|
+
arrivalSource: "top_of_book",
|
|
2505
|
+
bid,
|
|
2506
|
+
ask,
|
|
2507
|
+
arrivalMid,
|
|
2508
|
+
spreadBps
|
|
2509
|
+
};
|
|
2060
2510
|
} catch (error) {
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
let currentDir = import_path.default.resolve(cwd);
|
|
2075
|
-
while (true) {
|
|
2076
|
-
for (const fileName of CONFIG_FILE_NAMES) {
|
|
2077
|
-
const fullPath = import_path.default.join(currentDir, fileName);
|
|
2078
|
-
if (import_fs.default.existsSync(fullPath) && import_fs.default.statSync(fullPath).isFile()) {
|
|
2079
|
-
return fullPath;
|
|
2080
|
-
}
|
|
2081
|
-
}
|
|
2082
|
-
const parentDir = import_path.default.dirname(currentDir);
|
|
2083
|
-
if (parentDir === currentDir) {
|
|
2084
|
-
return null;
|
|
2085
|
-
}
|
|
2086
|
-
currentDir = parentDir;
|
|
2511
|
+
import_logger3.logger.warn(
|
|
2512
|
+
"runtime order arrival snapshot failed: %s %s",
|
|
2513
|
+
symbol,
|
|
2514
|
+
error?.message || String(error)
|
|
2515
|
+
);
|
|
2516
|
+
return {
|
|
2517
|
+
arrivalSnapshotTime: Date.now(),
|
|
2518
|
+
arrivalSource: "top_of_book_error",
|
|
2519
|
+
bid: null,
|
|
2520
|
+
ask: null,
|
|
2521
|
+
arrivalMid: null,
|
|
2522
|
+
spreadBps: null
|
|
2523
|
+
};
|
|
2087
2524
|
}
|
|
2088
2525
|
};
|
|
2089
|
-
var
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2526
|
+
var validateEntryProtectionAtArrival = ({
|
|
2527
|
+
direction,
|
|
2528
|
+
signalPrice,
|
|
2529
|
+
bid,
|
|
2530
|
+
ask,
|
|
2531
|
+
arrivalMid,
|
|
2532
|
+
takeProfits,
|
|
2533
|
+
stopLossPrice
|
|
2534
|
+
}) => {
|
|
2535
|
+
const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
|
|
2536
|
+
const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
|
|
2537
|
+
if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
|
|
2538
|
+
throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
|
|
2093
2539
|
}
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
} catch {
|
|
2098
|
-
return normalized;
|
|
2540
|
+
for (const takeProfit of takeProfits) {
|
|
2541
|
+
if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
|
|
2542
|
+
throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
|
|
2099
2543
|
}
|
|
2100
2544
|
}
|
|
2101
|
-
if (
|
|
2102
|
-
|
|
2103
|
-
}
|
|
2104
|
-
if (isRelativeModulePath(normalized)) {
|
|
2105
|
-
return import_path.default.resolve(cwd, normalized);
|
|
2545
|
+
if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
|
|
2546
|
+
throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
|
|
2106
2547
|
}
|
|
2107
|
-
return normalized;
|
|
2108
2548
|
};
|
|
2109
|
-
var
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2549
|
+
var createEntrySubmissionValidation = ({
|
|
2550
|
+
connector,
|
|
2551
|
+
symbol,
|
|
2552
|
+
direction,
|
|
2553
|
+
takeProfits,
|
|
2554
|
+
stopLossPrice,
|
|
2555
|
+
limits,
|
|
2556
|
+
onValidated
|
|
2557
|
+
}) => {
|
|
2558
|
+
if (!limits) return void 0;
|
|
2559
|
+
if (!connector.capabilities?.orderSubmissionValidation)
|
|
2560
|
+
throw new EntrySubmissionValidationError(
|
|
2561
|
+
"ENTRY_SUBMISSION_VALIDATION_UNSUPPORTED"
|
|
2562
|
+
);
|
|
2563
|
+
return async ({ qty, executionPrice }) => {
|
|
2564
|
+
const simulated = Reflect.get(connector, "__tradejsTestConnector") === true;
|
|
2565
|
+
const snapshot2 = simulated && executionPrice != null ? {
|
|
2566
|
+
arrivalSnapshotTime: Date.now(),
|
|
2567
|
+
arrivalSource: "simulation",
|
|
2568
|
+
bid: executionPrice,
|
|
2569
|
+
ask: executionPrice,
|
|
2570
|
+
arrivalMid: executionPrice,
|
|
2571
|
+
spreadBps: 0
|
|
2572
|
+
} : await getOrderArrivalSnapshot({
|
|
2573
|
+
connector,
|
|
2574
|
+
symbol,
|
|
2575
|
+
requireTimestamp: true
|
|
2576
|
+
});
|
|
2577
|
+
const age = Date.now() - snapshot2.arrivalSnapshotTime;
|
|
2578
|
+
if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
|
|
2579
|
+
throw new EntrySubmissionValidationError(
|
|
2580
|
+
"ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
|
|
2581
|
+
);
|
|
2582
|
+
if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
|
|
2583
|
+
throw new EntrySubmissionValidationError(
|
|
2584
|
+
"ENTRY_QUOTE_STALE_AT_SUBMISSION"
|
|
2585
|
+
);
|
|
2586
|
+
const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
|
|
2587
|
+
validateEntryProtectionAtArrival({
|
|
2588
|
+
direction,
|
|
2589
|
+
signalPrice: price,
|
|
2590
|
+
bid: snapshot2.bid,
|
|
2591
|
+
ask: snapshot2.ask,
|
|
2592
|
+
arrivalMid: snapshot2.arrivalMid,
|
|
2593
|
+
takeProfits,
|
|
2594
|
+
stopLossPrice
|
|
2595
|
+
});
|
|
2596
|
+
const prices = takeProfits.map((tp) => tp.price);
|
|
2597
|
+
const economics2 = validateEntryExecution({
|
|
2598
|
+
direction,
|
|
2599
|
+
price,
|
|
2600
|
+
qty,
|
|
2601
|
+
stopLossPrice,
|
|
2602
|
+
takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
|
|
2603
|
+
limits
|
|
2604
|
+
});
|
|
2605
|
+
if (economics2.belowMinimumNetRiskRatio) {
|
|
2606
|
+
import_logger3.logger.warn(
|
|
2607
|
+
"entry submission warning: %s %s ENTRY_RR_BELOW_MIN_AT_SUBMISSION (RR %s, threshold %s)",
|
|
2608
|
+
symbol,
|
|
2609
|
+
direction,
|
|
2610
|
+
economics2.netRiskRatio,
|
|
2611
|
+
limits.minimumNetRiskRatio
|
|
2612
|
+
);
|
|
2613
|
+
}
|
|
2614
|
+
onValidated?.(snapshot2, economics2);
|
|
2615
|
+
};
|
|
2616
|
+
};
|
|
2617
|
+
|
|
2618
|
+
// src/ai.ts
|
|
2619
|
+
var import_aiLanguages2 = require("@tradejs/core/aiLanguages");
|
|
2620
|
+
var import_redis3 = require("@tradejs/infra/redis");
|
|
2621
|
+
|
|
2622
|
+
// src/strategyAdapters/ai.ts
|
|
2623
|
+
var import_indicators2 = require("@tradejs/core/indicators");
|
|
2624
|
+
var import_aiFeatures = require("@tradejs/core/aiFeatures");
|
|
2625
|
+
|
|
2626
|
+
// src/aiShared.ts
|
|
2627
|
+
var MAX_AI_SERIES_POINTS = 5;
|
|
2628
|
+
var COMPACT_INDICATORS_SNAPSHOT_SYMBOL = /* @__PURE__ */ Symbol.for(
|
|
2629
|
+
"tradejs.indicators.compactSnapshot"
|
|
2630
|
+
);
|
|
2631
|
+
var COMPACT_INDICATORS_SNAPSHOT_KEY = "__tradejsCompactIndicatorsSnapshot";
|
|
2632
|
+
var trimSeriesDeep = (value) => {
|
|
2633
|
+
if (Array.isArray(value)) {
|
|
2634
|
+
const trimmed = value.slice(-MAX_AI_SERIES_POINTS);
|
|
2635
|
+
const isMatrix = trimmed.every((item) => Array.isArray(item));
|
|
2636
|
+
if (isMatrix) {
|
|
2637
|
+
return trimmed;
|
|
2126
2638
|
}
|
|
2127
|
-
return
|
|
2128
|
-
|
|
2129
|
-
import_logger2.logger.log(
|
|
2130
|
-
"warn",
|
|
2131
|
-
"Failed to load TradeJS config from %s: %s",
|
|
2132
|
-
configFilePath,
|
|
2133
|
-
String(error)
|
|
2639
|
+
return trimmed.map(
|
|
2640
|
+
(item) => item && typeof item === "object" ? trimSeriesDeep(item) : item
|
|
2134
2641
|
);
|
|
2135
|
-
cachedByCwd.set(cwd, {});
|
|
2136
|
-
return {};
|
|
2137
2642
|
}
|
|
2643
|
+
if (value && typeof value === "object") {
|
|
2644
|
+
return Object.fromEntries(
|
|
2645
|
+
Object.entries(value).map(([key, nested]) => [
|
|
2646
|
+
key,
|
|
2647
|
+
trimSeriesDeep(nested)
|
|
2648
|
+
])
|
|
2649
|
+
);
|
|
2650
|
+
}
|
|
2651
|
+
return value;
|
|
2652
|
+
};
|
|
2653
|
+
var buildCompactAiIndicatorsSnapshot = (value) => {
|
|
2654
|
+
const compactSnapshot = value && typeof value === "object" ? value[COMPACT_INDICATORS_SNAPSHOT_SYMBOL] ?? value[COMPACT_INDICATORS_SNAPSHOT_KEY] : void 0;
|
|
2655
|
+
if (typeof compactSnapshot === "function") {
|
|
2656
|
+
return compactSnapshot({ limit: MAX_AI_SERIES_POINTS });
|
|
2657
|
+
}
|
|
2658
|
+
return trimSeriesDeep(value);
|
|
2138
2659
|
};
|
|
2139
2660
|
|
|
2140
|
-
// src/
|
|
2141
|
-
var
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2661
|
+
// src/aiMarketContext.ts
|
|
2662
|
+
var toRecord = (value) => {
|
|
2663
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2664
|
+
return null;
|
|
2665
|
+
}
|
|
2666
|
+
return value;
|
|
2667
|
+
};
|
|
2668
|
+
var toFiniteNumber = (value) => {
|
|
2669
|
+
const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
|
|
2670
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
2671
|
+
};
|
|
2672
|
+
var roundTo = (value, decimals) => {
|
|
2673
|
+
const factor = 10 ** decimals;
|
|
2674
|
+
return Math.round(value * factor) / factor;
|
|
2675
|
+
};
|
|
2676
|
+
var buildMissingSpreadContext = () => ({
|
|
2677
|
+
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2678
|
+
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2679
|
+
available: false,
|
|
2680
|
+
value: null,
|
|
2681
|
+
zScore: null,
|
|
2682
|
+
bps: null,
|
|
2683
|
+
absBps: null,
|
|
2684
|
+
bias: null,
|
|
2685
|
+
severity: null
|
|
2153
2686
|
});
|
|
2154
|
-
var
|
|
2155
|
-
|
|
2156
|
-
const
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2687
|
+
var buildSpreadContextFromSignal = (signal) => {
|
|
2688
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2689
|
+
const relative = toRecord(baseContext?.relative);
|
|
2690
|
+
const execution = toRecord(relative?.execution);
|
|
2691
|
+
const spread = toFiniteNumber(execution?.venueSpread);
|
|
2692
|
+
const zScore = toFiniteNumber(execution?.venueSpreadZScore);
|
|
2693
|
+
if (spread == null) {
|
|
2694
|
+
return buildMissingSpreadContext();
|
|
2161
2695
|
}
|
|
2696
|
+
const value = roundTo(spread, 8);
|
|
2697
|
+
const bps = roundTo(value * 1e4, 2);
|
|
2698
|
+
const absBps = Math.abs(bps);
|
|
2699
|
+
const bias = absBps < 1 ? "flat" : bps > 0 ? "coinbase_premium" : "binance_premium";
|
|
2700
|
+
const severity = absBps >= 20 ? "wide" : absBps >= 5 ? "elevated" : "normal";
|
|
2162
2701
|
return {
|
|
2163
|
-
|
|
2164
|
-
|
|
2702
|
+
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2703
|
+
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2704
|
+
available: true,
|
|
2705
|
+
value,
|
|
2706
|
+
zScore,
|
|
2707
|
+
bps,
|
|
2708
|
+
absBps,
|
|
2709
|
+
bias,
|
|
2710
|
+
severity
|
|
2165
2711
|
};
|
|
2166
2712
|
};
|
|
2167
|
-
var
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
const
|
|
2713
|
+
var buildTrueDeltaContextFromSignal = (signal) => {
|
|
2714
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2715
|
+
const participation = toRecord(baseContext?.participation);
|
|
2716
|
+
const delta = toRecord(participation?.delta);
|
|
2717
|
+
const source = String(delta?.source ?? "");
|
|
2718
|
+
const isTrueDeltaSource = source === "kline_taker_volume" || source === "agg_trades" || source === "trades";
|
|
2719
|
+
if (!delta || !isTrueDeltaSource) {
|
|
2720
|
+
return {
|
|
2721
|
+
source: source || null,
|
|
2722
|
+
available: false,
|
|
2723
|
+
buyPressurePct: null,
|
|
2724
|
+
buyVolume: null,
|
|
2725
|
+
sellVolume: null,
|
|
2726
|
+
netDelta: null,
|
|
2727
|
+
deltaPct: null,
|
|
2728
|
+
signedVolumeZScore: null
|
|
2729
|
+
};
|
|
2730
|
+
}
|
|
2172
2731
|
return {
|
|
2173
|
-
|
|
2174
|
-
|
|
2732
|
+
source,
|
|
2733
|
+
available: true,
|
|
2734
|
+
buyPressurePct: toFiniteNumber(delta.buyPressurePct),
|
|
2735
|
+
buyVolume: toFiniteNumber(delta.buyVolume),
|
|
2736
|
+
sellVolume: toFiniteNumber(delta.sellVolume),
|
|
2737
|
+
netDelta: toFiniteNumber(delta.netDelta),
|
|
2738
|
+
deltaPct: toFiniteNumber(delta.deltaPct),
|
|
2739
|
+
signedVolumeZScore: toFiniteNumber(delta.signedVolumeZScore)
|
|
2175
2740
|
};
|
|
2176
2741
|
};
|
|
2177
|
-
var
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2742
|
+
var buildTradeFlowContextFromSignal = (signal) => {
|
|
2743
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2744
|
+
const participation = toRecord(baseContext?.participation);
|
|
2745
|
+
const tradeFlow = toRecord(participation?.tradeFlow);
|
|
2746
|
+
if (!tradeFlow) {
|
|
2747
|
+
return {
|
|
2748
|
+
source: null,
|
|
2749
|
+
available: false,
|
|
2750
|
+
interval: null,
|
|
2751
|
+
stale: null,
|
|
2752
|
+
trades: null,
|
|
2753
|
+
buyPressurePct: null,
|
|
2754
|
+
netBaseDelta: null,
|
|
2755
|
+
netQuoteDelta: null
|
|
2756
|
+
};
|
|
2188
2757
|
}
|
|
2189
|
-
return
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2758
|
+
return {
|
|
2759
|
+
source: String(tradeFlow.source ?? ""),
|
|
2760
|
+
available: true,
|
|
2761
|
+
interval: String(tradeFlow.interval ?? ""),
|
|
2762
|
+
stale: typeof tradeFlow.stale === "boolean" ? tradeFlow.stale : null,
|
|
2763
|
+
trades: toFiniteNumber(tradeFlow.trades),
|
|
2764
|
+
buyPressurePct: toFiniteNumber(tradeFlow.buyPressurePct),
|
|
2765
|
+
netBaseDelta: toFiniteNumber(tradeFlow.netBaseDelta),
|
|
2766
|
+
netQuoteDelta: toFiniteNumber(tradeFlow.netQuoteDelta)
|
|
2767
|
+
};
|
|
2197
2768
|
};
|
|
2198
|
-
var
|
|
2199
|
-
const
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
)
|
|
2203
|
-
|
|
2769
|
+
var buildMarketBreadthContextFromSignal = (signal) => {
|
|
2770
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2771
|
+
const relative = toRecord(baseContext?.relative);
|
|
2772
|
+
const breadth = toRecord(relative?.marketBreadth);
|
|
2773
|
+
if (!breadth) {
|
|
2774
|
+
return {
|
|
2775
|
+
source: null,
|
|
2776
|
+
available: false,
|
|
2777
|
+
universe: null,
|
|
2778
|
+
interval: null,
|
|
2779
|
+
stale: null,
|
|
2780
|
+
symbolsCount: null,
|
|
2781
|
+
advanceDeclineRatio: null,
|
|
2782
|
+
pctAboveMa20: null,
|
|
2783
|
+
pctAboveMa50: null,
|
|
2784
|
+
equalWeightedReturn: null,
|
|
2785
|
+
volumeWeightedReturn: null,
|
|
2786
|
+
dispersion: null
|
|
2787
|
+
};
|
|
2788
|
+
}
|
|
2789
|
+
return {
|
|
2790
|
+
source: String(breadth.source ?? ""),
|
|
2791
|
+
available: true,
|
|
2792
|
+
universe: String(breadth.universe ?? ""),
|
|
2793
|
+
interval: String(breadth.interval ?? ""),
|
|
2794
|
+
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
2795
|
+
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
2796
|
+
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
2797
|
+
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
2798
|
+
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
2799
|
+
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
2800
|
+
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
2801
|
+
dispersion: toFiniteNumber(breadth.dispersion)
|
|
2802
|
+
};
|
|
2204
2803
|
};
|
|
2205
|
-
var
|
|
2206
|
-
const
|
|
2207
|
-
const
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
const
|
|
2211
|
-
if (
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2804
|
+
var buildMarketBreadthsContextFromSignal = (signal) => {
|
|
2805
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2806
|
+
const relative = toRecord(baseContext?.relative);
|
|
2807
|
+
const breadths = toRecord(relative?.marketBreadths);
|
|
2808
|
+
const build = (key) => {
|
|
2809
|
+
const breadth = toRecord(breadths?.[key]);
|
|
2810
|
+
if (!breadth) {
|
|
2811
|
+
return {
|
|
2812
|
+
source: null,
|
|
2813
|
+
available: false,
|
|
2814
|
+
universe: null,
|
|
2815
|
+
interval: null,
|
|
2816
|
+
stale: null,
|
|
2817
|
+
symbolsCount: null,
|
|
2818
|
+
advanceDeclineRatio: null,
|
|
2819
|
+
pctAboveMa20: null,
|
|
2820
|
+
pctAboveMa50: null,
|
|
2821
|
+
equalWeightedReturn: null,
|
|
2822
|
+
volumeWeightedReturn: null,
|
|
2823
|
+
dispersion: null
|
|
2824
|
+
};
|
|
2226
2825
|
}
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2826
|
+
return {
|
|
2827
|
+
source: String(breadth.source ?? ""),
|
|
2828
|
+
available: true,
|
|
2829
|
+
universe: String(breadth.universe ?? ""),
|
|
2830
|
+
interval: String(breadth.interval ?? ""),
|
|
2831
|
+
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
2832
|
+
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
2833
|
+
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
2834
|
+
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
2835
|
+
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
2836
|
+
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
2837
|
+
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
2838
|
+
dispersion: toFiniteNumber(breadth.dispersion)
|
|
2839
|
+
};
|
|
2840
|
+
};
|
|
2841
|
+
return {
|
|
2842
|
+
top5: build("top5"),
|
|
2843
|
+
top10: build("top10"),
|
|
2844
|
+
top30: build("top30"),
|
|
2845
|
+
top50: build("top50"),
|
|
2846
|
+
top100: build("top100")
|
|
2847
|
+
};
|
|
2238
2848
|
};
|
|
2239
|
-
var
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
}
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
);
|
|
2849
|
+
var buildTargetVsReference = (signal, asset) => {
|
|
2850
|
+
const base = toRecord(signal.additionalIndicators?.baseContext);
|
|
2851
|
+
const relative = toRecord(base?.relative);
|
|
2852
|
+
const target = toRecord(relative?.[`targetVs${asset}`]);
|
|
2853
|
+
return {
|
|
2854
|
+
source: target ? String(target.source ?? "") : null,
|
|
2855
|
+
available: Boolean(target),
|
|
2856
|
+
ratioReturn1h: toFiniteNumber(target?.ratioReturn1h),
|
|
2857
|
+
ratioReturn4h: toFiniteNumber(target?.ratioReturn4h),
|
|
2858
|
+
ratioReturn24h: toFiniteNumber(target?.ratioReturn24h),
|
|
2859
|
+
alpha1h: toFiniteNumber(target?.[`alphaVs${asset}1h`]),
|
|
2860
|
+
alpha4h: toFiniteNumber(target?.[`alphaVs${asset}4h`]),
|
|
2861
|
+
alpha24h: toFiniteNumber(target?.[`alphaVs${asset}24h`]),
|
|
2862
|
+
beta: toFiniteNumber(target?.[`betaTo${asset}20`]),
|
|
2863
|
+
correlation: toFiniteNumber(target?.[`correlationTo${asset}20`]),
|
|
2864
|
+
ratioTrend: typeof target?.ratioTrend === "string" ? target.ratioTrend : null
|
|
2865
|
+
};
|
|
2257
2866
|
};
|
|
2258
|
-
var
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2867
|
+
var buildTargetVsBtcContextFromSignal = (signal) => {
|
|
2868
|
+
const { alpha1h, alpha4h, alpha24h, beta, correlation, ...shared } = buildTargetVsReference(signal, "Btc");
|
|
2869
|
+
return {
|
|
2870
|
+
...shared,
|
|
2871
|
+
alphaVsBtc1h: alpha1h,
|
|
2872
|
+
alphaVsBtc4h: alpha4h,
|
|
2873
|
+
alphaVsBtc24h: alpha24h,
|
|
2874
|
+
betaToBtc20: beta,
|
|
2875
|
+
correlationToBtc20: correlation
|
|
2876
|
+
};
|
|
2265
2877
|
};
|
|
2266
|
-
var
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2878
|
+
var buildTargetVsEthContextFromSignal = (signal) => {
|
|
2879
|
+
const { alpha1h, alpha4h, alpha24h, beta, correlation, ...shared } = buildTargetVsReference(signal, "Eth");
|
|
2880
|
+
return {
|
|
2881
|
+
...shared,
|
|
2882
|
+
alphaVsEth1h: alpha1h,
|
|
2883
|
+
alphaVsEth4h: alpha4h,
|
|
2884
|
+
alphaVsEth24h: alpha24h,
|
|
2885
|
+
betaToEth20: beta,
|
|
2886
|
+
correlationToEth20: correlation
|
|
2887
|
+
};
|
|
2274
2888
|
};
|
|
2275
|
-
var
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
const { projectRoot, state } = getStrategyRegistryState(cwd);
|
|
2281
|
-
const source = moduleName.trim();
|
|
2282
|
-
const resolvedModuleName = resolvePluginModuleSpecifier(source, projectRoot);
|
|
2283
|
-
const cacheKey = `${resolvedModuleName}\0${strategyName}`;
|
|
2284
|
-
const cached = state.strategyModuleResolutions.get(cacheKey);
|
|
2285
|
-
if (cached) return cached;
|
|
2286
|
-
const resolution = (async () => {
|
|
2287
|
-
const moduleExport = await importStrategyPluginModule(
|
|
2288
|
-
resolvedModuleName,
|
|
2289
|
-
projectRoot
|
|
2290
|
-
);
|
|
2291
|
-
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
2292
|
-
if (!pluginDefinition) {
|
|
2293
|
-
throw new Error(`${source}: export { strategyEntries } is missing`);
|
|
2294
|
-
}
|
|
2295
|
-
const issues = validateStrategyEntries(
|
|
2296
|
-
source,
|
|
2297
|
-
pluginDefinition.strategyEntries
|
|
2298
|
-
);
|
|
2299
|
-
if (issues.length > 0) {
|
|
2300
|
-
throw new Error(
|
|
2301
|
-
["Invalid TradeJS strategy module:", ...issues].join("\n")
|
|
2302
|
-
);
|
|
2303
|
-
}
|
|
2304
|
-
const entry = pluginDefinition.strategyEntries.find(
|
|
2305
|
-
(candidate) => candidate.manifest.name === strategyName
|
|
2306
|
-
);
|
|
2307
|
-
if (!entry) {
|
|
2308
|
-
throw new Error(`${source}: strategy ${strategyName} is missing`);
|
|
2309
|
-
}
|
|
2310
|
-
const factory = sharedStrategyRegistry.strategyRuntimeFactory;
|
|
2311
|
-
if (!factory) {
|
|
2312
|
-
throw new Error("Strategy runtime factory is not configured");
|
|
2313
|
-
}
|
|
2314
|
-
const manifestsByName = new Map(
|
|
2315
|
-
pluginDefinition.strategyEntries.map(
|
|
2316
|
-
(candidate) => [candidate.manifest.name, candidate.manifest]
|
|
2317
|
-
)
|
|
2318
|
-
);
|
|
2889
|
+
var buildBtcAltRegimeContextFromSignal = (signal) => {
|
|
2890
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2891
|
+
const relative = toRecord(baseContext?.relative);
|
|
2892
|
+
const btcAltRegime = toRecord(relative?.btcAltRegime);
|
|
2893
|
+
if (!btcAltRegime) {
|
|
2319
2894
|
return {
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2895
|
+
source: null,
|
|
2896
|
+
available: false,
|
|
2897
|
+
universe: null,
|
|
2898
|
+
interval: null,
|
|
2899
|
+
stale: null,
|
|
2900
|
+
regime: null,
|
|
2901
|
+
btcReturn24h: null,
|
|
2902
|
+
altBasketReturn24h: null,
|
|
2903
|
+
btcVsAltReturn24h: null,
|
|
2904
|
+
btcTurnoverShare24h: null,
|
|
2905
|
+
btcTurnoverShareChange24h: null,
|
|
2906
|
+
altVolToBtcVol24h: null,
|
|
2907
|
+
altDispersion24h: null
|
|
2331
2908
|
};
|
|
2332
|
-
})();
|
|
2333
|
-
state.strategyModuleResolutions.set(cacheKey, resolution);
|
|
2334
|
-
try {
|
|
2335
|
-
return await resolution;
|
|
2336
|
-
} catch (error) {
|
|
2337
|
-
state.strategyModuleResolutions.delete(cacheKey);
|
|
2338
|
-
throw error;
|
|
2339
2909
|
}
|
|
2910
|
+
return {
|
|
2911
|
+
source: String(btcAltRegime.source ?? ""),
|
|
2912
|
+
available: true,
|
|
2913
|
+
universe: String(btcAltRegime.universe ?? ""),
|
|
2914
|
+
interval: String(btcAltRegime.interval ?? ""),
|
|
2915
|
+
stale: typeof btcAltRegime.stale === "boolean" ? btcAltRegime.stale : null,
|
|
2916
|
+
regime: typeof btcAltRegime.regime === "string" ? btcAltRegime.regime : null,
|
|
2917
|
+
btcReturn24h: toFiniteNumber(btcAltRegime.btcReturn24h),
|
|
2918
|
+
altBasketReturn24h: toFiniteNumber(btcAltRegime.altBasketReturn24h),
|
|
2919
|
+
btcVsAltReturn24h: toFiniteNumber(btcAltRegime.btcVsAltReturn24h),
|
|
2920
|
+
btcTurnoverShare24h: toFiniteNumber(btcAltRegime.btcTurnoverShare24h),
|
|
2921
|
+
btcTurnoverShareChange24h: toFiniteNumber(
|
|
2922
|
+
btcAltRegime.btcTurnoverShareChange24h
|
|
2923
|
+
),
|
|
2924
|
+
altVolToBtcVol24h: toFiniteNumber(btcAltRegime.altVolToBtcVol24h),
|
|
2925
|
+
altDispersion24h: toFiniteNumber(btcAltRegime.altDispersion24h)
|
|
2926
|
+
};
|
|
2340
2927
|
};
|
|
2341
|
-
var
|
|
2342
|
-
const
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
issues.push(
|
|
2370
|
-
`${moduleName}: export { strategyEntries } is missing`
|
|
2371
|
-
);
|
|
2372
|
-
} else {
|
|
2373
|
-
const entryIssues = validateStrategyEntries(
|
|
2374
|
-
moduleName,
|
|
2375
|
-
pluginDefinition.strategyEntries,
|
|
2376
|
-
state
|
|
2377
|
-
);
|
|
2378
|
-
issues.push(...entryIssues);
|
|
2379
|
-
if (entryIssues.length === 0) {
|
|
2380
|
-
registerEntries(
|
|
2381
|
-
pluginDefinition.strategyEntries,
|
|
2382
|
-
moduleName,
|
|
2383
|
-
state
|
|
2384
|
-
);
|
|
2385
|
-
}
|
|
2386
|
-
}
|
|
2387
|
-
}
|
|
2388
|
-
if (indicatorSet.has(moduleName)) {
|
|
2389
|
-
const indicatorPluginDefinition = extractIndicatorPluginDefinition(moduleExport);
|
|
2390
|
-
if (!indicatorPluginDefinition) {
|
|
2391
|
-
issues.push(
|
|
2392
|
-
`${moduleName}: export { indicatorEntries } is missing`
|
|
2393
|
-
);
|
|
2394
|
-
} else {
|
|
2395
|
-
(0, import_indicators.registerIndicatorEntries)(
|
|
2396
|
-
indicatorPluginDefinition.indicatorEntries,
|
|
2397
|
-
moduleName,
|
|
2398
|
-
projectRoot
|
|
2399
|
-
);
|
|
2400
|
-
}
|
|
2401
|
-
}
|
|
2402
|
-
if (!strategySet.has(moduleName) && !indicatorSet.has(moduleName)) {
|
|
2403
|
-
issues.push(`${moduleName}: plugin is not declared in config`);
|
|
2404
|
-
}
|
|
2405
|
-
} catch (error) {
|
|
2406
|
-
issues.push(`${moduleName}: failed to import: ${String(error)}`);
|
|
2407
|
-
}
|
|
2408
|
-
}
|
|
2409
|
-
if (issues.length > 0) {
|
|
2410
|
-
throw new Error(
|
|
2411
|
-
["Invalid TradeJS plugin catalog:", ...issues].join("\n")
|
|
2412
|
-
);
|
|
2413
|
-
}
|
|
2414
|
-
})();
|
|
2928
|
+
var buildCmcGlobalContextFromSignal = (signal) => {
|
|
2929
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2930
|
+
const relative = toRecord(baseContext?.relative);
|
|
2931
|
+
const cmcGlobal = toRecord(relative?.cmcGlobal);
|
|
2932
|
+
if (!cmcGlobal) {
|
|
2933
|
+
return {
|
|
2934
|
+
source: null,
|
|
2935
|
+
available: false,
|
|
2936
|
+
interval: null,
|
|
2937
|
+
asOfTs: null,
|
|
2938
|
+
stale: null,
|
|
2939
|
+
totalMarketCapUsd: null,
|
|
2940
|
+
totalVolumeUsd: null,
|
|
2941
|
+
totalVolumeReportedUsd: null,
|
|
2942
|
+
altMarketCapUsd: null,
|
|
2943
|
+
altVolumeUsd: null,
|
|
2944
|
+
altVolumeReportedUsd: null,
|
|
2945
|
+
btcDominancePct: null,
|
|
2946
|
+
ethDominancePct: null,
|
|
2947
|
+
btcDominanceChange24hPct: null,
|
|
2948
|
+
ethDominanceChange24hPct: null,
|
|
2949
|
+
altMarketCapChange24hPct: null,
|
|
2950
|
+
altVolumeChange24hPct: null,
|
|
2951
|
+
activeCryptocurrencies: null,
|
|
2952
|
+
activeExchanges: null,
|
|
2953
|
+
activeMarketPairs: null,
|
|
2954
|
+
altLiquidityRegime: null
|
|
2955
|
+
};
|
|
2415
2956
|
}
|
|
2416
|
-
|
|
2957
|
+
return {
|
|
2958
|
+
source: String(cmcGlobal.source ?? ""),
|
|
2959
|
+
available: true,
|
|
2960
|
+
interval: typeof cmcGlobal.interval === "string" ? cmcGlobal.interval : null,
|
|
2961
|
+
asOfTs: toFiniteNumber(cmcGlobal.asOfTs),
|
|
2962
|
+
stale: typeof cmcGlobal.stale === "boolean" ? cmcGlobal.stale : null,
|
|
2963
|
+
totalMarketCapUsd: toFiniteNumber(cmcGlobal.totalMarketCapUsd),
|
|
2964
|
+
totalVolumeUsd: toFiniteNumber(cmcGlobal.totalVolumeUsd),
|
|
2965
|
+
totalVolumeReportedUsd: toFiniteNumber(cmcGlobal.totalVolumeReportedUsd),
|
|
2966
|
+
altMarketCapUsd: toFiniteNumber(cmcGlobal.altMarketCapUsd),
|
|
2967
|
+
altVolumeUsd: toFiniteNumber(cmcGlobal.altVolumeUsd),
|
|
2968
|
+
altVolumeReportedUsd: toFiniteNumber(cmcGlobal.altVolumeReportedUsd),
|
|
2969
|
+
btcDominancePct: toFiniteNumber(cmcGlobal.btcDominancePct),
|
|
2970
|
+
ethDominancePct: toFiniteNumber(cmcGlobal.ethDominancePct),
|
|
2971
|
+
btcDominanceChange24hPct: toFiniteNumber(
|
|
2972
|
+
cmcGlobal.btcDominanceChange24hPct
|
|
2973
|
+
),
|
|
2974
|
+
ethDominanceChange24hPct: toFiniteNumber(
|
|
2975
|
+
cmcGlobal.ethDominanceChange24hPct
|
|
2976
|
+
),
|
|
2977
|
+
altMarketCapChange24hPct: toFiniteNumber(
|
|
2978
|
+
cmcGlobal.altMarketCapChange24hPct
|
|
2979
|
+
),
|
|
2980
|
+
altVolumeChange24hPct: toFiniteNumber(cmcGlobal.altVolumeChange24hPct),
|
|
2981
|
+
activeCryptocurrencies: toFiniteNumber(cmcGlobal.activeCryptocurrencies),
|
|
2982
|
+
activeExchanges: toFiniteNumber(cmcGlobal.activeExchanges),
|
|
2983
|
+
activeMarketPairs: toFiniteNumber(cmcGlobal.activeMarketPairs),
|
|
2984
|
+
altLiquidityRegime: typeof cmcGlobal.altLiquidityRegime === "string" ? cmcGlobal.altLiquidityRegime : null
|
|
2985
|
+
};
|
|
2417
2986
|
};
|
|
2418
|
-
var
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2987
|
+
var buildCmcReferenceAssetsContextFromSignal = (signal) => {
|
|
2988
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2989
|
+
const relative = toRecord(baseContext?.relative);
|
|
2990
|
+
const cmcReferenceAssets = toRecord(relative?.cmcReferenceAssets);
|
|
2991
|
+
if (!cmcReferenceAssets) {
|
|
2992
|
+
return {
|
|
2993
|
+
source: null,
|
|
2994
|
+
available: false,
|
|
2995
|
+
interval: null,
|
|
2996
|
+
asOfTs: null,
|
|
2997
|
+
stale: null,
|
|
2998
|
+
btcMarketCapUsd: null,
|
|
2999
|
+
ethMarketCapUsd: null,
|
|
3000
|
+
btcVolumeUsd: null,
|
|
3001
|
+
ethVolumeUsd: null,
|
|
3002
|
+
btcVolumeToMarketCap: null,
|
|
3003
|
+
ethVolumeToMarketCap: null,
|
|
3004
|
+
ethBtcMarketCapRatio: null,
|
|
3005
|
+
ethBtcMarketCapRatioChange24hPct: null,
|
|
3006
|
+
ethVsBtcVolumeRatio: null,
|
|
3007
|
+
referenceLiquidityRegime: null
|
|
3008
|
+
};
|
|
2423
3009
|
}
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
3010
|
+
return {
|
|
3011
|
+
source: String(cmcReferenceAssets.source ?? ""),
|
|
3012
|
+
available: true,
|
|
3013
|
+
interval: typeof cmcReferenceAssets.interval === "string" ? cmcReferenceAssets.interval : null,
|
|
3014
|
+
asOfTs: toFiniteNumber(cmcReferenceAssets.asOfTs),
|
|
3015
|
+
stale: typeof cmcReferenceAssets.stale === "boolean" ? cmcReferenceAssets.stale : null,
|
|
3016
|
+
btcMarketCapUsd: toFiniteNumber(cmcReferenceAssets.btcMarketCapUsd),
|
|
3017
|
+
ethMarketCapUsd: toFiniteNumber(cmcReferenceAssets.ethMarketCapUsd),
|
|
3018
|
+
btcVolumeUsd: toFiniteNumber(cmcReferenceAssets.btcVolumeUsd),
|
|
3019
|
+
ethVolumeUsd: toFiniteNumber(cmcReferenceAssets.ethVolumeUsd),
|
|
3020
|
+
btcVolumeToMarketCap: toFiniteNumber(
|
|
3021
|
+
cmcReferenceAssets.btcVolumeToMarketCap
|
|
3022
|
+
),
|
|
3023
|
+
ethVolumeToMarketCap: toFiniteNumber(
|
|
3024
|
+
cmcReferenceAssets.ethVolumeToMarketCap
|
|
3025
|
+
),
|
|
3026
|
+
ethBtcMarketCapRatio: toFiniteNumber(
|
|
3027
|
+
cmcReferenceAssets.ethBtcMarketCapRatio
|
|
3028
|
+
),
|
|
3029
|
+
ethBtcMarketCapRatioChange24hPct: toFiniteNumber(
|
|
3030
|
+
cmcReferenceAssets.ethBtcMarketCapRatioChange24hPct
|
|
3031
|
+
),
|
|
3032
|
+
ethVsBtcVolumeRatio: toFiniteNumber(cmcReferenceAssets.ethVsBtcVolumeRatio),
|
|
3033
|
+
referenceLiquidityRegime: typeof cmcReferenceAssets.referenceLiquidityRegime === "string" ? cmcReferenceAssets.referenceLiquidityRegime : null
|
|
3034
|
+
};
|
|
2427
3035
|
};
|
|
2428
|
-
var
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
3036
|
+
var buildCmcExchangeLiquidityContextFromSignal = (signal) => {
|
|
3037
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3038
|
+
const relative = toRecord(baseContext?.relative);
|
|
3039
|
+
const cmcExchangeLiquidity = toRecord(relative?.cmcExchangeLiquidity);
|
|
3040
|
+
if (!cmcExchangeLiquidity) {
|
|
3041
|
+
return {
|
|
3042
|
+
source: null,
|
|
3043
|
+
available: false,
|
|
3044
|
+
interval: null,
|
|
3045
|
+
asOfTs: null,
|
|
3046
|
+
stale: null,
|
|
3047
|
+
exchangesCount: null,
|
|
3048
|
+
totalVolumeUsd: null,
|
|
3049
|
+
totalVolumeChange24hPct: null,
|
|
3050
|
+
binanceVolumeUsd: null,
|
|
3051
|
+
binanceVolumeShare: null,
|
|
3052
|
+
topExchangeVolumeShare: null,
|
|
3053
|
+
liquidityRegime: null
|
|
3054
|
+
};
|
|
2432
3055
|
}
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
3056
|
+
return {
|
|
3057
|
+
source: String(cmcExchangeLiquidity.source ?? ""),
|
|
3058
|
+
available: true,
|
|
3059
|
+
interval: typeof cmcExchangeLiquidity.interval === "string" ? cmcExchangeLiquidity.interval : null,
|
|
3060
|
+
asOfTs: toFiniteNumber(cmcExchangeLiquidity.asOfTs),
|
|
3061
|
+
stale: typeof cmcExchangeLiquidity.stale === "boolean" ? cmcExchangeLiquidity.stale : null,
|
|
3062
|
+
exchangesCount: toFiniteNumber(cmcExchangeLiquidity.exchangesCount),
|
|
3063
|
+
totalVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.totalVolumeUsd),
|
|
3064
|
+
totalVolumeChange24hPct: toFiniteNumber(
|
|
3065
|
+
cmcExchangeLiquidity.totalVolumeChange24hPct
|
|
3066
|
+
),
|
|
3067
|
+
binanceVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeUsd),
|
|
3068
|
+
binanceVolumeShare: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeShare),
|
|
3069
|
+
topExchangeVolumeShare: toFiniteNumber(
|
|
3070
|
+
cmcExchangeLiquidity.topExchangeVolumeShare
|
|
3071
|
+
),
|
|
3072
|
+
liquidityRegime: typeof cmcExchangeLiquidity.liquidityRegime === "string" ? cmcExchangeLiquidity.liquidityRegime : null
|
|
3073
|
+
};
|
|
2436
3074
|
};
|
|
2437
|
-
var
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
3075
|
+
var buildCmcFearGreedContextFromSignal = (signal) => {
|
|
3076
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3077
|
+
const relative = toRecord(baseContext?.relative);
|
|
3078
|
+
const cmcFearGreed = toRecord(relative?.cmcFearGreed);
|
|
3079
|
+
if (!cmcFearGreed) {
|
|
3080
|
+
return {
|
|
3081
|
+
source: null,
|
|
3082
|
+
available: false,
|
|
3083
|
+
interval: null,
|
|
3084
|
+
asOfTs: null,
|
|
3085
|
+
stale: null,
|
|
3086
|
+
value: null,
|
|
3087
|
+
valueChange24h: null,
|
|
3088
|
+
valueChange7d: null,
|
|
3089
|
+
classification: null,
|
|
3090
|
+
sentimentRegime: null
|
|
3091
|
+
};
|
|
2441
3092
|
}
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
3093
|
+
return {
|
|
3094
|
+
source: String(cmcFearGreed.source ?? ""),
|
|
3095
|
+
available: true,
|
|
3096
|
+
interval: typeof cmcFearGreed.interval === "string" ? cmcFearGreed.interval : null,
|
|
3097
|
+
asOfTs: toFiniteNumber(cmcFearGreed.asOfTs),
|
|
3098
|
+
stale: typeof cmcFearGreed.stale === "boolean" ? cmcFearGreed.stale : null,
|
|
3099
|
+
value: toFiniteNumber(cmcFearGreed.value),
|
|
3100
|
+
valueChange24h: toFiniteNumber(cmcFearGreed.valueChange24h),
|
|
3101
|
+
valueChange7d: toFiniteNumber(cmcFearGreed.valueChange7d),
|
|
3102
|
+
classification: typeof cmcFearGreed.classification === "string" ? cmcFearGreed.classification : null,
|
|
3103
|
+
sentimentRegime: typeof cmcFearGreed.sentimentRegime === "string" ? cmcFearGreed.sentimentRegime : null
|
|
3104
|
+
};
|
|
2445
3105
|
};
|
|
2446
|
-
var
|
|
2447
|
-
|
|
2448
|
-
|
|
3106
|
+
var buildCmcIndexesContextFromSignal = (signal) => {
|
|
3107
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3108
|
+
const relative = toRecord(baseContext?.relative);
|
|
3109
|
+
const cmcIndexes = toRecord(relative?.cmcIndexes);
|
|
3110
|
+
if (!cmcIndexes) {
|
|
3111
|
+
return {
|
|
3112
|
+
source: null,
|
|
3113
|
+
available: false,
|
|
3114
|
+
interval: null,
|
|
3115
|
+
asOfTs: null,
|
|
3116
|
+
stale: null,
|
|
3117
|
+
cmc100Value: null,
|
|
3118
|
+
cmc100Change24hPct: null,
|
|
3119
|
+
cmc100TopConstituentSymbol: null,
|
|
3120
|
+
cmc100TopConstituentWeightPct: null,
|
|
3121
|
+
cmc20Value: null,
|
|
3122
|
+
cmc20Change24hPct: null,
|
|
3123
|
+
cmc20TopConstituentSymbol: null,
|
|
3124
|
+
cmc20TopConstituentWeightPct: null,
|
|
3125
|
+
cmc20ToCmc100Ratio: null,
|
|
3126
|
+
cmc20ToCmc100RatioChange24hPct: null,
|
|
3127
|
+
indexRegime: null
|
|
3128
|
+
};
|
|
3129
|
+
}
|
|
3130
|
+
return {
|
|
3131
|
+
source: String(cmcIndexes.source ?? ""),
|
|
3132
|
+
available: true,
|
|
3133
|
+
interval: typeof cmcIndexes.interval === "string" ? cmcIndexes.interval : null,
|
|
3134
|
+
asOfTs: toFiniteNumber(cmcIndexes.asOfTs),
|
|
3135
|
+
stale: typeof cmcIndexes.stale === "boolean" ? cmcIndexes.stale : null,
|
|
3136
|
+
cmc100Value: toFiniteNumber(cmcIndexes.cmc100Value),
|
|
3137
|
+
cmc100Change24hPct: toFiniteNumber(cmcIndexes.cmc100Change24hPct),
|
|
3138
|
+
cmc100TopConstituentSymbol: typeof cmcIndexes.cmc100TopConstituentSymbol === "string" ? cmcIndexes.cmc100TopConstituentSymbol : null,
|
|
3139
|
+
cmc100TopConstituentWeightPct: toFiniteNumber(
|
|
3140
|
+
cmcIndexes.cmc100TopConstituentWeightPct
|
|
3141
|
+
),
|
|
3142
|
+
cmc20Value: toFiniteNumber(cmcIndexes.cmc20Value),
|
|
3143
|
+
cmc20Change24hPct: toFiniteNumber(cmcIndexes.cmc20Change24hPct),
|
|
3144
|
+
cmc20TopConstituentSymbol: typeof cmcIndexes.cmc20TopConstituentSymbol === "string" ? cmcIndexes.cmc20TopConstituentSymbol : null,
|
|
3145
|
+
cmc20TopConstituentWeightPct: toFiniteNumber(
|
|
3146
|
+
cmcIndexes.cmc20TopConstituentWeightPct
|
|
3147
|
+
),
|
|
3148
|
+
cmc20ToCmc100Ratio: toFiniteNumber(cmcIndexes.cmc20ToCmc100Ratio),
|
|
3149
|
+
cmc20ToCmc100RatioChange24hPct: toFiniteNumber(
|
|
3150
|
+
cmcIndexes.cmc20ToCmc100RatioChange24hPct
|
|
3151
|
+
),
|
|
3152
|
+
indexRegime: typeof cmcIndexes.indexRegime === "string" ? cmcIndexes.indexRegime : null
|
|
3153
|
+
};
|
|
3154
|
+
};
|
|
3155
|
+
var buildReferenceTradeFlowContextFromSignal = (signal) => {
|
|
3156
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3157
|
+
const relative = toRecord(baseContext?.relative);
|
|
3158
|
+
const refs = toRecord(relative?.referenceTradeFlow);
|
|
3159
|
+
const primaryReferenceSymbol = typeof refs?.primaryReferenceSymbol === "string" ? refs.primaryReferenceSymbol : null;
|
|
3160
|
+
const tradeFlowBySymbol = toRecord(refs?.tradeFlowBySymbol);
|
|
3161
|
+
const primaryTradeFlow = primaryReferenceSymbol != null ? toRecord(tradeFlowBySymbol?.[primaryReferenceSymbol]) : null;
|
|
3162
|
+
if (!refs) {
|
|
3163
|
+
return {
|
|
3164
|
+
source: null,
|
|
3165
|
+
available: false,
|
|
3166
|
+
primaryReferenceSymbol: null,
|
|
3167
|
+
referenceSymbols: [],
|
|
3168
|
+
primaryTradeFlowBuyPressurePct: null,
|
|
3169
|
+
primaryTradeFlowStale: null
|
|
3170
|
+
};
|
|
2449
3171
|
}
|
|
2450
|
-
|
|
2451
|
-
|
|
3172
|
+
return {
|
|
3173
|
+
source: String(refs.source ?? ""),
|
|
3174
|
+
available: true,
|
|
3175
|
+
primaryReferenceSymbol,
|
|
3176
|
+
referenceSymbols: Array.isArray(refs.referenceSymbols) ? refs.referenceSymbols.map(String) : [],
|
|
3177
|
+
primaryTradeFlowBuyPressurePct: toFiniteNumber(
|
|
3178
|
+
primaryTradeFlow?.buyPressurePct
|
|
3179
|
+
),
|
|
3180
|
+
primaryTradeFlowStale: typeof primaryTradeFlow?.stale === "boolean" ? primaryTradeFlow.stale : null
|
|
3181
|
+
};
|
|
2452
3182
|
};
|
|
2453
|
-
var
|
|
2454
|
-
{
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
3183
|
+
var buildAiMarketContext = (signal) => ({
|
|
3184
|
+
execution: {
|
|
3185
|
+
binanceCoinbaseSpread: buildSpreadContextFromSignal(signal)
|
|
3186
|
+
},
|
|
3187
|
+
participation: {
|
|
3188
|
+
trueDelta: buildTrueDeltaContextFromSignal(signal),
|
|
3189
|
+
tradeFlow: buildTradeFlowContextFromSignal(signal)
|
|
3190
|
+
},
|
|
3191
|
+
relative: {
|
|
3192
|
+
marketBreadth: buildMarketBreadthContextFromSignal(signal),
|
|
3193
|
+
marketBreadths: buildMarketBreadthsContextFromSignal(signal),
|
|
3194
|
+
targetVsBtc: buildTargetVsBtcContextFromSignal(signal),
|
|
3195
|
+
targetVsEth: buildTargetVsEthContextFromSignal(signal),
|
|
3196
|
+
btcAltRegime: buildBtcAltRegimeContextFromSignal(signal),
|
|
3197
|
+
cmcGlobal: buildCmcGlobalContextFromSignal(signal),
|
|
3198
|
+
cmcReferenceAssets: buildCmcReferenceAssetsContextFromSignal(signal),
|
|
3199
|
+
cmcExchangeLiquidity: buildCmcExchangeLiquidityContextFromSignal(signal),
|
|
3200
|
+
cmcFearGreed: buildCmcFearGreedContextFromSignal(signal),
|
|
3201
|
+
cmcIndexes: buildCmcIndexesContextFromSignal(signal),
|
|
3202
|
+
referenceTradeFlow: buildReferenceTradeFlowContextFromSignal(signal)
|
|
2469
3203
|
}
|
|
2470
|
-
);
|
|
3204
|
+
});
|
|
2471
3205
|
|
|
2472
3206
|
// src/strategy/policyProfiles.ts
|
|
2473
3207
|
var profileMatches = (profile, universe, assetClass) => {
|
|
@@ -2569,12 +3303,61 @@ var buildBaseAiPayload = (signal) => {
|
|
|
2569
3303
|
};
|
|
2570
3304
|
};
|
|
2571
3305
|
var defaultAiAdapter = {};
|
|
3306
|
+
var tickerProfileFeatureIds = new Set(
|
|
3307
|
+
import_aiFeatures.TICKER_PROFILE_AI_FEATURES.map(({ id }) => id)
|
|
3308
|
+
);
|
|
2572
3309
|
var getStrategyAiAdapter = (strategy, profileId) => getStrategyProfileAiAdapter(getStrategyManifest(strategy), profileId) ?? defaultAiAdapter;
|
|
2573
3310
|
var getSignalAiAdapter = (signal) => getStrategyAiAdapter(signal.strategy, signal.policyProfileId);
|
|
2574
3311
|
var buildAiPayloadByStrategy = (signal) => {
|
|
2575
3312
|
const basePayload = buildBaseAiPayload(signal);
|
|
2576
3313
|
const adapter = getSignalAiAdapter(signal);
|
|
2577
|
-
|
|
3314
|
+
const payload = adapter.buildPayload?.({ signal, basePayload }) ?? basePayload;
|
|
3315
|
+
const custom = getStrategyManifest(signal.strategy)?.aiFeatures ?? [];
|
|
3316
|
+
const frozen = signal.aiFeatureReplay?.snapshot;
|
|
3317
|
+
if (frozen) (0, import_aiFeatures.validateAiFeatureSnapshot)(frozen);
|
|
3318
|
+
const tickerProfile = signal.additionalIndicators?.tickerProfile ?? signal.additionalIndicators?.baseContext?.tickerProfile;
|
|
3319
|
+
const hasProfile = Boolean(
|
|
3320
|
+
tickerProfile || frozen?.definitions.some(({ id }) => tickerProfileFeatureIds.has(id))
|
|
3321
|
+
);
|
|
3322
|
+
const indicatorFeatures = (0, import_indicators2.getRegisteredIndicatorEntries)(
|
|
3323
|
+
getTradejsProjectCwd()
|
|
3324
|
+
).flatMap((entry) => entry.indicator.enabled ? entry.aiFeatures ?? [] : []);
|
|
3325
|
+
const hasPortfolio = Boolean(
|
|
3326
|
+
signal.additionalIndicators?.portfolioContext || frozen?.definitions.some(
|
|
3327
|
+
({ id }) => import_aiFeatures.PORTFOLIO_AI_FEATURES.some((d) => d.id === id)
|
|
3328
|
+
)
|
|
3329
|
+
);
|
|
3330
|
+
const definitions = [
|
|
3331
|
+
...hasPortfolio ? import_aiFeatures.PORTFOLIO_AI_FEATURES : [],
|
|
3332
|
+
...hasProfile ? import_aiFeatures.TICKER_PROFILE_AI_FEATURES : [],
|
|
3333
|
+
...indicatorFeatures,
|
|
3334
|
+
...custom
|
|
3335
|
+
];
|
|
3336
|
+
if (!definitions.length && !signal.aiFeatureReplay?.snapshot) return payload;
|
|
3337
|
+
(0, import_aiFeatures.validateAiFeatureDefinitions)(definitions);
|
|
3338
|
+
if (signal.aiFeatureReplay && !frozen) {
|
|
3339
|
+
throw new Error(
|
|
3340
|
+
"Dataset has no frozen AI features; regenerate the decision-time export"
|
|
3341
|
+
);
|
|
3342
|
+
}
|
|
3343
|
+
if (frozen && JSON.stringify(frozen.definitions) !== JSON.stringify(definitions)) {
|
|
3344
|
+
throw new Error(
|
|
3345
|
+
"AI feature definition changed; regenerate the decision-time export"
|
|
3346
|
+
);
|
|
3347
|
+
}
|
|
3348
|
+
return {
|
|
3349
|
+
...payload,
|
|
3350
|
+
featureSnapshot: frozen ?? (0, import_aiFeatures.projectAiFeatures)(definitions, {
|
|
3351
|
+
signal: tickerProfile ? {
|
|
3352
|
+
...signal,
|
|
3353
|
+
additionalIndicators: {
|
|
3354
|
+
...signal.additionalIndicators,
|
|
3355
|
+
tickerProfile
|
|
3356
|
+
}
|
|
3357
|
+
} : signal,
|
|
3358
|
+
payload
|
|
3359
|
+
})
|
|
3360
|
+
};
|
|
2578
3361
|
};
|
|
2579
3362
|
var buildAiSystemPromptAddonByStrategy = (signal) => getSignalAiAdapter(signal).buildSystemPromptAddon?.({ signal }) ?? "";
|
|
2580
3363
|
var buildAiHumanPromptAddonByStrategy = (signal, payload) => getSignalAiAdapter(signal).buildHumanPromptAddon?.({
|
|
@@ -2988,352 +3771,104 @@ var runAiPrompt = async ({ systemPrompt, humanPrompt }, options = {}) => {
|
|
|
2988
3771
|
{
|
|
2989
3772
|
role: "system",
|
|
2990
3773
|
content: `Write all user-visible text fields in ${responseLanguage}. Keep field names and JSON syntax unchanged.`
|
|
2991
|
-
},
|
|
2992
|
-
{ role: "user", content: humanPrompt }
|
|
2993
|
-
]
|
|
2994
|
-
});
|
|
2995
|
-
const parsed = parseAIResponse(response.content);
|
|
2996
|
-
const normalized = normalizeAnalysis(parsed);
|
|
2997
|
-
if (!options.signal) {
|
|
2998
|
-
return normalized;
|
|
2999
|
-
}
|
|
3000
|
-
return postProcessAiAnalysisByStrategy(
|
|
3001
|
-
options.signal,
|
|
3002
|
-
normalized,
|
|
3003
|
-
options.payload
|
|
3004
|
-
);
|
|
3005
|
-
};
|
|
3006
|
-
var runAiPromptLocal = async (signal, options = {}) => {
|
|
3007
|
-
const payload = options.payload ?? buildAiPayload(signal);
|
|
3008
|
-
const gateContext = getDeterministicAiGateContext(payload);
|
|
3009
|
-
const signalDirection = getSignalDirection(signal);
|
|
3010
|
-
const deterministicQuality = getDeterministicQuality(gateContext);
|
|
3011
|
-
const approvalAllowedNow = typeof gateContext?.approvalAllowedNow === "boolean" ? gateContext.approvalAllowedNow : deterministicQuality >= 4;
|
|
3012
|
-
return postProcessLocalAiAnalysisByStrategy(
|
|
3013
|
-
signal,
|
|
3014
|
-
{
|
|
3015
|
-
direction: approvalAllowedNow ? signalDirection : null,
|
|
3016
|
-
quality: deterministicQuality,
|
|
3017
|
-
needRetest: !approvalAllowedNow,
|
|
3018
|
-
retestPrice: null,
|
|
3019
|
-
takeProfitPrice: approvalAllowedNow ? signal.prices?.takeProfitPrice ?? null : null,
|
|
3020
|
-
stopLossPrice: approvalAllowedNow ? signal.prices?.stopLossPrice ?? null : null
|
|
3021
|
-
},
|
|
3022
|
-
payload
|
|
3023
|
-
);
|
|
3024
|
-
};
|
|
3025
|
-
var askAI = async (signal, options = {}) => {
|
|
3026
|
-
const { symbol } = signal;
|
|
3027
|
-
const payload = buildAiPayload(signal);
|
|
3028
|
-
const content = await runAiPrompt(
|
|
3029
|
-
{
|
|
3030
|
-
systemPrompt: buildAiSystemPrompt(signal),
|
|
3031
|
-
humanPrompt: buildAiHumanPrompt(signal, payload)
|
|
3032
|
-
},
|
|
3033
|
-
{
|
|
3034
|
-
...options,
|
|
3035
|
-
signal,
|
|
3036
|
-
payload
|
|
3037
|
-
}
|
|
3038
|
-
);
|
|
3039
|
-
await (0, import_redis.setData)(import_redis.redisKeys.analysis(symbol, signal.signalId), content);
|
|
3040
|
-
return content;
|
|
3041
|
-
};
|
|
3042
|
-
|
|
3043
|
-
// src/strategyAdapters/ml.ts
|
|
3044
|
-
var defaultMlAdapter = {
|
|
3045
|
-
normalizeStrategyConfig: (strategyConfig) => strategyConfig
|
|
3046
|
-
};
|
|
3047
|
-
var getStrategyMlAdapter = (strategy, profileId) => {
|
|
3048
|
-
const strategyAdapter = getStrategyProfileMlAdapter(
|
|
3049
|
-
getStrategyManifest(strategy),
|
|
3050
|
-
profileId
|
|
3051
|
-
);
|
|
3052
|
-
if (!strategyAdapter) return defaultMlAdapter;
|
|
3053
|
-
return {
|
|
3054
|
-
...defaultMlAdapter,
|
|
3055
|
-
...strategyAdapter
|
|
3056
|
-
};
|
|
3057
|
-
};
|
|
3058
|
-
|
|
3059
|
-
// src/mlPayload.ts
|
|
3060
|
-
var normalizeStrategyConfig = (strategyConfig, strategyName, profileId) => {
|
|
3061
|
-
return getStrategyMlAdapter(
|
|
3062
|
-
strategyName,
|
|
3063
|
-
profileId
|
|
3064
|
-
).normalizeStrategyConfig?.(strategyConfig);
|
|
3065
|
-
};
|
|
3066
|
-
var buildMlPayload = (payload) => {
|
|
3067
|
-
const strategyName = payload.signal?.strategy ?? payload.context?.strategyName;
|
|
3068
|
-
const profileId = payload.signal?.policyProfileId;
|
|
3069
|
-
const mlAdapter = getStrategyMlAdapter(strategyName, profileId);
|
|
3070
|
-
const normalizedSignal = mlAdapter.normalizeSignal?.(payload.signal) ?? payload.signal;
|
|
3071
|
-
const nextSignal = {
|
|
3072
|
-
...normalizedSignal,
|
|
3073
|
-
indicators: {
|
|
3074
|
-
...normalizedSignal?.indicators ?? {}
|
|
3075
|
-
}
|
|
3076
|
-
};
|
|
3077
|
-
const nextContext = payload.context ? {
|
|
3078
|
-
...payload.context,
|
|
3079
|
-
strategyConfig: normalizeStrategyConfig(
|
|
3080
|
-
payload.context.strategyConfig,
|
|
3081
|
-
strategyName,
|
|
3082
|
-
profileId
|
|
3083
|
-
)
|
|
3084
|
-
} : void 0;
|
|
3085
|
-
return {
|
|
3086
|
-
signal: nextSignal,
|
|
3087
|
-
context: nextContext
|
|
3088
|
-
};
|
|
3089
|
-
};
|
|
3090
|
-
|
|
3091
|
-
// src/runtimeJournal.ts
|
|
3092
|
-
var import_node_crypto = require("crypto");
|
|
3093
|
-
var import_constants = require("@tradejs/core/constants");
|
|
3094
|
-
var import_time = require("@tradejs/core/time");
|
|
3095
|
-
var import_trade = require("@tradejs/core/trade");
|
|
3096
|
-
var import_logger3 = require("@tradejs/infra/logger");
|
|
3097
|
-
var import_redis2 = require("@tradejs/infra/redis");
|
|
3098
|
-
var now = () => Date.now();
|
|
3099
|
-
var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
|
|
3100
|
-
var calculateClosedPnl = ({
|
|
3101
|
-
direction,
|
|
3102
|
-
entryPrice,
|
|
3103
|
-
exitPrice,
|
|
3104
|
-
qty
|
|
3105
|
-
}) => {
|
|
3106
|
-
const pnl = direction === "LONG" ? (exitPrice - entryPrice) * qty : (entryPrice - exitPrice) * qty;
|
|
3107
|
-
return Number.isFinite(pnl) ? pnl : null;
|
|
3108
|
-
};
|
|
3109
|
-
var createRuntimeOrderId = (strategy) => {
|
|
3110
|
-
const prefix = (0, import_trade.createRuntimeOrderLinkPrefix)(strategy);
|
|
3111
|
-
if (prefix === "tjs-") {
|
|
3112
|
-
return `tjs-${(0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 24).toLowerCase()}`;
|
|
3113
|
-
}
|
|
3114
|
-
return `${prefix}${toRandomOrderSuffix()}`;
|
|
3115
|
-
};
|
|
3116
|
-
var recordRuntimeTradeOpen = async (params) => {
|
|
3117
|
-
const { userName } = params;
|
|
3118
|
-
if (!userName) {
|
|
3119
|
-
return null;
|
|
3120
|
-
}
|
|
3121
|
-
const record = {
|
|
3122
|
-
...params,
|
|
3123
|
-
entryCount: 1,
|
|
3124
|
-
lastEntryPrice: params.entryPrice,
|
|
3125
|
-
lastEntryQty: params.qty,
|
|
3126
|
-
lastEntryTimestamp: params.entryTimestamp,
|
|
3127
|
-
status: "active",
|
|
3128
|
-
currentPrice: params.entryPrice,
|
|
3129
|
-
currentPnl: 0,
|
|
3130
|
-
closedPnl: null,
|
|
3131
|
-
exitPrice: null,
|
|
3132
|
-
exitTimestamp: null,
|
|
3133
|
-
lastSyncedAt: now()
|
|
3134
|
-
};
|
|
3135
|
-
const dayKey = (0, import_time.getRuntimeStorageDayKey)(record.entryTimestamp);
|
|
3136
|
-
const runtimeScopeId = record.deploymentId ?? record.accountId;
|
|
3137
|
-
try {
|
|
3138
|
-
await Promise.all([
|
|
3139
|
-
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, record.orderId), record, {
|
|
3140
|
-
expire: 0
|
|
3141
|
-
}),
|
|
3142
|
-
(0, import_redis2.setHashJsonField)(
|
|
3143
|
-
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
3144
|
-
record.orderId,
|
|
3145
|
-
record,
|
|
3146
|
-
{ expire: 0 }
|
|
3147
|
-
),
|
|
3148
|
-
(0, import_redis2.setData)(
|
|
3149
|
-
import_redis2.redisKeys.runtimeActiveTrade(userName, record.symbol, runtimeScopeId),
|
|
3150
|
-
{ orderId: record.orderId },
|
|
3151
|
-
{ expire: 0 }
|
|
3152
|
-
)
|
|
3153
|
-
]);
|
|
3154
|
-
} catch (error) {
|
|
3155
|
-
import_logger3.logger.error(
|
|
3156
|
-
"runtime trade open journal failed: %s %s",
|
|
3157
|
-
record.symbol,
|
|
3158
|
-
error?.message || String(error)
|
|
3159
|
-
);
|
|
3160
|
-
}
|
|
3161
|
-
return record;
|
|
3162
|
-
};
|
|
3163
|
-
var recordRuntimeTradeIncrease = async (params) => {
|
|
3164
|
-
const {
|
|
3165
|
-
userName,
|
|
3166
|
-
strategy,
|
|
3167
|
-
symbol,
|
|
3168
|
-
direction,
|
|
3169
|
-
resultingQty,
|
|
3170
|
-
resultingEntryPrice,
|
|
3171
|
-
addedQty,
|
|
3172
|
-
addedEntryPrice,
|
|
3173
|
-
entryTimestamp,
|
|
3174
|
-
fee,
|
|
3175
|
-
accountId,
|
|
3176
|
-
deploymentId
|
|
3177
|
-
} = params;
|
|
3178
|
-
if (!userName) {
|
|
3179
|
-
return null;
|
|
3180
|
-
}
|
|
3181
|
-
const existing = await getActiveRuntimeTrade({
|
|
3182
|
-
userName,
|
|
3183
|
-
symbol,
|
|
3184
|
-
accountId,
|
|
3185
|
-
deploymentId
|
|
3774
|
+
},
|
|
3775
|
+
{ role: "user", content: humanPrompt }
|
|
3776
|
+
]
|
|
3186
3777
|
});
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
const openFee = (existing.openFee ?? existing.fee ?? 0) + addedFee;
|
|
3192
|
-
const totalFee = (existing.totalFee ?? existing.fee ?? 0) + addedFee;
|
|
3193
|
-
const next = {
|
|
3194
|
-
...existing,
|
|
3195
|
-
...params.orderLeverage ? { orderLeverage: params.orderLeverage } : {},
|
|
3196
|
-
qty: resultingQty,
|
|
3197
|
-
entryPrice: resultingEntryPrice,
|
|
3198
|
-
entryCount: Math.max(1, existing.entryCount ?? 1) + 1,
|
|
3199
|
-
lastEntryPrice: addedEntryPrice,
|
|
3200
|
-
lastEntryQty: addedQty,
|
|
3201
|
-
lastEntryTimestamp: entryTimestamp,
|
|
3202
|
-
currentPrice: resultingEntryPrice,
|
|
3203
|
-
currentPnl: 0,
|
|
3204
|
-
fee: openFee,
|
|
3205
|
-
openFee,
|
|
3206
|
-
totalFee,
|
|
3207
|
-
lastSyncedAt: now()
|
|
3208
|
-
};
|
|
3209
|
-
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
3210
|
-
try {
|
|
3211
|
-
await Promise.all([
|
|
3212
|
-
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, existing.orderId), next, {
|
|
3213
|
-
expire: 0
|
|
3214
|
-
}),
|
|
3215
|
-
(0, import_redis2.setHashJsonField)(
|
|
3216
|
-
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
3217
|
-
existing.orderId,
|
|
3218
|
-
next,
|
|
3219
|
-
{ expire: 0 }
|
|
3220
|
-
)
|
|
3221
|
-
]);
|
|
3222
|
-
} catch (error) {
|
|
3223
|
-
import_logger3.logger.error(
|
|
3224
|
-
"runtime trade increase journal failed: %s %s",
|
|
3225
|
-
symbol,
|
|
3226
|
-
error?.message || String(error)
|
|
3227
|
-
);
|
|
3778
|
+
const parsed = parseAIResponse(response.content);
|
|
3779
|
+
const normalized = normalizeAnalysis(parsed);
|
|
3780
|
+
if (!options.signal) {
|
|
3781
|
+
return normalized;
|
|
3228
3782
|
}
|
|
3229
|
-
return
|
|
3783
|
+
return postProcessAiAnalysisByStrategy(
|
|
3784
|
+
options.signal,
|
|
3785
|
+
normalized,
|
|
3786
|
+
options.payload
|
|
3787
|
+
);
|
|
3230
3788
|
};
|
|
3231
|
-
var
|
|
3232
|
-
const
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
const
|
|
3237
|
-
|
|
3238
|
-
|
|
3789
|
+
var runAiPromptLocal = async (signal, options = {}) => {
|
|
3790
|
+
const payload = options.payload ?? buildAiPayload(signal);
|
|
3791
|
+
const gateContext = getDeterministicAiGateContext(payload);
|
|
3792
|
+
const signalDirection = getSignalDirection(signal);
|
|
3793
|
+
const deterministicQuality = getDeterministicQuality(gateContext);
|
|
3794
|
+
const approvalAllowedNow = typeof gateContext?.approvalAllowedNow === "boolean" ? gateContext.approvalAllowedNow : deterministicQuality >= 4;
|
|
3795
|
+
return postProcessLocalAiAnalysisByStrategy(
|
|
3796
|
+
signal,
|
|
3797
|
+
{
|
|
3798
|
+
direction: approvalAllowedNow ? signalDirection : null,
|
|
3799
|
+
quality: deterministicQuality,
|
|
3800
|
+
needRetest: !approvalAllowedNow,
|
|
3801
|
+
retestPrice: null,
|
|
3802
|
+
takeProfitPrice: approvalAllowedNow ? signal.prices?.takeProfitPrice ?? null : null,
|
|
3803
|
+
stopLossPrice: approvalAllowedNow ? signal.prices?.stopLossPrice ?? null : null
|
|
3804
|
+
},
|
|
3805
|
+
payload
|
|
3239
3806
|
);
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
const
|
|
3245
|
-
|
|
3246
|
-
|
|
3807
|
+
};
|
|
3808
|
+
var askAI = async (signal, options = {}) => {
|
|
3809
|
+
const { symbol } = signal;
|
|
3810
|
+
const payload = buildAiPayload(signal);
|
|
3811
|
+
const content = await runAiPrompt(
|
|
3812
|
+
{
|
|
3813
|
+
systemPrompt: buildAiSystemPrompt(signal),
|
|
3814
|
+
humanPrompt: buildAiHumanPrompt(signal, payload)
|
|
3815
|
+
},
|
|
3816
|
+
{
|
|
3817
|
+
...options,
|
|
3818
|
+
signal,
|
|
3819
|
+
payload
|
|
3820
|
+
}
|
|
3247
3821
|
);
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
import_redis2.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
|
|
3251
|
-
);
|
|
3252
|
-
return null;
|
|
3253
|
-
}
|
|
3254
|
-
return existing;
|
|
3822
|
+
await (0, import_redis3.setData)(import_redis3.redisKeys.analysis(symbol, signal.signalId), content);
|
|
3823
|
+
return content;
|
|
3255
3824
|
};
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
}
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
const
|
|
3285
|
-
const
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
}
|
|
3291
|
-
const
|
|
3292
|
-
...
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3825
|
+
|
|
3826
|
+
// src/strategyAdapters/ml.ts
|
|
3827
|
+
var defaultMlAdapter = {
|
|
3828
|
+
normalizeStrategyConfig: (strategyConfig) => strategyConfig
|
|
3829
|
+
};
|
|
3830
|
+
var getStrategyMlAdapter = (strategy, profileId) => {
|
|
3831
|
+
const strategyAdapter = getStrategyProfileMlAdapter(
|
|
3832
|
+
getStrategyManifest(strategy),
|
|
3833
|
+
profileId
|
|
3834
|
+
);
|
|
3835
|
+
if (!strategyAdapter) return defaultMlAdapter;
|
|
3836
|
+
return {
|
|
3837
|
+
...defaultMlAdapter,
|
|
3838
|
+
...strategyAdapter
|
|
3839
|
+
};
|
|
3840
|
+
};
|
|
3841
|
+
|
|
3842
|
+
// src/mlPayload.ts
|
|
3843
|
+
var normalizeStrategyConfig = (strategyConfig, strategyName, profileId) => {
|
|
3844
|
+
return getStrategyMlAdapter(
|
|
3845
|
+
strategyName,
|
|
3846
|
+
profileId
|
|
3847
|
+
).normalizeStrategyConfig?.(strategyConfig);
|
|
3848
|
+
};
|
|
3849
|
+
var buildMlPayload = (payload) => {
|
|
3850
|
+
const strategyName = payload.signal?.strategy ?? payload.context?.strategyName;
|
|
3851
|
+
const profileId = payload.signal?.policyProfileId;
|
|
3852
|
+
const mlAdapter = getStrategyMlAdapter(strategyName, profileId);
|
|
3853
|
+
const normalizedSignal = mlAdapter.normalizeSignal?.(payload.signal) ?? payload.signal;
|
|
3854
|
+
const nextSignal = {
|
|
3855
|
+
...normalizedSignal,
|
|
3856
|
+
indicators: {
|
|
3857
|
+
...normalizedSignal?.indicators ?? {}
|
|
3858
|
+
}
|
|
3859
|
+
};
|
|
3860
|
+
const nextContext = payload.context ? {
|
|
3861
|
+
...payload.context,
|
|
3862
|
+
strategyConfig: normalizeStrategyConfig(
|
|
3863
|
+
payload.context.strategyConfig,
|
|
3864
|
+
strategyName,
|
|
3865
|
+
profileId
|
|
3866
|
+
)
|
|
3867
|
+
} : void 0;
|
|
3868
|
+
return {
|
|
3869
|
+
signal: nextSignal,
|
|
3870
|
+
context: nextContext
|
|
3301
3871
|
};
|
|
3302
|
-
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
3303
|
-
const closeDayKey = (0, import_time.getRuntimeStorageDayKey)(next.exitTimestamp);
|
|
3304
|
-
try {
|
|
3305
|
-
await Promise.all([
|
|
3306
|
-
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, orderId), next, {
|
|
3307
|
-
expire: import_constants.TTL_1M
|
|
3308
|
-
}),
|
|
3309
|
-
(0, import_redis2.setHashJsonField)(
|
|
3310
|
-
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
3311
|
-
orderId,
|
|
3312
|
-
next,
|
|
3313
|
-
{ expire: import_constants.TTL_1M }
|
|
3314
|
-
),
|
|
3315
|
-
(0, import_redis2.setHashJsonField)(
|
|
3316
|
-
import_redis2.redisKeys.runtimeClosedTradeBucket(userName, closeDayKey),
|
|
3317
|
-
orderId,
|
|
3318
|
-
next,
|
|
3319
|
-
{ expire: import_constants.TTL_1M }
|
|
3320
|
-
),
|
|
3321
|
-
(0, import_redis2.delKey)(
|
|
3322
|
-
import_redis2.redisKeys.runtimeActiveTrade(
|
|
3323
|
-
userName,
|
|
3324
|
-
symbol,
|
|
3325
|
-
deploymentId ?? accountId
|
|
3326
|
-
)
|
|
3327
|
-
)
|
|
3328
|
-
]);
|
|
3329
|
-
} catch (error) {
|
|
3330
|
-
import_logger3.logger.error(
|
|
3331
|
-
"runtime trade close journal failed: %s %s",
|
|
3332
|
-
symbol,
|
|
3333
|
-
error?.message || String(error)
|
|
3334
|
-
);
|
|
3335
|
-
}
|
|
3336
|
-
return next;
|
|
3337
3872
|
};
|
|
3338
3873
|
|
|
3339
3874
|
// src/strategyHelpers/marketContextStages.ts
|
|
@@ -3345,7 +3880,7 @@ var import_logger4 = require("@tradejs/infra/logger");
|
|
|
3345
3880
|
var import_strategies = require("@tradejs/core/strategies");
|
|
3346
3881
|
|
|
3347
3882
|
// src/binanceBreadthUniverses.ts
|
|
3348
|
-
var
|
|
3883
|
+
var import_node_crypto3 = require("crypto");
|
|
3349
3884
|
|
|
3350
3885
|
// src/config/binanceBreadthUniverses.json
|
|
3351
3886
|
var binanceBreadthUniverses_default = {
|
|
@@ -3584,7 +4119,7 @@ var BINANCE_BREADTH_UNIVERSE_KEYS = [
|
|
|
3584
4119
|
"top50",
|
|
3585
4120
|
"top100"
|
|
3586
4121
|
];
|
|
3587
|
-
var fingerprint = (value, length) => (0,
|
|
4122
|
+
var fingerprint = (value, length) => (0, import_node_crypto3.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, length);
|
|
3588
4123
|
var normalizeSymbols = (symbols) => symbols.map((symbol) => symbol.trim().toUpperCase()).filter(Boolean);
|
|
3589
4124
|
var buildBinanceBreadthUniverseSnapshot = ({
|
|
3590
4125
|
rankedSymbols,
|
|
@@ -4608,7 +5143,7 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
4608
5143
|
};
|
|
4609
5144
|
|
|
4610
5145
|
// src/strategyHelpers/derivativesContext.ts
|
|
4611
|
-
var
|
|
5146
|
+
var import_indicators3 = require("@tradejs/core/indicators");
|
|
4612
5147
|
var import_data2 = require("@tradejs/core/data");
|
|
4613
5148
|
var import_strategies3 = require("@tradejs/core/strategies");
|
|
4614
5149
|
var import_constants2 = require("@tradejs/core/constants");
|
|
@@ -4642,7 +5177,7 @@ var parseLookbackMs = () => {
|
|
|
4642
5177
|
};
|
|
4643
5178
|
var withHourlyFallbackRows = (rowsByInterval) => ({
|
|
4644
5179
|
"15m": rowsByInterval["15m"] ?? [],
|
|
4645
|
-
"1h": (0,
|
|
5180
|
+
"1h": (0, import_indicators3.buildCoinalyzeHourlyRowsWithFallback)({
|
|
4646
5181
|
rows15m: rowsByInterval["15m"],
|
|
4647
5182
|
fallbackRows1h: rowsByInterval["1h"]
|
|
4648
5183
|
})
|
|
@@ -4770,7 +5305,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
4770
5305
|
const targetSymbol = normalizeSymbol(signal.symbol);
|
|
4771
5306
|
const lookbackMs = parseLookbackMs();
|
|
4772
5307
|
const decisionTimeMs = signal.timestamp + (0, import_data2.intervalToMs)(signal.interval);
|
|
4773
|
-
const derivativesEndMs = (0,
|
|
5308
|
+
const derivativesEndMs = (0, import_indicators3.getLastClosedDerivativesBarStartMs)(
|
|
4774
5309
|
decisionTimeMs,
|
|
4775
5310
|
"15m"
|
|
4776
5311
|
);
|
|
@@ -4785,7 +5320,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
4785
5320
|
});
|
|
4786
5321
|
return [
|
|
4787
5322
|
symbol,
|
|
4788
|
-
(0,
|
|
5323
|
+
(0, import_indicators3.buildDerivativesContext)({
|
|
4789
5324
|
symbol,
|
|
4790
5325
|
direction: signal.direction,
|
|
4791
5326
|
timestamp: derivativesEndMs,
|
|
@@ -4812,7 +5347,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
4812
5347
|
lookbackMs,
|
|
4813
5348
|
...params.abortSignal ? { signal: params.abortSignal } : {}
|
|
4814
5349
|
});
|
|
4815
|
-
const context = (0,
|
|
5350
|
+
const context = (0, import_indicators3.buildDerivativesContext)({
|
|
4816
5351
|
symbol: targetSymbol,
|
|
4817
5352
|
direction: signal.direction,
|
|
4818
5353
|
timestamp: derivativesEndMs,
|
|
@@ -4860,7 +5395,7 @@ var import_hyperliquidWhales2 = require("@tradejs/infra/timescale/hyperliquidWha
|
|
|
4860
5395
|
var import_logger7 = require("@tradejs/infra/logger");
|
|
4861
5396
|
|
|
4862
5397
|
// src/hyperliquidWhaleUniverse.ts
|
|
4863
|
-
var
|
|
5398
|
+
var import_node_crypto4 = require("crypto");
|
|
4864
5399
|
|
|
4865
5400
|
// src/config/hyperliquidPerpUniverse.json
|
|
4866
5401
|
var hyperliquidPerpUniverse_default = {
|
|
@@ -5032,7 +5567,7 @@ var hyperliquidWhales_default = {
|
|
|
5032
5567
|
};
|
|
5033
5568
|
|
|
5034
5569
|
// src/hyperliquidWhaleUniverse.ts
|
|
5035
|
-
var fingerprint2 = (value) => (0,
|
|
5570
|
+
var fingerprint2 = (value) => (0, import_node_crypto4.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
|
|
5036
5571
|
var validateUnique = (values, label) => {
|
|
5037
5572
|
if (new Set(values).size !== values.length) {
|
|
5038
5573
|
throw new Error(`Hyperliquid ${label} snapshot contains duplicates`);
|
|
@@ -5519,6 +6054,7 @@ var enrichSignalWithMarketContextStages = async ({
|
|
|
5519
6054
|
})
|
|
5520
6055
|
});
|
|
5521
6056
|
}
|
|
6057
|
+
await enrichSignalWithTickerProfile(signal);
|
|
5522
6058
|
const results = [];
|
|
5523
6059
|
for (const stage of stages) {
|
|
5524
6060
|
if (abortSignal?.aborted) break;
|
|
@@ -5566,12 +6102,12 @@ var findReplayAiAnalysis = ({
|
|
|
5566
6102
|
direction,
|
|
5567
6103
|
ai
|
|
5568
6104
|
}) => {
|
|
5569
|
-
const
|
|
5570
|
-
if (!Array.isArray(
|
|
6105
|
+
const snapshots2 = ai?.replayAnalyses;
|
|
6106
|
+
if (!Array.isArray(snapshots2) || !snapshots2.length) {
|
|
5571
6107
|
return void 0;
|
|
5572
6108
|
}
|
|
5573
6109
|
let best = null;
|
|
5574
|
-
for (const snapshot2 of
|
|
6110
|
+
for (const snapshot2 of snapshots2) {
|
|
5575
6111
|
if (snapshot2.symbol !== signal.symbol || snapshot2.direction !== direction || snapshot2.strategy && snapshot2.strategy !== signal.strategy) {
|
|
5576
6112
|
continue;
|
|
5577
6113
|
}
|
|
@@ -5731,7 +6267,9 @@ var executeEntryOrder = async ({
|
|
|
5731
6267
|
beforePlaceOrder,
|
|
5732
6268
|
recordRuntimeTrade = true,
|
|
5733
6269
|
leverage,
|
|
5734
|
-
executionLimits
|
|
6270
|
+
executionLimits,
|
|
6271
|
+
markPortfolioSubmitted,
|
|
6272
|
+
rejectPortfolioSubmission
|
|
5735
6273
|
}) => {
|
|
5736
6274
|
await beforePlaceOrder?.();
|
|
5737
6275
|
const previousPosition = positionIntent === "increase" ? await connector.getPosition(symbol) : null;
|
|
@@ -5804,7 +6342,13 @@ var executeEntryOrder = async ({
|
|
|
5804
6342
|
...typeof leverage === "number" && Number.isFinite(leverage) ? { leverage } : {},
|
|
5805
6343
|
orderId,
|
|
5806
6344
|
signal,
|
|
5807
|
-
...
|
|
6345
|
+
...rejectPortfolioSubmission ? { onRejected: rejectPortfolioSubmission } : {},
|
|
6346
|
+
...beforeSubmit || markPortfolioSubmitted ? {
|
|
6347
|
+
beforeSubmit: async (context) => {
|
|
6348
|
+
await beforeSubmit?.(context);
|
|
6349
|
+
await markPortfolioSubmitted?.(context.qty);
|
|
6350
|
+
}
|
|
6351
|
+
} : {}
|
|
5808
6352
|
});
|
|
5809
6353
|
const orderAckTime = Date.now();
|
|
5810
6354
|
const placedQty = typeof signal.orderQty === "number" && Number.isFinite(signal.orderQty) && signal.orderQty > 0 ? signal.orderQty : qty;
|
|
@@ -6792,7 +7336,7 @@ var handleProtectDecision = async ({
|
|
|
6792
7336
|
}
|
|
6793
7337
|
return decision.code;
|
|
6794
7338
|
};
|
|
6795
|
-
var
|
|
7339
|
+
var executeAdmittedEntryDecision = async ({
|
|
6796
7340
|
connector,
|
|
6797
7341
|
symbol,
|
|
6798
7342
|
decision,
|
|
@@ -6806,7 +7350,9 @@ var executeEntryDecision = async ({
|
|
|
6806
7350
|
ai,
|
|
6807
7351
|
recordRuntimeJournal,
|
|
6808
7352
|
invokeStageHooks,
|
|
6809
|
-
notifyRuntimeError
|
|
7353
|
+
notifyRuntimeError,
|
|
7354
|
+
markPortfolioSubmitted,
|
|
7355
|
+
rejectPortfolioSubmission
|
|
6810
7356
|
}) => {
|
|
6811
7357
|
const signal = decision.signal;
|
|
6812
7358
|
const beforePlaceOrder = async () => {
|
|
@@ -6853,6 +7399,8 @@ var executeEntryDecision = async ({
|
|
|
6853
7399
|
...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {},
|
|
6854
7400
|
signal,
|
|
6855
7401
|
beforePlaceOrder,
|
|
7402
|
+
markPortfolioSubmitted,
|
|
7403
|
+
rejectPortfolioSubmission,
|
|
6856
7404
|
recordRuntimeTrade: recordRuntimeJournal,
|
|
6857
7405
|
executionLimits: resolveEntryExecutionLimits(
|
|
6858
7406
|
hookCtx.strategyConfig,
|
|
@@ -6993,6 +7541,37 @@ var executeEntryDecision = async ({
|
|
|
6993
7541
|
}
|
|
6994
7542
|
return signal ?? decision.code;
|
|
6995
7543
|
};
|
|
7544
|
+
async function executeEntryDecision(params) {
|
|
7545
|
+
if (!params.portfolio) return executeAdmittedEntryDecision(params);
|
|
7546
|
+
const { decision, portfolio } = params;
|
|
7547
|
+
const signal = decision.signal;
|
|
7548
|
+
if (!signal || decision.orderPlan.positionIntent === "increase")
|
|
7549
|
+
return "PORTFOLIO_REQUIRES_NEW_SIGNAL_POSITION";
|
|
7550
|
+
signal.orderId ??= createRuntimeOrderId(signal.strategy);
|
|
7551
|
+
const result = await portfolio.submit(
|
|
7552
|
+
params.portfolioTime ?? decision.entryContext.timestamp,
|
|
7553
|
+
{
|
|
7554
|
+
id: signal.orderId,
|
|
7555
|
+
signalId: signal.signalId,
|
|
7556
|
+
strategy: signal.strategy,
|
|
7557
|
+
symbol: params.symbol,
|
|
7558
|
+
direction: decision.entryContext.direction,
|
|
7559
|
+
qty: decision.orderPlan.qty,
|
|
7560
|
+
timestamp: decision.entryContext.timestamp
|
|
7561
|
+
},
|
|
7562
|
+
(markPortfolioSubmitted, rejectPortfolioSubmission) => executeAdmittedEntryDecision({
|
|
7563
|
+
...params,
|
|
7564
|
+
markPortfolioSubmitted,
|
|
7565
|
+
rejectPortfolioSubmission
|
|
7566
|
+
})
|
|
7567
|
+
);
|
|
7568
|
+
if (result.reason) {
|
|
7569
|
+
signal.orderStatus = "failed";
|
|
7570
|
+
signal.orderFailureReason = result.reason;
|
|
7571
|
+
return signal;
|
|
7572
|
+
}
|
|
7573
|
+
return result.value;
|
|
7574
|
+
}
|
|
6996
7575
|
|
|
6997
7576
|
// src/strategyRuntime.ts
|
|
6998
7577
|
var cloneWithPropertyDescriptors = (value) => Object.create(
|
|
@@ -7020,6 +7599,7 @@ var createStrategyRuntime = ({
|
|
|
7020
7599
|
};
|
|
7021
7600
|
const creator = async ({
|
|
7022
7601
|
userName,
|
|
7602
|
+
connectorName,
|
|
7023
7603
|
config: baseConfig,
|
|
7024
7604
|
symbol,
|
|
7025
7605
|
universe: requestedUniverse,
|
|
@@ -7058,6 +7638,17 @@ var createStrategyRuntime = ({
|
|
|
7058
7638
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
7059
7639
|
const projectHooks = projectConfig.hooks;
|
|
7060
7640
|
const env = String(config.ENV ?? "BACKTEST");
|
|
7641
|
+
const portfolio = getPortfolioRuntime({
|
|
7642
|
+
connectorName,
|
|
7643
|
+
value: baseConfig?.PORTFOLIO_POLICY ?? config.PORTFOLIO_POLICY,
|
|
7644
|
+
strategy: strategyName,
|
|
7645
|
+
connector,
|
|
7646
|
+
env,
|
|
7647
|
+
userName,
|
|
7648
|
+
accountId,
|
|
7649
|
+
deploymentId
|
|
7650
|
+
});
|
|
7651
|
+
const portfolioContext = portfolio ? (now2) => isRuntimeOrderExecutionEnabled({ config, env, connector }) ? portfolio.tick(now2) : portfolio.context(now2) : void 0;
|
|
7061
7652
|
const researchPolicyValue = baseConfig?.RESEARCH_ENTRY_POLICY ?? config.RESEARCH_ENTRY_POLICY;
|
|
7062
7653
|
const researchEntryPolicy = researchPolicyValue == null ? void 0 : parseResearchEntryPolicy(researchPolicyValue);
|
|
7063
7654
|
if (researchEntryPolicy && (env !== "BACKTEST" || Reflect.get(connector, "__tradejsTestConnector") !== true))
|
|
@@ -7356,6 +7947,20 @@ var createStrategyRuntime = ({
|
|
|
7356
7947
|
interval: decisionInterval
|
|
7357
7948
|
}) => {
|
|
7358
7949
|
if (!baseContext) return void 0;
|
|
7950
|
+
const profileSource = strategyManifest?.contextRequirements?.tickerProfiles;
|
|
7951
|
+
if (profileSource) {
|
|
7952
|
+
const profile = await loadTickerProfileContext({
|
|
7953
|
+
...profileSource,
|
|
7954
|
+
projectRoot,
|
|
7955
|
+
symbol: decisionSymbol,
|
|
7956
|
+
timestamp: candle.timestamp
|
|
7957
|
+
});
|
|
7958
|
+
if (!profile)
|
|
7959
|
+
throw new Error(
|
|
7960
|
+
"Required ticker profile is unavailable at decision time"
|
|
7961
|
+
);
|
|
7962
|
+
baseContext = withTickerProfile(baseContext, profile);
|
|
7963
|
+
}
|
|
7359
7964
|
if (!coreContextRequirements.has("hyperliquidWhales")) {
|
|
7360
7965
|
return baseContext;
|
|
7361
7966
|
}
|
|
@@ -7523,6 +8128,8 @@ var createStrategyRuntime = ({
|
|
|
7523
8128
|
runtime: pending2.runtime
|
|
7524
8129
|
});
|
|
7525
8130
|
return executeEntryDecision({
|
|
8131
|
+
portfolio,
|
|
8132
|
+
portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
|
|
7526
8133
|
connector,
|
|
7527
8134
|
symbol,
|
|
7528
8135
|
decision: pending2.decision,
|
|
@@ -7540,6 +8147,10 @@ var createStrategyRuntime = ({
|
|
|
7540
8147
|
});
|
|
7541
8148
|
};
|
|
7542
8149
|
const runWithDecisionOverride = async (candle, btcCandle, options = {}) => {
|
|
8150
|
+
if (portfolioContext)
|
|
8151
|
+
await portfolioContext(
|
|
8152
|
+
env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now()
|
|
8153
|
+
);
|
|
7543
8154
|
appendCurrentMarketData(candle, btcCandle, options.ethCandle);
|
|
7544
8155
|
const ethCandle = resolveEthCandle(candle, options.ethCandle);
|
|
7545
8156
|
indicatorsState.setCurrentBar(candle, btcCandle, ethCandle);
|
|
@@ -7710,6 +8321,13 @@ var createStrategyRuntime = ({
|
|
|
7710
8321
|
});
|
|
7711
8322
|
const signal = decision.signal;
|
|
7712
8323
|
if (signal) {
|
|
8324
|
+
if (portfolio)
|
|
8325
|
+
signal.additionalIndicators = {
|
|
8326
|
+
...signal.additionalIndicators,
|
|
8327
|
+
portfolioContext: await portfolioContext(
|
|
8328
|
+
env === "BACKTEST" || env === "PARITY" ? decision.entryContext.timestamp : Date.now()
|
|
8329
|
+
)
|
|
8330
|
+
};
|
|
7713
8331
|
if (runtimeLineage) signal.runtimeLineage = runtimeLineage;
|
|
7714
8332
|
if (universe) signal.universe = universe;
|
|
7715
8333
|
if (assetClass) signal.assetClass = assetClass;
|
|
@@ -7949,6 +8567,8 @@ var createStrategyRuntime = ({
|
|
|
7949
8567
|
});
|
|
7950
8568
|
}
|
|
7951
8569
|
return executeEntryDecision({
|
|
8570
|
+
portfolio,
|
|
8571
|
+
portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
|
|
7952
8572
|
connector,
|
|
7953
8573
|
symbol,
|
|
7954
8574
|
decision,
|
|
@@ -8516,7 +9136,7 @@ var DEFAULT_CONNECTOR_NAME = BUILTIN_CONNECTOR_NAMES.ByBit;
|
|
|
8516
9136
|
|
|
8517
9137
|
// src/runtimeStrategies.ts
|
|
8518
9138
|
var import_jev8 = require("@tradejs/core/jev");
|
|
8519
|
-
var
|
|
9139
|
+
var import_node_crypto5 = require("crypto");
|
|
8520
9140
|
var import_promises2 = require("fs/promises");
|
|
8521
9141
|
var import_node_path2 = __toESM(require("path"));
|
|
8522
9142
|
var import_runtimeControls = require("@tradejs/infra/runtimeControls");
|
|
@@ -8575,7 +9195,7 @@ var normalizeForCanonicalJson = (value) => {
|
|
|
8575
9195
|
}
|
|
8576
9196
|
return value;
|
|
8577
9197
|
};
|
|
8578
|
-
var revision = (prefix, value) => `${prefix}:${(0,
|
|
9198
|
+
var revision = (prefix, value) => `${prefix}:${(0, import_node_crypto5.createHash)("sha256").update(JSON.stringify(normalizeForCanonicalJson(value))).digest("hex").slice(0, 16)}`;
|
|
8579
9199
|
var computeStrategyRevision = ({
|
|
8580
9200
|
strategyName,
|
|
8581
9201
|
strategyPackage,
|
|
@@ -9051,13 +9671,13 @@ var loadResolvedRuntimeStrategies = async ({
|
|
|
9051
9671
|
};
|
|
9052
9672
|
|
|
9053
9673
|
// src/mcpJobs.ts
|
|
9054
|
-
var
|
|
9674
|
+
var import_node_crypto6 = require("crypto");
|
|
9055
9675
|
var import_promises3 = require("fs/promises");
|
|
9056
9676
|
var import_node_path3 = __toESM(require("path"));
|
|
9057
9677
|
var import_node_util = require("util");
|
|
9058
|
-
var
|
|
9678
|
+
var import_redis4 = require("@tradejs/infra/redis");
|
|
9059
9679
|
var MCP_JOB_TTL = 7 * 86400;
|
|
9060
|
-
var hash = (value) => (0,
|
|
9680
|
+
var hash = (value) => (0, import_node_crypto6.createHash)("sha256").update(value).digest("hex");
|
|
9061
9681
|
var mcpJobStorageId = (userName, id) => `jobs:${hash(userName)}:${id}`;
|
|
9062
9682
|
var mcpJobDirectory = (job) => import_node_path3.default.join(
|
|
9063
9683
|
String(process.env.PROJECT_CWD || process.cwd()),
|
|
@@ -9087,17 +9707,17 @@ var enqueueMcpJob = async (principal, kind, request, idempotencyKey) => {
|
|
|
9087
9707
|
`${principal.userName}:${principal.clientId}:${idempotencyKey}`
|
|
9088
9708
|
);
|
|
9089
9709
|
const storageId = mcpJobStorageId(principal.userName, id);
|
|
9090
|
-
const existing = await
|
|
9710
|
+
const existing = await import_redis4.mcpStorage.get(storageId);
|
|
9091
9711
|
if (existing) {
|
|
9092
9712
|
if (existing.kind !== kind || existing.request.requestFingerprint !== hash(JSON.stringify(request)))
|
|
9093
9713
|
throw new Error("Idempotency key was used for another request");
|
|
9094
9714
|
return existing;
|
|
9095
9715
|
}
|
|
9096
|
-
const worker = await
|
|
9716
|
+
const worker = await import_redis4.mcpStorage.get(
|
|
9097
9717
|
"worker:heartbeat"
|
|
9098
9718
|
);
|
|
9099
9719
|
if (!worker) throw new Error("MCP worker is unavailable; no job was queued");
|
|
9100
|
-
if (!await
|
|
9720
|
+
if (!await import_redis4.mcpStorage.rateLimit(`jobs:${hash(principal.userName)}`, 10, 3600))
|
|
9101
9721
|
throw new Error("Hourly job limit reached");
|
|
9102
9722
|
const job = {
|
|
9103
9723
|
id,
|
|
@@ -9119,8 +9739,8 @@ var enqueueMcpJob = async (principal, kind, request, idempotencyKey) => {
|
|
|
9119
9739
|
const configId = String(request.configId || "");
|
|
9120
9740
|
if (!/^[A-Za-z][A-Za-z0-9]*:[A-Za-z0-9_-]+$/.test(configId))
|
|
9121
9741
|
throw new Error("Select a named Strategy:config");
|
|
9122
|
-
const grid = await (0,
|
|
9123
|
-
|
|
9742
|
+
const grid = await (0, import_redis4.getDataStrict)(
|
|
9743
|
+
import_redis4.redisKeys.backtestConfig(principal.userName, configId)
|
|
9124
9744
|
);
|
|
9125
9745
|
if (!grid || typeof grid !== "object" || Array.isArray(grid))
|
|
9126
9746
|
throw new Error("Backtest config not found");
|
|
@@ -9197,7 +9817,7 @@ var enqueueMcpJob = async (principal, kind, request, idempotencyKey) => {
|
|
|
9197
9817
|
"Unsupported diagnostic kind; use verified runtime feedback for deployment-bound parity"
|
|
9198
9818
|
);
|
|
9199
9819
|
}
|
|
9200
|
-
if (!await
|
|
9820
|
+
if (!await import_redis4.mcpStorage.put(storageId, job, MCP_JOB_TTL, true)) {
|
|
9201
9821
|
const winner = await readMcpJob(principal.userName, id);
|
|
9202
9822
|
if (winner.kind !== kind || winner.request.requestFingerprint !== job.request.requestFingerprint)
|
|
9203
9823
|
throw new Error("Idempotency key was used for another request");
|
|
@@ -9207,7 +9827,7 @@ var enqueueMcpJob = async (principal, kind, request, idempotencyKey) => {
|
|
|
9207
9827
|
};
|
|
9208
9828
|
var readMcpJob = async (userName, id) => {
|
|
9209
9829
|
if (!/^[a-f0-9]{64}$/.test(id)) throw new Error("Invalid job id");
|
|
9210
|
-
const job = await
|
|
9830
|
+
const job = await import_redis4.mcpStorage.get(mcpJobStorageId(userName, id));
|
|
9211
9831
|
if (!job || job.userName !== userName) throw new Error("Job not found");
|
|
9212
9832
|
return job;
|
|
9213
9833
|
};
|
|
@@ -9283,7 +9903,7 @@ var cancelMcpJob = async (principal, id) => {
|
|
|
9283
9903
|
updatedAt: Date.now(),
|
|
9284
9904
|
...job.status === "queued" ? { status: "cancelled", finishedAt: Date.now() } : {}
|
|
9285
9905
|
};
|
|
9286
|
-
if (await
|
|
9906
|
+
if (await import_redis4.mcpStorage.compare(
|
|
9287
9907
|
mcpJobStorageId(job.userName, job.id),
|
|
9288
9908
|
job,
|
|
9289
9909
|
next,
|
|
@@ -9309,7 +9929,7 @@ var publicMcpJob = (job) => {
|
|
|
9309
9929
|
// src/mcp.ts
|
|
9310
9930
|
var mcpProjectRoot = () => String(process.env.PROJECT_CWD || process.cwd());
|
|
9311
9931
|
var isMcpRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
9312
|
-
var mcpId = (value) => (0,
|
|
9932
|
+
var mcpId = (value) => (0, import_node_crypto7.createHash)("sha256").update(value).digest("hex");
|
|
9313
9933
|
var redactMcpData = (value) => {
|
|
9314
9934
|
if (Array.isArray(value)) return value.map(redactMcpData);
|
|
9315
9935
|
if (!isMcpRecord(value)) return value;
|
|
@@ -9372,7 +9992,7 @@ var readMcpChart = async (userName, request) => {
|
|
|
9372
9992
|
});
|
|
9373
9993
|
const visibleCandles = candles.filter((candle) => candle.timestamp >= visibleStart).slice(-request.bars);
|
|
9374
9994
|
await ensureIndicatorPluginsLoaded(mcpProjectRoot());
|
|
9375
|
-
const allIndicators = (0,
|
|
9995
|
+
const allIndicators = (0, import_indicators4.createIndicators)(
|
|
9376
9996
|
candles,
|
|
9377
9997
|
btc.filter((candle) => candle.timestamp < end),
|
|
9378
9998
|
{
|
|
@@ -9461,7 +10081,7 @@ var McpCursorError = class extends Error {
|
|
|
9461
10081
|
var RUNTIME_CURSOR_TTL = 86400;
|
|
9462
10082
|
var readMcpRuntimePage = async (userName, kind, request) => {
|
|
9463
10083
|
const deployment = await requireMcpDeployment(userName, request.deploymentId);
|
|
9464
|
-
const prefix = kind === "signals" ?
|
|
10084
|
+
const prefix = kind === "signals" ? import_redis5.redisKeys.runtimeSignalBuckets(userName) : kind === "evaluations" ? import_redis5.redisKeys.runtimeSignalEvaluationBuckets(userName) : import_redis5.redisKeys.runtimeTrades(userName);
|
|
9465
10085
|
const limit = request.limit ?? 50;
|
|
9466
10086
|
if (!Number.isInteger(limit) || limit < 1 || limit > 50)
|
|
9467
10087
|
throw new Error("Select a page limit of 1\u201350");
|
|
@@ -9486,7 +10106,7 @@ var readMcpRuntimePage = async (userName, kind, request) => {
|
|
|
9486
10106
|
scanned: false
|
|
9487
10107
|
};
|
|
9488
10108
|
if (request.cursor !== "0") {
|
|
9489
|
-
const saved = await
|
|
10109
|
+
const saved = await import_redis5.mcpStorage.get(
|
|
9490
10110
|
`cursor:${request.cursor}`
|
|
9491
10111
|
);
|
|
9492
10112
|
if (!saved) throw new McpCursorError("CURSOR_EXPIRED");
|
|
@@ -9501,7 +10121,7 @@ var readMcpRuntimePage = async (userName, kind, request) => {
|
|
|
9501
10121
|
if (!state.values.length) {
|
|
9502
10122
|
if (!state.keys.length) {
|
|
9503
10123
|
if (state.scanned && state.scan === "0") break;
|
|
9504
|
-
const page = await (0,
|
|
10124
|
+
const page = await (0, import_redis5.scanDataKeys)(prefix, state.scan);
|
|
9505
10125
|
storageReads++;
|
|
9506
10126
|
state.scan = page.cursor;
|
|
9507
10127
|
state.scanned = true;
|
|
@@ -9518,9 +10138,9 @@ var readMcpRuntimePage = async (userName, kind, request) => {
|
|
|
9518
10138
|
storageReads++;
|
|
9519
10139
|
if (kind === "orders") {
|
|
9520
10140
|
state.keys.shift();
|
|
9521
|
-
state.values = [await (0,
|
|
10141
|
+
state.values = [await (0, import_redis5.getDataStrict)(key)];
|
|
9522
10142
|
} else {
|
|
9523
|
-
const page = await (0,
|
|
10143
|
+
const page = await (0, import_redis5.scanHashJson)(key, state.hash);
|
|
9524
10144
|
state.values = page.values;
|
|
9525
10145
|
state.hash = page.cursor;
|
|
9526
10146
|
if (page.cursor === "0") state.keys.shift();
|
|
@@ -9530,8 +10150,8 @@ var readMcpRuntimePage = async (userName, kind, request) => {
|
|
|
9530
10150
|
let row = state.values.shift();
|
|
9531
10151
|
inspected++;
|
|
9532
10152
|
if (kind === "signals" && isMcpRecord(row) && typeof row.symbol === "string" && typeof row.signalId === "string")
|
|
9533
|
-
row = await (0,
|
|
9534
|
-
|
|
10153
|
+
row = await (0, import_redis5.getDataStrict)(
|
|
10154
|
+
import_redis5.redisKeys.storeSignal(row.symbol, row.signalId)
|
|
9535
10155
|
);
|
|
9536
10156
|
if (!isMcpRecord(row) || row.deploymentId !== request.deploymentId || row.accountId !== deployment.accountId || row.userName !== void 0 && row.userName !== userName || request.strategy && row.strategy !== request.strategy || request.symbol && row.symbol !== request.symbol)
|
|
9537
10157
|
continue;
|
|
@@ -9561,7 +10181,7 @@ var readMcpRuntimePage = async (userName, kind, request) => {
|
|
|
9561
10181
|
let next = "0";
|
|
9562
10182
|
if (state.values.length || state.keys.length || state.scan !== "0") {
|
|
9563
10183
|
next = mcpId(JSON.stringify(state));
|
|
9564
|
-
if (!await
|
|
10184
|
+
if (!await import_redis5.mcpStorage.put(`cursor:${next}`, state, RUNTIME_CURSOR_TTL))
|
|
9565
10185
|
throw new Error("MCP cursor persistence failed");
|
|
9566
10186
|
}
|
|
9567
10187
|
return {
|
|
@@ -9587,8 +10207,8 @@ var readMcpRuntimePage = async (userName, kind, request) => {
|
|
|
9587
10207
|
};
|
|
9588
10208
|
var readMcpSignal = async (userName, signalId, deploymentId, timestamp, strategy) => {
|
|
9589
10209
|
const deployment = await requireMcpDeployment(userName, deploymentId);
|
|
9590
|
-
const ref = await (0,
|
|
9591
|
-
|
|
10210
|
+
const ref = await (0, import_redis5.getHashJsonField)(
|
|
10211
|
+
import_redis5.redisKeys.runtimeSignalBucket(
|
|
9592
10212
|
userName,
|
|
9593
10213
|
(0, import_time3.getRuntimeStorageDayKey)(timestamp),
|
|
9594
10214
|
strategy
|
|
@@ -9598,8 +10218,8 @@ var readMcpSignal = async (userName, signalId, deploymentId, timestamp, strategy
|
|
|
9598
10218
|
);
|
|
9599
10219
|
if (!isMcpRecord(ref) || typeof ref.symbol !== "string")
|
|
9600
10220
|
throw new Error("Signal not found");
|
|
9601
|
-
const signal = await (0,
|
|
9602
|
-
|
|
10221
|
+
const signal = await (0, import_redis5.getDataStrict)(
|
|
10222
|
+
import_redis5.redisKeys.storeSignal(ref.symbol, signalId)
|
|
9603
10223
|
);
|
|
9604
10224
|
if (!isMcpRecord(signal) || signal.deploymentId !== deploymentId || signal.accountId !== deployment.accountId || signal.userName !== void 0 && signal.userName !== userName)
|
|
9605
10225
|
throw new Error("Signal not found");
|
|
@@ -9607,19 +10227,19 @@ var readMcpSignal = async (userName, signalId, deploymentId, timestamp, strategy
|
|
|
9607
10227
|
};
|
|
9608
10228
|
var readMcpEvaluationStats = async (userName, deploymentId, cursor) => {
|
|
9609
10229
|
await requireMcpDeployment(userName, deploymentId);
|
|
9610
|
-
const page = await (0,
|
|
9611
|
-
|
|
10230
|
+
const page = await (0, import_redis5.scanDataKeys)(
|
|
10231
|
+
import_redis5.redisKeys.runtimeSignalEvaluationStatsBuckets(userName),
|
|
9612
10232
|
cursor
|
|
9613
10233
|
);
|
|
9614
10234
|
const buckets = [];
|
|
9615
10235
|
for (const key of page.keys) {
|
|
9616
|
-
const parts = key.slice(
|
|
10236
|
+
const parts = key.slice(import_redis5.redisKeys.runtimeSignalEvaluationStatsBuckets(userName).length).split(":");
|
|
9617
10237
|
if (parts.slice(1, -1).join(":") !== deploymentId) continue;
|
|
9618
10238
|
buckets.push({
|
|
9619
10239
|
bucket: key.slice(
|
|
9620
|
-
|
|
10240
|
+
import_redis5.redisKeys.runtimeSignalEvaluationStatsBuckets(userName).length
|
|
9621
10241
|
),
|
|
9622
|
-
counters: await (0,
|
|
10242
|
+
counters: await (0, import_redis5.getHashData)(key)
|
|
9623
10243
|
});
|
|
9624
10244
|
}
|
|
9625
10245
|
return {
|
|
@@ -9633,10 +10253,10 @@ var readMcpEvaluationStats = async (userName, deploymentId, cursor) => {
|
|
|
9633
10253
|
};
|
|
9634
10254
|
var readMcpBacktestPage = async (userName, kind, cursor) => {
|
|
9635
10255
|
const prefix = kind === "configs" ? `users:${userName}:backtests:configs:` : `users:${userName}:backtests:results:`;
|
|
9636
|
-
const page = await (0,
|
|
10256
|
+
const page = await (0, import_redis5.scanDataKeys)(prefix, cursor);
|
|
9637
10257
|
const items = [];
|
|
9638
10258
|
for (const key of page.keys) {
|
|
9639
|
-
const value = await (0,
|
|
10259
|
+
const value = await (0, import_redis5.getDataStrict)(key);
|
|
9640
10260
|
items.push({
|
|
9641
10261
|
id: key.slice(prefix.length),
|
|
9642
10262
|
value: kind === "results" && isMcpRecord(value) ? {
|
|
@@ -9660,7 +10280,7 @@ var readMcpBacktestPage = async (userName, kind, cursor) => {
|
|
|
9660
10280
|
var readMcpBacktestResult = async (userName, id, offset = 0, limit = 20) => {
|
|
9661
10281
|
if (id.includes("*") || id.includes("?") || id.includes("["))
|
|
9662
10282
|
throw new Error("Invalid result identifier");
|
|
9663
|
-
const result = await (0,
|
|
10283
|
+
const result = await (0, import_redis5.getDataStrict)(
|
|
9664
10284
|
`users:${userName}:backtests:results:${id}`
|
|
9665
10285
|
);
|
|
9666
10286
|
if (!result) throw new Error("Result not found");
|