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