@tradejs/node 3.1.29-beta.289 → 3.1.29-beta.291
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai.js +109 -48
- package/dist/ai.mjs +1 -1
- package/dist/backtest.js +2504 -1883
- package/dist/backtest.mjs +3 -2
- package/dist/{chunk-KYGJBMKE.mjs → chunk-HRON2EAI.mjs} +432 -40
- package/dist/{chunk-UL5Z46JL.mjs → chunk-PYJWXAYR.mjs} +97 -30
- package/dist/chunk-UUNEWAT4.mjs +193 -0
- package/dist/{chunk-BO42QWAB.mjs → chunk-W6PPAADK.mjs} +1 -1
- package/dist/cli.js +837 -775
- package/dist/cli.mjs +4 -3
- package/dist/diagnostics.js +2532 -1911
- package/dist/diagnostics.mjs +4 -3
- package/dist/mcp.d.mts +4 -2
- package/dist/mcp.d.ts +4 -2
- package/dist/mcp.js +2614 -1971
- package/dist/mcp.mjs +27 -5
- package/dist/portfolio.d.mts +28 -0
- package/dist/portfolio.d.ts +28 -0
- package/dist/portfolio.js +210 -0
- package/dist/portfolio.mjs +9 -0
- package/dist/registry.js +2091 -1470
- package/dist/registry.mjs +3 -2
- package/dist/runtimeDashboard.js +2255 -1632
- package/dist/runtimeDashboard.mjs +4 -3
- package/dist/runtimeStrategies.js +2417 -1796
- package/dist/runtimeStrategies.mjs +4 -3
- package/dist/strategies.d.mts +3 -1
- package/dist/strategies.d.ts +3 -1
- package/dist/strategies.js +2106 -1485
- package/dist/strategies.mjs +3 -2
- package/package.json +10 -5
|
@@ -789,1668 +789,2399 @@ __export(runtimeStrategies_exports, {
|
|
|
789
789
|
});
|
|
790
790
|
module.exports = __toCommonJS(runtimeStrategies_exports);
|
|
791
791
|
var import_jev8 = require("@tradejs/core/jev");
|
|
792
|
-
var
|
|
792
|
+
var import_node_crypto5 = require("crypto");
|
|
793
793
|
var import_promises2 = require("fs/promises");
|
|
794
794
|
var import_node_path2 = __toESM(require("path"));
|
|
795
795
|
var import_runtimeControls = require("@tradejs/infra/runtimeControls");
|
|
796
796
|
var import_tradingAccounts = require("@tradejs/infra/tradingAccounts");
|
|
797
797
|
|
|
798
|
-
// src/
|
|
799
|
-
var
|
|
798
|
+
// src/strategy/portfolioRuntime.ts
|
|
799
|
+
var import_node_crypto2 = require("crypto");
|
|
800
|
+
var import_portfolio2 = require("@tradejs/core/portfolio");
|
|
801
|
+
var import_redis2 = require("@tradejs/infra/redis");
|
|
800
802
|
|
|
801
|
-
// src/
|
|
802
|
-
var
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
const questions = questionsForJevInput(input);
|
|
833
|
-
const questionsHash = (0, import_jev5.jevHash)(questions);
|
|
834
|
-
const base = {
|
|
835
|
-
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
836
|
-
schema: "tradejs-signal-assessment/v5",
|
|
837
|
-
source: config.source,
|
|
838
|
-
mode: config.mode,
|
|
839
|
-
inputHash,
|
|
840
|
-
questionsHash,
|
|
841
|
-
geometryStatus: input.geometryStatus,
|
|
842
|
-
levelsValid: typeof input.features["signal.validLevels"] === "boolean" ? input.features["signal.validLevels"] : null
|
|
843
|
-
};
|
|
844
|
-
if (!input.questions.length)
|
|
845
|
-
return {
|
|
846
|
-
assessment: {
|
|
847
|
-
...base,
|
|
848
|
-
status: "unavailable",
|
|
849
|
-
model: "",
|
|
850
|
-
scores: emptyScores(),
|
|
851
|
-
confidence: emptyScores()
|
|
852
|
-
}
|
|
853
|
-
};
|
|
854
|
-
if (config.source === "local") {
|
|
855
|
-
const contents = await import_promises.default.readFile(
|
|
856
|
-
import_node_path.default.resolve(projectRoot, config.modelFile),
|
|
857
|
-
"utf8"
|
|
858
|
-
);
|
|
859
|
-
if ((0, import_jev5.jevFileHash)(contents) !== config.modelSha256)
|
|
860
|
-
throw new Error("Local Jev gate checksum mismatch");
|
|
861
|
-
const { validateJevGateModel: validateJevGateModel2 } = await Promise.resolve().then(() => (init_jevTraining(), jevTraining_exports));
|
|
862
|
-
const model = validateJevGateModel2(JSON.parse(contents));
|
|
863
|
-
if (model.strategy !== input.strategy || model.questionsHash !== (0, import_jev5.jevHash)(JEV_QUESTIONS))
|
|
864
|
-
throw new Error("Local Jev gate strategy or question version mismatch");
|
|
865
|
-
const scores = Object.fromEntries(
|
|
866
|
-
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
867
|
-
key,
|
|
868
|
-
input.questions.includes(key) && model.trees[key] ? (0, import_jev4.predictJevTree)(model.trees[key], input.features) : null
|
|
869
|
-
])
|
|
870
|
-
);
|
|
871
|
-
return {
|
|
872
|
-
assessment: {
|
|
873
|
-
...base,
|
|
874
|
-
status: "available",
|
|
875
|
-
model: config.modelSha256,
|
|
876
|
-
scores,
|
|
877
|
-
confidence: emptyScores()
|
|
803
|
+
// src/portfolio.ts
|
|
804
|
+
var import_portfolio = require("@tradejs/core/portfolio");
|
|
805
|
+
function createPortfolioCoordinator({
|
|
806
|
+
policy,
|
|
807
|
+
strategy,
|
|
808
|
+
store,
|
|
809
|
+
execution
|
|
810
|
+
}) {
|
|
811
|
+
const fingerprint3 = (0, import_portfolio.portfolioFingerprint)(policy, strategy);
|
|
812
|
+
let cache;
|
|
813
|
+
async function transact(now2, change, force = false) {
|
|
814
|
+
for (let attempt = 0; attempt < 16; attempt++) {
|
|
815
|
+
const previous = await store.read();
|
|
816
|
+
if (previous && previous.fingerprint !== fingerprint3)
|
|
817
|
+
throw new Error(
|
|
818
|
+
"Portfolio policy changed under existing state; reconcile the persisted scope before changing configuration"
|
|
819
|
+
);
|
|
820
|
+
const base = previous ?? (0, import_portfolio.createPortfolioState)(fingerprint3);
|
|
821
|
+
const bucket = Math.floor(now2 / policy.observationIntervalMs);
|
|
822
|
+
const observation = !force && cache?.revision === base.revision && cache.bucket === bucket ? cache.observation : await execution.observe(now2, base.reservations);
|
|
823
|
+
const reconciled = (0, import_portfolio.reconcilePortfolio)(
|
|
824
|
+
policy,
|
|
825
|
+
strategy,
|
|
826
|
+
base,
|
|
827
|
+
observation
|
|
828
|
+
);
|
|
829
|
+
for (const intent of base.closing) {
|
|
830
|
+
if (!observation.positions.some(
|
|
831
|
+
(p) => p.symbol === intent.position.symbol
|
|
832
|
+
))
|
|
833
|
+
await execution.confirmClosed?.(intent, observation.asOf);
|
|
878
834
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
const file = import_node_path.default.join(dir, "records", `${id}.json`);
|
|
885
|
-
const getRecord = async () => {
|
|
886
|
-
const existing = await (0, import_jev5.readJevArtifact)(file);
|
|
887
|
-
if (existing) {
|
|
888
|
-
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))
|
|
889
|
-
throw new Error("Jev recording identity mismatch");
|
|
890
|
-
(0, import_jev5.validateJevResponse)(existing.response, input.questions);
|
|
891
|
-
for (const key of import_jev4.JEV_DIMENSIONS) {
|
|
892
|
-
const expected = input.questions.includes(key) ? existing.response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null;
|
|
893
|
-
if (existing.scores[key] !== expected)
|
|
894
|
-
throw new Error("Jev recording scores do not match the response");
|
|
835
|
+
const changed = change(reconciled, observation);
|
|
836
|
+
const next = { ...changed.state, revision: base.revision + 1 };
|
|
837
|
+
if (await store.compare(previous, next)) {
|
|
838
|
+
cache = { revision: next.revision, bucket, observation };
|
|
839
|
+
return { state: next, observation, result: changed.result };
|
|
895
840
|
}
|
|
896
|
-
|
|
841
|
+
cache = void 0;
|
|
897
842
|
}
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
843
|
+
throw new Error("Portfolio state contention; entry was not submitted");
|
|
844
|
+
}
|
|
845
|
+
async function tick(now2, force = false) {
|
|
846
|
+
const claimed = await transact(
|
|
847
|
+
now2,
|
|
848
|
+
(state, observation) => {
|
|
849
|
+
const commands = (0, import_portfolio.portfolioCloseCommands)(policy, state, observation);
|
|
850
|
+
return {
|
|
851
|
+
state: {
|
|
852
|
+
...state,
|
|
853
|
+
closing: state.closing.map(
|
|
854
|
+
(c) => commands.some((x) => x.intent.positionId === c.positionId) ? { ...c, lastAttemptAt: observation.asOf } : c
|
|
855
|
+
)
|
|
856
|
+
},
|
|
857
|
+
result: commands
|
|
858
|
+
};
|
|
859
|
+
},
|
|
860
|
+
force
|
|
861
|
+
);
|
|
862
|
+
for (const command of claimed.result)
|
|
863
|
+
await execution.close(
|
|
864
|
+
command.position,
|
|
865
|
+
command.intent,
|
|
866
|
+
claimed.observation.asOf
|
|
901
867
|
);
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
868
|
+
if (claimed.result.length) {
|
|
869
|
+
cache = void 0;
|
|
870
|
+
const refreshed = await transact(
|
|
871
|
+
now2,
|
|
872
|
+
(state) => ({ state, result: void 0 }),
|
|
873
|
+
true
|
|
874
|
+
);
|
|
875
|
+
return (0, import_portfolio.buildPortfolioContext)(
|
|
876
|
+
policy,
|
|
877
|
+
strategy,
|
|
878
|
+
refreshed.state,
|
|
879
|
+
refreshed.observation
|
|
910
880
|
);
|
|
911
|
-
const started = Date.now();
|
|
912
|
-
const state = stateForJevInput(input);
|
|
913
|
-
const responseId = (0, import_jev5.jevHash)({
|
|
914
|
-
state,
|
|
915
|
-
questionsHash,
|
|
916
|
-
provider,
|
|
917
|
-
user: (0, import_jev5.jevHash)(userName)
|
|
918
|
-
});
|
|
919
|
-
const responseFile = import_node_path.default.join(dir, "responses", `${responseId}.json`);
|
|
920
|
-
let responsePromise = pendingResponses.get(responseFile);
|
|
921
|
-
if (!responsePromise) {
|
|
922
|
-
responsePromise = (async () => {
|
|
923
|
-
const saved = await (0, import_jev5.readJevArtifact)(responseFile);
|
|
924
|
-
if (saved) return (0, import_jev5.validateJevResponse)(saved, input.questions);
|
|
925
|
-
const fresh = await (0, import_jev5.requestJev)(provider, apiKey, state, questions);
|
|
926
|
-
await (0, import_jev5.writeJevArtifact)(responseFile, fresh);
|
|
927
|
-
return fresh;
|
|
928
|
-
})();
|
|
929
|
-
pendingResponses.set(responseFile, responsePromise);
|
|
930
|
-
}
|
|
931
|
-
let response;
|
|
932
|
-
try {
|
|
933
|
-
response = await responsePromise;
|
|
934
|
-
} finally {
|
|
935
|
-
pendingResponses.delete(responseFile);
|
|
936
881
|
}
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
882
|
+
return (0, import_portfolio.buildPortfolioContext)(
|
|
883
|
+
policy,
|
|
884
|
+
strategy,
|
|
885
|
+
claimed.state,
|
|
886
|
+
claimed.observation
|
|
942
887
|
);
|
|
943
|
-
const value = {
|
|
944
|
-
schema: "tradejs-jev-record/v4",
|
|
945
|
-
id,
|
|
946
|
-
inputHash,
|
|
947
|
-
questionsHash,
|
|
948
|
-
provider,
|
|
949
|
-
input,
|
|
950
|
-
response,
|
|
951
|
-
scores,
|
|
952
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
953
|
-
elapsedMs: Date.now() - started
|
|
954
|
-
};
|
|
955
|
-
await (0, import_jev5.writeJevArtifact)(file, value);
|
|
956
|
-
return await (0, import_jev5.readJevArtifact)(file);
|
|
957
|
-
};
|
|
958
|
-
const pendingKey = `${file}:${(0, import_jev5.jevHash)(userName)}`;
|
|
959
|
-
let promise = pending.get(pendingKey);
|
|
960
|
-
if (!promise) {
|
|
961
|
-
promise = getRecord();
|
|
962
|
-
pending.set(pendingKey, promise);
|
|
963
888
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
889
|
+
async function updateReservation(now2, id, submitted, qty) {
|
|
890
|
+
return transact(
|
|
891
|
+
now2,
|
|
892
|
+
(s, o) => {
|
|
893
|
+
const exists = s.reservations.some((r) => r.id === id);
|
|
894
|
+
const blocked = submitted && (!exists || s.closing.length > 0 || o.asOf <= s.blockedThrough);
|
|
895
|
+
return {
|
|
896
|
+
state: {
|
|
897
|
+
...s,
|
|
898
|
+
reservations: s.reservations.flatMap(
|
|
899
|
+
(r) => r.id !== id ? [r] : submitted && !blocked ? [{ ...r, submitted: true, qty: qty ?? r.qty }] : []
|
|
900
|
+
)
|
|
901
|
+
},
|
|
902
|
+
result: !blocked
|
|
903
|
+
};
|
|
904
|
+
},
|
|
905
|
+
true
|
|
906
|
+
);
|
|
969
907
|
}
|
|
970
908
|
return {
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
909
|
+
async context(now2) {
|
|
910
|
+
const state = await store.read() ?? (0, import_portfolio.createPortfolioState)(fingerprint3);
|
|
911
|
+
if (state.fingerprint !== fingerprint3)
|
|
912
|
+
throw new Error("Portfolio policy changed under existing state");
|
|
913
|
+
const observation = await execution.observe(now2, state.reservations);
|
|
914
|
+
(0, import_portfolio.validatePortfolioObservation)(observation);
|
|
915
|
+
return (0, import_portfolio.buildPortfolioContext)(policy, strategy, state, observation);
|
|
916
|
+
},
|
|
917
|
+
tick,
|
|
918
|
+
async submit(now2, entry, send) {
|
|
919
|
+
await tick(now2);
|
|
920
|
+
const admitted = await transact(
|
|
921
|
+
now2,
|
|
922
|
+
(s, o) => {
|
|
923
|
+
const r = (0, import_portfolio.reservePortfolioEntry)(policy, strategy, s, o, {
|
|
924
|
+
...entry,
|
|
925
|
+
submitted: false,
|
|
926
|
+
oppositeHandled: false
|
|
927
|
+
});
|
|
928
|
+
return { state: r.state, result: r.reason };
|
|
929
|
+
},
|
|
930
|
+
true
|
|
931
|
+
);
|
|
932
|
+
if (admitted.result) return { reason: admitted.result };
|
|
933
|
+
let submitted = false;
|
|
934
|
+
try {
|
|
935
|
+
const value = await send(
|
|
936
|
+
async (qty) => {
|
|
937
|
+
if (!Number.isFinite(qty) || qty <= 0)
|
|
938
|
+
throw new Error("Invalid submitted portfolio quantity");
|
|
939
|
+
submitted = true;
|
|
940
|
+
const update = await updateReservation(now2, entry.id, true, qty);
|
|
941
|
+
if (!update.result)
|
|
942
|
+
throw new Error("PORTFOLIO_SUBMISSION_NO_LONGER_ADMITTED");
|
|
943
|
+
},
|
|
944
|
+
async () => {
|
|
945
|
+
await transact(
|
|
946
|
+
now2,
|
|
947
|
+
(s) => ({
|
|
948
|
+
state: {
|
|
949
|
+
...s,
|
|
950
|
+
reservations: s.reservations.filter((r) => r.id !== entry.id),
|
|
951
|
+
completedSignals: {
|
|
952
|
+
...s.completedSignals,
|
|
953
|
+
[entry.signalId]: now2
|
|
954
|
+
}
|
|
955
|
+
},
|
|
956
|
+
result: void 0
|
|
957
|
+
}),
|
|
958
|
+
true
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
);
|
|
962
|
+
if (!submitted) await updateReservation(now2, entry.id, false);
|
|
963
|
+
else await tick(now2, true);
|
|
964
|
+
return { value };
|
|
965
|
+
} catch (error) {
|
|
966
|
+
if (!submitted) await updateReservation(now2, entry.id, false);
|
|
967
|
+
throw error;
|
|
968
|
+
}
|
|
984
969
|
}
|
|
985
970
|
};
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
try {
|
|
997
|
-
const input = buildJevInput(signal);
|
|
998
|
-
({ assessment } = await evaluateJevInput({
|
|
999
|
-
input,
|
|
1000
|
-
config,
|
|
1001
|
-
userName,
|
|
1002
|
-
projectRoot
|
|
1003
|
-
}));
|
|
1004
|
-
} catch (error) {
|
|
1005
|
-
if (strict) throw error;
|
|
1006
|
-
assessment = {
|
|
1007
|
-
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
1008
|
-
schema: "tradejs-signal-assessment/v5",
|
|
1009
|
-
source: config.source,
|
|
1010
|
-
mode: config.mode,
|
|
1011
|
-
status: "unavailable",
|
|
1012
|
-
inputHash: "",
|
|
1013
|
-
questionsHash: "",
|
|
1014
|
-
geometryStatus: "absent",
|
|
1015
|
-
levelsValid: null,
|
|
1016
|
-
model: config.provider?.model ?? config.modelSha256 ?? "",
|
|
1017
|
-
scores: emptyScores(),
|
|
1018
|
-
confidence: emptyScores()
|
|
1019
|
-
};
|
|
1020
|
-
}
|
|
1021
|
-
signal.assessment = assessment;
|
|
1022
|
-
signal.additionalIndicators = {
|
|
1023
|
-
...signal.additionalIndicators,
|
|
1024
|
-
jev: (0, import_jev4.jevAssessmentFeatures)(assessment)
|
|
971
|
+
}
|
|
972
|
+
function createMemoryPortfolioStore() {
|
|
973
|
+
let state = null;
|
|
974
|
+
return {
|
|
975
|
+
read: async () => state === null ? null : structuredClone(state),
|
|
976
|
+
compare: async (expected, next) => {
|
|
977
|
+
if ((state?.revision ?? -1) !== (expected?.revision ?? -1)) return false;
|
|
978
|
+
state = structuredClone(next);
|
|
979
|
+
return true;
|
|
980
|
+
}
|
|
1025
981
|
};
|
|
1026
|
-
|
|
1027
|
-
signalId: signal.signalId,
|
|
1028
|
-
strategy: signal.strategy,
|
|
1029
|
-
symbol: signal.symbol,
|
|
1030
|
-
timestamp: signal.timestamp,
|
|
1031
|
-
scope,
|
|
1032
|
-
user: (0, import_jev5.jevHash)(userName),
|
|
1033
|
-
config,
|
|
1034
|
-
assessment
|
|
1035
|
-
};
|
|
1036
|
-
await (0, import_jev5.writeJevArtifact)(
|
|
1037
|
-
import_node_path.default.resolve(
|
|
1038
|
-
projectRoot,
|
|
1039
|
-
config.recordsDir ?? "data/ai/jev",
|
|
1040
|
-
"decisions",
|
|
1041
|
-
`${(0, import_jev5.jevHash)(identity)}.json`
|
|
1042
|
-
),
|
|
1043
|
-
identity
|
|
1044
|
-
);
|
|
1045
|
-
return assessment;
|
|
1046
|
-
};
|
|
1047
|
-
|
|
1048
|
-
// src/strategyRuntime.ts
|
|
1049
|
-
var import_constants5 = require("@tradejs/core/constants");
|
|
1050
|
-
var import_strategies6 = require("@tradejs/core/strategies");
|
|
1051
|
-
var import_logger11 = require("@tradejs/infra/logger");
|
|
1052
|
-
|
|
1053
|
-
// src/strategyHelpers/runtime.ts
|
|
1054
|
-
var import_logger9 = require("@tradejs/infra/logger");
|
|
1055
|
-
var import_constants3 = require("@tradejs/core/constants");
|
|
1056
|
-
var import_ml2 = require("@tradejs/infra/ml");
|
|
982
|
+
}
|
|
1057
983
|
|
|
1058
|
-
// src/
|
|
984
|
+
// src/runtimeJournal.ts
|
|
985
|
+
var import_node_crypto = require("crypto");
|
|
986
|
+
var import_constants = require("@tradejs/core/constants");
|
|
987
|
+
var import_time = require("@tradejs/core/time");
|
|
988
|
+
var import_trade = require("@tradejs/core/trade");
|
|
1059
989
|
var import_logger = require("@tradejs/infra/logger");
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
var
|
|
1063
|
-
|
|
1064
|
-
super(...arguments);
|
|
1065
|
-
this.name = "EntrySubmissionValidationError";
|
|
1066
|
-
}
|
|
1067
|
-
};
|
|
1068
|
-
var resolveEntryExecutionLimits = (config, direction) => {
|
|
1069
|
-
const side = config[direction];
|
|
1070
|
-
const minimum = side && typeof side === "object" && "minRiskRatio" in side ? side.minRiskRatio : void 0;
|
|
1071
|
-
const maximum = config.MAX_LOSS_VALUE;
|
|
1072
|
-
if (minimum == null && maximum == null) return void 0;
|
|
1073
|
-
const minimumNetRiskRatio = minimum == null ? void 0 : Number(minimum);
|
|
1074
|
-
const maxLossValue = maximum == null ? void 0 : Number(maximum);
|
|
1075
|
-
const feeRate = Number(config.RISK_FEE_RATE ?? 0);
|
|
1076
|
-
const slippage = Number(config.RISK_SLIPPAGE_BPS ?? 0);
|
|
1077
|
-
const impact = Number(config.RISK_MARKET_IMPACT_BPS ?? 0);
|
|
1078
|
-
const slippageBps = slippage + impact;
|
|
1079
|
-
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)
|
|
1080
|
-
throw new Error("INVALID_ENTRY_EXECUTION_LIMITS");
|
|
1081
|
-
return { minimumNetRiskRatio, maxLossValue, feeRate, slippageBps };
|
|
1082
|
-
};
|
|
1083
|
-
var validateEntryExecution = ({
|
|
990
|
+
var import_redis = require("@tradejs/infra/redis");
|
|
991
|
+
var now = () => Date.now();
|
|
992
|
+
var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
|
|
993
|
+
var calculateClosedPnl = ({
|
|
1084
994
|
direction,
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
takeProfitPrice,
|
|
1089
|
-
limits
|
|
995
|
+
entryPrice,
|
|
996
|
+
exitPrice,
|
|
997
|
+
qty
|
|
1090
998
|
}) => {
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
1094
|
-
);
|
|
1095
|
-
const side = direction === "LONG" ? 1 : -1;
|
|
1096
|
-
if (side * (price - stopLossPrice) <= 0 || side * (takeProfitPrice - price) <= 0)
|
|
1097
|
-
throw new EntrySubmissionValidationError(
|
|
1098
|
-
"INVALID_ENTRY_PROTECTION_AT_SUBMISSION"
|
|
1099
|
-
);
|
|
1100
|
-
const slippage = limits.slippageBps / 1e4;
|
|
1101
|
-
const cost = (value, sign) => value * slippage + value * (1 + sign * slippage) * limits.feeRate;
|
|
1102
|
-
const entryCost = cost(price, side);
|
|
1103
|
-
const lossPerUnit = Math.abs(price - stopLossPrice) + entryCost + cost(stopLossPrice, -side);
|
|
1104
|
-
const rewardPerUnit = Math.max(
|
|
1105
|
-
0,
|
|
1106
|
-
Math.abs(takeProfitPrice - price) - entryCost - cost(takeProfitPrice, -side)
|
|
1107
|
-
);
|
|
1108
|
-
const netRiskRatio = rewardPerUnit / lossPerUnit;
|
|
1109
|
-
const plannedLossValue = qty * lossPerUnit;
|
|
1110
|
-
if (!Number.isFinite(netRiskRatio) || !Number.isFinite(plannedLossValue))
|
|
1111
|
-
throw new EntrySubmissionValidationError(
|
|
1112
|
-
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
1113
|
-
);
|
|
1114
|
-
if (limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio)
|
|
1115
|
-
throw new EntrySubmissionValidationError(
|
|
1116
|
-
"ENTRY_RR_BELOW_MIN_AT_SUBMISSION"
|
|
1117
|
-
);
|
|
1118
|
-
if (limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9))
|
|
1119
|
-
throw new EntrySubmissionValidationError(
|
|
1120
|
-
"ENTRY_RISK_EXCEEDS_MAX_LOSS_AT_SUBMISSION"
|
|
1121
|
-
);
|
|
1122
|
-
return { netRiskRatio, plannedLossValue };
|
|
999
|
+
const pnl = direction === "LONG" ? (exitPrice - entryPrice) * qty : (entryPrice - exitPrice) * qty;
|
|
1000
|
+
return Number.isFinite(pnl) ? pnl : null;
|
|
1123
1001
|
};
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
}
|
|
1133
|
-
if (
|
|
1134
|
-
return
|
|
1135
|
-
arrivalSnapshotTime: Date.now(),
|
|
1136
|
-
arrivalSource: "unavailable",
|
|
1137
|
-
bid: null,
|
|
1138
|
-
ask: null,
|
|
1139
|
-
arrivalMid: null,
|
|
1140
|
-
spreadBps: null
|
|
1141
|
-
};
|
|
1002
|
+
var createRuntimeOrderId = (strategy) => {
|
|
1003
|
+
const prefix = (0, import_trade.createRuntimeOrderLinkPrefix)(strategy);
|
|
1004
|
+
if (prefix === "tjs-") {
|
|
1005
|
+
return `tjs-${(0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 24).toLowerCase()}`;
|
|
1006
|
+
}
|
|
1007
|
+
return `${prefix}${toRandomOrderSuffix()}`;
|
|
1008
|
+
};
|
|
1009
|
+
var recordRuntimeTradeOpen = async (params) => {
|
|
1010
|
+
const { userName } = params;
|
|
1011
|
+
if (!userName) {
|
|
1012
|
+
return null;
|
|
1142
1013
|
}
|
|
1014
|
+
const record = {
|
|
1015
|
+
...params,
|
|
1016
|
+
entryCount: 1,
|
|
1017
|
+
lastEntryPrice: params.entryPrice,
|
|
1018
|
+
lastEntryQty: params.qty,
|
|
1019
|
+
lastEntryTimestamp: params.entryTimestamp,
|
|
1020
|
+
status: "active",
|
|
1021
|
+
currentPrice: params.entryPrice,
|
|
1022
|
+
currentPnl: 0,
|
|
1023
|
+
closedPnl: null,
|
|
1024
|
+
exitPrice: null,
|
|
1025
|
+
exitTimestamp: null,
|
|
1026
|
+
lastSyncedAt: now()
|
|
1027
|
+
};
|
|
1028
|
+
const dayKey = (0, import_time.getRuntimeStorageDayKey)(record.entryTimestamp);
|
|
1029
|
+
const runtimeScopeId = record.deploymentId ?? record.accountId;
|
|
1143
1030
|
try {
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1031
|
+
await Promise.all([
|
|
1032
|
+
(0, import_redis.setData)(import_redis.redisKeys.runtimeTrade(userName, record.orderId), record, {
|
|
1033
|
+
expire: 0
|
|
1034
|
+
}),
|
|
1035
|
+
(0, import_redis.setHashJsonField)(
|
|
1036
|
+
import_redis.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
1037
|
+
record.orderId,
|
|
1038
|
+
record,
|
|
1039
|
+
{ expire: 0 }
|
|
1040
|
+
),
|
|
1041
|
+
(0, import_redis.setData)(
|
|
1042
|
+
import_redis.redisKeys.runtimeActiveTrade(userName, record.symbol, runtimeScopeId),
|
|
1043
|
+
{ orderId: record.orderId },
|
|
1044
|
+
{ expire: 0 }
|
|
1045
|
+
)
|
|
1046
|
+
]);
|
|
1159
1047
|
} catch (error) {
|
|
1160
|
-
import_logger.logger.
|
|
1161
|
-
"runtime
|
|
1162
|
-
symbol,
|
|
1048
|
+
import_logger.logger.error(
|
|
1049
|
+
"runtime trade open journal failed: %s %s",
|
|
1050
|
+
record.symbol,
|
|
1163
1051
|
error?.message || String(error)
|
|
1164
1052
|
);
|
|
1165
|
-
return {
|
|
1166
|
-
arrivalSnapshotTime: Date.now(),
|
|
1167
|
-
arrivalSource: "top_of_book_error",
|
|
1168
|
-
bid: null,
|
|
1169
|
-
ask: null,
|
|
1170
|
-
arrivalMid: null,
|
|
1171
|
-
spreadBps: null
|
|
1172
|
-
};
|
|
1173
1053
|
}
|
|
1054
|
+
return record;
|
|
1174
1055
|
};
|
|
1175
|
-
var
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
}
|
|
1056
|
+
var recordRuntimeTradeIncrease = async (params) => {
|
|
1057
|
+
const {
|
|
1058
|
+
userName,
|
|
1059
|
+
strategy,
|
|
1060
|
+
symbol,
|
|
1061
|
+
direction,
|
|
1062
|
+
resultingQty,
|
|
1063
|
+
resultingEntryPrice,
|
|
1064
|
+
addedQty,
|
|
1065
|
+
addedEntryPrice,
|
|
1066
|
+
entryTimestamp,
|
|
1067
|
+
fee,
|
|
1068
|
+
accountId,
|
|
1069
|
+
deploymentId
|
|
1070
|
+
} = params;
|
|
1071
|
+
if (!userName) {
|
|
1072
|
+
return null;
|
|
1193
1073
|
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1074
|
+
const existing = await getActiveRuntimeTrade({
|
|
1075
|
+
userName,
|
|
1076
|
+
symbol,
|
|
1077
|
+
accountId,
|
|
1078
|
+
deploymentId
|
|
1079
|
+
});
|
|
1080
|
+
if (!existing || existing.strategy !== strategy || existing.direction !== direction) {
|
|
1081
|
+
return null;
|
|
1196
1082
|
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
arrivalSnapshotTime: Date.now(),
|
|
1216
|
-
arrivalSource: "simulation",
|
|
1217
|
-
bid: executionPrice,
|
|
1218
|
-
ask: executionPrice,
|
|
1219
|
-
arrivalMid: executionPrice,
|
|
1220
|
-
spreadBps: 0
|
|
1221
|
-
} : await getOrderArrivalSnapshot({
|
|
1222
|
-
connector,
|
|
1223
|
-
symbol,
|
|
1224
|
-
requireTimestamp: true
|
|
1225
|
-
});
|
|
1226
|
-
const age = Date.now() - snapshot2.arrivalSnapshotTime;
|
|
1227
|
-
if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
|
|
1228
|
-
throw new EntrySubmissionValidationError(
|
|
1229
|
-
"ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
|
|
1230
|
-
);
|
|
1231
|
-
if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
|
|
1232
|
-
throw new EntrySubmissionValidationError(
|
|
1233
|
-
"ENTRY_QUOTE_STALE_AT_SUBMISSION"
|
|
1234
|
-
);
|
|
1235
|
-
const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
|
|
1236
|
-
validateEntryProtectionAtArrival({
|
|
1237
|
-
direction,
|
|
1238
|
-
signalPrice: price,
|
|
1239
|
-
bid: snapshot2.bid,
|
|
1240
|
-
ask: snapshot2.ask,
|
|
1241
|
-
arrivalMid: snapshot2.arrivalMid,
|
|
1242
|
-
takeProfits,
|
|
1243
|
-
stopLossPrice
|
|
1244
|
-
});
|
|
1245
|
-
const prices = takeProfits.map((tp) => tp.price);
|
|
1246
|
-
const economics2 = validateEntryExecution({
|
|
1247
|
-
direction,
|
|
1248
|
-
price,
|
|
1249
|
-
qty,
|
|
1250
|
-
stopLossPrice,
|
|
1251
|
-
takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
|
|
1252
|
-
limits
|
|
1253
|
-
});
|
|
1254
|
-
onValidated?.(snapshot2, economics2);
|
|
1083
|
+
const addedFee = typeof fee === "number" && Number.isFinite(fee) ? fee : 0;
|
|
1084
|
+
const openFee = (existing.openFee ?? existing.fee ?? 0) + addedFee;
|
|
1085
|
+
const totalFee = (existing.totalFee ?? existing.fee ?? 0) + addedFee;
|
|
1086
|
+
const next = {
|
|
1087
|
+
...existing,
|
|
1088
|
+
...params.orderLeverage ? { orderLeverage: params.orderLeverage } : {},
|
|
1089
|
+
qty: resultingQty,
|
|
1090
|
+
entryPrice: resultingEntryPrice,
|
|
1091
|
+
entryCount: Math.max(1, existing.entryCount ?? 1) + 1,
|
|
1092
|
+
lastEntryPrice: addedEntryPrice,
|
|
1093
|
+
lastEntryQty: addedQty,
|
|
1094
|
+
lastEntryTimestamp: entryTimestamp,
|
|
1095
|
+
currentPrice: resultingEntryPrice,
|
|
1096
|
+
currentPnl: 0,
|
|
1097
|
+
fee: openFee,
|
|
1098
|
+
openFee,
|
|
1099
|
+
totalFee,
|
|
1100
|
+
lastSyncedAt: now()
|
|
1255
1101
|
};
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
}
|
|
1275
|
-
return trimmed.map(
|
|
1276
|
-
(item) => item && typeof item === "object" ? trimSeriesDeep(item) : item
|
|
1277
|
-
);
|
|
1278
|
-
}
|
|
1279
|
-
if (value && typeof value === "object") {
|
|
1280
|
-
return Object.fromEntries(
|
|
1281
|
-
Object.entries(value).map(([key, nested]) => [
|
|
1282
|
-
key,
|
|
1283
|
-
trimSeriesDeep(nested)
|
|
1284
|
-
])
|
|
1102
|
+
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
1103
|
+
try {
|
|
1104
|
+
await Promise.all([
|
|
1105
|
+
(0, import_redis.setData)(import_redis.redisKeys.runtimeTrade(userName, existing.orderId), next, {
|
|
1106
|
+
expire: 0
|
|
1107
|
+
}),
|
|
1108
|
+
(0, import_redis.setHashJsonField)(
|
|
1109
|
+
import_redis.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
1110
|
+
existing.orderId,
|
|
1111
|
+
next,
|
|
1112
|
+
{ expire: 0 }
|
|
1113
|
+
)
|
|
1114
|
+
]);
|
|
1115
|
+
} catch (error) {
|
|
1116
|
+
import_logger.logger.error(
|
|
1117
|
+
"runtime trade increase journal failed: %s %s",
|
|
1118
|
+
symbol,
|
|
1119
|
+
error?.message || String(error)
|
|
1285
1120
|
);
|
|
1286
1121
|
}
|
|
1287
|
-
return
|
|
1122
|
+
return next;
|
|
1288
1123
|
};
|
|
1289
|
-
var
|
|
1290
|
-
const
|
|
1291
|
-
if (
|
|
1292
|
-
return
|
|
1124
|
+
var getActiveRuntimeTrade = async (params) => {
|
|
1125
|
+
const { userName, symbol, accountId, deploymentId } = params;
|
|
1126
|
+
if (!userName) {
|
|
1127
|
+
return null;
|
|
1293
1128
|
}
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
if (!
|
|
1129
|
+
const activeRef = await (0, import_redis.getData)(
|
|
1130
|
+
import_redis.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId),
|
|
1131
|
+
null
|
|
1132
|
+
);
|
|
1133
|
+
const orderId = String(activeRef?.orderId || "").trim();
|
|
1134
|
+
if (!orderId) {
|
|
1300
1135
|
return null;
|
|
1301
1136
|
}
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
};
|
|
1312
|
-
var buildMissingSpreadContext = () => ({
|
|
1313
|
-
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1314
|
-
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1315
|
-
available: false,
|
|
1316
|
-
value: null,
|
|
1317
|
-
zScore: null,
|
|
1318
|
-
bps: null,
|
|
1319
|
-
absBps: null,
|
|
1320
|
-
bias: null,
|
|
1321
|
-
severity: null
|
|
1322
|
-
});
|
|
1323
|
-
var buildSpreadContextFromSignal = (signal) => {
|
|
1324
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1325
|
-
const relative = toRecord(baseContext?.relative);
|
|
1326
|
-
const execution = toRecord(relative?.execution);
|
|
1327
|
-
const spread = toFiniteNumber(execution?.venueSpread);
|
|
1328
|
-
const zScore = toFiniteNumber(execution?.venueSpreadZScore);
|
|
1329
|
-
if (spread == null) {
|
|
1330
|
-
return buildMissingSpreadContext();
|
|
1137
|
+
const existing = await (0, import_redis.getData)(
|
|
1138
|
+
import_redis.redisKeys.runtimeTrade(userName, orderId),
|
|
1139
|
+
null
|
|
1140
|
+
);
|
|
1141
|
+
if (!existing || existing.status !== "active") {
|
|
1142
|
+
await (0, import_redis.delKey)(
|
|
1143
|
+
import_redis.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
|
|
1144
|
+
);
|
|
1145
|
+
return null;
|
|
1331
1146
|
}
|
|
1332
|
-
|
|
1333
|
-
const bps = roundTo(value * 1e4, 2);
|
|
1334
|
-
const absBps = Math.abs(bps);
|
|
1335
|
-
const bias = absBps < 1 ? "flat" : bps > 0 ? "coinbase_premium" : "binance_premium";
|
|
1336
|
-
const severity = absBps >= 20 ? "wide" : absBps >= 5 ? "elevated" : "normal";
|
|
1337
|
-
return {
|
|
1338
|
-
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1339
|
-
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1340
|
-
available: true,
|
|
1341
|
-
value,
|
|
1342
|
-
zScore,
|
|
1343
|
-
bps,
|
|
1344
|
-
absBps,
|
|
1345
|
-
bias,
|
|
1346
|
-
severity
|
|
1347
|
-
};
|
|
1147
|
+
return existing;
|
|
1348
1148
|
};
|
|
1349
|
-
var
|
|
1350
|
-
const
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
signedVolumeZScore: null
|
|
1365
|
-
};
|
|
1149
|
+
var markRuntimeTradeClosed = async (params) => {
|
|
1150
|
+
const {
|
|
1151
|
+
userName,
|
|
1152
|
+
expectedOrderId,
|
|
1153
|
+
symbol,
|
|
1154
|
+
strategy,
|
|
1155
|
+
exitPrice,
|
|
1156
|
+
exitTimestamp,
|
|
1157
|
+
closedPnl,
|
|
1158
|
+
exitType,
|
|
1159
|
+
accountId,
|
|
1160
|
+
deploymentId
|
|
1161
|
+
} = params;
|
|
1162
|
+
if (!userName) {
|
|
1163
|
+
return null;
|
|
1366
1164
|
}
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
};
|
|
1378
|
-
var buildTradeFlowContextFromSignal = (signal) => {
|
|
1379
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1380
|
-
const participation = toRecord(baseContext?.participation);
|
|
1381
|
-
const tradeFlow = toRecord(participation?.tradeFlow);
|
|
1382
|
-
if (!tradeFlow) {
|
|
1383
|
-
return {
|
|
1384
|
-
source: null,
|
|
1385
|
-
available: false,
|
|
1386
|
-
interval: null,
|
|
1387
|
-
stale: null,
|
|
1388
|
-
trades: null,
|
|
1389
|
-
buyPressurePct: null,
|
|
1390
|
-
netBaseDelta: null,
|
|
1391
|
-
netQuoteDelta: null
|
|
1392
|
-
};
|
|
1165
|
+
const existing = expectedOrderId ? await (0, import_redis.getDataStrict)(
|
|
1166
|
+
import_redis.redisKeys.runtimeTrade(userName, expectedOrderId)
|
|
1167
|
+
) : await getActiveRuntimeTrade({
|
|
1168
|
+
userName,
|
|
1169
|
+
symbol,
|
|
1170
|
+
accountId,
|
|
1171
|
+
deploymentId
|
|
1172
|
+
});
|
|
1173
|
+
if (!existing || expectedOrderId && existing.orderId !== expectedOrderId) {
|
|
1174
|
+
return null;
|
|
1393
1175
|
}
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
interval: String(tradeFlow.interval ?? ""),
|
|
1398
|
-
stale: typeof tradeFlow.stale === "boolean" ? tradeFlow.stale : null,
|
|
1399
|
-
trades: toFiniteNumber(tradeFlow.trades),
|
|
1400
|
-
buyPressurePct: toFiniteNumber(tradeFlow.buyPressurePct),
|
|
1401
|
-
netBaseDelta: toFiniteNumber(tradeFlow.netBaseDelta),
|
|
1402
|
-
netQuoteDelta: toFiniteNumber(tradeFlow.netQuoteDelta)
|
|
1403
|
-
};
|
|
1404
|
-
};
|
|
1405
|
-
var buildMarketBreadthContextFromSignal = (signal) => {
|
|
1406
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1407
|
-
const relative = toRecord(baseContext?.relative);
|
|
1408
|
-
const breadth = toRecord(relative?.marketBreadth);
|
|
1409
|
-
if (!breadth) {
|
|
1410
|
-
return {
|
|
1411
|
-
source: null,
|
|
1412
|
-
available: false,
|
|
1413
|
-
universe: null,
|
|
1414
|
-
interval: null,
|
|
1415
|
-
stale: null,
|
|
1416
|
-
symbolsCount: null,
|
|
1417
|
-
advanceDeclineRatio: null,
|
|
1418
|
-
pctAboveMa20: null,
|
|
1419
|
-
pctAboveMa50: null,
|
|
1420
|
-
equalWeightedReturn: null,
|
|
1421
|
-
volumeWeightedReturn: null,
|
|
1422
|
-
dispersion: null
|
|
1423
|
-
};
|
|
1176
|
+
const orderId = existing.orderId;
|
|
1177
|
+
if (strategy && existing.strategy !== strategy) {
|
|
1178
|
+
return null;
|
|
1424
1179
|
}
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1180
|
+
const resolvedExitPrice = typeof exitPrice === "number" && Number.isFinite(exitPrice) ? exitPrice : existing.currentPrice ?? existing.entryPrice;
|
|
1181
|
+
const resolvedClosedPnl = typeof closedPnl === "number" && Number.isFinite(closedPnl) ? closedPnl : typeof resolvedExitPrice === "number" && Number.isFinite(resolvedExitPrice) ? calculateClosedPnl({
|
|
1182
|
+
direction: existing.direction,
|
|
1183
|
+
entryPrice: existing.entryPrice,
|
|
1184
|
+
exitPrice: resolvedExitPrice,
|
|
1185
|
+
qty: existing.qty
|
|
1186
|
+
}) : existing.closedPnl ?? existing.currentPnl ?? null;
|
|
1187
|
+
const next = expectedOrderId && existing.status === "closed" ? existing : {
|
|
1188
|
+
...existing,
|
|
1189
|
+
status: "closed",
|
|
1190
|
+
currentPrice: resolvedExitPrice,
|
|
1191
|
+
currentPnl: resolvedClosedPnl,
|
|
1192
|
+
closedPnl: resolvedClosedPnl,
|
|
1193
|
+
exitPrice: resolvedExitPrice,
|
|
1194
|
+
exitTimestamp: typeof exitTimestamp === "number" && Number.isFinite(exitTimestamp) ? exitTimestamp : now(),
|
|
1195
|
+
exitType: exitType ?? existing.exitType ?? null,
|
|
1196
|
+
lastSyncedAt: now()
|
|
1438
1197
|
};
|
|
1198
|
+
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
1199
|
+
const closeDayKey = (0, import_time.getRuntimeStorageDayKey)(next.exitTimestamp);
|
|
1200
|
+
try {
|
|
1201
|
+
await Promise.all([
|
|
1202
|
+
(expectedOrderId ? import_redis.setDataStrict : import_redis.setData)(
|
|
1203
|
+
import_redis.redisKeys.runtimeTrade(userName, orderId),
|
|
1204
|
+
next,
|
|
1205
|
+
{
|
|
1206
|
+
expire: import_constants.TTL_1M
|
|
1207
|
+
}
|
|
1208
|
+
),
|
|
1209
|
+
(expectedOrderId ? import_redis.setHashJsonFieldStrict : import_redis.setHashJsonField)(
|
|
1210
|
+
import_redis.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
1211
|
+
orderId,
|
|
1212
|
+
next,
|
|
1213
|
+
{ expire: import_constants.TTL_1M }
|
|
1214
|
+
),
|
|
1215
|
+
(expectedOrderId ? import_redis.setHashJsonFieldStrict : import_redis.setHashJsonField)(
|
|
1216
|
+
import_redis.redisKeys.runtimeClosedTradeBucket(userName, closeDayKey),
|
|
1217
|
+
orderId,
|
|
1218
|
+
next,
|
|
1219
|
+
{ expire: import_constants.TTL_1M }
|
|
1220
|
+
),
|
|
1221
|
+
expectedOrderId ? (0, import_redis.deleteRuntimeTradeReference)(
|
|
1222
|
+
import_redis.redisKeys.runtimeActiveTrade(
|
|
1223
|
+
userName,
|
|
1224
|
+
symbol,
|
|
1225
|
+
deploymentId ?? accountId
|
|
1226
|
+
),
|
|
1227
|
+
expectedOrderId
|
|
1228
|
+
) : (0, import_redis.delKey)(
|
|
1229
|
+
import_redis.redisKeys.runtimeActiveTrade(
|
|
1230
|
+
userName,
|
|
1231
|
+
symbol,
|
|
1232
|
+
deploymentId ?? accountId
|
|
1233
|
+
)
|
|
1234
|
+
)
|
|
1235
|
+
]);
|
|
1236
|
+
} catch (error) {
|
|
1237
|
+
if (expectedOrderId) throw error;
|
|
1238
|
+
import_logger.logger.error(
|
|
1239
|
+
"runtime trade close journal failed: %s %s",
|
|
1240
|
+
symbol,
|
|
1241
|
+
error?.message || String(error)
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
return next;
|
|
1439
1245
|
};
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1246
|
+
|
|
1247
|
+
// src/strategy/portfolioRuntime.ts
|
|
1248
|
+
var coordinators = /* @__PURE__ */ new WeakMap();
|
|
1249
|
+
async function observeRuntimePortfolio(connector, binding, asOf, reservations, observedNow = Date.now) {
|
|
1250
|
+
if (!connector.getOpenPositionPnl || !connector.getEntryExecutions)
|
|
1251
|
+
throw new Error(
|
|
1252
|
+
"Portfolio policies require position marks and entry executions"
|
|
1253
|
+
);
|
|
1254
|
+
const orders = await Promise.all(
|
|
1255
|
+
reservations.map(async (r) => {
|
|
1256
|
+
if (r.submitted && connector.getEntryOrderObservation) {
|
|
1257
|
+
const fact2 = await connector.getEntryOrderObservation({
|
|
1258
|
+
symbol: r.symbol,
|
|
1259
|
+
orderId: r.id
|
|
1260
|
+
});
|
|
1261
|
+
if (fact2) return fact2;
|
|
1262
|
+
}
|
|
1263
|
+
const executions = r.submitted ? await connector.getEntryExecutions({
|
|
1264
|
+
symbol: r.symbol,
|
|
1265
|
+
startTime: r.timestamp,
|
|
1266
|
+
endTime: asOf
|
|
1267
|
+
}) : [];
|
|
1268
|
+
const filledQty = executions.filter(
|
|
1269
|
+
(e) => (e.orderLinkId === r.id || e.orderId === r.id) && e.direction === r.direction
|
|
1270
|
+
).reduce((sum, e) => sum + e.qty, 0);
|
|
1447
1271
|
return {
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1272
|
+
id: r.id,
|
|
1273
|
+
status: filledQty >= r.qty ? "filled" : filledQty > 0 ? "partial" : "pending",
|
|
1274
|
+
filledQty,
|
|
1275
|
+
positionId: r.id
|
|
1276
|
+
};
|
|
1277
|
+
})
|
|
1278
|
+
);
|
|
1279
|
+
const snapshots2 = await connector.getOpenPositionPnl();
|
|
1280
|
+
const positions = await Promise.all(
|
|
1281
|
+
snapshots2.map(async (p) => {
|
|
1282
|
+
const reserved = reservations.find(
|
|
1283
|
+
(r) => r.symbol === p.symbol && r.direction === p.direction && orders.some(
|
|
1284
|
+
(o) => o.id === r.id && o.filledQty > 0 && p.qty <= o.filledQty * (1 + 1e-8)
|
|
1285
|
+
)
|
|
1286
|
+
);
|
|
1287
|
+
const trade = await getActiveRuntimeTrade({
|
|
1288
|
+
...binding,
|
|
1289
|
+
symbol: p.symbol
|
|
1290
|
+
});
|
|
1291
|
+
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;
|
|
1292
|
+
return {
|
|
1293
|
+
id: reserved?.id ?? owner?.orderId ?? `unknown:${p.symbol}:${p.direction}`,
|
|
1294
|
+
strategy: reserved?.strategy ?? owner?.strategy ?? null,
|
|
1295
|
+
symbol: p.symbol,
|
|
1296
|
+
direction: p.direction,
|
|
1297
|
+
qty: p.qty,
|
|
1298
|
+
entryPrice: p.price,
|
|
1299
|
+
markPrice: p.currentPrice,
|
|
1300
|
+
markedAt: p.observedAt ?? null,
|
|
1301
|
+
stopLossPrice: p.slPrice ?? p.stopLossPrice ?? null
|
|
1460
1302
|
};
|
|
1303
|
+
})
|
|
1304
|
+
);
|
|
1305
|
+
return { asOf: Math.max(asOf, observedNow()), positions, orders };
|
|
1306
|
+
}
|
|
1307
|
+
function persistentStore(key) {
|
|
1308
|
+
const raw = /* @__PURE__ */ new WeakMap();
|
|
1309
|
+
return {
|
|
1310
|
+
async read() {
|
|
1311
|
+
const value = await import_redis2.portfolioStateStorage.read(key);
|
|
1312
|
+
if (value === null) return null;
|
|
1313
|
+
const state = JSON.parse(value);
|
|
1314
|
+
(0, import_portfolio2.assertPortfolioState)(state);
|
|
1315
|
+
raw.set(state, value);
|
|
1316
|
+
return state;
|
|
1317
|
+
},
|
|
1318
|
+
async compare(previous, next) {
|
|
1319
|
+
const expected = previous === null ? null : raw.get(previous);
|
|
1320
|
+
if (expected === void 0)
|
|
1321
|
+
throw new Error("Portfolio CAS requires a storage snapshot");
|
|
1322
|
+
return import_redis2.portfolioStateStorage.compare(key, expected, JSON.stringify(next));
|
|
1461
1323
|
}
|
|
1462
|
-
return {
|
|
1463
|
-
source: String(breadth.source ?? ""),
|
|
1464
|
-
available: true,
|
|
1465
|
-
universe: String(breadth.universe ?? ""),
|
|
1466
|
-
interval: String(breadth.interval ?? ""),
|
|
1467
|
-
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
1468
|
-
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
1469
|
-
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
1470
|
-
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
1471
|
-
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
1472
|
-
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
1473
|
-
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
1474
|
-
dispersion: toFiniteNumber(breadth.dispersion)
|
|
1475
|
-
};
|
|
1476
1324
|
};
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1325
|
+
}
|
|
1326
|
+
function getPortfolioRuntime({
|
|
1327
|
+
value,
|
|
1328
|
+
connectorName,
|
|
1329
|
+
strategy,
|
|
1330
|
+
connector,
|
|
1331
|
+
env,
|
|
1332
|
+
userName,
|
|
1333
|
+
accountId,
|
|
1334
|
+
deploymentId
|
|
1335
|
+
}) {
|
|
1336
|
+
const policy = (0, import_portfolio2.parsePortfolioPolicy)(value);
|
|
1337
|
+
if (!policy) return void 0;
|
|
1338
|
+
const replay = (env === "BACKTEST" || env === "PARITY") && Reflect.get(connector, "__tradejsReplayConnector") === true;
|
|
1339
|
+
if ((env === "BACKTEST" || env === "PARITY") && (!replay || !connector.getPortfolioObservation))
|
|
1340
|
+
throw new Error(
|
|
1341
|
+
"Portfolio policies require shared portfolio replay; isolated symbol backtests are unsupported"
|
|
1342
|
+
);
|
|
1343
|
+
if (!replay && (!userName || !accountId || !deploymentId || !connector.getOpenPositionPnl || !connector.getEntryExecutions))
|
|
1344
|
+
throw new Error(
|
|
1345
|
+
"Portfolio runtime requires exact user/account/deployment binding, marks and executions"
|
|
1346
|
+
);
|
|
1347
|
+
const binding = {
|
|
1348
|
+
userName,
|
|
1349
|
+
accountId,
|
|
1350
|
+
deploymentId
|
|
1483
1351
|
};
|
|
1352
|
+
const scope = policy.scope === "strategy" ? strategy : policy.scope === "account" ? "account" : [...policy.scope.strategies].sort();
|
|
1353
|
+
const key = (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify([binding, connectorName, connector.universe, scope])).digest("hex");
|
|
1354
|
+
const fingerprint3 = (0, import_portfolio2.portfolioFingerprint)(policy, strategy);
|
|
1355
|
+
const cacheKey = key;
|
|
1356
|
+
let registry = coordinators.get(connector);
|
|
1357
|
+
if (!registry) {
|
|
1358
|
+
registry = /* @__PURE__ */ new Map();
|
|
1359
|
+
coordinators.set(connector, registry);
|
|
1360
|
+
}
|
|
1361
|
+
const existing = registry.get(cacheKey);
|
|
1362
|
+
if (existing) {
|
|
1363
|
+
if (existing.fingerprint !== fingerprint3)
|
|
1364
|
+
throw new Error("Conflicting portfolio configuration for the same scope");
|
|
1365
|
+
return existing.coordinator;
|
|
1366
|
+
}
|
|
1367
|
+
const coordinator = createPortfolioCoordinator({
|
|
1368
|
+
policy,
|
|
1369
|
+
strategy,
|
|
1370
|
+
store: replay ? createMemoryPortfolioStore() : persistentStore(key),
|
|
1371
|
+
execution: {
|
|
1372
|
+
async confirmClosed(intent, asOf) {
|
|
1373
|
+
if (replay) return;
|
|
1374
|
+
const position = intent.position;
|
|
1375
|
+
await markRuntimeTradeClosed({
|
|
1376
|
+
...binding,
|
|
1377
|
+
strategy: position.strategy,
|
|
1378
|
+
expectedOrderId: position.id,
|
|
1379
|
+
symbol: position.symbol,
|
|
1380
|
+
exitPrice: position.markPrice,
|
|
1381
|
+
exitTimestamp: asOf,
|
|
1382
|
+
exitType: "exit"
|
|
1383
|
+
});
|
|
1384
|
+
},
|
|
1385
|
+
observe: (asOf, reservations) => replay ? connector.getPortfolioObservation(asOf, reservations) : observeRuntimePortfolio(connector, binding, asOf, reservations),
|
|
1386
|
+
async close(position, intent, asOf) {
|
|
1387
|
+
if (!replay) {
|
|
1388
|
+
const trade = await getActiveRuntimeTrade({
|
|
1389
|
+
...binding,
|
|
1390
|
+
symbol: position.symbol
|
|
1391
|
+
});
|
|
1392
|
+
if (!trade || trade.orderId !== position.id)
|
|
1393
|
+
throw new Error("Portfolio close ownership changed");
|
|
1394
|
+
}
|
|
1395
|
+
await connector.closePosition({
|
|
1396
|
+
symbol: position.symbol,
|
|
1397
|
+
direction: position.direction,
|
|
1398
|
+
price: position.markPrice,
|
|
1399
|
+
timestamp: asOf,
|
|
1400
|
+
signal: {
|
|
1401
|
+
signalId: `${position.id}:${intent.reason}`,
|
|
1402
|
+
strategy: position.strategy,
|
|
1403
|
+
symbol: position.symbol,
|
|
1404
|
+
direction: position.direction,
|
|
1405
|
+
interval: "1",
|
|
1406
|
+
timestamp: asOf,
|
|
1407
|
+
prices: {
|
|
1408
|
+
riskRatio: 0,
|
|
1409
|
+
currentPrice: position.markPrice,
|
|
1410
|
+
takeProfitPrice: position.markPrice,
|
|
1411
|
+
stopLossPrice: position.markPrice
|
|
1412
|
+
},
|
|
1413
|
+
figures: {},
|
|
1414
|
+
indicators: {},
|
|
1415
|
+
additionalIndicators: { portfolioExitReason: intent.reason }
|
|
1416
|
+
}
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
});
|
|
1421
|
+
registry.set(cacheKey, { fingerprint: fingerprint3, coordinator });
|
|
1422
|
+
return coordinator;
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
// src/strategyHelpers/tickerProfiles.ts
|
|
1426
|
+
var import_tickerProfiles = require("@tradejs/infra/tickerProfiles");
|
|
1427
|
+
var import_tickerProfiles2 = require("@tradejs/core/tickerProfiles");
|
|
1428
|
+
|
|
1429
|
+
// src/tradejsConfig.ts
|
|
1430
|
+
var import_fs = __toESM(require("fs"));
|
|
1431
|
+
var import_path = __toESM(require("path"));
|
|
1432
|
+
var import_url = require("url");
|
|
1433
|
+
var import_config = require("@tradejs/core/config");
|
|
1434
|
+
var import_logger2 = require("@tradejs/infra/logger");
|
|
1435
|
+
var CONFIG_FILE_NAMES = [
|
|
1436
|
+
"tradejs.config.ts",
|
|
1437
|
+
"tradejs.config.mts",
|
|
1438
|
+
"tradejs.config.js",
|
|
1439
|
+
"tradejs.config.mjs",
|
|
1440
|
+
"tradejs.config.cjs"
|
|
1441
|
+
];
|
|
1442
|
+
var TS_MODULE_RE = /\.(cts|mts|ts)$/i;
|
|
1443
|
+
var cachedByCwd = /* @__PURE__ */ new Map();
|
|
1444
|
+
var announcedConfigFile = /* @__PURE__ */ new Set();
|
|
1445
|
+
var tsNodeRegistered = false;
|
|
1446
|
+
var tsconfigPathsRegisteredByCwd = /* @__PURE__ */ new Set();
|
|
1447
|
+
var tsconfigPathMatchersByCwd = /* @__PURE__ */ new Map();
|
|
1448
|
+
var getTradejsProjectCwd = (cwd) => {
|
|
1449
|
+
const explicit = String(cwd ?? "").trim();
|
|
1450
|
+
if (explicit) {
|
|
1451
|
+
return import_path.default.resolve(explicit);
|
|
1452
|
+
}
|
|
1453
|
+
const fromEnv = String(process.env.PROJECT_CWD || "").trim();
|
|
1454
|
+
if (fromEnv) {
|
|
1455
|
+
return import_path.default.resolve(fromEnv);
|
|
1456
|
+
}
|
|
1457
|
+
return process.cwd();
|
|
1484
1458
|
};
|
|
1485
|
-
var
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
const targetVsBtc = toRecord(relative?.targetVsBtc);
|
|
1489
|
-
if (!targetVsBtc) {
|
|
1490
|
-
return {
|
|
1491
|
-
source: null,
|
|
1492
|
-
available: false,
|
|
1493
|
-
ratioReturn1h: null,
|
|
1494
|
-
ratioReturn4h: null,
|
|
1495
|
-
ratioReturn24h: null,
|
|
1496
|
-
alphaVsBtc1h: null,
|
|
1497
|
-
alphaVsBtc4h: null,
|
|
1498
|
-
alphaVsBtc24h: null,
|
|
1499
|
-
betaToBtc20: null,
|
|
1500
|
-
correlationToBtc20: null,
|
|
1501
|
-
ratioTrend: null
|
|
1502
|
-
};
|
|
1459
|
+
var normalizeConfig = (rawConfig) => {
|
|
1460
|
+
if (!rawConfig || typeof rawConfig !== "object") {
|
|
1461
|
+
return {};
|
|
1503
1462
|
}
|
|
1463
|
+
const config = rawConfig;
|
|
1464
|
+
const strategies2 = Array.isArray(config.strategies) ? config.strategies.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1465
|
+
const indicators = Array.isArray(config.indicators) ? config.indicators.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1466
|
+
const connectors = Array.isArray(config.connectors) ? config.connectors.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1467
|
+
const hooks = (0, import_config.normalizeTradejsConfigHooks)(
|
|
1468
|
+
config.hooks
|
|
1469
|
+
);
|
|
1470
|
+
const runtime = config.runtime && typeof config.runtime === "object" && !Array.isArray(config.runtime) ? config.runtime : void 0;
|
|
1504
1471
|
return {
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
alphaVsBtc1h: toFiniteNumber(targetVsBtc.alphaVsBtc1h),
|
|
1511
|
-
alphaVsBtc4h: toFiniteNumber(targetVsBtc.alphaVsBtc4h),
|
|
1512
|
-
alphaVsBtc24h: toFiniteNumber(targetVsBtc.alphaVsBtc24h),
|
|
1513
|
-
betaToBtc20: toFiniteNumber(targetVsBtc.betaToBtc20),
|
|
1514
|
-
correlationToBtc20: toFiniteNumber(targetVsBtc.correlationToBtc20),
|
|
1515
|
-
ratioTrend: typeof targetVsBtc.ratioTrend === "string" ? targetVsBtc.ratioTrend : null
|
|
1472
|
+
strategies: strategies2,
|
|
1473
|
+
indicators,
|
|
1474
|
+
connectors,
|
|
1475
|
+
...hooks ? { hooks } : {},
|
|
1476
|
+
...runtime ? { runtime } : {}
|
|
1516
1477
|
};
|
|
1517
1478
|
};
|
|
1518
|
-
var
|
|
1519
|
-
const
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
if (!btcAltRegime) {
|
|
1523
|
-
return {
|
|
1524
|
-
source: null,
|
|
1525
|
-
available: false,
|
|
1526
|
-
universe: null,
|
|
1527
|
-
interval: null,
|
|
1528
|
-
stale: null,
|
|
1529
|
-
regime: null,
|
|
1530
|
-
btcReturn24h: null,
|
|
1531
|
-
altBasketReturn24h: null,
|
|
1532
|
-
btcVsAltReturn24h: null,
|
|
1533
|
-
btcTurnoverShare24h: null,
|
|
1534
|
-
btcTurnoverShareChange24h: null,
|
|
1535
|
-
altVolToBtcVol24h: null,
|
|
1536
|
-
altDispersion24h: null
|
|
1537
|
-
};
|
|
1479
|
+
var getNodeCreateRequire = () => {
|
|
1480
|
+
const builtinModule = process.getBuiltinModule?.("module");
|
|
1481
|
+
if (typeof builtinModule?.createRequire === "function") {
|
|
1482
|
+
return builtinModule.createRequire;
|
|
1538
1483
|
}
|
|
1539
|
-
|
|
1540
|
-
source: String(btcAltRegime.source ?? ""),
|
|
1541
|
-
available: true,
|
|
1542
|
-
universe: String(btcAltRegime.universe ?? ""),
|
|
1543
|
-
interval: String(btcAltRegime.interval ?? ""),
|
|
1544
|
-
stale: typeof btcAltRegime.stale === "boolean" ? btcAltRegime.stale : null,
|
|
1545
|
-
regime: typeof btcAltRegime.regime === "string" ? btcAltRegime.regime : null,
|
|
1546
|
-
btcReturn24h: toFiniteNumber(btcAltRegime.btcReturn24h),
|
|
1547
|
-
altBasketReturn24h: toFiniteNumber(btcAltRegime.altBasketReturn24h),
|
|
1548
|
-
btcVsAltReturn24h: toFiniteNumber(btcAltRegime.btcVsAltReturn24h),
|
|
1549
|
-
btcTurnoverShare24h: toFiniteNumber(btcAltRegime.btcTurnoverShare24h),
|
|
1550
|
-
btcTurnoverShareChange24h: toFiniteNumber(
|
|
1551
|
-
btcAltRegime.btcTurnoverShareChange24h
|
|
1552
|
-
),
|
|
1553
|
-
altVolToBtcVol24h: toFiniteNumber(btcAltRegime.altVolToBtcVol24h),
|
|
1554
|
-
altDispersion24h: toFiniteNumber(btcAltRegime.altDispersion24h)
|
|
1555
|
-
};
|
|
1484
|
+
throw new TypeError("module.createRequire is not available");
|
|
1556
1485
|
};
|
|
1557
|
-
var
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
if (!cmcGlobal) {
|
|
1562
|
-
return {
|
|
1563
|
-
source: null,
|
|
1564
|
-
available: false,
|
|
1565
|
-
interval: null,
|
|
1566
|
-
asOfTs: null,
|
|
1567
|
-
stale: null,
|
|
1568
|
-
totalMarketCapUsd: null,
|
|
1569
|
-
totalVolumeUsd: null,
|
|
1570
|
-
totalVolumeReportedUsd: null,
|
|
1571
|
-
altMarketCapUsd: null,
|
|
1572
|
-
altVolumeUsd: null,
|
|
1573
|
-
altVolumeReportedUsd: null,
|
|
1574
|
-
btcDominancePct: null,
|
|
1575
|
-
ethDominancePct: null,
|
|
1576
|
-
btcDominanceChange24hPct: null,
|
|
1577
|
-
ethDominanceChange24hPct: null,
|
|
1578
|
-
altMarketCapChange24hPct: null,
|
|
1579
|
-
altVolumeChange24hPct: null,
|
|
1580
|
-
activeCryptocurrencies: null,
|
|
1581
|
-
activeExchanges: null,
|
|
1582
|
-
activeMarketPairs: null,
|
|
1583
|
-
altLiquidityRegime: null
|
|
1584
|
-
};
|
|
1486
|
+
var getRequireFn = (cwd = getTradejsProjectCwd()) => getNodeCreateRequire()(import_path.default.join(import_path.default.resolve(cwd), "__tradejs_loader__.js"));
|
|
1487
|
+
var ensureTsNodeRegistered = async () => {
|
|
1488
|
+
if (tsNodeRegistered) {
|
|
1489
|
+
return;
|
|
1585
1490
|
}
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
altVolumeUsd: toFiniteNumber(cmcGlobal.altVolumeUsd),
|
|
1597
|
-
altVolumeReportedUsd: toFiniteNumber(cmcGlobal.altVolumeReportedUsd),
|
|
1598
|
-
btcDominancePct: toFiniteNumber(cmcGlobal.btcDominancePct),
|
|
1599
|
-
ethDominancePct: toFiniteNumber(cmcGlobal.ethDominancePct),
|
|
1600
|
-
btcDominanceChange24hPct: toFiniteNumber(
|
|
1601
|
-
cmcGlobal.btcDominanceChange24hPct
|
|
1602
|
-
),
|
|
1603
|
-
ethDominanceChange24hPct: toFiniteNumber(
|
|
1604
|
-
cmcGlobal.ethDominanceChange24hPct
|
|
1605
|
-
),
|
|
1606
|
-
altMarketCapChange24hPct: toFiniteNumber(
|
|
1607
|
-
cmcGlobal.altMarketCapChange24hPct
|
|
1608
|
-
),
|
|
1609
|
-
altVolumeChange24hPct: toFiniteNumber(cmcGlobal.altVolumeChange24hPct),
|
|
1610
|
-
activeCryptocurrencies: toFiniteNumber(cmcGlobal.activeCryptocurrencies),
|
|
1611
|
-
activeExchanges: toFiniteNumber(cmcGlobal.activeExchanges),
|
|
1612
|
-
activeMarketPairs: toFiniteNumber(cmcGlobal.activeMarketPairs),
|
|
1613
|
-
altLiquidityRegime: typeof cmcGlobal.altLiquidityRegime === "string" ? cmcGlobal.altLiquidityRegime : null
|
|
1614
|
-
};
|
|
1491
|
+
const tsNodeModule = await import("ts-node");
|
|
1492
|
+
const tsNode = tsNodeModule.default ?? tsNodeModule;
|
|
1493
|
+
tsNode.register?.({
|
|
1494
|
+
transpileOnly: true,
|
|
1495
|
+
compilerOptions: {
|
|
1496
|
+
module: "Node16",
|
|
1497
|
+
moduleResolution: "node16"
|
|
1498
|
+
}
|
|
1499
|
+
});
|
|
1500
|
+
tsNodeRegistered = true;
|
|
1615
1501
|
};
|
|
1616
|
-
var
|
|
1617
|
-
const
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1502
|
+
var ensureTsconfigPathsRegistered = async (cwd = getTradejsProjectCwd()) => {
|
|
1503
|
+
const projectRoot = getTradejsProjectCwd(cwd);
|
|
1504
|
+
if (tsconfigPathsRegisteredByCwd.has(projectRoot)) {
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
const tsconfigPathsModule = await import("tsconfig-paths");
|
|
1508
|
+
const loadConfig = tsconfigPathsModule.loadConfig;
|
|
1509
|
+
const register = tsconfigPathsModule.register;
|
|
1510
|
+
if (typeof loadConfig !== "function" || typeof register !== "function") {
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
const loadedConfig = loadConfig(projectRoot);
|
|
1514
|
+
if (loadedConfig.resultType !== "success") {
|
|
1515
|
+
return;
|
|
1516
|
+
}
|
|
1517
|
+
register({
|
|
1518
|
+
baseUrl: loadedConfig.absoluteBaseUrl,
|
|
1519
|
+
paths: loadedConfig.paths,
|
|
1520
|
+
addMatchAll: false
|
|
1521
|
+
});
|
|
1522
|
+
tsconfigPathsRegisteredByCwd.add(projectRoot);
|
|
1523
|
+
};
|
|
1524
|
+
var resolveTsconfigPathModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
1525
|
+
const projectRoot = getTradejsProjectCwd(cwd);
|
|
1526
|
+
const cachedMatcher = tsconfigPathMatchersByCwd.get(projectRoot);
|
|
1527
|
+
if (cachedMatcher) {
|
|
1528
|
+
const resolved2 = cachedMatcher(moduleName);
|
|
1529
|
+
return resolved2 || null;
|
|
1530
|
+
}
|
|
1531
|
+
const tsconfigPathsModule = await import("tsconfig-paths");
|
|
1532
|
+
const loadConfig = tsconfigPathsModule.loadConfig;
|
|
1533
|
+
const createMatchPath = tsconfigPathsModule.createMatchPath;
|
|
1534
|
+
if (typeof loadConfig !== "function" || typeof createMatchPath !== "function") {
|
|
1535
|
+
return null;
|
|
1536
|
+
}
|
|
1537
|
+
const loadedConfig = loadConfig(projectRoot);
|
|
1538
|
+
if (loadedConfig.resultType !== "success") {
|
|
1539
|
+
return null;
|
|
1540
|
+
}
|
|
1541
|
+
const matchPath = createMatchPath(
|
|
1542
|
+
loadedConfig.absoluteBaseUrl,
|
|
1543
|
+
loadedConfig.paths
|
|
1544
|
+
);
|
|
1545
|
+
const matcher = (requestedModule) => matchPath(requestedModule, void 0, import_fs.default.existsSync, [
|
|
1546
|
+
".ts",
|
|
1547
|
+
".tsx",
|
|
1548
|
+
".mts",
|
|
1549
|
+
".cts",
|
|
1550
|
+
".js",
|
|
1551
|
+
".jsx",
|
|
1552
|
+
".mjs",
|
|
1553
|
+
".cjs",
|
|
1554
|
+
".json"
|
|
1555
|
+
]) || "";
|
|
1556
|
+
tsconfigPathMatchersByCwd.set(projectRoot, matcher);
|
|
1557
|
+
const resolved = matcher(moduleName);
|
|
1558
|
+
return resolved || null;
|
|
1559
|
+
};
|
|
1560
|
+
var toImportSpecifier = (moduleName) => {
|
|
1561
|
+
if (moduleName.startsWith("file://")) {
|
|
1562
|
+
return moduleName;
|
|
1563
|
+
}
|
|
1564
|
+
if (import_path.default.isAbsolute(moduleName)) {
|
|
1565
|
+
return (0, import_url.pathToFileURL)(moduleName).href;
|
|
1566
|
+
}
|
|
1567
|
+
return moduleName;
|
|
1568
|
+
};
|
|
1569
|
+
var isTsModulePath = (moduleName) => TS_MODULE_RE.test(moduleName.split("?")[0]);
|
|
1570
|
+
var isRelativeModulePath = (moduleName) => moduleName.startsWith("./") || moduleName.startsWith("../");
|
|
1571
|
+
var isBareModuleSpecifier = (moduleName) => {
|
|
1572
|
+
const normalized = String(moduleName ?? "").trim();
|
|
1573
|
+
if (!normalized) {
|
|
1574
|
+
return false;
|
|
1575
|
+
}
|
|
1576
|
+
if (normalized.startsWith("file://") || import_path.default.isAbsolute(normalized) || isRelativeModulePath(normalized)) {
|
|
1577
|
+
return false;
|
|
1578
|
+
}
|
|
1579
|
+
return true;
|
|
1580
|
+
};
|
|
1581
|
+
var importConfigFile = async (configFilePath) => {
|
|
1582
|
+
const ext = import_path.default.extname(configFilePath).toLowerCase();
|
|
1583
|
+
const configFileUrl = `${toImportSpecifier(configFilePath)}?t=${Date.now()}`;
|
|
1584
|
+
if (ext === ".ts" || ext === ".mts") {
|
|
1585
|
+
const requireFn = getRequireFn(import_path.default.dirname(configFilePath));
|
|
1586
|
+
await ensureTsNodeRegistered();
|
|
1587
|
+
await ensureTsconfigPathsRegistered(import_path.default.dirname(configFilePath));
|
|
1588
|
+
return requireFn(configFilePath);
|
|
1589
|
+
}
|
|
1590
|
+
return import(
|
|
1591
|
+
/* webpackIgnore: true */
|
|
1592
|
+
configFileUrl
|
|
1593
|
+
);
|
|
1594
|
+
};
|
|
1595
|
+
var importTradejsModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
1596
|
+
const normalized = String(moduleName ?? "").trim();
|
|
1597
|
+
if (!normalized) {
|
|
1598
|
+
return {};
|
|
1599
|
+
}
|
|
1600
|
+
let modulePath = normalized;
|
|
1601
|
+
if (normalized.startsWith("file://")) {
|
|
1602
|
+
try {
|
|
1603
|
+
modulePath = (0, import_url.fileURLToPath)(normalized);
|
|
1604
|
+
} catch {
|
|
1605
|
+
modulePath = normalized;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
const requireFn = getRequireFn(
|
|
1609
|
+
import_path.default.isAbsolute(modulePath) ? import_path.default.dirname(modulePath) : cwd
|
|
1610
|
+
);
|
|
1611
|
+
if (isTsModulePath(modulePath)) {
|
|
1612
|
+
await ensureTsNodeRegistered();
|
|
1613
|
+
await ensureTsconfigPathsRegistered(cwd);
|
|
1614
|
+
return requireFn(modulePath);
|
|
1615
|
+
}
|
|
1616
|
+
if (isBareModuleSpecifier(normalized)) {
|
|
1617
|
+
await ensureTsconfigPathsRegistered(cwd);
|
|
1618
|
+
try {
|
|
1619
|
+
return requireFn(normalized);
|
|
1620
|
+
} catch (error) {
|
|
1621
|
+
const resolvedByTsconfig = await resolveTsconfigPathModule(
|
|
1622
|
+
normalized,
|
|
1623
|
+
cwd
|
|
1624
|
+
);
|
|
1625
|
+
if (resolvedByTsconfig && resolvedByTsconfig !== normalized) {
|
|
1626
|
+
return requireFn(resolvedByTsconfig);
|
|
1627
|
+
}
|
|
1628
|
+
throw error;
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
try {
|
|
1632
|
+
return await import(
|
|
1633
|
+
/* webpackIgnore: true */
|
|
1634
|
+
toImportSpecifier(normalized)
|
|
1635
|
+
);
|
|
1636
|
+
} catch (error) {
|
|
1637
|
+
if (isTsModulePath(modulePath)) {
|
|
1638
|
+
await ensureTsNodeRegistered();
|
|
1639
|
+
await ensureTsconfigPathsRegistered(cwd);
|
|
1640
|
+
return requireFn(modulePath);
|
|
1641
|
+
}
|
|
1642
|
+
throw error;
|
|
1643
|
+
}
|
|
1644
|
+
};
|
|
1645
|
+
var resolveExportedConfig = (moduleExports) => {
|
|
1646
|
+
const candidate = moduleExports && typeof moduleExports === "object" && "default" in moduleExports ? moduleExports.default : moduleExports;
|
|
1647
|
+
return normalizeConfig(candidate);
|
|
1648
|
+
};
|
|
1649
|
+
var findConfigFilePath = (cwd) => {
|
|
1650
|
+
let currentDir = import_path.default.resolve(cwd);
|
|
1651
|
+
while (true) {
|
|
1652
|
+
for (const fileName of CONFIG_FILE_NAMES) {
|
|
1653
|
+
const fullPath = import_path.default.join(currentDir, fileName);
|
|
1654
|
+
if (import_fs.default.existsSync(fullPath) && import_fs.default.statSync(fullPath).isFile()) {
|
|
1655
|
+
return fullPath;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
const parentDir = import_path.default.dirname(currentDir);
|
|
1659
|
+
if (parentDir === currentDir) {
|
|
1660
|
+
return null;
|
|
1661
|
+
}
|
|
1662
|
+
currentDir = parentDir;
|
|
1663
|
+
}
|
|
1664
|
+
};
|
|
1665
|
+
var resolvePluginModuleSpecifier = (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
1666
|
+
const normalized = String(moduleName ?? "").trim();
|
|
1667
|
+
if (!normalized) {
|
|
1668
|
+
return "";
|
|
1669
|
+
}
|
|
1670
|
+
if (normalized.startsWith("file://")) {
|
|
1671
|
+
try {
|
|
1672
|
+
return (0, import_url.fileURLToPath)(normalized);
|
|
1673
|
+
} catch {
|
|
1674
|
+
return normalized;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
if (import_path.default.isAbsolute(normalized)) {
|
|
1678
|
+
return normalized;
|
|
1679
|
+
}
|
|
1680
|
+
if (isRelativeModulePath(normalized)) {
|
|
1681
|
+
return import_path.default.resolve(cwd, normalized);
|
|
1682
|
+
}
|
|
1683
|
+
return normalized;
|
|
1684
|
+
};
|
|
1685
|
+
var loadTradejsConfig = async (cwd = getTradejsProjectCwd()) => {
|
|
1686
|
+
const cached = cachedByCwd.get(cwd);
|
|
1687
|
+
if (cached) {
|
|
1688
|
+
return cached;
|
|
1689
|
+
}
|
|
1690
|
+
const configFilePath = findConfigFilePath(cwd);
|
|
1691
|
+
if (!configFilePath) {
|
|
1692
|
+
cachedByCwd.set(cwd, {});
|
|
1693
|
+
return {};
|
|
1694
|
+
}
|
|
1695
|
+
try {
|
|
1696
|
+
const moduleExports = await importConfigFile(configFilePath);
|
|
1697
|
+
const config = resolveExportedConfig(moduleExports);
|
|
1698
|
+
cachedByCwd.set(cwd, config);
|
|
1699
|
+
if (!announcedConfigFile.has(configFilePath)) {
|
|
1700
|
+
announcedConfigFile.add(configFilePath);
|
|
1701
|
+
import_logger2.logger.log("debug", "Loaded TradeJS config: %s", configFilePath);
|
|
1702
|
+
}
|
|
1703
|
+
return config;
|
|
1704
|
+
} catch (error) {
|
|
1705
|
+
import_logger2.logger.log(
|
|
1706
|
+
"warn",
|
|
1707
|
+
"Failed to load TradeJS config from %s: %s",
|
|
1708
|
+
configFilePath,
|
|
1709
|
+
String(error)
|
|
1710
|
+
);
|
|
1711
|
+
cachedByCwd.set(cwd, {});
|
|
1712
|
+
return {};
|
|
1638
1713
|
}
|
|
1639
|
-
return {
|
|
1640
|
-
source: String(cmcReferenceAssets.source ?? ""),
|
|
1641
|
-
available: true,
|
|
1642
|
-
interval: typeof cmcReferenceAssets.interval === "string" ? cmcReferenceAssets.interval : null,
|
|
1643
|
-
asOfTs: toFiniteNumber(cmcReferenceAssets.asOfTs),
|
|
1644
|
-
stale: typeof cmcReferenceAssets.stale === "boolean" ? cmcReferenceAssets.stale : null,
|
|
1645
|
-
btcMarketCapUsd: toFiniteNumber(cmcReferenceAssets.btcMarketCapUsd),
|
|
1646
|
-
ethMarketCapUsd: toFiniteNumber(cmcReferenceAssets.ethMarketCapUsd),
|
|
1647
|
-
btcVolumeUsd: toFiniteNumber(cmcReferenceAssets.btcVolumeUsd),
|
|
1648
|
-
ethVolumeUsd: toFiniteNumber(cmcReferenceAssets.ethVolumeUsd),
|
|
1649
|
-
btcVolumeToMarketCap: toFiniteNumber(
|
|
1650
|
-
cmcReferenceAssets.btcVolumeToMarketCap
|
|
1651
|
-
),
|
|
1652
|
-
ethVolumeToMarketCap: toFiniteNumber(
|
|
1653
|
-
cmcReferenceAssets.ethVolumeToMarketCap
|
|
1654
|
-
),
|
|
1655
|
-
ethBtcMarketCapRatio: toFiniteNumber(
|
|
1656
|
-
cmcReferenceAssets.ethBtcMarketCapRatio
|
|
1657
|
-
),
|
|
1658
|
-
ethBtcMarketCapRatioChange24hPct: toFiniteNumber(
|
|
1659
|
-
cmcReferenceAssets.ethBtcMarketCapRatioChange24hPct
|
|
1660
|
-
),
|
|
1661
|
-
ethVsBtcVolumeRatio: toFiniteNumber(cmcReferenceAssets.ethVsBtcVolumeRatio),
|
|
1662
|
-
referenceLiquidityRegime: typeof cmcReferenceAssets.referenceLiquidityRegime === "string" ? cmcReferenceAssets.referenceLiquidityRegime : null
|
|
1663
|
-
};
|
|
1664
1714
|
};
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1715
|
+
|
|
1716
|
+
// src/strategy/manifests.ts
|
|
1717
|
+
var import_indicators = require("@tradejs/core/indicators");
|
|
1718
|
+
var SHARED_STRATEGY_REGISTRY_KEY = "__tradejsNodeSharedStrategyRegistryV1__";
|
|
1719
|
+
var sharedRegistryScope = globalThis;
|
|
1720
|
+
var sharedStrategyRegistry = sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] ?? (sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] = {
|
|
1721
|
+
registryStateByProjectRoot: /* @__PURE__ */ new Map()
|
|
1722
|
+
});
|
|
1723
|
+
var createStrategyRegistryState = () => ({
|
|
1724
|
+
strategyCreators: /* @__PURE__ */ new Map(),
|
|
1725
|
+
strategyManifestsMap: /* @__PURE__ */ new Map(),
|
|
1726
|
+
strategyEntriesMap: /* @__PURE__ */ new Map(),
|
|
1727
|
+
strategySourcesMap: /* @__PURE__ */ new Map(),
|
|
1728
|
+
strategyModuleResolutions: /* @__PURE__ */ new Map(),
|
|
1729
|
+
pluginsLoadPromise: null
|
|
1730
|
+
});
|
|
1731
|
+
var registryStateByProjectRoot = sharedStrategyRegistry.registryStateByProjectRoot;
|
|
1732
|
+
var getStrategyRegistryState = (cwd = getTradejsProjectCwd()) => {
|
|
1733
|
+
const projectRoot = getTradejsProjectCwd(cwd);
|
|
1734
|
+
let state = registryStateByProjectRoot.get(projectRoot);
|
|
1735
|
+
if (!state) {
|
|
1736
|
+
state = createStrategyRegistryState();
|
|
1737
|
+
registryStateByProjectRoot.set(projectRoot, state);
|
|
1684
1738
|
}
|
|
1685
1739
|
return {
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
interval: typeof cmcExchangeLiquidity.interval === "string" ? cmcExchangeLiquidity.interval : null,
|
|
1689
|
-
asOfTs: toFiniteNumber(cmcExchangeLiquidity.asOfTs),
|
|
1690
|
-
stale: typeof cmcExchangeLiquidity.stale === "boolean" ? cmcExchangeLiquidity.stale : null,
|
|
1691
|
-
exchangesCount: toFiniteNumber(cmcExchangeLiquidity.exchangesCount),
|
|
1692
|
-
totalVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.totalVolumeUsd),
|
|
1693
|
-
totalVolumeChange24hPct: toFiniteNumber(
|
|
1694
|
-
cmcExchangeLiquidity.totalVolumeChange24hPct
|
|
1695
|
-
),
|
|
1696
|
-
binanceVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeUsd),
|
|
1697
|
-
binanceVolumeShare: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeShare),
|
|
1698
|
-
topExchangeVolumeShare: toFiniteNumber(
|
|
1699
|
-
cmcExchangeLiquidity.topExchangeVolumeShare
|
|
1700
|
-
),
|
|
1701
|
-
liquidityRegime: typeof cmcExchangeLiquidity.liquidityRegime === "string" ? cmcExchangeLiquidity.liquidityRegime : null
|
|
1740
|
+
projectRoot,
|
|
1741
|
+
state
|
|
1702
1742
|
};
|
|
1703
1743
|
};
|
|
1704
|
-
var
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
return {
|
|
1710
|
-
source: null,
|
|
1711
|
-
available: false,
|
|
1712
|
-
interval: null,
|
|
1713
|
-
asOfTs: null,
|
|
1714
|
-
stale: null,
|
|
1715
|
-
value: null,
|
|
1716
|
-
valueChange24h: null,
|
|
1717
|
-
valueChange7d: null,
|
|
1718
|
-
classification: null,
|
|
1719
|
-
sentimentRegime: null
|
|
1720
|
-
};
|
|
1721
|
-
}
|
|
1744
|
+
var toUniqueModules = (modules = []) => [
|
|
1745
|
+
...new Set(modules.map((moduleName) => moduleName.trim()).filter(Boolean))
|
|
1746
|
+
];
|
|
1747
|
+
var getConfiguredPluginModuleNames = async (cwd = getTradejsProjectCwd()) => {
|
|
1748
|
+
const config = await loadTradejsConfig(cwd);
|
|
1722
1749
|
return {
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
interval: typeof cmcFearGreed.interval === "string" ? cmcFearGreed.interval : null,
|
|
1726
|
-
asOfTs: toFiniteNumber(cmcFearGreed.asOfTs),
|
|
1727
|
-
stale: typeof cmcFearGreed.stale === "boolean" ? cmcFearGreed.stale : null,
|
|
1728
|
-
value: toFiniteNumber(cmcFearGreed.value),
|
|
1729
|
-
valueChange24h: toFiniteNumber(cmcFearGreed.valueChange24h),
|
|
1730
|
-
valueChange7d: toFiniteNumber(cmcFearGreed.valueChange7d),
|
|
1731
|
-
classification: typeof cmcFearGreed.classification === "string" ? cmcFearGreed.classification : null,
|
|
1732
|
-
sentimentRegime: typeof cmcFearGreed.sentimentRegime === "string" ? cmcFearGreed.sentimentRegime : null
|
|
1750
|
+
strategyModules: toUniqueModules(config.strategies),
|
|
1751
|
+
indicatorModules: toUniqueModules(config.indicators)
|
|
1733
1752
|
};
|
|
1734
1753
|
};
|
|
1735
|
-
var
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1754
|
+
var extractModuleEntries = (moduleExport, key) => {
|
|
1755
|
+
if (!moduleExport || typeof moduleExport !== "object") {
|
|
1756
|
+
return null;
|
|
1757
|
+
}
|
|
1758
|
+
const candidate = moduleExport;
|
|
1759
|
+
if (Array.isArray(candidate[key])) {
|
|
1760
|
+
return candidate[key];
|
|
1761
|
+
}
|
|
1762
|
+
const defaultExport = candidate.default;
|
|
1763
|
+
if (defaultExport && Array.isArray(defaultExport[key])) {
|
|
1764
|
+
return defaultExport[key];
|
|
1765
|
+
}
|
|
1766
|
+
return null;
|
|
1767
|
+
};
|
|
1768
|
+
var extractStrategyPluginDefinition = (moduleExport) => {
|
|
1769
|
+
const strategyEntries = extractModuleEntries(
|
|
1770
|
+
moduleExport,
|
|
1771
|
+
"strategyEntries"
|
|
1772
|
+
);
|
|
1773
|
+
return strategyEntries ? { strategyEntries } : null;
|
|
1774
|
+
};
|
|
1775
|
+
var extractIndicatorPluginDefinition = (moduleExport) => {
|
|
1776
|
+
const indicatorEntries = extractModuleEntries(
|
|
1777
|
+
moduleExport,
|
|
1778
|
+
"indicatorEntries"
|
|
1779
|
+
);
|
|
1780
|
+
return indicatorEntries ? { indicatorEntries } : null;
|
|
1781
|
+
};
|
|
1782
|
+
var validateStrategyEntries = (moduleName, entries, state) => {
|
|
1783
|
+
const issues = [];
|
|
1784
|
+
const names = /* @__PURE__ */ new Set();
|
|
1785
|
+
entries.forEach((entry, index) => {
|
|
1786
|
+
const entryPath = `${moduleName}.strategyEntries[${index}]`;
|
|
1787
|
+
const strategyName = entry?.manifest?.name;
|
|
1788
|
+
if (typeof strategyName !== "string" || !strategyName.trim()) {
|
|
1789
|
+
issues.push(`${entryPath}: manifest.name is required`);
|
|
1790
|
+
} else if (names.has(strategyName) || state?.strategyEntriesMap.has(strategyName)) {
|
|
1791
|
+
issues.push(`${entryPath}: duplicate strategy ${strategyName}`);
|
|
1792
|
+
} else {
|
|
1793
|
+
names.add(strategyName);
|
|
1794
|
+
}
|
|
1795
|
+
if (!entry || typeof entry !== "object" || !entry.defaults || typeof entry.defaults !== "object" || Array.isArray(entry.defaults)) {
|
|
1796
|
+
issues.push(`${entryPath}: defaults must be an object`);
|
|
1797
|
+
}
|
|
1798
|
+
if (typeof entry?.parseConfig !== "function") {
|
|
1799
|
+
issues.push(`${entryPath}: parseConfig is required`);
|
|
1800
|
+
}
|
|
1801
|
+
if (typeof entry?.createCore !== "function") {
|
|
1802
|
+
issues.push(`${entryPath}: createCore is required`);
|
|
1803
|
+
}
|
|
1804
|
+
});
|
|
1805
|
+
return issues;
|
|
1806
|
+
};
|
|
1807
|
+
var registerEntries = (entries, source, state) => {
|
|
1808
|
+
for (const entry of entries) {
|
|
1809
|
+
const strategyName = entry.manifest.name;
|
|
1810
|
+
state.strategyManifestsMap.set(strategyName, entry.manifest);
|
|
1811
|
+
state.strategyEntriesMap.set(strategyName, entry);
|
|
1812
|
+
state.strategySourcesMap.set(strategyName, source);
|
|
1813
|
+
materializeStrategyCreator(strategyName, state);
|
|
1814
|
+
}
|
|
1815
|
+
};
|
|
1816
|
+
var materializeStrategyCreator = (strategyName, state) => {
|
|
1817
|
+
if (state.strategyCreators.has(strategyName) || !sharedStrategyRegistry.strategyRuntimeFactory) {
|
|
1818
|
+
return;
|
|
1819
|
+
}
|
|
1820
|
+
const entry = state.strategyEntriesMap.get(strategyName);
|
|
1821
|
+
if (!entry) return;
|
|
1822
|
+
state.strategyCreators.set(
|
|
1823
|
+
strategyName,
|
|
1824
|
+
sharedStrategyRegistry.strategyRuntimeFactory({
|
|
1825
|
+
strategyName,
|
|
1826
|
+
defaults: entry.defaults,
|
|
1827
|
+
createCore: entry.createCore,
|
|
1828
|
+
manifest: entry.manifest,
|
|
1829
|
+
detectorKey: entry.detectorKey,
|
|
1830
|
+
detectorNoSignalSkipReason: entry.detectorNoSignalSkipReason,
|
|
1831
|
+
resolveRegisteredManifest: (name) => state.strategyManifestsMap.get(name)
|
|
1832
|
+
})
|
|
1833
|
+
);
|
|
1834
|
+
};
|
|
1835
|
+
var setStrategyRuntimeFactory = (factory) => {
|
|
1836
|
+
sharedStrategyRegistry.strategyRuntimeFactory = factory;
|
|
1837
|
+
for (const state of registryStateByProjectRoot.values()) {
|
|
1838
|
+
for (const strategyName of state.strategyEntriesMap.keys()) {
|
|
1839
|
+
materializeStrategyCreator(strategyName, state);
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
};
|
|
1843
|
+
var importStrategyPluginModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
1844
|
+
if (typeof importTradejsModule === "function") {
|
|
1845
|
+
return importTradejsModule(moduleName, cwd);
|
|
1846
|
+
}
|
|
1847
|
+
return import(
|
|
1848
|
+
/* webpackIgnore: true */
|
|
1849
|
+
moduleName
|
|
1850
|
+
);
|
|
1851
|
+
};
|
|
1852
|
+
var resolveStrategyModuleEntry = async ({
|
|
1853
|
+
strategyName,
|
|
1854
|
+
moduleName,
|
|
1855
|
+
cwd
|
|
1856
|
+
}) => {
|
|
1857
|
+
const { projectRoot, state } = getStrategyRegistryState(cwd);
|
|
1858
|
+
const source = moduleName.trim();
|
|
1859
|
+
const resolvedModuleName = resolvePluginModuleSpecifier(source, projectRoot);
|
|
1860
|
+
const cacheKey = `${resolvedModuleName}\0${strategyName}`;
|
|
1861
|
+
const cached = state.strategyModuleResolutions.get(cacheKey);
|
|
1862
|
+
if (cached) return cached;
|
|
1863
|
+
const resolution = (async () => {
|
|
1864
|
+
const moduleExport = await importStrategyPluginModule(
|
|
1865
|
+
resolvedModuleName,
|
|
1866
|
+
projectRoot
|
|
1867
|
+
);
|
|
1868
|
+
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
1869
|
+
if (!pluginDefinition) {
|
|
1870
|
+
throw new Error(`${source}: export { strategyEntries } is missing`);
|
|
1871
|
+
}
|
|
1872
|
+
const issues = validateStrategyEntries(
|
|
1873
|
+
source,
|
|
1874
|
+
pluginDefinition.strategyEntries
|
|
1875
|
+
);
|
|
1876
|
+
if (issues.length > 0) {
|
|
1877
|
+
throw new Error(
|
|
1878
|
+
["Invalid TradeJS strategy module:", ...issues].join("\n")
|
|
1879
|
+
);
|
|
1880
|
+
}
|
|
1881
|
+
const entry = pluginDefinition.strategyEntries.find(
|
|
1882
|
+
(candidate) => candidate.manifest.name === strategyName
|
|
1883
|
+
);
|
|
1884
|
+
if (!entry) {
|
|
1885
|
+
throw new Error(`${source}: strategy ${strategyName} is missing`);
|
|
1886
|
+
}
|
|
1887
|
+
const factory = sharedStrategyRegistry.strategyRuntimeFactory;
|
|
1888
|
+
if (!factory) {
|
|
1889
|
+
throw new Error("Strategy runtime factory is not configured");
|
|
1890
|
+
}
|
|
1891
|
+
const manifestsByName = new Map(
|
|
1892
|
+
pluginDefinition.strategyEntries.map(
|
|
1893
|
+
(candidate) => [candidate.manifest.name, candidate.manifest]
|
|
1894
|
+
)
|
|
1895
|
+
);
|
|
1740
1896
|
return {
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
cmc20TopConstituentSymbol: null,
|
|
1753
|
-
cmc20TopConstituentWeightPct: null,
|
|
1754
|
-
cmc20ToCmc100Ratio: null,
|
|
1755
|
-
cmc20ToCmc100RatioChange24hPct: null,
|
|
1756
|
-
indexRegime: null
|
|
1897
|
+
entry,
|
|
1898
|
+
creator: factory({
|
|
1899
|
+
strategyName,
|
|
1900
|
+
defaults: entry.defaults,
|
|
1901
|
+
createCore: entry.createCore,
|
|
1902
|
+
manifest: entry.manifest,
|
|
1903
|
+
detectorKey: entry.detectorKey,
|
|
1904
|
+
detectorNoSignalSkipReason: entry.detectorNoSignalSkipReason,
|
|
1905
|
+
resolveRegisteredManifest: (name) => manifestsByName.get(name) ?? state.strategyManifestsMap.get(name)
|
|
1906
|
+
}),
|
|
1907
|
+
source
|
|
1757
1908
|
};
|
|
1909
|
+
})();
|
|
1910
|
+
state.strategyModuleResolutions.set(cacheKey, resolution);
|
|
1911
|
+
try {
|
|
1912
|
+
return await resolution;
|
|
1913
|
+
} catch (error) {
|
|
1914
|
+
state.strategyModuleResolutions.delete(cacheKey);
|
|
1915
|
+
throw error;
|
|
1758
1916
|
}
|
|
1759
|
-
return {
|
|
1760
|
-
source: String(cmcIndexes.source ?? ""),
|
|
1761
|
-
available: true,
|
|
1762
|
-
interval: typeof cmcIndexes.interval === "string" ? cmcIndexes.interval : null,
|
|
1763
|
-
asOfTs: toFiniteNumber(cmcIndexes.asOfTs),
|
|
1764
|
-
stale: typeof cmcIndexes.stale === "boolean" ? cmcIndexes.stale : null,
|
|
1765
|
-
cmc100Value: toFiniteNumber(cmcIndexes.cmc100Value),
|
|
1766
|
-
cmc100Change24hPct: toFiniteNumber(cmcIndexes.cmc100Change24hPct),
|
|
1767
|
-
cmc100TopConstituentSymbol: typeof cmcIndexes.cmc100TopConstituentSymbol === "string" ? cmcIndexes.cmc100TopConstituentSymbol : null,
|
|
1768
|
-
cmc100TopConstituentWeightPct: toFiniteNumber(
|
|
1769
|
-
cmcIndexes.cmc100TopConstituentWeightPct
|
|
1770
|
-
),
|
|
1771
|
-
cmc20Value: toFiniteNumber(cmcIndexes.cmc20Value),
|
|
1772
|
-
cmc20Change24hPct: toFiniteNumber(cmcIndexes.cmc20Change24hPct),
|
|
1773
|
-
cmc20TopConstituentSymbol: typeof cmcIndexes.cmc20TopConstituentSymbol === "string" ? cmcIndexes.cmc20TopConstituentSymbol : null,
|
|
1774
|
-
cmc20TopConstituentWeightPct: toFiniteNumber(
|
|
1775
|
-
cmcIndexes.cmc20TopConstituentWeightPct
|
|
1776
|
-
),
|
|
1777
|
-
cmc20ToCmc100Ratio: toFiniteNumber(cmcIndexes.cmc20ToCmc100Ratio),
|
|
1778
|
-
cmc20ToCmc100RatioChange24hPct: toFiniteNumber(
|
|
1779
|
-
cmcIndexes.cmc20ToCmc100RatioChange24hPct
|
|
1780
|
-
),
|
|
1781
|
-
indexRegime: typeof cmcIndexes.indexRegime === "string" ? cmcIndexes.indexRegime : null
|
|
1782
|
-
};
|
|
1783
1917
|
};
|
|
1784
|
-
var
|
|
1785
|
-
const
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1918
|
+
var ensureStrategyPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
|
|
1919
|
+
const { projectRoot, state } = getStrategyRegistryState(cwd);
|
|
1920
|
+
if (!state.pluginsLoadPromise) {
|
|
1921
|
+
(0, import_indicators.resetIndicatorRegistryCache)(projectRoot);
|
|
1922
|
+
state.pluginsLoadPromise = (async () => {
|
|
1923
|
+
const { strategyModules, indicatorModules } = await getConfiguredPluginModuleNames(projectRoot);
|
|
1924
|
+
const strategySet = new Set(strategyModules);
|
|
1925
|
+
const indicatorSet = new Set(indicatorModules);
|
|
1926
|
+
const pluginModuleNames = [
|
|
1927
|
+
.../* @__PURE__ */ new Set([...strategyModules, ...indicatorModules])
|
|
1928
|
+
];
|
|
1929
|
+
if (!pluginModuleNames.length) {
|
|
1930
|
+
return;
|
|
1931
|
+
}
|
|
1932
|
+
const issues = [];
|
|
1933
|
+
for (const moduleName of pluginModuleNames) {
|
|
1934
|
+
try {
|
|
1935
|
+
const resolvedModuleName = resolvePluginModuleSpecifier(
|
|
1936
|
+
moduleName,
|
|
1937
|
+
projectRoot
|
|
1938
|
+
);
|
|
1939
|
+
const moduleExport = await importStrategyPluginModule(
|
|
1940
|
+
resolvedModuleName,
|
|
1941
|
+
projectRoot
|
|
1942
|
+
);
|
|
1943
|
+
if (strategySet.has(moduleName)) {
|
|
1944
|
+
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
1945
|
+
if (!pluginDefinition) {
|
|
1946
|
+
issues.push(
|
|
1947
|
+
`${moduleName}: export { strategyEntries } is missing`
|
|
1948
|
+
);
|
|
1949
|
+
} else {
|
|
1950
|
+
const entryIssues = validateStrategyEntries(
|
|
1951
|
+
moduleName,
|
|
1952
|
+
pluginDefinition.strategyEntries,
|
|
1953
|
+
state
|
|
1954
|
+
);
|
|
1955
|
+
issues.push(...entryIssues);
|
|
1956
|
+
if (entryIssues.length === 0) {
|
|
1957
|
+
registerEntries(
|
|
1958
|
+
pluginDefinition.strategyEntries,
|
|
1959
|
+
moduleName,
|
|
1960
|
+
state
|
|
1961
|
+
);
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
if (indicatorSet.has(moduleName)) {
|
|
1966
|
+
const indicatorPluginDefinition = extractIndicatorPluginDefinition(moduleExport);
|
|
1967
|
+
if (!indicatorPluginDefinition) {
|
|
1968
|
+
issues.push(
|
|
1969
|
+
`${moduleName}: export { indicatorEntries } is missing`
|
|
1970
|
+
);
|
|
1971
|
+
} else {
|
|
1972
|
+
(0, import_indicators.registerIndicatorEntries)(
|
|
1973
|
+
indicatorPluginDefinition.indicatorEntries,
|
|
1974
|
+
moduleName,
|
|
1975
|
+
projectRoot
|
|
1976
|
+
);
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
if (!strategySet.has(moduleName) && !indicatorSet.has(moduleName)) {
|
|
1980
|
+
issues.push(`${moduleName}: plugin is not declared in config`);
|
|
1981
|
+
}
|
|
1982
|
+
} catch (error) {
|
|
1983
|
+
issues.push(`${moduleName}: failed to import: ${String(error)}`);
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
if (issues.length > 0) {
|
|
1987
|
+
throw new Error(
|
|
1988
|
+
["Invalid TradeJS plugin catalog:", ...issues].join("\n")
|
|
1989
|
+
);
|
|
1990
|
+
}
|
|
1991
|
+
})();
|
|
1800
1992
|
}
|
|
1801
|
-
|
|
1802
|
-
source: String(refs.source ?? ""),
|
|
1803
|
-
available: true,
|
|
1804
|
-
primaryReferenceSymbol,
|
|
1805
|
-
referenceSymbols: Array.isArray(refs.referenceSymbols) ? refs.referenceSymbols.map(String) : [],
|
|
1806
|
-
primaryTradeFlowBuyPressurePct: toFiniteNumber(
|
|
1807
|
-
primaryTradeFlow?.buyPressurePct
|
|
1808
|
-
),
|
|
1809
|
-
primaryTradeFlowStale: typeof primaryTradeFlow?.stale === "boolean" ? primaryTradeFlow.stale : null
|
|
1810
|
-
};
|
|
1993
|
+
await state.pluginsLoadPromise;
|
|
1811
1994
|
};
|
|
1812
|
-
var
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
participation: {
|
|
1817
|
-
trueDelta: buildTrueDeltaContextFromSignal(signal),
|
|
1818
|
-
tradeFlow: buildTradeFlowContextFromSignal(signal)
|
|
1819
|
-
},
|
|
1820
|
-
relative: {
|
|
1821
|
-
marketBreadth: buildMarketBreadthContextFromSignal(signal),
|
|
1822
|
-
marketBreadths: buildMarketBreadthsContextFromSignal(signal),
|
|
1823
|
-
targetVsBtc: buildTargetVsBtcContextFromSignal(signal),
|
|
1824
|
-
btcAltRegime: buildBtcAltRegimeContextFromSignal(signal),
|
|
1825
|
-
cmcGlobal: buildCmcGlobalContextFromSignal(signal),
|
|
1826
|
-
cmcReferenceAssets: buildCmcReferenceAssetsContextFromSignal(signal),
|
|
1827
|
-
cmcExchangeLiquidity: buildCmcExchangeLiquidityContextFromSignal(signal),
|
|
1828
|
-
cmcFearGreed: buildCmcFearGreedContextFromSignal(signal),
|
|
1829
|
-
cmcIndexes: buildCmcIndexesContextFromSignal(signal),
|
|
1830
|
-
referenceTradeFlow: buildReferenceTradeFlowContextFromSignal(signal)
|
|
1995
|
+
var getStrategyCreator = async (name, cwd = getTradejsProjectCwd(), moduleName) => {
|
|
1996
|
+
if (moduleName?.trim()) {
|
|
1997
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
1998
|
+
return (await resolveStrategyModuleEntry({ strategyName: name, moduleName, cwd })).creator;
|
|
1831
1999
|
}
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
var import_url = require("url");
|
|
1841
|
-
var import_config = require("@tradejs/core/config");
|
|
1842
|
-
var import_logger2 = require("@tradejs/infra/logger");
|
|
1843
|
-
var CONFIG_FILE_NAMES = [
|
|
1844
|
-
"tradejs.config.ts",
|
|
1845
|
-
"tradejs.config.mts",
|
|
1846
|
-
"tradejs.config.js",
|
|
1847
|
-
"tradejs.config.mjs",
|
|
1848
|
-
"tradejs.config.cjs"
|
|
1849
|
-
];
|
|
1850
|
-
var TS_MODULE_RE = /\.(cts|mts|ts)$/i;
|
|
1851
|
-
var cachedByCwd = /* @__PURE__ */ new Map();
|
|
1852
|
-
var announcedConfigFile = /* @__PURE__ */ new Set();
|
|
1853
|
-
var tsNodeRegistered = false;
|
|
1854
|
-
var tsconfigPathsRegisteredByCwd = /* @__PURE__ */ new Set();
|
|
1855
|
-
var tsconfigPathMatchersByCwd = /* @__PURE__ */ new Map();
|
|
1856
|
-
var getTradejsProjectCwd = (cwd) => {
|
|
1857
|
-
const explicit = String(cwd ?? "").trim();
|
|
1858
|
-
if (explicit) {
|
|
1859
|
-
return import_path.default.resolve(explicit);
|
|
2000
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
2001
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
2002
|
+
return state.strategyCreators.get(name);
|
|
2003
|
+
};
|
|
2004
|
+
var getStrategyEntry = async (name, cwd = getTradejsProjectCwd(), moduleName) => {
|
|
2005
|
+
if (moduleName?.trim()) {
|
|
2006
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
2007
|
+
return (await resolveStrategyModuleEntry({ strategyName: name, moduleName, cwd })).entry;
|
|
1860
2008
|
}
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
2009
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
2010
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
2011
|
+
return state.strategyEntriesMap.get(name);
|
|
2012
|
+
};
|
|
2013
|
+
var getStrategyPluginSource = async (name, cwd = getTradejsProjectCwd(), moduleName) => {
|
|
2014
|
+
if (moduleName?.trim()) {
|
|
2015
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
2016
|
+
return (await resolveStrategyModuleEntry({ strategyName: name, moduleName, cwd })).source;
|
|
1864
2017
|
}
|
|
1865
|
-
|
|
2018
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
2019
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
2020
|
+
return state.strategySourcesMap.get(name);
|
|
1866
2021
|
};
|
|
1867
|
-
var
|
|
1868
|
-
if (!
|
|
1869
|
-
return
|
|
2022
|
+
var getStrategyManifest = (name, cwd = getTradejsProjectCwd()) => {
|
|
2023
|
+
if (!name) {
|
|
2024
|
+
return void 0;
|
|
1870
2025
|
}
|
|
1871
|
-
const
|
|
1872
|
-
|
|
1873
|
-
const indicators = Array.isArray(config.indicators) ? config.indicators.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1874
|
-
const connectors = Array.isArray(config.connectors) ? config.connectors.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1875
|
-
const hooks = (0, import_config.normalizeTradejsConfigHooks)(
|
|
1876
|
-
config.hooks
|
|
1877
|
-
);
|
|
1878
|
-
const runtime = config.runtime && typeof config.runtime === "object" && !Array.isArray(config.runtime) ? config.runtime : void 0;
|
|
1879
|
-
return {
|
|
1880
|
-
strategies: strategies2,
|
|
1881
|
-
indicators,
|
|
1882
|
-
connectors,
|
|
1883
|
-
...hooks ? { hooks } : {},
|
|
1884
|
-
...runtime ? { runtime } : {}
|
|
1885
|
-
};
|
|
2026
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
2027
|
+
return state.strategyManifestsMap.get(name);
|
|
1886
2028
|
};
|
|
1887
|
-
var
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
2029
|
+
var strategies = new Proxy(
|
|
2030
|
+
{},
|
|
2031
|
+
{
|
|
2032
|
+
get: (_target, property) => {
|
|
2033
|
+
if (typeof property !== "string") {
|
|
2034
|
+
return void 0;
|
|
2035
|
+
}
|
|
2036
|
+
return getStrategyRegistryState().state.strategyCreators.get(property);
|
|
2037
|
+
},
|
|
2038
|
+
ownKeys: () => {
|
|
2039
|
+
return [...getStrategyRegistryState().state.strategyCreators.keys()];
|
|
2040
|
+
},
|
|
2041
|
+
getOwnPropertyDescriptor: () => ({
|
|
2042
|
+
enumerable: true,
|
|
2043
|
+
configurable: true
|
|
2044
|
+
})
|
|
1891
2045
|
}
|
|
1892
|
-
|
|
2046
|
+
);
|
|
2047
|
+
|
|
2048
|
+
// src/strategyHelpers/tickerProfiles.ts
|
|
2049
|
+
var snapshots = /* @__PURE__ */ new Map();
|
|
2050
|
+
var resolveTickerProfileSource = (strategy) => {
|
|
2051
|
+
const declared = getStrategyManifest(strategy)?.contextRequirements?.tickerProfiles;
|
|
2052
|
+
if (declared) return declared;
|
|
2053
|
+
const provider = process.env.TICKER_PROFILES_PROVIDER?.trim();
|
|
2054
|
+
const universeId = process.env.TICKER_PROFILES_UNIVERSE?.trim();
|
|
2055
|
+
if (!provider && !universeId) return void 0;
|
|
2056
|
+
if (!provider || !universeId)
|
|
2057
|
+
throw new Error(
|
|
2058
|
+
"Set both TICKER_PROFILES_PROVIDER and TICKER_PROFILES_UNIVERSE"
|
|
2059
|
+
);
|
|
2060
|
+
return { provider, universeId };
|
|
1893
2061
|
};
|
|
1894
|
-
var
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
2062
|
+
var loadTickerProfileContext = async ({
|
|
2063
|
+
projectRoot,
|
|
2064
|
+
provider,
|
|
2065
|
+
universeId,
|
|
2066
|
+
symbol,
|
|
2067
|
+
timestamp
|
|
2068
|
+
}) => {
|
|
2069
|
+
const asOf = (0, import_tickerProfiles2.tickerProfileMonth)(timestamp), key = JSON.stringify([projectRoot, provider, universeId, asOf]);
|
|
2070
|
+
let cached = snapshots.get(key);
|
|
2071
|
+
if (!cached || cached.expires < Date.now()) {
|
|
2072
|
+
const value = (0, import_tickerProfiles.readTickerProfiles)(projectRoot, provider, universeId, asOf).then((envelope) => {
|
|
2073
|
+
if (!envelope) return null;
|
|
2074
|
+
const s = envelope.snapshot;
|
|
2075
|
+
if (!(0, import_tickerProfiles2.isTickerProfileSnapshot)(s) || s.asOf !== asOf || s.provider !== provider || s.universeId !== universeId)
|
|
2076
|
+
throw new Error("Ticker profile identity/schema mismatch");
|
|
2077
|
+
const entry = snapshots.get(key);
|
|
2078
|
+
if (entry) entry.expires = Infinity;
|
|
2079
|
+
return new Map(
|
|
2080
|
+
s.profiles.map((p) => [
|
|
2081
|
+
p.symbol,
|
|
2082
|
+
Object.freeze({
|
|
2083
|
+
...p,
|
|
2084
|
+
definition: s.definition,
|
|
2085
|
+
provider,
|
|
2086
|
+
universeId,
|
|
2087
|
+
snapshotId: envelope.snapshotId
|
|
2088
|
+
})
|
|
2089
|
+
])
|
|
2090
|
+
);
|
|
2091
|
+
}).catch((error) => {
|
|
2092
|
+
snapshots.delete(key);
|
|
2093
|
+
throw error;
|
|
2094
|
+
});
|
|
2095
|
+
cached = { expires: Date.now() + 6e4, value };
|
|
2096
|
+
snapshots.set(key, cached);
|
|
2097
|
+
if (snapshots.size > 24) {
|
|
2098
|
+
const oldest = snapshots.keys().next().value;
|
|
2099
|
+
if (oldest) snapshots.delete(oldest);
|
|
1906
2100
|
}
|
|
1907
|
-
}
|
|
1908
|
-
|
|
2101
|
+
}
|
|
2102
|
+
return (await cached.value)?.get(symbol);
|
|
1909
2103
|
};
|
|
1910
|
-
var
|
|
1911
|
-
|
|
1912
|
-
|
|
2104
|
+
var withTickerProfile = (baseContext, profile) => Object.defineProperty(
|
|
2105
|
+
Object.create(
|
|
2106
|
+
Object.getPrototypeOf(baseContext),
|
|
2107
|
+
Object.getOwnPropertyDescriptors(baseContext)
|
|
2108
|
+
),
|
|
2109
|
+
"tickerProfile",
|
|
2110
|
+
{ enumerable: true, configurable: true, value: profile }
|
|
2111
|
+
);
|
|
2112
|
+
var enrichSignalWithTickerProfile = async (signal) => {
|
|
2113
|
+
const source = resolveTickerProfileSource(signal.strategy);
|
|
2114
|
+
if (!source || signal.universe === "tradfi" || !signal.symbol.endsWith("USDT"))
|
|
1913
2115
|
return;
|
|
2116
|
+
const profile = await loadTickerProfileContext({
|
|
2117
|
+
...source,
|
|
2118
|
+
projectRoot: getTradejsProjectCwd(),
|
|
2119
|
+
symbol: signal.symbol,
|
|
2120
|
+
timestamp: signal.timestamp
|
|
2121
|
+
});
|
|
2122
|
+
if (profile) {
|
|
2123
|
+
signal.additionalIndicators = {
|
|
2124
|
+
...signal.additionalIndicators,
|
|
2125
|
+
tickerProfile: profile
|
|
2126
|
+
};
|
|
1914
2127
|
}
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
2128
|
+
};
|
|
2129
|
+
|
|
2130
|
+
// src/strategyRuntime.ts
|
|
2131
|
+
var import_jev6 = require("@tradejs/core/jev");
|
|
2132
|
+
|
|
2133
|
+
// src/jev.ts
|
|
2134
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
2135
|
+
var import_node_path = __toESM(require("path"));
|
|
2136
|
+
var import_jev4 = require("@tradejs/core/jev");
|
|
2137
|
+
var import_jev5 = require("@tradejs/infra/jev");
|
|
2138
|
+
var import_userSettings = require("@tradejs/infra/userSettings");
|
|
2139
|
+
init_jevInput();
|
|
2140
|
+
init_jevInput();
|
|
2141
|
+
init_jevTraining();
|
|
2142
|
+
var resolveJevProvider = async (userName) => {
|
|
2143
|
+
const settings = await (0, import_userSettings.getUserSettings)(userName);
|
|
2144
|
+
const provider = {
|
|
2145
|
+
endpoint: settings.JEV_API_ENDPOINT || import_jev4.JEV_PROVIDERS[0].endpoint,
|
|
2146
|
+
model: settings.JEV_MODEL || import_jev4.JEV_PROVIDERS[0].model
|
|
2147
|
+
};
|
|
2148
|
+
(0, import_jev4.parseJevConfig)({ source: "provider", mode: "observe", provider });
|
|
2149
|
+
if (!settings.JEV_API_KEY)
|
|
2150
|
+
throw new Error("Configure the Jev API key in Account settings");
|
|
2151
|
+
return provider;
|
|
2152
|
+
};
|
|
2153
|
+
var emptyScores = () => Object.fromEntries(import_jev4.JEV_DIMENSIONS.map((key) => [key, null]));
|
|
2154
|
+
var pending = /* @__PURE__ */ new Map();
|
|
2155
|
+
var pendingResponses = /* @__PURE__ */ new Map();
|
|
2156
|
+
var evaluateJevInput = async ({
|
|
2157
|
+
input,
|
|
2158
|
+
config,
|
|
2159
|
+
userName,
|
|
2160
|
+
projectRoot
|
|
2161
|
+
}) => {
|
|
2162
|
+
config = (0, import_jev4.parseJevConfig)(config);
|
|
2163
|
+
const inputHash = (0, import_jev5.jevHash)(input);
|
|
2164
|
+
const questions = questionsForJevInput(input);
|
|
2165
|
+
const questionsHash = (0, import_jev5.jevHash)(questions);
|
|
2166
|
+
const base = {
|
|
2167
|
+
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
2168
|
+
schema: "tradejs-signal-assessment/v5",
|
|
2169
|
+
source: config.source,
|
|
2170
|
+
mode: config.mode,
|
|
2171
|
+
inputHash,
|
|
2172
|
+
questionsHash,
|
|
2173
|
+
geometryStatus: input.geometryStatus,
|
|
2174
|
+
levelsValid: typeof input.features["signal.validLevels"] === "boolean" ? input.features["signal.validLevels"] : null
|
|
2175
|
+
};
|
|
2176
|
+
if (!input.questions.length)
|
|
2177
|
+
return {
|
|
2178
|
+
assessment: {
|
|
2179
|
+
...base,
|
|
2180
|
+
status: "unavailable",
|
|
2181
|
+
model: "",
|
|
2182
|
+
scores: emptyScores(),
|
|
2183
|
+
confidence: emptyScores()
|
|
2184
|
+
}
|
|
2185
|
+
};
|
|
2186
|
+
if (config.source === "local") {
|
|
2187
|
+
const contents = await import_promises.default.readFile(
|
|
2188
|
+
import_node_path.default.resolve(projectRoot, config.modelFile),
|
|
2189
|
+
"utf8"
|
|
2190
|
+
);
|
|
2191
|
+
if ((0, import_jev5.jevFileHash)(contents) !== config.modelSha256)
|
|
2192
|
+
throw new Error("Local Jev gate checksum mismatch");
|
|
2193
|
+
const { validateJevGateModel: validateJevGateModel2 } = await Promise.resolve().then(() => (init_jevTraining(), jevTraining_exports));
|
|
2194
|
+
const model = validateJevGateModel2(JSON.parse(contents));
|
|
2195
|
+
if (model.strategy !== input.strategy || model.questionsHash !== (0, import_jev5.jevHash)(JEV_QUESTIONS))
|
|
2196
|
+
throw new Error("Local Jev gate strategy or question version mismatch");
|
|
2197
|
+
const scores = Object.fromEntries(
|
|
2198
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
2199
|
+
key,
|
|
2200
|
+
input.questions.includes(key) && model.trees[key] ? (0, import_jev4.predictJevTree)(model.trees[key], input.features) : null
|
|
2201
|
+
])
|
|
2202
|
+
);
|
|
2203
|
+
return {
|
|
2204
|
+
assessment: {
|
|
2205
|
+
...base,
|
|
2206
|
+
status: "available",
|
|
2207
|
+
model: config.modelSha256,
|
|
2208
|
+
scores,
|
|
2209
|
+
confidence: emptyScores()
|
|
2210
|
+
}
|
|
2211
|
+
};
|
|
2212
|
+
}
|
|
2213
|
+
const provider = config.provider ?? await resolveJevProvider(userName);
|
|
2214
|
+
const id = (0, import_jev5.jevHash)({ inputHash, questionsHash, provider });
|
|
2215
|
+
const dir = import_node_path.default.resolve(projectRoot, config.recordsDir ?? "data/ai/jev");
|
|
2216
|
+
const file = import_node_path.default.join(dir, "records", `${id}.json`);
|
|
2217
|
+
const getRecord = async () => {
|
|
2218
|
+
const existing = await (0, import_jev5.readJevArtifact)(file);
|
|
2219
|
+
if (existing) {
|
|
2220
|
+
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))
|
|
2221
|
+
throw new Error("Jev recording identity mismatch");
|
|
2222
|
+
(0, import_jev5.validateJevResponse)(existing.response, input.questions);
|
|
2223
|
+
for (const key of import_jev4.JEV_DIMENSIONS) {
|
|
2224
|
+
const expected = input.questions.includes(key) ? existing.response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null;
|
|
2225
|
+
if (existing.scores[key] !== expected)
|
|
2226
|
+
throw new Error("Jev recording scores do not match the response");
|
|
2227
|
+
}
|
|
2228
|
+
return existing;
|
|
2229
|
+
}
|
|
2230
|
+
if (config.source === "recorded")
|
|
2231
|
+
throw new Error(
|
|
2232
|
+
`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.`
|
|
2233
|
+
);
|
|
2234
|
+
const settings = await (0, import_userSettings.getUserSettings)(userName);
|
|
2235
|
+
if (!settings.JEV_API_KEY)
|
|
2236
|
+
throw new Error("Configure the Jev API key in Account settings");
|
|
2237
|
+
const apiKey = settings.JEV_API_KEY;
|
|
2238
|
+
const configuredProvider = await resolveJevProvider(userName);
|
|
2239
|
+
if (configuredProvider.endpoint !== provider.endpoint)
|
|
2240
|
+
throw new Error(
|
|
2241
|
+
"Jev account endpoint changed; update the frozen provider configuration"
|
|
2242
|
+
);
|
|
2243
|
+
const started = Date.now();
|
|
2244
|
+
const state = stateForJevInput(input);
|
|
2245
|
+
const responseId = (0, import_jev5.jevHash)({
|
|
2246
|
+
state,
|
|
2247
|
+
questionsHash,
|
|
2248
|
+
provider,
|
|
2249
|
+
user: (0, import_jev5.jevHash)(userName)
|
|
2250
|
+
});
|
|
2251
|
+
const responseFile = import_node_path.default.join(dir, "responses", `${responseId}.json`);
|
|
2252
|
+
let responsePromise = pendingResponses.get(responseFile);
|
|
2253
|
+
if (!responsePromise) {
|
|
2254
|
+
responsePromise = (async () => {
|
|
2255
|
+
const saved = await (0, import_jev5.readJevArtifact)(responseFile);
|
|
2256
|
+
if (saved) return (0, import_jev5.validateJevResponse)(saved, input.questions);
|
|
2257
|
+
const fresh = await (0, import_jev5.requestJev)(provider, apiKey, state, questions);
|
|
2258
|
+
await (0, import_jev5.writeJevArtifact)(responseFile, fresh);
|
|
2259
|
+
return fresh;
|
|
2260
|
+
})();
|
|
2261
|
+
pendingResponses.set(responseFile, responsePromise);
|
|
2262
|
+
}
|
|
2263
|
+
let response;
|
|
2264
|
+
try {
|
|
2265
|
+
response = await responsePromise;
|
|
2266
|
+
} finally {
|
|
2267
|
+
pendingResponses.delete(responseFile);
|
|
2268
|
+
}
|
|
2269
|
+
const scores = Object.fromEntries(
|
|
2270
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
2271
|
+
key,
|
|
2272
|
+
input.questions.includes(key) ? response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null
|
|
2273
|
+
])
|
|
2274
|
+
);
|
|
2275
|
+
const value = {
|
|
2276
|
+
schema: "tradejs-jev-record/v4",
|
|
2277
|
+
id,
|
|
2278
|
+
inputHash,
|
|
2279
|
+
questionsHash,
|
|
2280
|
+
provider,
|
|
2281
|
+
input,
|
|
2282
|
+
response,
|
|
2283
|
+
scores,
|
|
2284
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2285
|
+
elapsedMs: Date.now() - started
|
|
2286
|
+
};
|
|
2287
|
+
await (0, import_jev5.writeJevArtifact)(file, value);
|
|
2288
|
+
return await (0, import_jev5.readJevArtifact)(file);
|
|
2289
|
+
};
|
|
2290
|
+
const pendingKey = `${file}:${(0, import_jev5.jevHash)(userName)}`;
|
|
2291
|
+
let promise = pending.get(pendingKey);
|
|
2292
|
+
if (!promise) {
|
|
2293
|
+
promise = getRecord();
|
|
2294
|
+
pending.set(pendingKey, promise);
|
|
1920
2295
|
}
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
2296
|
+
let record;
|
|
2297
|
+
try {
|
|
2298
|
+
record = await promise;
|
|
2299
|
+
} finally {
|
|
2300
|
+
pending.delete(pendingKey);
|
|
1924
2301
|
}
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
2302
|
+
return {
|
|
2303
|
+
record,
|
|
2304
|
+
assessment: {
|
|
2305
|
+
...base,
|
|
2306
|
+
status: "available",
|
|
2307
|
+
recordId: id,
|
|
2308
|
+
model: record.response.model,
|
|
2309
|
+
scores: record.scores,
|
|
2310
|
+
confidence: Object.fromEntries(
|
|
2311
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
2312
|
+
key,
|
|
2313
|
+
record.response.answers[key]?.confidence ?? null
|
|
2314
|
+
])
|
|
2315
|
+
)
|
|
2316
|
+
}
|
|
2317
|
+
};
|
|
1931
2318
|
};
|
|
1932
|
-
var
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
2319
|
+
var assessSignalWithJev = async ({
|
|
2320
|
+
signal,
|
|
2321
|
+
config,
|
|
2322
|
+
userName,
|
|
2323
|
+
projectRoot,
|
|
2324
|
+
strict,
|
|
2325
|
+
scope = "runtime"
|
|
2326
|
+
}) => {
|
|
2327
|
+
let assessment;
|
|
2328
|
+
try {
|
|
2329
|
+
const input = buildJevInput(signal);
|
|
2330
|
+
({ assessment } = await evaluateJevInput({
|
|
2331
|
+
input,
|
|
2332
|
+
config,
|
|
2333
|
+
userName,
|
|
2334
|
+
projectRoot
|
|
2335
|
+
}));
|
|
2336
|
+
} catch (error) {
|
|
2337
|
+
if (strict) throw error;
|
|
2338
|
+
assessment = {
|
|
2339
|
+
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
2340
|
+
schema: "tradejs-signal-assessment/v5",
|
|
2341
|
+
source: config.source,
|
|
2342
|
+
mode: config.mode,
|
|
2343
|
+
status: "unavailable",
|
|
2344
|
+
inputHash: "",
|
|
2345
|
+
questionsHash: "",
|
|
2346
|
+
geometryStatus: "absent",
|
|
2347
|
+
levelsValid: null,
|
|
2348
|
+
model: config.provider?.model ?? config.modelSha256 ?? "",
|
|
2349
|
+
scores: emptyScores(),
|
|
2350
|
+
confidence: emptyScores()
|
|
2351
|
+
};
|
|
1948
2352
|
}
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
2353
|
+
signal.assessment = assessment;
|
|
2354
|
+
signal.additionalIndicators = {
|
|
2355
|
+
...signal.additionalIndicators,
|
|
2356
|
+
jev: (0, import_jev4.jevAssessmentFeatures)(assessment)
|
|
2357
|
+
};
|
|
2358
|
+
const identity = {
|
|
2359
|
+
signalId: signal.signalId,
|
|
2360
|
+
strategy: signal.strategy,
|
|
2361
|
+
symbol: signal.symbol,
|
|
2362
|
+
timestamp: signal.timestamp,
|
|
2363
|
+
scope,
|
|
2364
|
+
user: (0, import_jev5.jevHash)(userName),
|
|
2365
|
+
config,
|
|
2366
|
+
assessment
|
|
2367
|
+
};
|
|
2368
|
+
await (0, import_jev5.writeJevArtifact)(
|
|
2369
|
+
import_node_path.default.resolve(
|
|
2370
|
+
projectRoot,
|
|
2371
|
+
config.recordsDir ?? "data/ai/jev",
|
|
2372
|
+
"decisions",
|
|
2373
|
+
`${(0, import_jev5.jevHash)(identity)}.json`
|
|
2374
|
+
),
|
|
2375
|
+
identity
|
|
1952
2376
|
);
|
|
1953
|
-
|
|
1954
|
-
".ts",
|
|
1955
|
-
".tsx",
|
|
1956
|
-
".mts",
|
|
1957
|
-
".cts",
|
|
1958
|
-
".js",
|
|
1959
|
-
".jsx",
|
|
1960
|
-
".mjs",
|
|
1961
|
-
".cjs",
|
|
1962
|
-
".json"
|
|
1963
|
-
]) || "";
|
|
1964
|
-
tsconfigPathMatchersByCwd.set(projectRoot, matcher);
|
|
1965
|
-
const resolved = matcher(moduleName);
|
|
1966
|
-
return resolved || null;
|
|
2377
|
+
return assessment;
|
|
1967
2378
|
};
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
2379
|
+
|
|
2380
|
+
// src/strategyRuntime.ts
|
|
2381
|
+
var import_constants5 = require("@tradejs/core/constants");
|
|
2382
|
+
var import_strategies6 = require("@tradejs/core/strategies");
|
|
2383
|
+
var import_logger11 = require("@tradejs/infra/logger");
|
|
2384
|
+
|
|
2385
|
+
// src/strategyHelpers/runtime.ts
|
|
2386
|
+
var import_logger9 = require("@tradejs/infra/logger");
|
|
2387
|
+
var import_constants3 = require("@tradejs/core/constants");
|
|
2388
|
+
var import_ml2 = require("@tradejs/infra/ml");
|
|
2389
|
+
|
|
2390
|
+
// src/strategy/entrySubmissionValidation.ts
|
|
2391
|
+
var import_logger3 = require("@tradejs/infra/logger");
|
|
2392
|
+
|
|
2393
|
+
// src/strategy/entryExecutionValidation.ts
|
|
2394
|
+
var EntrySubmissionValidationError = class extends Error {
|
|
2395
|
+
constructor() {
|
|
2396
|
+
super(...arguments);
|
|
2397
|
+
this.name = "EntrySubmissionValidationError";
|
|
1974
2398
|
}
|
|
1975
|
-
return moduleName;
|
|
1976
2399
|
};
|
|
1977
|
-
var
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
const
|
|
1981
|
-
if (
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
2400
|
+
var resolveEntryExecutionLimits = (config, direction) => {
|
|
2401
|
+
const side = config[direction];
|
|
2402
|
+
const minimum = side && typeof side === "object" && "minRiskRatio" in side ? side.minRiskRatio : void 0;
|
|
2403
|
+
const maximum = config.MAX_LOSS_VALUE;
|
|
2404
|
+
if (minimum == null && maximum == null) return void 0;
|
|
2405
|
+
const minimumNetRiskRatio = minimum == null ? void 0 : Number(minimum);
|
|
2406
|
+
const maxLossValue = maximum == null ? void 0 : Number(maximum);
|
|
2407
|
+
const feeRate = Number(config.RISK_FEE_RATE ?? 0);
|
|
2408
|
+
const slippage = Number(config.RISK_SLIPPAGE_BPS ?? 0);
|
|
2409
|
+
const impact = Number(config.RISK_MARKET_IMPACT_BPS ?? 0);
|
|
2410
|
+
const slippageBps = slippage + impact;
|
|
2411
|
+
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)
|
|
2412
|
+
throw new Error("INVALID_ENTRY_EXECUTION_LIMITS");
|
|
2413
|
+
return { minimumNetRiskRatio, maxLossValue, feeRate, slippageBps };
|
|
1988
2414
|
};
|
|
1989
|
-
var
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
2415
|
+
var validateEntryExecution = ({
|
|
2416
|
+
direction,
|
|
2417
|
+
price,
|
|
2418
|
+
qty,
|
|
2419
|
+
stopLossPrice,
|
|
2420
|
+
takeProfitPrice,
|
|
2421
|
+
limits
|
|
2422
|
+
}) => {
|
|
2423
|
+
if (!Number.isFinite(price) || price <= 0 || !Number.isFinite(qty) || qty <= 0 || stopLossPrice == null || !Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || !Number.isFinite(takeProfitPrice) || takeProfitPrice <= 0)
|
|
2424
|
+
throw new EntrySubmissionValidationError(
|
|
2425
|
+
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
2426
|
+
);
|
|
2427
|
+
const side = direction === "LONG" ? 1 : -1;
|
|
2428
|
+
if (side * (price - stopLossPrice) <= 0 || side * (takeProfitPrice - price) <= 0)
|
|
2429
|
+
throw new EntrySubmissionValidationError(
|
|
2430
|
+
"INVALID_ENTRY_PROTECTION_AT_SUBMISSION"
|
|
2431
|
+
);
|
|
2432
|
+
const slippage = limits.slippageBps / 1e4;
|
|
2433
|
+
const cost = (value, sign) => value * slippage + value * (1 + sign * slippage) * limits.feeRate;
|
|
2434
|
+
const entryCost = cost(price, side);
|
|
2435
|
+
const lossPerUnit = Math.abs(price - stopLossPrice) + entryCost + cost(stopLossPrice, -side);
|
|
2436
|
+
const rewardPerUnit = Math.max(
|
|
2437
|
+
0,
|
|
2438
|
+
Math.abs(takeProfitPrice - price) - entryCost - cost(takeProfitPrice, -side)
|
|
2439
|
+
);
|
|
2440
|
+
const netRiskRatio = rewardPerUnit / lossPerUnit;
|
|
2441
|
+
const plannedLossValue = qty * lossPerUnit;
|
|
2442
|
+
if (!Number.isFinite(netRiskRatio) || !Number.isFinite(plannedLossValue))
|
|
2443
|
+
throw new EntrySubmissionValidationError(
|
|
2444
|
+
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
2445
|
+
);
|
|
2446
|
+
const belowMinimumNetRiskRatio = limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio;
|
|
2447
|
+
const riskExceeded = limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9);
|
|
2448
|
+
return {
|
|
2449
|
+
netRiskRatio,
|
|
2450
|
+
plannedLossValue,
|
|
2451
|
+
riskExceeded,
|
|
2452
|
+
belowMinimumNetRiskRatio
|
|
2453
|
+
};
|
|
2454
|
+
};
|
|
2455
|
+
|
|
2456
|
+
// src/strategy/entrySubmissionValidation.ts
|
|
2457
|
+
var MAX_ENTRY_QUOTE_AGE_MS = 5e3;
|
|
2458
|
+
var toFiniteNumberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
2459
|
+
var getOrderArrivalSnapshot = async ({
|
|
2460
|
+
connector,
|
|
2461
|
+
symbol,
|
|
2462
|
+
requireTimestamp = false
|
|
2463
|
+
}) => {
|
|
2464
|
+
if (typeof connector.getTopOfBookTicker !== "function") {
|
|
2465
|
+
return {
|
|
2466
|
+
arrivalSnapshotTime: Date.now(),
|
|
2467
|
+
arrivalSource: "unavailable",
|
|
2468
|
+
bid: null,
|
|
2469
|
+
ask: null,
|
|
2470
|
+
arrivalMid: null,
|
|
2471
|
+
spreadBps: null
|
|
2472
|
+
};
|
|
2473
|
+
}
|
|
2474
|
+
try {
|
|
2475
|
+
const ticker = await connector.getTopOfBookTicker(symbol);
|
|
2476
|
+
const arrivalSnapshotTime = toFiniteNumberOrNull(ticker?.timestamp);
|
|
2477
|
+
const timestampUnavailable = requireTimestamp && arrivalSnapshotTime == null;
|
|
2478
|
+
const bid = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.bidPrice);
|
|
2479
|
+
const ask = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.askPrice);
|
|
2480
|
+
const arrivalMid = bid != null && ask != null ? (bid + ask) / 2 : null;
|
|
2481
|
+
const spreadBps = bid != null && ask != null && arrivalMid != null && arrivalMid > 0 ? (ask - bid) / arrivalMid * 1e4 : null;
|
|
2482
|
+
return {
|
|
2483
|
+
arrivalSnapshotTime: arrivalSnapshotTime ?? Date.now(),
|
|
2484
|
+
arrivalSource: "top_of_book",
|
|
2485
|
+
bid,
|
|
2486
|
+
ask,
|
|
2487
|
+
arrivalMid,
|
|
2488
|
+
spreadBps
|
|
2489
|
+
};
|
|
2490
|
+
} catch (error) {
|
|
2491
|
+
import_logger3.logger.warn(
|
|
2492
|
+
"runtime order arrival snapshot failed: %s %s",
|
|
2493
|
+
symbol,
|
|
2494
|
+
error?.message || String(error)
|
|
2495
|
+
);
|
|
2496
|
+
return {
|
|
2497
|
+
arrivalSnapshotTime: Date.now(),
|
|
2498
|
+
arrivalSource: "top_of_book_error",
|
|
2499
|
+
bid: null,
|
|
2500
|
+
ask: null,
|
|
2501
|
+
arrivalMid: null,
|
|
2502
|
+
spreadBps: null
|
|
2503
|
+
};
|
|
1997
2504
|
}
|
|
1998
|
-
return import(
|
|
1999
|
-
/* webpackIgnore: true */
|
|
2000
|
-
configFileUrl
|
|
2001
|
-
);
|
|
2002
2505
|
};
|
|
2003
|
-
var
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2506
|
+
var validateEntryProtectionAtArrival = ({
|
|
2507
|
+
direction,
|
|
2508
|
+
signalPrice,
|
|
2509
|
+
bid,
|
|
2510
|
+
ask,
|
|
2511
|
+
arrivalMid,
|
|
2512
|
+
takeProfits,
|
|
2513
|
+
stopLossPrice
|
|
2514
|
+
}) => {
|
|
2515
|
+
const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
|
|
2516
|
+
const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
|
|
2517
|
+
if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
|
|
2518
|
+
throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
|
|
2007
2519
|
}
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
modulePath = (0, import_url.fileURLToPath)(normalized);
|
|
2012
|
-
} catch {
|
|
2013
|
-
modulePath = normalized;
|
|
2520
|
+
for (const takeProfit of takeProfits) {
|
|
2521
|
+
if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
|
|
2522
|
+
throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
|
|
2014
2523
|
}
|
|
2015
2524
|
}
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
);
|
|
2019
|
-
if (isTsModulePath(modulePath)) {
|
|
2020
|
-
await ensureTsNodeRegistered();
|
|
2021
|
-
await ensureTsconfigPathsRegistered(cwd);
|
|
2022
|
-
return requireFn(modulePath);
|
|
2525
|
+
if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
|
|
2526
|
+
throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
|
|
2023
2527
|
}
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2528
|
+
};
|
|
2529
|
+
var createEntrySubmissionValidation = ({
|
|
2530
|
+
connector,
|
|
2531
|
+
symbol,
|
|
2532
|
+
direction,
|
|
2533
|
+
takeProfits,
|
|
2534
|
+
stopLossPrice,
|
|
2535
|
+
limits,
|
|
2536
|
+
onValidated
|
|
2537
|
+
}) => {
|
|
2538
|
+
if (!limits) return void 0;
|
|
2539
|
+
if (!connector.capabilities?.orderSubmissionValidation)
|
|
2540
|
+
throw new EntrySubmissionValidationError(
|
|
2541
|
+
"ENTRY_SUBMISSION_VALIDATION_UNSUPPORTED"
|
|
2542
|
+
);
|
|
2543
|
+
return async ({ qty, executionPrice }) => {
|
|
2544
|
+
const simulated = Reflect.get(connector, "__tradejsTestConnector") === true;
|
|
2545
|
+
const snapshot2 = simulated && executionPrice != null ? {
|
|
2546
|
+
arrivalSnapshotTime: Date.now(),
|
|
2547
|
+
arrivalSource: "simulation",
|
|
2548
|
+
bid: executionPrice,
|
|
2549
|
+
ask: executionPrice,
|
|
2550
|
+
arrivalMid: executionPrice,
|
|
2551
|
+
spreadBps: 0
|
|
2552
|
+
} : await getOrderArrivalSnapshot({
|
|
2553
|
+
connector,
|
|
2554
|
+
symbol,
|
|
2555
|
+
requireTimestamp: true
|
|
2556
|
+
});
|
|
2557
|
+
const age = Date.now() - snapshot2.arrivalSnapshotTime;
|
|
2558
|
+
if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
|
|
2559
|
+
throw new EntrySubmissionValidationError(
|
|
2560
|
+
"ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
|
|
2032
2561
|
);
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2562
|
+
if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
|
|
2563
|
+
throw new EntrySubmissionValidationError(
|
|
2564
|
+
"ENTRY_QUOTE_STALE_AT_SUBMISSION"
|
|
2565
|
+
);
|
|
2566
|
+
const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
|
|
2567
|
+
validateEntryProtectionAtArrival({
|
|
2568
|
+
direction,
|
|
2569
|
+
signalPrice: price,
|
|
2570
|
+
bid: snapshot2.bid,
|
|
2571
|
+
ask: snapshot2.ask,
|
|
2572
|
+
arrivalMid: snapshot2.arrivalMid,
|
|
2573
|
+
takeProfits,
|
|
2574
|
+
stopLossPrice
|
|
2575
|
+
});
|
|
2576
|
+
const prices = takeProfits.map((tp) => tp.price);
|
|
2577
|
+
const economics2 = validateEntryExecution({
|
|
2578
|
+
direction,
|
|
2579
|
+
price,
|
|
2580
|
+
qty,
|
|
2581
|
+
stopLossPrice,
|
|
2582
|
+
takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
|
|
2583
|
+
limits
|
|
2584
|
+
});
|
|
2585
|
+
if (economics2.belowMinimumNetRiskRatio) {
|
|
2586
|
+
import_logger3.logger.warn(
|
|
2587
|
+
"entry submission warning: %s %s ENTRY_RR_BELOW_MIN_AT_SUBMISSION (RR %s, threshold %s)",
|
|
2588
|
+
symbol,
|
|
2589
|
+
direction,
|
|
2590
|
+
economics2.netRiskRatio,
|
|
2591
|
+
limits.minimumNetRiskRatio
|
|
2592
|
+
);
|
|
2593
|
+
}
|
|
2594
|
+
onValidated?.(snapshot2, economics2);
|
|
2595
|
+
};
|
|
2596
|
+
};
|
|
2597
|
+
|
|
2598
|
+
// src/ai.ts
|
|
2599
|
+
var import_aiLanguages2 = require("@tradejs/core/aiLanguages");
|
|
2600
|
+
var import_redis3 = require("@tradejs/infra/redis");
|
|
2601
|
+
|
|
2602
|
+
// src/strategyAdapters/ai.ts
|
|
2603
|
+
var import_indicators2 = require("@tradejs/core/indicators");
|
|
2604
|
+
var import_aiFeatures = require("@tradejs/core/aiFeatures");
|
|
2605
|
+
|
|
2606
|
+
// src/aiShared.ts
|
|
2607
|
+
var MAX_AI_SERIES_POINTS = 5;
|
|
2608
|
+
var COMPACT_INDICATORS_SNAPSHOT_SYMBOL = /* @__PURE__ */ Symbol.for(
|
|
2609
|
+
"tradejs.indicators.compactSnapshot"
|
|
2610
|
+
);
|
|
2611
|
+
var COMPACT_INDICATORS_SNAPSHOT_KEY = "__tradejsCompactIndicatorsSnapshot";
|
|
2612
|
+
var trimSeriesDeep = (value) => {
|
|
2613
|
+
if (Array.isArray(value)) {
|
|
2614
|
+
const trimmed = value.slice(-MAX_AI_SERIES_POINTS);
|
|
2615
|
+
const isMatrix = trimmed.every((item) => Array.isArray(item));
|
|
2616
|
+
if (isMatrix) {
|
|
2617
|
+
return trimmed;
|
|
2037
2618
|
}
|
|
2619
|
+
return trimmed.map(
|
|
2620
|
+
(item) => item && typeof item === "object" ? trimSeriesDeep(item) : item
|
|
2621
|
+
);
|
|
2038
2622
|
}
|
|
2039
|
-
|
|
2040
|
-
return
|
|
2041
|
-
|
|
2042
|
-
|
|
2623
|
+
if (value && typeof value === "object") {
|
|
2624
|
+
return Object.fromEntries(
|
|
2625
|
+
Object.entries(value).map(([key, nested]) => [
|
|
2626
|
+
key,
|
|
2627
|
+
trimSeriesDeep(nested)
|
|
2628
|
+
])
|
|
2043
2629
|
);
|
|
2044
|
-
} catch (error) {
|
|
2045
|
-
if (isTsModulePath(modulePath)) {
|
|
2046
|
-
await ensureTsNodeRegistered();
|
|
2047
|
-
await ensureTsconfigPathsRegistered(cwd);
|
|
2048
|
-
return requireFn(modulePath);
|
|
2049
|
-
}
|
|
2050
|
-
throw error;
|
|
2051
2630
|
}
|
|
2631
|
+
return value;
|
|
2052
2632
|
};
|
|
2053
|
-
var
|
|
2054
|
-
const
|
|
2055
|
-
|
|
2056
|
-
};
|
|
2057
|
-
var findConfigFilePath = (cwd) => {
|
|
2058
|
-
let currentDir = import_path.default.resolve(cwd);
|
|
2059
|
-
while (true) {
|
|
2060
|
-
for (const fileName of CONFIG_FILE_NAMES) {
|
|
2061
|
-
const fullPath = import_path.default.join(currentDir, fileName);
|
|
2062
|
-
if (import_fs.default.existsSync(fullPath) && import_fs.default.statSync(fullPath).isFile()) {
|
|
2063
|
-
return fullPath;
|
|
2064
|
-
}
|
|
2065
|
-
}
|
|
2066
|
-
const parentDir = import_path.default.dirname(currentDir);
|
|
2067
|
-
if (parentDir === currentDir) {
|
|
2068
|
-
return null;
|
|
2069
|
-
}
|
|
2070
|
-
currentDir = parentDir;
|
|
2633
|
+
var buildCompactAiIndicatorsSnapshot = (value) => {
|
|
2634
|
+
const compactSnapshot = value && typeof value === "object" ? value[COMPACT_INDICATORS_SNAPSHOT_SYMBOL] ?? value[COMPACT_INDICATORS_SNAPSHOT_KEY] : void 0;
|
|
2635
|
+
if (typeof compactSnapshot === "function") {
|
|
2636
|
+
return compactSnapshot({ limit: MAX_AI_SERIES_POINTS });
|
|
2071
2637
|
}
|
|
2638
|
+
return trimSeriesDeep(value);
|
|
2072
2639
|
};
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
if (normalized.startsWith("file://")) {
|
|
2079
|
-
try {
|
|
2080
|
-
return (0, import_url.fileURLToPath)(normalized);
|
|
2081
|
-
} catch {
|
|
2082
|
-
return normalized;
|
|
2083
|
-
}
|
|
2084
|
-
}
|
|
2085
|
-
if (import_path.default.isAbsolute(normalized)) {
|
|
2086
|
-
return normalized;
|
|
2087
|
-
}
|
|
2088
|
-
if (isRelativeModulePath(normalized)) {
|
|
2089
|
-
return import_path.default.resolve(cwd, normalized);
|
|
2640
|
+
|
|
2641
|
+
// src/aiMarketContext.ts
|
|
2642
|
+
var toRecord = (value) => {
|
|
2643
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2644
|
+
return null;
|
|
2090
2645
|
}
|
|
2091
|
-
return
|
|
2646
|
+
return value;
|
|
2092
2647
|
};
|
|
2093
|
-
var
|
|
2094
|
-
const
|
|
2095
|
-
|
|
2096
|
-
return cached;
|
|
2097
|
-
}
|
|
2098
|
-
const configFilePath = findConfigFilePath(cwd);
|
|
2099
|
-
if (!configFilePath) {
|
|
2100
|
-
cachedByCwd.set(cwd, {});
|
|
2101
|
-
return {};
|
|
2102
|
-
}
|
|
2103
|
-
try {
|
|
2104
|
-
const moduleExports = await importConfigFile(configFilePath);
|
|
2105
|
-
const config = resolveExportedConfig(moduleExports);
|
|
2106
|
-
cachedByCwd.set(cwd, config);
|
|
2107
|
-
if (!announcedConfigFile.has(configFilePath)) {
|
|
2108
|
-
announcedConfigFile.add(configFilePath);
|
|
2109
|
-
import_logger2.logger.log("debug", "Loaded TradeJS config: %s", configFilePath);
|
|
2110
|
-
}
|
|
2111
|
-
return config;
|
|
2112
|
-
} catch (error) {
|
|
2113
|
-
import_logger2.logger.log(
|
|
2114
|
-
"warn",
|
|
2115
|
-
"Failed to load TradeJS config from %s: %s",
|
|
2116
|
-
configFilePath,
|
|
2117
|
-
String(error)
|
|
2118
|
-
);
|
|
2119
|
-
cachedByCwd.set(cwd, {});
|
|
2120
|
-
return {};
|
|
2121
|
-
}
|
|
2648
|
+
var toFiniteNumber = (value) => {
|
|
2649
|
+
const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
|
|
2650
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
2122
2651
|
};
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
var
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2652
|
+
var roundTo = (value, decimals) => {
|
|
2653
|
+
const factor = 10 ** decimals;
|
|
2654
|
+
return Math.round(value * factor) / factor;
|
|
2655
|
+
};
|
|
2656
|
+
var buildMissingSpreadContext = () => ({
|
|
2657
|
+
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2658
|
+
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2659
|
+
available: false,
|
|
2660
|
+
value: null,
|
|
2661
|
+
zScore: null,
|
|
2662
|
+
bps: null,
|
|
2663
|
+
absBps: null,
|
|
2664
|
+
bias: null,
|
|
2665
|
+
severity: null
|
|
2137
2666
|
});
|
|
2138
|
-
var
|
|
2139
|
-
|
|
2140
|
-
const
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2667
|
+
var buildSpreadContextFromSignal = (signal) => {
|
|
2668
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2669
|
+
const relative = toRecord(baseContext?.relative);
|
|
2670
|
+
const execution = toRecord(relative?.execution);
|
|
2671
|
+
const spread = toFiniteNumber(execution?.venueSpread);
|
|
2672
|
+
const zScore = toFiniteNumber(execution?.venueSpreadZScore);
|
|
2673
|
+
if (spread == null) {
|
|
2674
|
+
return buildMissingSpreadContext();
|
|
2145
2675
|
}
|
|
2676
|
+
const value = roundTo(spread, 8);
|
|
2677
|
+
const bps = roundTo(value * 1e4, 2);
|
|
2678
|
+
const absBps = Math.abs(bps);
|
|
2679
|
+
const bias = absBps < 1 ? "flat" : bps > 0 ? "coinbase_premium" : "binance_premium";
|
|
2680
|
+
const severity = absBps >= 20 ? "wide" : absBps >= 5 ? "elevated" : "normal";
|
|
2146
2681
|
return {
|
|
2147
|
-
|
|
2148
|
-
|
|
2682
|
+
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2683
|
+
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2684
|
+
available: true,
|
|
2685
|
+
value,
|
|
2686
|
+
zScore,
|
|
2687
|
+
bps,
|
|
2688
|
+
absBps,
|
|
2689
|
+
bias,
|
|
2690
|
+
severity
|
|
2149
2691
|
};
|
|
2150
2692
|
};
|
|
2151
|
-
var
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
const
|
|
2693
|
+
var buildTrueDeltaContextFromSignal = (signal) => {
|
|
2694
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2695
|
+
const participation = toRecord(baseContext?.participation);
|
|
2696
|
+
const delta = toRecord(participation?.delta);
|
|
2697
|
+
const source = String(delta?.source ?? "");
|
|
2698
|
+
const isTrueDeltaSource = source === "kline_taker_volume" || source === "agg_trades" || source === "trades";
|
|
2699
|
+
if (!delta || !isTrueDeltaSource) {
|
|
2700
|
+
return {
|
|
2701
|
+
source: source || null,
|
|
2702
|
+
available: false,
|
|
2703
|
+
buyPressurePct: null,
|
|
2704
|
+
buyVolume: null,
|
|
2705
|
+
sellVolume: null,
|
|
2706
|
+
netDelta: null,
|
|
2707
|
+
deltaPct: null,
|
|
2708
|
+
signedVolumeZScore: null
|
|
2709
|
+
};
|
|
2710
|
+
}
|
|
2156
2711
|
return {
|
|
2157
|
-
|
|
2158
|
-
|
|
2712
|
+
source,
|
|
2713
|
+
available: true,
|
|
2714
|
+
buyPressurePct: toFiniteNumber(delta.buyPressurePct),
|
|
2715
|
+
buyVolume: toFiniteNumber(delta.buyVolume),
|
|
2716
|
+
sellVolume: toFiniteNumber(delta.sellVolume),
|
|
2717
|
+
netDelta: toFiniteNumber(delta.netDelta),
|
|
2718
|
+
deltaPct: toFiniteNumber(delta.deltaPct),
|
|
2719
|
+
signedVolumeZScore: toFiniteNumber(delta.signedVolumeZScore)
|
|
2159
2720
|
};
|
|
2160
2721
|
};
|
|
2161
|
-
var
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2722
|
+
var buildTradeFlowContextFromSignal = (signal) => {
|
|
2723
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2724
|
+
const participation = toRecord(baseContext?.participation);
|
|
2725
|
+
const tradeFlow = toRecord(participation?.tradeFlow);
|
|
2726
|
+
if (!tradeFlow) {
|
|
2727
|
+
return {
|
|
2728
|
+
source: null,
|
|
2729
|
+
available: false,
|
|
2730
|
+
interval: null,
|
|
2731
|
+
stale: null,
|
|
2732
|
+
trades: null,
|
|
2733
|
+
buyPressurePct: null,
|
|
2734
|
+
netBaseDelta: null,
|
|
2735
|
+
netQuoteDelta: null
|
|
2736
|
+
};
|
|
2172
2737
|
}
|
|
2173
|
-
return
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2738
|
+
return {
|
|
2739
|
+
source: String(tradeFlow.source ?? ""),
|
|
2740
|
+
available: true,
|
|
2741
|
+
interval: String(tradeFlow.interval ?? ""),
|
|
2742
|
+
stale: typeof tradeFlow.stale === "boolean" ? tradeFlow.stale : null,
|
|
2743
|
+
trades: toFiniteNumber(tradeFlow.trades),
|
|
2744
|
+
buyPressurePct: toFiniteNumber(tradeFlow.buyPressurePct),
|
|
2745
|
+
netBaseDelta: toFiniteNumber(tradeFlow.netBaseDelta),
|
|
2746
|
+
netQuoteDelta: toFiniteNumber(tradeFlow.netQuoteDelta)
|
|
2747
|
+
};
|
|
2181
2748
|
};
|
|
2182
|
-
var
|
|
2183
|
-
const
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
)
|
|
2187
|
-
|
|
2749
|
+
var buildMarketBreadthContextFromSignal = (signal) => {
|
|
2750
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2751
|
+
const relative = toRecord(baseContext?.relative);
|
|
2752
|
+
const breadth = toRecord(relative?.marketBreadth);
|
|
2753
|
+
if (!breadth) {
|
|
2754
|
+
return {
|
|
2755
|
+
source: null,
|
|
2756
|
+
available: false,
|
|
2757
|
+
universe: null,
|
|
2758
|
+
interval: null,
|
|
2759
|
+
stale: null,
|
|
2760
|
+
symbolsCount: null,
|
|
2761
|
+
advanceDeclineRatio: null,
|
|
2762
|
+
pctAboveMa20: null,
|
|
2763
|
+
pctAboveMa50: null,
|
|
2764
|
+
equalWeightedReturn: null,
|
|
2765
|
+
volumeWeightedReturn: null,
|
|
2766
|
+
dispersion: null
|
|
2767
|
+
};
|
|
2768
|
+
}
|
|
2769
|
+
return {
|
|
2770
|
+
source: String(breadth.source ?? ""),
|
|
2771
|
+
available: true,
|
|
2772
|
+
universe: String(breadth.universe ?? ""),
|
|
2773
|
+
interval: String(breadth.interval ?? ""),
|
|
2774
|
+
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
2775
|
+
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
2776
|
+
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
2777
|
+
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
2778
|
+
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
2779
|
+
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
2780
|
+
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
2781
|
+
dispersion: toFiniteNumber(breadth.dispersion)
|
|
2782
|
+
};
|
|
2188
2783
|
};
|
|
2189
|
-
var
|
|
2190
|
-
const
|
|
2191
|
-
const
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
const
|
|
2195
|
-
if (
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2784
|
+
var buildMarketBreadthsContextFromSignal = (signal) => {
|
|
2785
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2786
|
+
const relative = toRecord(baseContext?.relative);
|
|
2787
|
+
const breadths = toRecord(relative?.marketBreadths);
|
|
2788
|
+
const build = (key) => {
|
|
2789
|
+
const breadth = toRecord(breadths?.[key]);
|
|
2790
|
+
if (!breadth) {
|
|
2791
|
+
return {
|
|
2792
|
+
source: null,
|
|
2793
|
+
available: false,
|
|
2794
|
+
universe: null,
|
|
2795
|
+
interval: null,
|
|
2796
|
+
stale: null,
|
|
2797
|
+
symbolsCount: null,
|
|
2798
|
+
advanceDeclineRatio: null,
|
|
2799
|
+
pctAboveMa20: null,
|
|
2800
|
+
pctAboveMa50: null,
|
|
2801
|
+
equalWeightedReturn: null,
|
|
2802
|
+
volumeWeightedReturn: null,
|
|
2803
|
+
dispersion: null
|
|
2804
|
+
};
|
|
2210
2805
|
}
|
|
2211
|
-
|
|
2212
|
-
|
|
2806
|
+
return {
|
|
2807
|
+
source: String(breadth.source ?? ""),
|
|
2808
|
+
available: true,
|
|
2809
|
+
universe: String(breadth.universe ?? ""),
|
|
2810
|
+
interval: String(breadth.interval ?? ""),
|
|
2811
|
+
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
2812
|
+
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
2813
|
+
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
2814
|
+
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
2815
|
+
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
2816
|
+
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
2817
|
+
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
2818
|
+
dispersion: toFiniteNumber(breadth.dispersion)
|
|
2819
|
+
};
|
|
2820
|
+
};
|
|
2821
|
+
return {
|
|
2822
|
+
top5: build("top5"),
|
|
2823
|
+
top10: build("top10"),
|
|
2824
|
+
top30: build("top30"),
|
|
2825
|
+
top50: build("top50"),
|
|
2826
|
+
top100: build("top100")
|
|
2827
|
+
};
|
|
2213
2828
|
};
|
|
2214
|
-
var
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2829
|
+
var buildTargetVsReference = (signal, asset) => {
|
|
2830
|
+
const base = toRecord(signal.additionalIndicators?.baseContext);
|
|
2831
|
+
const relative = toRecord(base?.relative);
|
|
2832
|
+
const target = toRecord(relative?.[`targetVs${asset}`]);
|
|
2833
|
+
return {
|
|
2834
|
+
source: target ? String(target.source ?? "") : null,
|
|
2835
|
+
available: Boolean(target),
|
|
2836
|
+
ratioReturn1h: toFiniteNumber(target?.ratioReturn1h),
|
|
2837
|
+
ratioReturn4h: toFiniteNumber(target?.ratioReturn4h),
|
|
2838
|
+
ratioReturn24h: toFiniteNumber(target?.ratioReturn24h),
|
|
2839
|
+
alpha1h: toFiniteNumber(target?.[`alphaVs${asset}1h`]),
|
|
2840
|
+
alpha4h: toFiniteNumber(target?.[`alphaVs${asset}4h`]),
|
|
2841
|
+
alpha24h: toFiniteNumber(target?.[`alphaVs${asset}24h`]),
|
|
2842
|
+
beta: toFiniteNumber(target?.[`betaTo${asset}20`]),
|
|
2843
|
+
correlation: toFiniteNumber(target?.[`correlationTo${asset}20`]),
|
|
2844
|
+
ratioTrend: typeof target?.ratioTrend === "string" ? target.ratioTrend : null
|
|
2845
|
+
};
|
|
2222
2846
|
};
|
|
2223
|
-
var
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
defaults: entry.defaults,
|
|
2234
|
-
createCore: entry.createCore,
|
|
2235
|
-
manifest: entry.manifest,
|
|
2236
|
-
detectorKey: entry.detectorKey,
|
|
2237
|
-
detectorNoSignalSkipReason: entry.detectorNoSignalSkipReason,
|
|
2238
|
-
resolveRegisteredManifest: (name) => state.strategyManifestsMap.get(name)
|
|
2239
|
-
})
|
|
2240
|
-
);
|
|
2847
|
+
var buildTargetVsBtcContextFromSignal = (signal) => {
|
|
2848
|
+
const { alpha1h, alpha4h, alpha24h, beta, correlation, ...shared } = buildTargetVsReference(signal, "Btc");
|
|
2849
|
+
return {
|
|
2850
|
+
...shared,
|
|
2851
|
+
alphaVsBtc1h: alpha1h,
|
|
2852
|
+
alphaVsBtc4h: alpha4h,
|
|
2853
|
+
alphaVsBtc24h: alpha24h,
|
|
2854
|
+
betaToBtc20: beta,
|
|
2855
|
+
correlationToBtc20: correlation
|
|
2856
|
+
};
|
|
2241
2857
|
};
|
|
2242
|
-
var
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2858
|
+
var buildTargetVsEthContextFromSignal = (signal) => {
|
|
2859
|
+
const { alpha1h, alpha4h, alpha24h, beta, correlation, ...shared } = buildTargetVsReference(signal, "Eth");
|
|
2860
|
+
return {
|
|
2861
|
+
...shared,
|
|
2862
|
+
alphaVsEth1h: alpha1h,
|
|
2863
|
+
alphaVsEth4h: alpha4h,
|
|
2864
|
+
alphaVsEth24h: alpha24h,
|
|
2865
|
+
betaToEth20: beta,
|
|
2866
|
+
correlationToEth20: correlation
|
|
2867
|
+
};
|
|
2249
2868
|
};
|
|
2250
|
-
var
|
|
2251
|
-
|
|
2252
|
-
|
|
2869
|
+
var buildBtcAltRegimeContextFromSignal = (signal) => {
|
|
2870
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2871
|
+
const relative = toRecord(baseContext?.relative);
|
|
2872
|
+
const btcAltRegime = toRecord(relative?.btcAltRegime);
|
|
2873
|
+
if (!btcAltRegime) {
|
|
2874
|
+
return {
|
|
2875
|
+
source: null,
|
|
2876
|
+
available: false,
|
|
2877
|
+
universe: null,
|
|
2878
|
+
interval: null,
|
|
2879
|
+
stale: null,
|
|
2880
|
+
regime: null,
|
|
2881
|
+
btcReturn24h: null,
|
|
2882
|
+
altBasketReturn24h: null,
|
|
2883
|
+
btcVsAltReturn24h: null,
|
|
2884
|
+
btcTurnoverShare24h: null,
|
|
2885
|
+
btcTurnoverShareChange24h: null,
|
|
2886
|
+
altVolToBtcVol24h: null,
|
|
2887
|
+
altDispersion24h: null
|
|
2888
|
+
};
|
|
2253
2889
|
}
|
|
2254
|
-
return
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2890
|
+
return {
|
|
2891
|
+
source: String(btcAltRegime.source ?? ""),
|
|
2892
|
+
available: true,
|
|
2893
|
+
universe: String(btcAltRegime.universe ?? ""),
|
|
2894
|
+
interval: String(btcAltRegime.interval ?? ""),
|
|
2895
|
+
stale: typeof btcAltRegime.stale === "boolean" ? btcAltRegime.stale : null,
|
|
2896
|
+
regime: typeof btcAltRegime.regime === "string" ? btcAltRegime.regime : null,
|
|
2897
|
+
btcReturn24h: toFiniteNumber(btcAltRegime.btcReturn24h),
|
|
2898
|
+
altBasketReturn24h: toFiniteNumber(btcAltRegime.altBasketReturn24h),
|
|
2899
|
+
btcVsAltReturn24h: toFiniteNumber(btcAltRegime.btcVsAltReturn24h),
|
|
2900
|
+
btcTurnoverShare24h: toFiniteNumber(btcAltRegime.btcTurnoverShare24h),
|
|
2901
|
+
btcTurnoverShareChange24h: toFiniteNumber(
|
|
2902
|
+
btcAltRegime.btcTurnoverShareChange24h
|
|
2903
|
+
),
|
|
2904
|
+
altVolToBtcVol24h: toFiniteNumber(btcAltRegime.altVolToBtcVol24h),
|
|
2905
|
+
altDispersion24h: toFiniteNumber(btcAltRegime.altDispersion24h)
|
|
2906
|
+
};
|
|
2258
2907
|
};
|
|
2259
|
-
var
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
const { projectRoot, state } = getStrategyRegistryState(cwd);
|
|
2265
|
-
const source = moduleName.trim();
|
|
2266
|
-
const resolvedModuleName = resolvePluginModuleSpecifier(source, projectRoot);
|
|
2267
|
-
const cacheKey = `${resolvedModuleName}\0${strategyName}`;
|
|
2268
|
-
const cached = state.strategyModuleResolutions.get(cacheKey);
|
|
2269
|
-
if (cached) return cached;
|
|
2270
|
-
const resolution = (async () => {
|
|
2271
|
-
const moduleExport = await importStrategyPluginModule(
|
|
2272
|
-
resolvedModuleName,
|
|
2273
|
-
projectRoot
|
|
2274
|
-
);
|
|
2275
|
-
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
2276
|
-
if (!pluginDefinition) {
|
|
2277
|
-
throw new Error(`${source}: export { strategyEntries } is missing`);
|
|
2278
|
-
}
|
|
2279
|
-
const issues = validateStrategyEntries(
|
|
2280
|
-
source,
|
|
2281
|
-
pluginDefinition.strategyEntries
|
|
2282
|
-
);
|
|
2283
|
-
if (issues.length > 0) {
|
|
2284
|
-
throw new Error(
|
|
2285
|
-
["Invalid TradeJS strategy module:", ...issues].join("\n")
|
|
2286
|
-
);
|
|
2287
|
-
}
|
|
2288
|
-
const entry = pluginDefinition.strategyEntries.find(
|
|
2289
|
-
(candidate) => candidate.manifest.name === strategyName
|
|
2290
|
-
);
|
|
2291
|
-
if (!entry) {
|
|
2292
|
-
throw new Error(`${source}: strategy ${strategyName} is missing`);
|
|
2293
|
-
}
|
|
2294
|
-
const factory = sharedStrategyRegistry.strategyRuntimeFactory;
|
|
2295
|
-
if (!factory) {
|
|
2296
|
-
throw new Error("Strategy runtime factory is not configured");
|
|
2297
|
-
}
|
|
2298
|
-
const manifestsByName = new Map(
|
|
2299
|
-
pluginDefinition.strategyEntries.map(
|
|
2300
|
-
(candidate) => [candidate.manifest.name, candidate.manifest]
|
|
2301
|
-
)
|
|
2302
|
-
);
|
|
2908
|
+
var buildCmcGlobalContextFromSignal = (signal) => {
|
|
2909
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2910
|
+
const relative = toRecord(baseContext?.relative);
|
|
2911
|
+
const cmcGlobal = toRecord(relative?.cmcGlobal);
|
|
2912
|
+
if (!cmcGlobal) {
|
|
2303
2913
|
return {
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2914
|
+
source: null,
|
|
2915
|
+
available: false,
|
|
2916
|
+
interval: null,
|
|
2917
|
+
asOfTs: null,
|
|
2918
|
+
stale: null,
|
|
2919
|
+
totalMarketCapUsd: null,
|
|
2920
|
+
totalVolumeUsd: null,
|
|
2921
|
+
totalVolumeReportedUsd: null,
|
|
2922
|
+
altMarketCapUsd: null,
|
|
2923
|
+
altVolumeUsd: null,
|
|
2924
|
+
altVolumeReportedUsd: null,
|
|
2925
|
+
btcDominancePct: null,
|
|
2926
|
+
ethDominancePct: null,
|
|
2927
|
+
btcDominanceChange24hPct: null,
|
|
2928
|
+
ethDominanceChange24hPct: null,
|
|
2929
|
+
altMarketCapChange24hPct: null,
|
|
2930
|
+
altVolumeChange24hPct: null,
|
|
2931
|
+
activeCryptocurrencies: null,
|
|
2932
|
+
activeExchanges: null,
|
|
2933
|
+
activeMarketPairs: null,
|
|
2934
|
+
altLiquidityRegime: null
|
|
2315
2935
|
};
|
|
2316
|
-
})();
|
|
2317
|
-
state.strategyModuleResolutions.set(cacheKey, resolution);
|
|
2318
|
-
try {
|
|
2319
|
-
return await resolution;
|
|
2320
|
-
} catch (error) {
|
|
2321
|
-
state.strategyModuleResolutions.delete(cacheKey);
|
|
2322
|
-
throw error;
|
|
2323
2936
|
}
|
|
2937
|
+
return {
|
|
2938
|
+
source: String(cmcGlobal.source ?? ""),
|
|
2939
|
+
available: true,
|
|
2940
|
+
interval: typeof cmcGlobal.interval === "string" ? cmcGlobal.interval : null,
|
|
2941
|
+
asOfTs: toFiniteNumber(cmcGlobal.asOfTs),
|
|
2942
|
+
stale: typeof cmcGlobal.stale === "boolean" ? cmcGlobal.stale : null,
|
|
2943
|
+
totalMarketCapUsd: toFiniteNumber(cmcGlobal.totalMarketCapUsd),
|
|
2944
|
+
totalVolumeUsd: toFiniteNumber(cmcGlobal.totalVolumeUsd),
|
|
2945
|
+
totalVolumeReportedUsd: toFiniteNumber(cmcGlobal.totalVolumeReportedUsd),
|
|
2946
|
+
altMarketCapUsd: toFiniteNumber(cmcGlobal.altMarketCapUsd),
|
|
2947
|
+
altVolumeUsd: toFiniteNumber(cmcGlobal.altVolumeUsd),
|
|
2948
|
+
altVolumeReportedUsd: toFiniteNumber(cmcGlobal.altVolumeReportedUsd),
|
|
2949
|
+
btcDominancePct: toFiniteNumber(cmcGlobal.btcDominancePct),
|
|
2950
|
+
ethDominancePct: toFiniteNumber(cmcGlobal.ethDominancePct),
|
|
2951
|
+
btcDominanceChange24hPct: toFiniteNumber(
|
|
2952
|
+
cmcGlobal.btcDominanceChange24hPct
|
|
2953
|
+
),
|
|
2954
|
+
ethDominanceChange24hPct: toFiniteNumber(
|
|
2955
|
+
cmcGlobal.ethDominanceChange24hPct
|
|
2956
|
+
),
|
|
2957
|
+
altMarketCapChange24hPct: toFiniteNumber(
|
|
2958
|
+
cmcGlobal.altMarketCapChange24hPct
|
|
2959
|
+
),
|
|
2960
|
+
altVolumeChange24hPct: toFiniteNumber(cmcGlobal.altVolumeChange24hPct),
|
|
2961
|
+
activeCryptocurrencies: toFiniteNumber(cmcGlobal.activeCryptocurrencies),
|
|
2962
|
+
activeExchanges: toFiniteNumber(cmcGlobal.activeExchanges),
|
|
2963
|
+
activeMarketPairs: toFiniteNumber(cmcGlobal.activeMarketPairs),
|
|
2964
|
+
altLiquidityRegime: typeof cmcGlobal.altLiquidityRegime === "string" ? cmcGlobal.altLiquidityRegime : null
|
|
2965
|
+
};
|
|
2324
2966
|
};
|
|
2325
|
-
var
|
|
2326
|
-
const
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
resolvedModuleName,
|
|
2348
|
-
projectRoot
|
|
2349
|
-
);
|
|
2350
|
-
if (strategySet.has(moduleName)) {
|
|
2351
|
-
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
2352
|
-
if (!pluginDefinition) {
|
|
2353
|
-
issues.push(
|
|
2354
|
-
`${moduleName}: export { strategyEntries } is missing`
|
|
2355
|
-
);
|
|
2356
|
-
} else {
|
|
2357
|
-
const entryIssues = validateStrategyEntries(
|
|
2358
|
-
moduleName,
|
|
2359
|
-
pluginDefinition.strategyEntries,
|
|
2360
|
-
state
|
|
2361
|
-
);
|
|
2362
|
-
issues.push(...entryIssues);
|
|
2363
|
-
if (entryIssues.length === 0) {
|
|
2364
|
-
registerEntries(
|
|
2365
|
-
pluginDefinition.strategyEntries,
|
|
2366
|
-
moduleName,
|
|
2367
|
-
state
|
|
2368
|
-
);
|
|
2369
|
-
}
|
|
2370
|
-
}
|
|
2371
|
-
}
|
|
2372
|
-
if (indicatorSet.has(moduleName)) {
|
|
2373
|
-
const indicatorPluginDefinition = extractIndicatorPluginDefinition(moduleExport);
|
|
2374
|
-
if (!indicatorPluginDefinition) {
|
|
2375
|
-
issues.push(
|
|
2376
|
-
`${moduleName}: export { indicatorEntries } is missing`
|
|
2377
|
-
);
|
|
2378
|
-
} else {
|
|
2379
|
-
(0, import_indicators.registerIndicatorEntries)(
|
|
2380
|
-
indicatorPluginDefinition.indicatorEntries,
|
|
2381
|
-
moduleName,
|
|
2382
|
-
projectRoot
|
|
2383
|
-
);
|
|
2384
|
-
}
|
|
2385
|
-
}
|
|
2386
|
-
if (!strategySet.has(moduleName) && !indicatorSet.has(moduleName)) {
|
|
2387
|
-
issues.push(`${moduleName}: plugin is not declared in config`);
|
|
2388
|
-
}
|
|
2389
|
-
} catch (error) {
|
|
2390
|
-
issues.push(`${moduleName}: failed to import: ${String(error)}`);
|
|
2391
|
-
}
|
|
2392
|
-
}
|
|
2393
|
-
if (issues.length > 0) {
|
|
2394
|
-
throw new Error(
|
|
2395
|
-
["Invalid TradeJS plugin catalog:", ...issues].join("\n")
|
|
2396
|
-
);
|
|
2397
|
-
}
|
|
2398
|
-
})();
|
|
2967
|
+
var buildCmcReferenceAssetsContextFromSignal = (signal) => {
|
|
2968
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2969
|
+
const relative = toRecord(baseContext?.relative);
|
|
2970
|
+
const cmcReferenceAssets = toRecord(relative?.cmcReferenceAssets);
|
|
2971
|
+
if (!cmcReferenceAssets) {
|
|
2972
|
+
return {
|
|
2973
|
+
source: null,
|
|
2974
|
+
available: false,
|
|
2975
|
+
interval: null,
|
|
2976
|
+
asOfTs: null,
|
|
2977
|
+
stale: null,
|
|
2978
|
+
btcMarketCapUsd: null,
|
|
2979
|
+
ethMarketCapUsd: null,
|
|
2980
|
+
btcVolumeUsd: null,
|
|
2981
|
+
ethVolumeUsd: null,
|
|
2982
|
+
btcVolumeToMarketCap: null,
|
|
2983
|
+
ethVolumeToMarketCap: null,
|
|
2984
|
+
ethBtcMarketCapRatio: null,
|
|
2985
|
+
ethBtcMarketCapRatioChange24hPct: null,
|
|
2986
|
+
ethVsBtcVolumeRatio: null,
|
|
2987
|
+
referenceLiquidityRegime: null
|
|
2988
|
+
};
|
|
2399
2989
|
}
|
|
2400
|
-
|
|
2990
|
+
return {
|
|
2991
|
+
source: String(cmcReferenceAssets.source ?? ""),
|
|
2992
|
+
available: true,
|
|
2993
|
+
interval: typeof cmcReferenceAssets.interval === "string" ? cmcReferenceAssets.interval : null,
|
|
2994
|
+
asOfTs: toFiniteNumber(cmcReferenceAssets.asOfTs),
|
|
2995
|
+
stale: typeof cmcReferenceAssets.stale === "boolean" ? cmcReferenceAssets.stale : null,
|
|
2996
|
+
btcMarketCapUsd: toFiniteNumber(cmcReferenceAssets.btcMarketCapUsd),
|
|
2997
|
+
ethMarketCapUsd: toFiniteNumber(cmcReferenceAssets.ethMarketCapUsd),
|
|
2998
|
+
btcVolumeUsd: toFiniteNumber(cmcReferenceAssets.btcVolumeUsd),
|
|
2999
|
+
ethVolumeUsd: toFiniteNumber(cmcReferenceAssets.ethVolumeUsd),
|
|
3000
|
+
btcVolumeToMarketCap: toFiniteNumber(
|
|
3001
|
+
cmcReferenceAssets.btcVolumeToMarketCap
|
|
3002
|
+
),
|
|
3003
|
+
ethVolumeToMarketCap: toFiniteNumber(
|
|
3004
|
+
cmcReferenceAssets.ethVolumeToMarketCap
|
|
3005
|
+
),
|
|
3006
|
+
ethBtcMarketCapRatio: toFiniteNumber(
|
|
3007
|
+
cmcReferenceAssets.ethBtcMarketCapRatio
|
|
3008
|
+
),
|
|
3009
|
+
ethBtcMarketCapRatioChange24hPct: toFiniteNumber(
|
|
3010
|
+
cmcReferenceAssets.ethBtcMarketCapRatioChange24hPct
|
|
3011
|
+
),
|
|
3012
|
+
ethVsBtcVolumeRatio: toFiniteNumber(cmcReferenceAssets.ethVsBtcVolumeRatio),
|
|
3013
|
+
referenceLiquidityRegime: typeof cmcReferenceAssets.referenceLiquidityRegime === "string" ? cmcReferenceAssets.referenceLiquidityRegime : null
|
|
3014
|
+
};
|
|
2401
3015
|
};
|
|
2402
|
-
var
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
3016
|
+
var buildCmcExchangeLiquidityContextFromSignal = (signal) => {
|
|
3017
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3018
|
+
const relative = toRecord(baseContext?.relative);
|
|
3019
|
+
const cmcExchangeLiquidity = toRecord(relative?.cmcExchangeLiquidity);
|
|
3020
|
+
if (!cmcExchangeLiquidity) {
|
|
3021
|
+
return {
|
|
3022
|
+
source: null,
|
|
3023
|
+
available: false,
|
|
3024
|
+
interval: null,
|
|
3025
|
+
asOfTs: null,
|
|
3026
|
+
stale: null,
|
|
3027
|
+
exchangesCount: null,
|
|
3028
|
+
totalVolumeUsd: null,
|
|
3029
|
+
totalVolumeChange24hPct: null,
|
|
3030
|
+
binanceVolumeUsd: null,
|
|
3031
|
+
binanceVolumeShare: null,
|
|
3032
|
+
topExchangeVolumeShare: null,
|
|
3033
|
+
liquidityRegime: null
|
|
3034
|
+
};
|
|
2406
3035
|
}
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
3036
|
+
return {
|
|
3037
|
+
source: String(cmcExchangeLiquidity.source ?? ""),
|
|
3038
|
+
available: true,
|
|
3039
|
+
interval: typeof cmcExchangeLiquidity.interval === "string" ? cmcExchangeLiquidity.interval : null,
|
|
3040
|
+
asOfTs: toFiniteNumber(cmcExchangeLiquidity.asOfTs),
|
|
3041
|
+
stale: typeof cmcExchangeLiquidity.stale === "boolean" ? cmcExchangeLiquidity.stale : null,
|
|
3042
|
+
exchangesCount: toFiniteNumber(cmcExchangeLiquidity.exchangesCount),
|
|
3043
|
+
totalVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.totalVolumeUsd),
|
|
3044
|
+
totalVolumeChange24hPct: toFiniteNumber(
|
|
3045
|
+
cmcExchangeLiquidity.totalVolumeChange24hPct
|
|
3046
|
+
),
|
|
3047
|
+
binanceVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeUsd),
|
|
3048
|
+
binanceVolumeShare: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeShare),
|
|
3049
|
+
topExchangeVolumeShare: toFiniteNumber(
|
|
3050
|
+
cmcExchangeLiquidity.topExchangeVolumeShare
|
|
3051
|
+
),
|
|
3052
|
+
liquidityRegime: typeof cmcExchangeLiquidity.liquidityRegime === "string" ? cmcExchangeLiquidity.liquidityRegime : null
|
|
3053
|
+
};
|
|
2410
3054
|
};
|
|
2411
|
-
var
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
3055
|
+
var buildCmcFearGreedContextFromSignal = (signal) => {
|
|
3056
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3057
|
+
const relative = toRecord(baseContext?.relative);
|
|
3058
|
+
const cmcFearGreed = toRecord(relative?.cmcFearGreed);
|
|
3059
|
+
if (!cmcFearGreed) {
|
|
3060
|
+
return {
|
|
3061
|
+
source: null,
|
|
3062
|
+
available: false,
|
|
3063
|
+
interval: null,
|
|
3064
|
+
asOfTs: null,
|
|
3065
|
+
stale: null,
|
|
3066
|
+
value: null,
|
|
3067
|
+
valueChange24h: null,
|
|
3068
|
+
valueChange7d: null,
|
|
3069
|
+
classification: null,
|
|
3070
|
+
sentimentRegime: null
|
|
3071
|
+
};
|
|
2415
3072
|
}
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
3073
|
+
return {
|
|
3074
|
+
source: String(cmcFearGreed.source ?? ""),
|
|
3075
|
+
available: true,
|
|
3076
|
+
interval: typeof cmcFearGreed.interval === "string" ? cmcFearGreed.interval : null,
|
|
3077
|
+
asOfTs: toFiniteNumber(cmcFearGreed.asOfTs),
|
|
3078
|
+
stale: typeof cmcFearGreed.stale === "boolean" ? cmcFearGreed.stale : null,
|
|
3079
|
+
value: toFiniteNumber(cmcFearGreed.value),
|
|
3080
|
+
valueChange24h: toFiniteNumber(cmcFearGreed.valueChange24h),
|
|
3081
|
+
valueChange7d: toFiniteNumber(cmcFearGreed.valueChange7d),
|
|
3082
|
+
classification: typeof cmcFearGreed.classification === "string" ? cmcFearGreed.classification : null,
|
|
3083
|
+
sentimentRegime: typeof cmcFearGreed.sentimentRegime === "string" ? cmcFearGreed.sentimentRegime : null
|
|
3084
|
+
};
|
|
2419
3085
|
};
|
|
2420
|
-
var
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
3086
|
+
var buildCmcIndexesContextFromSignal = (signal) => {
|
|
3087
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3088
|
+
const relative = toRecord(baseContext?.relative);
|
|
3089
|
+
const cmcIndexes = toRecord(relative?.cmcIndexes);
|
|
3090
|
+
if (!cmcIndexes) {
|
|
3091
|
+
return {
|
|
3092
|
+
source: null,
|
|
3093
|
+
available: false,
|
|
3094
|
+
interval: null,
|
|
3095
|
+
asOfTs: null,
|
|
3096
|
+
stale: null,
|
|
3097
|
+
cmc100Value: null,
|
|
3098
|
+
cmc100Change24hPct: null,
|
|
3099
|
+
cmc100TopConstituentSymbol: null,
|
|
3100
|
+
cmc100TopConstituentWeightPct: null,
|
|
3101
|
+
cmc20Value: null,
|
|
3102
|
+
cmc20Change24hPct: null,
|
|
3103
|
+
cmc20TopConstituentSymbol: null,
|
|
3104
|
+
cmc20TopConstituentWeightPct: null,
|
|
3105
|
+
cmc20ToCmc100Ratio: null,
|
|
3106
|
+
cmc20ToCmc100RatioChange24hPct: null,
|
|
3107
|
+
indexRegime: null
|
|
3108
|
+
};
|
|
2424
3109
|
}
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
3110
|
+
return {
|
|
3111
|
+
source: String(cmcIndexes.source ?? ""),
|
|
3112
|
+
available: true,
|
|
3113
|
+
interval: typeof cmcIndexes.interval === "string" ? cmcIndexes.interval : null,
|
|
3114
|
+
asOfTs: toFiniteNumber(cmcIndexes.asOfTs),
|
|
3115
|
+
stale: typeof cmcIndexes.stale === "boolean" ? cmcIndexes.stale : null,
|
|
3116
|
+
cmc100Value: toFiniteNumber(cmcIndexes.cmc100Value),
|
|
3117
|
+
cmc100Change24hPct: toFiniteNumber(cmcIndexes.cmc100Change24hPct),
|
|
3118
|
+
cmc100TopConstituentSymbol: typeof cmcIndexes.cmc100TopConstituentSymbol === "string" ? cmcIndexes.cmc100TopConstituentSymbol : null,
|
|
3119
|
+
cmc100TopConstituentWeightPct: toFiniteNumber(
|
|
3120
|
+
cmcIndexes.cmc100TopConstituentWeightPct
|
|
3121
|
+
),
|
|
3122
|
+
cmc20Value: toFiniteNumber(cmcIndexes.cmc20Value),
|
|
3123
|
+
cmc20Change24hPct: toFiniteNumber(cmcIndexes.cmc20Change24hPct),
|
|
3124
|
+
cmc20TopConstituentSymbol: typeof cmcIndexes.cmc20TopConstituentSymbol === "string" ? cmcIndexes.cmc20TopConstituentSymbol : null,
|
|
3125
|
+
cmc20TopConstituentWeightPct: toFiniteNumber(
|
|
3126
|
+
cmcIndexes.cmc20TopConstituentWeightPct
|
|
3127
|
+
),
|
|
3128
|
+
cmc20ToCmc100Ratio: toFiniteNumber(cmcIndexes.cmc20ToCmc100Ratio),
|
|
3129
|
+
cmc20ToCmc100RatioChange24hPct: toFiniteNumber(
|
|
3130
|
+
cmcIndexes.cmc20ToCmc100RatioChange24hPct
|
|
3131
|
+
),
|
|
3132
|
+
indexRegime: typeof cmcIndexes.indexRegime === "string" ? cmcIndexes.indexRegime : null
|
|
3133
|
+
};
|
|
2428
3134
|
};
|
|
2429
|
-
var
|
|
2430
|
-
|
|
2431
|
-
|
|
3135
|
+
var buildReferenceTradeFlowContextFromSignal = (signal) => {
|
|
3136
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3137
|
+
const relative = toRecord(baseContext?.relative);
|
|
3138
|
+
const refs = toRecord(relative?.referenceTradeFlow);
|
|
3139
|
+
const primaryReferenceSymbol = typeof refs?.primaryReferenceSymbol === "string" ? refs.primaryReferenceSymbol : null;
|
|
3140
|
+
const tradeFlowBySymbol = toRecord(refs?.tradeFlowBySymbol);
|
|
3141
|
+
const primaryTradeFlow = primaryReferenceSymbol != null ? toRecord(tradeFlowBySymbol?.[primaryReferenceSymbol]) : null;
|
|
3142
|
+
if (!refs) {
|
|
3143
|
+
return {
|
|
3144
|
+
source: null,
|
|
3145
|
+
available: false,
|
|
3146
|
+
primaryReferenceSymbol: null,
|
|
3147
|
+
referenceSymbols: [],
|
|
3148
|
+
primaryTradeFlowBuyPressurePct: null,
|
|
3149
|
+
primaryTradeFlowStale: null
|
|
3150
|
+
};
|
|
2432
3151
|
}
|
|
2433
|
-
|
|
2434
|
-
|
|
3152
|
+
return {
|
|
3153
|
+
source: String(refs.source ?? ""),
|
|
3154
|
+
available: true,
|
|
3155
|
+
primaryReferenceSymbol,
|
|
3156
|
+
referenceSymbols: Array.isArray(refs.referenceSymbols) ? refs.referenceSymbols.map(String) : [],
|
|
3157
|
+
primaryTradeFlowBuyPressurePct: toFiniteNumber(
|
|
3158
|
+
primaryTradeFlow?.buyPressurePct
|
|
3159
|
+
),
|
|
3160
|
+
primaryTradeFlowStale: typeof primaryTradeFlow?.stale === "boolean" ? primaryTradeFlow.stale : null
|
|
3161
|
+
};
|
|
2435
3162
|
};
|
|
2436
|
-
var
|
|
2437
|
-
{
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
3163
|
+
var buildAiMarketContext = (signal) => ({
|
|
3164
|
+
execution: {
|
|
3165
|
+
binanceCoinbaseSpread: buildSpreadContextFromSignal(signal)
|
|
3166
|
+
},
|
|
3167
|
+
participation: {
|
|
3168
|
+
trueDelta: buildTrueDeltaContextFromSignal(signal),
|
|
3169
|
+
tradeFlow: buildTradeFlowContextFromSignal(signal)
|
|
3170
|
+
},
|
|
3171
|
+
relative: {
|
|
3172
|
+
marketBreadth: buildMarketBreadthContextFromSignal(signal),
|
|
3173
|
+
marketBreadths: buildMarketBreadthsContextFromSignal(signal),
|
|
3174
|
+
targetVsBtc: buildTargetVsBtcContextFromSignal(signal),
|
|
3175
|
+
targetVsEth: buildTargetVsEthContextFromSignal(signal),
|
|
3176
|
+
btcAltRegime: buildBtcAltRegimeContextFromSignal(signal),
|
|
3177
|
+
cmcGlobal: buildCmcGlobalContextFromSignal(signal),
|
|
3178
|
+
cmcReferenceAssets: buildCmcReferenceAssetsContextFromSignal(signal),
|
|
3179
|
+
cmcExchangeLiquidity: buildCmcExchangeLiquidityContextFromSignal(signal),
|
|
3180
|
+
cmcFearGreed: buildCmcFearGreedContextFromSignal(signal),
|
|
3181
|
+
cmcIndexes: buildCmcIndexesContextFromSignal(signal),
|
|
3182
|
+
referenceTradeFlow: buildReferenceTradeFlowContextFromSignal(signal)
|
|
2452
3183
|
}
|
|
2453
|
-
);
|
|
3184
|
+
});
|
|
2454
3185
|
|
|
2455
3186
|
// src/strategy/policyProfiles.ts
|
|
2456
3187
|
var profileMatches = (profile, universe, assetClass) => {
|
|
@@ -2552,12 +3283,61 @@ var buildBaseAiPayload = (signal) => {
|
|
|
2552
3283
|
};
|
|
2553
3284
|
};
|
|
2554
3285
|
var defaultAiAdapter = {};
|
|
3286
|
+
var tickerProfileFeatureIds = new Set(
|
|
3287
|
+
import_aiFeatures.TICKER_PROFILE_AI_FEATURES.map(({ id }) => id)
|
|
3288
|
+
);
|
|
2555
3289
|
var getStrategyAiAdapter = (strategy, profileId) => getStrategyProfileAiAdapter(getStrategyManifest(strategy), profileId) ?? defaultAiAdapter;
|
|
2556
3290
|
var getSignalAiAdapter = (signal) => getStrategyAiAdapter(signal.strategy, signal.policyProfileId);
|
|
2557
3291
|
var buildAiPayloadByStrategy = (signal) => {
|
|
2558
3292
|
const basePayload = buildBaseAiPayload(signal);
|
|
2559
3293
|
const adapter = getSignalAiAdapter(signal);
|
|
2560
|
-
|
|
3294
|
+
const payload = adapter.buildPayload?.({ signal, basePayload }) ?? basePayload;
|
|
3295
|
+
const custom = getStrategyManifest(signal.strategy)?.aiFeatures ?? [];
|
|
3296
|
+
const frozen = signal.aiFeatureReplay?.snapshot;
|
|
3297
|
+
if (frozen) (0, import_aiFeatures.validateAiFeatureSnapshot)(frozen);
|
|
3298
|
+
const tickerProfile = signal.additionalIndicators?.tickerProfile ?? signal.additionalIndicators?.baseContext?.tickerProfile;
|
|
3299
|
+
const hasProfile = Boolean(
|
|
3300
|
+
tickerProfile || frozen?.definitions.some(({ id }) => tickerProfileFeatureIds.has(id))
|
|
3301
|
+
);
|
|
3302
|
+
const indicatorFeatures = (0, import_indicators2.getRegisteredIndicatorEntries)(
|
|
3303
|
+
getTradejsProjectCwd()
|
|
3304
|
+
).flatMap((entry) => entry.indicator.enabled ? entry.aiFeatures ?? [] : []);
|
|
3305
|
+
const hasPortfolio = Boolean(
|
|
3306
|
+
signal.additionalIndicators?.portfolioContext || frozen?.definitions.some(
|
|
3307
|
+
({ id }) => import_aiFeatures.PORTFOLIO_AI_FEATURES.some((d) => d.id === id)
|
|
3308
|
+
)
|
|
3309
|
+
);
|
|
3310
|
+
const definitions = [
|
|
3311
|
+
...hasPortfolio ? import_aiFeatures.PORTFOLIO_AI_FEATURES : [],
|
|
3312
|
+
...hasProfile ? import_aiFeatures.TICKER_PROFILE_AI_FEATURES : [],
|
|
3313
|
+
...indicatorFeatures,
|
|
3314
|
+
...custom
|
|
3315
|
+
];
|
|
3316
|
+
if (!definitions.length && !signal.aiFeatureReplay?.snapshot) return payload;
|
|
3317
|
+
(0, import_aiFeatures.validateAiFeatureDefinitions)(definitions);
|
|
3318
|
+
if (signal.aiFeatureReplay && !frozen) {
|
|
3319
|
+
throw new Error(
|
|
3320
|
+
"Dataset has no frozen AI features; regenerate the decision-time export"
|
|
3321
|
+
);
|
|
3322
|
+
}
|
|
3323
|
+
if (frozen && JSON.stringify(frozen.definitions) !== JSON.stringify(definitions)) {
|
|
3324
|
+
throw new Error(
|
|
3325
|
+
"AI feature definition changed; regenerate the decision-time export"
|
|
3326
|
+
);
|
|
3327
|
+
}
|
|
3328
|
+
return {
|
|
3329
|
+
...payload,
|
|
3330
|
+
featureSnapshot: frozen ?? (0, import_aiFeatures.projectAiFeatures)(definitions, {
|
|
3331
|
+
signal: tickerProfile ? {
|
|
3332
|
+
...signal,
|
|
3333
|
+
additionalIndicators: {
|
|
3334
|
+
...signal.additionalIndicators,
|
|
3335
|
+
tickerProfile
|
|
3336
|
+
}
|
|
3337
|
+
} : signal,
|
|
3338
|
+
payload
|
|
3339
|
+
})
|
|
3340
|
+
};
|
|
2561
3341
|
};
|
|
2562
3342
|
var buildAiSystemPromptAddonByStrategy = (signal) => getSignalAiAdapter(signal).buildSystemPromptAddon?.({ signal }) ?? "";
|
|
2563
3343
|
var buildAiHumanPromptAddonByStrategy = (signal, payload) => getSignalAiAdapter(signal).buildHumanPromptAddon?.({
|
|
@@ -3019,7 +3799,7 @@ var askAI = async (signal, options = {}) => {
|
|
|
3019
3799
|
payload
|
|
3020
3800
|
}
|
|
3021
3801
|
);
|
|
3022
|
-
await (0,
|
|
3802
|
+
await (0, import_redis3.setData)(import_redis3.redisKeys.analysis(symbol, signal.signalId), content);
|
|
3023
3803
|
return content;
|
|
3024
3804
|
};
|
|
3025
3805
|
|
|
@@ -3071,254 +3851,6 @@ var buildMlPayload = (payload) => {
|
|
|
3071
3851
|
};
|
|
3072
3852
|
};
|
|
3073
3853
|
|
|
3074
|
-
// src/runtimeJournal.ts
|
|
3075
|
-
var import_node_crypto = require("crypto");
|
|
3076
|
-
var import_constants = require("@tradejs/core/constants");
|
|
3077
|
-
var import_time = require("@tradejs/core/time");
|
|
3078
|
-
var import_trade = require("@tradejs/core/trade");
|
|
3079
|
-
var import_logger3 = require("@tradejs/infra/logger");
|
|
3080
|
-
var import_redis2 = require("@tradejs/infra/redis");
|
|
3081
|
-
var now = () => Date.now();
|
|
3082
|
-
var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
|
|
3083
|
-
var calculateClosedPnl = ({
|
|
3084
|
-
direction,
|
|
3085
|
-
entryPrice,
|
|
3086
|
-
exitPrice,
|
|
3087
|
-
qty
|
|
3088
|
-
}) => {
|
|
3089
|
-
const pnl = direction === "LONG" ? (exitPrice - entryPrice) * qty : (entryPrice - exitPrice) * qty;
|
|
3090
|
-
return Number.isFinite(pnl) ? pnl : null;
|
|
3091
|
-
};
|
|
3092
|
-
var createRuntimeOrderId = (strategy) => {
|
|
3093
|
-
const prefix = (0, import_trade.createRuntimeOrderLinkPrefix)(strategy);
|
|
3094
|
-
if (prefix === "tjs-") {
|
|
3095
|
-
return `tjs-${(0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 24).toLowerCase()}`;
|
|
3096
|
-
}
|
|
3097
|
-
return `${prefix}${toRandomOrderSuffix()}`;
|
|
3098
|
-
};
|
|
3099
|
-
var recordRuntimeTradeOpen = async (params) => {
|
|
3100
|
-
const { userName } = params;
|
|
3101
|
-
if (!userName) {
|
|
3102
|
-
return null;
|
|
3103
|
-
}
|
|
3104
|
-
const record = {
|
|
3105
|
-
...params,
|
|
3106
|
-
entryCount: 1,
|
|
3107
|
-
lastEntryPrice: params.entryPrice,
|
|
3108
|
-
lastEntryQty: params.qty,
|
|
3109
|
-
lastEntryTimestamp: params.entryTimestamp,
|
|
3110
|
-
status: "active",
|
|
3111
|
-
currentPrice: params.entryPrice,
|
|
3112
|
-
currentPnl: 0,
|
|
3113
|
-
closedPnl: null,
|
|
3114
|
-
exitPrice: null,
|
|
3115
|
-
exitTimestamp: null,
|
|
3116
|
-
lastSyncedAt: now()
|
|
3117
|
-
};
|
|
3118
|
-
const dayKey = (0, import_time.getRuntimeStorageDayKey)(record.entryTimestamp);
|
|
3119
|
-
const runtimeScopeId = record.deploymentId ?? record.accountId;
|
|
3120
|
-
try {
|
|
3121
|
-
await Promise.all([
|
|
3122
|
-
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, record.orderId), record, {
|
|
3123
|
-
expire: 0
|
|
3124
|
-
}),
|
|
3125
|
-
(0, import_redis2.setHashJsonField)(
|
|
3126
|
-
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
3127
|
-
record.orderId,
|
|
3128
|
-
record,
|
|
3129
|
-
{ expire: 0 }
|
|
3130
|
-
),
|
|
3131
|
-
(0, import_redis2.setData)(
|
|
3132
|
-
import_redis2.redisKeys.runtimeActiveTrade(userName, record.symbol, runtimeScopeId),
|
|
3133
|
-
{ orderId: record.orderId },
|
|
3134
|
-
{ expire: 0 }
|
|
3135
|
-
)
|
|
3136
|
-
]);
|
|
3137
|
-
} catch (error) {
|
|
3138
|
-
import_logger3.logger.error(
|
|
3139
|
-
"runtime trade open journal failed: %s %s",
|
|
3140
|
-
record.symbol,
|
|
3141
|
-
error?.message || String(error)
|
|
3142
|
-
);
|
|
3143
|
-
}
|
|
3144
|
-
return record;
|
|
3145
|
-
};
|
|
3146
|
-
var recordRuntimeTradeIncrease = async (params) => {
|
|
3147
|
-
const {
|
|
3148
|
-
userName,
|
|
3149
|
-
strategy,
|
|
3150
|
-
symbol,
|
|
3151
|
-
direction,
|
|
3152
|
-
resultingQty,
|
|
3153
|
-
resultingEntryPrice,
|
|
3154
|
-
addedQty,
|
|
3155
|
-
addedEntryPrice,
|
|
3156
|
-
entryTimestamp,
|
|
3157
|
-
fee,
|
|
3158
|
-
accountId,
|
|
3159
|
-
deploymentId
|
|
3160
|
-
} = params;
|
|
3161
|
-
if (!userName) {
|
|
3162
|
-
return null;
|
|
3163
|
-
}
|
|
3164
|
-
const existing = await getActiveRuntimeTrade({
|
|
3165
|
-
userName,
|
|
3166
|
-
symbol,
|
|
3167
|
-
accountId,
|
|
3168
|
-
deploymentId
|
|
3169
|
-
});
|
|
3170
|
-
if (!existing || existing.strategy !== strategy || existing.direction !== direction) {
|
|
3171
|
-
return null;
|
|
3172
|
-
}
|
|
3173
|
-
const addedFee = typeof fee === "number" && Number.isFinite(fee) ? fee : 0;
|
|
3174
|
-
const openFee = (existing.openFee ?? existing.fee ?? 0) + addedFee;
|
|
3175
|
-
const totalFee = (existing.totalFee ?? existing.fee ?? 0) + addedFee;
|
|
3176
|
-
const next = {
|
|
3177
|
-
...existing,
|
|
3178
|
-
...params.orderLeverage ? { orderLeverage: params.orderLeverage } : {},
|
|
3179
|
-
qty: resultingQty,
|
|
3180
|
-
entryPrice: resultingEntryPrice,
|
|
3181
|
-
entryCount: Math.max(1, existing.entryCount ?? 1) + 1,
|
|
3182
|
-
lastEntryPrice: addedEntryPrice,
|
|
3183
|
-
lastEntryQty: addedQty,
|
|
3184
|
-
lastEntryTimestamp: entryTimestamp,
|
|
3185
|
-
currentPrice: resultingEntryPrice,
|
|
3186
|
-
currentPnl: 0,
|
|
3187
|
-
fee: openFee,
|
|
3188
|
-
openFee,
|
|
3189
|
-
totalFee,
|
|
3190
|
-
lastSyncedAt: now()
|
|
3191
|
-
};
|
|
3192
|
-
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
3193
|
-
try {
|
|
3194
|
-
await Promise.all([
|
|
3195
|
-
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, existing.orderId), next, {
|
|
3196
|
-
expire: 0
|
|
3197
|
-
}),
|
|
3198
|
-
(0, import_redis2.setHashJsonField)(
|
|
3199
|
-
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
3200
|
-
existing.orderId,
|
|
3201
|
-
next,
|
|
3202
|
-
{ expire: 0 }
|
|
3203
|
-
)
|
|
3204
|
-
]);
|
|
3205
|
-
} catch (error) {
|
|
3206
|
-
import_logger3.logger.error(
|
|
3207
|
-
"runtime trade increase journal failed: %s %s",
|
|
3208
|
-
symbol,
|
|
3209
|
-
error?.message || String(error)
|
|
3210
|
-
);
|
|
3211
|
-
}
|
|
3212
|
-
return next;
|
|
3213
|
-
};
|
|
3214
|
-
var getActiveRuntimeTrade = async (params) => {
|
|
3215
|
-
const { userName, symbol, accountId, deploymentId } = params;
|
|
3216
|
-
if (!userName) {
|
|
3217
|
-
return null;
|
|
3218
|
-
}
|
|
3219
|
-
const activeRef = await (0, import_redis2.getData)(
|
|
3220
|
-
import_redis2.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId),
|
|
3221
|
-
null
|
|
3222
|
-
);
|
|
3223
|
-
const orderId = String(activeRef?.orderId || "").trim();
|
|
3224
|
-
if (!orderId) {
|
|
3225
|
-
return null;
|
|
3226
|
-
}
|
|
3227
|
-
const existing = await (0, import_redis2.getData)(
|
|
3228
|
-
import_redis2.redisKeys.runtimeTrade(userName, orderId),
|
|
3229
|
-
null
|
|
3230
|
-
);
|
|
3231
|
-
if (!existing || existing.status !== "active") {
|
|
3232
|
-
await (0, import_redis2.delKey)(
|
|
3233
|
-
import_redis2.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
|
|
3234
|
-
);
|
|
3235
|
-
return null;
|
|
3236
|
-
}
|
|
3237
|
-
return existing;
|
|
3238
|
-
};
|
|
3239
|
-
var markRuntimeTradeClosed = async (params) => {
|
|
3240
|
-
const {
|
|
3241
|
-
userName,
|
|
3242
|
-
symbol,
|
|
3243
|
-
strategy,
|
|
3244
|
-
exitPrice,
|
|
3245
|
-
exitTimestamp,
|
|
3246
|
-
closedPnl,
|
|
3247
|
-
exitType,
|
|
3248
|
-
accountId,
|
|
3249
|
-
deploymentId
|
|
3250
|
-
} = params;
|
|
3251
|
-
if (!userName) {
|
|
3252
|
-
return null;
|
|
3253
|
-
}
|
|
3254
|
-
const existing = await getActiveRuntimeTrade({
|
|
3255
|
-
userName,
|
|
3256
|
-
symbol,
|
|
3257
|
-
accountId,
|
|
3258
|
-
deploymentId
|
|
3259
|
-
});
|
|
3260
|
-
if (!existing) {
|
|
3261
|
-
return null;
|
|
3262
|
-
}
|
|
3263
|
-
const orderId = existing.orderId;
|
|
3264
|
-
if (strategy && existing.strategy !== strategy) {
|
|
3265
|
-
return null;
|
|
3266
|
-
}
|
|
3267
|
-
const resolvedExitPrice = typeof exitPrice === "number" && Number.isFinite(exitPrice) ? exitPrice : existing.currentPrice ?? existing.entryPrice;
|
|
3268
|
-
const resolvedClosedPnl = typeof closedPnl === "number" && Number.isFinite(closedPnl) ? closedPnl : typeof resolvedExitPrice === "number" && Number.isFinite(resolvedExitPrice) ? calculateClosedPnl({
|
|
3269
|
-
direction: existing.direction,
|
|
3270
|
-
entryPrice: existing.entryPrice,
|
|
3271
|
-
exitPrice: resolvedExitPrice,
|
|
3272
|
-
qty: existing.qty
|
|
3273
|
-
}) : existing.closedPnl ?? existing.currentPnl ?? null;
|
|
3274
|
-
const next = {
|
|
3275
|
-
...existing,
|
|
3276
|
-
status: "closed",
|
|
3277
|
-
currentPrice: resolvedExitPrice,
|
|
3278
|
-
currentPnl: resolvedClosedPnl,
|
|
3279
|
-
closedPnl: resolvedClosedPnl,
|
|
3280
|
-
exitPrice: resolvedExitPrice,
|
|
3281
|
-
exitTimestamp: typeof exitTimestamp === "number" && Number.isFinite(exitTimestamp) ? exitTimestamp : now(),
|
|
3282
|
-
exitType: exitType ?? existing.exitType ?? null,
|
|
3283
|
-
lastSyncedAt: now()
|
|
3284
|
-
};
|
|
3285
|
-
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
3286
|
-
const closeDayKey = (0, import_time.getRuntimeStorageDayKey)(next.exitTimestamp);
|
|
3287
|
-
try {
|
|
3288
|
-
await Promise.all([
|
|
3289
|
-
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, orderId), next, {
|
|
3290
|
-
expire: import_constants.TTL_1M
|
|
3291
|
-
}),
|
|
3292
|
-
(0, import_redis2.setHashJsonField)(
|
|
3293
|
-
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
3294
|
-
orderId,
|
|
3295
|
-
next,
|
|
3296
|
-
{ expire: import_constants.TTL_1M }
|
|
3297
|
-
),
|
|
3298
|
-
(0, import_redis2.setHashJsonField)(
|
|
3299
|
-
import_redis2.redisKeys.runtimeClosedTradeBucket(userName, closeDayKey),
|
|
3300
|
-
orderId,
|
|
3301
|
-
next,
|
|
3302
|
-
{ expire: import_constants.TTL_1M }
|
|
3303
|
-
),
|
|
3304
|
-
(0, import_redis2.delKey)(
|
|
3305
|
-
import_redis2.redisKeys.runtimeActiveTrade(
|
|
3306
|
-
userName,
|
|
3307
|
-
symbol,
|
|
3308
|
-
deploymentId ?? accountId
|
|
3309
|
-
)
|
|
3310
|
-
)
|
|
3311
|
-
]);
|
|
3312
|
-
} catch (error) {
|
|
3313
|
-
import_logger3.logger.error(
|
|
3314
|
-
"runtime trade close journal failed: %s %s",
|
|
3315
|
-
symbol,
|
|
3316
|
-
error?.message || String(error)
|
|
3317
|
-
);
|
|
3318
|
-
}
|
|
3319
|
-
return next;
|
|
3320
|
-
};
|
|
3321
|
-
|
|
3322
3854
|
// src/strategyHelpers/marketContextStages.ts
|
|
3323
3855
|
var import_logger8 = require("@tradejs/infra/logger");
|
|
3324
3856
|
|
|
@@ -3328,7 +3860,7 @@ var import_logger4 = require("@tradejs/infra/logger");
|
|
|
3328
3860
|
var import_strategies = require("@tradejs/core/strategies");
|
|
3329
3861
|
|
|
3330
3862
|
// src/binanceBreadthUniverses.ts
|
|
3331
|
-
var
|
|
3863
|
+
var import_node_crypto3 = require("crypto");
|
|
3332
3864
|
|
|
3333
3865
|
// src/config/binanceBreadthUniverses.json
|
|
3334
3866
|
var binanceBreadthUniverses_default = {
|
|
@@ -3567,7 +4099,7 @@ var BINANCE_BREADTH_UNIVERSE_KEYS = [
|
|
|
3567
4099
|
"top50",
|
|
3568
4100
|
"top100"
|
|
3569
4101
|
];
|
|
3570
|
-
var fingerprint = (value, length) => (0,
|
|
4102
|
+
var fingerprint = (value, length) => (0, import_node_crypto3.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, length);
|
|
3571
4103
|
var normalizeSymbols = (symbols) => symbols.map((symbol) => symbol.trim().toUpperCase()).filter(Boolean);
|
|
3572
4104
|
var buildBinanceBreadthUniverseSnapshot = ({
|
|
3573
4105
|
rankedSymbols,
|
|
@@ -4591,7 +5123,7 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
4591
5123
|
};
|
|
4592
5124
|
|
|
4593
5125
|
// src/strategyHelpers/derivativesContext.ts
|
|
4594
|
-
var
|
|
5126
|
+
var import_indicators3 = require("@tradejs/core/indicators");
|
|
4595
5127
|
var import_data2 = require("@tradejs/core/data");
|
|
4596
5128
|
var import_strategies3 = require("@tradejs/core/strategies");
|
|
4597
5129
|
var import_constants2 = require("@tradejs/core/constants");
|
|
@@ -4625,7 +5157,7 @@ var parseLookbackMs = () => {
|
|
|
4625
5157
|
};
|
|
4626
5158
|
var withHourlyFallbackRows = (rowsByInterval) => ({
|
|
4627
5159
|
"15m": rowsByInterval["15m"] ?? [],
|
|
4628
|
-
"1h": (0,
|
|
5160
|
+
"1h": (0, import_indicators3.buildCoinalyzeHourlyRowsWithFallback)({
|
|
4629
5161
|
rows15m: rowsByInterval["15m"],
|
|
4630
5162
|
fallbackRows1h: rowsByInterval["1h"]
|
|
4631
5163
|
})
|
|
@@ -4753,7 +5285,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
4753
5285
|
const targetSymbol = normalizeSymbol(signal.symbol);
|
|
4754
5286
|
const lookbackMs = parseLookbackMs();
|
|
4755
5287
|
const decisionTimeMs = signal.timestamp + (0, import_data2.intervalToMs)(signal.interval);
|
|
4756
|
-
const derivativesEndMs = (0,
|
|
5288
|
+
const derivativesEndMs = (0, import_indicators3.getLastClosedDerivativesBarStartMs)(
|
|
4757
5289
|
decisionTimeMs,
|
|
4758
5290
|
"15m"
|
|
4759
5291
|
);
|
|
@@ -4768,7 +5300,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
4768
5300
|
});
|
|
4769
5301
|
return [
|
|
4770
5302
|
symbol,
|
|
4771
|
-
(0,
|
|
5303
|
+
(0, import_indicators3.buildDerivativesContext)({
|
|
4772
5304
|
symbol,
|
|
4773
5305
|
direction: signal.direction,
|
|
4774
5306
|
timestamp: derivativesEndMs,
|
|
@@ -4795,7 +5327,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
4795
5327
|
lookbackMs,
|
|
4796
5328
|
...params.abortSignal ? { signal: params.abortSignal } : {}
|
|
4797
5329
|
});
|
|
4798
|
-
const context = (0,
|
|
5330
|
+
const context = (0, import_indicators3.buildDerivativesContext)({
|
|
4799
5331
|
symbol: targetSymbol,
|
|
4800
5332
|
direction: signal.direction,
|
|
4801
5333
|
timestamp: derivativesEndMs,
|
|
@@ -4843,7 +5375,7 @@ var import_hyperliquidWhales2 = require("@tradejs/infra/timescale/hyperliquidWha
|
|
|
4843
5375
|
var import_logger7 = require("@tradejs/infra/logger");
|
|
4844
5376
|
|
|
4845
5377
|
// src/hyperliquidWhaleUniverse.ts
|
|
4846
|
-
var
|
|
5378
|
+
var import_node_crypto4 = require("crypto");
|
|
4847
5379
|
|
|
4848
5380
|
// src/config/hyperliquidPerpUniverse.json
|
|
4849
5381
|
var hyperliquidPerpUniverse_default = {
|
|
@@ -5015,7 +5547,7 @@ var hyperliquidWhales_default = {
|
|
|
5015
5547
|
};
|
|
5016
5548
|
|
|
5017
5549
|
// src/hyperliquidWhaleUniverse.ts
|
|
5018
|
-
var fingerprint2 = (value) => (0,
|
|
5550
|
+
var fingerprint2 = (value) => (0, import_node_crypto4.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
|
|
5019
5551
|
var validateUnique = (values, label) => {
|
|
5020
5552
|
if (new Set(values).size !== values.length) {
|
|
5021
5553
|
throw new Error(`Hyperliquid ${label} snapshot contains duplicates`);
|
|
@@ -5502,6 +6034,7 @@ var enrichSignalWithMarketContextStages = async ({
|
|
|
5502
6034
|
})
|
|
5503
6035
|
});
|
|
5504
6036
|
}
|
|
6037
|
+
await enrichSignalWithTickerProfile(signal);
|
|
5505
6038
|
const results = [];
|
|
5506
6039
|
for (const stage of stages) {
|
|
5507
6040
|
if (abortSignal?.aborted) break;
|
|
@@ -5549,12 +6082,12 @@ var findReplayAiAnalysis = ({
|
|
|
5549
6082
|
direction,
|
|
5550
6083
|
ai
|
|
5551
6084
|
}) => {
|
|
5552
|
-
const
|
|
5553
|
-
if (!Array.isArray(
|
|
6085
|
+
const snapshots2 = ai?.replayAnalyses;
|
|
6086
|
+
if (!Array.isArray(snapshots2) || !snapshots2.length) {
|
|
5554
6087
|
return void 0;
|
|
5555
6088
|
}
|
|
5556
6089
|
let best = null;
|
|
5557
|
-
for (const snapshot2 of
|
|
6090
|
+
for (const snapshot2 of snapshots2) {
|
|
5558
6091
|
if (snapshot2.symbol !== signal.symbol || snapshot2.direction !== direction || snapshot2.strategy && snapshot2.strategy !== signal.strategy) {
|
|
5559
6092
|
continue;
|
|
5560
6093
|
}
|
|
@@ -5714,7 +6247,9 @@ var executeEntryOrder = async ({
|
|
|
5714
6247
|
beforePlaceOrder,
|
|
5715
6248
|
recordRuntimeTrade = true,
|
|
5716
6249
|
leverage,
|
|
5717
|
-
executionLimits
|
|
6250
|
+
executionLimits,
|
|
6251
|
+
markPortfolioSubmitted,
|
|
6252
|
+
rejectPortfolioSubmission
|
|
5718
6253
|
}) => {
|
|
5719
6254
|
await beforePlaceOrder?.();
|
|
5720
6255
|
const previousPosition = positionIntent === "increase" ? await connector.getPosition(symbol) : null;
|
|
@@ -5747,12 +6282,12 @@ var executeEntryOrder = async ({
|
|
|
5747
6282
|
if (executionLimits?.maxLossValue != null) {
|
|
5748
6283
|
signal.riskDecision = {
|
|
5749
6284
|
stage: "order_plan",
|
|
5750
|
-
status: "approved",
|
|
5751
|
-
reason: "PLANNED_LOSS_WITHIN_MAX_LOSS_VALUE",
|
|
6285
|
+
status: economics2.riskExceeded ? "warning" : "approved",
|
|
6286
|
+
reason: economics2.riskExceeded ? "ENTRY_RISK_EXCEEDS_MAX_LOSS_AT_SUBMISSION" : "PLANNED_LOSS_WITHIN_MAX_LOSS_VALUE",
|
|
5752
6287
|
plannedLossValue: economics2.plannedLossValue,
|
|
5753
6288
|
maxLossValue: executionLimits.maxLossValue,
|
|
5754
6289
|
limitUtilization: economics2.plannedLossValue / executionLimits.maxLossValue,
|
|
5755
|
-
enforced:
|
|
6290
|
+
enforced: false
|
|
5756
6291
|
};
|
|
5757
6292
|
}
|
|
5758
6293
|
}
|
|
@@ -5787,7 +6322,13 @@ var executeEntryOrder = async ({
|
|
|
5787
6322
|
...typeof leverage === "number" && Number.isFinite(leverage) ? { leverage } : {},
|
|
5788
6323
|
orderId,
|
|
5789
6324
|
signal,
|
|
5790
|
-
...
|
|
6325
|
+
...rejectPortfolioSubmission ? { onRejected: rejectPortfolioSubmission } : {},
|
|
6326
|
+
...beforeSubmit || markPortfolioSubmitted ? {
|
|
6327
|
+
beforeSubmit: async (context) => {
|
|
6328
|
+
await beforeSubmit?.(context);
|
|
6329
|
+
await markPortfolioSubmitted?.(context.qty);
|
|
6330
|
+
}
|
|
6331
|
+
} : {}
|
|
5791
6332
|
});
|
|
5792
6333
|
const orderAckTime = Date.now();
|
|
5793
6334
|
const placedQty = typeof signal.orderQty === "number" && Number.isFinite(signal.orderQty) && signal.orderQty > 0 ? signal.orderQty : qty;
|
|
@@ -6775,7 +7316,7 @@ var handleProtectDecision = async ({
|
|
|
6775
7316
|
}
|
|
6776
7317
|
return decision.code;
|
|
6777
7318
|
};
|
|
6778
|
-
var
|
|
7319
|
+
var executeAdmittedEntryDecision = async ({
|
|
6779
7320
|
connector,
|
|
6780
7321
|
symbol,
|
|
6781
7322
|
decision,
|
|
@@ -6789,7 +7330,9 @@ var executeEntryDecision = async ({
|
|
|
6789
7330
|
ai,
|
|
6790
7331
|
recordRuntimeJournal,
|
|
6791
7332
|
invokeStageHooks,
|
|
6792
|
-
notifyRuntimeError
|
|
7333
|
+
notifyRuntimeError,
|
|
7334
|
+
markPortfolioSubmitted,
|
|
7335
|
+
rejectPortfolioSubmission
|
|
6793
7336
|
}) => {
|
|
6794
7337
|
const signal = decision.signal;
|
|
6795
7338
|
const beforePlaceOrder = async () => {
|
|
@@ -6836,6 +7379,8 @@ var executeEntryDecision = async ({
|
|
|
6836
7379
|
...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {},
|
|
6837
7380
|
signal,
|
|
6838
7381
|
beforePlaceOrder,
|
|
7382
|
+
markPortfolioSubmitted,
|
|
7383
|
+
rejectPortfolioSubmission,
|
|
6839
7384
|
recordRuntimeTrade: recordRuntimeJournal,
|
|
6840
7385
|
executionLimits: resolveEntryExecutionLimits(
|
|
6841
7386
|
hookCtx.strategyConfig,
|
|
@@ -6976,6 +7521,37 @@ var executeEntryDecision = async ({
|
|
|
6976
7521
|
}
|
|
6977
7522
|
return signal ?? decision.code;
|
|
6978
7523
|
};
|
|
7524
|
+
async function executeEntryDecision(params) {
|
|
7525
|
+
if (!params.portfolio) return executeAdmittedEntryDecision(params);
|
|
7526
|
+
const { decision, portfolio } = params;
|
|
7527
|
+
const signal = decision.signal;
|
|
7528
|
+
if (!signal || decision.orderPlan.positionIntent === "increase")
|
|
7529
|
+
return "PORTFOLIO_REQUIRES_NEW_SIGNAL_POSITION";
|
|
7530
|
+
signal.orderId ??= createRuntimeOrderId(signal.strategy);
|
|
7531
|
+
const result = await portfolio.submit(
|
|
7532
|
+
params.portfolioTime ?? decision.entryContext.timestamp,
|
|
7533
|
+
{
|
|
7534
|
+
id: signal.orderId,
|
|
7535
|
+
signalId: signal.signalId,
|
|
7536
|
+
strategy: signal.strategy,
|
|
7537
|
+
symbol: params.symbol,
|
|
7538
|
+
direction: decision.entryContext.direction,
|
|
7539
|
+
qty: decision.orderPlan.qty,
|
|
7540
|
+
timestamp: decision.entryContext.timestamp
|
|
7541
|
+
},
|
|
7542
|
+
(markPortfolioSubmitted, rejectPortfolioSubmission) => executeAdmittedEntryDecision({
|
|
7543
|
+
...params,
|
|
7544
|
+
markPortfolioSubmitted,
|
|
7545
|
+
rejectPortfolioSubmission
|
|
7546
|
+
})
|
|
7547
|
+
);
|
|
7548
|
+
if (result.reason) {
|
|
7549
|
+
signal.orderStatus = "failed";
|
|
7550
|
+
signal.orderFailureReason = result.reason;
|
|
7551
|
+
return signal;
|
|
7552
|
+
}
|
|
7553
|
+
return result.value;
|
|
7554
|
+
}
|
|
6979
7555
|
|
|
6980
7556
|
// src/strategyRuntime.ts
|
|
6981
7557
|
var cloneWithPropertyDescriptors = (value) => Object.create(
|
|
@@ -7003,6 +7579,7 @@ var createStrategyRuntime = ({
|
|
|
7003
7579
|
};
|
|
7004
7580
|
const creator = async ({
|
|
7005
7581
|
userName,
|
|
7582
|
+
connectorName,
|
|
7006
7583
|
config: baseConfig,
|
|
7007
7584
|
symbol,
|
|
7008
7585
|
universe: requestedUniverse,
|
|
@@ -7041,6 +7618,17 @@ var createStrategyRuntime = ({
|
|
|
7041
7618
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
7042
7619
|
const projectHooks = projectConfig.hooks;
|
|
7043
7620
|
const env = String(config.ENV ?? "BACKTEST");
|
|
7621
|
+
const portfolio = getPortfolioRuntime({
|
|
7622
|
+
connectorName,
|
|
7623
|
+
value: baseConfig?.PORTFOLIO_POLICY ?? config.PORTFOLIO_POLICY,
|
|
7624
|
+
strategy: strategyName,
|
|
7625
|
+
connector,
|
|
7626
|
+
env,
|
|
7627
|
+
userName,
|
|
7628
|
+
accountId,
|
|
7629
|
+
deploymentId
|
|
7630
|
+
});
|
|
7631
|
+
const portfolioContext = portfolio ? (now2) => isRuntimeOrderExecutionEnabled({ config, env, connector }) ? portfolio.tick(now2) : portfolio.context(now2) : void 0;
|
|
7044
7632
|
const researchPolicyValue = baseConfig?.RESEARCH_ENTRY_POLICY ?? config.RESEARCH_ENTRY_POLICY;
|
|
7045
7633
|
const researchEntryPolicy = researchPolicyValue == null ? void 0 : parseResearchEntryPolicy(researchPolicyValue);
|
|
7046
7634
|
if (researchEntryPolicy && (env !== "BACKTEST" || Reflect.get(connector, "__tradejsTestConnector") !== true))
|
|
@@ -7078,6 +7666,9 @@ var createStrategyRuntime = ({
|
|
|
7078
7666
|
});
|
|
7079
7667
|
const strategyPolicyProfile = getPolicyProfile();
|
|
7080
7668
|
const indicatorPeriods = (0, import_strategies6.buildDefaultIndicatorPeriods)(config);
|
|
7669
|
+
const strategyConfig = { ...config };
|
|
7670
|
+
delete strategyConfig.RECORD_RUNTIME_TRADES;
|
|
7671
|
+
delete strategyConfig.SIMULATE_ORDERS;
|
|
7081
7672
|
const hookBase = {
|
|
7082
7673
|
connector,
|
|
7083
7674
|
strategyName,
|
|
@@ -7088,7 +7679,7 @@ var createStrategyRuntime = ({
|
|
|
7088
7679
|
accountId,
|
|
7089
7680
|
deploymentId,
|
|
7090
7681
|
policyProfileId: strategyPolicyProfile?.id ?? requestedPolicyProfileId,
|
|
7091
|
-
strategyConfig
|
|
7682
|
+
strategyConfig,
|
|
7092
7683
|
env,
|
|
7093
7684
|
isConfigFromBacktest
|
|
7094
7685
|
};
|
|
@@ -7156,7 +7747,7 @@ var createStrategyRuntime = ({
|
|
|
7156
7747
|
);
|
|
7157
7748
|
}
|
|
7158
7749
|
};
|
|
7159
|
-
const invokeHook = async (stage, hook, params, errorContext = {}) => {
|
|
7750
|
+
const invokeHook = async (stage, hook, params, errorContext = {}, failureResult) => {
|
|
7160
7751
|
if (!hook) {
|
|
7161
7752
|
return void 0;
|
|
7162
7753
|
}
|
|
@@ -7176,17 +7767,18 @@ var createStrategyRuntime = ({
|
|
|
7176
7767
|
entry: errorContext.entry,
|
|
7177
7768
|
market: errorContext.market
|
|
7178
7769
|
});
|
|
7179
|
-
return
|
|
7770
|
+
return failureResult;
|
|
7180
7771
|
}
|
|
7181
7772
|
};
|
|
7182
|
-
const invokeProjectHooks = async (stage, params, errorContext = {}) => {
|
|
7773
|
+
const invokeProjectHooks = async (stage, params, errorContext = {}, failureResult) => {
|
|
7183
7774
|
const results = [];
|
|
7184
7775
|
for (const hook of getProjectHookList(stage)) {
|
|
7185
7776
|
const result = await invokeHook(
|
|
7186
7777
|
stage,
|
|
7187
7778
|
hook,
|
|
7188
7779
|
params,
|
|
7189
|
-
errorContext
|
|
7780
|
+
errorContext,
|
|
7781
|
+
failureResult
|
|
7190
7782
|
);
|
|
7191
7783
|
if (result !== void 0) {
|
|
7192
7784
|
results.push(result);
|
|
@@ -7201,10 +7793,12 @@ var createStrategyRuntime = ({
|
|
|
7201
7793
|
return invokeHook(stage, hook, params, errorContext);
|
|
7202
7794
|
};
|
|
7203
7795
|
const invokeGateHooks = async (stage, hook, params, errorContext = {}) => {
|
|
7796
|
+
const failureResult = stage === "beforeEntryGate" ? { allow: false, reason: "ERROR" } : void 0;
|
|
7204
7797
|
const projectResults = await invokeProjectHooks(
|
|
7205
7798
|
stage,
|
|
7206
7799
|
params,
|
|
7207
|
-
errorContext
|
|
7800
|
+
errorContext,
|
|
7801
|
+
failureResult
|
|
7208
7802
|
);
|
|
7209
7803
|
const projectBlock = projectResults.find(
|
|
7210
7804
|
(result) => result?.allow === false
|
|
@@ -7216,7 +7810,8 @@ var createStrategyRuntime = ({
|
|
|
7216
7810
|
stage,
|
|
7217
7811
|
hook,
|
|
7218
7812
|
params,
|
|
7219
|
-
errorContext
|
|
7813
|
+
errorContext,
|
|
7814
|
+
failureResult
|
|
7220
7815
|
);
|
|
7221
7816
|
};
|
|
7222
7817
|
const applyProjectAfterCoreDecisionHooks = async ({
|
|
@@ -7332,6 +7927,20 @@ var createStrategyRuntime = ({
|
|
|
7332
7927
|
interval: decisionInterval
|
|
7333
7928
|
}) => {
|
|
7334
7929
|
if (!baseContext) return void 0;
|
|
7930
|
+
const profileSource = strategyManifest?.contextRequirements?.tickerProfiles;
|
|
7931
|
+
if (profileSource) {
|
|
7932
|
+
const profile = await loadTickerProfileContext({
|
|
7933
|
+
...profileSource,
|
|
7934
|
+
projectRoot,
|
|
7935
|
+
symbol: decisionSymbol,
|
|
7936
|
+
timestamp: candle.timestamp
|
|
7937
|
+
});
|
|
7938
|
+
if (!profile)
|
|
7939
|
+
throw new Error(
|
|
7940
|
+
"Required ticker profile is unavailable at decision time"
|
|
7941
|
+
);
|
|
7942
|
+
baseContext = withTickerProfile(baseContext, profile);
|
|
7943
|
+
}
|
|
7335
7944
|
if (!coreContextRequirements.has("hyperliquidWhales")) {
|
|
7336
7945
|
return baseContext;
|
|
7337
7946
|
}
|
|
@@ -7362,11 +7971,8 @@ var createStrategyRuntime = ({
|
|
|
7362
7971
|
return enrichedBaseContext;
|
|
7363
7972
|
}
|
|
7364
7973
|
});
|
|
7365
|
-
const coreConfig = { ...config };
|
|
7366
|
-
delete coreConfig.RECORD_RUNTIME_TRADES;
|
|
7367
|
-
delete coreConfig.SIMULATE_ORDERS;
|
|
7368
7974
|
const core = await createCore({
|
|
7369
|
-
config:
|
|
7975
|
+
config: strategyConfig,
|
|
7370
7976
|
data,
|
|
7371
7977
|
strategyApi,
|
|
7372
7978
|
indicatorsState
|
|
@@ -7502,6 +8108,8 @@ var createStrategyRuntime = ({
|
|
|
7502
8108
|
runtime: pending2.runtime
|
|
7503
8109
|
});
|
|
7504
8110
|
return executeEntryDecision({
|
|
8111
|
+
portfolio,
|
|
8112
|
+
portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
|
|
7505
8113
|
connector,
|
|
7506
8114
|
symbol,
|
|
7507
8115
|
decision: pending2.decision,
|
|
@@ -7519,6 +8127,10 @@ var createStrategyRuntime = ({
|
|
|
7519
8127
|
});
|
|
7520
8128
|
};
|
|
7521
8129
|
const runWithDecisionOverride = async (candle, btcCandle, options = {}) => {
|
|
8130
|
+
if (portfolioContext)
|
|
8131
|
+
await portfolioContext(
|
|
8132
|
+
env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now()
|
|
8133
|
+
);
|
|
7522
8134
|
appendCurrentMarketData(candle, btcCandle, options.ethCandle);
|
|
7523
8135
|
const ethCandle = resolveEthCandle(candle, options.ethCandle);
|
|
7524
8136
|
indicatorsState.setCurrentBar(candle, btcCandle, ethCandle);
|
|
@@ -7689,6 +8301,13 @@ var createStrategyRuntime = ({
|
|
|
7689
8301
|
});
|
|
7690
8302
|
const signal = decision.signal;
|
|
7691
8303
|
if (signal) {
|
|
8304
|
+
if (portfolio)
|
|
8305
|
+
signal.additionalIndicators = {
|
|
8306
|
+
...signal.additionalIndicators,
|
|
8307
|
+
portfolioContext: await portfolioContext(
|
|
8308
|
+
env === "BACKTEST" || env === "PARITY" ? decision.entryContext.timestamp : Date.now()
|
|
8309
|
+
)
|
|
8310
|
+
};
|
|
7692
8311
|
if (runtimeLineage) signal.runtimeLineage = runtimeLineage;
|
|
7693
8312
|
if (universe) signal.universe = universe;
|
|
7694
8313
|
if (assetClass) signal.assetClass = assetClass;
|
|
@@ -7928,6 +8547,8 @@ var createStrategyRuntime = ({
|
|
|
7928
8547
|
});
|
|
7929
8548
|
}
|
|
7930
8549
|
return executeEntryDecision({
|
|
8550
|
+
portfolio,
|
|
8551
|
+
portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
|
|
7931
8552
|
connector,
|
|
7932
8553
|
symbol,
|
|
7933
8554
|
decision,
|
|
@@ -8039,7 +8660,7 @@ var normalizeForCanonicalJson = (value) => {
|
|
|
8039
8660
|
}
|
|
8040
8661
|
return value;
|
|
8041
8662
|
};
|
|
8042
|
-
var revision = (prefix, value) => `${prefix}:${(0,
|
|
8663
|
+
var revision = (prefix, value) => `${prefix}:${(0, import_node_crypto5.createHash)("sha256").update(JSON.stringify(normalizeForCanonicalJson(value))).digest("hex").slice(0, 16)}`;
|
|
8043
8664
|
var computeStrategyRevision = ({
|
|
8044
8665
|
strategyName,
|
|
8045
8666
|
strategyPackage,
|