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