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