@tradejs/node 3.1.29-beta.290 → 3.1.29-beta.292
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai.js +109 -48
- package/dist/ai.mjs +1 -1
- package/dist/backtest.js +2594 -1904
- package/dist/backtest.mjs +3 -2
- package/dist/{chunk-PB2KHBGH.mjs → chunk-EVKQ4DI6.mjs} +461 -33
- package/dist/chunk-NJEYNGRU.mjs +226 -0
- package/dist/{chunk-UL5Z46JL.mjs → chunk-PYJWXAYR.mjs} +97 -30
- package/dist/{chunk-BO42QWAB.mjs → chunk-W6PPAADK.mjs} +1 -1
- package/dist/cli.js +834 -773
- package/dist/cli.mjs +1 -1
- package/dist/diagnostics.js +2611 -1921
- package/dist/diagnostics.mjs +4 -3
- package/dist/mcp.d.mts +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +2634 -1944
- package/dist/mcp.mjs +4 -3
- package/dist/portfolio.d.mts +28 -0
- package/dist/portfolio.d.ts +28 -0
- package/dist/portfolio.js +243 -0
- package/dist/portfolio.mjs +9 -0
- package/dist/registry.js +2614 -1924
- package/dist/registry.mjs +3 -2
- package/dist/runtimeDashboard.js +2335 -1643
- package/dist/runtimeDashboard.mjs +4 -3
- package/dist/runtimeStrategies.js +2615 -1925
- package/dist/runtimeStrategies.mjs +4 -3
- package/dist/strategies.d.mts +3 -1
- package/dist/strategies.d.ts +3 -1
- package/dist/strategies.js +2153 -1463
- package/dist/strategies.mjs +3 -2
- package/package.json +10 -5
package/dist/registry.js
CHANGED
|
@@ -792,1176 +792,815 @@ __export(registry_exports, {
|
|
|
792
792
|
});
|
|
793
793
|
module.exports = __toCommonJS(registry_exports);
|
|
794
794
|
|
|
795
|
-
// src/
|
|
796
|
-
var
|
|
795
|
+
// src/strategy/portfolioRuntime.ts
|
|
796
|
+
var import_node_crypto2 = require("crypto");
|
|
797
|
+
var import_portfolio2 = require("@tradejs/core/portfolio");
|
|
798
|
+
var import_redis2 = require("@tradejs/infra/redis");
|
|
797
799
|
|
|
798
|
-
// src/
|
|
799
|
-
var
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
const questions = questionsForJevInput(input);
|
|
830
|
-
const questionsHash = (0, import_jev5.jevHash)(questions);
|
|
831
|
-
const base = {
|
|
832
|
-
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
833
|
-
schema: "tradejs-signal-assessment/v5",
|
|
834
|
-
source: config.source,
|
|
835
|
-
mode: config.mode,
|
|
836
|
-
inputHash,
|
|
837
|
-
questionsHash,
|
|
838
|
-
geometryStatus: input.geometryStatus,
|
|
839
|
-
levelsValid: typeof input.features["signal.validLevels"] === "boolean" ? input.features["signal.validLevels"] : null
|
|
840
|
-
};
|
|
841
|
-
if (!input.questions.length)
|
|
842
|
-
return {
|
|
843
|
-
assessment: {
|
|
844
|
-
...base,
|
|
845
|
-
status: "unavailable",
|
|
846
|
-
model: "",
|
|
847
|
-
scores: emptyScores(),
|
|
848
|
-
confidence: emptyScores()
|
|
849
|
-
}
|
|
850
|
-
};
|
|
851
|
-
if (config.source === "local") {
|
|
852
|
-
const contents = await import_promises.default.readFile(
|
|
853
|
-
import_node_path.default.resolve(projectRoot, config.modelFile),
|
|
854
|
-
"utf8"
|
|
855
|
-
);
|
|
856
|
-
if ((0, import_jev5.jevFileHash)(contents) !== config.modelSha256)
|
|
857
|
-
throw new Error("Local Jev gate checksum mismatch");
|
|
858
|
-
const { validateJevGateModel: validateJevGateModel2 } = await Promise.resolve().then(() => (init_jevTraining(), jevTraining_exports));
|
|
859
|
-
const model = validateJevGateModel2(JSON.parse(contents));
|
|
860
|
-
if (model.strategy !== input.strategy || model.questionsHash !== (0, import_jev5.jevHash)(JEV_QUESTIONS))
|
|
861
|
-
throw new Error("Local Jev gate strategy or question version mismatch");
|
|
862
|
-
const scores = Object.fromEntries(
|
|
863
|
-
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
864
|
-
key,
|
|
865
|
-
input.questions.includes(key) && model.trees[key] ? (0, import_jev4.predictJevTree)(model.trees[key], input.features) : null
|
|
866
|
-
])
|
|
867
|
-
);
|
|
868
|
-
return {
|
|
869
|
-
assessment: {
|
|
870
|
-
...base,
|
|
871
|
-
status: "available",
|
|
872
|
-
model: config.modelSha256,
|
|
873
|
-
scores,
|
|
874
|
-
confidence: emptyScores()
|
|
800
|
+
// src/portfolio.ts
|
|
801
|
+
var import_portfolio = require("@tradejs/core/portfolio");
|
|
802
|
+
function createPortfolioCoordinator({
|
|
803
|
+
policy,
|
|
804
|
+
strategy,
|
|
805
|
+
store,
|
|
806
|
+
execution
|
|
807
|
+
}) {
|
|
808
|
+
const fingerprint3 = (0, import_portfolio.portfolioFingerprint)(policy, strategy);
|
|
809
|
+
let cache;
|
|
810
|
+
async function transact(now2, change, force = false) {
|
|
811
|
+
for (let attempt = 0; attempt < 16; attempt++) {
|
|
812
|
+
const previous = await store.read();
|
|
813
|
+
if (previous && previous.fingerprint !== fingerprint3)
|
|
814
|
+
throw new Error(
|
|
815
|
+
"Portfolio policy changed under existing state; reconcile the persisted scope before changing configuration"
|
|
816
|
+
);
|
|
817
|
+
const base = previous ?? (0, import_portfolio.createPortfolioState)(fingerprint3);
|
|
818
|
+
const bucket = Math.floor(now2 / policy.observationIntervalMs);
|
|
819
|
+
const observation = !force && cache?.revision === base.revision && cache.bucket === bucket ? cache.observation : await execution.observe(now2, base.reservations);
|
|
820
|
+
const reconciled = (0, import_portfolio.reconcilePortfolio)(
|
|
821
|
+
policy,
|
|
822
|
+
strategy,
|
|
823
|
+
base,
|
|
824
|
+
observation
|
|
825
|
+
);
|
|
826
|
+
for (const intent of base.closing) {
|
|
827
|
+
if (!observation.positions.some(
|
|
828
|
+
(p) => p.symbol === intent.position.symbol
|
|
829
|
+
))
|
|
830
|
+
await execution.confirmClosed?.(intent, observation.asOf);
|
|
875
831
|
}
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
const file = import_node_path.default.join(dir, "records", `${id}.json`);
|
|
882
|
-
const getRecord = async () => {
|
|
883
|
-
const existing = await (0, import_jev5.readJevArtifact)(file);
|
|
884
|
-
if (existing) {
|
|
885
|
-
if (existing.schema !== "tradejs-jev-record/v4" || existing.id !== id || existing.inputHash !== inputHash || existing.questionsHash !== questionsHash || (0, import_jev5.jevHash)(existing.input) !== inputHash || (0, import_jev5.jevHash)(existing.provider) !== (0, import_jev5.jevHash)(provider))
|
|
886
|
-
throw new Error("Jev recording identity mismatch");
|
|
887
|
-
(0, import_jev5.validateJevResponse)(existing.response, input.questions);
|
|
888
|
-
for (const key of import_jev4.JEV_DIMENSIONS) {
|
|
889
|
-
const expected = input.questions.includes(key) ? existing.response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null;
|
|
890
|
-
if (existing.scores[key] !== expected)
|
|
891
|
-
throw new Error("Jev recording scores do not match the response");
|
|
832
|
+
const changed = change(reconciled, observation);
|
|
833
|
+
const next = { ...changed.state, revision: base.revision + 1 };
|
|
834
|
+
if (await store.compare(previous, next)) {
|
|
835
|
+
cache = { revision: next.revision, bucket, observation };
|
|
836
|
+
return { state: next, observation, result: changed.result };
|
|
892
837
|
}
|
|
893
|
-
|
|
838
|
+
cache = void 0;
|
|
894
839
|
}
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
840
|
+
throw new Error("Portfolio state contention; entry was not submitted");
|
|
841
|
+
}
|
|
842
|
+
async function tick(now2, force = false) {
|
|
843
|
+
const claimed = await transact(
|
|
844
|
+
now2,
|
|
845
|
+
(state, observation) => {
|
|
846
|
+
const commands = (0, import_portfolio.portfolioCloseCommands)(policy, state, observation);
|
|
847
|
+
return {
|
|
848
|
+
state: {
|
|
849
|
+
...state,
|
|
850
|
+
closing: state.closing.map(
|
|
851
|
+
(c) => commands.some((x) => x.intent.positionId === c.positionId) ? { ...c, lastAttemptAt: observation.asOf } : c
|
|
852
|
+
)
|
|
853
|
+
},
|
|
854
|
+
result: commands
|
|
855
|
+
};
|
|
856
|
+
},
|
|
857
|
+
force
|
|
858
|
+
);
|
|
859
|
+
for (const command of claimed.result)
|
|
860
|
+
await execution.close(
|
|
861
|
+
command.position,
|
|
862
|
+
command.intent,
|
|
863
|
+
claimed.observation.asOf
|
|
898
864
|
);
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
865
|
+
if (claimed.result.length) {
|
|
866
|
+
cache = void 0;
|
|
867
|
+
const refreshed = await transact(
|
|
868
|
+
now2,
|
|
869
|
+
(state) => ({ state, result: void 0 }),
|
|
870
|
+
true
|
|
871
|
+
);
|
|
872
|
+
return (0, import_portfolio.buildPortfolioContext)(
|
|
873
|
+
policy,
|
|
874
|
+
strategy,
|
|
875
|
+
refreshed.state,
|
|
876
|
+
refreshed.observation
|
|
907
877
|
);
|
|
908
|
-
const started = Date.now();
|
|
909
|
-
const state = stateForJevInput(input);
|
|
910
|
-
const responseId = (0, import_jev5.jevHash)({
|
|
911
|
-
state,
|
|
912
|
-
questionsHash,
|
|
913
|
-
provider,
|
|
914
|
-
user: (0, import_jev5.jevHash)(userName)
|
|
915
|
-
});
|
|
916
|
-
const responseFile = import_node_path.default.join(dir, "responses", `${responseId}.json`);
|
|
917
|
-
let responsePromise = pendingResponses.get(responseFile);
|
|
918
|
-
if (!responsePromise) {
|
|
919
|
-
responsePromise = (async () => {
|
|
920
|
-
const saved = await (0, import_jev5.readJevArtifact)(responseFile);
|
|
921
|
-
if (saved) return (0, import_jev5.validateJevResponse)(saved, input.questions);
|
|
922
|
-
const fresh = await (0, import_jev5.requestJev)(provider, apiKey, state, questions);
|
|
923
|
-
await (0, import_jev5.writeJevArtifact)(responseFile, fresh);
|
|
924
|
-
return fresh;
|
|
925
|
-
})();
|
|
926
|
-
pendingResponses.set(responseFile, responsePromise);
|
|
927
|
-
}
|
|
928
|
-
let response;
|
|
929
|
-
try {
|
|
930
|
-
response = await responsePromise;
|
|
931
|
-
} finally {
|
|
932
|
-
pendingResponses.delete(responseFile);
|
|
933
878
|
}
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
879
|
+
return (0, import_portfolio.buildPortfolioContext)(
|
|
880
|
+
policy,
|
|
881
|
+
strategy,
|
|
882
|
+
claimed.state,
|
|
883
|
+
claimed.observation
|
|
939
884
|
);
|
|
940
|
-
const value = {
|
|
941
|
-
schema: "tradejs-jev-record/v4",
|
|
942
|
-
id,
|
|
943
|
-
inputHash,
|
|
944
|
-
questionsHash,
|
|
945
|
-
provider,
|
|
946
|
-
input,
|
|
947
|
-
response,
|
|
948
|
-
scores,
|
|
949
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
950
|
-
elapsedMs: Date.now() - started
|
|
951
|
-
};
|
|
952
|
-
await (0, import_jev5.writeJevArtifact)(file, value);
|
|
953
|
-
return await (0, import_jev5.readJevArtifact)(file);
|
|
954
|
-
};
|
|
955
|
-
const pendingKey = `${file}:${(0, import_jev5.jevHash)(userName)}`;
|
|
956
|
-
let promise = pending.get(pendingKey);
|
|
957
|
-
if (!promise) {
|
|
958
|
-
promise = getRecord();
|
|
959
|
-
pending.set(pendingKey, promise);
|
|
960
885
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
886
|
+
async function updateReservation(now2, id, submitted, qty) {
|
|
887
|
+
return transact(
|
|
888
|
+
now2,
|
|
889
|
+
(s, o) => {
|
|
890
|
+
const exists = s.reservations.some((r) => r.id === id);
|
|
891
|
+
const blocked = submitted && (!exists || s.closing.length > 0 || o.asOf <= s.blockedThrough);
|
|
892
|
+
return {
|
|
893
|
+
state: {
|
|
894
|
+
...s,
|
|
895
|
+
reservations: s.reservations.flatMap(
|
|
896
|
+
(r) => r.id !== id ? [r] : submitted && !blocked ? [{ ...r, submitted: true, qty: qty ?? r.qty }] : []
|
|
897
|
+
)
|
|
898
|
+
},
|
|
899
|
+
result: !blocked
|
|
900
|
+
};
|
|
901
|
+
},
|
|
902
|
+
true
|
|
903
|
+
);
|
|
966
904
|
}
|
|
967
905
|
return {
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
906
|
+
async context(now2) {
|
|
907
|
+
const state = await store.read() ?? (0, import_portfolio.createPortfolioState)(fingerprint3);
|
|
908
|
+
if (state.fingerprint !== fingerprint3)
|
|
909
|
+
throw new Error("Portfolio policy changed under existing state");
|
|
910
|
+
const observation = await execution.observe(now2, state.reservations);
|
|
911
|
+
(0, import_portfolio.validatePortfolioObservation)(observation);
|
|
912
|
+
return (0, import_portfolio.buildPortfolioContext)(policy, strategy, state, observation);
|
|
913
|
+
},
|
|
914
|
+
tick,
|
|
915
|
+
async submit(now2, entry, send) {
|
|
916
|
+
await tick(now2);
|
|
917
|
+
const admitted = await transact(
|
|
918
|
+
now2,
|
|
919
|
+
(s, o) => {
|
|
920
|
+
const r = (0, import_portfolio.reservePortfolioEntry)(policy, strategy, s, o, {
|
|
921
|
+
...entry,
|
|
922
|
+
submitted: false,
|
|
923
|
+
oppositeHandled: false
|
|
924
|
+
});
|
|
925
|
+
return { state: r.state, result: r.reason };
|
|
926
|
+
},
|
|
927
|
+
true
|
|
928
|
+
);
|
|
929
|
+
if (admitted.result) return { reason: admitted.result };
|
|
930
|
+
let submitted = false;
|
|
931
|
+
let retry = 0;
|
|
932
|
+
let attemptId = entry.id;
|
|
933
|
+
let needsReservation = 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
|
+
if (needsReservation) {
|
|
940
|
+
attemptId = `${entry.id.slice(0, 30)}r${++retry}`;
|
|
941
|
+
const admission = await transact(
|
|
942
|
+
now2,
|
|
943
|
+
(s, o) => {
|
|
944
|
+
const r = (0, import_portfolio.reservePortfolioEntry)(policy, strategy, s, o, {
|
|
945
|
+
...entry,
|
|
946
|
+
id: attemptId,
|
|
947
|
+
qty,
|
|
948
|
+
submitted: true,
|
|
949
|
+
oppositeHandled: false
|
|
950
|
+
});
|
|
951
|
+
return { state: r.state, result: r.reason };
|
|
952
|
+
},
|
|
953
|
+
true
|
|
954
|
+
);
|
|
955
|
+
if (admission.result) throw new Error(admission.result);
|
|
956
|
+
needsReservation = false;
|
|
957
|
+
submitted = true;
|
|
958
|
+
return attemptId;
|
|
959
|
+
}
|
|
960
|
+
const update = await updateReservation(now2, attemptId, true, qty);
|
|
961
|
+
if (!update.result)
|
|
962
|
+
throw new Error("PORTFOLIO_SUBMISSION_NO_LONGER_ADMITTED");
|
|
963
|
+
submitted = true;
|
|
964
|
+
return attemptId;
|
|
965
|
+
},
|
|
966
|
+
async (retrying = false) => {
|
|
967
|
+
await transact(
|
|
968
|
+
now2,
|
|
969
|
+
(s) => ({
|
|
970
|
+
state: {
|
|
971
|
+
...s,
|
|
972
|
+
reservations: s.reservations.filter(
|
|
973
|
+
(r) => r.id !== attemptId
|
|
974
|
+
),
|
|
975
|
+
completedSignals: {
|
|
976
|
+
...Object.fromEntries(
|
|
977
|
+
Object.entries(s.completedSignals).filter(
|
|
978
|
+
([id]) => !retrying || id !== entry.signalId
|
|
979
|
+
)
|
|
980
|
+
),
|
|
981
|
+
...retrying ? {} : { [entry.signalId]: now2 }
|
|
982
|
+
}
|
|
983
|
+
},
|
|
984
|
+
result: void 0
|
|
985
|
+
}),
|
|
986
|
+
true
|
|
987
|
+
);
|
|
988
|
+
submitted = false;
|
|
989
|
+
needsReservation = retrying;
|
|
990
|
+
}
|
|
991
|
+
);
|
|
992
|
+
if (!submitted) await updateReservation(now2, attemptId, false);
|
|
993
|
+
else await tick(now2, true);
|
|
994
|
+
return { value };
|
|
995
|
+
} catch (error) {
|
|
996
|
+
if (!submitted) await updateReservation(now2, attemptId, false);
|
|
997
|
+
throw error;
|
|
998
|
+
}
|
|
981
999
|
}
|
|
982
1000
|
};
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
try {
|
|
994
|
-
const input = buildJevInput(signal);
|
|
995
|
-
({ assessment } = await evaluateJevInput({
|
|
996
|
-
input,
|
|
997
|
-
config,
|
|
998
|
-
userName,
|
|
999
|
-
projectRoot
|
|
1000
|
-
}));
|
|
1001
|
-
} catch (error) {
|
|
1002
|
-
if (strict) throw error;
|
|
1003
|
-
assessment = {
|
|
1004
|
-
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
1005
|
-
schema: "tradejs-signal-assessment/v5",
|
|
1006
|
-
source: config.source,
|
|
1007
|
-
mode: config.mode,
|
|
1008
|
-
status: "unavailable",
|
|
1009
|
-
inputHash: "",
|
|
1010
|
-
questionsHash: "",
|
|
1011
|
-
geometryStatus: "absent",
|
|
1012
|
-
levelsValid: null,
|
|
1013
|
-
model: config.provider?.model ?? config.modelSha256 ?? "",
|
|
1014
|
-
scores: emptyScores(),
|
|
1015
|
-
confidence: emptyScores()
|
|
1016
|
-
};
|
|
1017
|
-
}
|
|
1018
|
-
signal.assessment = assessment;
|
|
1019
|
-
signal.additionalIndicators = {
|
|
1020
|
-
...signal.additionalIndicators,
|
|
1021
|
-
jev: (0, import_jev4.jevAssessmentFeatures)(assessment)
|
|
1001
|
+
}
|
|
1002
|
+
function createMemoryPortfolioStore() {
|
|
1003
|
+
let state = null;
|
|
1004
|
+
return {
|
|
1005
|
+
read: async () => state === null ? null : structuredClone(state),
|
|
1006
|
+
compare: async (expected, next) => {
|
|
1007
|
+
if ((state?.revision ?? -1) !== (expected?.revision ?? -1)) return false;
|
|
1008
|
+
state = structuredClone(next);
|
|
1009
|
+
return true;
|
|
1010
|
+
}
|
|
1022
1011
|
};
|
|
1023
|
-
|
|
1024
|
-
signalId: signal.signalId,
|
|
1025
|
-
strategy: signal.strategy,
|
|
1026
|
-
symbol: signal.symbol,
|
|
1027
|
-
timestamp: signal.timestamp,
|
|
1028
|
-
scope,
|
|
1029
|
-
user: (0, import_jev5.jevHash)(userName),
|
|
1030
|
-
config,
|
|
1031
|
-
assessment
|
|
1032
|
-
};
|
|
1033
|
-
await (0, import_jev5.writeJevArtifact)(
|
|
1034
|
-
import_node_path.default.resolve(
|
|
1035
|
-
projectRoot,
|
|
1036
|
-
config.recordsDir ?? "data/ai/jev",
|
|
1037
|
-
"decisions",
|
|
1038
|
-
`${(0, import_jev5.jevHash)(identity)}.json`
|
|
1039
|
-
),
|
|
1040
|
-
identity
|
|
1041
|
-
);
|
|
1042
|
-
return assessment;
|
|
1043
|
-
};
|
|
1044
|
-
|
|
1045
|
-
// src/strategyRuntime.ts
|
|
1046
|
-
var import_constants5 = require("@tradejs/core/constants");
|
|
1047
|
-
var import_strategies6 = require("@tradejs/core/strategies");
|
|
1048
|
-
var import_logger11 = require("@tradejs/infra/logger");
|
|
1049
|
-
|
|
1050
|
-
// src/strategyHelpers/runtime.ts
|
|
1051
|
-
var import_logger9 = require("@tradejs/infra/logger");
|
|
1052
|
-
var import_constants3 = require("@tradejs/core/constants");
|
|
1053
|
-
var import_ml2 = require("@tradejs/infra/ml");
|
|
1012
|
+
}
|
|
1054
1013
|
|
|
1055
|
-
// src/
|
|
1014
|
+
// src/runtimeJournal.ts
|
|
1015
|
+
var import_node_crypto = require("crypto");
|
|
1016
|
+
var import_constants = require("@tradejs/core/constants");
|
|
1017
|
+
var import_time = require("@tradejs/core/time");
|
|
1018
|
+
var import_trade = require("@tradejs/core/trade");
|
|
1056
1019
|
var import_logger = require("@tradejs/infra/logger");
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
var
|
|
1060
|
-
|
|
1061
|
-
super(...arguments);
|
|
1062
|
-
this.name = "EntrySubmissionValidationError";
|
|
1063
|
-
}
|
|
1064
|
-
};
|
|
1065
|
-
var resolveEntryExecutionLimits = (config, direction) => {
|
|
1066
|
-
const side = config[direction];
|
|
1067
|
-
const minimum = side && typeof side === "object" && "minRiskRatio" in side ? side.minRiskRatio : void 0;
|
|
1068
|
-
const maximum = config.MAX_LOSS_VALUE;
|
|
1069
|
-
if (minimum == null && maximum == null) return void 0;
|
|
1070
|
-
const minimumNetRiskRatio = minimum == null ? void 0 : Number(minimum);
|
|
1071
|
-
const maxLossValue = maximum == null ? void 0 : Number(maximum);
|
|
1072
|
-
const feeRate = Number(config.RISK_FEE_RATE ?? 0);
|
|
1073
|
-
const slippage = Number(config.RISK_SLIPPAGE_BPS ?? 0);
|
|
1074
|
-
const impact = Number(config.RISK_MARKET_IMPACT_BPS ?? 0);
|
|
1075
|
-
const slippageBps = slippage + impact;
|
|
1076
|
-
if (minimumNetRiskRatio != null && (!Number.isFinite(minimumNetRiskRatio) || minimumNetRiskRatio < 0) || maxLossValue != null && (!Number.isFinite(maxLossValue) || maxLossValue <= 0) || !Number.isFinite(feeRate) || feeRate < 0 || feeRate >= 1 || !Number.isFinite(slippage) || slippage < 0 || !Number.isFinite(impact) || impact < 0 || !Number.isFinite(slippageBps) || slippageBps < 0 || slippageBps >= 1e4)
|
|
1077
|
-
throw new Error("INVALID_ENTRY_EXECUTION_LIMITS");
|
|
1078
|
-
return { minimumNetRiskRatio, maxLossValue, feeRate, slippageBps };
|
|
1079
|
-
};
|
|
1080
|
-
var validateEntryExecution = ({
|
|
1020
|
+
var import_redis = require("@tradejs/infra/redis");
|
|
1021
|
+
var now = () => Date.now();
|
|
1022
|
+
var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
|
|
1023
|
+
var calculateClosedPnl = ({
|
|
1081
1024
|
direction,
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
takeProfitPrice,
|
|
1086
|
-
limits
|
|
1025
|
+
entryPrice,
|
|
1026
|
+
exitPrice,
|
|
1027
|
+
qty
|
|
1087
1028
|
}) => {
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
1091
|
-
);
|
|
1092
|
-
const side = direction === "LONG" ? 1 : -1;
|
|
1093
|
-
if (side * (price - stopLossPrice) <= 0 || side * (takeProfitPrice - price) <= 0)
|
|
1094
|
-
throw new EntrySubmissionValidationError(
|
|
1095
|
-
"INVALID_ENTRY_PROTECTION_AT_SUBMISSION"
|
|
1096
|
-
);
|
|
1097
|
-
const slippage = limits.slippageBps / 1e4;
|
|
1098
|
-
const cost = (value, sign) => value * slippage + value * (1 + sign * slippage) * limits.feeRate;
|
|
1099
|
-
const entryCost = cost(price, side);
|
|
1100
|
-
const lossPerUnit = Math.abs(price - stopLossPrice) + entryCost + cost(stopLossPrice, -side);
|
|
1101
|
-
const rewardPerUnit = Math.max(
|
|
1102
|
-
0,
|
|
1103
|
-
Math.abs(takeProfitPrice - price) - entryCost - cost(takeProfitPrice, -side)
|
|
1104
|
-
);
|
|
1105
|
-
const netRiskRatio = rewardPerUnit / lossPerUnit;
|
|
1106
|
-
const plannedLossValue = qty * lossPerUnit;
|
|
1107
|
-
if (!Number.isFinite(netRiskRatio) || !Number.isFinite(plannedLossValue))
|
|
1108
|
-
throw new EntrySubmissionValidationError(
|
|
1109
|
-
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
1110
|
-
);
|
|
1111
|
-
if (limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio)
|
|
1112
|
-
throw new EntrySubmissionValidationError(
|
|
1113
|
-
"ENTRY_RR_BELOW_MIN_AT_SUBMISSION"
|
|
1114
|
-
);
|
|
1115
|
-
const riskExceeded = limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9);
|
|
1116
|
-
return { netRiskRatio, plannedLossValue, riskExceeded };
|
|
1029
|
+
const pnl = direction === "LONG" ? (exitPrice - entryPrice) * qty : (entryPrice - exitPrice) * qty;
|
|
1030
|
+
return Number.isFinite(pnl) ? pnl : null;
|
|
1117
1031
|
};
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
}
|
|
1127
|
-
if (
|
|
1128
|
-
return
|
|
1129
|
-
arrivalSnapshotTime: Date.now(),
|
|
1130
|
-
arrivalSource: "unavailable",
|
|
1131
|
-
bid: null,
|
|
1132
|
-
ask: null,
|
|
1133
|
-
arrivalMid: null,
|
|
1134
|
-
spreadBps: null
|
|
1135
|
-
};
|
|
1032
|
+
var createRuntimeOrderId = (strategy) => {
|
|
1033
|
+
const prefix = (0, import_trade.createRuntimeOrderLinkPrefix)(strategy);
|
|
1034
|
+
if (prefix === "tjs-") {
|
|
1035
|
+
return `tjs-${(0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 24).toLowerCase()}`;
|
|
1036
|
+
}
|
|
1037
|
+
return `${prefix}${toRandomOrderSuffix()}`;
|
|
1038
|
+
};
|
|
1039
|
+
var recordRuntimeTradeOpen = async (params) => {
|
|
1040
|
+
const { userName } = params;
|
|
1041
|
+
if (!userName) {
|
|
1042
|
+
return null;
|
|
1136
1043
|
}
|
|
1044
|
+
const record = {
|
|
1045
|
+
...params,
|
|
1046
|
+
entryCount: 1,
|
|
1047
|
+
lastEntryPrice: params.entryPrice,
|
|
1048
|
+
lastEntryQty: params.qty,
|
|
1049
|
+
lastEntryTimestamp: params.entryTimestamp,
|
|
1050
|
+
status: "active",
|
|
1051
|
+
currentPrice: params.entryPrice,
|
|
1052
|
+
currentPnl: 0,
|
|
1053
|
+
closedPnl: null,
|
|
1054
|
+
exitPrice: null,
|
|
1055
|
+
exitTimestamp: null,
|
|
1056
|
+
lastSyncedAt: now()
|
|
1057
|
+
};
|
|
1058
|
+
const dayKey = (0, import_time.getRuntimeStorageDayKey)(record.entryTimestamp);
|
|
1059
|
+
const runtimeScopeId = record.deploymentId ?? record.accountId;
|
|
1137
1060
|
try {
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1061
|
+
await Promise.all([
|
|
1062
|
+
(0, import_redis.setData)(import_redis.redisKeys.runtimeTrade(userName, record.orderId), record, {
|
|
1063
|
+
expire: 0
|
|
1064
|
+
}),
|
|
1065
|
+
(0, import_redis.setHashJsonField)(
|
|
1066
|
+
import_redis.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
1067
|
+
record.orderId,
|
|
1068
|
+
record,
|
|
1069
|
+
{ expire: 0 }
|
|
1070
|
+
),
|
|
1071
|
+
(0, import_redis.setData)(
|
|
1072
|
+
import_redis.redisKeys.runtimeActiveTrade(userName, record.symbol, runtimeScopeId),
|
|
1073
|
+
{ orderId: record.orderId },
|
|
1074
|
+
{ expire: 0 }
|
|
1075
|
+
)
|
|
1076
|
+
]);
|
|
1153
1077
|
} catch (error) {
|
|
1154
|
-
import_logger.logger.
|
|
1155
|
-
"runtime
|
|
1156
|
-
symbol,
|
|
1078
|
+
import_logger.logger.error(
|
|
1079
|
+
"runtime trade open journal failed: %s %s",
|
|
1080
|
+
record.symbol,
|
|
1157
1081
|
error?.message || String(error)
|
|
1158
1082
|
);
|
|
1159
|
-
return {
|
|
1160
|
-
arrivalSnapshotTime: Date.now(),
|
|
1161
|
-
arrivalSource: "top_of_book_error",
|
|
1162
|
-
bid: null,
|
|
1163
|
-
ask: null,
|
|
1164
|
-
arrivalMid: null,
|
|
1165
|
-
spreadBps: null
|
|
1166
|
-
};
|
|
1167
1083
|
}
|
|
1084
|
+
return record;
|
|
1168
1085
|
};
|
|
1169
|
-
var
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
}
|
|
1086
|
+
var recordRuntimeTradeIncrease = async (params) => {
|
|
1087
|
+
const {
|
|
1088
|
+
userName,
|
|
1089
|
+
strategy,
|
|
1090
|
+
symbol,
|
|
1091
|
+
direction,
|
|
1092
|
+
resultingQty,
|
|
1093
|
+
resultingEntryPrice,
|
|
1094
|
+
addedQty,
|
|
1095
|
+
addedEntryPrice,
|
|
1096
|
+
entryTimestamp,
|
|
1097
|
+
fee,
|
|
1098
|
+
accountId,
|
|
1099
|
+
deploymentId
|
|
1100
|
+
} = params;
|
|
1101
|
+
if (!userName) {
|
|
1102
|
+
return null;
|
|
1187
1103
|
}
|
|
1188
|
-
|
|
1189
|
-
|
|
1104
|
+
const existing = await getActiveRuntimeTrade({
|
|
1105
|
+
userName,
|
|
1106
|
+
symbol,
|
|
1107
|
+
accountId,
|
|
1108
|
+
deploymentId
|
|
1109
|
+
});
|
|
1110
|
+
if (!existing || existing.strategy !== strategy || existing.direction !== direction) {
|
|
1111
|
+
return null;
|
|
1190
1112
|
}
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
arrivalSnapshotTime: Date.now(),
|
|
1210
|
-
arrivalSource: "simulation",
|
|
1211
|
-
bid: executionPrice,
|
|
1212
|
-
ask: executionPrice,
|
|
1213
|
-
arrivalMid: executionPrice,
|
|
1214
|
-
spreadBps: 0
|
|
1215
|
-
} : await getOrderArrivalSnapshot({
|
|
1216
|
-
connector,
|
|
1217
|
-
symbol,
|
|
1218
|
-
requireTimestamp: true
|
|
1219
|
-
});
|
|
1220
|
-
const age = Date.now() - snapshot2.arrivalSnapshotTime;
|
|
1221
|
-
if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
|
|
1222
|
-
throw new EntrySubmissionValidationError(
|
|
1223
|
-
"ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
|
|
1224
|
-
);
|
|
1225
|
-
if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
|
|
1226
|
-
throw new EntrySubmissionValidationError(
|
|
1227
|
-
"ENTRY_QUOTE_STALE_AT_SUBMISSION"
|
|
1228
|
-
);
|
|
1229
|
-
const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
|
|
1230
|
-
validateEntryProtectionAtArrival({
|
|
1231
|
-
direction,
|
|
1232
|
-
signalPrice: price,
|
|
1233
|
-
bid: snapshot2.bid,
|
|
1234
|
-
ask: snapshot2.ask,
|
|
1235
|
-
arrivalMid: snapshot2.arrivalMid,
|
|
1236
|
-
takeProfits,
|
|
1237
|
-
stopLossPrice
|
|
1238
|
-
});
|
|
1239
|
-
const prices = takeProfits.map((tp) => tp.price);
|
|
1240
|
-
const economics2 = validateEntryExecution({
|
|
1241
|
-
direction,
|
|
1242
|
-
price,
|
|
1243
|
-
qty,
|
|
1244
|
-
stopLossPrice,
|
|
1245
|
-
takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
|
|
1246
|
-
limits
|
|
1247
|
-
});
|
|
1248
|
-
onValidated?.(snapshot2, economics2);
|
|
1113
|
+
const addedFee = typeof fee === "number" && Number.isFinite(fee) ? fee : 0;
|
|
1114
|
+
const openFee = (existing.openFee ?? existing.fee ?? 0) + addedFee;
|
|
1115
|
+
const totalFee = (existing.totalFee ?? existing.fee ?? 0) + addedFee;
|
|
1116
|
+
const next = {
|
|
1117
|
+
...existing,
|
|
1118
|
+
...params.orderLeverage ? { orderLeverage: params.orderLeverage } : {},
|
|
1119
|
+
qty: resultingQty,
|
|
1120
|
+
entryPrice: resultingEntryPrice,
|
|
1121
|
+
entryCount: Math.max(1, existing.entryCount ?? 1) + 1,
|
|
1122
|
+
lastEntryPrice: addedEntryPrice,
|
|
1123
|
+
lastEntryQty: addedQty,
|
|
1124
|
+
lastEntryTimestamp: entryTimestamp,
|
|
1125
|
+
currentPrice: resultingEntryPrice,
|
|
1126
|
+
currentPnl: 0,
|
|
1127
|
+
fee: openFee,
|
|
1128
|
+
openFee,
|
|
1129
|
+
totalFee,
|
|
1130
|
+
lastSyncedAt: now()
|
|
1249
1131
|
};
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
}
|
|
1269
|
-
return trimmed.map(
|
|
1270
|
-
(item) => item && typeof item === "object" ? trimSeriesDeep(item) : item
|
|
1271
|
-
);
|
|
1272
|
-
}
|
|
1273
|
-
if (value && typeof value === "object") {
|
|
1274
|
-
return Object.fromEntries(
|
|
1275
|
-
Object.entries(value).map(([key, nested]) => [
|
|
1276
|
-
key,
|
|
1277
|
-
trimSeriesDeep(nested)
|
|
1278
|
-
])
|
|
1132
|
+
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
1133
|
+
try {
|
|
1134
|
+
await Promise.all([
|
|
1135
|
+
(0, import_redis.setData)(import_redis.redisKeys.runtimeTrade(userName, existing.orderId), next, {
|
|
1136
|
+
expire: 0
|
|
1137
|
+
}),
|
|
1138
|
+
(0, import_redis.setHashJsonField)(
|
|
1139
|
+
import_redis.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
1140
|
+
existing.orderId,
|
|
1141
|
+
next,
|
|
1142
|
+
{ expire: 0 }
|
|
1143
|
+
)
|
|
1144
|
+
]);
|
|
1145
|
+
} catch (error) {
|
|
1146
|
+
import_logger.logger.error(
|
|
1147
|
+
"runtime trade increase journal failed: %s %s",
|
|
1148
|
+
symbol,
|
|
1149
|
+
error?.message || String(error)
|
|
1279
1150
|
);
|
|
1280
1151
|
}
|
|
1281
|
-
return
|
|
1152
|
+
return next;
|
|
1282
1153
|
};
|
|
1283
|
-
var
|
|
1284
|
-
const
|
|
1285
|
-
if (
|
|
1286
|
-
return
|
|
1154
|
+
var getActiveRuntimeTrade = async (params) => {
|
|
1155
|
+
const { userName, symbol, accountId, deploymentId } = params;
|
|
1156
|
+
if (!userName) {
|
|
1157
|
+
return null;
|
|
1287
1158
|
}
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
if (!
|
|
1159
|
+
const activeRef = await (0, import_redis.getData)(
|
|
1160
|
+
import_redis.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId),
|
|
1161
|
+
null
|
|
1162
|
+
);
|
|
1163
|
+
const orderId = String(activeRef?.orderId || "").trim();
|
|
1164
|
+
if (!orderId) {
|
|
1294
1165
|
return null;
|
|
1295
1166
|
}
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
};
|
|
1306
|
-
var buildMissingSpreadContext = () => ({
|
|
1307
|
-
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1308
|
-
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1309
|
-
available: false,
|
|
1310
|
-
value: null,
|
|
1311
|
-
zScore: null,
|
|
1312
|
-
bps: null,
|
|
1313
|
-
absBps: null,
|
|
1314
|
-
bias: null,
|
|
1315
|
-
severity: null
|
|
1316
|
-
});
|
|
1317
|
-
var buildSpreadContextFromSignal = (signal) => {
|
|
1318
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1319
|
-
const relative = toRecord(baseContext?.relative);
|
|
1320
|
-
const execution = toRecord(relative?.execution);
|
|
1321
|
-
const spread = toFiniteNumber(execution?.venueSpread);
|
|
1322
|
-
const zScore = toFiniteNumber(execution?.venueSpreadZScore);
|
|
1323
|
-
if (spread == null) {
|
|
1324
|
-
return buildMissingSpreadContext();
|
|
1167
|
+
const existing = await (0, import_redis.getData)(
|
|
1168
|
+
import_redis.redisKeys.runtimeTrade(userName, orderId),
|
|
1169
|
+
null
|
|
1170
|
+
);
|
|
1171
|
+
if (!existing || existing.status !== "active") {
|
|
1172
|
+
await (0, import_redis.delKey)(
|
|
1173
|
+
import_redis.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
|
|
1174
|
+
);
|
|
1175
|
+
return null;
|
|
1325
1176
|
}
|
|
1326
|
-
|
|
1327
|
-
const bps = roundTo(value * 1e4, 2);
|
|
1328
|
-
const absBps = Math.abs(bps);
|
|
1329
|
-
const bias = absBps < 1 ? "flat" : bps > 0 ? "coinbase_premium" : "binance_premium";
|
|
1330
|
-
const severity = absBps >= 20 ? "wide" : absBps >= 5 ? "elevated" : "normal";
|
|
1331
|
-
return {
|
|
1332
|
-
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1333
|
-
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
1334
|
-
available: true,
|
|
1335
|
-
value,
|
|
1336
|
-
zScore,
|
|
1337
|
-
bps,
|
|
1338
|
-
absBps,
|
|
1339
|
-
bias,
|
|
1340
|
-
severity
|
|
1341
|
-
};
|
|
1177
|
+
return existing;
|
|
1342
1178
|
};
|
|
1343
|
-
var
|
|
1344
|
-
const
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
signedVolumeZScore: null
|
|
1359
|
-
};
|
|
1179
|
+
var markRuntimeTradeClosed = async (params) => {
|
|
1180
|
+
const {
|
|
1181
|
+
userName,
|
|
1182
|
+
expectedOrderId,
|
|
1183
|
+
symbol,
|
|
1184
|
+
strategy,
|
|
1185
|
+
exitPrice,
|
|
1186
|
+
exitTimestamp,
|
|
1187
|
+
closedPnl,
|
|
1188
|
+
exitType,
|
|
1189
|
+
accountId,
|
|
1190
|
+
deploymentId
|
|
1191
|
+
} = params;
|
|
1192
|
+
if (!userName) {
|
|
1193
|
+
return null;
|
|
1360
1194
|
}
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
};
|
|
1372
|
-
var buildTradeFlowContextFromSignal = (signal) => {
|
|
1373
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1374
|
-
const participation = toRecord(baseContext?.participation);
|
|
1375
|
-
const tradeFlow = toRecord(participation?.tradeFlow);
|
|
1376
|
-
if (!tradeFlow) {
|
|
1377
|
-
return {
|
|
1378
|
-
source: null,
|
|
1379
|
-
available: false,
|
|
1380
|
-
interval: null,
|
|
1381
|
-
stale: null,
|
|
1382
|
-
trades: null,
|
|
1383
|
-
buyPressurePct: null,
|
|
1384
|
-
netBaseDelta: null,
|
|
1385
|
-
netQuoteDelta: null
|
|
1386
|
-
};
|
|
1195
|
+
const existing = expectedOrderId ? await (0, import_redis.getDataStrict)(
|
|
1196
|
+
import_redis.redisKeys.runtimeTrade(userName, expectedOrderId)
|
|
1197
|
+
) : await getActiveRuntimeTrade({
|
|
1198
|
+
userName,
|
|
1199
|
+
symbol,
|
|
1200
|
+
accountId,
|
|
1201
|
+
deploymentId
|
|
1202
|
+
});
|
|
1203
|
+
if (!existing || expectedOrderId && existing.orderId !== expectedOrderId) {
|
|
1204
|
+
return null;
|
|
1387
1205
|
}
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
interval: String(tradeFlow.interval ?? ""),
|
|
1392
|
-
stale: typeof tradeFlow.stale === "boolean" ? tradeFlow.stale : null,
|
|
1393
|
-
trades: toFiniteNumber(tradeFlow.trades),
|
|
1394
|
-
buyPressurePct: toFiniteNumber(tradeFlow.buyPressurePct),
|
|
1395
|
-
netBaseDelta: toFiniteNumber(tradeFlow.netBaseDelta),
|
|
1396
|
-
netQuoteDelta: toFiniteNumber(tradeFlow.netQuoteDelta)
|
|
1397
|
-
};
|
|
1398
|
-
};
|
|
1399
|
-
var buildMarketBreadthContextFromSignal = (signal) => {
|
|
1400
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1401
|
-
const relative = toRecord(baseContext?.relative);
|
|
1402
|
-
const breadth = toRecord(relative?.marketBreadth);
|
|
1403
|
-
if (!breadth) {
|
|
1404
|
-
return {
|
|
1405
|
-
source: null,
|
|
1406
|
-
available: false,
|
|
1407
|
-
universe: null,
|
|
1408
|
-
interval: null,
|
|
1409
|
-
stale: null,
|
|
1410
|
-
symbolsCount: null,
|
|
1411
|
-
advanceDeclineRatio: null,
|
|
1412
|
-
pctAboveMa20: null,
|
|
1413
|
-
pctAboveMa50: null,
|
|
1414
|
-
equalWeightedReturn: null,
|
|
1415
|
-
volumeWeightedReturn: null,
|
|
1416
|
-
dispersion: null
|
|
1417
|
-
};
|
|
1206
|
+
const orderId = existing.orderId;
|
|
1207
|
+
if (strategy && existing.strategy !== strategy) {
|
|
1208
|
+
return null;
|
|
1418
1209
|
}
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1210
|
+
const resolvedExitPrice = typeof exitPrice === "number" && Number.isFinite(exitPrice) ? exitPrice : existing.currentPrice ?? existing.entryPrice;
|
|
1211
|
+
const resolvedClosedPnl = typeof closedPnl === "number" && Number.isFinite(closedPnl) ? closedPnl : typeof resolvedExitPrice === "number" && Number.isFinite(resolvedExitPrice) ? calculateClosedPnl({
|
|
1212
|
+
direction: existing.direction,
|
|
1213
|
+
entryPrice: existing.entryPrice,
|
|
1214
|
+
exitPrice: resolvedExitPrice,
|
|
1215
|
+
qty: existing.qty
|
|
1216
|
+
}) : existing.closedPnl ?? existing.currentPnl ?? null;
|
|
1217
|
+
const next = expectedOrderId && existing.status === "closed" ? existing : {
|
|
1218
|
+
...existing,
|
|
1219
|
+
status: "closed",
|
|
1220
|
+
currentPrice: resolvedExitPrice,
|
|
1221
|
+
currentPnl: resolvedClosedPnl,
|
|
1222
|
+
closedPnl: resolvedClosedPnl,
|
|
1223
|
+
exitPrice: resolvedExitPrice,
|
|
1224
|
+
exitTimestamp: typeof exitTimestamp === "number" && Number.isFinite(exitTimestamp) ? exitTimestamp : now(),
|
|
1225
|
+
exitType: exitType ?? existing.exitType ?? null,
|
|
1226
|
+
lastSyncedAt: now()
|
|
1432
1227
|
};
|
|
1228
|
+
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
1229
|
+
const closeDayKey = (0, import_time.getRuntimeStorageDayKey)(next.exitTimestamp);
|
|
1230
|
+
try {
|
|
1231
|
+
await Promise.all([
|
|
1232
|
+
(expectedOrderId ? import_redis.setDataStrict : import_redis.setData)(
|
|
1233
|
+
import_redis.redisKeys.runtimeTrade(userName, orderId),
|
|
1234
|
+
next,
|
|
1235
|
+
{
|
|
1236
|
+
expire: import_constants.TTL_1M
|
|
1237
|
+
}
|
|
1238
|
+
),
|
|
1239
|
+
(expectedOrderId ? import_redis.setHashJsonFieldStrict : import_redis.setHashJsonField)(
|
|
1240
|
+
import_redis.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
1241
|
+
orderId,
|
|
1242
|
+
next,
|
|
1243
|
+
{ expire: import_constants.TTL_1M }
|
|
1244
|
+
),
|
|
1245
|
+
(expectedOrderId ? import_redis.setHashJsonFieldStrict : import_redis.setHashJsonField)(
|
|
1246
|
+
import_redis.redisKeys.runtimeClosedTradeBucket(userName, closeDayKey),
|
|
1247
|
+
orderId,
|
|
1248
|
+
next,
|
|
1249
|
+
{ expire: import_constants.TTL_1M }
|
|
1250
|
+
),
|
|
1251
|
+
expectedOrderId ? (0, import_redis.deleteRuntimeTradeReference)(
|
|
1252
|
+
import_redis.redisKeys.runtimeActiveTrade(
|
|
1253
|
+
userName,
|
|
1254
|
+
symbol,
|
|
1255
|
+
deploymentId ?? accountId
|
|
1256
|
+
),
|
|
1257
|
+
expectedOrderId
|
|
1258
|
+
) : (0, import_redis.delKey)(
|
|
1259
|
+
import_redis.redisKeys.runtimeActiveTrade(
|
|
1260
|
+
userName,
|
|
1261
|
+
symbol,
|
|
1262
|
+
deploymentId ?? accountId
|
|
1263
|
+
)
|
|
1264
|
+
)
|
|
1265
|
+
]);
|
|
1266
|
+
} catch (error) {
|
|
1267
|
+
if (expectedOrderId) throw error;
|
|
1268
|
+
import_logger.logger.error(
|
|
1269
|
+
"runtime trade close journal failed: %s %s",
|
|
1270
|
+
symbol,
|
|
1271
|
+
error?.message || String(error)
|
|
1272
|
+
);
|
|
1273
|
+
}
|
|
1274
|
+
return next;
|
|
1433
1275
|
};
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1276
|
+
|
|
1277
|
+
// src/strategy/portfolioRuntime.ts
|
|
1278
|
+
var coordinators = /* @__PURE__ */ new WeakMap();
|
|
1279
|
+
async function observeRuntimePortfolio(connector, binding, asOf, reservations, observedNow = Date.now) {
|
|
1280
|
+
if (!connector.getOpenPositionPnl || !connector.getEntryExecutions)
|
|
1281
|
+
throw new Error(
|
|
1282
|
+
"Portfolio policies require position marks and entry executions"
|
|
1283
|
+
);
|
|
1284
|
+
const orders = await Promise.all(
|
|
1285
|
+
reservations.map(async (r) => {
|
|
1286
|
+
if (r.submitted && connector.getEntryOrderObservation) {
|
|
1287
|
+
const fact2 = await connector.getEntryOrderObservation({
|
|
1288
|
+
symbol: r.symbol,
|
|
1289
|
+
orderId: r.id
|
|
1290
|
+
});
|
|
1291
|
+
if (fact2) return fact2;
|
|
1292
|
+
}
|
|
1293
|
+
const executions = r.submitted ? await connector.getEntryExecutions({
|
|
1294
|
+
symbol: r.symbol,
|
|
1295
|
+
startTime: r.timestamp,
|
|
1296
|
+
endTime: asOf
|
|
1297
|
+
}) : [];
|
|
1298
|
+
const filledQty = executions.filter(
|
|
1299
|
+
(e) => (e.orderLinkId === r.id || e.orderId === r.id) && e.direction === r.direction
|
|
1300
|
+
).reduce((sum, e) => sum + e.qty, 0);
|
|
1441
1301
|
return {
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1302
|
+
id: r.id,
|
|
1303
|
+
status: filledQty >= r.qty ? "filled" : filledQty > 0 ? "partial" : "pending",
|
|
1304
|
+
filledQty,
|
|
1305
|
+
positionId: r.id
|
|
1306
|
+
};
|
|
1307
|
+
})
|
|
1308
|
+
);
|
|
1309
|
+
const snapshots2 = await connector.getOpenPositionPnl();
|
|
1310
|
+
const positions = await Promise.all(
|
|
1311
|
+
snapshots2.map(async (p) => {
|
|
1312
|
+
const reserved = reservations.find(
|
|
1313
|
+
(r) => r.symbol === p.symbol && r.direction === p.direction && orders.some(
|
|
1314
|
+
(o) => o.id === r.id && o.filledQty > 0 && p.qty <= o.filledQty * (1 + 1e-8)
|
|
1315
|
+
)
|
|
1316
|
+
);
|
|
1317
|
+
const trade = await getActiveRuntimeTrade({
|
|
1318
|
+
...binding,
|
|
1319
|
+
symbol: p.symbol
|
|
1320
|
+
});
|
|
1321
|
+
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;
|
|
1322
|
+
return {
|
|
1323
|
+
id: reserved?.id ?? owner?.orderId ?? `unknown:${p.symbol}:${p.direction}`,
|
|
1324
|
+
strategy: reserved?.strategy ?? owner?.strategy ?? null,
|
|
1325
|
+
symbol: p.symbol,
|
|
1326
|
+
direction: p.direction,
|
|
1327
|
+
qty: p.qty,
|
|
1328
|
+
entryPrice: p.price,
|
|
1329
|
+
markPrice: p.currentPrice,
|
|
1330
|
+
markedAt: p.observedAt ?? null,
|
|
1331
|
+
stopLossPrice: p.slPrice ?? p.stopLossPrice ?? null
|
|
1454
1332
|
};
|
|
1333
|
+
})
|
|
1334
|
+
);
|
|
1335
|
+
return { asOf: Math.max(asOf, observedNow()), positions, orders };
|
|
1336
|
+
}
|
|
1337
|
+
function persistentStore(key) {
|
|
1338
|
+
const raw = /* @__PURE__ */ new WeakMap();
|
|
1339
|
+
return {
|
|
1340
|
+
async read() {
|
|
1341
|
+
const value = await import_redis2.portfolioStateStorage.read(key);
|
|
1342
|
+
if (value === null) return null;
|
|
1343
|
+
const state = JSON.parse(value);
|
|
1344
|
+
(0, import_portfolio2.assertPortfolioState)(state);
|
|
1345
|
+
raw.set(state, value);
|
|
1346
|
+
return state;
|
|
1347
|
+
},
|
|
1348
|
+
async compare(previous, next) {
|
|
1349
|
+
const expected = previous === null ? null : raw.get(previous);
|
|
1350
|
+
if (expected === void 0)
|
|
1351
|
+
throw new Error("Portfolio CAS requires a storage snapshot");
|
|
1352
|
+
return import_redis2.portfolioStateStorage.compare(key, expected, JSON.stringify(next));
|
|
1455
1353
|
}
|
|
1456
|
-
return {
|
|
1457
|
-
source: String(breadth.source ?? ""),
|
|
1458
|
-
available: true,
|
|
1459
|
-
universe: String(breadth.universe ?? ""),
|
|
1460
|
-
interval: String(breadth.interval ?? ""),
|
|
1461
|
-
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
1462
|
-
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
1463
|
-
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
1464
|
-
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
1465
|
-
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
1466
|
-
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
1467
|
-
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
1468
|
-
dispersion: toFiniteNumber(breadth.dispersion)
|
|
1469
|
-
};
|
|
1470
1354
|
};
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1355
|
+
}
|
|
1356
|
+
function getPortfolioRuntime({
|
|
1357
|
+
value,
|
|
1358
|
+
connectorName,
|
|
1359
|
+
strategy,
|
|
1360
|
+
connector,
|
|
1361
|
+
env,
|
|
1362
|
+
userName,
|
|
1363
|
+
accountId,
|
|
1364
|
+
deploymentId,
|
|
1365
|
+
symbol,
|
|
1366
|
+
canClose
|
|
1367
|
+
}) {
|
|
1368
|
+
const policy = (0, import_portfolio2.parsePortfolioPolicy)(value);
|
|
1369
|
+
if (!policy) return void 0;
|
|
1370
|
+
const replay = (env === "BACKTEST" || env === "PARITY") && Reflect.get(connector, "__tradejsReplayConnector") === true;
|
|
1371
|
+
if ((env === "BACKTEST" || env === "PARITY") && (!replay || !connector.getPortfolioObservation))
|
|
1372
|
+
throw new Error(
|
|
1373
|
+
"Portfolio policies require shared portfolio replay; isolated symbol backtests are unsupported"
|
|
1374
|
+
);
|
|
1375
|
+
if (!replay && (!userName || !accountId || !deploymentId || !connector.getOpenPositionPnl || !connector.getEntryExecutions))
|
|
1376
|
+
throw new Error(
|
|
1377
|
+
"Portfolio runtime requires exact user/account/deployment binding, marks and executions"
|
|
1378
|
+
);
|
|
1379
|
+
const binding = {
|
|
1380
|
+
userName,
|
|
1381
|
+
accountId,
|
|
1382
|
+
deploymentId
|
|
1477
1383
|
};
|
|
1384
|
+
const scope = policy.scope === "strategy" ? strategy : policy.scope === "account" ? "account" : [...policy.scope.strategies].sort();
|
|
1385
|
+
const key = (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify([binding, connectorName, connector.universe, scope])).digest("hex");
|
|
1386
|
+
const fingerprint3 = (0, import_portfolio2.portfolioFingerprint)(policy, strategy);
|
|
1387
|
+
const cacheKey = key;
|
|
1388
|
+
let registry = coordinators.get(connector);
|
|
1389
|
+
if (!registry) {
|
|
1390
|
+
registry = /* @__PURE__ */ new Map();
|
|
1391
|
+
coordinators.set(connector, registry);
|
|
1392
|
+
}
|
|
1393
|
+
const existing = registry.get(cacheKey);
|
|
1394
|
+
if (existing) {
|
|
1395
|
+
if (existing.fingerprint !== fingerprint3)
|
|
1396
|
+
throw new Error("Conflicting portfolio configuration for the same scope");
|
|
1397
|
+
if (symbol && canClose)
|
|
1398
|
+
existing.permissions.set(JSON.stringify([strategy, symbol]), canClose);
|
|
1399
|
+
return existing.coordinator;
|
|
1400
|
+
}
|
|
1401
|
+
const permissions = /* @__PURE__ */ new Map();
|
|
1402
|
+
if (symbol && canClose)
|
|
1403
|
+
permissions.set(JSON.stringify([strategy, symbol]), canClose);
|
|
1404
|
+
const coordinator = createPortfolioCoordinator({
|
|
1405
|
+
policy,
|
|
1406
|
+
strategy,
|
|
1407
|
+
store: replay ? createMemoryPortfolioStore() : persistentStore(key),
|
|
1408
|
+
execution: {
|
|
1409
|
+
async confirmClosed(intent, asOf) {
|
|
1410
|
+
if (replay) return;
|
|
1411
|
+
const position = intent.position;
|
|
1412
|
+
await markRuntimeTradeClosed({
|
|
1413
|
+
...binding,
|
|
1414
|
+
strategy: position.strategy,
|
|
1415
|
+
expectedOrderId: position.id,
|
|
1416
|
+
symbol: position.symbol,
|
|
1417
|
+
exitPrice: position.markPrice,
|
|
1418
|
+
exitTimestamp: asOf,
|
|
1419
|
+
exitType: "exit"
|
|
1420
|
+
});
|
|
1421
|
+
},
|
|
1422
|
+
observe: (asOf, reservations) => replay ? connector.getPortfolioObservation(asOf, reservations) : observeRuntimePortfolio(connector, binding, asOf, reservations),
|
|
1423
|
+
async close(position, intent, asOf) {
|
|
1424
|
+
const permission = permissions.get(
|
|
1425
|
+
JSON.stringify([position.strategy, position.symbol])
|
|
1426
|
+
);
|
|
1427
|
+
if (!permission || !await permission(position, intent, asOf)) return;
|
|
1428
|
+
if (!replay) {
|
|
1429
|
+
const trade = await getActiveRuntimeTrade({
|
|
1430
|
+
...binding,
|
|
1431
|
+
symbol: position.symbol
|
|
1432
|
+
});
|
|
1433
|
+
if (!trade || trade.orderId !== position.id)
|
|
1434
|
+
throw new Error("Portfolio close ownership changed");
|
|
1435
|
+
}
|
|
1436
|
+
await connector.closePosition({
|
|
1437
|
+
symbol: position.symbol,
|
|
1438
|
+
direction: position.direction,
|
|
1439
|
+
price: position.markPrice,
|
|
1440
|
+
timestamp: asOf,
|
|
1441
|
+
signal: {
|
|
1442
|
+
signalId: `${position.id}:${intent.reason}`,
|
|
1443
|
+
strategy: position.strategy,
|
|
1444
|
+
symbol: position.symbol,
|
|
1445
|
+
direction: position.direction,
|
|
1446
|
+
interval: "1",
|
|
1447
|
+
timestamp: asOf,
|
|
1448
|
+
prices: {
|
|
1449
|
+
riskRatio: 0,
|
|
1450
|
+
currentPrice: position.markPrice,
|
|
1451
|
+
takeProfitPrice: position.markPrice,
|
|
1452
|
+
stopLossPrice: position.markPrice
|
|
1453
|
+
},
|
|
1454
|
+
figures: {},
|
|
1455
|
+
indicators: {},
|
|
1456
|
+
additionalIndicators: { portfolioExitReason: intent.reason }
|
|
1457
|
+
}
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
});
|
|
1462
|
+
registry.set(cacheKey, { fingerprint: fingerprint3, coordinator, permissions });
|
|
1463
|
+
return coordinator;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
// src/strategyHelpers/tickerProfiles.ts
|
|
1467
|
+
var import_tickerProfiles = require("@tradejs/infra/tickerProfiles");
|
|
1468
|
+
var import_tickerProfiles2 = require("@tradejs/core/tickerProfiles");
|
|
1469
|
+
|
|
1470
|
+
// src/tradejsConfig.ts
|
|
1471
|
+
var import_fs = __toESM(require("fs"));
|
|
1472
|
+
var import_path = __toESM(require("path"));
|
|
1473
|
+
var import_url = require("url");
|
|
1474
|
+
var import_config = require("@tradejs/core/config");
|
|
1475
|
+
var import_logger2 = require("@tradejs/infra/logger");
|
|
1476
|
+
var CONFIG_FILE_NAMES = [
|
|
1477
|
+
"tradejs.config.ts",
|
|
1478
|
+
"tradejs.config.mts",
|
|
1479
|
+
"tradejs.config.js",
|
|
1480
|
+
"tradejs.config.mjs",
|
|
1481
|
+
"tradejs.config.cjs"
|
|
1482
|
+
];
|
|
1483
|
+
var TS_MODULE_RE = /\.(cts|mts|ts)$/i;
|
|
1484
|
+
var cachedByCwd = /* @__PURE__ */ new Map();
|
|
1485
|
+
var announcedConfigFile = /* @__PURE__ */ new Set();
|
|
1486
|
+
var tsNodeRegistered = false;
|
|
1487
|
+
var tsconfigPathsRegisteredByCwd = /* @__PURE__ */ new Set();
|
|
1488
|
+
var tsconfigPathMatchersByCwd = /* @__PURE__ */ new Map();
|
|
1489
|
+
var getTradejsProjectCwd = (cwd) => {
|
|
1490
|
+
const explicit = String(cwd ?? "").trim();
|
|
1491
|
+
if (explicit) {
|
|
1492
|
+
return import_path.default.resolve(explicit);
|
|
1493
|
+
}
|
|
1494
|
+
const fromEnv = String(process.env.PROJECT_CWD || "").trim();
|
|
1495
|
+
if (fromEnv) {
|
|
1496
|
+
return import_path.default.resolve(fromEnv);
|
|
1497
|
+
}
|
|
1498
|
+
return process.cwd();
|
|
1478
1499
|
};
|
|
1479
|
-
var
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
const targetVsBtc = toRecord(relative?.targetVsBtc);
|
|
1483
|
-
if (!targetVsBtc) {
|
|
1484
|
-
return {
|
|
1485
|
-
source: null,
|
|
1486
|
-
available: false,
|
|
1487
|
-
ratioReturn1h: null,
|
|
1488
|
-
ratioReturn4h: null,
|
|
1489
|
-
ratioReturn24h: null,
|
|
1490
|
-
alphaVsBtc1h: null,
|
|
1491
|
-
alphaVsBtc4h: null,
|
|
1492
|
-
alphaVsBtc24h: null,
|
|
1493
|
-
betaToBtc20: null,
|
|
1494
|
-
correlationToBtc20: null,
|
|
1495
|
-
ratioTrend: null
|
|
1496
|
-
};
|
|
1500
|
+
var normalizeConfig = (rawConfig) => {
|
|
1501
|
+
if (!rawConfig || typeof rawConfig !== "object") {
|
|
1502
|
+
return {};
|
|
1497
1503
|
}
|
|
1504
|
+
const config = rawConfig;
|
|
1505
|
+
const strategies2 = Array.isArray(config.strategies) ? config.strategies.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1506
|
+
const indicators = Array.isArray(config.indicators) ? config.indicators.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1507
|
+
const connectors = Array.isArray(config.connectors) ? config.connectors.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1508
|
+
const hooks = (0, import_config.normalizeTradejsConfigHooks)(
|
|
1509
|
+
config.hooks
|
|
1510
|
+
);
|
|
1511
|
+
const runtime = config.runtime && typeof config.runtime === "object" && !Array.isArray(config.runtime) ? config.runtime : void 0;
|
|
1498
1512
|
return {
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
alphaVsBtc1h: toFiniteNumber(targetVsBtc.alphaVsBtc1h),
|
|
1505
|
-
alphaVsBtc4h: toFiniteNumber(targetVsBtc.alphaVsBtc4h),
|
|
1506
|
-
alphaVsBtc24h: toFiniteNumber(targetVsBtc.alphaVsBtc24h),
|
|
1507
|
-
betaToBtc20: toFiniteNumber(targetVsBtc.betaToBtc20),
|
|
1508
|
-
correlationToBtc20: toFiniteNumber(targetVsBtc.correlationToBtc20),
|
|
1509
|
-
ratioTrend: typeof targetVsBtc.ratioTrend === "string" ? targetVsBtc.ratioTrend : null
|
|
1513
|
+
strategies: strategies2,
|
|
1514
|
+
indicators,
|
|
1515
|
+
connectors,
|
|
1516
|
+
...hooks ? { hooks } : {},
|
|
1517
|
+
...runtime ? { runtime } : {}
|
|
1510
1518
|
};
|
|
1511
1519
|
};
|
|
1512
|
-
var
|
|
1513
|
-
const
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
if (!btcAltRegime) {
|
|
1517
|
-
return {
|
|
1518
|
-
source: null,
|
|
1519
|
-
available: false,
|
|
1520
|
-
universe: null,
|
|
1521
|
-
interval: null,
|
|
1522
|
-
stale: null,
|
|
1523
|
-
regime: null,
|
|
1524
|
-
btcReturn24h: null,
|
|
1525
|
-
altBasketReturn24h: null,
|
|
1526
|
-
btcVsAltReturn24h: null,
|
|
1527
|
-
btcTurnoverShare24h: null,
|
|
1528
|
-
btcTurnoverShareChange24h: null,
|
|
1529
|
-
altVolToBtcVol24h: null,
|
|
1530
|
-
altDispersion24h: null
|
|
1531
|
-
};
|
|
1520
|
+
var getNodeCreateRequire = () => {
|
|
1521
|
+
const builtinModule = process.getBuiltinModule?.("module");
|
|
1522
|
+
if (typeof builtinModule?.createRequire === "function") {
|
|
1523
|
+
return builtinModule.createRequire;
|
|
1532
1524
|
}
|
|
1533
|
-
|
|
1534
|
-
source: String(btcAltRegime.source ?? ""),
|
|
1535
|
-
available: true,
|
|
1536
|
-
universe: String(btcAltRegime.universe ?? ""),
|
|
1537
|
-
interval: String(btcAltRegime.interval ?? ""),
|
|
1538
|
-
stale: typeof btcAltRegime.stale === "boolean" ? btcAltRegime.stale : null,
|
|
1539
|
-
regime: typeof btcAltRegime.regime === "string" ? btcAltRegime.regime : null,
|
|
1540
|
-
btcReturn24h: toFiniteNumber(btcAltRegime.btcReturn24h),
|
|
1541
|
-
altBasketReturn24h: toFiniteNumber(btcAltRegime.altBasketReturn24h),
|
|
1542
|
-
btcVsAltReturn24h: toFiniteNumber(btcAltRegime.btcVsAltReturn24h),
|
|
1543
|
-
btcTurnoverShare24h: toFiniteNumber(btcAltRegime.btcTurnoverShare24h),
|
|
1544
|
-
btcTurnoverShareChange24h: toFiniteNumber(
|
|
1545
|
-
btcAltRegime.btcTurnoverShareChange24h
|
|
1546
|
-
),
|
|
1547
|
-
altVolToBtcVol24h: toFiniteNumber(btcAltRegime.altVolToBtcVol24h),
|
|
1548
|
-
altDispersion24h: toFiniteNumber(btcAltRegime.altDispersion24h)
|
|
1549
|
-
};
|
|
1525
|
+
throw new TypeError("module.createRequire is not available");
|
|
1550
1526
|
};
|
|
1551
|
-
var
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
if (!cmcGlobal) {
|
|
1556
|
-
return {
|
|
1557
|
-
source: null,
|
|
1558
|
-
available: false,
|
|
1559
|
-
interval: null,
|
|
1560
|
-
asOfTs: null,
|
|
1561
|
-
stale: null,
|
|
1562
|
-
totalMarketCapUsd: null,
|
|
1563
|
-
totalVolumeUsd: null,
|
|
1564
|
-
totalVolumeReportedUsd: null,
|
|
1565
|
-
altMarketCapUsd: null,
|
|
1566
|
-
altVolumeUsd: null,
|
|
1567
|
-
altVolumeReportedUsd: null,
|
|
1568
|
-
btcDominancePct: null,
|
|
1569
|
-
ethDominancePct: null,
|
|
1570
|
-
btcDominanceChange24hPct: null,
|
|
1571
|
-
ethDominanceChange24hPct: null,
|
|
1572
|
-
altMarketCapChange24hPct: null,
|
|
1573
|
-
altVolumeChange24hPct: null,
|
|
1574
|
-
activeCryptocurrencies: null,
|
|
1575
|
-
activeExchanges: null,
|
|
1576
|
-
activeMarketPairs: null,
|
|
1577
|
-
altLiquidityRegime: null
|
|
1578
|
-
};
|
|
1527
|
+
var getRequireFn = (cwd = getTradejsProjectCwd()) => getNodeCreateRequire()(import_path.default.join(import_path.default.resolve(cwd), "__tradejs_loader__.js"));
|
|
1528
|
+
var ensureTsNodeRegistered = async () => {
|
|
1529
|
+
if (tsNodeRegistered) {
|
|
1530
|
+
return;
|
|
1579
1531
|
}
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
altVolumeUsd: toFiniteNumber(cmcGlobal.altVolumeUsd),
|
|
1591
|
-
altVolumeReportedUsd: toFiniteNumber(cmcGlobal.altVolumeReportedUsd),
|
|
1592
|
-
btcDominancePct: toFiniteNumber(cmcGlobal.btcDominancePct),
|
|
1593
|
-
ethDominancePct: toFiniteNumber(cmcGlobal.ethDominancePct),
|
|
1594
|
-
btcDominanceChange24hPct: toFiniteNumber(
|
|
1595
|
-
cmcGlobal.btcDominanceChange24hPct
|
|
1596
|
-
),
|
|
1597
|
-
ethDominanceChange24hPct: toFiniteNumber(
|
|
1598
|
-
cmcGlobal.ethDominanceChange24hPct
|
|
1599
|
-
),
|
|
1600
|
-
altMarketCapChange24hPct: toFiniteNumber(
|
|
1601
|
-
cmcGlobal.altMarketCapChange24hPct
|
|
1602
|
-
),
|
|
1603
|
-
altVolumeChange24hPct: toFiniteNumber(cmcGlobal.altVolumeChange24hPct),
|
|
1604
|
-
activeCryptocurrencies: toFiniteNumber(cmcGlobal.activeCryptocurrencies),
|
|
1605
|
-
activeExchanges: toFiniteNumber(cmcGlobal.activeExchanges),
|
|
1606
|
-
activeMarketPairs: toFiniteNumber(cmcGlobal.activeMarketPairs),
|
|
1607
|
-
altLiquidityRegime: typeof cmcGlobal.altLiquidityRegime === "string" ? cmcGlobal.altLiquidityRegime : null
|
|
1608
|
-
};
|
|
1532
|
+
const tsNodeModule = await import("ts-node");
|
|
1533
|
+
const tsNode = tsNodeModule.default ?? tsNodeModule;
|
|
1534
|
+
tsNode.register?.({
|
|
1535
|
+
transpileOnly: true,
|
|
1536
|
+
compilerOptions: {
|
|
1537
|
+
module: "Node16",
|
|
1538
|
+
moduleResolution: "node16"
|
|
1539
|
+
}
|
|
1540
|
+
});
|
|
1541
|
+
tsNodeRegistered = true;
|
|
1609
1542
|
};
|
|
1610
|
-
var
|
|
1611
|
-
const
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
if (!cmcReferenceAssets) {
|
|
1615
|
-
return {
|
|
1616
|
-
source: null,
|
|
1617
|
-
available: false,
|
|
1618
|
-
interval: null,
|
|
1619
|
-
asOfTs: null,
|
|
1620
|
-
stale: null,
|
|
1621
|
-
btcMarketCapUsd: null,
|
|
1622
|
-
ethMarketCapUsd: null,
|
|
1623
|
-
btcVolumeUsd: null,
|
|
1624
|
-
ethVolumeUsd: null,
|
|
1625
|
-
btcVolumeToMarketCap: null,
|
|
1626
|
-
ethVolumeToMarketCap: null,
|
|
1627
|
-
ethBtcMarketCapRatio: null,
|
|
1628
|
-
ethBtcMarketCapRatioChange24hPct: null,
|
|
1629
|
-
ethVsBtcVolumeRatio: null,
|
|
1630
|
-
referenceLiquidityRegime: null
|
|
1631
|
-
};
|
|
1543
|
+
var ensureTsconfigPathsRegistered = async (cwd = getTradejsProjectCwd()) => {
|
|
1544
|
+
const projectRoot = getTradejsProjectCwd(cwd);
|
|
1545
|
+
if (tsconfigPathsRegisteredByCwd.has(projectRoot)) {
|
|
1546
|
+
return;
|
|
1632
1547
|
}
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
stale: typeof cmcReferenceAssets.stale === "boolean" ? cmcReferenceAssets.stale : null,
|
|
1639
|
-
btcMarketCapUsd: toFiniteNumber(cmcReferenceAssets.btcMarketCapUsd),
|
|
1640
|
-
ethMarketCapUsd: toFiniteNumber(cmcReferenceAssets.ethMarketCapUsd),
|
|
1641
|
-
btcVolumeUsd: toFiniteNumber(cmcReferenceAssets.btcVolumeUsd),
|
|
1642
|
-
ethVolumeUsd: toFiniteNumber(cmcReferenceAssets.ethVolumeUsd),
|
|
1643
|
-
btcVolumeToMarketCap: toFiniteNumber(
|
|
1644
|
-
cmcReferenceAssets.btcVolumeToMarketCap
|
|
1645
|
-
),
|
|
1646
|
-
ethVolumeToMarketCap: toFiniteNumber(
|
|
1647
|
-
cmcReferenceAssets.ethVolumeToMarketCap
|
|
1648
|
-
),
|
|
1649
|
-
ethBtcMarketCapRatio: toFiniteNumber(
|
|
1650
|
-
cmcReferenceAssets.ethBtcMarketCapRatio
|
|
1651
|
-
),
|
|
1652
|
-
ethBtcMarketCapRatioChange24hPct: toFiniteNumber(
|
|
1653
|
-
cmcReferenceAssets.ethBtcMarketCapRatioChange24hPct
|
|
1654
|
-
),
|
|
1655
|
-
ethVsBtcVolumeRatio: toFiniteNumber(cmcReferenceAssets.ethVsBtcVolumeRatio),
|
|
1656
|
-
referenceLiquidityRegime: typeof cmcReferenceAssets.referenceLiquidityRegime === "string" ? cmcReferenceAssets.referenceLiquidityRegime : null
|
|
1657
|
-
};
|
|
1658
|
-
};
|
|
1659
|
-
var buildCmcExchangeLiquidityContextFromSignal = (signal) => {
|
|
1660
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1661
|
-
const relative = toRecord(baseContext?.relative);
|
|
1662
|
-
const cmcExchangeLiquidity = toRecord(relative?.cmcExchangeLiquidity);
|
|
1663
|
-
if (!cmcExchangeLiquidity) {
|
|
1664
|
-
return {
|
|
1665
|
-
source: null,
|
|
1666
|
-
available: false,
|
|
1667
|
-
interval: null,
|
|
1668
|
-
asOfTs: null,
|
|
1669
|
-
stale: null,
|
|
1670
|
-
exchangesCount: null,
|
|
1671
|
-
totalVolumeUsd: null,
|
|
1672
|
-
totalVolumeChange24hPct: null,
|
|
1673
|
-
binanceVolumeUsd: null,
|
|
1674
|
-
binanceVolumeShare: null,
|
|
1675
|
-
topExchangeVolumeShare: null,
|
|
1676
|
-
liquidityRegime: null
|
|
1677
|
-
};
|
|
1548
|
+
const tsconfigPathsModule = await import("tsconfig-paths");
|
|
1549
|
+
const loadConfig = tsconfigPathsModule.loadConfig;
|
|
1550
|
+
const register = tsconfigPathsModule.register;
|
|
1551
|
+
if (typeof loadConfig !== "function" || typeof register !== "function") {
|
|
1552
|
+
return;
|
|
1678
1553
|
}
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
),
|
|
1690
|
-
binanceVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeUsd),
|
|
1691
|
-
binanceVolumeShare: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeShare),
|
|
1692
|
-
topExchangeVolumeShare: toFiniteNumber(
|
|
1693
|
-
cmcExchangeLiquidity.topExchangeVolumeShare
|
|
1694
|
-
),
|
|
1695
|
-
liquidityRegime: typeof cmcExchangeLiquidity.liquidityRegime === "string" ? cmcExchangeLiquidity.liquidityRegime : null
|
|
1696
|
-
};
|
|
1554
|
+
const loadedConfig = loadConfig(projectRoot);
|
|
1555
|
+
if (loadedConfig.resultType !== "success") {
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
register({
|
|
1559
|
+
baseUrl: loadedConfig.absoluteBaseUrl,
|
|
1560
|
+
paths: loadedConfig.paths,
|
|
1561
|
+
addMatchAll: false
|
|
1562
|
+
});
|
|
1563
|
+
tsconfigPathsRegisteredByCwd.add(projectRoot);
|
|
1697
1564
|
};
|
|
1698
|
-
var
|
|
1699
|
-
const
|
|
1700
|
-
const
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
return
|
|
1704
|
-
source: null,
|
|
1705
|
-
available: false,
|
|
1706
|
-
interval: null,
|
|
1707
|
-
asOfTs: null,
|
|
1708
|
-
stale: null,
|
|
1709
|
-
value: null,
|
|
1710
|
-
valueChange24h: null,
|
|
1711
|
-
valueChange7d: null,
|
|
1712
|
-
classification: null,
|
|
1713
|
-
sentimentRegime: null
|
|
1714
|
-
};
|
|
1565
|
+
var resolveTsconfigPathModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
1566
|
+
const projectRoot = getTradejsProjectCwd(cwd);
|
|
1567
|
+
const cachedMatcher = tsconfigPathMatchersByCwd.get(projectRoot);
|
|
1568
|
+
if (cachedMatcher) {
|
|
1569
|
+
const resolved2 = cachedMatcher(moduleName);
|
|
1570
|
+
return resolved2 || null;
|
|
1715
1571
|
}
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1572
|
+
const tsconfigPathsModule = await import("tsconfig-paths");
|
|
1573
|
+
const loadConfig = tsconfigPathsModule.loadConfig;
|
|
1574
|
+
const createMatchPath = tsconfigPathsModule.createMatchPath;
|
|
1575
|
+
if (typeof loadConfig !== "function" || typeof createMatchPath !== "function") {
|
|
1576
|
+
return null;
|
|
1577
|
+
}
|
|
1578
|
+
const loadedConfig = loadConfig(projectRoot);
|
|
1579
|
+
if (loadedConfig.resultType !== "success") {
|
|
1580
|
+
return null;
|
|
1581
|
+
}
|
|
1582
|
+
const matchPath = createMatchPath(
|
|
1583
|
+
loadedConfig.absoluteBaseUrl,
|
|
1584
|
+
loadedConfig.paths
|
|
1585
|
+
);
|
|
1586
|
+
const matcher = (requestedModule) => matchPath(requestedModule, void 0, import_fs.default.existsSync, [
|
|
1587
|
+
".ts",
|
|
1588
|
+
".tsx",
|
|
1589
|
+
".mts",
|
|
1590
|
+
".cts",
|
|
1591
|
+
".js",
|
|
1592
|
+
".jsx",
|
|
1593
|
+
".mjs",
|
|
1594
|
+
".cjs",
|
|
1595
|
+
".json"
|
|
1596
|
+
]) || "";
|
|
1597
|
+
tsconfigPathMatchersByCwd.set(projectRoot, matcher);
|
|
1598
|
+
const resolved = matcher(moduleName);
|
|
1599
|
+
return resolved || null;
|
|
1728
1600
|
};
|
|
1729
|
-
var
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
const cmcIndexes = toRecord(relative?.cmcIndexes);
|
|
1733
|
-
if (!cmcIndexes) {
|
|
1734
|
-
return {
|
|
1735
|
-
source: null,
|
|
1736
|
-
available: false,
|
|
1737
|
-
interval: null,
|
|
1738
|
-
asOfTs: null,
|
|
1739
|
-
stale: null,
|
|
1740
|
-
cmc100Value: null,
|
|
1741
|
-
cmc100Change24hPct: null,
|
|
1742
|
-
cmc100TopConstituentSymbol: null,
|
|
1743
|
-
cmc100TopConstituentWeightPct: null,
|
|
1744
|
-
cmc20Value: null,
|
|
1745
|
-
cmc20Change24hPct: null,
|
|
1746
|
-
cmc20TopConstituentSymbol: null,
|
|
1747
|
-
cmc20TopConstituentWeightPct: null,
|
|
1748
|
-
cmc20ToCmc100Ratio: null,
|
|
1749
|
-
cmc20ToCmc100RatioChange24hPct: null,
|
|
1750
|
-
indexRegime: null
|
|
1751
|
-
};
|
|
1752
|
-
}
|
|
1753
|
-
return {
|
|
1754
|
-
source: String(cmcIndexes.source ?? ""),
|
|
1755
|
-
available: true,
|
|
1756
|
-
interval: typeof cmcIndexes.interval === "string" ? cmcIndexes.interval : null,
|
|
1757
|
-
asOfTs: toFiniteNumber(cmcIndexes.asOfTs),
|
|
1758
|
-
stale: typeof cmcIndexes.stale === "boolean" ? cmcIndexes.stale : null,
|
|
1759
|
-
cmc100Value: toFiniteNumber(cmcIndexes.cmc100Value),
|
|
1760
|
-
cmc100Change24hPct: toFiniteNumber(cmcIndexes.cmc100Change24hPct),
|
|
1761
|
-
cmc100TopConstituentSymbol: typeof cmcIndexes.cmc100TopConstituentSymbol === "string" ? cmcIndexes.cmc100TopConstituentSymbol : null,
|
|
1762
|
-
cmc100TopConstituentWeightPct: toFiniteNumber(
|
|
1763
|
-
cmcIndexes.cmc100TopConstituentWeightPct
|
|
1764
|
-
),
|
|
1765
|
-
cmc20Value: toFiniteNumber(cmcIndexes.cmc20Value),
|
|
1766
|
-
cmc20Change24hPct: toFiniteNumber(cmcIndexes.cmc20Change24hPct),
|
|
1767
|
-
cmc20TopConstituentSymbol: typeof cmcIndexes.cmc20TopConstituentSymbol === "string" ? cmcIndexes.cmc20TopConstituentSymbol : null,
|
|
1768
|
-
cmc20TopConstituentWeightPct: toFiniteNumber(
|
|
1769
|
-
cmcIndexes.cmc20TopConstituentWeightPct
|
|
1770
|
-
),
|
|
1771
|
-
cmc20ToCmc100Ratio: toFiniteNumber(cmcIndexes.cmc20ToCmc100Ratio),
|
|
1772
|
-
cmc20ToCmc100RatioChange24hPct: toFiniteNumber(
|
|
1773
|
-
cmcIndexes.cmc20ToCmc100RatioChange24hPct
|
|
1774
|
-
),
|
|
1775
|
-
indexRegime: typeof cmcIndexes.indexRegime === "string" ? cmcIndexes.indexRegime : null
|
|
1776
|
-
};
|
|
1777
|
-
};
|
|
1778
|
-
var buildReferenceTradeFlowContextFromSignal = (signal) => {
|
|
1779
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
1780
|
-
const relative = toRecord(baseContext?.relative);
|
|
1781
|
-
const refs = toRecord(relative?.referenceTradeFlow);
|
|
1782
|
-
const primaryReferenceSymbol = typeof refs?.primaryReferenceSymbol === "string" ? refs.primaryReferenceSymbol : null;
|
|
1783
|
-
const tradeFlowBySymbol = toRecord(refs?.tradeFlowBySymbol);
|
|
1784
|
-
const primaryTradeFlow = primaryReferenceSymbol != null ? toRecord(tradeFlowBySymbol?.[primaryReferenceSymbol]) : null;
|
|
1785
|
-
if (!refs) {
|
|
1786
|
-
return {
|
|
1787
|
-
source: null,
|
|
1788
|
-
available: false,
|
|
1789
|
-
primaryReferenceSymbol: null,
|
|
1790
|
-
referenceSymbols: [],
|
|
1791
|
-
primaryTradeFlowBuyPressurePct: null,
|
|
1792
|
-
primaryTradeFlowStale: null
|
|
1793
|
-
};
|
|
1794
|
-
}
|
|
1795
|
-
return {
|
|
1796
|
-
source: String(refs.source ?? ""),
|
|
1797
|
-
available: true,
|
|
1798
|
-
primaryReferenceSymbol,
|
|
1799
|
-
referenceSymbols: Array.isArray(refs.referenceSymbols) ? refs.referenceSymbols.map(String) : [],
|
|
1800
|
-
primaryTradeFlowBuyPressurePct: toFiniteNumber(
|
|
1801
|
-
primaryTradeFlow?.buyPressurePct
|
|
1802
|
-
),
|
|
1803
|
-
primaryTradeFlowStale: typeof primaryTradeFlow?.stale === "boolean" ? primaryTradeFlow.stale : null
|
|
1804
|
-
};
|
|
1805
|
-
};
|
|
1806
|
-
var buildAiMarketContext = (signal) => ({
|
|
1807
|
-
execution: {
|
|
1808
|
-
binanceCoinbaseSpread: buildSpreadContextFromSignal(signal)
|
|
1809
|
-
},
|
|
1810
|
-
participation: {
|
|
1811
|
-
trueDelta: buildTrueDeltaContextFromSignal(signal),
|
|
1812
|
-
tradeFlow: buildTradeFlowContextFromSignal(signal)
|
|
1813
|
-
},
|
|
1814
|
-
relative: {
|
|
1815
|
-
marketBreadth: buildMarketBreadthContextFromSignal(signal),
|
|
1816
|
-
marketBreadths: buildMarketBreadthsContextFromSignal(signal),
|
|
1817
|
-
targetVsBtc: buildTargetVsBtcContextFromSignal(signal),
|
|
1818
|
-
btcAltRegime: buildBtcAltRegimeContextFromSignal(signal),
|
|
1819
|
-
cmcGlobal: buildCmcGlobalContextFromSignal(signal),
|
|
1820
|
-
cmcReferenceAssets: buildCmcReferenceAssetsContextFromSignal(signal),
|
|
1821
|
-
cmcExchangeLiquidity: buildCmcExchangeLiquidityContextFromSignal(signal),
|
|
1822
|
-
cmcFearGreed: buildCmcFearGreedContextFromSignal(signal),
|
|
1823
|
-
cmcIndexes: buildCmcIndexesContextFromSignal(signal),
|
|
1824
|
-
referenceTradeFlow: buildReferenceTradeFlowContextFromSignal(signal)
|
|
1825
|
-
}
|
|
1826
|
-
});
|
|
1827
|
-
|
|
1828
|
-
// src/strategy/manifests.ts
|
|
1829
|
-
var import_indicators = require("@tradejs/core/indicators");
|
|
1830
|
-
|
|
1831
|
-
// src/tradejsConfig.ts
|
|
1832
|
-
var import_fs = __toESM(require("fs"));
|
|
1833
|
-
var import_path = __toESM(require("path"));
|
|
1834
|
-
var import_url = require("url");
|
|
1835
|
-
var import_config = require("@tradejs/core/config");
|
|
1836
|
-
var import_logger2 = require("@tradejs/infra/logger");
|
|
1837
|
-
var CONFIG_FILE_NAMES = [
|
|
1838
|
-
"tradejs.config.ts",
|
|
1839
|
-
"tradejs.config.mts",
|
|
1840
|
-
"tradejs.config.js",
|
|
1841
|
-
"tradejs.config.mjs",
|
|
1842
|
-
"tradejs.config.cjs"
|
|
1843
|
-
];
|
|
1844
|
-
var TS_MODULE_RE = /\.(cts|mts|ts)$/i;
|
|
1845
|
-
var cachedByCwd = /* @__PURE__ */ new Map();
|
|
1846
|
-
var announcedConfigFile = /* @__PURE__ */ new Set();
|
|
1847
|
-
var tsNodeRegistered = false;
|
|
1848
|
-
var tsconfigPathsRegisteredByCwd = /* @__PURE__ */ new Set();
|
|
1849
|
-
var tsconfigPathMatchersByCwd = /* @__PURE__ */ new Map();
|
|
1850
|
-
var getTradejsProjectCwd = (cwd) => {
|
|
1851
|
-
const explicit = String(cwd ?? "").trim();
|
|
1852
|
-
if (explicit) {
|
|
1853
|
-
return import_path.default.resolve(explicit);
|
|
1854
|
-
}
|
|
1855
|
-
const fromEnv = String(process.env.PROJECT_CWD || "").trim();
|
|
1856
|
-
if (fromEnv) {
|
|
1857
|
-
return import_path.default.resolve(fromEnv);
|
|
1858
|
-
}
|
|
1859
|
-
return process.cwd();
|
|
1860
|
-
};
|
|
1861
|
-
var normalizeConfig = (rawConfig) => {
|
|
1862
|
-
if (!rawConfig || typeof rawConfig !== "object") {
|
|
1863
|
-
return {};
|
|
1864
|
-
}
|
|
1865
|
-
const config = rawConfig;
|
|
1866
|
-
const strategies2 = Array.isArray(config.strategies) ? config.strategies.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1867
|
-
const indicators = Array.isArray(config.indicators) ? config.indicators.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1868
|
-
const connectors = Array.isArray(config.connectors) ? config.connectors.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
1869
|
-
const hooks = (0, import_config.normalizeTradejsConfigHooks)(
|
|
1870
|
-
config.hooks
|
|
1871
|
-
);
|
|
1872
|
-
const runtime = config.runtime && typeof config.runtime === "object" && !Array.isArray(config.runtime) ? config.runtime : void 0;
|
|
1873
|
-
return {
|
|
1874
|
-
strategies: strategies2,
|
|
1875
|
-
indicators,
|
|
1876
|
-
connectors,
|
|
1877
|
-
...hooks ? { hooks } : {},
|
|
1878
|
-
...runtime ? { runtime } : {}
|
|
1879
|
-
};
|
|
1880
|
-
};
|
|
1881
|
-
var getNodeCreateRequire = () => {
|
|
1882
|
-
const builtinModule = process.getBuiltinModule?.("module");
|
|
1883
|
-
if (typeof builtinModule?.createRequire === "function") {
|
|
1884
|
-
return builtinModule.createRequire;
|
|
1885
|
-
}
|
|
1886
|
-
throw new TypeError("module.createRequire is not available");
|
|
1887
|
-
};
|
|
1888
|
-
var getRequireFn = (cwd = getTradejsProjectCwd()) => getNodeCreateRequire()(import_path.default.join(import_path.default.resolve(cwd), "__tradejs_loader__.js"));
|
|
1889
|
-
var ensureTsNodeRegistered = async () => {
|
|
1890
|
-
if (tsNodeRegistered) {
|
|
1891
|
-
return;
|
|
1892
|
-
}
|
|
1893
|
-
const tsNodeModule = await import("ts-node");
|
|
1894
|
-
const tsNode = tsNodeModule.default ?? tsNodeModule;
|
|
1895
|
-
tsNode.register?.({
|
|
1896
|
-
transpileOnly: true,
|
|
1897
|
-
compilerOptions: {
|
|
1898
|
-
module: "Node16",
|
|
1899
|
-
moduleResolution: "node16"
|
|
1900
|
-
}
|
|
1901
|
-
});
|
|
1902
|
-
tsNodeRegistered = true;
|
|
1903
|
-
};
|
|
1904
|
-
var ensureTsconfigPathsRegistered = async (cwd = getTradejsProjectCwd()) => {
|
|
1905
|
-
const projectRoot = getTradejsProjectCwd(cwd);
|
|
1906
|
-
if (tsconfigPathsRegisteredByCwd.has(projectRoot)) {
|
|
1907
|
-
return;
|
|
1908
|
-
}
|
|
1909
|
-
const tsconfigPathsModule = await import("tsconfig-paths");
|
|
1910
|
-
const loadConfig = tsconfigPathsModule.loadConfig;
|
|
1911
|
-
const register = tsconfigPathsModule.register;
|
|
1912
|
-
if (typeof loadConfig !== "function" || typeof register !== "function") {
|
|
1913
|
-
return;
|
|
1914
|
-
}
|
|
1915
|
-
const loadedConfig = loadConfig(projectRoot);
|
|
1916
|
-
if (loadedConfig.resultType !== "success") {
|
|
1917
|
-
return;
|
|
1918
|
-
}
|
|
1919
|
-
register({
|
|
1920
|
-
baseUrl: loadedConfig.absoluteBaseUrl,
|
|
1921
|
-
paths: loadedConfig.paths,
|
|
1922
|
-
addMatchAll: false
|
|
1923
|
-
});
|
|
1924
|
-
tsconfigPathsRegisteredByCwd.add(projectRoot);
|
|
1925
|
-
};
|
|
1926
|
-
var resolveTsconfigPathModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
1927
|
-
const projectRoot = getTradejsProjectCwd(cwd);
|
|
1928
|
-
const cachedMatcher = tsconfigPathMatchersByCwd.get(projectRoot);
|
|
1929
|
-
if (cachedMatcher) {
|
|
1930
|
-
const resolved2 = cachedMatcher(moduleName);
|
|
1931
|
-
return resolved2 || null;
|
|
1932
|
-
}
|
|
1933
|
-
const tsconfigPathsModule = await import("tsconfig-paths");
|
|
1934
|
-
const loadConfig = tsconfigPathsModule.loadConfig;
|
|
1935
|
-
const createMatchPath = tsconfigPathsModule.createMatchPath;
|
|
1936
|
-
if (typeof loadConfig !== "function" || typeof createMatchPath !== "function") {
|
|
1937
|
-
return null;
|
|
1938
|
-
}
|
|
1939
|
-
const loadedConfig = loadConfig(projectRoot);
|
|
1940
|
-
if (loadedConfig.resultType !== "success") {
|
|
1941
|
-
return null;
|
|
1942
|
-
}
|
|
1943
|
-
const matchPath = createMatchPath(
|
|
1944
|
-
loadedConfig.absoluteBaseUrl,
|
|
1945
|
-
loadedConfig.paths
|
|
1946
|
-
);
|
|
1947
|
-
const matcher = (requestedModule) => matchPath(requestedModule, void 0, import_fs.default.existsSync, [
|
|
1948
|
-
".ts",
|
|
1949
|
-
".tsx",
|
|
1950
|
-
".mts",
|
|
1951
|
-
".cts",
|
|
1952
|
-
".js",
|
|
1953
|
-
".jsx",
|
|
1954
|
-
".mjs",
|
|
1955
|
-
".cjs",
|
|
1956
|
-
".json"
|
|
1957
|
-
]) || "";
|
|
1958
|
-
tsconfigPathMatchersByCwd.set(projectRoot, matcher);
|
|
1959
|
-
const resolved = matcher(moduleName);
|
|
1960
|
-
return resolved || null;
|
|
1961
|
-
};
|
|
1962
|
-
var toImportSpecifier = (moduleName) => {
|
|
1963
|
-
if (moduleName.startsWith("file://")) {
|
|
1964
|
-
return moduleName;
|
|
1601
|
+
var toImportSpecifier = (moduleName) => {
|
|
1602
|
+
if (moduleName.startsWith("file://")) {
|
|
1603
|
+
return moduleName;
|
|
1965
1604
|
}
|
|
1966
1605
|
if (import_path.default.isAbsolute(moduleName)) {
|
|
1967
1606
|
return (0, import_url.pathToFileURL)(moduleName).href;
|
|
@@ -2116,6 +1755,7 @@ var loadTradejsConfig = async (cwd = getTradejsProjectCwd()) => {
|
|
|
2116
1755
|
};
|
|
2117
1756
|
|
|
2118
1757
|
// src/strategy/manifests.ts
|
|
1758
|
+
var import_indicators = require("@tradejs/core/indicators");
|
|
2119
1759
|
var SHARED_STRATEGY_REGISTRY_KEY = "__tradejsNodeSharedStrategyRegistryV1__";
|
|
2120
1760
|
var sharedRegistryScope = globalThis;
|
|
2121
1761
|
var sharedStrategyRegistry = sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] ?? (sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] = {
|
|
@@ -2470,871 +2110,1810 @@ var strategies = new Proxy(
|
|
|
2470
2110
|
}
|
|
2471
2111
|
);
|
|
2472
2112
|
|
|
2473
|
-
// src/
|
|
2474
|
-
var
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
if (
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
if (
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2113
|
+
// src/strategyHelpers/tickerProfiles.ts
|
|
2114
|
+
var snapshots = /* @__PURE__ */ new Map();
|
|
2115
|
+
var resolveTickerProfileSource = (strategy) => {
|
|
2116
|
+
const declared = getStrategyManifest(strategy)?.contextRequirements?.tickerProfiles;
|
|
2117
|
+
if (declared) return declared;
|
|
2118
|
+
const provider = process.env.TICKER_PROFILES_PROVIDER?.trim();
|
|
2119
|
+
const universeId = process.env.TICKER_PROFILES_UNIVERSE?.trim();
|
|
2120
|
+
if (!provider && !universeId) return void 0;
|
|
2121
|
+
if (!provider || !universeId)
|
|
2122
|
+
throw new Error(
|
|
2123
|
+
"Set both TICKER_PROFILES_PROVIDER and TICKER_PROFILES_UNIVERSE"
|
|
2124
|
+
);
|
|
2125
|
+
return { provider, universeId };
|
|
2484
2126
|
};
|
|
2485
|
-
var
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2127
|
+
var loadTickerProfileContext = async ({
|
|
2128
|
+
projectRoot,
|
|
2129
|
+
provider,
|
|
2130
|
+
universeId,
|
|
2131
|
+
symbol,
|
|
2132
|
+
timestamp
|
|
2133
|
+
}) => {
|
|
2134
|
+
const asOf = (0, import_tickerProfiles2.tickerProfileMonth)(timestamp), key = JSON.stringify([projectRoot, provider, universeId, asOf]);
|
|
2135
|
+
let cached = snapshots.get(key);
|
|
2136
|
+
if (!cached || cached.expires < Date.now()) {
|
|
2137
|
+
const value = (0, import_tickerProfiles.readTickerProfiles)(projectRoot, provider, universeId, asOf).then((envelope) => {
|
|
2138
|
+
if (!envelope) return null;
|
|
2139
|
+
const s = envelope.snapshot;
|
|
2140
|
+
if (!(0, import_tickerProfiles2.isTickerProfileSnapshot)(s) || s.asOf !== asOf || s.provider !== provider || s.universeId !== universeId)
|
|
2141
|
+
throw new Error("Ticker profile identity/schema mismatch");
|
|
2142
|
+
const entry = snapshots.get(key);
|
|
2143
|
+
if (entry) entry.expires = Infinity;
|
|
2144
|
+
return new Map(
|
|
2145
|
+
s.profiles.map((p) => [
|
|
2146
|
+
p.symbol,
|
|
2147
|
+
Object.freeze({
|
|
2148
|
+
...p,
|
|
2149
|
+
definition: s.definition,
|
|
2150
|
+
provider,
|
|
2151
|
+
universeId,
|
|
2152
|
+
snapshotId: envelope.snapshotId
|
|
2153
|
+
})
|
|
2154
|
+
])
|
|
2498
2155
|
);
|
|
2156
|
+
}).catch((error) => {
|
|
2157
|
+
snapshots.delete(key);
|
|
2158
|
+
throw error;
|
|
2159
|
+
});
|
|
2160
|
+
cached = { expires: Date.now() + 6e4, value };
|
|
2161
|
+
snapshots.set(key, cached);
|
|
2162
|
+
if (snapshots.size > 24) {
|
|
2163
|
+
const oldest = snapshots.keys().next().value;
|
|
2164
|
+
if (oldest) snapshots.delete(oldest);
|
|
2499
2165
|
}
|
|
2500
|
-
return {
|
|
2501
|
-
id: inferredId,
|
|
2502
|
-
appliesTo: { universes: [inferredId] },
|
|
2503
|
-
marketDataRequirements: inferredId === "crypto" ? ["crypto.btcReference"] : [],
|
|
2504
|
-
...manifest?.mlAdapter ? {
|
|
2505
|
-
entryRuntimeDefaults: {
|
|
2506
|
-
ml: {
|
|
2507
|
-
modelKey: inferredId === "crypto" ? manifest.name : `${manifest.name}:tradfi`
|
|
2508
|
-
}
|
|
2509
|
-
}
|
|
2510
|
-
} : {}
|
|
2511
|
-
};
|
|
2512
2166
|
}
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2167
|
+
return (await cached.value)?.get(symbol);
|
|
2168
|
+
};
|
|
2169
|
+
var withTickerProfile = (baseContext, profile) => Object.defineProperty(
|
|
2170
|
+
Object.create(
|
|
2171
|
+
Object.getPrototypeOf(baseContext),
|
|
2172
|
+
Object.getOwnPropertyDescriptors(baseContext)
|
|
2173
|
+
),
|
|
2174
|
+
"tickerProfile",
|
|
2175
|
+
{ enumerable: true, configurable: true, value: profile }
|
|
2176
|
+
);
|
|
2177
|
+
var enrichSignalWithTickerProfile = async (signal) => {
|
|
2178
|
+
const source = resolveTickerProfileSource(signal.strategy);
|
|
2179
|
+
if (!source || signal.universe === "tradfi" || !signal.symbol.endsWith("USDT"))
|
|
2180
|
+
return;
|
|
2181
|
+
const profile = await loadTickerProfileContext({
|
|
2182
|
+
...source,
|
|
2183
|
+
projectRoot: getTradejsProjectCwd(),
|
|
2184
|
+
symbol: signal.symbol,
|
|
2185
|
+
timestamp: signal.timestamp
|
|
2186
|
+
});
|
|
2187
|
+
if (profile) {
|
|
2188
|
+
signal.additionalIndicators = {
|
|
2189
|
+
...signal.additionalIndicators,
|
|
2190
|
+
tickerProfile: profile
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
2193
|
+
};
|
|
2194
|
+
|
|
2195
|
+
// src/strategyRuntime.ts
|
|
2196
|
+
var import_jev6 = require("@tradejs/core/jev");
|
|
2197
|
+
|
|
2198
|
+
// src/jev.ts
|
|
2199
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
2200
|
+
var import_node_path = __toESM(require("path"));
|
|
2201
|
+
var import_jev4 = require("@tradejs/core/jev");
|
|
2202
|
+
var import_jev5 = require("@tradejs/infra/jev");
|
|
2203
|
+
var import_userSettings = require("@tradejs/infra/userSettings");
|
|
2204
|
+
init_jevInput();
|
|
2205
|
+
init_jevInput();
|
|
2206
|
+
init_jevTraining();
|
|
2207
|
+
var resolveJevProvider = async (userName) => {
|
|
2208
|
+
const settings = await (0, import_userSettings.getUserSettings)(userName);
|
|
2209
|
+
const provider = {
|
|
2210
|
+
endpoint: settings.JEV_API_ENDPOINT || import_jev4.JEV_PROVIDERS[0].endpoint,
|
|
2211
|
+
model: settings.JEV_MODEL || import_jev4.JEV_PROVIDERS[0].model
|
|
2212
|
+
};
|
|
2213
|
+
(0, import_jev4.parseJevConfig)({ source: "provider", mode: "observe", provider });
|
|
2214
|
+
if (!settings.JEV_API_KEY)
|
|
2215
|
+
throw new Error("Configure the Jev API key in Account settings");
|
|
2216
|
+
return provider;
|
|
2217
|
+
};
|
|
2218
|
+
var emptyScores = () => Object.fromEntries(import_jev4.JEV_DIMENSIONS.map((key) => [key, null]));
|
|
2219
|
+
var pending = /* @__PURE__ */ new Map();
|
|
2220
|
+
var pendingResponses = /* @__PURE__ */ new Map();
|
|
2221
|
+
var evaluateJevInput = async ({
|
|
2222
|
+
input,
|
|
2223
|
+
config,
|
|
2224
|
+
userName,
|
|
2225
|
+
projectRoot
|
|
2226
|
+
}) => {
|
|
2227
|
+
config = (0, import_jev4.parseJevConfig)(config);
|
|
2228
|
+
const inputHash = (0, import_jev5.jevHash)(input);
|
|
2229
|
+
const questions = questionsForJevInput(input);
|
|
2230
|
+
const questionsHash = (0, import_jev5.jevHash)(questions);
|
|
2231
|
+
const base = {
|
|
2232
|
+
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
2233
|
+
schema: "tradejs-signal-assessment/v5",
|
|
2234
|
+
source: config.source,
|
|
2235
|
+
mode: config.mode,
|
|
2236
|
+
inputHash,
|
|
2237
|
+
questionsHash,
|
|
2238
|
+
geometryStatus: input.geometryStatus,
|
|
2239
|
+
levelsValid: typeof input.features["signal.validLevels"] === "boolean" ? input.features["signal.validLevels"] : null
|
|
2240
|
+
};
|
|
2241
|
+
if (!input.questions.length)
|
|
2242
|
+
return {
|
|
2243
|
+
assessment: {
|
|
2244
|
+
...base,
|
|
2245
|
+
status: "unavailable",
|
|
2246
|
+
model: "",
|
|
2247
|
+
scores: emptyScores(),
|
|
2248
|
+
confidence: emptyScores()
|
|
2249
|
+
}
|
|
2250
|
+
};
|
|
2251
|
+
if (config.source === "local") {
|
|
2252
|
+
const contents = await import_promises.default.readFile(
|
|
2253
|
+
import_node_path.default.resolve(projectRoot, config.modelFile),
|
|
2254
|
+
"utf8"
|
|
2255
|
+
);
|
|
2256
|
+
if ((0, import_jev5.jevFileHash)(contents) !== config.modelSha256)
|
|
2257
|
+
throw new Error("Local Jev gate checksum mismatch");
|
|
2258
|
+
const { validateJevGateModel: validateJevGateModel2 } = await Promise.resolve().then(() => (init_jevTraining(), jevTraining_exports));
|
|
2259
|
+
const model = validateJevGateModel2(JSON.parse(contents));
|
|
2260
|
+
if (model.strategy !== input.strategy || model.questionsHash !== (0, import_jev5.jevHash)(JEV_QUESTIONS))
|
|
2261
|
+
throw new Error("Local Jev gate strategy or question version mismatch");
|
|
2262
|
+
const scores = Object.fromEntries(
|
|
2263
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
2264
|
+
key,
|
|
2265
|
+
input.questions.includes(key) && model.trees[key] ? (0, import_jev4.predictJevTree)(model.trees[key], input.features) : null
|
|
2266
|
+
])
|
|
2267
|
+
);
|
|
2268
|
+
return {
|
|
2269
|
+
assessment: {
|
|
2270
|
+
...base,
|
|
2271
|
+
status: "available",
|
|
2272
|
+
model: config.modelSha256,
|
|
2273
|
+
scores,
|
|
2274
|
+
confidence: emptyScores()
|
|
2275
|
+
}
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2278
|
+
const provider = config.provider ?? await resolveJevProvider(userName);
|
|
2279
|
+
const id = (0, import_jev5.jevHash)({ inputHash, questionsHash, provider });
|
|
2280
|
+
const dir = import_node_path.default.resolve(projectRoot, config.recordsDir ?? "data/ai/jev");
|
|
2281
|
+
const file = import_node_path.default.join(dir, "records", `${id}.json`);
|
|
2282
|
+
const getRecord = async () => {
|
|
2283
|
+
const existing = await (0, import_jev5.readJevArtifact)(file);
|
|
2284
|
+
if (existing) {
|
|
2285
|
+
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))
|
|
2286
|
+
throw new Error("Jev recording identity mismatch");
|
|
2287
|
+
(0, import_jev5.validateJevResponse)(existing.response, input.questions);
|
|
2288
|
+
for (const key of import_jev4.JEV_DIMENSIONS) {
|
|
2289
|
+
const expected = input.questions.includes(key) ? existing.response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null;
|
|
2290
|
+
if (existing.scores[key] !== expected)
|
|
2291
|
+
throw new Error("Jev recording scores do not match the response");
|
|
2292
|
+
}
|
|
2293
|
+
return existing;
|
|
2519
2294
|
}
|
|
2520
|
-
if (
|
|
2295
|
+
if (config.source === "recorded")
|
|
2521
2296
|
throw new Error(
|
|
2522
|
-
`
|
|
2297
|
+
`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.`
|
|
2298
|
+
);
|
|
2299
|
+
const settings = await (0, import_userSettings.getUserSettings)(userName);
|
|
2300
|
+
if (!settings.JEV_API_KEY)
|
|
2301
|
+
throw new Error("Configure the Jev API key in Account settings");
|
|
2302
|
+
const apiKey = settings.JEV_API_KEY;
|
|
2303
|
+
const configuredProvider = await resolveJevProvider(userName);
|
|
2304
|
+
if (configuredProvider.endpoint !== provider.endpoint)
|
|
2305
|
+
throw new Error(
|
|
2306
|
+
"Jev account endpoint changed; update the frozen provider configuration"
|
|
2523
2307
|
);
|
|
2308
|
+
const started = Date.now();
|
|
2309
|
+
const state = stateForJevInput(input);
|
|
2310
|
+
const responseId = (0, import_jev5.jevHash)({
|
|
2311
|
+
state,
|
|
2312
|
+
questionsHash,
|
|
2313
|
+
provider,
|
|
2314
|
+
user: (0, import_jev5.jevHash)(userName)
|
|
2315
|
+
});
|
|
2316
|
+
const responseFile = import_node_path.default.join(dir, "responses", `${responseId}.json`);
|
|
2317
|
+
let responsePromise = pendingResponses.get(responseFile);
|
|
2318
|
+
if (!responsePromise) {
|
|
2319
|
+
responsePromise = (async () => {
|
|
2320
|
+
const saved = await (0, import_jev5.readJevArtifact)(responseFile);
|
|
2321
|
+
if (saved) return (0, import_jev5.validateJevResponse)(saved, input.questions);
|
|
2322
|
+
const fresh = await (0, import_jev5.requestJev)(provider, apiKey, state, questions);
|
|
2323
|
+
await (0, import_jev5.writeJevArtifact)(responseFile, fresh);
|
|
2324
|
+
return fresh;
|
|
2325
|
+
})();
|
|
2326
|
+
pendingResponses.set(responseFile, responsePromise);
|
|
2524
2327
|
}
|
|
2525
|
-
|
|
2328
|
+
let response;
|
|
2329
|
+
try {
|
|
2330
|
+
response = await responsePromise;
|
|
2331
|
+
} finally {
|
|
2332
|
+
pendingResponses.delete(responseFile);
|
|
2333
|
+
}
|
|
2334
|
+
const scores = Object.fromEntries(
|
|
2335
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
2336
|
+
key,
|
|
2337
|
+
input.questions.includes(key) ? response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null
|
|
2338
|
+
])
|
|
2339
|
+
);
|
|
2340
|
+
const value = {
|
|
2341
|
+
schema: "tradejs-jev-record/v4",
|
|
2342
|
+
id,
|
|
2343
|
+
inputHash,
|
|
2344
|
+
questionsHash,
|
|
2345
|
+
provider,
|
|
2346
|
+
input,
|
|
2347
|
+
response,
|
|
2348
|
+
scores,
|
|
2349
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2350
|
+
elapsedMs: Date.now() - started
|
|
2351
|
+
};
|
|
2352
|
+
await (0, import_jev5.writeJevArtifact)(file, value);
|
|
2353
|
+
return await (0, import_jev5.readJevArtifact)(file);
|
|
2354
|
+
};
|
|
2355
|
+
const pendingKey = `${file}:${(0, import_jev5.jevHash)(userName)}`;
|
|
2356
|
+
let promise = pending.get(pendingKey);
|
|
2357
|
+
if (!promise) {
|
|
2358
|
+
promise = getRecord();
|
|
2359
|
+
pending.set(pendingKey, promise);
|
|
2526
2360
|
}
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
(
|
|
2532
|
-
);
|
|
2533
|
-
return defaultProfile ?? matching[0];
|
|
2534
|
-
};
|
|
2535
|
-
var getStrategyProfileAiAdapter = (manifest, profileId) => manifest?.policyProfiles?.find(({ id }) => id === profileId)?.aiAdapter ?? manifest?.aiAdapter;
|
|
2536
|
-
var getStrategyProfileMlAdapter = (manifest, profileId) => manifest?.policyProfiles?.find(({ id }) => id === profileId)?.mlAdapter ?? manifest?.mlAdapter;
|
|
2537
|
-
|
|
2538
|
-
// src/strategyAdapters/ai.ts
|
|
2539
|
-
var toRecord2 = (value) => {
|
|
2540
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2541
|
-
return {};
|
|
2361
|
+
let record;
|
|
2362
|
+
try {
|
|
2363
|
+
record = await promise;
|
|
2364
|
+
} finally {
|
|
2365
|
+
pending.delete(pendingKey);
|
|
2542
2366
|
}
|
|
2543
|
-
return value;
|
|
2544
|
-
};
|
|
2545
|
-
var buildBaseAiPayload = (signal) => {
|
|
2546
|
-
const { jevEvidence: _jevEvidence, ...signalAdditionalIndicators } = toRecord2(
|
|
2547
|
-
signal.additionalIndicators
|
|
2548
|
-
);
|
|
2549
|
-
const additionalIndicators = {
|
|
2550
|
-
...signalAdditionalIndicators,
|
|
2551
|
-
marketContext: buildAiMarketContext(signal)
|
|
2552
|
-
};
|
|
2553
2367
|
return {
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
indicators: buildCompactAiIndicatorsSnapshot(signal.indicators),
|
|
2569
|
-
additionalIndicators: trimSeriesDeep(additionalIndicators)
|
|
2368
|
+
record,
|
|
2369
|
+
assessment: {
|
|
2370
|
+
...base,
|
|
2371
|
+
status: "available",
|
|
2372
|
+
recordId: id,
|
|
2373
|
+
model: record.response.model,
|
|
2374
|
+
scores: record.scores,
|
|
2375
|
+
confidence: Object.fromEntries(
|
|
2376
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
2377
|
+
key,
|
|
2378
|
+
record.response.answers[key]?.confidence ?? null
|
|
2379
|
+
])
|
|
2380
|
+
)
|
|
2381
|
+
}
|
|
2570
2382
|
};
|
|
2571
2383
|
};
|
|
2572
|
-
var
|
|
2573
|
-
var getStrategyAiAdapter = (strategy, profileId) => getStrategyProfileAiAdapter(getStrategyManifest(strategy), profileId) ?? defaultAiAdapter;
|
|
2574
|
-
var getSignalAiAdapter = (signal) => getStrategyAiAdapter(signal.strategy, signal.policyProfileId);
|
|
2575
|
-
var buildAiPayloadByStrategy = (signal) => {
|
|
2576
|
-
const basePayload = buildBaseAiPayload(signal);
|
|
2577
|
-
const adapter = getSignalAiAdapter(signal);
|
|
2578
|
-
return adapter.buildPayload?.({ signal, basePayload }) ?? basePayload;
|
|
2579
|
-
};
|
|
2580
|
-
var buildAiSystemPromptAddonByStrategy = (signal) => getSignalAiAdapter(signal).buildSystemPromptAddon?.({ signal }) ?? "";
|
|
2581
|
-
var buildAiHumanPromptAddonByStrategy = (signal, payload) => getSignalAiAdapter(signal).buildHumanPromptAddon?.({
|
|
2582
|
-
signal,
|
|
2583
|
-
payload
|
|
2584
|
-
}) ?? "";
|
|
2585
|
-
var postProcessAiAnalysisByStrategy = (signal, analysis, payload = buildAiPayloadByStrategy(signal)) => getSignalAiAdapter(signal).postProcessAnalysis?.({
|
|
2384
|
+
var assessSignalWithJev = async ({
|
|
2586
2385
|
signal,
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2386
|
+
config,
|
|
2387
|
+
userName,
|
|
2388
|
+
projectRoot,
|
|
2389
|
+
strict,
|
|
2390
|
+
scope = "runtime"
|
|
2391
|
+
}) => {
|
|
2392
|
+
let assessment;
|
|
2393
|
+
try {
|
|
2394
|
+
const input = buildJevInput(signal);
|
|
2395
|
+
({ assessment } = await evaluateJevInput({
|
|
2396
|
+
input,
|
|
2397
|
+
config,
|
|
2398
|
+
userName,
|
|
2399
|
+
projectRoot
|
|
2400
|
+
}));
|
|
2401
|
+
} catch (error) {
|
|
2402
|
+
if (strict) throw error;
|
|
2403
|
+
assessment = {
|
|
2404
|
+
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
2405
|
+
schema: "tradejs-signal-assessment/v5",
|
|
2406
|
+
source: config.source,
|
|
2407
|
+
mode: config.mode,
|
|
2408
|
+
status: "unavailable",
|
|
2409
|
+
inputHash: "",
|
|
2410
|
+
questionsHash: "",
|
|
2411
|
+
geometryStatus: "absent",
|
|
2412
|
+
levelsValid: null,
|
|
2413
|
+
model: config.provider?.model ?? config.modelSha256 ?? "",
|
|
2414
|
+
scores: emptyScores(),
|
|
2415
|
+
confidence: emptyScores()
|
|
2416
|
+
};
|
|
2417
|
+
}
|
|
2418
|
+
signal.assessment = assessment;
|
|
2419
|
+
signal.additionalIndicators = {
|
|
2420
|
+
...signal.additionalIndicators,
|
|
2421
|
+
jev: (0, import_jev4.jevAssessmentFeatures)(assessment)
|
|
2422
|
+
};
|
|
2423
|
+
const identity = {
|
|
2424
|
+
signalId: signal.signalId,
|
|
2425
|
+
strategy: signal.strategy,
|
|
2426
|
+
symbol: signal.symbol,
|
|
2427
|
+
timestamp: signal.timestamp,
|
|
2428
|
+
scope,
|
|
2429
|
+
user: (0, import_jev5.jevHash)(userName),
|
|
2430
|
+
config,
|
|
2431
|
+
assessment
|
|
2432
|
+
};
|
|
2433
|
+
await (0, import_jev5.writeJevArtifact)(
|
|
2434
|
+
import_node_path.default.resolve(
|
|
2435
|
+
projectRoot,
|
|
2436
|
+
config.recordsDir ?? "data/ai/jev",
|
|
2437
|
+
"decisions",
|
|
2438
|
+
`${(0, import_jev5.jevHash)(identity)}.json`
|
|
2439
|
+
),
|
|
2440
|
+
identity
|
|
2441
|
+
);
|
|
2442
|
+
return assessment;
|
|
2598
2443
|
};
|
|
2599
2444
|
|
|
2600
|
-
// src/
|
|
2601
|
-
var
|
|
2602
|
-
var
|
|
2603
|
-
var
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
var
|
|
2607
|
-
var
|
|
2608
|
-
var
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2445
|
+
// src/strategyRuntime.ts
|
|
2446
|
+
var import_constants5 = require("@tradejs/core/constants");
|
|
2447
|
+
var import_strategies6 = require("@tradejs/core/strategies");
|
|
2448
|
+
var import_logger11 = require("@tradejs/infra/logger");
|
|
2449
|
+
|
|
2450
|
+
// src/strategyHelpers/runtime.ts
|
|
2451
|
+
var import_logger9 = require("@tradejs/infra/logger");
|
|
2452
|
+
var import_constants3 = require("@tradejs/core/constants");
|
|
2453
|
+
var import_ml2 = require("@tradejs/infra/ml");
|
|
2454
|
+
|
|
2455
|
+
// src/strategy/entrySubmissionValidation.ts
|
|
2456
|
+
var import_logger3 = require("@tradejs/infra/logger");
|
|
2457
|
+
|
|
2458
|
+
// src/strategy/entryExecutionValidation.ts
|
|
2459
|
+
var EntrySubmissionValidationError = class extends Error {
|
|
2460
|
+
constructor() {
|
|
2461
|
+
super(...arguments);
|
|
2462
|
+
this.name = "EntrySubmissionValidationError";
|
|
2617
2463
|
}
|
|
2618
|
-
return String(content ?? "");
|
|
2619
2464
|
};
|
|
2620
|
-
var
|
|
2621
|
-
const
|
|
2622
|
-
const
|
|
2623
|
-
|
|
2624
|
-
);
|
|
2625
|
-
const
|
|
2626
|
-
|
|
2627
|
-
);
|
|
2628
|
-
|
|
2629
|
-
|
|
2465
|
+
var resolveEntryExecutionLimits = (config, direction) => {
|
|
2466
|
+
const side = config[direction];
|
|
2467
|
+
const minimum = side && typeof side === "object" && "minRiskRatio" in side ? side.minRiskRatio : void 0;
|
|
2468
|
+
const maximum = config.MAX_LOSS_VALUE;
|
|
2469
|
+
if (minimum == null && maximum == null) return void 0;
|
|
2470
|
+
const minimumNetRiskRatio = minimum == null ? void 0 : Number(minimum);
|
|
2471
|
+
const maxLossValue = maximum == null ? void 0 : Number(maximum);
|
|
2472
|
+
const feeRate = Number(config.RISK_FEE_RATE ?? 0);
|
|
2473
|
+
const slippage = Number(config.RISK_SLIPPAGE_BPS ?? 0);
|
|
2474
|
+
const impact = Number(config.RISK_MARKET_IMPACT_BPS ?? 0);
|
|
2475
|
+
const slippageBps = slippage + impact;
|
|
2476
|
+
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)
|
|
2477
|
+
throw new Error("INVALID_ENTRY_EXECUTION_LIMITS");
|
|
2478
|
+
return { minimumNetRiskRatio, maxLossValue, feeRate, slippageBps };
|
|
2630
2479
|
};
|
|
2631
|
-
var
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2480
|
+
var validateEntryExecution = ({
|
|
2481
|
+
direction,
|
|
2482
|
+
price,
|
|
2483
|
+
qty,
|
|
2484
|
+
stopLossPrice,
|
|
2485
|
+
takeProfitPrice,
|
|
2486
|
+
limits
|
|
2487
|
+
}) => {
|
|
2488
|
+
if (!Number.isFinite(price) || price <= 0 || !Number.isFinite(qty) || qty <= 0 || stopLossPrice == null || !Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || !Number.isFinite(takeProfitPrice) || takeProfitPrice <= 0)
|
|
2489
|
+
throw new EntrySubmissionValidationError(
|
|
2490
|
+
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
2491
|
+
);
|
|
2492
|
+
const side = direction === "LONG" ? 1 : -1;
|
|
2493
|
+
if (side * (price - stopLossPrice) <= 0 || side * (takeProfitPrice - price) <= 0)
|
|
2494
|
+
throw new EntrySubmissionValidationError(
|
|
2495
|
+
"INVALID_ENTRY_PROTECTION_AT_SUBMISSION"
|
|
2496
|
+
);
|
|
2497
|
+
const slippage = limits.slippageBps / 1e4;
|
|
2498
|
+
const cost = (value, sign) => value * slippage + value * (1 + sign * slippage) * limits.feeRate;
|
|
2499
|
+
const entryCost = cost(price, side);
|
|
2500
|
+
const lossPerUnit = Math.abs(price - stopLossPrice) + entryCost + cost(stopLossPrice, -side);
|
|
2501
|
+
const rewardPerUnit = Math.max(
|
|
2502
|
+
0,
|
|
2503
|
+
Math.abs(takeProfitPrice - price) - entryCost - cost(takeProfitPrice, -side)
|
|
2504
|
+
);
|
|
2505
|
+
const netRiskRatio = rewardPerUnit / lossPerUnit;
|
|
2506
|
+
const plannedLossValue = qty * lossPerUnit;
|
|
2507
|
+
if (!Number.isFinite(netRiskRatio) || !Number.isFinite(plannedLossValue))
|
|
2508
|
+
throw new EntrySubmissionValidationError(
|
|
2509
|
+
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
2510
|
+
);
|
|
2511
|
+
const belowMinimumNetRiskRatio = limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio;
|
|
2512
|
+
const riskExceeded = limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9);
|
|
2513
|
+
return {
|
|
2514
|
+
netRiskRatio,
|
|
2515
|
+
plannedLossValue,
|
|
2516
|
+
riskExceeded,
|
|
2517
|
+
belowMinimumNetRiskRatio
|
|
2518
|
+
};
|
|
2637
2519
|
};
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2520
|
+
|
|
2521
|
+
// src/strategy/entrySubmissionValidation.ts
|
|
2522
|
+
var MAX_ENTRY_QUOTE_AGE_MS = 5e3;
|
|
2523
|
+
var toFiniteNumberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
2524
|
+
var getOrderArrivalSnapshot = async ({
|
|
2525
|
+
connector,
|
|
2526
|
+
symbol,
|
|
2527
|
+
requireTimestamp = false
|
|
2528
|
+
}) => {
|
|
2529
|
+
if (typeof connector.getTopOfBookTicker !== "function") {
|
|
2530
|
+
return {
|
|
2531
|
+
arrivalSnapshotTime: Date.now(),
|
|
2532
|
+
arrivalSource: "unavailable",
|
|
2533
|
+
bid: null,
|
|
2534
|
+
ask: null,
|
|
2535
|
+
arrivalMid: null,
|
|
2536
|
+
spreadBps: null
|
|
2537
|
+
};
|
|
2538
|
+
}
|
|
2642
2539
|
try {
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2540
|
+
const ticker = await connector.getTopOfBookTicker(symbol);
|
|
2541
|
+
const arrivalSnapshotTime = toFiniteNumberOrNull(ticker?.timestamp);
|
|
2542
|
+
const timestampUnavailable = requireTimestamp && arrivalSnapshotTime == null;
|
|
2543
|
+
const bid = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.bidPrice);
|
|
2544
|
+
const ask = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.askPrice);
|
|
2545
|
+
const arrivalMid = bid != null && ask != null ? (bid + ask) / 2 : null;
|
|
2546
|
+
const spreadBps = bid != null && ask != null && arrivalMid != null && arrivalMid > 0 ? (ask - bid) / arrivalMid * 1e4 : null;
|
|
2547
|
+
return {
|
|
2548
|
+
arrivalSnapshotTime: arrivalSnapshotTime ?? Date.now(),
|
|
2549
|
+
arrivalSource: "top_of_book",
|
|
2550
|
+
bid,
|
|
2551
|
+
ask,
|
|
2552
|
+
arrivalMid,
|
|
2553
|
+
spreadBps
|
|
2554
|
+
};
|
|
2555
|
+
} catch (error) {
|
|
2556
|
+
import_logger3.logger.warn(
|
|
2557
|
+
"runtime order arrival snapshot failed: %s %s",
|
|
2558
|
+
symbol,
|
|
2559
|
+
error?.message || String(error)
|
|
2560
|
+
);
|
|
2561
|
+
return {
|
|
2562
|
+
arrivalSnapshotTime: Date.now(),
|
|
2563
|
+
arrivalSource: "top_of_book_error",
|
|
2564
|
+
bid: null,
|
|
2565
|
+
ask: null,
|
|
2566
|
+
arrivalMid: null,
|
|
2567
|
+
spreadBps: null
|
|
2568
|
+
};
|
|
2646
2569
|
}
|
|
2647
|
-
return hostname.toLowerCase().includes("openrouter") ? { provider: { ignore: ["azure"] } } : {};
|
|
2648
2570
|
};
|
|
2649
|
-
var
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
});
|
|
2663
|
-
settingsPromise.catch(() => userSettingsCache.delete(userName));
|
|
2664
|
-
userSettingsCache.set(userName, settingsPromise);
|
|
2571
|
+
var validateEntryProtectionAtArrival = ({
|
|
2572
|
+
direction,
|
|
2573
|
+
signalPrice,
|
|
2574
|
+
bid,
|
|
2575
|
+
ask,
|
|
2576
|
+
arrivalMid,
|
|
2577
|
+
takeProfits,
|
|
2578
|
+
stopLossPrice
|
|
2579
|
+
}) => {
|
|
2580
|
+
const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
|
|
2581
|
+
const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
|
|
2582
|
+
if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
|
|
2583
|
+
throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
|
|
2665
2584
|
}
|
|
2666
|
-
const
|
|
2667
|
-
|
|
2668
|
-
|
|
2585
|
+
for (const takeProfit of takeProfits) {
|
|
2586
|
+
if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
|
|
2587
|
+
throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
|
|
2588
|
+
}
|
|
2669
2589
|
}
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
var getAiModel = async (userName = "root", requestedModelName, temperature = 0.2) => {
|
|
2673
|
-
const settings = await getAiUserSettings(userName);
|
|
2674
|
-
const modelName = resolveAiModelName(settings, requestedModelName);
|
|
2675
|
-
const cacheKey = getAiModelCacheKey(userName, modelName, temperature);
|
|
2676
|
-
let modelPromise = aiModelCache.get(cacheKey);
|
|
2677
|
-
if (!modelPromise) {
|
|
2678
|
-
modelPromise = import("@langchain/openai").then(({ ChatOpenAI }) => {
|
|
2679
|
-
const modelKwargs = getOpenRouterModelKwargs(settings.AI_API_ENDPOINT);
|
|
2680
|
-
return new ChatOpenAI({
|
|
2681
|
-
temperature,
|
|
2682
|
-
modelName,
|
|
2683
|
-
apiKey: settings.AI_API_KEY,
|
|
2684
|
-
...Object.keys(modelKwargs).length ? { modelKwargs } : {},
|
|
2685
|
-
configuration: {
|
|
2686
|
-
baseURL: settings.AI_API_ENDPOINT,
|
|
2687
|
-
defaultHeaders: {
|
|
2688
|
-
"HTTP-Referer": "https://tradejs.dev",
|
|
2689
|
-
"X-Title": "Inv"
|
|
2690
|
-
}
|
|
2691
|
-
}
|
|
2692
|
-
});
|
|
2693
|
-
});
|
|
2694
|
-
modelPromise.catch(() => aiModelCache.delete(cacheKey));
|
|
2695
|
-
aiModelCache.set(cacheKey, modelPromise);
|
|
2696
|
-
}
|
|
2697
|
-
try {
|
|
2698
|
-
return await modelPromise;
|
|
2699
|
-
} catch (error) {
|
|
2700
|
-
aiModelCache.delete(cacheKey);
|
|
2701
|
-
userSettingsCache.delete(userName);
|
|
2702
|
-
throw error;
|
|
2590
|
+
if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
|
|
2591
|
+
throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
|
|
2703
2592
|
}
|
|
2704
2593
|
};
|
|
2705
|
-
var
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
)
|
|
2719
|
-
)
|
|
2720
|
-
|
|
2721
|
-
const
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2594
|
+
var createEntrySubmissionValidation = ({
|
|
2595
|
+
connector,
|
|
2596
|
+
symbol,
|
|
2597
|
+
direction,
|
|
2598
|
+
takeProfits,
|
|
2599
|
+
stopLossPrice,
|
|
2600
|
+
limits,
|
|
2601
|
+
onValidated
|
|
2602
|
+
}) => {
|
|
2603
|
+
if (!limits) return void 0;
|
|
2604
|
+
if (!connector.capabilities?.orderSubmissionValidation)
|
|
2605
|
+
throw new EntrySubmissionValidationError(
|
|
2606
|
+
"ENTRY_SUBMISSION_VALIDATION_UNSUPPORTED"
|
|
2607
|
+
);
|
|
2608
|
+
return async ({ qty, executionPrice }) => {
|
|
2609
|
+
const simulated = Reflect.get(connector, "__tradejsTestConnector") === true;
|
|
2610
|
+
const snapshot2 = simulated && executionPrice != null ? {
|
|
2611
|
+
arrivalSnapshotTime: Date.now(),
|
|
2612
|
+
arrivalSource: "simulation",
|
|
2613
|
+
bid: executionPrice,
|
|
2614
|
+
ask: executionPrice,
|
|
2615
|
+
arrivalMid: executionPrice,
|
|
2616
|
+
spreadBps: 0
|
|
2617
|
+
} : await getOrderArrivalSnapshot({
|
|
2618
|
+
connector,
|
|
2619
|
+
symbol,
|
|
2620
|
+
requireTimestamp: true
|
|
2621
|
+
});
|
|
2622
|
+
const age = Date.now() - snapshot2.arrivalSnapshotTime;
|
|
2623
|
+
if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
|
|
2624
|
+
throw new EntrySubmissionValidationError(
|
|
2625
|
+
"ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
|
|
2626
|
+
);
|
|
2627
|
+
if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
|
|
2628
|
+
throw new EntrySubmissionValidationError(
|
|
2629
|
+
"ENTRY_QUOTE_STALE_AT_SUBMISSION"
|
|
2630
|
+
);
|
|
2631
|
+
const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
|
|
2632
|
+
validateEntryProtectionAtArrival({
|
|
2633
|
+
direction,
|
|
2634
|
+
signalPrice: price,
|
|
2635
|
+
bid: snapshot2.bid,
|
|
2636
|
+
ask: snapshot2.ask,
|
|
2637
|
+
arrivalMid: snapshot2.arrivalMid,
|
|
2638
|
+
takeProfits,
|
|
2639
|
+
stopLossPrice
|
|
2640
|
+
});
|
|
2641
|
+
const prices = takeProfits.map((tp) => tp.price);
|
|
2642
|
+
const economics2 = validateEntryExecution({
|
|
2643
|
+
direction,
|
|
2644
|
+
price,
|
|
2645
|
+
qty,
|
|
2646
|
+
stopLossPrice,
|
|
2647
|
+
takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
|
|
2648
|
+
limits
|
|
2649
|
+
});
|
|
2650
|
+
if (economics2.belowMinimumNetRiskRatio) {
|
|
2651
|
+
import_logger3.logger.warn(
|
|
2652
|
+
"entry submission warning: %s %s ENTRY_RR_BELOW_MIN_AT_SUBMISSION (RR %s, threshold %s)",
|
|
2653
|
+
symbol,
|
|
2654
|
+
direction,
|
|
2655
|
+
economics2.netRiskRatio,
|
|
2656
|
+
limits.minimumNetRiskRatio
|
|
2657
|
+
);
|
|
2725
2658
|
}
|
|
2726
|
-
|
|
2727
|
-
}
|
|
2728
|
-
throw getAiInvocationError(error);
|
|
2729
|
-
}
|
|
2659
|
+
onValidated?.(snapshot2, economics2);
|
|
2660
|
+
};
|
|
2730
2661
|
};
|
|
2731
|
-
var invokeAiPromptChat = (options) => invokeAiChatWithUserMessageEncoding(options, () => "text-block");
|
|
2732
2662
|
|
|
2733
2663
|
// src/ai.ts
|
|
2734
|
-
var
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2664
|
+
var import_aiLanguages2 = require("@tradejs/core/aiLanguages");
|
|
2665
|
+
var import_redis3 = require("@tradejs/infra/redis");
|
|
2666
|
+
|
|
2667
|
+
// src/strategyAdapters/ai.ts
|
|
2668
|
+
var import_indicators2 = require("@tradejs/core/indicators");
|
|
2669
|
+
var import_aiFeatures = require("@tradejs/core/aiFeatures");
|
|
2670
|
+
|
|
2671
|
+
// src/aiShared.ts
|
|
2672
|
+
var MAX_AI_SERIES_POINTS = 5;
|
|
2673
|
+
var COMPACT_INDICATORS_SNAPSHOT_SYMBOL = /* @__PURE__ */ Symbol.for(
|
|
2674
|
+
"tradejs.indicators.compactSnapshot"
|
|
2675
|
+
);
|
|
2676
|
+
var COMPACT_INDICATORS_SNAPSHOT_KEY = "__tradejsCompactIndicatorsSnapshot";
|
|
2677
|
+
var trimSeriesDeep = (value) => {
|
|
2678
|
+
if (Array.isArray(value)) {
|
|
2679
|
+
const trimmed = value.slice(-MAX_AI_SERIES_POINTS);
|
|
2680
|
+
const isMatrix = trimmed.every((item) => Array.isArray(item));
|
|
2681
|
+
if (isMatrix) {
|
|
2682
|
+
return trimmed;
|
|
2683
|
+
}
|
|
2684
|
+
return trimmed.map(
|
|
2685
|
+
(item) => item && typeof item === "object" ? trimSeriesDeep(item) : item
|
|
2686
|
+
);
|
|
2687
|
+
}
|
|
2688
|
+
if (value && typeof value === "object") {
|
|
2689
|
+
return Object.fromEntries(
|
|
2690
|
+
Object.entries(value).map(([key, nested]) => [
|
|
2691
|
+
key,
|
|
2692
|
+
trimSeriesDeep(nested)
|
|
2693
|
+
])
|
|
2694
|
+
);
|
|
2744
2695
|
}
|
|
2696
|
+
return value;
|
|
2745
2697
|
};
|
|
2746
|
-
var
|
|
2747
|
-
const
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
const parsed = Number(value);
|
|
2753
|
-
if (Number.isFinite(parsed)) return parsed;
|
|
2754
|
-
}
|
|
2755
|
-
return null;
|
|
2756
|
-
};
|
|
2757
|
-
const toText = (value) => typeof value === "string" ? value.slice(0, 400) : void 0;
|
|
2758
|
-
return {
|
|
2759
|
-
direction,
|
|
2760
|
-
quality: qualityNum,
|
|
2761
|
-
needRetest: Boolean(raw?.needRetest),
|
|
2762
|
-
retestPrice: toNumberOrNull(raw?.retestPrice),
|
|
2763
|
-
takeProfitPrice: toNumberOrNull(raw?.takeProfitPrice),
|
|
2764
|
-
stopLossPrice: toNumberOrNull(raw?.stopLossPrice),
|
|
2765
|
-
setup: toText(raw?.setup),
|
|
2766
|
-
confirmations: toText(raw?.confirmations),
|
|
2767
|
-
btcContext: toText(raw?.btcContext),
|
|
2768
|
-
retestPlan: toText(raw?.retestPlan),
|
|
2769
|
-
riskLevels: toText(raw?.riskLevels),
|
|
2770
|
-
qualityReason: toText(raw?.qualityReason),
|
|
2771
|
-
triggerInvalidation: toText(raw?.triggerInvalidation),
|
|
2772
|
-
comment: typeof raw?.comment === "string" ? raw.comment.slice(0, 1024) : ""
|
|
2773
|
-
};
|
|
2698
|
+
var buildCompactAiIndicatorsSnapshot = (value) => {
|
|
2699
|
+
const compactSnapshot = value && typeof value === "object" ? value[COMPACT_INDICATORS_SNAPSHOT_SYMBOL] ?? value[COMPACT_INDICATORS_SNAPSHOT_KEY] : void 0;
|
|
2700
|
+
if (typeof compactSnapshot === "function") {
|
|
2701
|
+
return compactSnapshot({ limit: MAX_AI_SERIES_POINTS });
|
|
2702
|
+
}
|
|
2703
|
+
return trimSeriesDeep(value);
|
|
2774
2704
|
};
|
|
2775
|
-
|
|
2705
|
+
|
|
2706
|
+
// src/aiMarketContext.ts
|
|
2707
|
+
var toRecord = (value) => {
|
|
2776
2708
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2777
2709
|
return null;
|
|
2778
2710
|
}
|
|
2779
2711
|
return value;
|
|
2780
2712
|
};
|
|
2781
|
-
var
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2713
|
+
var toFiniteNumber = (value) => {
|
|
2714
|
+
const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
|
|
2715
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
2716
|
+
};
|
|
2717
|
+
var roundTo = (value, decimals) => {
|
|
2718
|
+
const factor = 10 ** decimals;
|
|
2719
|
+
return Math.round(value * factor) / factor;
|
|
2720
|
+
};
|
|
2721
|
+
var buildMissingSpreadContext = () => ({
|
|
2722
|
+
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2723
|
+
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2724
|
+
available: false,
|
|
2725
|
+
value: null,
|
|
2726
|
+
zScore: null,
|
|
2727
|
+
bps: null,
|
|
2728
|
+
absBps: null,
|
|
2729
|
+
bias: null,
|
|
2730
|
+
severity: null
|
|
2731
|
+
});
|
|
2732
|
+
var buildSpreadContextFromSignal = (signal) => {
|
|
2733
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2734
|
+
const relative = toRecord(baseContext?.relative);
|
|
2735
|
+
const execution = toRecord(relative?.execution);
|
|
2736
|
+
const spread = toFiniteNumber(execution?.venueSpread);
|
|
2737
|
+
const zScore = toFiniteNumber(execution?.venueSpreadZScore);
|
|
2738
|
+
if (spread == null) {
|
|
2739
|
+
return buildMissingSpreadContext();
|
|
2790
2740
|
}
|
|
2791
|
-
|
|
2741
|
+
const value = roundTo(spread, 8);
|
|
2742
|
+
const bps = roundTo(value * 1e4, 2);
|
|
2743
|
+
const absBps = Math.abs(bps);
|
|
2744
|
+
const bias = absBps < 1 ? "flat" : bps > 0 ? "coinbase_premium" : "binance_premium";
|
|
2745
|
+
const severity = absBps >= 20 ? "wide" : absBps >= 5 ? "elevated" : "normal";
|
|
2746
|
+
return {
|
|
2747
|
+
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2748
|
+
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2749
|
+
available: true,
|
|
2750
|
+
value,
|
|
2751
|
+
zScore,
|
|
2752
|
+
bps,
|
|
2753
|
+
absBps,
|
|
2754
|
+
bias,
|
|
2755
|
+
severity
|
|
2756
|
+
};
|
|
2792
2757
|
};
|
|
2793
|
-
var
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
{
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2758
|
+
var buildTrueDeltaContextFromSignal = (signal) => {
|
|
2759
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2760
|
+
const participation = toRecord(baseContext?.participation);
|
|
2761
|
+
const delta = toRecord(participation?.delta);
|
|
2762
|
+
const source = String(delta?.source ?? "");
|
|
2763
|
+
const isTrueDeltaSource = source === "kline_taker_volume" || source === "agg_trades" || source === "trades";
|
|
2764
|
+
if (!delta || !isTrueDeltaSource) {
|
|
2765
|
+
return {
|
|
2766
|
+
source: source || null,
|
|
2767
|
+
available: false,
|
|
2768
|
+
buyPressurePct: null,
|
|
2769
|
+
buyVolume: null,
|
|
2770
|
+
sellVolume: null,
|
|
2771
|
+
netDelta: null,
|
|
2772
|
+
deltaPct: null,
|
|
2773
|
+
signedVolumeZScore: null
|
|
2774
|
+
};
|
|
2775
|
+
}
|
|
2776
|
+
return {
|
|
2777
|
+
source,
|
|
2778
|
+
available: true,
|
|
2779
|
+
buyPressurePct: toFiniteNumber(delta.buyPressurePct),
|
|
2780
|
+
buyVolume: toFiniteNumber(delta.buyVolume),
|
|
2781
|
+
sellVolume: toFiniteNumber(delta.sellVolume),
|
|
2782
|
+
netDelta: toFiniteNumber(delta.netDelta),
|
|
2783
|
+
deltaPct: toFiniteNumber(delta.deltaPct),
|
|
2784
|
+
signedVolumeZScore: toFiniteNumber(delta.signedVolumeZScore)
|
|
2785
|
+
};
|
|
2786
|
+
};
|
|
2787
|
+
var buildTradeFlowContextFromSignal = (signal) => {
|
|
2788
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2789
|
+
const participation = toRecord(baseContext?.participation);
|
|
2790
|
+
const tradeFlow = toRecord(participation?.tradeFlow);
|
|
2791
|
+
if (!tradeFlow) {
|
|
2792
|
+
return {
|
|
2793
|
+
source: null,
|
|
2794
|
+
available: false,
|
|
2795
|
+
interval: null,
|
|
2796
|
+
stale: null,
|
|
2797
|
+
trades: null,
|
|
2798
|
+
buyPressurePct: null,
|
|
2799
|
+
netBaseDelta: null,
|
|
2800
|
+
netQuoteDelta: null
|
|
2801
|
+
};
|
|
2802
|
+
}
|
|
2803
|
+
return {
|
|
2804
|
+
source: String(tradeFlow.source ?? ""),
|
|
2805
|
+
available: true,
|
|
2806
|
+
interval: String(tradeFlow.interval ?? ""),
|
|
2807
|
+
stale: typeof tradeFlow.stale === "boolean" ? tradeFlow.stale : null,
|
|
2808
|
+
trades: toFiniteNumber(tradeFlow.trades),
|
|
2809
|
+
buyPressurePct: toFiniteNumber(tradeFlow.buyPressurePct),
|
|
2810
|
+
netBaseDelta: toFiniteNumber(tradeFlow.netBaseDelta),
|
|
2811
|
+
netQuoteDelta: toFiniteNumber(tradeFlow.netQuoteDelta)
|
|
2812
|
+
};
|
|
2813
|
+
};
|
|
2814
|
+
var buildMarketBreadthContextFromSignal = (signal) => {
|
|
2815
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2816
|
+
const relative = toRecord(baseContext?.relative);
|
|
2817
|
+
const breadth = toRecord(relative?.marketBreadth);
|
|
2818
|
+
if (!breadth) {
|
|
2819
|
+
return {
|
|
2820
|
+
source: null,
|
|
2821
|
+
available: false,
|
|
2822
|
+
universe: null,
|
|
2823
|
+
interval: null,
|
|
2824
|
+
stale: null,
|
|
2825
|
+
symbolsCount: null,
|
|
2826
|
+
advanceDeclineRatio: null,
|
|
2827
|
+
pctAboveMa20: null,
|
|
2828
|
+
pctAboveMa50: null,
|
|
2829
|
+
equalWeightedReturn: null,
|
|
2830
|
+
volumeWeightedReturn: null,
|
|
2831
|
+
dispersion: null
|
|
2832
|
+
};
|
|
2833
|
+
}
|
|
2834
|
+
return {
|
|
2835
|
+
source: String(breadth.source ?? ""),
|
|
2836
|
+
available: true,
|
|
2837
|
+
universe: String(breadth.universe ?? ""),
|
|
2838
|
+
interval: String(breadth.interval ?? ""),
|
|
2839
|
+
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
2840
|
+
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
2841
|
+
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
2842
|
+
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
2843
|
+
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
2844
|
+
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
2845
|
+
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
2846
|
+
dispersion: toFiniteNumber(breadth.dispersion)
|
|
2847
|
+
};
|
|
2848
|
+
};
|
|
2849
|
+
var buildMarketBreadthsContextFromSignal = (signal) => {
|
|
2850
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2851
|
+
const relative = toRecord(baseContext?.relative);
|
|
2852
|
+
const breadths = toRecord(relative?.marketBreadths);
|
|
2853
|
+
const build = (key) => {
|
|
2854
|
+
const breadth = toRecord(breadths?.[key]);
|
|
2855
|
+
if (!breadth) {
|
|
2856
|
+
return {
|
|
2857
|
+
source: null,
|
|
2858
|
+
available: false,
|
|
2859
|
+
universe: null,
|
|
2860
|
+
interval: null,
|
|
2861
|
+
stale: null,
|
|
2862
|
+
symbolsCount: null,
|
|
2863
|
+
advanceDeclineRatio: null,
|
|
2864
|
+
pctAboveMa20: null,
|
|
2865
|
+
pctAboveMa50: null,
|
|
2866
|
+
equalWeightedReturn: null,
|
|
2867
|
+
volumeWeightedReturn: null,
|
|
2868
|
+
dispersion: null
|
|
2869
|
+
};
|
|
2870
|
+
}
|
|
2871
|
+
return {
|
|
2872
|
+
source: String(breadth.source ?? ""),
|
|
2873
|
+
available: true,
|
|
2874
|
+
universe: String(breadth.universe ?? ""),
|
|
2875
|
+
interval: String(breadth.interval ?? ""),
|
|
2876
|
+
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
2877
|
+
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
2878
|
+
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
2879
|
+
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
2880
|
+
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
2881
|
+
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
2882
|
+
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
2883
|
+
dispersion: toFiniteNumber(breadth.dispersion)
|
|
2884
|
+
};
|
|
2885
|
+
};
|
|
2886
|
+
return {
|
|
2887
|
+
top5: build("top5"),
|
|
2888
|
+
top10: build("top10"),
|
|
2889
|
+
top30: build("top30"),
|
|
2890
|
+
top50: build("top50"),
|
|
2891
|
+
top100: build("top100")
|
|
2892
|
+
};
|
|
2893
|
+
};
|
|
2894
|
+
var buildTargetVsReference = (signal, asset) => {
|
|
2895
|
+
const base = toRecord(signal.additionalIndicators?.baseContext);
|
|
2896
|
+
const relative = toRecord(base?.relative);
|
|
2897
|
+
const target = toRecord(relative?.[`targetVs${asset}`]);
|
|
2898
|
+
return {
|
|
2899
|
+
source: target ? String(target.source ?? "") : null,
|
|
2900
|
+
available: Boolean(target),
|
|
2901
|
+
ratioReturn1h: toFiniteNumber(target?.ratioReturn1h),
|
|
2902
|
+
ratioReturn4h: toFiniteNumber(target?.ratioReturn4h),
|
|
2903
|
+
ratioReturn24h: toFiniteNumber(target?.ratioReturn24h),
|
|
2904
|
+
alpha1h: toFiniteNumber(target?.[`alphaVs${asset}1h`]),
|
|
2905
|
+
alpha4h: toFiniteNumber(target?.[`alphaVs${asset}4h`]),
|
|
2906
|
+
alpha24h: toFiniteNumber(target?.[`alphaVs${asset}24h`]),
|
|
2907
|
+
beta: toFiniteNumber(target?.[`betaTo${asset}20`]),
|
|
2908
|
+
correlation: toFiniteNumber(target?.[`correlationTo${asset}20`]),
|
|
2909
|
+
ratioTrend: typeof target?.ratioTrend === "string" ? target.ratioTrend : null
|
|
2910
|
+
};
|
|
2911
|
+
};
|
|
2912
|
+
var buildTargetVsBtcContextFromSignal = (signal) => {
|
|
2913
|
+
const { alpha1h, alpha4h, alpha24h, beta, correlation, ...shared } = buildTargetVsReference(signal, "Btc");
|
|
2914
|
+
return {
|
|
2915
|
+
...shared,
|
|
2916
|
+
alphaVsBtc1h: alpha1h,
|
|
2917
|
+
alphaVsBtc4h: alpha4h,
|
|
2918
|
+
alphaVsBtc24h: alpha24h,
|
|
2919
|
+
betaToBtc20: beta,
|
|
2920
|
+
correlationToBtc20: correlation
|
|
2921
|
+
};
|
|
2922
|
+
};
|
|
2923
|
+
var buildTargetVsEthContextFromSignal = (signal) => {
|
|
2924
|
+
const { alpha1h, alpha4h, alpha24h, beta, correlation, ...shared } = buildTargetVsReference(signal, "Eth");
|
|
2925
|
+
return {
|
|
2926
|
+
...shared,
|
|
2927
|
+
alphaVsEth1h: alpha1h,
|
|
2928
|
+
alphaVsEth4h: alpha4h,
|
|
2929
|
+
alphaVsEth24h: alpha24h,
|
|
2930
|
+
betaToEth20: beta,
|
|
2931
|
+
correlationToEth20: correlation
|
|
2932
|
+
};
|
|
2933
|
+
};
|
|
2934
|
+
var buildBtcAltRegimeContextFromSignal = (signal) => {
|
|
2935
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2936
|
+
const relative = toRecord(baseContext?.relative);
|
|
2937
|
+
const btcAltRegime = toRecord(relative?.btcAltRegime);
|
|
2938
|
+
if (!btcAltRegime) {
|
|
2939
|
+
return {
|
|
2940
|
+
source: null,
|
|
2941
|
+
available: false,
|
|
2942
|
+
universe: null,
|
|
2943
|
+
interval: null,
|
|
2944
|
+
stale: null,
|
|
2945
|
+
regime: null,
|
|
2946
|
+
btcReturn24h: null,
|
|
2947
|
+
altBasketReturn24h: null,
|
|
2948
|
+
btcVsAltReturn24h: null,
|
|
2949
|
+
btcTurnoverShare24h: null,
|
|
2950
|
+
btcTurnoverShareChange24h: null,
|
|
2951
|
+
altVolToBtcVol24h: null,
|
|
2952
|
+
altDispersion24h: null
|
|
2953
|
+
};
|
|
2954
|
+
}
|
|
2955
|
+
return {
|
|
2956
|
+
source: String(btcAltRegime.source ?? ""),
|
|
2957
|
+
available: true,
|
|
2958
|
+
universe: String(btcAltRegime.universe ?? ""),
|
|
2959
|
+
interval: String(btcAltRegime.interval ?? ""),
|
|
2960
|
+
stale: typeof btcAltRegime.stale === "boolean" ? btcAltRegime.stale : null,
|
|
2961
|
+
regime: typeof btcAltRegime.regime === "string" ? btcAltRegime.regime : null,
|
|
2962
|
+
btcReturn24h: toFiniteNumber(btcAltRegime.btcReturn24h),
|
|
2963
|
+
altBasketReturn24h: toFiniteNumber(btcAltRegime.altBasketReturn24h),
|
|
2964
|
+
btcVsAltReturn24h: toFiniteNumber(btcAltRegime.btcVsAltReturn24h),
|
|
2965
|
+
btcTurnoverShare24h: toFiniteNumber(btcAltRegime.btcTurnoverShare24h),
|
|
2966
|
+
btcTurnoverShareChange24h: toFiniteNumber(
|
|
2967
|
+
btcAltRegime.btcTurnoverShareChange24h
|
|
2968
|
+
),
|
|
2969
|
+
altVolToBtcVol24h: toFiniteNumber(btcAltRegime.altVolToBtcVol24h),
|
|
2970
|
+
altDispersion24h: toFiniteNumber(btcAltRegime.altDispersion24h)
|
|
2971
|
+
};
|
|
2972
|
+
};
|
|
2973
|
+
var buildCmcGlobalContextFromSignal = (signal) => {
|
|
2974
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2975
|
+
const relative = toRecord(baseContext?.relative);
|
|
2976
|
+
const cmcGlobal = toRecord(relative?.cmcGlobal);
|
|
2977
|
+
if (!cmcGlobal) {
|
|
2978
|
+
return {
|
|
2979
|
+
source: null,
|
|
2980
|
+
available: false,
|
|
2981
|
+
interval: null,
|
|
2982
|
+
asOfTs: null,
|
|
2983
|
+
stale: null,
|
|
2984
|
+
totalMarketCapUsd: null,
|
|
2985
|
+
totalVolumeUsd: null,
|
|
2986
|
+
totalVolumeReportedUsd: null,
|
|
2987
|
+
altMarketCapUsd: null,
|
|
2988
|
+
altVolumeUsd: null,
|
|
2989
|
+
altVolumeReportedUsd: null,
|
|
2990
|
+
btcDominancePct: null,
|
|
2991
|
+
ethDominancePct: null,
|
|
2992
|
+
btcDominanceChange24hPct: null,
|
|
2993
|
+
ethDominanceChange24hPct: null,
|
|
2994
|
+
altMarketCapChange24hPct: null,
|
|
2995
|
+
altVolumeChange24hPct: null,
|
|
2996
|
+
activeCryptocurrencies: null,
|
|
2997
|
+
activeExchanges: null,
|
|
2998
|
+
activeMarketPairs: null,
|
|
2999
|
+
altLiquidityRegime: null
|
|
3000
|
+
};
|
|
3001
|
+
}
|
|
3002
|
+
return {
|
|
3003
|
+
source: String(cmcGlobal.source ?? ""),
|
|
3004
|
+
available: true,
|
|
3005
|
+
interval: typeof cmcGlobal.interval === "string" ? cmcGlobal.interval : null,
|
|
3006
|
+
asOfTs: toFiniteNumber(cmcGlobal.asOfTs),
|
|
3007
|
+
stale: typeof cmcGlobal.stale === "boolean" ? cmcGlobal.stale : null,
|
|
3008
|
+
totalMarketCapUsd: toFiniteNumber(cmcGlobal.totalMarketCapUsd),
|
|
3009
|
+
totalVolumeUsd: toFiniteNumber(cmcGlobal.totalVolumeUsd),
|
|
3010
|
+
totalVolumeReportedUsd: toFiniteNumber(cmcGlobal.totalVolumeReportedUsd),
|
|
3011
|
+
altMarketCapUsd: toFiniteNumber(cmcGlobal.altMarketCapUsd),
|
|
3012
|
+
altVolumeUsd: toFiniteNumber(cmcGlobal.altVolumeUsd),
|
|
3013
|
+
altVolumeReportedUsd: toFiniteNumber(cmcGlobal.altVolumeReportedUsd),
|
|
3014
|
+
btcDominancePct: toFiniteNumber(cmcGlobal.btcDominancePct),
|
|
3015
|
+
ethDominancePct: toFiniteNumber(cmcGlobal.ethDominancePct),
|
|
3016
|
+
btcDominanceChange24hPct: toFiniteNumber(
|
|
3017
|
+
cmcGlobal.btcDominanceChange24hPct
|
|
3018
|
+
),
|
|
3019
|
+
ethDominanceChange24hPct: toFiniteNumber(
|
|
3020
|
+
cmcGlobal.ethDominanceChange24hPct
|
|
3021
|
+
),
|
|
3022
|
+
altMarketCapChange24hPct: toFiniteNumber(
|
|
3023
|
+
cmcGlobal.altMarketCapChange24hPct
|
|
3024
|
+
),
|
|
3025
|
+
altVolumeChange24hPct: toFiniteNumber(cmcGlobal.altVolumeChange24hPct),
|
|
3026
|
+
activeCryptocurrencies: toFiniteNumber(cmcGlobal.activeCryptocurrencies),
|
|
3027
|
+
activeExchanges: toFiniteNumber(cmcGlobal.activeExchanges),
|
|
3028
|
+
activeMarketPairs: toFiniteNumber(cmcGlobal.activeMarketPairs),
|
|
3029
|
+
altLiquidityRegime: typeof cmcGlobal.altLiquidityRegime === "string" ? cmcGlobal.altLiquidityRegime : null
|
|
3030
|
+
};
|
|
3031
|
+
};
|
|
3032
|
+
var buildCmcReferenceAssetsContextFromSignal = (signal) => {
|
|
3033
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3034
|
+
const relative = toRecord(baseContext?.relative);
|
|
3035
|
+
const cmcReferenceAssets = toRecord(relative?.cmcReferenceAssets);
|
|
3036
|
+
if (!cmcReferenceAssets) {
|
|
3037
|
+
return {
|
|
3038
|
+
source: null,
|
|
3039
|
+
available: false,
|
|
3040
|
+
interval: null,
|
|
3041
|
+
asOfTs: null,
|
|
3042
|
+
stale: null,
|
|
3043
|
+
btcMarketCapUsd: null,
|
|
3044
|
+
ethMarketCapUsd: null,
|
|
3045
|
+
btcVolumeUsd: null,
|
|
3046
|
+
ethVolumeUsd: null,
|
|
3047
|
+
btcVolumeToMarketCap: null,
|
|
3048
|
+
ethVolumeToMarketCap: null,
|
|
3049
|
+
ethBtcMarketCapRatio: null,
|
|
3050
|
+
ethBtcMarketCapRatioChange24hPct: null,
|
|
3051
|
+
ethVsBtcVolumeRatio: null,
|
|
3052
|
+
referenceLiquidityRegime: null
|
|
3053
|
+
};
|
|
3054
|
+
}
|
|
3055
|
+
return {
|
|
3056
|
+
source: String(cmcReferenceAssets.source ?? ""),
|
|
3057
|
+
available: true,
|
|
3058
|
+
interval: typeof cmcReferenceAssets.interval === "string" ? cmcReferenceAssets.interval : null,
|
|
3059
|
+
asOfTs: toFiniteNumber(cmcReferenceAssets.asOfTs),
|
|
3060
|
+
stale: typeof cmcReferenceAssets.stale === "boolean" ? cmcReferenceAssets.stale : null,
|
|
3061
|
+
btcMarketCapUsd: toFiniteNumber(cmcReferenceAssets.btcMarketCapUsd),
|
|
3062
|
+
ethMarketCapUsd: toFiniteNumber(cmcReferenceAssets.ethMarketCapUsd),
|
|
3063
|
+
btcVolumeUsd: toFiniteNumber(cmcReferenceAssets.btcVolumeUsd),
|
|
3064
|
+
ethVolumeUsd: toFiniteNumber(cmcReferenceAssets.ethVolumeUsd),
|
|
3065
|
+
btcVolumeToMarketCap: toFiniteNumber(
|
|
3066
|
+
cmcReferenceAssets.btcVolumeToMarketCap
|
|
3067
|
+
),
|
|
3068
|
+
ethVolumeToMarketCap: toFiniteNumber(
|
|
3069
|
+
cmcReferenceAssets.ethVolumeToMarketCap
|
|
3070
|
+
),
|
|
3071
|
+
ethBtcMarketCapRatio: toFiniteNumber(
|
|
3072
|
+
cmcReferenceAssets.ethBtcMarketCapRatio
|
|
3073
|
+
),
|
|
3074
|
+
ethBtcMarketCapRatioChange24hPct: toFiniteNumber(
|
|
3075
|
+
cmcReferenceAssets.ethBtcMarketCapRatioChange24hPct
|
|
3076
|
+
),
|
|
3077
|
+
ethVsBtcVolumeRatio: toFiniteNumber(cmcReferenceAssets.ethVsBtcVolumeRatio),
|
|
3078
|
+
referenceLiquidityRegime: typeof cmcReferenceAssets.referenceLiquidityRegime === "string" ? cmcReferenceAssets.referenceLiquidityRegime : null
|
|
3079
|
+
};
|
|
3080
|
+
};
|
|
3081
|
+
var buildCmcExchangeLiquidityContextFromSignal = (signal) => {
|
|
3082
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3083
|
+
const relative = toRecord(baseContext?.relative);
|
|
3084
|
+
const cmcExchangeLiquidity = toRecord(relative?.cmcExchangeLiquidity);
|
|
3085
|
+
if (!cmcExchangeLiquidity) {
|
|
3086
|
+
return {
|
|
3087
|
+
source: null,
|
|
3088
|
+
available: false,
|
|
3089
|
+
interval: null,
|
|
3090
|
+
asOfTs: null,
|
|
3091
|
+
stale: null,
|
|
3092
|
+
exchangesCount: null,
|
|
3093
|
+
totalVolumeUsd: null,
|
|
3094
|
+
totalVolumeChange24hPct: null,
|
|
3095
|
+
binanceVolumeUsd: null,
|
|
3096
|
+
binanceVolumeShare: null,
|
|
3097
|
+
topExchangeVolumeShare: null,
|
|
3098
|
+
liquidityRegime: null
|
|
3099
|
+
};
|
|
3100
|
+
}
|
|
3101
|
+
return {
|
|
3102
|
+
source: String(cmcExchangeLiquidity.source ?? ""),
|
|
3103
|
+
available: true,
|
|
3104
|
+
interval: typeof cmcExchangeLiquidity.interval === "string" ? cmcExchangeLiquidity.interval : null,
|
|
3105
|
+
asOfTs: toFiniteNumber(cmcExchangeLiquidity.asOfTs),
|
|
3106
|
+
stale: typeof cmcExchangeLiquidity.stale === "boolean" ? cmcExchangeLiquidity.stale : null,
|
|
3107
|
+
exchangesCount: toFiniteNumber(cmcExchangeLiquidity.exchangesCount),
|
|
3108
|
+
totalVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.totalVolumeUsd),
|
|
3109
|
+
totalVolumeChange24hPct: toFiniteNumber(
|
|
3110
|
+
cmcExchangeLiquidity.totalVolumeChange24hPct
|
|
3111
|
+
),
|
|
3112
|
+
binanceVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeUsd),
|
|
3113
|
+
binanceVolumeShare: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeShare),
|
|
3114
|
+
topExchangeVolumeShare: toFiniteNumber(
|
|
3115
|
+
cmcExchangeLiquidity.topExchangeVolumeShare
|
|
3116
|
+
),
|
|
3117
|
+
liquidityRegime: typeof cmcExchangeLiquidity.liquidityRegime === "string" ? cmcExchangeLiquidity.liquidityRegime : null
|
|
3118
|
+
};
|
|
3119
|
+
};
|
|
3120
|
+
var buildCmcFearGreedContextFromSignal = (signal) => {
|
|
3121
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3122
|
+
const relative = toRecord(baseContext?.relative);
|
|
3123
|
+
const cmcFearGreed = toRecord(relative?.cmcFearGreed);
|
|
3124
|
+
if (!cmcFearGreed) {
|
|
3125
|
+
return {
|
|
3126
|
+
source: null,
|
|
3127
|
+
available: false,
|
|
3128
|
+
interval: null,
|
|
3129
|
+
asOfTs: null,
|
|
3130
|
+
stale: null,
|
|
3131
|
+
value: null,
|
|
3132
|
+
valueChange24h: null,
|
|
3133
|
+
valueChange7d: null,
|
|
3134
|
+
classification: null,
|
|
3135
|
+
sentimentRegime: null
|
|
3136
|
+
};
|
|
3137
|
+
}
|
|
3138
|
+
return {
|
|
3139
|
+
source: String(cmcFearGreed.source ?? ""),
|
|
3140
|
+
available: true,
|
|
3141
|
+
interval: typeof cmcFearGreed.interval === "string" ? cmcFearGreed.interval : null,
|
|
3142
|
+
asOfTs: toFiniteNumber(cmcFearGreed.asOfTs),
|
|
3143
|
+
stale: typeof cmcFearGreed.stale === "boolean" ? cmcFearGreed.stale : null,
|
|
3144
|
+
value: toFiniteNumber(cmcFearGreed.value),
|
|
3145
|
+
valueChange24h: toFiniteNumber(cmcFearGreed.valueChange24h),
|
|
3146
|
+
valueChange7d: toFiniteNumber(cmcFearGreed.valueChange7d),
|
|
3147
|
+
classification: typeof cmcFearGreed.classification === "string" ? cmcFearGreed.classification : null,
|
|
3148
|
+
sentimentRegime: typeof cmcFearGreed.sentimentRegime === "string" ? cmcFearGreed.sentimentRegime : null
|
|
3149
|
+
};
|
|
3150
|
+
};
|
|
3151
|
+
var buildCmcIndexesContextFromSignal = (signal) => {
|
|
3152
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3153
|
+
const relative = toRecord(baseContext?.relative);
|
|
3154
|
+
const cmcIndexes = toRecord(relative?.cmcIndexes);
|
|
3155
|
+
if (!cmcIndexes) {
|
|
3156
|
+
return {
|
|
3157
|
+
source: null,
|
|
3158
|
+
available: false,
|
|
3159
|
+
interval: null,
|
|
3160
|
+
asOfTs: null,
|
|
3161
|
+
stale: null,
|
|
3162
|
+
cmc100Value: null,
|
|
3163
|
+
cmc100Change24hPct: null,
|
|
3164
|
+
cmc100TopConstituentSymbol: null,
|
|
3165
|
+
cmc100TopConstituentWeightPct: null,
|
|
3166
|
+
cmc20Value: null,
|
|
3167
|
+
cmc20Change24hPct: null,
|
|
3168
|
+
cmc20TopConstituentSymbol: null,
|
|
3169
|
+
cmc20TopConstituentWeightPct: null,
|
|
3170
|
+
cmc20ToCmc100Ratio: null,
|
|
3171
|
+
cmc20ToCmc100RatioChange24hPct: null,
|
|
3172
|
+
indexRegime: null
|
|
3173
|
+
};
|
|
3174
|
+
}
|
|
3175
|
+
return {
|
|
3176
|
+
source: String(cmcIndexes.source ?? ""),
|
|
3177
|
+
available: true,
|
|
3178
|
+
interval: typeof cmcIndexes.interval === "string" ? cmcIndexes.interval : null,
|
|
3179
|
+
asOfTs: toFiniteNumber(cmcIndexes.asOfTs),
|
|
3180
|
+
stale: typeof cmcIndexes.stale === "boolean" ? cmcIndexes.stale : null,
|
|
3181
|
+
cmc100Value: toFiniteNumber(cmcIndexes.cmc100Value),
|
|
3182
|
+
cmc100Change24hPct: toFiniteNumber(cmcIndexes.cmc100Change24hPct),
|
|
3183
|
+
cmc100TopConstituentSymbol: typeof cmcIndexes.cmc100TopConstituentSymbol === "string" ? cmcIndexes.cmc100TopConstituentSymbol : null,
|
|
3184
|
+
cmc100TopConstituentWeightPct: toFiniteNumber(
|
|
3185
|
+
cmcIndexes.cmc100TopConstituentWeightPct
|
|
3186
|
+
),
|
|
3187
|
+
cmc20Value: toFiniteNumber(cmcIndexes.cmc20Value),
|
|
3188
|
+
cmc20Change24hPct: toFiniteNumber(cmcIndexes.cmc20Change24hPct),
|
|
3189
|
+
cmc20TopConstituentSymbol: typeof cmcIndexes.cmc20TopConstituentSymbol === "string" ? cmcIndexes.cmc20TopConstituentSymbol : null,
|
|
3190
|
+
cmc20TopConstituentWeightPct: toFiniteNumber(
|
|
3191
|
+
cmcIndexes.cmc20TopConstituentWeightPct
|
|
3192
|
+
),
|
|
3193
|
+
cmc20ToCmc100Ratio: toFiniteNumber(cmcIndexes.cmc20ToCmc100Ratio),
|
|
3194
|
+
cmc20ToCmc100RatioChange24hPct: toFiniteNumber(
|
|
3195
|
+
cmcIndexes.cmc20ToCmc100RatioChange24hPct
|
|
3196
|
+
),
|
|
3197
|
+
indexRegime: typeof cmcIndexes.indexRegime === "string" ? cmcIndexes.indexRegime : null
|
|
3198
|
+
};
|
|
2970
3199
|
};
|
|
2971
|
-
var
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
messages: [
|
|
2988
|
-
{ role: "system", content: systemPrompt },
|
|
2989
|
-
{
|
|
2990
|
-
role: "system",
|
|
2991
|
-
content: `Write all user-visible text fields in ${responseLanguage}. Keep field names and JSON syntax unchanged.`
|
|
2992
|
-
},
|
|
2993
|
-
{ role: "user", content: humanPrompt }
|
|
2994
|
-
]
|
|
2995
|
-
});
|
|
2996
|
-
const parsed = parseAIResponse(response.content);
|
|
2997
|
-
const normalized = normalizeAnalysis(parsed);
|
|
2998
|
-
if (!options.signal) {
|
|
2999
|
-
return normalized;
|
|
3200
|
+
var buildReferenceTradeFlowContextFromSignal = (signal) => {
|
|
3201
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3202
|
+
const relative = toRecord(baseContext?.relative);
|
|
3203
|
+
const refs = toRecord(relative?.referenceTradeFlow);
|
|
3204
|
+
const primaryReferenceSymbol = typeof refs?.primaryReferenceSymbol === "string" ? refs.primaryReferenceSymbol : null;
|
|
3205
|
+
const tradeFlowBySymbol = toRecord(refs?.tradeFlowBySymbol);
|
|
3206
|
+
const primaryTradeFlow = primaryReferenceSymbol != null ? toRecord(tradeFlowBySymbol?.[primaryReferenceSymbol]) : null;
|
|
3207
|
+
if (!refs) {
|
|
3208
|
+
return {
|
|
3209
|
+
source: null,
|
|
3210
|
+
available: false,
|
|
3211
|
+
primaryReferenceSymbol: null,
|
|
3212
|
+
referenceSymbols: [],
|
|
3213
|
+
primaryTradeFlowBuyPressurePct: null,
|
|
3214
|
+
primaryTradeFlowStale: null
|
|
3215
|
+
};
|
|
3000
3216
|
}
|
|
3001
|
-
return
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3217
|
+
return {
|
|
3218
|
+
source: String(refs.source ?? ""),
|
|
3219
|
+
available: true,
|
|
3220
|
+
primaryReferenceSymbol,
|
|
3221
|
+
referenceSymbols: Array.isArray(refs.referenceSymbols) ? refs.referenceSymbols.map(String) : [],
|
|
3222
|
+
primaryTradeFlowBuyPressurePct: toFiniteNumber(
|
|
3223
|
+
primaryTradeFlow?.buyPressurePct
|
|
3224
|
+
),
|
|
3225
|
+
primaryTradeFlowStale: typeof primaryTradeFlow?.stale === "boolean" ? primaryTradeFlow.stale : null
|
|
3226
|
+
};
|
|
3006
3227
|
};
|
|
3007
|
-
var
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3228
|
+
var buildAiMarketContext = (signal) => ({
|
|
3229
|
+
execution: {
|
|
3230
|
+
binanceCoinbaseSpread: buildSpreadContextFromSignal(signal)
|
|
3231
|
+
},
|
|
3232
|
+
participation: {
|
|
3233
|
+
trueDelta: buildTrueDeltaContextFromSignal(signal),
|
|
3234
|
+
tradeFlow: buildTradeFlowContextFromSignal(signal)
|
|
3235
|
+
},
|
|
3236
|
+
relative: {
|
|
3237
|
+
marketBreadth: buildMarketBreadthContextFromSignal(signal),
|
|
3238
|
+
marketBreadths: buildMarketBreadthsContextFromSignal(signal),
|
|
3239
|
+
targetVsBtc: buildTargetVsBtcContextFromSignal(signal),
|
|
3240
|
+
targetVsEth: buildTargetVsEthContextFromSignal(signal),
|
|
3241
|
+
btcAltRegime: buildBtcAltRegimeContextFromSignal(signal),
|
|
3242
|
+
cmcGlobal: buildCmcGlobalContextFromSignal(signal),
|
|
3243
|
+
cmcReferenceAssets: buildCmcReferenceAssetsContextFromSignal(signal),
|
|
3244
|
+
cmcExchangeLiquidity: buildCmcExchangeLiquidityContextFromSignal(signal),
|
|
3245
|
+
cmcFearGreed: buildCmcFearGreedContextFromSignal(signal),
|
|
3246
|
+
cmcIndexes: buildCmcIndexesContextFromSignal(signal),
|
|
3247
|
+
referenceTradeFlow: buildReferenceTradeFlowContextFromSignal(signal)
|
|
3248
|
+
}
|
|
3249
|
+
});
|
|
3250
|
+
|
|
3251
|
+
// src/strategy/policyProfiles.ts
|
|
3252
|
+
var profileMatches = (profile, universe, assetClass) => {
|
|
3253
|
+
const { appliesTo } = profile;
|
|
3254
|
+
if (!appliesTo) return true;
|
|
3255
|
+
if (appliesTo.universes?.length && (!universe || !appliesTo.universes.includes(universe))) {
|
|
3256
|
+
return false;
|
|
3257
|
+
}
|
|
3258
|
+
if (appliesTo.assetClasses?.length && (!assetClass || !appliesTo.assetClasses.includes(assetClass))) {
|
|
3259
|
+
return false;
|
|
3260
|
+
}
|
|
3261
|
+
return true;
|
|
3025
3262
|
};
|
|
3026
|
-
var
|
|
3027
|
-
const
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3263
|
+
var resolveStrategyPolicyProfile = (manifest, params) => {
|
|
3264
|
+
const profiles = manifest?.policyProfiles ?? [];
|
|
3265
|
+
if (!profiles.length) {
|
|
3266
|
+
const inferredId = params.profileId ?? (params.universe === "tradfi" ? "tradfi" : void 0);
|
|
3267
|
+
if (!inferredId) return void 0;
|
|
3268
|
+
if (inferredId !== "crypto" && inferredId !== "tradfi") {
|
|
3269
|
+
throw new Error(
|
|
3270
|
+
`Unknown policy profile "${inferredId}" for strategy "${manifest?.name}"`
|
|
3271
|
+
);
|
|
3272
|
+
}
|
|
3273
|
+
if (params.universe && inferredId !== params.universe) {
|
|
3274
|
+
throw new Error(
|
|
3275
|
+
`Policy profile "${inferredId}" is not compatible with ${params.universe}`
|
|
3276
|
+
);
|
|
3277
|
+
}
|
|
3278
|
+
return {
|
|
3279
|
+
id: inferredId,
|
|
3280
|
+
appliesTo: { universes: [inferredId] },
|
|
3281
|
+
marketDataRequirements: inferredId === "crypto" ? ["crypto.btcReference"] : [],
|
|
3282
|
+
...manifest?.mlAdapter ? {
|
|
3283
|
+
entryRuntimeDefaults: {
|
|
3284
|
+
ml: {
|
|
3285
|
+
modelKey: inferredId === "crypto" ? manifest.name : `${manifest.name}:tradfi`
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3288
|
+
} : {}
|
|
3289
|
+
};
|
|
3290
|
+
}
|
|
3291
|
+
if (params.profileId) {
|
|
3292
|
+
const profile = profiles.find(({ id }) => id === params.profileId);
|
|
3293
|
+
if (!profile) {
|
|
3294
|
+
throw new Error(
|
|
3295
|
+
`Unknown policy profile "${params.profileId}" for strategy "${manifest?.name}"`
|
|
3296
|
+
);
|
|
3297
|
+
}
|
|
3298
|
+
if (!profileMatches(profile, params.universe, params.assetClass)) {
|
|
3299
|
+
throw new Error(
|
|
3300
|
+
`Policy profile "${params.profileId}" is not compatible with ${params.universe ?? "unknown"}:${params.assetClass ?? "unknown"}`
|
|
3301
|
+
);
|
|
3038
3302
|
}
|
|
3303
|
+
return profile;
|
|
3304
|
+
}
|
|
3305
|
+
const matching = profiles.filter(
|
|
3306
|
+
(profile) => profileMatches(profile, params.universe, params.assetClass)
|
|
3039
3307
|
);
|
|
3040
|
-
|
|
3041
|
-
|
|
3308
|
+
const defaultProfile = matching.find(
|
|
3309
|
+
({ id }) => id === manifest?.defaultPolicyProfileId
|
|
3310
|
+
);
|
|
3311
|
+
return defaultProfile ?? matching[0];
|
|
3042
3312
|
};
|
|
3313
|
+
var getStrategyProfileAiAdapter = (manifest, profileId) => manifest?.policyProfiles?.find(({ id }) => id === profileId)?.aiAdapter ?? manifest?.aiAdapter;
|
|
3314
|
+
var getStrategyProfileMlAdapter = (manifest, profileId) => manifest?.policyProfiles?.find(({ id }) => id === profileId)?.mlAdapter ?? manifest?.mlAdapter;
|
|
3043
3315
|
|
|
3044
|
-
// src/strategyAdapters/
|
|
3045
|
-
var
|
|
3046
|
-
|
|
3316
|
+
// src/strategyAdapters/ai.ts
|
|
3317
|
+
var toRecord2 = (value) => {
|
|
3318
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
3319
|
+
return {};
|
|
3320
|
+
}
|
|
3321
|
+
return value;
|
|
3047
3322
|
};
|
|
3048
|
-
var
|
|
3049
|
-
const
|
|
3050
|
-
|
|
3051
|
-
profileId
|
|
3323
|
+
var buildBaseAiPayload = (signal) => {
|
|
3324
|
+
const { jevEvidence: _jevEvidence, ...signalAdditionalIndicators } = toRecord2(
|
|
3325
|
+
signal.additionalIndicators
|
|
3052
3326
|
);
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
...strategyAdapter
|
|
3327
|
+
const additionalIndicators = {
|
|
3328
|
+
...signalAdditionalIndicators,
|
|
3329
|
+
marketContext: buildAiMarketContext(signal)
|
|
3057
3330
|
};
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
...normalizedSignal?.indicators ?? {}
|
|
3076
|
-
}
|
|
3331
|
+
return {
|
|
3332
|
+
signal: {
|
|
3333
|
+
symbol: signal.symbol,
|
|
3334
|
+
signalId: signal.signalId,
|
|
3335
|
+
interval: signal.interval,
|
|
3336
|
+
direction: signal.direction,
|
|
3337
|
+
timestamp: signal.timestamp,
|
|
3338
|
+
strategy: signal.strategy,
|
|
3339
|
+
prices: {
|
|
3340
|
+
currentPrice: signal.prices.currentPrice,
|
|
3341
|
+
takeProfitPrice: signal.prices.takeProfitPrice,
|
|
3342
|
+
stopLossPrice: signal.prices.stopLossPrice
|
|
3343
|
+
}
|
|
3344
|
+
},
|
|
3345
|
+
figures: trimSeriesDeep(signal.figures ?? {}),
|
|
3346
|
+
indicators: buildCompactAiIndicatorsSnapshot(signal.indicators),
|
|
3347
|
+
additionalIndicators: trimSeriesDeep(additionalIndicators)
|
|
3077
3348
|
};
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3349
|
+
};
|
|
3350
|
+
var defaultAiAdapter = {};
|
|
3351
|
+
var tickerProfileFeatureIds = new Set(
|
|
3352
|
+
import_aiFeatures.TICKER_PROFILE_AI_FEATURES.map(({ id }) => id)
|
|
3353
|
+
);
|
|
3354
|
+
var getStrategyAiAdapter = (strategy, profileId) => getStrategyProfileAiAdapter(getStrategyManifest(strategy), profileId) ?? defaultAiAdapter;
|
|
3355
|
+
var getSignalAiAdapter = (signal) => getStrategyAiAdapter(signal.strategy, signal.policyProfileId);
|
|
3356
|
+
var buildAiPayloadByStrategy = (signal) => {
|
|
3357
|
+
const basePayload = buildBaseAiPayload(signal);
|
|
3358
|
+
const adapter = getSignalAiAdapter(signal);
|
|
3359
|
+
const payload = adapter.buildPayload?.({ signal, basePayload }) ?? basePayload;
|
|
3360
|
+
const custom = getStrategyManifest(signal.strategy)?.aiFeatures ?? [];
|
|
3361
|
+
const frozen = signal.aiFeatureReplay?.snapshot;
|
|
3362
|
+
if (frozen) (0, import_aiFeatures.validateAiFeatureSnapshot)(frozen);
|
|
3363
|
+
const tickerProfile = signal.additionalIndicators?.tickerProfile ?? signal.additionalIndicators?.baseContext?.tickerProfile;
|
|
3364
|
+
const hasProfile = Boolean(
|
|
3365
|
+
tickerProfile || frozen?.definitions.some(({ id }) => tickerProfileFeatureIds.has(id))
|
|
3366
|
+
);
|
|
3367
|
+
const indicatorFeatures = (0, import_indicators2.getRegisteredIndicatorEntries)(
|
|
3368
|
+
getTradejsProjectCwd()
|
|
3369
|
+
).flatMap((entry) => entry.indicator.enabled ? entry.aiFeatures ?? [] : []);
|
|
3370
|
+
const hasPortfolio = Boolean(
|
|
3371
|
+
signal.additionalIndicators?.portfolioContext || frozen?.definitions.some(
|
|
3372
|
+
({ id }) => import_aiFeatures.PORTFOLIO_AI_FEATURES.some((d) => d.id === id)
|
|
3084
3373
|
)
|
|
3085
|
-
|
|
3374
|
+
);
|
|
3375
|
+
const definitions = [
|
|
3376
|
+
...hasPortfolio ? import_aiFeatures.PORTFOLIO_AI_FEATURES : [],
|
|
3377
|
+
...hasProfile ? import_aiFeatures.TICKER_PROFILE_AI_FEATURES : [],
|
|
3378
|
+
...indicatorFeatures,
|
|
3379
|
+
...custom
|
|
3380
|
+
];
|
|
3381
|
+
if (!definitions.length && !signal.aiFeatureReplay?.snapshot) return payload;
|
|
3382
|
+
(0, import_aiFeatures.validateAiFeatureDefinitions)(definitions);
|
|
3383
|
+
if (signal.aiFeatureReplay && !frozen) {
|
|
3384
|
+
throw new Error(
|
|
3385
|
+
"Dataset has no frozen AI features; regenerate the decision-time export"
|
|
3386
|
+
);
|
|
3387
|
+
}
|
|
3388
|
+
if (frozen && JSON.stringify(frozen.definitions) !== JSON.stringify(definitions)) {
|
|
3389
|
+
throw new Error(
|
|
3390
|
+
"AI feature definition changed; regenerate the decision-time export"
|
|
3391
|
+
);
|
|
3392
|
+
}
|
|
3086
3393
|
return {
|
|
3087
|
-
|
|
3088
|
-
|
|
3394
|
+
...payload,
|
|
3395
|
+
featureSnapshot: frozen ?? (0, import_aiFeatures.projectAiFeatures)(definitions, {
|
|
3396
|
+
signal: tickerProfile ? {
|
|
3397
|
+
...signal,
|
|
3398
|
+
additionalIndicators: {
|
|
3399
|
+
...signal.additionalIndicators,
|
|
3400
|
+
tickerProfile
|
|
3401
|
+
}
|
|
3402
|
+
} : signal,
|
|
3403
|
+
payload
|
|
3404
|
+
})
|
|
3089
3405
|
};
|
|
3090
3406
|
};
|
|
3407
|
+
var buildAiSystemPromptAddonByStrategy = (signal) => getSignalAiAdapter(signal).buildSystemPromptAddon?.({ signal }) ?? "";
|
|
3408
|
+
var buildAiHumanPromptAddonByStrategy = (signal, payload) => getSignalAiAdapter(signal).buildHumanPromptAddon?.({
|
|
3409
|
+
signal,
|
|
3410
|
+
payload
|
|
3411
|
+
}) ?? "";
|
|
3412
|
+
var postProcessAiAnalysisByStrategy = (signal, analysis, payload = buildAiPayloadByStrategy(signal)) => getSignalAiAdapter(signal).postProcessAnalysis?.({
|
|
3413
|
+
signal,
|
|
3414
|
+
payload,
|
|
3415
|
+
analysis
|
|
3416
|
+
}) ?? analysis;
|
|
3417
|
+
var postProcessLocalAiAnalysisByStrategy = (signal, analysis, payload = buildAiPayloadByStrategy(signal)) => {
|
|
3418
|
+
const adapter = getSignalAiAdapter(signal);
|
|
3419
|
+
const strategyAnalysis = adapter.postProcessAnalysis?.({ signal, payload, analysis }) ?? analysis;
|
|
3420
|
+
return adapter.postProcessLocalAnalysis?.({
|
|
3421
|
+
signal,
|
|
3422
|
+
payload,
|
|
3423
|
+
analysis: strategyAnalysis
|
|
3424
|
+
}) ?? strategyAnalysis;
|
|
3425
|
+
};
|
|
3091
3426
|
|
|
3092
|
-
// src/
|
|
3093
|
-
var
|
|
3094
|
-
var
|
|
3095
|
-
var
|
|
3096
|
-
var
|
|
3097
|
-
var
|
|
3098
|
-
var
|
|
3099
|
-
var
|
|
3100
|
-
var
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3427
|
+
// src/aiProvider.ts
|
|
3428
|
+
var import_aiEndpoints = require("@tradejs/core/aiEndpoints");
|
|
3429
|
+
var import_aiModels = require("@tradejs/core/aiModels");
|
|
3430
|
+
var import_aiLanguages = require("@tradejs/core/aiLanguages");
|
|
3431
|
+
var import_userSettings2 = require("@tradejs/infra/userSettings");
|
|
3432
|
+
var DEFAULT_AI_MODEL = "openai/gpt-5-mini";
|
|
3433
|
+
var userSettingsCache = /* @__PURE__ */ new Map();
|
|
3434
|
+
var aiModelCache = /* @__PURE__ */ new Map();
|
|
3435
|
+
var normalizeResponseContent = (content) => {
|
|
3436
|
+
if (typeof content === "string") return content;
|
|
3437
|
+
if (content && typeof content === "object" && !Array.isArray(content)) {
|
|
3438
|
+
return content;
|
|
3439
|
+
}
|
|
3440
|
+
if (Array.isArray(content)) {
|
|
3441
|
+
return content.map(
|
|
3442
|
+
(part) => typeof part?.text === "string" ? part.text : ""
|
|
3443
|
+
).join("\n").trim();
|
|
3444
|
+
}
|
|
3445
|
+
return String(content ?? "");
|
|
3109
3446
|
};
|
|
3110
|
-
var
|
|
3111
|
-
const
|
|
3112
|
-
|
|
3113
|
-
|
|
3447
|
+
var getAiInvocationError = (error) => {
|
|
3448
|
+
const details = error instanceof Error && error.message.trim() ? error.message.trim() : String(error);
|
|
3449
|
+
const isEmptyCompletion = error instanceof TypeError && /Cannot read properties of undefined \(reading ['"]message['"]\)/.test(
|
|
3450
|
+
details
|
|
3451
|
+
);
|
|
3452
|
+
const wrapped = new Error(
|
|
3453
|
+
isEmptyCompletion ? "AI provider returned an empty chat completion" : `AI model invocation failed: ${details}`
|
|
3454
|
+
);
|
|
3455
|
+
wrapped.cause = error;
|
|
3456
|
+
return wrapped;
|
|
3457
|
+
};
|
|
3458
|
+
var isEmptyResponseContent = (content) => typeof content === "string" ? content.trim().length === 0 : Object.keys(content).length === 0;
|
|
3459
|
+
var getAiModelCacheKey = (userName, modelName, temperature) => `${userName}::${modelName}::${temperature}`;
|
|
3460
|
+
var resolveAiModelName = (settings, requestedModelName) => {
|
|
3461
|
+
const explicitModelName = requestedModelName?.trim() ?? "";
|
|
3462
|
+
if (explicitModelName) return explicitModelName;
|
|
3463
|
+
return settings.AI_MODEL?.trim() || DEFAULT_AI_MODEL;
|
|
3464
|
+
};
|
|
3465
|
+
var getOpenRouterModelKwargs = (apiEndpoint) => {
|
|
3466
|
+
const endpoint = String(apiEndpoint ?? "").trim();
|
|
3467
|
+
if (!endpoint) return {};
|
|
3468
|
+
let hostname = "";
|
|
3469
|
+
try {
|
|
3470
|
+
hostname = new URL(endpoint).hostname;
|
|
3471
|
+
} catch {
|
|
3472
|
+
hostname = endpoint;
|
|
3114
3473
|
}
|
|
3115
|
-
return
|
|
3474
|
+
return hostname.toLowerCase().includes("openrouter") ? { provider: { ignore: ["azure"] } } : {};
|
|
3116
3475
|
};
|
|
3117
|
-
var
|
|
3118
|
-
|
|
3119
|
-
if (!
|
|
3120
|
-
|
|
3476
|
+
var getAiUserSettings = async (userName = "root") => {
|
|
3477
|
+
let settingsPromise = userSettingsCache.get(userName);
|
|
3478
|
+
if (!settingsPromise) {
|
|
3479
|
+
settingsPromise = (0, import_userSettings2.getUserSettings)(userName).then((settings2) => {
|
|
3480
|
+
const endpoint = (0, import_aiEndpoints.normalizeAiEndpoint)(settings2.AI_API_ENDPOINT);
|
|
3481
|
+
return {
|
|
3482
|
+
...settings2,
|
|
3483
|
+
AI_API_ENDPOINT: endpoint,
|
|
3484
|
+
AI_MODEL: (0, import_aiModels.normalizeAiModel)(settings2.AI_MODEL, endpoint),
|
|
3485
|
+
AI_RESPONSE_LANGUAGE: (0, import_aiLanguages.normalizeAiResponseLanguage)(
|
|
3486
|
+
settings2.AI_RESPONSE_LANGUAGE
|
|
3487
|
+
)
|
|
3488
|
+
};
|
|
3489
|
+
});
|
|
3490
|
+
settingsPromise.catch(() => userSettingsCache.delete(userName));
|
|
3491
|
+
userSettingsCache.set(userName, settingsPromise);
|
|
3492
|
+
}
|
|
3493
|
+
const settings = await settingsPromise;
|
|
3494
|
+
if (!settings.AI_API_KEY || !settings.AI_API_ENDPOINT) {
|
|
3495
|
+
throw new Error(`AI settings are incomplete for user ${userName}`);
|
|
3496
|
+
}
|
|
3497
|
+
return settings;
|
|
3498
|
+
};
|
|
3499
|
+
var getAiModel = async (userName = "root", requestedModelName, temperature = 0.2) => {
|
|
3500
|
+
const settings = await getAiUserSettings(userName);
|
|
3501
|
+
const modelName = resolveAiModelName(settings, requestedModelName);
|
|
3502
|
+
const cacheKey = getAiModelCacheKey(userName, modelName, temperature);
|
|
3503
|
+
let modelPromise = aiModelCache.get(cacheKey);
|
|
3504
|
+
if (!modelPromise) {
|
|
3505
|
+
modelPromise = import("@langchain/openai").then(({ ChatOpenAI }) => {
|
|
3506
|
+
const modelKwargs = getOpenRouterModelKwargs(settings.AI_API_ENDPOINT);
|
|
3507
|
+
return new ChatOpenAI({
|
|
3508
|
+
temperature,
|
|
3509
|
+
modelName,
|
|
3510
|
+
apiKey: settings.AI_API_KEY,
|
|
3511
|
+
...Object.keys(modelKwargs).length ? { modelKwargs } : {},
|
|
3512
|
+
configuration: {
|
|
3513
|
+
baseURL: settings.AI_API_ENDPOINT,
|
|
3514
|
+
defaultHeaders: {
|
|
3515
|
+
"HTTP-Referer": "https://tradejs.dev",
|
|
3516
|
+
"X-Title": "Inv"
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
});
|
|
3520
|
+
});
|
|
3521
|
+
modelPromise.catch(() => aiModelCache.delete(cacheKey));
|
|
3522
|
+
aiModelCache.set(cacheKey, modelPromise);
|
|
3121
3523
|
}
|
|
3122
|
-
const record = {
|
|
3123
|
-
...params,
|
|
3124
|
-
entryCount: 1,
|
|
3125
|
-
lastEntryPrice: params.entryPrice,
|
|
3126
|
-
lastEntryQty: params.qty,
|
|
3127
|
-
lastEntryTimestamp: params.entryTimestamp,
|
|
3128
|
-
status: "active",
|
|
3129
|
-
currentPrice: params.entryPrice,
|
|
3130
|
-
currentPnl: 0,
|
|
3131
|
-
closedPnl: null,
|
|
3132
|
-
exitPrice: null,
|
|
3133
|
-
exitTimestamp: null,
|
|
3134
|
-
lastSyncedAt: now()
|
|
3135
|
-
};
|
|
3136
|
-
const dayKey = (0, import_time.getRuntimeStorageDayKey)(record.entryTimestamp);
|
|
3137
|
-
const runtimeScopeId = record.deploymentId ?? record.accountId;
|
|
3138
3524
|
try {
|
|
3139
|
-
await
|
|
3140
|
-
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, record.orderId), record, {
|
|
3141
|
-
expire: 0
|
|
3142
|
-
}),
|
|
3143
|
-
(0, import_redis2.setHashJsonField)(
|
|
3144
|
-
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
3145
|
-
record.orderId,
|
|
3146
|
-
record,
|
|
3147
|
-
{ expire: 0 }
|
|
3148
|
-
),
|
|
3149
|
-
(0, import_redis2.setData)(
|
|
3150
|
-
import_redis2.redisKeys.runtimeActiveTrade(userName, record.symbol, runtimeScopeId),
|
|
3151
|
-
{ orderId: record.orderId },
|
|
3152
|
-
{ expire: 0 }
|
|
3153
|
-
)
|
|
3154
|
-
]);
|
|
3525
|
+
return await modelPromise;
|
|
3155
3526
|
} catch (error) {
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
error?.message || String(error)
|
|
3160
|
-
);
|
|
3527
|
+
aiModelCache.delete(cacheKey);
|
|
3528
|
+
userSettingsCache.delete(userName);
|
|
3529
|
+
throw error;
|
|
3161
3530
|
}
|
|
3162
|
-
return record;
|
|
3163
3531
|
};
|
|
3164
|
-
var
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3532
|
+
var invokeAiChatWithUserMessageEncoding = async ({
|
|
3533
|
+
messages,
|
|
3534
|
+
userName = "root",
|
|
3535
|
+
model,
|
|
3536
|
+
temperature = 0.2
|
|
3537
|
+
}, resolveUserMessageEncoding) => {
|
|
3538
|
+
const [{ HumanMessage, SystemMessage }, aiModel] = await Promise.all([
|
|
3539
|
+
import("@langchain/core/messages"),
|
|
3540
|
+
getAiModel(userName, model, temperature)
|
|
3541
|
+
]);
|
|
3542
|
+
const providerMessages = messages.map(
|
|
3543
|
+
(message) => message.role === "system" ? new SystemMessage(message.content) : new HumanMessage(
|
|
3544
|
+
resolveUserMessageEncoding(message) === "text-block" ? { content: [{ type: "text", text: message.content }] } : message.content
|
|
3545
|
+
)
|
|
3546
|
+
);
|
|
3547
|
+
try {
|
|
3548
|
+
const response = await aiModel.invoke(providerMessages);
|
|
3549
|
+
const content = normalizeResponseContent(response?.content);
|
|
3550
|
+
if (isEmptyResponseContent(content)) {
|
|
3551
|
+
throw new Error("AI provider returned an empty chat completion");
|
|
3552
|
+
}
|
|
3553
|
+
return { content };
|
|
3554
|
+
} catch (error) {
|
|
3555
|
+
throw getAiInvocationError(error);
|
|
3556
|
+
}
|
|
3557
|
+
};
|
|
3558
|
+
var invokeAiPromptChat = (options) => invokeAiChatWithUserMessageEncoding(options, () => "text-block");
|
|
3559
|
+
|
|
3560
|
+
// src/ai.ts
|
|
3561
|
+
var parseAIResponse = (input) => {
|
|
3562
|
+
try {
|
|
3563
|
+
if (typeof input === "object" && input !== null) return input;
|
|
3564
|
+
const match = input.match(/\{[\s\S]*\}/);
|
|
3565
|
+
if (!match) throw new Error("JSON block not found");
|
|
3566
|
+
return JSON.parse(match[0]);
|
|
3567
|
+
} catch (err) {
|
|
3568
|
+
console.error("Failed to parse AI response:", err);
|
|
3569
|
+
console.log("Raw AI response:", input);
|
|
3570
|
+
return {};
|
|
3571
|
+
}
|
|
3572
|
+
};
|
|
3573
|
+
var normalizeAnalysis = (raw) => {
|
|
3574
|
+
const direction = raw?.direction === "LONG" || raw?.direction === "SHORT" ? raw.direction : null;
|
|
3575
|
+
const qualityNum = typeof raw?.quality === "number" ? Math.max(1, Math.min(5, Math.round(raw.quality))) : void 0;
|
|
3576
|
+
const toNumberOrNull = (value) => {
|
|
3577
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
3578
|
+
if (typeof value === "string" && value.trim()) {
|
|
3579
|
+
const parsed = Number(value);
|
|
3580
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
3581
|
+
}
|
|
3582
|
+
return null;
|
|
3583
|
+
};
|
|
3584
|
+
const toText = (value) => typeof value === "string" ? value.slice(0, 400) : void 0;
|
|
3585
|
+
return {
|
|
3169
3586
|
direction,
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3587
|
+
quality: qualityNum,
|
|
3588
|
+
needRetest: Boolean(raw?.needRetest),
|
|
3589
|
+
retestPrice: toNumberOrNull(raw?.retestPrice),
|
|
3590
|
+
takeProfitPrice: toNumberOrNull(raw?.takeProfitPrice),
|
|
3591
|
+
stopLossPrice: toNumberOrNull(raw?.stopLossPrice),
|
|
3592
|
+
setup: toText(raw?.setup),
|
|
3593
|
+
confirmations: toText(raw?.confirmations),
|
|
3594
|
+
btcContext: toText(raw?.btcContext),
|
|
3595
|
+
retestPlan: toText(raw?.retestPlan),
|
|
3596
|
+
riskLevels: toText(raw?.riskLevels),
|
|
3597
|
+
qualityReason: toText(raw?.qualityReason),
|
|
3598
|
+
triggerInvalidation: toText(raw?.triggerInvalidation),
|
|
3599
|
+
comment: typeof raw?.comment === "string" ? raw.comment.slice(0, 1024) : ""
|
|
3600
|
+
};
|
|
3601
|
+
};
|
|
3602
|
+
var asRecord = (value) => {
|
|
3603
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
3180
3604
|
return null;
|
|
3181
3605
|
}
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
return null;
|
|
3606
|
+
return value;
|
|
3607
|
+
};
|
|
3608
|
+
var getSignalDirection = (signal) => signal.direction === "LONG" || signal.direction === "SHORT" ? signal.direction : null;
|
|
3609
|
+
var getDeterministicQuality = (gateContext) => {
|
|
3610
|
+
const deterministicQuality = Number(gateContext?.deterministicQuality);
|
|
3611
|
+
if (Number.isFinite(deterministicQuality)) {
|
|
3612
|
+
return Math.max(1, Math.min(5, Math.round(deterministicQuality)));
|
|
3190
3613
|
}
|
|
3191
|
-
const
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
const next = {
|
|
3195
|
-
...existing,
|
|
3196
|
-
...params.orderLeverage ? { orderLeverage: params.orderLeverage } : {},
|
|
3197
|
-
qty: resultingQty,
|
|
3198
|
-
entryPrice: resultingEntryPrice,
|
|
3199
|
-
entryCount: Math.max(1, existing.entryCount ?? 1) + 1,
|
|
3200
|
-
lastEntryPrice: addedEntryPrice,
|
|
3201
|
-
lastEntryQty: addedQty,
|
|
3202
|
-
lastEntryTimestamp: entryTimestamp,
|
|
3203
|
-
currentPrice: resultingEntryPrice,
|
|
3204
|
-
currentPnl: 0,
|
|
3205
|
-
fee: openFee,
|
|
3206
|
-
openFee,
|
|
3207
|
-
totalFee,
|
|
3208
|
-
lastSyncedAt: now()
|
|
3209
|
-
};
|
|
3210
|
-
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
3211
|
-
try {
|
|
3212
|
-
await Promise.all([
|
|
3213
|
-
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, existing.orderId), next, {
|
|
3214
|
-
expire: 0
|
|
3215
|
-
}),
|
|
3216
|
-
(0, import_redis2.setHashJsonField)(
|
|
3217
|
-
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
3218
|
-
existing.orderId,
|
|
3219
|
-
next,
|
|
3220
|
-
{ expire: 0 }
|
|
3221
|
-
)
|
|
3222
|
-
]);
|
|
3223
|
-
} catch (error) {
|
|
3224
|
-
import_logger3.logger.error(
|
|
3225
|
-
"runtime trade increase journal failed: %s %s",
|
|
3226
|
-
symbol,
|
|
3227
|
-
error?.message || String(error)
|
|
3228
|
-
);
|
|
3614
|
+
const maxAllowedQuality = Number(gateContext?.maxAllowedQuality);
|
|
3615
|
+
if (Number.isFinite(maxAllowedQuality)) {
|
|
3616
|
+
return Math.max(1, Math.min(5, Math.round(maxAllowedQuality)));
|
|
3229
3617
|
}
|
|
3230
|
-
return
|
|
3618
|
+
return Array.isArray(gateContext?.approvalBlockReasons) && gateContext.approvalBlockReasons.length > 0 || Array.isArray(gateContext?.structuralHardBlockReasons) && gateContext.structuralHardBlockReasons.length > 0 ? 2 : 3;
|
|
3619
|
+
};
|
|
3620
|
+
var buildAiSystemPrompt = (signal) => `
|
|
3621
|
+
You are an internal market-structure classifier for an already computed system signal.
|
|
3622
|
+
Analyze the provided JSON containing the trade, candles, indicators (for the coin and BTC across multiple timeframes), and strategy figures/context.
|
|
3623
|
+
Series data is already trimmed to the latest 5 values.
|
|
3624
|
+
|
|
3625
|
+
Important:
|
|
3626
|
+
- Do not invent missing data.
|
|
3627
|
+
- This is an internal audit/classification task, not user-facing trading advice.
|
|
3628
|
+
- Do not generate execution instructions, do not replace the original thesis with a new one, and do not provide personalized investment advice.
|
|
3629
|
+
- Use the original signal direction and levels as the anchor, but you may state that the current structure does not support them.
|
|
3630
|
+
- Respect the source strategy specified in \`signal.strategy\`.
|
|
3631
|
+
- Your goal is to explain how well the observed structure matches the existing signal and how structurally confirmed it is right now.
|
|
3632
|
+
- Do not write vague statements like "there is momentum/slope" without tying them to the decision.
|
|
3633
|
+
- Write all user-visible text fields in the requested response language. If no explicit language instruction is provided later, default to English.
|
|
3634
|
+
- If confidence is incomplete, prefer cautious wording such as "likely", "not confirmed yet", or "probably" instead of categorical claims.
|
|
3635
|
+
|
|
3636
|
+
Return exactly one JSON object and nothing else:
|
|
3637
|
+
|
|
3638
|
+
{
|
|
3639
|
+
"direction": payload.signal.direction | null,
|
|
3640
|
+
"quality": 1 | 2 | 3 | 4 | 5,
|
|
3641
|
+
"needRetest": boolean,
|
|
3642
|
+
"retestPrice": number | null,
|
|
3643
|
+
"takeProfitPrice": number | null,
|
|
3644
|
+
"stopLossPrice": number | null,
|
|
3645
|
+
"setup": string,
|
|
3646
|
+
"confirmations": string,
|
|
3647
|
+
"btcContext": string,
|
|
3648
|
+
"retestPlan": string,
|
|
3649
|
+
"riskLevels": string,
|
|
3650
|
+
"qualityReason": string,
|
|
3651
|
+
"triggerInvalidation": string
|
|
3652
|
+
}
|
|
3653
|
+
|
|
3654
|
+
- Do not add any other fields.
|
|
3655
|
+
- All numbers must be finite, with no \`NaN\` or \`Infinity\`.
|
|
3656
|
+
- All text fields must be short strings with no line breaks and no markdown lists.
|
|
3657
|
+
- \`direction\` is not a new trade idea. It is only a compatibility flag for the existing signal: either exactly \`payload.signal.direction\` or \`null\` if the current structure does not confirm that signal. Never propose the opposite direction.
|
|
3658
|
+
- \`quality\` is the structural confirmation level of the current signal right now, including timing and confirmations. It is not a general attractiveness score and not investment advice.
|
|
3659
|
+
- \`needRetest\` indicates whether an additional confirmation level is required before the current signal can be treated as structurally confirmed.
|
|
3660
|
+
- \`retestPrice\` is the key level that would confirm or invalidate the structure, or \`null\` if no extra level is needed or available.
|
|
3661
|
+
- \`takeProfitPrice\` and \`stopLossPrice\` must not be newly invented levels. If the levels already supplied in \`payload.signal.prices\` still look internally coherent relative to the current price and the confirmed signal, you may return them as an audit of existing levels; otherwise return \`null\`.
|
|
3662
|
+
- Use these fields as separate parts of the analysis:
|
|
3663
|
+
- \`setup\`: the current structural setup or trendline state.
|
|
3664
|
+
- \`confirmations\`: 2-4 concrete confirmations or conflicts from the coin indicators.
|
|
3665
|
+
- \`btcContext\`: whether BTC supports the idea, is neutral, or conflicts with it.
|
|
3666
|
+
- \`retestPlan\`: what must happen at the key level to confirm the structure, or why no extra level is needed.
|
|
3667
|
+
- \`riskLevels\`: a short note on whether the existing levels and risk structure are internally coherent, without creating a new trade plan.
|
|
3668
|
+
- \`qualityReason\`: why the quality score is what it is.
|
|
3669
|
+
- \`triggerInvalidation\`: what must happen to confirm the signal or what invalidates the current structural thesis.
|
|
3670
|
+
- \`comment\` is optional. If you include it, do not just duplicate the structured fields.
|
|
3671
|
+
|
|
3672
|
+
If the data is insufficient or the setup is weak, return \`"direction": null\`, \`quality <= 2\`, and explain why.
|
|
3673
|
+
|
|
3674
|
+
Input payload structure:
|
|
3675
|
+
- payload.signal:
|
|
3676
|
+
symbol, signalId, interval, direction, timestamp, strategy, prices
|
|
3677
|
+
- payload.signal.prices:
|
|
3678
|
+
currentPrice, takeProfitPrice, stopLossPrice
|
|
3679
|
+
- payload.figures:
|
|
3680
|
+
strategy-specific figures or geometry when available. Fields vary by strategy.
|
|
3681
|
+
- payload.indicators:
|
|
3682
|
+
historical indicator dictionaries and series for the coin and BTC; all series are already trimmed to the latest 5 values. Treat this block as recent-history transport, not as the primary source of the current shared context.
|
|
3683
|
+
- payload.additionalIndicators:
|
|
3684
|
+
strategy-specific summary/context fields plus the canonical current shared context snapshot.
|
|
3685
|
+
This is not noise; it contains derived fields deliberately passed by the strategy to help the decision.
|
|
3686
|
+
Examples: baseContext, helperFlags, structureContext, volatilitySummary.
|
|
3687
|
+
Always inspect \`payload.additionalIndicators.baseContext\` first for the current shared state:
|
|
3688
|
+
\u2022 \`baseContext.raw\`: current MA, ATR, BB, OBV, price stats, levels, BTC correlation.
|
|
3689
|
+
\u2022 \`baseContext.regime\`: derived trend / volatility / momentum / session regime fields.
|
|
3690
|
+
\u2022 \`baseContext.structure\`: local range position, breakout freshness/quality, level-touch counts, rejection wick context.
|
|
3691
|
+
\u2022 \`baseContext.participation\`: volume/turnover participation, effort-vs-result context, Binance aggTrades, and fingerprinted Hyperliquid whale-flow when available.
|
|
3692
|
+
\u2022 \`baseContext.relative\`: BTC/ETH relative-strength, benchmark MA bias context, Binance alt-basket breadth, and CoinMarketCap historical global/exchange/index context when available.
|
|
3693
|
+
\u2022 \`baseContext.derivatives\`: Coinalyze-aligned derivatives summary when available.
|
|
3694
|
+
\u2022 \`baseContext.mtf\`: compact multi-timeframe summary plus only the latest few candles for each timeframe.
|
|
3695
|
+
\u2022 \`baseContext.gateFeatures\`: compact direction-aware fields derived from baseContext; prefer \`setup\`, \`scores\`, \`conflicts\`, \`risk\`, \`decisionHints\`, \`mtf\`, \`volatility\`, \`participation\`, and \`relative\` for quick gate checks before inspecting raw nested context.
|
|
3696
|
+
Always inspect \`payload.additionalIndicators.marketContext\` when present:
|
|
3697
|
+
\u2022 \`marketContext.execution.binanceCoinbaseSpread\`: AI-friendly BTC spread view projected from \`payload.additionalIndicators.baseContext.relative.execution.venueSpread\`; \`value=(Coinbase-Binance)/Binance\`, \`bps=value*10000\`.
|
|
3698
|
+
\u2022 \`marketContext.participation.trueDelta\`: Binance taker buy/sell volume delta from kline payload when \`source=kline_taker_volume\`; otherwise absent/unavailable.
|
|
3699
|
+
\u2022 \`marketContext.participation.tradeFlow\`: Binance aggTrades buy/sell pressure buckets when available.
|
|
3700
|
+
\u2022 \`marketContext.relative.marketBreadths.top5|top10|top30|top50|top100\`: equal/volume-weighted alt-basket return, advance/decline ratio, and MA breadth for the five versioned Binance breadth universes. \`marketBreadth\` remains the top30 primary view used by existing gates.
|
|
3701
|
+
\u2022 \`marketContext.relative.targetVsBtc\`: target/BTC ratio returns, alpha, beta, and short-window correlation; use it to decide whether the target is leading or lagging BTC in the signal direction.
|
|
3702
|
+
\u2022 \`marketContext.relative.btcAltRegime\`: Binance-derived BTC-vs-alt basket regime, BTC/alt 24h returns, BTC turnover share, and alt dispersion; use it as a broad alt-market risk pocket.
|
|
3703
|
+
\u2022 \`marketContext.relative.cmcGlobal\`: historical CoinMarketCap global market metrics: total/alt market cap, total/alt volume, BTC/ETH dominance and 24h changes, active markets, \`interval\`, and \`altLiquidityRegime\`.
|
|
3704
|
+
\u2022 \`marketContext.relative.cmcReferenceAssets\`: historical CoinMarketCap BTC/ETH market-cap and volume context, ETH/BTC market-cap ratio, ETH-vs-BTC volume ratio, \`interval\`, and \`referenceLiquidityRegime\`.
|
|
3705
|
+
\u2022 \`marketContext.relative.cmcExchangeLiquidity\`: historical CoinMarketCap major-exchange liquidity aggregate: total volume, 24h volume change, Binance share, concentration, and \`liquidityRegime\`.
|
|
3706
|
+
\u2022 \`marketContext.relative.cmcFearGreed\`: historical daily CoinMarketCap Fear & Greed sentiment index: value, classification, 24h/7d value changes, and \`sentimentRegime\`.
|
|
3707
|
+
\u2022 \`marketContext.relative.cmcIndexes\`: historical daily CoinMarketCap CMC100/CMC20 index values, 24h changes, top constituents, CMC20/CMC100 ratio, and \`indexRegime\`.
|
|
3708
|
+
\u2022 \`marketContext.relative.referenceTradeFlow\`: BTC/ETH reference trade-flow summary used for broad market pressure when the target symbol itself is not BTC/ETH.
|
|
3709
|
+
If those fields exist, use them as a more explicit hint instead of trying to re-derive the same idea from raw lines or points.
|
|
3710
|
+
If \`baseContext.derivatives\` exists, its top-level \`summary\` and \`intervals\` are the primary BTCUSDT Coinalyze benchmark context for the time of the signal. \`secondaryReferenceSymbol\` identifies the ETHUSDT secondary benchmark, and \`referenceContexts\` contains BTCUSDT/ETHUSDT plus configured extra reference symbols such as BNBUSDT/SOLUSDT/TRXUSDT/XRPUSDT. If \`targetContext\` or \`targetDerived\` exists, those fields are the Coinalyze context for the actual target coin; use them as target-specific positioning evidence, but do not infer target-coin derivatives when they are absent.
|
|
3711
|
+
Key patterns:
|
|
3712
|
+
\u2022 current shared state: prefer \`payload.additionalIndicators.baseContext\`
|
|
3713
|
+
\u2022 recent historical series: \`payload.indicators\`
|
|
3714
|
+
\u2022 strategy service keys are possible as well, for example \`touches\`, \`distance\`, timing flags, and other setup-specific summaries
|
|
3715
|
+
|
|
3716
|
+
How to analyze, in order:
|
|
3717
|
+
1. Start with price structure and the setup geometry or context in \`payload.figures\`. This has higher priority than indicators.
|
|
3718
|
+
2. Then use \`payload.additionalIndicators.baseContext\` and other explicit strategy-specific context fields.
|
|
3719
|
+
3. Then assess confirmation or conflict from the current shared state and recent coin indicator history.
|
|
3720
|
+
4. Then evaluate BTC context.
|
|
3721
|
+
5. Only after that choose \`direction\`, \`quality\`, and whether an extra confirmation level is required.
|
|
3722
|
+
6. If strong conflicts exist, reduce quality or set direction to \`null\`.
|
|
3723
|
+
|
|
3724
|
+
Explicit conflict rules:
|
|
3725
|
+
- If the figure or price structure is invalid or doubtful, indicators must not rescue the setup.
|
|
3726
|
+
- If strategy-specific helper fields explicitly say the signal is not confirmed yet, lacks margin, or requires waiting, do not overstate quality.
|
|
3727
|
+
- If the structure is acceptable but BTC or key indicators noticeably conflict, quality is usually \`<= 3\`.
|
|
3728
|
+
- If \`baseContext.derivatives.referenceContexts\` exists, check \`primaryReferenceSymbol\` first as the BTC benchmark, then compare \`secondaryReferenceSymbol\`/ETHUSDT and any target-specific \`targetDerived\`. If \`targetDerived\` exists, compare it to the primary reference instead of treating reference pressure as the target coin's own pressure.
|
|
3729
|
+
- If top-level \`baseContext.derivatives.summary.riskFlags\` contains \`crowded_long\` for a LONG or \`crowded_short\` for a SHORT, treat that as broad-market crowded positioning. If \`targetDerived.riskFlags\` contains the same directional crowding, treat that as target-specific crowded positioning.
|
|
3730
|
+
- If top-level \`baseContext.derivatives.summary.directionAligned=false\`, explicitly mention the broad-market derivatives conflict in \`confirmations\` or \`qualityReason\`. If \`targetDerived.directionAligned=false\`, explicitly mention the target-specific derivatives conflict.
|
|
3731
|
+
- If \`baseContext.derivatives\` is absent, stale, or \`missing_derivatives\`, do not infer Coinalyze conclusions and do not penalize the signal just because that data is missing.
|
|
3732
|
+
- Use \`baseContext.regime.session\` directly as the canonical session/liquidity regime: asia is often thinner, europe/us are more active, and overlaps can amplify both momentum and noise. Do not reject a signal solely because of session, but mention clear session support or conflict in \`confirmations\` or \`qualityReason\`.
|
|
3733
|
+
- If \`marketContext.execution.binanceCoinbaseSpread.available=true\` and \`severity=elevated/wide\`, treat it as cross-exchange divergence or BTC liquidity risk. Do not use the spread as a standalone long/short signal, but reduce confidence or require more confirmation when the rest of the structure is weak or BTC context conflicts.
|
|
3734
|
+
- If \`marketContext.execution.binanceCoinbaseSpread\` is missing or \`available=false\`, do not infer anything from Binance/Coinbase spread and do not penalize the signal just because it is absent.
|
|
3735
|
+
- If \`marketContext.participation.trueDelta.available=true\`, use it as better participation evidence than OHLCV-derived proxy delta; still do not let delta override invalid price structure.
|
|
3736
|
+
- If \`marketContext.participation.tradeFlow.available=true\` and \`stale=false\`, use it as direct lower-timeframe participation evidence. Treat stale or missing tradeFlow as absent, not as negative evidence.
|
|
3737
|
+
- If \`marketContext.relative.marketBreadth.available=true\` and \`stale=false\`, use it as broad alt-market support/conflict. Breadth is contextual; do not let it override the target symbol structure.
|
|
3738
|
+
- If \`marketContext.relative.targetVsBtc.available=true\`, treat positive target/BTC ratio trend as support for alt LONGs and negative ratio trend as support for alt SHORTs; ignore it when the target structure is stronger and clearly explains the setup.
|
|
3739
|
+
- If \`marketContext.relative.btcAltRegime.available=true\` and \`stale=false\`, treat \`alt_lead\`/\`risk_on\` as broad support for alt LONGs and \`btc_lead\`/\`risk_off\` as pressure against alt LONGs or support for cautious alt SHORTs. Do not use it as a standalone entry reason.
|
|
3740
|
+
- If \`marketContext.relative.cmcGlobal.available=true\` and \`stale=false\`, use falling alt market cap/volume or rising BTC dominance as broad risk pressure for alt LONGs. Treat missing CMC history as absent context, not a bearish signal.
|
|
3741
|
+
- If \`marketContext.relative.cmcReferenceAssets.available=true\` and \`stale=false\`, use \`eth_led\` as broad support for ETH/high-beta alt strength and \`btc_led\`/\`thin\` as broad caution. Do not describe BTC/ETH reference history as target-symbol flow.
|
|
3742
|
+
- If \`marketContext.relative.cmcExchangeLiquidity.available=true\` and \`stale=false\`, treat \`contracting\`, \`thin\`, or \`concentrated\` as broad liquidity risk; \`expanding\` or \`balanced\` supports cleaner execution context but is not a standalone entry reason.
|
|
3743
|
+
- If \`marketContext.relative.cmcFearGreed.available=true\` and \`stale=false\`, use \`risk_on\` as broad support for LONGs and \`risk_off\`/\`capitulation\` as broad pressure. Treat \`euphoric\` as overheating/chase caution, not as standalone SHORT proof.
|
|
3744
|
+
- If \`marketContext.relative.cmcIndexes.available=true\` and \`stale=false\`, use \`top20_led\` as broad support for mega-cap leadership, \`large_cap_led\` as broader CMC100 participation, and \`risk_off\` as broad pressure. Do not use CMC index history as a standalone entry reason.
|
|
3745
|
+
- If \`marketContext.relative.referenceTradeFlow.available=true\`, treat BTC/ETH trade-flow as broad market context only. For alt symbols, do not describe it as the target coin's own flow.
|
|
3746
|
+
- If the current signal is not confirmed (\`direction=null\`), name the main reason briefly in \`comment\`.
|
|
3747
|
+
If you use the structured fields, include the main reason in \`qualityReason\` or \`triggerInvalidation\`.
|
|
3748
|
+
|
|
3749
|
+
Rules for \`direction\` / TP / SL:
|
|
3750
|
+
- \`direction = LONG\` only if the data confirms the existing LONG signal; \`SHORT\` only if the data confirms the existing SHORT signal; otherwise \`null\`.
|
|
3751
|
+
- For LONG, the expected relation is usually \`stopLossPrice < currentPrice < takeProfitPrice\`.
|
|
3752
|
+
- For SHORT, the expected relation is usually \`takeProfitPrice < currentPrice < stopLossPrice\`.
|
|
3753
|
+
- Do not optimize or recalculate TP/SL for a "better trade"; only assess whether the already supplied levels are coherent.
|
|
3754
|
+
- If \`direction = null\`, then \`takeProfitPrice = null\` and \`stopLossPrice = null\`.
|
|
3755
|
+
- If \`needRetest = false\`, then \`retestPrice = null\`.
|
|
3756
|
+
- If \`needRetest = true\`, \`retestPrice\` must be a finite number tied to a meaningful retest or breakout level.
|
|
3757
|
+
- Before responding, sanity-check the consistency of \`direction\`, TP/SL, and the current price.
|
|
3758
|
+
|
|
3759
|
+
Quality scale:
|
|
3760
|
+
- 1: poor or chaotic setup, strong conflicts, signal not structurally confirmed
|
|
3761
|
+
- 2: weak setup, few confirmations, more of a watch or reject
|
|
3762
|
+
- 3: average setup, some structure exists, but notable conflicts remain
|
|
3763
|
+
- 4: good setup, several confirmations, structure is mostly coherent
|
|
3764
|
+
- 5: very strong setup, clean structure, confirmations, and internally coherent levels
|
|
3765
|
+
|
|
3766
|
+
Requirements for useful structured analysis:
|
|
3767
|
+
- Include 2-4 concrete factors for or against confirmation in \`confirmations\`.
|
|
3768
|
+
- Explicitly mention the role of the key figure or structural state, for example breakout, retest, false break, touch, or lack of confirmation.
|
|
3769
|
+
- Explicitly mention BTC context as supportive, neutral, or conflicting.
|
|
3770
|
+
- Explain why the quality score is what it is.
|
|
3771
|
+
- If the signal is not confirmed (\`direction=null\`), state clearly what must change for confirmation.
|
|
3772
|
+
- In \`retestPlan\`, avoid technical placeholders like \`needRetest=false @ null\`; write a human explanation.
|
|
3773
|
+
- Do not simply restate JSON fields; add interpretation and decision logic.
|
|
3774
|
+
|
|
3775
|
+
Rules for using trimmed series (last 5 values):
|
|
3776
|
+
- Do not make strong long-term conclusions from only 5 points.
|
|
3777
|
+
- Use 4h and 1d series as brief context, not full history.
|
|
3778
|
+
- If the data is too limited for confidence, reduce quality and use cautious wording.
|
|
3779
|
+
|
|
3780
|
+
Short few-shot examples:
|
|
3781
|
+
{"direction":"LONG","quality":4,"needRetest":true,"retestPrice":100.2,"takeProfitPrice":101.5,"stopLossPrice":98.9,"setup":"Likely trendline breakout upward, but the signal still needs a level check for confirmation.","confirmations":"The coin shows momentum support without obvious overheating, but confirmation is not fully clean yet.","btcContext":"BTC is neutral-to-supportive and does not conflict with the current LONG signal.","retestPlan":"The key level is 100.2; holding above it would confirm the signal structure.","riskLevels":"The supplied TP and SL remain on the correct sides of the current price and still look internally coherent.","qualityReason":"Quality=4 because the structure is solid, but an extra level confirmation is still preferable.","triggerInvalidation":"The structure confirms on a hold above the level and weakens on a move back under the line."}
|
|
3782
|
+
{"direction":null,"quality":2,"needRetest":false,"retestPrice":null,"takeProfitPrice":null,"stopLossPrice":null,"setup":"Touch or noise around the trendline without a convincing breakout.","confirmations":"Indicators are mixed and do not provide strong structural support.","btcContext":"BTC is either conflicting or not supportive of the current thesis.","retestPlan":"It is too early to define an extra level because a quality breakout is not present yet.","riskLevels":"The supplied levels should not be treated as confirmed while the structure remains weak.","qualityReason":"Quality=2 because timing is weak and confirmations are limited.","triggerInvalidation":"Wait for a clear breakout and confirmation from both the coin and BTC."}
|
|
3783
|
+
|
|
3784
|
+
Return only the JSON object, with no extra characters.
|
|
3785
|
+
${signal ? buildAiSystemPromptAddonByStrategy(signal) : ""}
|
|
3786
|
+
`;
|
|
3787
|
+
var buildAiPayload = (signal) => buildAiPayloadByStrategy(signal);
|
|
3788
|
+
var getDeterministicAiGateContext = (payload) => {
|
|
3789
|
+
const additionalIndicators = asRecord(payload.additionalIndicators);
|
|
3790
|
+
const candidates = [
|
|
3791
|
+
additionalIndicators,
|
|
3792
|
+
...Object.values(additionalIndicators ?? {}).map(asRecord)
|
|
3793
|
+
].filter((value) => Boolean(value));
|
|
3794
|
+
return candidates.find(
|
|
3795
|
+
(candidate) => Array.isArray(candidate.approvalBlockReasons) || Array.isArray(candidate.riskAnnotations) || Array.isArray(candidate.structuralHardBlockReasons) || typeof candidate.approvalAllowedNow === "boolean"
|
|
3796
|
+
) ?? null;
|
|
3231
3797
|
};
|
|
3232
|
-
var
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3798
|
+
var buildAiHumanPrompt = (signal, payload = buildAiPayload(signal)) => `
|
|
3799
|
+
Analyze the already computed internal signal for ${signal.symbol}. The original signal direction is ${signal.direction}.
|
|
3800
|
+
This is a structure-classification and audit task, not execution advice. Determine whether the current structure confirms the existing signal, how structurally coherent it is right now, whether an extra confirmation level is needed, and whether the already supplied levels in \`payload.signal.prices\` still look internally coherent. Do not replace the original thesis with a new one and do not invent new levels; return only the requested JSON.
|
|
3801
|
+
|
|
3802
|
+
Trade payload:
|
|
3803
|
+
${JSON.stringify(payload)}
|
|
3804
|
+
${buildAiHumanPromptAddonByStrategy(signal, payload)}
|
|
3805
|
+
`;
|
|
3806
|
+
var runAiPrompt = async ({ systemPrompt, humanPrompt }, options = {}) => {
|
|
3807
|
+
const settings = await getAiUserSettings(options.userName);
|
|
3808
|
+
const responseLanguage = (0, import_aiLanguages2.getAiResponseLanguagePromptName)(
|
|
3809
|
+
settings.AI_RESPONSE_LANGUAGE || import_aiLanguages2.DEFAULT_AI_RESPONSE_LANGUAGE
|
|
3240
3810
|
);
|
|
3241
|
-
const
|
|
3242
|
-
|
|
3243
|
-
|
|
3811
|
+
const response = await invokeAiPromptChat({
|
|
3812
|
+
userName: options.userName,
|
|
3813
|
+
model: options.model,
|
|
3814
|
+
messages: [
|
|
3815
|
+
{ role: "system", content: systemPrompt },
|
|
3816
|
+
{
|
|
3817
|
+
role: "system",
|
|
3818
|
+
content: `Write all user-visible text fields in ${responseLanguage}. Keep field names and JSON syntax unchanged.`
|
|
3819
|
+
},
|
|
3820
|
+
{ role: "user", content: humanPrompt }
|
|
3821
|
+
]
|
|
3822
|
+
});
|
|
3823
|
+
const parsed = parseAIResponse(response.content);
|
|
3824
|
+
const normalized = normalizeAnalysis(parsed);
|
|
3825
|
+
if (!options.signal) {
|
|
3826
|
+
return normalized;
|
|
3244
3827
|
}
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3828
|
+
return postProcessAiAnalysisByStrategy(
|
|
3829
|
+
options.signal,
|
|
3830
|
+
normalized,
|
|
3831
|
+
options.payload
|
|
3248
3832
|
);
|
|
3249
|
-
if (!existing || existing.status !== "active") {
|
|
3250
|
-
await (0, import_redis2.delKey)(
|
|
3251
|
-
import_redis2.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
|
|
3252
|
-
);
|
|
3253
|
-
return null;
|
|
3254
|
-
}
|
|
3255
|
-
return existing;
|
|
3256
3833
|
};
|
|
3257
|
-
var
|
|
3258
|
-
const
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
}
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3834
|
+
var runAiPromptLocal = async (signal, options = {}) => {
|
|
3835
|
+
const payload = options.payload ?? buildAiPayload(signal);
|
|
3836
|
+
const gateContext = getDeterministicAiGateContext(payload);
|
|
3837
|
+
const signalDirection = getSignalDirection(signal);
|
|
3838
|
+
const deterministicQuality = getDeterministicQuality(gateContext);
|
|
3839
|
+
const approvalAllowedNow = typeof gateContext?.approvalAllowedNow === "boolean" ? gateContext.approvalAllowedNow : deterministicQuality >= 4;
|
|
3840
|
+
return postProcessLocalAiAnalysisByStrategy(
|
|
3841
|
+
signal,
|
|
3842
|
+
{
|
|
3843
|
+
direction: approvalAllowedNow ? signalDirection : null,
|
|
3844
|
+
quality: deterministicQuality,
|
|
3845
|
+
needRetest: !approvalAllowedNow,
|
|
3846
|
+
retestPrice: null,
|
|
3847
|
+
takeProfitPrice: approvalAllowedNow ? signal.prices?.takeProfitPrice ?? null : null,
|
|
3848
|
+
stopLossPrice: approvalAllowedNow ? signal.prices?.stopLossPrice ?? null : null
|
|
3849
|
+
},
|
|
3850
|
+
payload
|
|
3851
|
+
);
|
|
3852
|
+
};
|
|
3853
|
+
var askAI = async (signal, options = {}) => {
|
|
3854
|
+
const { symbol } = signal;
|
|
3855
|
+
const payload = buildAiPayload(signal);
|
|
3856
|
+
const content = await runAiPrompt(
|
|
3857
|
+
{
|
|
3858
|
+
systemPrompt: buildAiSystemPrompt(signal),
|
|
3859
|
+
humanPrompt: buildAiHumanPrompt(signal, payload)
|
|
3860
|
+
},
|
|
3861
|
+
{
|
|
3862
|
+
...options,
|
|
3863
|
+
signal,
|
|
3864
|
+
payload
|
|
3865
|
+
}
|
|
3866
|
+
);
|
|
3867
|
+
await (0, import_redis3.setData)(import_redis3.redisKeys.analysis(symbol, signal.signalId), content);
|
|
3868
|
+
return content;
|
|
3869
|
+
};
|
|
3870
|
+
|
|
3871
|
+
// src/strategyAdapters/ml.ts
|
|
3872
|
+
var defaultMlAdapter = {
|
|
3873
|
+
normalizeStrategyConfig: (strategyConfig) => strategyConfig
|
|
3874
|
+
};
|
|
3875
|
+
var getStrategyMlAdapter = (strategy, profileId) => {
|
|
3876
|
+
const strategyAdapter = getStrategyProfileMlAdapter(
|
|
3877
|
+
getStrategyManifest(strategy),
|
|
3878
|
+
profileId
|
|
3879
|
+
);
|
|
3880
|
+
if (!strategyAdapter) return defaultMlAdapter;
|
|
3881
|
+
return {
|
|
3882
|
+
...defaultMlAdapter,
|
|
3883
|
+
...strategyAdapter
|
|
3884
|
+
};
|
|
3885
|
+
};
|
|
3886
|
+
|
|
3887
|
+
// src/mlPayload.ts
|
|
3888
|
+
var normalizeStrategyConfig = (strategyConfig, strategyName, profileId) => {
|
|
3889
|
+
return getStrategyMlAdapter(
|
|
3890
|
+
strategyName,
|
|
3891
|
+
profileId
|
|
3892
|
+
).normalizeStrategyConfig?.(strategyConfig);
|
|
3893
|
+
};
|
|
3894
|
+
var buildMlPayload = (payload) => {
|
|
3895
|
+
const strategyName = payload.signal?.strategy ?? payload.context?.strategyName;
|
|
3896
|
+
const profileId = payload.signal?.policyProfileId;
|
|
3897
|
+
const mlAdapter = getStrategyMlAdapter(strategyName, profileId);
|
|
3898
|
+
const normalizedSignal = mlAdapter.normalizeSignal?.(payload.signal) ?? payload.signal;
|
|
3899
|
+
const nextSignal = {
|
|
3900
|
+
...normalizedSignal,
|
|
3901
|
+
indicators: {
|
|
3902
|
+
...normalizedSignal?.indicators ?? {}
|
|
3903
|
+
}
|
|
3904
|
+
};
|
|
3905
|
+
const nextContext = payload.context ? {
|
|
3906
|
+
...payload.context,
|
|
3907
|
+
strategyConfig: normalizeStrategyConfig(
|
|
3908
|
+
payload.context.strategyConfig,
|
|
3909
|
+
strategyName,
|
|
3910
|
+
profileId
|
|
3911
|
+
)
|
|
3912
|
+
} : void 0;
|
|
3913
|
+
return {
|
|
3914
|
+
signal: nextSignal,
|
|
3915
|
+
context: nextContext
|
|
3302
3916
|
};
|
|
3303
|
-
const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
3304
|
-
const closeDayKey = (0, import_time.getRuntimeStorageDayKey)(next.exitTimestamp);
|
|
3305
|
-
try {
|
|
3306
|
-
await Promise.all([
|
|
3307
|
-
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, orderId), next, {
|
|
3308
|
-
expire: import_constants.TTL_1M
|
|
3309
|
-
}),
|
|
3310
|
-
(0, import_redis2.setHashJsonField)(
|
|
3311
|
-
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
3312
|
-
orderId,
|
|
3313
|
-
next,
|
|
3314
|
-
{ expire: import_constants.TTL_1M }
|
|
3315
|
-
),
|
|
3316
|
-
(0, import_redis2.setHashJsonField)(
|
|
3317
|
-
import_redis2.redisKeys.runtimeClosedTradeBucket(userName, closeDayKey),
|
|
3318
|
-
orderId,
|
|
3319
|
-
next,
|
|
3320
|
-
{ expire: import_constants.TTL_1M }
|
|
3321
|
-
),
|
|
3322
|
-
(0, import_redis2.delKey)(
|
|
3323
|
-
import_redis2.redisKeys.runtimeActiveTrade(
|
|
3324
|
-
userName,
|
|
3325
|
-
symbol,
|
|
3326
|
-
deploymentId ?? accountId
|
|
3327
|
-
)
|
|
3328
|
-
)
|
|
3329
|
-
]);
|
|
3330
|
-
} catch (error) {
|
|
3331
|
-
import_logger3.logger.error(
|
|
3332
|
-
"runtime trade close journal failed: %s %s",
|
|
3333
|
-
symbol,
|
|
3334
|
-
error?.message || String(error)
|
|
3335
|
-
);
|
|
3336
|
-
}
|
|
3337
|
-
return next;
|
|
3338
3917
|
};
|
|
3339
3918
|
|
|
3340
3919
|
// src/strategyHelpers/marketContextStages.ts
|
|
@@ -3346,7 +3925,7 @@ var import_logger4 = require("@tradejs/infra/logger");
|
|
|
3346
3925
|
var import_strategies = require("@tradejs/core/strategies");
|
|
3347
3926
|
|
|
3348
3927
|
// src/binanceBreadthUniverses.ts
|
|
3349
|
-
var
|
|
3928
|
+
var import_node_crypto3 = require("crypto");
|
|
3350
3929
|
|
|
3351
3930
|
// src/config/binanceBreadthUniverses.json
|
|
3352
3931
|
var binanceBreadthUniverses_default = {
|
|
@@ -3585,7 +4164,7 @@ var BINANCE_BREADTH_UNIVERSE_KEYS = [
|
|
|
3585
4164
|
"top50",
|
|
3586
4165
|
"top100"
|
|
3587
4166
|
];
|
|
3588
|
-
var fingerprint = (value, length) => (0,
|
|
4167
|
+
var fingerprint = (value, length) => (0, import_node_crypto3.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, length);
|
|
3589
4168
|
var normalizeSymbols = (symbols) => symbols.map((symbol) => symbol.trim().toUpperCase()).filter(Boolean);
|
|
3590
4169
|
var buildBinanceBreadthUniverseSnapshot = ({
|
|
3591
4170
|
rankedSymbols,
|
|
@@ -4609,7 +5188,7 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
4609
5188
|
};
|
|
4610
5189
|
|
|
4611
5190
|
// src/strategyHelpers/derivativesContext.ts
|
|
4612
|
-
var
|
|
5191
|
+
var import_indicators3 = require("@tradejs/core/indicators");
|
|
4613
5192
|
var import_data2 = require("@tradejs/core/data");
|
|
4614
5193
|
var import_strategies3 = require("@tradejs/core/strategies");
|
|
4615
5194
|
var import_constants2 = require("@tradejs/core/constants");
|
|
@@ -4643,7 +5222,7 @@ var parseLookbackMs = () => {
|
|
|
4643
5222
|
};
|
|
4644
5223
|
var withHourlyFallbackRows = (rowsByInterval) => ({
|
|
4645
5224
|
"15m": rowsByInterval["15m"] ?? [],
|
|
4646
|
-
"1h": (0,
|
|
5225
|
+
"1h": (0, import_indicators3.buildCoinalyzeHourlyRowsWithFallback)({
|
|
4647
5226
|
rows15m: rowsByInterval["15m"],
|
|
4648
5227
|
fallbackRows1h: rowsByInterval["1h"]
|
|
4649
5228
|
})
|
|
@@ -4771,7 +5350,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
4771
5350
|
const targetSymbol = normalizeSymbol(signal.symbol);
|
|
4772
5351
|
const lookbackMs = parseLookbackMs();
|
|
4773
5352
|
const decisionTimeMs = signal.timestamp + (0, import_data2.intervalToMs)(signal.interval);
|
|
4774
|
-
const derivativesEndMs = (0,
|
|
5353
|
+
const derivativesEndMs = (0, import_indicators3.getLastClosedDerivativesBarStartMs)(
|
|
4775
5354
|
decisionTimeMs,
|
|
4776
5355
|
"15m"
|
|
4777
5356
|
);
|
|
@@ -4786,7 +5365,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
4786
5365
|
});
|
|
4787
5366
|
return [
|
|
4788
5367
|
symbol,
|
|
4789
|
-
(0,
|
|
5368
|
+
(0, import_indicators3.buildDerivativesContext)({
|
|
4790
5369
|
symbol,
|
|
4791
5370
|
direction: signal.direction,
|
|
4792
5371
|
timestamp: derivativesEndMs,
|
|
@@ -4813,7 +5392,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
4813
5392
|
lookbackMs,
|
|
4814
5393
|
...params.abortSignal ? { signal: params.abortSignal } : {}
|
|
4815
5394
|
});
|
|
4816
|
-
const context = (0,
|
|
5395
|
+
const context = (0, import_indicators3.buildDerivativesContext)({
|
|
4817
5396
|
symbol: targetSymbol,
|
|
4818
5397
|
direction: signal.direction,
|
|
4819
5398
|
timestamp: derivativesEndMs,
|
|
@@ -4861,7 +5440,7 @@ var import_hyperliquidWhales2 = require("@tradejs/infra/timescale/hyperliquidWha
|
|
|
4861
5440
|
var import_logger7 = require("@tradejs/infra/logger");
|
|
4862
5441
|
|
|
4863
5442
|
// src/hyperliquidWhaleUniverse.ts
|
|
4864
|
-
var
|
|
5443
|
+
var import_node_crypto4 = require("crypto");
|
|
4865
5444
|
|
|
4866
5445
|
// src/config/hyperliquidPerpUniverse.json
|
|
4867
5446
|
var hyperliquidPerpUniverse_default = {
|
|
@@ -5033,7 +5612,7 @@ var hyperliquidWhales_default = {
|
|
|
5033
5612
|
};
|
|
5034
5613
|
|
|
5035
5614
|
// src/hyperliquidWhaleUniverse.ts
|
|
5036
|
-
var fingerprint2 = (value) => (0,
|
|
5615
|
+
var fingerprint2 = (value) => (0, import_node_crypto4.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
|
|
5037
5616
|
var validateUnique = (values, label) => {
|
|
5038
5617
|
if (new Set(values).size !== values.length) {
|
|
5039
5618
|
throw new Error(`Hyperliquid ${label} snapshot contains duplicates`);
|
|
@@ -5520,6 +6099,7 @@ var enrichSignalWithMarketContextStages = async ({
|
|
|
5520
6099
|
})
|
|
5521
6100
|
});
|
|
5522
6101
|
}
|
|
6102
|
+
await enrichSignalWithTickerProfile(signal);
|
|
5523
6103
|
const results = [];
|
|
5524
6104
|
for (const stage of stages) {
|
|
5525
6105
|
if (abortSignal?.aborted) break;
|
|
@@ -5567,12 +6147,12 @@ var findReplayAiAnalysis = ({
|
|
|
5567
6147
|
direction,
|
|
5568
6148
|
ai
|
|
5569
6149
|
}) => {
|
|
5570
|
-
const
|
|
5571
|
-
if (!Array.isArray(
|
|
6150
|
+
const snapshots2 = ai?.replayAnalyses;
|
|
6151
|
+
if (!Array.isArray(snapshots2) || !snapshots2.length) {
|
|
5572
6152
|
return void 0;
|
|
5573
6153
|
}
|
|
5574
6154
|
let best = null;
|
|
5575
|
-
for (const snapshot2 of
|
|
6155
|
+
for (const snapshot2 of snapshots2) {
|
|
5576
6156
|
if (snapshot2.symbol !== signal.symbol || snapshot2.direction !== direction || snapshot2.strategy && snapshot2.strategy !== signal.strategy) {
|
|
5577
6157
|
continue;
|
|
5578
6158
|
}
|
|
@@ -5732,11 +6312,13 @@ var executeEntryOrder = async ({
|
|
|
5732
6312
|
beforePlaceOrder,
|
|
5733
6313
|
recordRuntimeTrade = true,
|
|
5734
6314
|
leverage,
|
|
5735
|
-
executionLimits
|
|
6315
|
+
executionLimits,
|
|
6316
|
+
markPortfolioSubmitted,
|
|
6317
|
+
rejectPortfolioSubmission
|
|
5736
6318
|
}) => {
|
|
5737
6319
|
await beforePlaceOrder?.();
|
|
5738
6320
|
const previousPosition = positionIntent === "increase" ? await connector.getPosition(symbol) : null;
|
|
5739
|
-
|
|
6321
|
+
let orderId = signal.orderId || createRuntimeOrderId(signal.strategy);
|
|
5740
6322
|
const signalTimestamp = signal.timestamp;
|
|
5741
6323
|
const signalClosePrice = currentPrice;
|
|
5742
6324
|
signal.orderId = orderId;
|
|
@@ -5805,9 +6387,16 @@ var executeEntryOrder = async ({
|
|
|
5805
6387
|
...typeof leverage === "number" && Number.isFinite(leverage) ? { leverage } : {},
|
|
5806
6388
|
orderId,
|
|
5807
6389
|
signal,
|
|
5808
|
-
...
|
|
6390
|
+
...rejectPortfolioSubmission ? { onRejected: rejectPortfolioSubmission } : {},
|
|
6391
|
+
...beforeSubmit || markPortfolioSubmitted ? {
|
|
6392
|
+
beforeSubmit: async (context) => {
|
|
6393
|
+
await beforeSubmit?.(context);
|
|
6394
|
+
return markPortfolioSubmitted?.(context.qty);
|
|
6395
|
+
}
|
|
6396
|
+
} : {}
|
|
5809
6397
|
});
|
|
5810
6398
|
const orderAckTime = Date.now();
|
|
6399
|
+
if (markPortfolioSubmitted && signal.orderId) orderId = signal.orderId;
|
|
5811
6400
|
const placedQty = typeof signal.orderQty === "number" && Number.isFinite(signal.orderQty) && signal.orderQty > 0 ? signal.orderQty : qty;
|
|
5812
6401
|
const currentPosition = await connector.getPosition(symbol);
|
|
5813
6402
|
const fillTime = Date.now();
|
|
@@ -6793,7 +7382,7 @@ var handleProtectDecision = async ({
|
|
|
6793
7382
|
}
|
|
6794
7383
|
return decision.code;
|
|
6795
7384
|
};
|
|
6796
|
-
var
|
|
7385
|
+
var executeAdmittedEntryDecision = async ({
|
|
6797
7386
|
connector,
|
|
6798
7387
|
symbol,
|
|
6799
7388
|
decision,
|
|
@@ -6807,7 +7396,9 @@ var executeEntryDecision = async ({
|
|
|
6807
7396
|
ai,
|
|
6808
7397
|
recordRuntimeJournal,
|
|
6809
7398
|
invokeStageHooks,
|
|
6810
|
-
notifyRuntimeError
|
|
7399
|
+
notifyRuntimeError,
|
|
7400
|
+
markPortfolioSubmitted,
|
|
7401
|
+
rejectPortfolioSubmission
|
|
6811
7402
|
}) => {
|
|
6812
7403
|
const signal = decision.signal;
|
|
6813
7404
|
const beforePlaceOrder = async () => {
|
|
@@ -6854,6 +7445,8 @@ var executeEntryDecision = async ({
|
|
|
6854
7445
|
...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {},
|
|
6855
7446
|
signal,
|
|
6856
7447
|
beforePlaceOrder,
|
|
7448
|
+
markPortfolioSubmitted,
|
|
7449
|
+
rejectPortfolioSubmission,
|
|
6857
7450
|
recordRuntimeTrade: recordRuntimeJournal,
|
|
6858
7451
|
executionLimits: resolveEntryExecutionLimits(
|
|
6859
7452
|
hookCtx.strategyConfig,
|
|
@@ -6994,6 +7587,37 @@ var executeEntryDecision = async ({
|
|
|
6994
7587
|
}
|
|
6995
7588
|
return signal ?? decision.code;
|
|
6996
7589
|
};
|
|
7590
|
+
async function executeEntryDecision(params) {
|
|
7591
|
+
if (!params.portfolio) return executeAdmittedEntryDecision(params);
|
|
7592
|
+
const { decision, portfolio } = params;
|
|
7593
|
+
const signal = decision.signal;
|
|
7594
|
+
if (!signal || decision.orderPlan.positionIntent === "increase")
|
|
7595
|
+
return "PORTFOLIO_REQUIRES_NEW_SIGNAL_POSITION";
|
|
7596
|
+
signal.orderId ??= createRuntimeOrderId(signal.strategy);
|
|
7597
|
+
const result = await portfolio.submit(
|
|
7598
|
+
params.portfolioTime ?? decision.entryContext.timestamp,
|
|
7599
|
+
{
|
|
7600
|
+
id: signal.orderId,
|
|
7601
|
+
signalId: signal.signalId,
|
|
7602
|
+
strategy: signal.strategy,
|
|
7603
|
+
symbol: params.symbol,
|
|
7604
|
+
direction: decision.entryContext.direction,
|
|
7605
|
+
qty: decision.orderPlan.qty,
|
|
7606
|
+
timestamp: decision.entryContext.timestamp
|
|
7607
|
+
},
|
|
7608
|
+
(markPortfolioSubmitted, rejectPortfolioSubmission) => executeAdmittedEntryDecision({
|
|
7609
|
+
...params,
|
|
7610
|
+
markPortfolioSubmitted,
|
|
7611
|
+
rejectPortfolioSubmission
|
|
7612
|
+
})
|
|
7613
|
+
);
|
|
7614
|
+
if (result.reason) {
|
|
7615
|
+
signal.orderStatus = "failed";
|
|
7616
|
+
signal.orderFailureReason = result.reason;
|
|
7617
|
+
return signal;
|
|
7618
|
+
}
|
|
7619
|
+
return result.value;
|
|
7620
|
+
}
|
|
6997
7621
|
|
|
6998
7622
|
// src/strategyRuntime.ts
|
|
6999
7623
|
var cloneWithPropertyDescriptors = (value) => Object.create(
|
|
@@ -7021,6 +7645,7 @@ var createStrategyRuntime = ({
|
|
|
7021
7645
|
};
|
|
7022
7646
|
const creator = async ({
|
|
7023
7647
|
userName,
|
|
7648
|
+
connectorName,
|
|
7024
7649
|
config: baseConfig,
|
|
7025
7650
|
symbol,
|
|
7026
7651
|
universe: requestedUniverse,
|
|
@@ -7059,6 +7684,37 @@ var createStrategyRuntime = ({
|
|
|
7059
7684
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
7060
7685
|
const projectHooks = projectConfig.hooks;
|
|
7061
7686
|
const env = String(config.ENV ?? "BACKTEST");
|
|
7687
|
+
let portfolioMarket;
|
|
7688
|
+
const portfolio = getPortfolioRuntime({
|
|
7689
|
+
symbol,
|
|
7690
|
+
canClose: async (position, intent, asOf) => {
|
|
7691
|
+
if (!portfolioMarket || portfolioMarket.candle.timestamp > asOf || !isRuntimeOrderExecutionEnabled({ config, env, connector }))
|
|
7692
|
+
return false;
|
|
7693
|
+
const gate = await authorizeClose(
|
|
7694
|
+
{
|
|
7695
|
+
kind: "exit",
|
|
7696
|
+
code: intent.reason,
|
|
7697
|
+
closePlan: {
|
|
7698
|
+
direction: position.direction,
|
|
7699
|
+
price: position.markPrice,
|
|
7700
|
+
timestamp: asOf
|
|
7701
|
+
}
|
|
7702
|
+
},
|
|
7703
|
+
portfolioMarket,
|
|
7704
|
+
position.strategy
|
|
7705
|
+
);
|
|
7706
|
+
return gate?.allow !== false;
|
|
7707
|
+
},
|
|
7708
|
+
connectorName,
|
|
7709
|
+
value: baseConfig?.PORTFOLIO_POLICY ?? config.PORTFOLIO_POLICY,
|
|
7710
|
+
strategy: strategyName,
|
|
7711
|
+
connector,
|
|
7712
|
+
env,
|
|
7713
|
+
userName,
|
|
7714
|
+
accountId,
|
|
7715
|
+
deploymentId
|
|
7716
|
+
});
|
|
7717
|
+
const portfolioContext = portfolio ? (now2) => isRuntimeOrderExecutionEnabled({ config, env, connector }) ? portfolio.tick(now2) : portfolio.context(now2) : void 0;
|
|
7062
7718
|
const researchPolicyValue = baseConfig?.RESEARCH_ENTRY_POLICY ?? config.RESEARCH_ENTRY_POLICY;
|
|
7063
7719
|
const researchEntryPolicy = researchPolicyValue == null ? void 0 : parseResearchEntryPolicy(researchPolicyValue);
|
|
7064
7720
|
if (researchEntryPolicy && (env !== "BACKTEST" || Reflect.get(connector, "__tradejsTestConnector") !== true))
|
|
@@ -7223,7 +7879,10 @@ var createStrategyRuntime = ({
|
|
|
7223
7879
|
return invokeHook(stage, hook, params, errorContext);
|
|
7224
7880
|
};
|
|
7225
7881
|
const invokeGateHooks = async (stage, hook, params, errorContext = {}) => {
|
|
7226
|
-
const failureResult =
|
|
7882
|
+
const failureResult = {
|
|
7883
|
+
allow: false,
|
|
7884
|
+
reason: "ERROR"
|
|
7885
|
+
};
|
|
7227
7886
|
const projectResults = await invokeProjectHooks(
|
|
7228
7887
|
stage,
|
|
7229
7888
|
params,
|
|
@@ -7244,6 +7903,12 @@ var createStrategyRuntime = ({
|
|
|
7244
7903
|
failureResult
|
|
7245
7904
|
);
|
|
7246
7905
|
};
|
|
7906
|
+
const authorizeClose = (decision, market, owner = strategyName) => invokeGateHooks(
|
|
7907
|
+
"beforeClosePosition",
|
|
7908
|
+
resolveManifest(owner)?.hooks?.beforeClosePosition,
|
|
7909
|
+
{ ctx: getHookCtx(owner), market, decision },
|
|
7910
|
+
{ decision, market }
|
|
7911
|
+
);
|
|
7247
7912
|
const applyProjectAfterCoreDecisionHooks = async ({
|
|
7248
7913
|
hookCtx,
|
|
7249
7914
|
market,
|
|
@@ -7357,6 +8022,20 @@ var createStrategyRuntime = ({
|
|
|
7357
8022
|
interval: decisionInterval
|
|
7358
8023
|
}) => {
|
|
7359
8024
|
if (!baseContext) return void 0;
|
|
8025
|
+
const profileSource = strategyManifest?.contextRequirements?.tickerProfiles;
|
|
8026
|
+
if (profileSource) {
|
|
8027
|
+
const profile = await loadTickerProfileContext({
|
|
8028
|
+
...profileSource,
|
|
8029
|
+
projectRoot,
|
|
8030
|
+
symbol: decisionSymbol,
|
|
8031
|
+
timestamp: candle.timestamp
|
|
8032
|
+
});
|
|
8033
|
+
if (!profile)
|
|
8034
|
+
throw new Error(
|
|
8035
|
+
"Required ticker profile is unavailable at decision time"
|
|
8036
|
+
);
|
|
8037
|
+
baseContext = withTickerProfile(baseContext, profile);
|
|
8038
|
+
}
|
|
7360
8039
|
if (!coreContextRequirements.has("hyperliquidWhales")) {
|
|
7361
8040
|
return baseContext;
|
|
7362
8041
|
}
|
|
@@ -7524,6 +8203,8 @@ var createStrategyRuntime = ({
|
|
|
7524
8203
|
runtime: pending2.runtime
|
|
7525
8204
|
});
|
|
7526
8205
|
return executeEntryDecision({
|
|
8206
|
+
portfolio,
|
|
8207
|
+
portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
|
|
7527
8208
|
connector,
|
|
7528
8209
|
symbol,
|
|
7529
8210
|
decision: pending2.decision,
|
|
@@ -7541,6 +8222,11 @@ var createStrategyRuntime = ({
|
|
|
7541
8222
|
});
|
|
7542
8223
|
};
|
|
7543
8224
|
const runWithDecisionOverride = async (candle, btcCandle, options = {}) => {
|
|
8225
|
+
portfolioMarket = { candle, btcCandle };
|
|
8226
|
+
if (portfolioContext)
|
|
8227
|
+
await portfolioContext(
|
|
8228
|
+
env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now()
|
|
8229
|
+
);
|
|
7544
8230
|
appendCurrentMarketData(candle, btcCandle, options.ethCandle);
|
|
7545
8231
|
const ethCandle = resolveEthCandle(candle, options.ethCandle);
|
|
7546
8232
|
indicatorsState.setCurrentBar(candle, btcCandle, ethCandle);
|
|
@@ -7643,15 +8329,10 @@ var createStrategyRuntime = ({
|
|
|
7643
8329
|
if (!makeOrdersEnabled) {
|
|
7644
8330
|
return decision.code;
|
|
7645
8331
|
}
|
|
7646
|
-
const closeGate = await
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
ctx: decisionHookCtx,
|
|
7651
|
-
market,
|
|
7652
|
-
decision
|
|
7653
|
-
},
|
|
7654
|
-
{ decision, market }
|
|
8332
|
+
const closeGate = await authorizeClose(
|
|
8333
|
+
decision,
|
|
8334
|
+
market,
|
|
8335
|
+
decisionHookCtx.strategyName
|
|
7655
8336
|
);
|
|
7656
8337
|
if (closeGate?.allow === false) {
|
|
7657
8338
|
return closeGate.reason ? `CLOSE_BLOCKED_BY_HOOK:${closeGate.reason}` : "CLOSE_BLOCKED_BY_HOOK";
|
|
@@ -7711,6 +8392,13 @@ var createStrategyRuntime = ({
|
|
|
7711
8392
|
});
|
|
7712
8393
|
const signal = decision.signal;
|
|
7713
8394
|
if (signal) {
|
|
8395
|
+
if (portfolio)
|
|
8396
|
+
signal.additionalIndicators = {
|
|
8397
|
+
...signal.additionalIndicators,
|
|
8398
|
+
portfolioContext: await portfolioContext(
|
|
8399
|
+
env === "BACKTEST" || env === "PARITY" ? decision.entryContext.timestamp : Date.now()
|
|
8400
|
+
)
|
|
8401
|
+
};
|
|
7714
8402
|
if (runtimeLineage) signal.runtimeLineage = runtimeLineage;
|
|
7715
8403
|
if (universe) signal.universe = universe;
|
|
7716
8404
|
if (assetClass) signal.assetClass = assetClass;
|
|
@@ -7950,6 +8638,8 @@ var createStrategyRuntime = ({
|
|
|
7950
8638
|
});
|
|
7951
8639
|
}
|
|
7952
8640
|
return executeEntryDecision({
|
|
8641
|
+
portfolio,
|
|
8642
|
+
portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
|
|
7953
8643
|
connector,
|
|
7954
8644
|
symbol,
|
|
7955
8645
|
decision,
|