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