@tradejs/node 3.1.29-beta.289 → 3.1.29-beta.291
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai.js +109 -48
- package/dist/ai.mjs +1 -1
- package/dist/backtest.js +2504 -1883
- package/dist/backtest.mjs +3 -2
- package/dist/{chunk-KYGJBMKE.mjs → chunk-HRON2EAI.mjs} +432 -40
- package/dist/{chunk-UL5Z46JL.mjs → chunk-PYJWXAYR.mjs} +97 -30
- package/dist/chunk-UUNEWAT4.mjs +193 -0
- package/dist/{chunk-BO42QWAB.mjs → chunk-W6PPAADK.mjs} +1 -1
- package/dist/cli.js +837 -775
- package/dist/cli.mjs +4 -3
- package/dist/diagnostics.js +2532 -1911
- package/dist/diagnostics.mjs +4 -3
- package/dist/mcp.d.mts +4 -2
- package/dist/mcp.d.ts +4 -2
- package/dist/mcp.js +2614 -1971
- package/dist/mcp.mjs +27 -5
- package/dist/portfolio.d.mts +28 -0
- package/dist/portfolio.d.ts +28 -0
- package/dist/portfolio.js +210 -0
- package/dist/portfolio.mjs +9 -0
- package/dist/registry.js +2091 -1470
- package/dist/registry.mjs +3 -2
- package/dist/runtimeDashboard.js +2255 -1632
- package/dist/runtimeDashboard.mjs +4 -3
- package/dist/runtimeStrategies.js +2417 -1796
- package/dist/runtimeStrategies.mjs +4 -3
- package/dist/strategies.d.mts +3 -1
- package/dist/strategies.d.ts +3 -1
- package/dist/strategies.js +2106 -1485
- package/dist/strategies.mjs +3 -2
- package/package.json +10 -5
package/dist/registry.js
CHANGED
|
@@ -792,1191 +792,783 @@ __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
|
-
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
1091
|
-
);
|
|
1092
|
-
const side = direction === "LONG" ? 1 : -1;
|
|
1093
|
-
if (side * (price - stopLossPrice) <= 0 || side * (takeProfitPrice - price) <= 0)
|
|
1094
|
-
throw new EntrySubmissionValidationError(
|
|
1095
|
-
"INVALID_ENTRY_PROTECTION_AT_SUBMISSION"
|
|
1096
|
-
);
|
|
1097
|
-
const slippage = limits.slippageBps / 1e4;
|
|
1098
|
-
const cost = (value, sign) => value * slippage + value * (1 + sign * slippage) * limits.feeRate;
|
|
1099
|
-
const entryCost = cost(price, side);
|
|
1100
|
-
const lossPerUnit = Math.abs(price - stopLossPrice) + entryCost + cost(stopLossPrice, -side);
|
|
1101
|
-
const rewardPerUnit = Math.max(
|
|
1102
|
-
0,
|
|
1103
|
-
Math.abs(takeProfitPrice - price) - entryCost - cost(takeProfitPrice, -side)
|
|
1104
|
-
);
|
|
1105
|
-
const netRiskRatio = rewardPerUnit / lossPerUnit;
|
|
1106
|
-
const plannedLossValue = qty * lossPerUnit;
|
|
1107
|
-
if (!Number.isFinite(netRiskRatio) || !Number.isFinite(plannedLossValue))
|
|
1108
|
-
throw new EntrySubmissionValidationError(
|
|
1109
|
-
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
1110
|
-
);
|
|
1111
|
-
if (limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio)
|
|
1112
|
-
throw new EntrySubmissionValidationError(
|
|
1113
|
-
"ENTRY_RR_BELOW_MIN_AT_SUBMISSION"
|
|
1114
|
-
);
|
|
1115
|
-
if (limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9))
|
|
1116
|
-
throw new EntrySubmissionValidationError(
|
|
1117
|
-
"ENTRY_RISK_EXCEEDS_MAX_LOSS_AT_SUBMISSION"
|
|
1118
|
-
);
|
|
1119
|
-
return { netRiskRatio, plannedLossValue };
|
|
996
|
+
const pnl = direction === "LONG" ? (exitPrice - entryPrice) * qty : (entryPrice - exitPrice) * qty;
|
|
997
|
+
return Number.isFinite(pnl) ? pnl : null;
|
|
1120
998
|
};
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
}
|
|
1130
|
-
if (
|
|
1131
|
-
return
|
|
1132
|
-
arrivalSnapshotTime: Date.now(),
|
|
1133
|
-
arrivalSource: "unavailable",
|
|
1134
|
-
bid: null,
|
|
1135
|
-
ask: null,
|
|
1136
|
-
arrivalMid: null,
|
|
1137
|
-
spreadBps: null
|
|
1138
|
-
};
|
|
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;
|
|
1139
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;
|
|
1140
1027
|
try {
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
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
|
+
]);
|
|
1156
1044
|
} catch (error) {
|
|
1157
|
-
import_logger.logger.
|
|
1158
|
-
"runtime
|
|
1159
|
-
symbol,
|
|
1045
|
+
import_logger.logger.error(
|
|
1046
|
+
"runtime trade open journal failed: %s %s",
|
|
1047
|
+
record.symbol,
|
|
1160
1048
|
error?.message || String(error)
|
|
1161
1049
|
);
|
|
1162
|
-
return {
|
|
1163
|
-
arrivalSnapshotTime: Date.now(),
|
|
1164
|
-
arrivalSource: "top_of_book_error",
|
|
1165
|
-
bid: null,
|
|
1166
|
-
ask: null,
|
|
1167
|
-
arrivalMid: null,
|
|
1168
|
-
spreadBps: null
|
|
1169
|
-
};
|
|
1170
1050
|
}
|
|
1051
|
+
return record;
|
|
1171
1052
|
};
|
|
1172
|
-
var
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
}
|
|
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;
|
|
1190
1070
|
}
|
|
1191
|
-
|
|
1192
|
-
|
|
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;
|
|
1193
1079
|
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
arrivalSnapshotTime: Date.now(),
|
|
1213
|
-
arrivalSource: "simulation",
|
|
1214
|
-
bid: executionPrice,
|
|
1215
|
-
ask: executionPrice,
|
|
1216
|
-
arrivalMid: executionPrice,
|
|
1217
|
-
spreadBps: 0
|
|
1218
|
-
} : await getOrderArrivalSnapshot({
|
|
1219
|
-
connector,
|
|
1220
|
-
symbol,
|
|
1221
|
-
requireTimestamp: true
|
|
1222
|
-
});
|
|
1223
|
-
const age = Date.now() - snapshot2.arrivalSnapshotTime;
|
|
1224
|
-
if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
|
|
1225
|
-
throw new EntrySubmissionValidationError(
|
|
1226
|
-
"ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
|
|
1227
|
-
);
|
|
1228
|
-
if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
|
|
1229
|
-
throw new EntrySubmissionValidationError(
|
|
1230
|
-
"ENTRY_QUOTE_STALE_AT_SUBMISSION"
|
|
1231
|
-
);
|
|
1232
|
-
const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
|
|
1233
|
-
validateEntryProtectionAtArrival({
|
|
1234
|
-
direction,
|
|
1235
|
-
signalPrice: price,
|
|
1236
|
-
bid: snapshot2.bid,
|
|
1237
|
-
ask: snapshot2.ask,
|
|
1238
|
-
arrivalMid: snapshot2.arrivalMid,
|
|
1239
|
-
takeProfits,
|
|
1240
|
-
stopLossPrice
|
|
1241
|
-
});
|
|
1242
|
-
const prices = takeProfits.map((tp) => tp.price);
|
|
1243
|
-
const economics2 = validateEntryExecution({
|
|
1244
|
-
direction,
|
|
1245
|
-
price,
|
|
1246
|
-
qty,
|
|
1247
|
-
stopLossPrice,
|
|
1248
|
-
takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
|
|
1249
|
-
limits
|
|
1250
|
-
});
|
|
1251
|
-
onValidated?.(snapshot2, economics2);
|
|
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()
|
|
1252
1098
|
};
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
}
|
|
1272
|
-
return trimmed.map(
|
|
1273
|
-
(item) => item && typeof item === "object" ? trimSeriesDeep(item) : item
|
|
1274
|
-
);
|
|
1275
|
-
}
|
|
1276
|
-
if (value && typeof value === "object") {
|
|
1277
|
-
return Object.fromEntries(
|
|
1278
|
-
Object.entries(value).map(([key, nested]) => [
|
|
1279
|
-
key,
|
|
1280
|
-
trimSeriesDeep(nested)
|
|
1281
|
-
])
|
|
1099
|
+
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
1100
|
+
try {
|
|
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
|
+
]);
|
|
1112
|
+
} catch (error) {
|
|
1113
|
+
import_logger.logger.error(
|
|
1114
|
+
"runtime trade increase journal failed: %s %s",
|
|
1115
|
+
symbol,
|
|
1116
|
+
error?.message || String(error)
|
|
1282
1117
|
);
|
|
1283
1118
|
}
|
|
1284
|
-
return
|
|
1119
|
+
return next;
|
|
1285
1120
|
};
|
|
1286
|
-
var
|
|
1287
|
-
const
|
|
1288
|
-
if (
|
|
1289
|
-
return
|
|
1121
|
+
var getActiveRuntimeTrade = async (params) => {
|
|
1122
|
+
const { userName, symbol, accountId, deploymentId } = params;
|
|
1123
|
+
if (!userName) {
|
|
1124
|
+
return null;
|
|
1290
1125
|
}
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
if (!
|
|
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) {
|
|
1297
1132
|
return null;
|
|
1298
1133
|
}
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
};
|
|
1309
|
-
var buildMissingSpreadContext = () => ({
|
|
1310
|
-
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1311
|
-
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1312
|
-
available: false,
|
|
1313
|
-
value: null,
|
|
1314
|
-
zScore: null,
|
|
1315
|
-
bps: null,
|
|
1316
|
-
absBps: null,
|
|
1317
|
-
bias: null,
|
|
1318
|
-
severity: null
|
|
1319
|
-
});
|
|
1320
|
-
var buildSpreadContextFromSignal = (signal) => {
|
|
1321
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1322
|
-
const relative = toRecord(baseContext?.relative);
|
|
1323
|
-
const execution = toRecord(relative?.execution);
|
|
1324
|
-
const spread = toFiniteNumber(execution?.venueSpread);
|
|
1325
|
-
const zScore = toFiniteNumber(execution?.venueSpreadZScore);
|
|
1326
|
-
if (spread == null) {
|
|
1327
|
-
return buildMissingSpreadContext();
|
|
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;
|
|
1328
1143
|
}
|
|
1329
|
-
|
|
1330
|
-
const bps = roundTo(value * 1e4, 2);
|
|
1331
|
-
const absBps = Math.abs(bps);
|
|
1332
|
-
const bias = absBps < 1 ? "flat" : bps > 0 ? "coinbase_premium" : "binance_premium";
|
|
1333
|
-
const severity = absBps >= 20 ? "wide" : absBps >= 5 ? "elevated" : "normal";
|
|
1334
|
-
return {
|
|
1335
|
-
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1336
|
-
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1337
|
-
available: true,
|
|
1338
|
-
value,
|
|
1339
|
-
zScore,
|
|
1340
|
-
bps,
|
|
1341
|
-
absBps,
|
|
1342
|
-
bias,
|
|
1343
|
-
severity
|
|
1344
|
-
};
|
|
1144
|
+
return existing;
|
|
1345
1145
|
};
|
|
1346
|
-
var
|
|
1347
|
-
const
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
signedVolumeZScore: null
|
|
1362
|
-
};
|
|
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;
|
|
1363
1161
|
}
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
};
|
|
1375
|
-
var buildTradeFlowContextFromSignal = (signal) => {
|
|
1376
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1377
|
-
const participation = toRecord(baseContext?.participation);
|
|
1378
|
-
const tradeFlow = toRecord(participation?.tradeFlow);
|
|
1379
|
-
if (!tradeFlow) {
|
|
1380
|
-
return {
|
|
1381
|
-
source: null,
|
|
1382
|
-
available: false,
|
|
1383
|
-
interval: null,
|
|
1384
|
-
stale: null,
|
|
1385
|
-
trades: null,
|
|
1386
|
-
buyPressurePct: null,
|
|
1387
|
-
netBaseDelta: null,
|
|
1388
|
-
netQuoteDelta: null
|
|
1389
|
-
};
|
|
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;
|
|
1390
1172
|
}
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
interval: String(tradeFlow.interval ?? ""),
|
|
1395
|
-
stale: typeof tradeFlow.stale === "boolean" ? tradeFlow.stale : null,
|
|
1396
|
-
trades: toFiniteNumber(tradeFlow.trades),
|
|
1397
|
-
buyPressurePct: toFiniteNumber(tradeFlow.buyPressurePct),
|
|
1398
|
-
netBaseDelta: toFiniteNumber(tradeFlow.netBaseDelta),
|
|
1399
|
-
netQuoteDelta: toFiniteNumber(tradeFlow.netQuoteDelta)
|
|
1400
|
-
};
|
|
1401
|
-
};
|
|
1402
|
-
var buildMarketBreadthContextFromSignal = (signal) => {
|
|
1403
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1404
|
-
const relative = toRecord(baseContext?.relative);
|
|
1405
|
-
const breadth = toRecord(relative?.marketBreadth);
|
|
1406
|
-
if (!breadth) {
|
|
1407
|
-
return {
|
|
1408
|
-
source: null,
|
|
1409
|
-
available: false,
|
|
1410
|
-
universe: null,
|
|
1411
|
-
interval: null,
|
|
1412
|
-
stale: null,
|
|
1413
|
-
symbolsCount: null,
|
|
1414
|
-
advanceDeclineRatio: null,
|
|
1415
|
-
pctAboveMa20: null,
|
|
1416
|
-
pctAboveMa50: null,
|
|
1417
|
-
equalWeightedReturn: null,
|
|
1418
|
-
volumeWeightedReturn: null,
|
|
1419
|
-
dispersion: null
|
|
1420
|
-
};
|
|
1173
|
+
const orderId = existing.orderId;
|
|
1174
|
+
if (strategy && existing.strategy !== strategy) {
|
|
1175
|
+
return null;
|
|
1421
1176
|
}
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
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()
|
|
1435
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;
|
|
1436
1242
|
};
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1243
|
+
|
|
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);
|
|
1444
1268
|
return {
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
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
|
|
1457
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));
|
|
1458
1320
|
}
|
|
1459
|
-
return {
|
|
1460
|
-
source: String(breadth.source ?? ""),
|
|
1461
|
-
available: true,
|
|
1462
|
-
universe: String(breadth.universe ?? ""),
|
|
1463
|
-
interval: String(breadth.interval ?? ""),
|
|
1464
|
-
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
1465
|
-
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
1466
|
-
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
1467
|
-
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
1468
|
-
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
1469
|
-
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
1470
|
-
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
1471
|
-
dispersion: toFiniteNumber(breadth.dispersion)
|
|
1472
|
-
};
|
|
1473
1321
|
};
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
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"
|
|
1339
|
+
);
|
|
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"
|
|
1343
|
+
);
|
|
1344
|
+
const binding = {
|
|
1345
|
+
userName,
|
|
1346
|
+
accountId,
|
|
1347
|
+
deploymentId
|
|
1480
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);
|
|
1449
|
+
}
|
|
1450
|
+
const fromEnv = String(process.env.PROJECT_CWD || "").trim();
|
|
1451
|
+
if (fromEnv) {
|
|
1452
|
+
return import_path.default.resolve(fromEnv);
|
|
1453
|
+
}
|
|
1454
|
+
return process.cwd();
|
|
1481
1455
|
};
|
|
1482
|
-
var
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
const targetVsBtc = toRecord(relative?.targetVsBtc);
|
|
1486
|
-
if (!targetVsBtc) {
|
|
1487
|
-
return {
|
|
1488
|
-
source: null,
|
|
1489
|
-
available: false,
|
|
1490
|
-
ratioReturn1h: null,
|
|
1491
|
-
ratioReturn4h: null,
|
|
1492
|
-
ratioReturn24h: null,
|
|
1493
|
-
alphaVsBtc1h: null,
|
|
1494
|
-
alphaVsBtc4h: null,
|
|
1495
|
-
alphaVsBtc24h: null,
|
|
1496
|
-
betaToBtc20: null,
|
|
1497
|
-
correlationToBtc20: null,
|
|
1498
|
-
ratioTrend: null
|
|
1499
|
-
};
|
|
1456
|
+
var normalizeConfig = (rawConfig) => {
|
|
1457
|
+
if (!rawConfig || typeof rawConfig !== "object") {
|
|
1458
|
+
return {};
|
|
1500
1459
|
}
|
|
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;
|
|
1501
1468
|
return {
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
alphaVsBtc1h: toFiniteNumber(targetVsBtc.alphaVsBtc1h),
|
|
1508
|
-
alphaVsBtc4h: toFiniteNumber(targetVsBtc.alphaVsBtc4h),
|
|
1509
|
-
alphaVsBtc24h: toFiniteNumber(targetVsBtc.alphaVsBtc24h),
|
|
1510
|
-
betaToBtc20: toFiniteNumber(targetVsBtc.betaToBtc20),
|
|
1511
|
-
correlationToBtc20: toFiniteNumber(targetVsBtc.correlationToBtc20),
|
|
1512
|
-
ratioTrend: typeof targetVsBtc.ratioTrend === "string" ? targetVsBtc.ratioTrend : null
|
|
1469
|
+
strategies: strategies2,
|
|
1470
|
+
indicators,
|
|
1471
|
+
connectors,
|
|
1472
|
+
...hooks ? { hooks } : {},
|
|
1473
|
+
...runtime ? { runtime } : {}
|
|
1513
1474
|
};
|
|
1514
1475
|
};
|
|
1515
|
-
var
|
|
1516
|
-
const
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
if (!btcAltRegime) {
|
|
1520
|
-
return {
|
|
1521
|
-
source: null,
|
|
1522
|
-
available: false,
|
|
1523
|
-
universe: null,
|
|
1524
|
-
interval: null,
|
|
1525
|
-
stale: null,
|
|
1526
|
-
regime: null,
|
|
1527
|
-
btcReturn24h: null,
|
|
1528
|
-
altBasketReturn24h: null,
|
|
1529
|
-
btcVsAltReturn24h: null,
|
|
1530
|
-
btcTurnoverShare24h: null,
|
|
1531
|
-
btcTurnoverShareChange24h: null,
|
|
1532
|
-
altVolToBtcVol24h: null,
|
|
1533
|
-
altDispersion24h: null
|
|
1534
|
-
};
|
|
1476
|
+
var getNodeCreateRequire = () => {
|
|
1477
|
+
const builtinModule = process.getBuiltinModule?.("module");
|
|
1478
|
+
if (typeof builtinModule?.createRequire === "function") {
|
|
1479
|
+
return builtinModule.createRequire;
|
|
1535
1480
|
}
|
|
1536
|
-
|
|
1537
|
-
source: String(btcAltRegime.source ?? ""),
|
|
1538
|
-
available: true,
|
|
1539
|
-
universe: String(btcAltRegime.universe ?? ""),
|
|
1540
|
-
interval: String(btcAltRegime.interval ?? ""),
|
|
1541
|
-
stale: typeof btcAltRegime.stale === "boolean" ? btcAltRegime.stale : null,
|
|
1542
|
-
regime: typeof btcAltRegime.regime === "string" ? btcAltRegime.regime : null,
|
|
1543
|
-
btcReturn24h: toFiniteNumber(btcAltRegime.btcReturn24h),
|
|
1544
|
-
altBasketReturn24h: toFiniteNumber(btcAltRegime.altBasketReturn24h),
|
|
1545
|
-
btcVsAltReturn24h: toFiniteNumber(btcAltRegime.btcVsAltReturn24h),
|
|
1546
|
-
btcTurnoverShare24h: toFiniteNumber(btcAltRegime.btcTurnoverShare24h),
|
|
1547
|
-
btcTurnoverShareChange24h: toFiniteNumber(
|
|
1548
|
-
btcAltRegime.btcTurnoverShareChange24h
|
|
1549
|
-
),
|
|
1550
|
-
altVolToBtcVol24h: toFiniteNumber(btcAltRegime.altVolToBtcVol24h),
|
|
1551
|
-
altDispersion24h: toFiniteNumber(btcAltRegime.altDispersion24h)
|
|
1552
|
-
};
|
|
1481
|
+
throw new TypeError("module.createRequire is not available");
|
|
1553
1482
|
};
|
|
1554
|
-
var
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
if (!cmcGlobal) {
|
|
1559
|
-
return {
|
|
1560
|
-
source: null,
|
|
1561
|
-
available: false,
|
|
1562
|
-
interval: null,
|
|
1563
|
-
asOfTs: null,
|
|
1564
|
-
stale: null,
|
|
1565
|
-
totalMarketCapUsd: null,
|
|
1566
|
-
totalVolumeUsd: null,
|
|
1567
|
-
totalVolumeReportedUsd: null,
|
|
1568
|
-
altMarketCapUsd: null,
|
|
1569
|
-
altVolumeUsd: null,
|
|
1570
|
-
altVolumeReportedUsd: null,
|
|
1571
|
-
btcDominancePct: null,
|
|
1572
|
-
ethDominancePct: null,
|
|
1573
|
-
btcDominanceChange24hPct: null,
|
|
1574
|
-
ethDominanceChange24hPct: null,
|
|
1575
|
-
altMarketCapChange24hPct: null,
|
|
1576
|
-
altVolumeChange24hPct: null,
|
|
1577
|
-
activeCryptocurrencies: null,
|
|
1578
|
-
activeExchanges: null,
|
|
1579
|
-
activeMarketPairs: null,
|
|
1580
|
-
altLiquidityRegime: null
|
|
1581
|
-
};
|
|
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;
|
|
1582
1487
|
}
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
altVolumeUsd: toFiniteNumber(cmcGlobal.altVolumeUsd),
|
|
1594
|
-
altVolumeReportedUsd: toFiniteNumber(cmcGlobal.altVolumeReportedUsd),
|
|
1595
|
-
btcDominancePct: toFiniteNumber(cmcGlobal.btcDominancePct),
|
|
1596
|
-
ethDominancePct: toFiniteNumber(cmcGlobal.ethDominancePct),
|
|
1597
|
-
btcDominanceChange24hPct: toFiniteNumber(
|
|
1598
|
-
cmcGlobal.btcDominanceChange24hPct
|
|
1599
|
-
),
|
|
1600
|
-
ethDominanceChange24hPct: toFiniteNumber(
|
|
1601
|
-
cmcGlobal.ethDominanceChange24hPct
|
|
1602
|
-
),
|
|
1603
|
-
altMarketCapChange24hPct: toFiniteNumber(
|
|
1604
|
-
cmcGlobal.altMarketCapChange24hPct
|
|
1605
|
-
),
|
|
1606
|
-
altVolumeChange24hPct: toFiniteNumber(cmcGlobal.altVolumeChange24hPct),
|
|
1607
|
-
activeCryptocurrencies: toFiniteNumber(cmcGlobal.activeCryptocurrencies),
|
|
1608
|
-
activeExchanges: toFiniteNumber(cmcGlobal.activeExchanges),
|
|
1609
|
-
activeMarketPairs: toFiniteNumber(cmcGlobal.activeMarketPairs),
|
|
1610
|
-
altLiquidityRegime: typeof cmcGlobal.altLiquidityRegime === "string" ? cmcGlobal.altLiquidityRegime : null
|
|
1611
|
-
};
|
|
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;
|
|
1612
1498
|
};
|
|
1613
|
-
var
|
|
1614
|
-
const
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
if (!cmcReferenceAssets) {
|
|
1618
|
-
return {
|
|
1619
|
-
source: null,
|
|
1620
|
-
available: false,
|
|
1621
|
-
interval: null,
|
|
1622
|
-
asOfTs: null,
|
|
1623
|
-
stale: null,
|
|
1624
|
-
btcMarketCapUsd: null,
|
|
1625
|
-
ethMarketCapUsd: null,
|
|
1626
|
-
btcVolumeUsd: null,
|
|
1627
|
-
ethVolumeUsd: null,
|
|
1628
|
-
btcVolumeToMarketCap: null,
|
|
1629
|
-
ethVolumeToMarketCap: null,
|
|
1630
|
-
ethBtcMarketCapRatio: null,
|
|
1631
|
-
ethBtcMarketCapRatioChange24hPct: null,
|
|
1632
|
-
ethVsBtcVolumeRatio: null,
|
|
1633
|
-
referenceLiquidityRegime: null
|
|
1634
|
-
};
|
|
1499
|
+
var ensureTsconfigPathsRegistered = async (cwd = getTradejsProjectCwd()) => {
|
|
1500
|
+
const projectRoot = getTradejsProjectCwd(cwd);
|
|
1501
|
+
if (tsconfigPathsRegisteredByCwd.has(projectRoot)) {
|
|
1502
|
+
return;
|
|
1635
1503
|
}
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
ethBtcMarketCapRatio: toFiniteNumber(
|
|
1653
|
-
cmcReferenceAssets.ethBtcMarketCapRatio
|
|
1654
|
-
),
|
|
1655
|
-
ethBtcMarketCapRatioChange24hPct: toFiniteNumber(
|
|
1656
|
-
cmcReferenceAssets.ethBtcMarketCapRatioChange24hPct
|
|
1657
|
-
),
|
|
1658
|
-
ethVsBtcVolumeRatio: toFiniteNumber(cmcReferenceAssets.ethVsBtcVolumeRatio),
|
|
1659
|
-
referenceLiquidityRegime: typeof cmcReferenceAssets.referenceLiquidityRegime === "string" ? cmcReferenceAssets.referenceLiquidityRegime : null
|
|
1660
|
-
};
|
|
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);
|
|
1661
1520
|
};
|
|
1662
|
-
var
|
|
1663
|
-
const
|
|
1664
|
-
const
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
return
|
|
1668
|
-
source: null,
|
|
1669
|
-
available: false,
|
|
1670
|
-
interval: null,
|
|
1671
|
-
asOfTs: null,
|
|
1672
|
-
stale: null,
|
|
1673
|
-
exchangesCount: null,
|
|
1674
|
-
totalVolumeUsd: null,
|
|
1675
|
-
totalVolumeChange24hPct: null,
|
|
1676
|
-
binanceVolumeUsd: null,
|
|
1677
|
-
binanceVolumeShare: null,
|
|
1678
|
-
topExchangeVolumeShare: null,
|
|
1679
|
-
liquidityRegime: null
|
|
1680
|
-
};
|
|
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;
|
|
1681
1527
|
}
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
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;
|
|
1700
1556
|
};
|
|
1701
|
-
var
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
const cmcFearGreed = toRecord(relative?.cmcFearGreed);
|
|
1705
|
-
if (!cmcFearGreed) {
|
|
1706
|
-
return {
|
|
1707
|
-
source: null,
|
|
1708
|
-
available: false,
|
|
1709
|
-
interval: null,
|
|
1710
|
-
asOfTs: null,
|
|
1711
|
-
stale: null,
|
|
1712
|
-
value: null,
|
|
1713
|
-
valueChange24h: null,
|
|
1714
|
-
valueChange7d: null,
|
|
1715
|
-
classification: null,
|
|
1716
|
-
sentimentRegime: null
|
|
1717
|
-
};
|
|
1557
|
+
var toImportSpecifier = (moduleName) => {
|
|
1558
|
+
if (moduleName.startsWith("file://")) {
|
|
1559
|
+
return moduleName;
|
|
1718
1560
|
}
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
asOfTs: toFiniteNumber(cmcFearGreed.asOfTs),
|
|
1724
|
-
stale: typeof cmcFearGreed.stale === "boolean" ? cmcFearGreed.stale : null,
|
|
1725
|
-
value: toFiniteNumber(cmcFearGreed.value),
|
|
1726
|
-
valueChange24h: toFiniteNumber(cmcFearGreed.valueChange24h),
|
|
1727
|
-
valueChange7d: toFiniteNumber(cmcFearGreed.valueChange7d),
|
|
1728
|
-
classification: typeof cmcFearGreed.classification === "string" ? cmcFearGreed.classification : null,
|
|
1729
|
-
sentimentRegime: typeof cmcFearGreed.sentimentRegime === "string" ? cmcFearGreed.sentimentRegime : null
|
|
1730
|
-
};
|
|
1561
|
+
if (import_path.default.isAbsolute(moduleName)) {
|
|
1562
|
+
return (0, import_url.pathToFileURL)(moduleName).href;
|
|
1563
|
+
}
|
|
1564
|
+
return moduleName;
|
|
1731
1565
|
};
|
|
1732
|
-
var
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
const
|
|
1736
|
-
if (!
|
|
1737
|
-
return
|
|
1738
|
-
source: null,
|
|
1739
|
-
available: false,
|
|
1740
|
-
interval: null,
|
|
1741
|
-
asOfTs: null,
|
|
1742
|
-
stale: null,
|
|
1743
|
-
cmc100Value: null,
|
|
1744
|
-
cmc100Change24hPct: null,
|
|
1745
|
-
cmc100TopConstituentSymbol: null,
|
|
1746
|
-
cmc100TopConstituentWeightPct: null,
|
|
1747
|
-
cmc20Value: null,
|
|
1748
|
-
cmc20Change24hPct: null,
|
|
1749
|
-
cmc20TopConstituentSymbol: null,
|
|
1750
|
-
cmc20TopConstituentWeightPct: null,
|
|
1751
|
-
cmc20ToCmc100Ratio: null,
|
|
1752
|
-
cmc20ToCmc100RatioChange24hPct: null,
|
|
1753
|
-
indexRegime: null
|
|
1754
|
-
};
|
|
1755
|
-
}
|
|
1756
|
-
return {
|
|
1757
|
-
source: String(cmcIndexes.source ?? ""),
|
|
1758
|
-
available: true,
|
|
1759
|
-
interval: typeof cmcIndexes.interval === "string" ? cmcIndexes.interval : null,
|
|
1760
|
-
asOfTs: toFiniteNumber(cmcIndexes.asOfTs),
|
|
1761
|
-
stale: typeof cmcIndexes.stale === "boolean" ? cmcIndexes.stale : null,
|
|
1762
|
-
cmc100Value: toFiniteNumber(cmcIndexes.cmc100Value),
|
|
1763
|
-
cmc100Change24hPct: toFiniteNumber(cmcIndexes.cmc100Change24hPct),
|
|
1764
|
-
cmc100TopConstituentSymbol: typeof cmcIndexes.cmc100TopConstituentSymbol === "string" ? cmcIndexes.cmc100TopConstituentSymbol : null,
|
|
1765
|
-
cmc100TopConstituentWeightPct: toFiniteNumber(
|
|
1766
|
-
cmcIndexes.cmc100TopConstituentWeightPct
|
|
1767
|
-
),
|
|
1768
|
-
cmc20Value: toFiniteNumber(cmcIndexes.cmc20Value),
|
|
1769
|
-
cmc20Change24hPct: toFiniteNumber(cmcIndexes.cmc20Change24hPct),
|
|
1770
|
-
cmc20TopConstituentSymbol: typeof cmcIndexes.cmc20TopConstituentSymbol === "string" ? cmcIndexes.cmc20TopConstituentSymbol : null,
|
|
1771
|
-
cmc20TopConstituentWeightPct: toFiniteNumber(
|
|
1772
|
-
cmcIndexes.cmc20TopConstituentWeightPct
|
|
1773
|
-
),
|
|
1774
|
-
cmc20ToCmc100Ratio: toFiniteNumber(cmcIndexes.cmc20ToCmc100Ratio),
|
|
1775
|
-
cmc20ToCmc100RatioChange24hPct: toFiniteNumber(
|
|
1776
|
-
cmcIndexes.cmc20ToCmc100RatioChange24hPct
|
|
1777
|
-
),
|
|
1778
|
-
indexRegime: typeof cmcIndexes.indexRegime === "string" ? cmcIndexes.indexRegime : null
|
|
1779
|
-
};
|
|
1780
|
-
};
|
|
1781
|
-
var buildReferenceTradeFlowContextFromSignal = (signal) => {
|
|
1782
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1783
|
-
const relative = toRecord(baseContext?.relative);
|
|
1784
|
-
const refs = toRecord(relative?.referenceTradeFlow);
|
|
1785
|
-
const primaryReferenceSymbol = typeof refs?.primaryReferenceSymbol === "string" ? refs.primaryReferenceSymbol : null;
|
|
1786
|
-
const tradeFlowBySymbol = toRecord(refs?.tradeFlowBySymbol);
|
|
1787
|
-
const primaryTradeFlow = primaryReferenceSymbol != null ? toRecord(tradeFlowBySymbol?.[primaryReferenceSymbol]) : null;
|
|
1788
|
-
if (!refs) {
|
|
1789
|
-
return {
|
|
1790
|
-
source: null,
|
|
1791
|
-
available: false,
|
|
1792
|
-
primaryReferenceSymbol: null,
|
|
1793
|
-
referenceSymbols: [],
|
|
1794
|
-
primaryTradeFlowBuyPressurePct: null,
|
|
1795
|
-
primaryTradeFlowStale: null
|
|
1796
|
-
};
|
|
1797
|
-
}
|
|
1798
|
-
return {
|
|
1799
|
-
source: String(refs.source ?? ""),
|
|
1800
|
-
available: true,
|
|
1801
|
-
primaryReferenceSymbol,
|
|
1802
|
-
referenceSymbols: Array.isArray(refs.referenceSymbols) ? refs.referenceSymbols.map(String) : [],
|
|
1803
|
-
primaryTradeFlowBuyPressurePct: toFiniteNumber(
|
|
1804
|
-
primaryTradeFlow?.buyPressurePct
|
|
1805
|
-
),
|
|
1806
|
-
primaryTradeFlowStale: typeof primaryTradeFlow?.stale === "boolean" ? primaryTradeFlow.stale : null
|
|
1807
|
-
};
|
|
1808
|
-
};
|
|
1809
|
-
var buildAiMarketContext = (signal) => ({
|
|
1810
|
-
execution: {
|
|
1811
|
-
binanceCoinbaseSpread: buildSpreadContextFromSignal(signal)
|
|
1812
|
-
},
|
|
1813
|
-
participation: {
|
|
1814
|
-
trueDelta: buildTrueDeltaContextFromSignal(signal),
|
|
1815
|
-
tradeFlow: buildTradeFlowContextFromSignal(signal)
|
|
1816
|
-
},
|
|
1817
|
-
relative: {
|
|
1818
|
-
marketBreadth: buildMarketBreadthContextFromSignal(signal),
|
|
1819
|
-
marketBreadths: buildMarketBreadthsContextFromSignal(signal),
|
|
1820
|
-
targetVsBtc: buildTargetVsBtcContextFromSignal(signal),
|
|
1821
|
-
btcAltRegime: buildBtcAltRegimeContextFromSignal(signal),
|
|
1822
|
-
cmcGlobal: buildCmcGlobalContextFromSignal(signal),
|
|
1823
|
-
cmcReferenceAssets: buildCmcReferenceAssetsContextFromSignal(signal),
|
|
1824
|
-
cmcExchangeLiquidity: buildCmcExchangeLiquidityContextFromSignal(signal),
|
|
1825
|
-
cmcFearGreed: buildCmcFearGreedContextFromSignal(signal),
|
|
1826
|
-
cmcIndexes: buildCmcIndexesContextFromSignal(signal),
|
|
1827
|
-
referenceTradeFlow: buildReferenceTradeFlowContextFromSignal(signal)
|
|
1828
|
-
}
|
|
1829
|
-
});
|
|
1830
|
-
|
|
1831
|
-
// src/strategy/manifests.ts
|
|
1832
|
-
var import_indicators = require("@tradejs/core/indicators");
|
|
1833
|
-
|
|
1834
|
-
// src/tradejsConfig.ts
|
|
1835
|
-
var import_fs = __toESM(require("fs"));
|
|
1836
|
-
var import_path = __toESM(require("path"));
|
|
1837
|
-
var import_url = require("url");
|
|
1838
|
-
var import_config = require("@tradejs/core/config");
|
|
1839
|
-
var import_logger2 = require("@tradejs/infra/logger");
|
|
1840
|
-
var CONFIG_FILE_NAMES = [
|
|
1841
|
-
"tradejs.config.ts",
|
|
1842
|
-
"tradejs.config.mts",
|
|
1843
|
-
"tradejs.config.js",
|
|
1844
|
-
"tradejs.config.mjs",
|
|
1845
|
-
"tradejs.config.cjs"
|
|
1846
|
-
];
|
|
1847
|
-
var TS_MODULE_RE = /\.(cts|mts|ts)$/i;
|
|
1848
|
-
var cachedByCwd = /* @__PURE__ */ new Map();
|
|
1849
|
-
var announcedConfigFile = /* @__PURE__ */ new Set();
|
|
1850
|
-
var tsNodeRegistered = false;
|
|
1851
|
-
var tsconfigPathsRegisteredByCwd = /* @__PURE__ */ new Set();
|
|
1852
|
-
var tsconfigPathMatchersByCwd = /* @__PURE__ */ new Map();
|
|
1853
|
-
var getTradejsProjectCwd = (cwd) => {
|
|
1854
|
-
const explicit = String(cwd ?? "").trim();
|
|
1855
|
-
if (explicit) {
|
|
1856
|
-
return import_path.default.resolve(explicit);
|
|
1857
|
-
}
|
|
1858
|
-
const fromEnv = String(process.env.PROJECT_CWD || "").trim();
|
|
1859
|
-
if (fromEnv) {
|
|
1860
|
-
return import_path.default.resolve(fromEnv);
|
|
1861
|
-
}
|
|
1862
|
-
return process.cwd();
|
|
1863
|
-
};
|
|
1864
|
-
var normalizeConfig = (rawConfig) => {
|
|
1865
|
-
if (!rawConfig || typeof rawConfig !== "object") {
|
|
1866
|
-
return {};
|
|
1867
|
-
}
|
|
1868
|
-
const config = rawConfig;
|
|
1869
|
-
const strategies2 = Array.isArray(config.strategies) ? config.strategies.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1870
|
-
const indicators = Array.isArray(config.indicators) ? config.indicators.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1871
|
-
const connectors = Array.isArray(config.connectors) ? config.connectors.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1872
|
-
const hooks = (0, import_config.normalizeTradejsConfigHooks)(
|
|
1873
|
-
config.hooks
|
|
1874
|
-
);
|
|
1875
|
-
const runtime = config.runtime && typeof config.runtime === "object" && !Array.isArray(config.runtime) ? config.runtime : void 0;
|
|
1876
|
-
return {
|
|
1877
|
-
strategies: strategies2,
|
|
1878
|
-
indicators,
|
|
1879
|
-
connectors,
|
|
1880
|
-
...hooks ? { hooks } : {},
|
|
1881
|
-
...runtime ? { runtime } : {}
|
|
1882
|
-
};
|
|
1883
|
-
};
|
|
1884
|
-
var getNodeCreateRequire = () => {
|
|
1885
|
-
const builtinModule = process.getBuiltinModule?.("module");
|
|
1886
|
-
if (typeof builtinModule?.createRequire === "function") {
|
|
1887
|
-
return builtinModule.createRequire;
|
|
1888
|
-
}
|
|
1889
|
-
throw new TypeError("module.createRequire is not available");
|
|
1890
|
-
};
|
|
1891
|
-
var getRequireFn = (cwd = getTradejsProjectCwd()) => getNodeCreateRequire()(import_path.default.join(import_path.default.resolve(cwd), "__tradejs_loader__.js"));
|
|
1892
|
-
var ensureTsNodeRegistered = async () => {
|
|
1893
|
-
if (tsNodeRegistered) {
|
|
1894
|
-
return;
|
|
1895
|
-
}
|
|
1896
|
-
const tsNodeModule = await import("ts-node");
|
|
1897
|
-
const tsNode = tsNodeModule.default ?? tsNodeModule;
|
|
1898
|
-
tsNode.register?.({
|
|
1899
|
-
transpileOnly: true,
|
|
1900
|
-
compilerOptions: {
|
|
1901
|
-
module: "Node16",
|
|
1902
|
-
moduleResolution: "node16"
|
|
1903
|
-
}
|
|
1904
|
-
});
|
|
1905
|
-
tsNodeRegistered = true;
|
|
1906
|
-
};
|
|
1907
|
-
var ensureTsconfigPathsRegistered = async (cwd = getTradejsProjectCwd()) => {
|
|
1908
|
-
const projectRoot = getTradejsProjectCwd(cwd);
|
|
1909
|
-
if (tsconfigPathsRegisteredByCwd.has(projectRoot)) {
|
|
1910
|
-
return;
|
|
1911
|
-
}
|
|
1912
|
-
const tsconfigPathsModule = await import("tsconfig-paths");
|
|
1913
|
-
const loadConfig = tsconfigPathsModule.loadConfig;
|
|
1914
|
-
const register = tsconfigPathsModule.register;
|
|
1915
|
-
if (typeof loadConfig !== "function" || typeof register !== "function") {
|
|
1916
|
-
return;
|
|
1917
|
-
}
|
|
1918
|
-
const loadedConfig = loadConfig(projectRoot);
|
|
1919
|
-
if (loadedConfig.resultType !== "success") {
|
|
1920
|
-
return;
|
|
1921
|
-
}
|
|
1922
|
-
register({
|
|
1923
|
-
baseUrl: loadedConfig.absoluteBaseUrl,
|
|
1924
|
-
paths: loadedConfig.paths,
|
|
1925
|
-
addMatchAll: false
|
|
1926
|
-
});
|
|
1927
|
-
tsconfigPathsRegisteredByCwd.add(projectRoot);
|
|
1928
|
-
};
|
|
1929
|
-
var resolveTsconfigPathModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
1930
|
-
const projectRoot = getTradejsProjectCwd(cwd);
|
|
1931
|
-
const cachedMatcher = tsconfigPathMatchersByCwd.get(projectRoot);
|
|
1932
|
-
if (cachedMatcher) {
|
|
1933
|
-
const resolved2 = cachedMatcher(moduleName);
|
|
1934
|
-
return resolved2 || null;
|
|
1935
|
-
}
|
|
1936
|
-
const tsconfigPathsModule = await import("tsconfig-paths");
|
|
1937
|
-
const loadConfig = tsconfigPathsModule.loadConfig;
|
|
1938
|
-
const createMatchPath = tsconfigPathsModule.createMatchPath;
|
|
1939
|
-
if (typeof loadConfig !== "function" || typeof createMatchPath !== "function") {
|
|
1940
|
-
return null;
|
|
1941
|
-
}
|
|
1942
|
-
const loadedConfig = loadConfig(projectRoot);
|
|
1943
|
-
if (loadedConfig.resultType !== "success") {
|
|
1944
|
-
return null;
|
|
1945
|
-
}
|
|
1946
|
-
const matchPath = createMatchPath(
|
|
1947
|
-
loadedConfig.absoluteBaseUrl,
|
|
1948
|
-
loadedConfig.paths
|
|
1949
|
-
);
|
|
1950
|
-
const matcher = (requestedModule) => matchPath(requestedModule, void 0, import_fs.default.existsSync, [
|
|
1951
|
-
".ts",
|
|
1952
|
-
".tsx",
|
|
1953
|
-
".mts",
|
|
1954
|
-
".cts",
|
|
1955
|
-
".js",
|
|
1956
|
-
".jsx",
|
|
1957
|
-
".mjs",
|
|
1958
|
-
".cjs",
|
|
1959
|
-
".json"
|
|
1960
|
-
]) || "";
|
|
1961
|
-
tsconfigPathMatchersByCwd.set(projectRoot, matcher);
|
|
1962
|
-
const resolved = matcher(moduleName);
|
|
1963
|
-
return resolved || null;
|
|
1964
|
-
};
|
|
1965
|
-
var toImportSpecifier = (moduleName) => {
|
|
1966
|
-
if (moduleName.startsWith("file://")) {
|
|
1967
|
-
return moduleName;
|
|
1968
|
-
}
|
|
1969
|
-
if (import_path.default.isAbsolute(moduleName)) {
|
|
1970
|
-
return (0, import_url.pathToFileURL)(moduleName).href;
|
|
1971
|
-
}
|
|
1972
|
-
return moduleName;
|
|
1973
|
-
};
|
|
1974
|
-
var isTsModulePath = (moduleName) => TS_MODULE_RE.test(moduleName.split("?")[0]);
|
|
1975
|
-
var isRelativeModulePath = (moduleName) => moduleName.startsWith("./") || moduleName.startsWith("../");
|
|
1976
|
-
var isBareModuleSpecifier = (moduleName) => {
|
|
1977
|
-
const normalized = String(moduleName ?? "").trim();
|
|
1978
|
-
if (!normalized) {
|
|
1979
|
-
return false;
|
|
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;
|
|
1980
1572
|
}
|
|
1981
1573
|
if (normalized.startsWith("file://") || import_path.default.isAbsolute(normalized) || isRelativeModulePath(normalized)) {
|
|
1982
1574
|
return false;
|
|
@@ -2119,6 +1711,7 @@ var loadTradejsConfig = async (cwd = getTradejsProjectCwd()) => {
|
|
|
2119
1711
|
};
|
|
2120
1712
|
|
|
2121
1713
|
// src/strategy/manifests.ts
|
|
1714
|
+
var import_indicators = require("@tradejs/core/indicators");
|
|
2122
1715
|
var SHARED_STRATEGY_REGISTRY_KEY = "__tradejsNodeSharedStrategyRegistryV1__";
|
|
2123
1716
|
var sharedRegistryScope = globalThis;
|
|
2124
1717
|
var sharedStrategyRegistry = sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] ?? (sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] = {
|
|
@@ -2396,82 +1989,1220 @@ var ensureStrategyPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
|
|
|
2396
1989
|
}
|
|
2397
1990
|
await state.pluginsLoadPromise;
|
|
2398
1991
|
};
|
|
2399
|
-
var ensureIndicatorPluginsLoaded = async (cwd = getTradejsProjectCwd()) => ensureStrategyPluginsLoaded(cwd);
|
|
2400
|
-
var getStrategyCreator = async (name, cwd = getTradejsProjectCwd(), moduleName) => {
|
|
2401
|
-
if (moduleName?.trim()) {
|
|
2402
|
-
await ensureStrategyPluginsLoaded(cwd);
|
|
2403
|
-
return (await resolveStrategyModuleEntry({ strategyName: name, moduleName, cwd })).creator;
|
|
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;
|
|
1997
|
+
}
|
|
1998
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
1999
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
2000
|
+
return state.strategyCreators.get(name);
|
|
2001
|
+
};
|
|
2002
|
+
var getStrategyDefaults = async (name, cwd = getTradejsProjectCwd()) => {
|
|
2003
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
2004
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
2005
|
+
return state.strategyEntriesMap.get(name)?.defaults;
|
|
2006
|
+
};
|
|
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));
|
|
2011
|
+
};
|
|
2012
|
+
var getRegisteredStrategies = (cwd = getTradejsProjectCwd()) => {
|
|
2013
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
2014
|
+
return Object.fromEntries(state.strategyCreators.entries());
|
|
2015
|
+
};
|
|
2016
|
+
var getRegisteredManifests = (cwd = getTradejsProjectCwd()) => {
|
|
2017
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
2018
|
+
return [...state.strategyManifestsMap.values()];
|
|
2019
|
+
};
|
|
2020
|
+
var getStrategyManifest = (name, cwd = getTradejsProjectCwd()) => {
|
|
2021
|
+
if (!name) {
|
|
2022
|
+
return void 0;
|
|
2023
|
+
}
|
|
2024
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
2025
|
+
return state.strategyManifestsMap.get(name);
|
|
2026
|
+
};
|
|
2027
|
+
var isKnownStrategy = (name, cwd = getTradejsProjectCwd()) => {
|
|
2028
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
2029
|
+
return state.strategyCreators.has(name);
|
|
2030
|
+
};
|
|
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"));
|
|
2036
|
+
}
|
|
2037
|
+
registerEntries(entries, "runtime", state);
|
|
2038
|
+
};
|
|
2039
|
+
var resetStrategyRegistryCache = (cwd) => {
|
|
2040
|
+
const normalizedCwd = String(cwd ?? "").trim();
|
|
2041
|
+
if (!normalizedCwd) {
|
|
2042
|
+
registryStateByProjectRoot.clear();
|
|
2043
|
+
(0, import_indicators.resetIndicatorRegistryCache)();
|
|
2044
|
+
return;
|
|
2045
|
+
}
|
|
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;
|
|
2056
|
+
}
|
|
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
|
+
})
|
|
2066
|
+
}
|
|
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"
|
|
2080
|
+
);
|
|
2081
|
+
return { provider, universeId };
|
|
2082
|
+
};
|
|
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);
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
return (await cached.value)?.get(symbol);
|
|
2124
|
+
};
|
|
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
|
+
};
|
|
2148
|
+
}
|
|
2149
|
+
};
|
|
2150
|
+
|
|
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
|
|
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;
|
|
2173
|
+
};
|
|
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
|
|
2196
|
+
};
|
|
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
|
+
};
|
|
2233
|
+
}
|
|
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;
|
|
2250
|
+
}
|
|
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);
|
|
2283
|
+
}
|
|
2284
|
+
let response;
|
|
2285
|
+
try {
|
|
2286
|
+
response = await responsePromise;
|
|
2287
|
+
} finally {
|
|
2288
|
+
pendingResponses.delete(responseFile);
|
|
2289
|
+
}
|
|
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);
|
|
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
|
+
};
|
|
2339
|
+
};
|
|
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
|
+
};
|
|
2373
|
+
}
|
|
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
|
|
2397
|
+
);
|
|
2398
|
+
return assessment;
|
|
2399
|
+
};
|
|
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";
|
|
2419
|
+
}
|
|
2420
|
+
};
|
|
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 };
|
|
2435
|
+
};
|
|
2436
|
+
var validateEntryExecution = ({
|
|
2437
|
+
direction,
|
|
2438
|
+
price,
|
|
2439
|
+
qty,
|
|
2440
|
+
stopLossPrice,
|
|
2441
|
+
takeProfitPrice,
|
|
2442
|
+
limits
|
|
2443
|
+
}) => {
|
|
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"
|
|
2447
|
+
);
|
|
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"
|
|
2452
|
+
);
|
|
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"
|
|
2466
|
+
);
|
|
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
|
+
}
|
|
2495
|
+
try {
|
|
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
|
+
};
|
|
2511
|
+
} catch (error) {
|
|
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
|
+
};
|
|
2525
|
+
}
|
|
2526
|
+
};
|
|
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");
|
|
2540
|
+
}
|
|
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
|
+
};
|
|
3036
|
+
};
|
|
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
|
+
};
|
|
2404
3056
|
}
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
};
|
|
2423
|
-
var getRegisteredManifests = (cwd = getTradejsProjectCwd()) => {
|
|
2424
|
-
const { state } = getStrategyRegistryState(cwd);
|
|
2425
|
-
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
|
+
};
|
|
2426
3075
|
};
|
|
2427
|
-
var
|
|
2428
|
-
|
|
2429
|
-
|
|
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
|
+
};
|
|
2430
3093
|
}
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
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
|
+
};
|
|
2437
3106
|
};
|
|
2438
|
-
var
|
|
2439
|
-
const
|
|
2440
|
-
const
|
|
2441
|
-
|
|
2442
|
-
|
|
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
|
+
};
|
|
2443
3130
|
}
|
|
2444
|
-
|
|
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
|
+
};
|
|
2445
3155
|
};
|
|
2446
|
-
var
|
|
2447
|
-
const
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
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
|
+
};
|
|
2452
3172
|
}
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
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
|
+
};
|
|
2456
3183
|
};
|
|
2457
|
-
var
|
|
2458
|
-
{
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
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)
|
|
2473
3204
|
}
|
|
2474
|
-
);
|
|
3205
|
+
});
|
|
2475
3206
|
|
|
2476
3207
|
// src/strategy/policyProfiles.ts
|
|
2477
3208
|
var profileMatches = (profile, universe, assetClass) => {
|
|
@@ -2573,12 +3304,61 @@ var buildBaseAiPayload = (signal) => {
|
|
|
2573
3304
|
};
|
|
2574
3305
|
};
|
|
2575
3306
|
var defaultAiAdapter = {};
|
|
3307
|
+
var tickerProfileFeatureIds = new Set(
|
|
3308
|
+
import_aiFeatures.TICKER_PROFILE_AI_FEATURES.map(({ id }) => id)
|
|
3309
|
+
);
|
|
2576
3310
|
var getStrategyAiAdapter = (strategy, profileId) => getStrategyProfileAiAdapter(getStrategyManifest(strategy), profileId) ?? defaultAiAdapter;
|
|
2577
3311
|
var getSignalAiAdapter = (signal) => getStrategyAiAdapter(signal.strategy, signal.policyProfileId);
|
|
2578
3312
|
var buildAiPayloadByStrategy = (signal) => {
|
|
2579
3313
|
const basePayload = buildBaseAiPayload(signal);
|
|
2580
3314
|
const adapter = getSignalAiAdapter(signal);
|
|
2581
|
-
|
|
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
|
+
};
|
|
2582
3362
|
};
|
|
2583
3363
|
var buildAiSystemPromptAddonByStrategy = (signal) => getSignalAiAdapter(signal).buildSystemPromptAddon?.({ signal }) ?? "";
|
|
2584
3364
|
var buildAiHumanPromptAddonByStrategy = (signal, payload) => getSignalAiAdapter(signal).buildHumanPromptAddon?.({
|
|
@@ -3040,7 +3820,7 @@ var askAI = async (signal, options = {}) => {
|
|
|
3040
3820
|
payload
|
|
3041
3821
|
}
|
|
3042
3822
|
);
|
|
3043
|
-
await (0,
|
|
3823
|
+
await (0, import_redis3.setData)(import_redis3.redisKeys.analysis(symbol, signal.signalId), content);
|
|
3044
3824
|
return content;
|
|
3045
3825
|
};
|
|
3046
3826
|
|
|
@@ -3092,254 +3872,6 @@ var buildMlPayload = (payload) => {
|
|
|
3092
3872
|
};
|
|
3093
3873
|
};
|
|
3094
3874
|
|
|
3095
|
-
// src/runtimeJournal.ts
|
|
3096
|
-
var import_node_crypto = require("crypto");
|
|
3097
|
-
var import_constants = require("@tradejs/core/constants");
|
|
3098
|
-
var import_time = require("@tradejs/core/time");
|
|
3099
|
-
var import_trade = require("@tradejs/core/trade");
|
|
3100
|
-
var import_logger3 = require("@tradejs/infra/logger");
|
|
3101
|
-
var import_redis2 = require("@tradejs/infra/redis");
|
|
3102
|
-
var now = () => Date.now();
|
|
3103
|
-
var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
|
|
3104
|
-
var calculateClosedPnl = ({
|
|
3105
|
-
direction,
|
|
3106
|
-
entryPrice,
|
|
3107
|
-
exitPrice,
|
|
3108
|
-
qty
|
|
3109
|
-
}) => {
|
|
3110
|
-
const pnl = direction === "LONG" ? (exitPrice - entryPrice) * qty : (entryPrice - exitPrice) * qty;
|
|
3111
|
-
return Number.isFinite(pnl) ? pnl : null;
|
|
3112
|
-
};
|
|
3113
|
-
var createRuntimeOrderId = (strategy) => {
|
|
3114
|
-
const prefix = (0, import_trade.createRuntimeOrderLinkPrefix)(strategy);
|
|
3115
|
-
if (prefix === "tjs-") {
|
|
3116
|
-
return `tjs-${(0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 24).toLowerCase()}`;
|
|
3117
|
-
}
|
|
3118
|
-
return `${prefix}${toRandomOrderSuffix()}`;
|
|
3119
|
-
};
|
|
3120
|
-
var recordRuntimeTradeOpen = async (params) => {
|
|
3121
|
-
const { userName } = params;
|
|
3122
|
-
if (!userName) {
|
|
3123
|
-
return null;
|
|
3124
|
-
}
|
|
3125
|
-
const record = {
|
|
3126
|
-
...params,
|
|
3127
|
-
entryCount: 1,
|
|
3128
|
-
lastEntryPrice: params.entryPrice,
|
|
3129
|
-
lastEntryQty: params.qty,
|
|
3130
|
-
lastEntryTimestamp: params.entryTimestamp,
|
|
3131
|
-
status: "active",
|
|
3132
|
-
currentPrice: params.entryPrice,
|
|
3133
|
-
currentPnl: 0,
|
|
3134
|
-
closedPnl: null,
|
|
3135
|
-
exitPrice: null,
|
|
3136
|
-
exitTimestamp: null,
|
|
3137
|
-
lastSyncedAt: now()
|
|
3138
|
-
};
|
|
3139
|
-
const dayKey = (0, import_time.getRuntimeStorageDayKey)(record.entryTimestamp);
|
|
3140
|
-
const runtimeScopeId = record.deploymentId ?? record.accountId;
|
|
3141
|
-
try {
|
|
3142
|
-
await Promise.all([
|
|
3143
|
-
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, record.orderId), record, {
|
|
3144
|
-
expire: 0
|
|
3145
|
-
}),
|
|
3146
|
-
(0, import_redis2.setHashJsonField)(
|
|
3147
|
-
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
3148
|
-
record.orderId,
|
|
3149
|
-
record,
|
|
3150
|
-
{ expire: 0 }
|
|
3151
|
-
),
|
|
3152
|
-
(0, import_redis2.setData)(
|
|
3153
|
-
import_redis2.redisKeys.runtimeActiveTrade(userName, record.symbol, runtimeScopeId),
|
|
3154
|
-
{ orderId: record.orderId },
|
|
3155
|
-
{ expire: 0 }
|
|
3156
|
-
)
|
|
3157
|
-
]);
|
|
3158
|
-
} catch (error) {
|
|
3159
|
-
import_logger3.logger.error(
|
|
3160
|
-
"runtime trade open journal failed: %s %s",
|
|
3161
|
-
record.symbol,
|
|
3162
|
-
error?.message || String(error)
|
|
3163
|
-
);
|
|
3164
|
-
}
|
|
3165
|
-
return record;
|
|
3166
|
-
};
|
|
3167
|
-
var recordRuntimeTradeIncrease = async (params) => {
|
|
3168
|
-
const {
|
|
3169
|
-
userName,
|
|
3170
|
-
strategy,
|
|
3171
|
-
symbol,
|
|
3172
|
-
direction,
|
|
3173
|
-
resultingQty,
|
|
3174
|
-
resultingEntryPrice,
|
|
3175
|
-
addedQty,
|
|
3176
|
-
addedEntryPrice,
|
|
3177
|
-
entryTimestamp,
|
|
3178
|
-
fee,
|
|
3179
|
-
accountId,
|
|
3180
|
-
deploymentId
|
|
3181
|
-
} = params;
|
|
3182
|
-
if (!userName) {
|
|
3183
|
-
return null;
|
|
3184
|
-
}
|
|
3185
|
-
const existing = await getActiveRuntimeTrade({
|
|
3186
|
-
userName,
|
|
3187
|
-
symbol,
|
|
3188
|
-
accountId,
|
|
3189
|
-
deploymentId
|
|
3190
|
-
});
|
|
3191
|
-
if (!existing || existing.strategy !== strategy || existing.direction !== direction) {
|
|
3192
|
-
return null;
|
|
3193
|
-
}
|
|
3194
|
-
const addedFee = typeof fee === "number" && Number.isFinite(fee) ? fee : 0;
|
|
3195
|
-
const openFee = (existing.openFee ?? existing.fee ?? 0) + addedFee;
|
|
3196
|
-
const totalFee = (existing.totalFee ?? existing.fee ?? 0) + addedFee;
|
|
3197
|
-
const next = {
|
|
3198
|
-
...existing,
|
|
3199
|
-
...params.orderLeverage ? { orderLeverage: params.orderLeverage } : {},
|
|
3200
|
-
qty: resultingQty,
|
|
3201
|
-
entryPrice: resultingEntryPrice,
|
|
3202
|
-
entryCount: Math.max(1, existing.entryCount ?? 1) + 1,
|
|
3203
|
-
lastEntryPrice: addedEntryPrice,
|
|
3204
|
-
lastEntryQty: addedQty,
|
|
3205
|
-
lastEntryTimestamp: entryTimestamp,
|
|
3206
|
-
currentPrice: resultingEntryPrice,
|
|
3207
|
-
currentPnl: 0,
|
|
3208
|
-
fee: openFee,
|
|
3209
|
-
openFee,
|
|
3210
|
-
totalFee,
|
|
3211
|
-
lastSyncedAt: now()
|
|
3212
|
-
};
|
|
3213
|
-
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
3214
|
-
try {
|
|
3215
|
-
await Promise.all([
|
|
3216
|
-
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, existing.orderId), next, {
|
|
3217
|
-
expire: 0
|
|
3218
|
-
}),
|
|
3219
|
-
(0, import_redis2.setHashJsonField)(
|
|
3220
|
-
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
3221
|
-
existing.orderId,
|
|
3222
|
-
next,
|
|
3223
|
-
{ expire: 0 }
|
|
3224
|
-
)
|
|
3225
|
-
]);
|
|
3226
|
-
} catch (error) {
|
|
3227
|
-
import_logger3.logger.error(
|
|
3228
|
-
"runtime trade increase journal failed: %s %s",
|
|
3229
|
-
symbol,
|
|
3230
|
-
error?.message || String(error)
|
|
3231
|
-
);
|
|
3232
|
-
}
|
|
3233
|
-
return next;
|
|
3234
|
-
};
|
|
3235
|
-
var getActiveRuntimeTrade = async (params) => {
|
|
3236
|
-
const { userName, symbol, accountId, deploymentId } = params;
|
|
3237
|
-
if (!userName) {
|
|
3238
|
-
return null;
|
|
3239
|
-
}
|
|
3240
|
-
const activeRef = await (0, import_redis2.getData)(
|
|
3241
|
-
import_redis2.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId),
|
|
3242
|
-
null
|
|
3243
|
-
);
|
|
3244
|
-
const orderId = String(activeRef?.orderId || "").trim();
|
|
3245
|
-
if (!orderId) {
|
|
3246
|
-
return null;
|
|
3247
|
-
}
|
|
3248
|
-
const existing = await (0, import_redis2.getData)(
|
|
3249
|
-
import_redis2.redisKeys.runtimeTrade(userName, orderId),
|
|
3250
|
-
null
|
|
3251
|
-
);
|
|
3252
|
-
if (!existing || existing.status !== "active") {
|
|
3253
|
-
await (0, import_redis2.delKey)(
|
|
3254
|
-
import_redis2.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
|
|
3255
|
-
);
|
|
3256
|
-
return null;
|
|
3257
|
-
}
|
|
3258
|
-
return existing;
|
|
3259
|
-
};
|
|
3260
|
-
var markRuntimeTradeClosed = async (params) => {
|
|
3261
|
-
const {
|
|
3262
|
-
userName,
|
|
3263
|
-
symbol,
|
|
3264
|
-
strategy,
|
|
3265
|
-
exitPrice,
|
|
3266
|
-
exitTimestamp,
|
|
3267
|
-
closedPnl,
|
|
3268
|
-
exitType,
|
|
3269
|
-
accountId,
|
|
3270
|
-
deploymentId
|
|
3271
|
-
} = params;
|
|
3272
|
-
if (!userName) {
|
|
3273
|
-
return null;
|
|
3274
|
-
}
|
|
3275
|
-
const existing = await getActiveRuntimeTrade({
|
|
3276
|
-
userName,
|
|
3277
|
-
symbol,
|
|
3278
|
-
accountId,
|
|
3279
|
-
deploymentId
|
|
3280
|
-
});
|
|
3281
|
-
if (!existing) {
|
|
3282
|
-
return null;
|
|
3283
|
-
}
|
|
3284
|
-
const orderId = existing.orderId;
|
|
3285
|
-
if (strategy && existing.strategy !== strategy) {
|
|
3286
|
-
return null;
|
|
3287
|
-
}
|
|
3288
|
-
const resolvedExitPrice = typeof exitPrice === "number" && Number.isFinite(exitPrice) ? exitPrice : existing.currentPrice ?? existing.entryPrice;
|
|
3289
|
-
const resolvedClosedPnl = typeof closedPnl === "number" && Number.isFinite(closedPnl) ? closedPnl : typeof resolvedExitPrice === "number" && Number.isFinite(resolvedExitPrice) ? calculateClosedPnl({
|
|
3290
|
-
direction: existing.direction,
|
|
3291
|
-
entryPrice: existing.entryPrice,
|
|
3292
|
-
exitPrice: resolvedExitPrice,
|
|
3293
|
-
qty: existing.qty
|
|
3294
|
-
}) : existing.closedPnl ?? existing.currentPnl ?? null;
|
|
3295
|
-
const next = {
|
|
3296
|
-
...existing,
|
|
3297
|
-
status: "closed",
|
|
3298
|
-
currentPrice: resolvedExitPrice,
|
|
3299
|
-
currentPnl: resolvedClosedPnl,
|
|
3300
|
-
closedPnl: resolvedClosedPnl,
|
|
3301
|
-
exitPrice: resolvedExitPrice,
|
|
3302
|
-
exitTimestamp: typeof exitTimestamp === "number" && Number.isFinite(exitTimestamp) ? exitTimestamp : now(),
|
|
3303
|
-
exitType: exitType ?? existing.exitType ?? null,
|
|
3304
|
-
lastSyncedAt: now()
|
|
3305
|
-
};
|
|
3306
|
-
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
3307
|
-
const closeDayKey = (0, import_time.getRuntimeStorageDayKey)(next.exitTimestamp);
|
|
3308
|
-
try {
|
|
3309
|
-
await Promise.all([
|
|
3310
|
-
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, orderId), next, {
|
|
3311
|
-
expire: import_constants.TTL_1M
|
|
3312
|
-
}),
|
|
3313
|
-
(0, import_redis2.setHashJsonField)(
|
|
3314
|
-
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
3315
|
-
orderId,
|
|
3316
|
-
next,
|
|
3317
|
-
{ expire: import_constants.TTL_1M }
|
|
3318
|
-
),
|
|
3319
|
-
(0, import_redis2.setHashJsonField)(
|
|
3320
|
-
import_redis2.redisKeys.runtimeClosedTradeBucket(userName, closeDayKey),
|
|
3321
|
-
orderId,
|
|
3322
|
-
next,
|
|
3323
|
-
{ expire: import_constants.TTL_1M }
|
|
3324
|
-
),
|
|
3325
|
-
(0, import_redis2.delKey)(
|
|
3326
|
-
import_redis2.redisKeys.runtimeActiveTrade(
|
|
3327
|
-
userName,
|
|
3328
|
-
symbol,
|
|
3329
|
-
deploymentId ?? accountId
|
|
3330
|
-
)
|
|
3331
|
-
)
|
|
3332
|
-
]);
|
|
3333
|
-
} catch (error) {
|
|
3334
|
-
import_logger3.logger.error(
|
|
3335
|
-
"runtime trade close journal failed: %s %s",
|
|
3336
|
-
symbol,
|
|
3337
|
-
error?.message || String(error)
|
|
3338
|
-
);
|
|
3339
|
-
}
|
|
3340
|
-
return next;
|
|
3341
|
-
};
|
|
3342
|
-
|
|
3343
3875
|
// src/strategyHelpers/marketContextStages.ts
|
|
3344
3876
|
var import_logger8 = require("@tradejs/infra/logger");
|
|
3345
3877
|
|
|
@@ -3349,7 +3881,7 @@ var import_logger4 = require("@tradejs/infra/logger");
|
|
|
3349
3881
|
var import_strategies = require("@tradejs/core/strategies");
|
|
3350
3882
|
|
|
3351
3883
|
// src/binanceBreadthUniverses.ts
|
|
3352
|
-
var
|
|
3884
|
+
var import_node_crypto3 = require("crypto");
|
|
3353
3885
|
|
|
3354
3886
|
// src/config/binanceBreadthUniverses.json
|
|
3355
3887
|
var binanceBreadthUniverses_default = {
|
|
@@ -3588,7 +4120,7 @@ var BINANCE_BREADTH_UNIVERSE_KEYS = [
|
|
|
3588
4120
|
"top50",
|
|
3589
4121
|
"top100"
|
|
3590
4122
|
];
|
|
3591
|
-
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);
|
|
3592
4124
|
var normalizeSymbols = (symbols) => symbols.map((symbol) => symbol.trim().toUpperCase()).filter(Boolean);
|
|
3593
4125
|
var buildBinanceBreadthUniverseSnapshot = ({
|
|
3594
4126
|
rankedSymbols,
|
|
@@ -4612,7 +5144,7 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
4612
5144
|
};
|
|
4613
5145
|
|
|
4614
5146
|
// src/strategyHelpers/derivativesContext.ts
|
|
4615
|
-
var
|
|
5147
|
+
var import_indicators3 = require("@tradejs/core/indicators");
|
|
4616
5148
|
var import_data2 = require("@tradejs/core/data");
|
|
4617
5149
|
var import_strategies3 = require("@tradejs/core/strategies");
|
|
4618
5150
|
var import_constants2 = require("@tradejs/core/constants");
|
|
@@ -4646,7 +5178,7 @@ var parseLookbackMs = () => {
|
|
|
4646
5178
|
};
|
|
4647
5179
|
var withHourlyFallbackRows = (rowsByInterval) => ({
|
|
4648
5180
|
"15m": rowsByInterval["15m"] ?? [],
|
|
4649
|
-
"1h": (0,
|
|
5181
|
+
"1h": (0, import_indicators3.buildCoinalyzeHourlyRowsWithFallback)({
|
|
4650
5182
|
rows15m: rowsByInterval["15m"],
|
|
4651
5183
|
fallbackRows1h: rowsByInterval["1h"]
|
|
4652
5184
|
})
|
|
@@ -4774,7 +5306,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
4774
5306
|
const targetSymbol = normalizeSymbol(signal.symbol);
|
|
4775
5307
|
const lookbackMs = parseLookbackMs();
|
|
4776
5308
|
const decisionTimeMs = signal.timestamp + (0, import_data2.intervalToMs)(signal.interval);
|
|
4777
|
-
const derivativesEndMs = (0,
|
|
5309
|
+
const derivativesEndMs = (0, import_indicators3.getLastClosedDerivativesBarStartMs)(
|
|
4778
5310
|
decisionTimeMs,
|
|
4779
5311
|
"15m"
|
|
4780
5312
|
);
|
|
@@ -4789,7 +5321,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
4789
5321
|
});
|
|
4790
5322
|
return [
|
|
4791
5323
|
symbol,
|
|
4792
|
-
(0,
|
|
5324
|
+
(0, import_indicators3.buildDerivativesContext)({
|
|
4793
5325
|
symbol,
|
|
4794
5326
|
direction: signal.direction,
|
|
4795
5327
|
timestamp: derivativesEndMs,
|
|
@@ -4816,7 +5348,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
4816
5348
|
lookbackMs,
|
|
4817
5349
|
...params.abortSignal ? { signal: params.abortSignal } : {}
|
|
4818
5350
|
});
|
|
4819
|
-
const context = (0,
|
|
5351
|
+
const context = (0, import_indicators3.buildDerivativesContext)({
|
|
4820
5352
|
symbol: targetSymbol,
|
|
4821
5353
|
direction: signal.direction,
|
|
4822
5354
|
timestamp: derivativesEndMs,
|
|
@@ -4864,7 +5396,7 @@ var import_hyperliquidWhales2 = require("@tradejs/infra/timescale/hyperliquidWha
|
|
|
4864
5396
|
var import_logger7 = require("@tradejs/infra/logger");
|
|
4865
5397
|
|
|
4866
5398
|
// src/hyperliquidWhaleUniverse.ts
|
|
4867
|
-
var
|
|
5399
|
+
var import_node_crypto4 = require("crypto");
|
|
4868
5400
|
|
|
4869
5401
|
// src/config/hyperliquidPerpUniverse.json
|
|
4870
5402
|
var hyperliquidPerpUniverse_default = {
|
|
@@ -5036,7 +5568,7 @@ var hyperliquidWhales_default = {
|
|
|
5036
5568
|
};
|
|
5037
5569
|
|
|
5038
5570
|
// src/hyperliquidWhaleUniverse.ts
|
|
5039
|
-
var fingerprint2 = (value) => (0,
|
|
5571
|
+
var fingerprint2 = (value) => (0, import_node_crypto4.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
|
|
5040
5572
|
var validateUnique = (values, label) => {
|
|
5041
5573
|
if (new Set(values).size !== values.length) {
|
|
5042
5574
|
throw new Error(`Hyperliquid ${label} snapshot contains duplicates`);
|
|
@@ -5523,6 +6055,7 @@ var enrichSignalWithMarketContextStages = async ({
|
|
|
5523
6055
|
})
|
|
5524
6056
|
});
|
|
5525
6057
|
}
|
|
6058
|
+
await enrichSignalWithTickerProfile(signal);
|
|
5526
6059
|
const results = [];
|
|
5527
6060
|
for (const stage of stages) {
|
|
5528
6061
|
if (abortSignal?.aborted) break;
|
|
@@ -5570,12 +6103,12 @@ var findReplayAiAnalysis = ({
|
|
|
5570
6103
|
direction,
|
|
5571
6104
|
ai
|
|
5572
6105
|
}) => {
|
|
5573
|
-
const
|
|
5574
|
-
if (!Array.isArray(
|
|
6106
|
+
const snapshots2 = ai?.replayAnalyses;
|
|
6107
|
+
if (!Array.isArray(snapshots2) || !snapshots2.length) {
|
|
5575
6108
|
return void 0;
|
|
5576
6109
|
}
|
|
5577
6110
|
let best = null;
|
|
5578
|
-
for (const snapshot2 of
|
|
6111
|
+
for (const snapshot2 of snapshots2) {
|
|
5579
6112
|
if (snapshot2.symbol !== signal.symbol || snapshot2.direction !== direction || snapshot2.strategy && snapshot2.strategy !== signal.strategy) {
|
|
5580
6113
|
continue;
|
|
5581
6114
|
}
|
|
@@ -5735,7 +6268,9 @@ var executeEntryOrder = async ({
|
|
|
5735
6268
|
beforePlaceOrder,
|
|
5736
6269
|
recordRuntimeTrade = true,
|
|
5737
6270
|
leverage,
|
|
5738
|
-
executionLimits
|
|
6271
|
+
executionLimits,
|
|
6272
|
+
markPortfolioSubmitted,
|
|
6273
|
+
rejectPortfolioSubmission
|
|
5739
6274
|
}) => {
|
|
5740
6275
|
await beforePlaceOrder?.();
|
|
5741
6276
|
const previousPosition = positionIntent === "increase" ? await connector.getPosition(symbol) : null;
|
|
@@ -5768,12 +6303,12 @@ var executeEntryOrder = async ({
|
|
|
5768
6303
|
if (executionLimits?.maxLossValue != null) {
|
|
5769
6304
|
signal.riskDecision = {
|
|
5770
6305
|
stage: "order_plan",
|
|
5771
|
-
status: "approved",
|
|
5772
|
-
reason: "PLANNED_LOSS_WITHIN_MAX_LOSS_VALUE",
|
|
6306
|
+
status: economics2.riskExceeded ? "warning" : "approved",
|
|
6307
|
+
reason: economics2.riskExceeded ? "ENTRY_RISK_EXCEEDS_MAX_LOSS_AT_SUBMISSION" : "PLANNED_LOSS_WITHIN_MAX_LOSS_VALUE",
|
|
5773
6308
|
plannedLossValue: economics2.plannedLossValue,
|
|
5774
6309
|
maxLossValue: executionLimits.maxLossValue,
|
|
5775
6310
|
limitUtilization: economics2.plannedLossValue / executionLimits.maxLossValue,
|
|
5776
|
-
enforced:
|
|
6311
|
+
enforced: false
|
|
5777
6312
|
};
|
|
5778
6313
|
}
|
|
5779
6314
|
}
|
|
@@ -5808,7 +6343,13 @@ var executeEntryOrder = async ({
|
|
|
5808
6343
|
...typeof leverage === "number" && Number.isFinite(leverage) ? { leverage } : {},
|
|
5809
6344
|
orderId,
|
|
5810
6345
|
signal,
|
|
5811
|
-
...
|
|
6346
|
+
...rejectPortfolioSubmission ? { onRejected: rejectPortfolioSubmission } : {},
|
|
6347
|
+
...beforeSubmit || markPortfolioSubmitted ? {
|
|
6348
|
+
beforeSubmit: async (context) => {
|
|
6349
|
+
await beforeSubmit?.(context);
|
|
6350
|
+
await markPortfolioSubmitted?.(context.qty);
|
|
6351
|
+
}
|
|
6352
|
+
} : {}
|
|
5812
6353
|
});
|
|
5813
6354
|
const orderAckTime = Date.now();
|
|
5814
6355
|
const placedQty = typeof signal.orderQty === "number" && Number.isFinite(signal.orderQty) && signal.orderQty > 0 ? signal.orderQty : qty;
|
|
@@ -6796,7 +7337,7 @@ var handleProtectDecision = async ({
|
|
|
6796
7337
|
}
|
|
6797
7338
|
return decision.code;
|
|
6798
7339
|
};
|
|
6799
|
-
var
|
|
7340
|
+
var executeAdmittedEntryDecision = async ({
|
|
6800
7341
|
connector,
|
|
6801
7342
|
symbol,
|
|
6802
7343
|
decision,
|
|
@@ -6810,7 +7351,9 @@ var executeEntryDecision = async ({
|
|
|
6810
7351
|
ai,
|
|
6811
7352
|
recordRuntimeJournal,
|
|
6812
7353
|
invokeStageHooks,
|
|
6813
|
-
notifyRuntimeError
|
|
7354
|
+
notifyRuntimeError,
|
|
7355
|
+
markPortfolioSubmitted,
|
|
7356
|
+
rejectPortfolioSubmission
|
|
6814
7357
|
}) => {
|
|
6815
7358
|
const signal = decision.signal;
|
|
6816
7359
|
const beforePlaceOrder = async () => {
|
|
@@ -6857,6 +7400,8 @@ var executeEntryDecision = async ({
|
|
|
6857
7400
|
...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {},
|
|
6858
7401
|
signal,
|
|
6859
7402
|
beforePlaceOrder,
|
|
7403
|
+
markPortfolioSubmitted,
|
|
7404
|
+
rejectPortfolioSubmission,
|
|
6860
7405
|
recordRuntimeTrade: recordRuntimeJournal,
|
|
6861
7406
|
executionLimits: resolveEntryExecutionLimits(
|
|
6862
7407
|
hookCtx.strategyConfig,
|
|
@@ -6997,6 +7542,37 @@ var executeEntryDecision = async ({
|
|
|
6997
7542
|
}
|
|
6998
7543
|
return signal ?? decision.code;
|
|
6999
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
|
+
}
|
|
7000
7576
|
|
|
7001
7577
|
// src/strategyRuntime.ts
|
|
7002
7578
|
var cloneWithPropertyDescriptors = (value) => Object.create(
|
|
@@ -7024,6 +7600,7 @@ var createStrategyRuntime = ({
|
|
|
7024
7600
|
};
|
|
7025
7601
|
const creator = async ({
|
|
7026
7602
|
userName,
|
|
7603
|
+
connectorName,
|
|
7027
7604
|
config: baseConfig,
|
|
7028
7605
|
symbol,
|
|
7029
7606
|
universe: requestedUniverse,
|
|
@@ -7062,6 +7639,17 @@ var createStrategyRuntime = ({
|
|
|
7062
7639
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
7063
7640
|
const projectHooks = projectConfig.hooks;
|
|
7064
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;
|
|
7065
7653
|
const researchPolicyValue = baseConfig?.RESEARCH_ENTRY_POLICY ?? config.RESEARCH_ENTRY_POLICY;
|
|
7066
7654
|
const researchEntryPolicy = researchPolicyValue == null ? void 0 : parseResearchEntryPolicy(researchPolicyValue);
|
|
7067
7655
|
if (researchEntryPolicy && (env !== "BACKTEST" || Reflect.get(connector, "__tradejsTestConnector") !== true))
|
|
@@ -7099,6 +7687,9 @@ var createStrategyRuntime = ({
|
|
|
7099
7687
|
});
|
|
7100
7688
|
const strategyPolicyProfile = getPolicyProfile();
|
|
7101
7689
|
const indicatorPeriods = (0, import_strategies6.buildDefaultIndicatorPeriods)(config);
|
|
7690
|
+
const strategyConfig = { ...config };
|
|
7691
|
+
delete strategyConfig.RECORD_RUNTIME_TRADES;
|
|
7692
|
+
delete strategyConfig.SIMULATE_ORDERS;
|
|
7102
7693
|
const hookBase = {
|
|
7103
7694
|
connector,
|
|
7104
7695
|
strategyName,
|
|
@@ -7109,7 +7700,7 @@ var createStrategyRuntime = ({
|
|
|
7109
7700
|
accountId,
|
|
7110
7701
|
deploymentId,
|
|
7111
7702
|
policyProfileId: strategyPolicyProfile?.id ?? requestedPolicyProfileId,
|
|
7112
|
-
strategyConfig
|
|
7703
|
+
strategyConfig,
|
|
7113
7704
|
env,
|
|
7114
7705
|
isConfigFromBacktest
|
|
7115
7706
|
};
|
|
@@ -7177,7 +7768,7 @@ var createStrategyRuntime = ({
|
|
|
7177
7768
|
);
|
|
7178
7769
|
}
|
|
7179
7770
|
};
|
|
7180
|
-
const invokeHook = async (stage, hook, params, errorContext = {}) => {
|
|
7771
|
+
const invokeHook = async (stage, hook, params, errorContext = {}, failureResult) => {
|
|
7181
7772
|
if (!hook) {
|
|
7182
7773
|
return void 0;
|
|
7183
7774
|
}
|
|
@@ -7197,17 +7788,18 @@ var createStrategyRuntime = ({
|
|
|
7197
7788
|
entry: errorContext.entry,
|
|
7198
7789
|
market: errorContext.market
|
|
7199
7790
|
});
|
|
7200
|
-
return
|
|
7791
|
+
return failureResult;
|
|
7201
7792
|
}
|
|
7202
7793
|
};
|
|
7203
|
-
const invokeProjectHooks = async (stage, params, errorContext = {}) => {
|
|
7794
|
+
const invokeProjectHooks = async (stage, params, errorContext = {}, failureResult) => {
|
|
7204
7795
|
const results = [];
|
|
7205
7796
|
for (const hook of getProjectHookList(stage)) {
|
|
7206
7797
|
const result = await invokeHook(
|
|
7207
7798
|
stage,
|
|
7208
7799
|
hook,
|
|
7209
7800
|
params,
|
|
7210
|
-
errorContext
|
|
7801
|
+
errorContext,
|
|
7802
|
+
failureResult
|
|
7211
7803
|
);
|
|
7212
7804
|
if (result !== void 0) {
|
|
7213
7805
|
results.push(result);
|
|
@@ -7222,10 +7814,12 @@ var createStrategyRuntime = ({
|
|
|
7222
7814
|
return invokeHook(stage, hook, params, errorContext);
|
|
7223
7815
|
};
|
|
7224
7816
|
const invokeGateHooks = async (stage, hook, params, errorContext = {}) => {
|
|
7817
|
+
const failureResult = stage === "beforeEntryGate" ? { allow: false, reason: "ERROR" } : void 0;
|
|
7225
7818
|
const projectResults = await invokeProjectHooks(
|
|
7226
7819
|
stage,
|
|
7227
7820
|
params,
|
|
7228
|
-
errorContext
|
|
7821
|
+
errorContext,
|
|
7822
|
+
failureResult
|
|
7229
7823
|
);
|
|
7230
7824
|
const projectBlock = projectResults.find(
|
|
7231
7825
|
(result) => result?.allow === false
|
|
@@ -7237,7 +7831,8 @@ var createStrategyRuntime = ({
|
|
|
7237
7831
|
stage,
|
|
7238
7832
|
hook,
|
|
7239
7833
|
params,
|
|
7240
|
-
errorContext
|
|
7834
|
+
errorContext,
|
|
7835
|
+
failureResult
|
|
7241
7836
|
);
|
|
7242
7837
|
};
|
|
7243
7838
|
const applyProjectAfterCoreDecisionHooks = async ({
|
|
@@ -7353,6 +7948,20 @@ var createStrategyRuntime = ({
|
|
|
7353
7948
|
interval: decisionInterval
|
|
7354
7949
|
}) => {
|
|
7355
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
|
+
}
|
|
7356
7965
|
if (!coreContextRequirements.has("hyperliquidWhales")) {
|
|
7357
7966
|
return baseContext;
|
|
7358
7967
|
}
|
|
@@ -7383,11 +7992,8 @@ var createStrategyRuntime = ({
|
|
|
7383
7992
|
return enrichedBaseContext;
|
|
7384
7993
|
}
|
|
7385
7994
|
});
|
|
7386
|
-
const coreConfig = { ...config };
|
|
7387
|
-
delete coreConfig.RECORD_RUNTIME_TRADES;
|
|
7388
|
-
delete coreConfig.SIMULATE_ORDERS;
|
|
7389
7995
|
const core = await createCore({
|
|
7390
|
-
config:
|
|
7996
|
+
config: strategyConfig,
|
|
7391
7997
|
data,
|
|
7392
7998
|
strategyApi,
|
|
7393
7999
|
indicatorsState
|
|
@@ -7523,6 +8129,8 @@ var createStrategyRuntime = ({
|
|
|
7523
8129
|
runtime: pending2.runtime
|
|
7524
8130
|
});
|
|
7525
8131
|
return executeEntryDecision({
|
|
8132
|
+
portfolio,
|
|
8133
|
+
portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
|
|
7526
8134
|
connector,
|
|
7527
8135
|
symbol,
|
|
7528
8136
|
decision: pending2.decision,
|
|
@@ -7540,6 +8148,10 @@ var createStrategyRuntime = ({
|
|
|
7540
8148
|
});
|
|
7541
8149
|
};
|
|
7542
8150
|
const runWithDecisionOverride = async (candle, btcCandle, options = {}) => {
|
|
8151
|
+
if (portfolioContext)
|
|
8152
|
+
await portfolioContext(
|
|
8153
|
+
env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now()
|
|
8154
|
+
);
|
|
7543
8155
|
appendCurrentMarketData(candle, btcCandle, options.ethCandle);
|
|
7544
8156
|
const ethCandle = resolveEthCandle(candle, options.ethCandle);
|
|
7545
8157
|
indicatorsState.setCurrentBar(candle, btcCandle, ethCandle);
|
|
@@ -7710,6 +8322,13 @@ var createStrategyRuntime = ({
|
|
|
7710
8322
|
});
|
|
7711
8323
|
const signal = decision.signal;
|
|
7712
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
|
+
};
|
|
7713
8332
|
if (runtimeLineage) signal.runtimeLineage = runtimeLineage;
|
|
7714
8333
|
if (universe) signal.universe = universe;
|
|
7715
8334
|
if (assetClass) signal.assetClass = assetClass;
|
|
@@ -7949,6 +8568,8 @@ var createStrategyRuntime = ({
|
|
|
7949
8568
|
});
|
|
7950
8569
|
}
|
|
7951
8570
|
return executeEntryDecision({
|
|
8571
|
+
portfolio,
|
|
8572
|
+
portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
|
|
7952
8573
|
connector,
|
|
7953
8574
|
symbol,
|
|
7954
8575
|
decision,
|